| 1 | # Copyright Vladimir Prus 2002-2006. |
|---|
| 2 | # Copyright Dave Abrahams 2003-2004. |
|---|
| 3 | # Copyright Rene Rivera 2003-2006. |
|---|
| 4 | # |
|---|
| 5 | # Distributed under the Boost Software License, Version 1.0. |
|---|
| 6 | # (See accompanying file LICENSE_1_0.txt or copy at |
|---|
| 7 | # http://www.boost.org/LICENSE_1_0.txt) |
|---|
| 8 | |
|---|
| 9 | # Performs various path manipulations. Path are always in a 'normilized' |
|---|
| 10 | # representation. In it, a path may be either: |
|---|
| 11 | # |
|---|
| 12 | # - '.', or |
|---|
| 13 | # |
|---|
| 14 | # - ['/'] [ ( '..' '/' )* (token '/')* token ] |
|---|
| 15 | # |
|---|
| 16 | # In plain english, path can be rooted, '..' elements are allowed only |
|---|
| 17 | # at the beginning, and it never ends in slash, except for path consisting |
|---|
| 18 | # of slash only. |
|---|
| 19 | |
|---|
| 20 | import modules ; |
|---|
| 21 | import sequence ; |
|---|
| 22 | import regex ; |
|---|
| 23 | import errors : error ; |
|---|
| 24 | import set ; |
|---|
| 25 | |
|---|
| 26 | |
|---|
| 27 | os = [ modules.peek : OS ] ; |
|---|
| 28 | if [ modules.peek : UNIX ] |
|---|
| 29 | { |
|---|
| 30 | local uname = [ modules.peek : JAMUNAME ] ; |
|---|
| 31 | switch $(uname) |
|---|
| 32 | { |
|---|
| 33 | case CYGWIN* : |
|---|
| 34 | os = CYGWIN ; |
|---|
| 35 | |
|---|
| 36 | case * : |
|---|
| 37 | os = UNIX ; |
|---|
| 38 | } |
|---|
| 39 | } |
|---|
| 40 | |
|---|
| 41 | # |
|---|
| 42 | # Converts the native path into normalized form. |
|---|
| 43 | # |
|---|
| 44 | rule make ( native ) |
|---|
| 45 | { |
|---|
| 46 | return [ make-$(os) $(native) ] ; |
|---|
| 47 | } |
|---|
| 48 | |
|---|
| 49 | # |
|---|
| 50 | # Builds native representation of the path. |
|---|
| 51 | # |
|---|
| 52 | rule native ( path ) |
|---|
| 53 | { |
|---|
| 54 | return [ native-$(os) $(path) ] ; |
|---|
| 55 | } |
|---|
| 56 | |
|---|
| 57 | # |
|---|
| 58 | # Tests if a path is rooted. |
|---|
| 59 | # |
|---|
| 60 | rule is-rooted ( path ) |
|---|
| 61 | { |
|---|
| 62 | return [ MATCH "^(/)" : $(path) ] ; |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | # |
|---|
| 66 | # Tests if a path has a parent. |
|---|
| 67 | # |
|---|
| 68 | rule has-parent ( path ) |
|---|
| 69 | { |
|---|
| 70 | if $(path) != / { |
|---|
| 71 | return 1 ; |
|---|
| 72 | } else { |
|---|
| 73 | return ; |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | # |
|---|
| 78 | # Returns the path without any directory components. |
|---|
| 79 | # |
|---|
| 80 | rule basename ( path ) |
|---|
| 81 | { |
|---|
| 82 | return [ MATCH "([^/]+)$" : $(path) ] ; |
|---|
| 83 | } |
|---|
| 84 | |
|---|
| 85 | # |
|---|
| 86 | # Returns parent directory of the path. If no parent exists, error is issued. |
|---|
| 87 | # |
|---|
| 88 | rule parent ( path ) |
|---|
| 89 | { |
|---|
| 90 | if [ has-parent $(path) ] { |
|---|
| 91 | |
|---|
| 92 | if $(path) = . { |
|---|
| 93 | return .. ; |
|---|
| 94 | } else { |
|---|
| 95 | |
|---|
| 96 | # Strip everything at the end of path up to and including |
|---|
| 97 | # the last slash |
|---|
| 98 | local result = [ regex.match "((.*)/)?([^/]+)" : $(path) : 2 3 ] ; |
|---|
| 99 | |
|---|
| 100 | # Did we strip what we shouldn't? |
|---|
| 101 | if $(result[2]) = ".." { |
|---|
| 102 | return $(path)/.. ; |
|---|
| 103 | } else { |
|---|
| 104 | if ! $(result[1]) { |
|---|
| 105 | if [ is-rooted $(path) ] { |
|---|
| 106 | result = / ; |
|---|
| 107 | } else { |
|---|
| 108 | result = . ; |
|---|
| 109 | } |
|---|
| 110 | } |
|---|
| 111 | return $(result[1]) ; |
|---|
| 112 | } |
|---|
| 113 | } |
|---|
| 114 | } else { |
|---|
| 115 | error "Path '$(path)' has no parent" ; |
|---|
| 116 | } |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | # |
|---|
| 120 | # Returns path2 such that "[ join path path2 ] = .". |
|---|
| 121 | # The path may not contain ".." element or be rooted. |
|---|
| 122 | # |
|---|
| 123 | rule reverse ( path ) |
|---|
| 124 | { |
|---|
| 125 | if $(path) = . |
|---|
| 126 | { |
|---|
| 127 | return $(path) ; |
|---|
| 128 | } |
|---|
| 129 | else |
|---|
| 130 | { |
|---|
| 131 | local tokens = [ regex.split $(path) "/" ] ; |
|---|
| 132 | local tokens2 ; |
|---|
| 133 | for local i in $(tokens) { |
|---|
| 134 | tokens2 += .. ; |
|---|
| 135 | } |
|---|
| 136 | return [ sequence.join $(tokens2) : "/" ] ; |
|---|
| 137 | } |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | # |
|---|
| 141 | # Auxillary rule: does all the semantic of 'join', except for error cheching. |
|---|
| 142 | # The error checking is separated because this rule is recursive, and I don't |
|---|
| 143 | # like the idea of checking the same input over and over. |
|---|
| 144 | # |
|---|
| 145 | local rule join-imp ( elements + ) |
|---|
| 146 | { |
|---|
| 147 | local result = ; |
|---|
| 148 | if ! $(elements[1]) |
|---|
| 149 | { |
|---|
| 150 | result = [ NORMALIZE_PATH "/" "$(elements[2-])" ] ; |
|---|
| 151 | } |
|---|
| 152 | else |
|---|
| 153 | { |
|---|
| 154 | result = [ NORMALIZE_PATH "$(elements)" ] ; |
|---|
| 155 | } |
|---|
| 156 | return $(result) ; |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | # |
|---|
| 160 | # Contanenates the passed path elements. Generates an error if |
|---|
| 161 | # any element other than the first one is rooted. |
|---|
| 162 | # |
|---|
| 163 | rule join ( elements + ) |
|---|
| 164 | { |
|---|
| 165 | if ! $(elements[2]) |
|---|
| 166 | { |
|---|
| 167 | return $(elements[1]) ; |
|---|
| 168 | } |
|---|
| 169 | else |
|---|
| 170 | { |
|---|
| 171 | for local e in $(elements[2-]) |
|---|
| 172 | { |
|---|
| 173 | if [ is-rooted $(e) ] |
|---|
| 174 | { |
|---|
| 175 | error only first element may be rooted ; |
|---|
| 176 | } |
|---|
| 177 | } |
|---|
| 178 | return [ join-imp $(elements) ] ; |
|---|
| 179 | } |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | |
|---|
| 183 | # |
|---|
| 184 | # If 'path' is relative, it is rooted at 'root'. Otherwise, it's unchanged. |
|---|
| 185 | # |
|---|
| 186 | rule root ( path root ) |
|---|
| 187 | { |
|---|
| 188 | if [ is-rooted $(path) ] { |
|---|
| 189 | return $(path) ; |
|---|
| 190 | } else { |
|---|
| 191 | return [ join $(root) $(path) ] ; |
|---|
| 192 | } |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | # |
|---|
| 196 | # Returns the current working directory. |
|---|
| 197 | # |
|---|
| 198 | rule pwd ( ) |
|---|
| 199 | { |
|---|
| 200 | if $(.pwd) |
|---|
| 201 | { |
|---|
| 202 | return $(.pwd) ; |
|---|
| 203 | } |
|---|
| 204 | else |
|---|
| 205 | { |
|---|
| 206 | .pwd = [ make [ PWD ] ] ; |
|---|
| 207 | return $(.pwd) ; |
|---|
| 208 | } |
|---|
| 209 | } |
|---|
| 210 | |
|---|
| 211 | # |
|---|
| 212 | # Returns the list of files matching the given pattern in the |
|---|
| 213 | # specified directory. Both directories and patterns are |
|---|
| 214 | # supplied as portable paths. Each pattern should be non-absolute |
|---|
| 215 | # path, and can't contain "." or ".." elements. Each slash separated |
|---|
| 216 | # element of pattern can contain the following special characters: |
|---|
| 217 | # - '?', which match any character |
|---|
| 218 | # - '*', which matches arbitrary number of characters. |
|---|
| 219 | # A file $(d)/e1/e2/e3 (where 'd' is in $(dirs)) matches pattern p1/p2/p3 |
|---|
| 220 | # if and only if e1 matches p1, e2 matches p2 and so on. |
|---|
| 221 | # |
|---|
| 222 | # For example: |
|---|
| 223 | # [ glob . : *.cpp ] |
|---|
| 224 | # [ glob . : */build/Jamfile ] |
|---|
| 225 | rule glob ( dirs * : patterns + : exclude-patterns * ) |
|---|
| 226 | { |
|---|
| 227 | local result ; |
|---|
| 228 | local real-patterns ; |
|---|
| 229 | local real-exclude-patterns ; |
|---|
| 230 | for local d in $(dirs) |
|---|
| 231 | { |
|---|
| 232 | for local p in $(patterns) |
|---|
| 233 | { |
|---|
| 234 | local pattern = [ path.root $(p) $(d) ] ; |
|---|
| 235 | real-patterns += [ path.native $(pattern) ] ; |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | for local p in $(exclude-patterns) |
|---|
| 239 | { |
|---|
| 240 | local pattern = [ path.root $(p) $(d) ] ; |
|---|
| 241 | real-exclude-patterns += [ path.native $(pattern) ] ; |
|---|
| 242 | } |
|---|
| 243 | } |
|---|
| 244 | |
|---|
| 245 | local inc = [ GLOB-RECURSIVELY $(real-patterns) ] ; |
|---|
| 246 | local exc = [ GLOB-RECURSIVELY $(real-exclude-patterns) ] ; |
|---|
| 247 | |
|---|
| 248 | return [ sequence.transform path.make : |
|---|
| 249 | [ set.difference $(inc) : $(exc) ] ] ; |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | # Recursive version of GLOB. Builds the glob of files while |
|---|
| 253 | # also searching in the subdirectories of the given roots. An |
|---|
| 254 | # optional set of exclusion patterns will filter out the |
|---|
| 255 | # matching entries from the result. The exclusions also apply |
|---|
| 256 | # to the subdirectory scanning, such that directories that |
|---|
| 257 | # match the exclusion patterns will not be searched. |
|---|
| 258 | # |
|---|
| 259 | rule glob-tree ( roots * : patterns + : exclude-patterns * ) |
|---|
| 260 | { |
|---|
| 261 | return [ sequence.transform path.make : [ .glob-tree |
|---|
| 262 | [ sequence.transform path.native : $(roots) ] |
|---|
| 263 | : $(patterns) |
|---|
| 264 | : $(exclude-patterns) |
|---|
| 265 | ] ] ; |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | local rule .glob-tree ( roots * : patterns * : exclude-patterns * ) |
|---|
| 269 | { |
|---|
| 270 | local excluded ; |
|---|
| 271 | if $(exclude-patterns) |
|---|
| 272 | { |
|---|
| 273 | excluded = [ GLOB $(roots) : $(exclude-patterns) ] ; |
|---|
| 274 | } |
|---|
| 275 | local result = [ set.difference |
|---|
| 276 | [ GLOB $(roots) : $(patterns) ] : $(excluded) ] ; |
|---|
| 277 | local subdirs ; |
|---|
| 278 | for local d in [ set.difference |
|---|
| 279 | [ GLOB $(roots) : * ] : $(excluded) ] |
|---|
| 280 | { |
|---|
| 281 | if ! ( $(d:D=) in . .. ) && ! [ CHECK_IF_FILE $(d) ] { subdirs += $(d) ; } |
|---|
| 282 | } |
|---|
| 283 | if $(subdirs) |
|---|
| 284 | { |
|---|
| 285 | result += [ .glob-tree $(subdirs) : $(patterns) : $(exclude-patterns) ] ; |
|---|
| 286 | } |
|---|
| 287 | return $(result) ; |
|---|
| 288 | } |
|---|
| 289 | |
|---|
| 290 | |
|---|
| 291 | # |
|---|
| 292 | # Returns true is the specified file exists. |
|---|
| 293 | # |
|---|
| 294 | rule exists ( file ) |
|---|
| 295 | { |
|---|
| 296 | return [ path.glob $(file:D) : $(file:D=) ] ; |
|---|
| 297 | } |
|---|
| 298 | NATIVE_RULE path : exists ; |
|---|
| 299 | |
|---|
| 300 | |
|---|
| 301 | |
|---|
| 302 | # |
|---|
| 303 | # Find out the absolute name of path and returns the list of all the parents, |
|---|
| 304 | # starting with the immediate one. Parents are returned as relative names. |
|---|
| 305 | # If 'upper_limit' is specified, directories above it will be pruned. |
|---|
| 306 | # |
|---|
| 307 | rule all-parents ( path : upper_limit ? : cwd ? ) |
|---|
| 308 | { |
|---|
| 309 | cwd ?= [ pwd ] ; |
|---|
| 310 | local path_ele = [ regex.split [ root $(path) $(cwd) ] "/" ] ; |
|---|
| 311 | |
|---|
| 312 | if ! $(upper_limit) { |
|---|
| 313 | upper_limit = / ; |
|---|
| 314 | } |
|---|
| 315 | local upper_ele = [ regex.split [ root $(upper_limit) $(cwd) ] "/" ] ; |
|---|
| 316 | |
|---|
| 317 | # Leave only elements in 'path_ele' below 'upper_ele' |
|---|
| 318 | while $(path_ele) && $(upper_ele[1]) = $(path_ele[1]) { |
|---|
| 319 | upper_ele = $(upper_ele[2-]) ; |
|---|
| 320 | path_ele = $(path_ele[2-]) ; |
|---|
| 321 | } |
|---|
| 322 | |
|---|
| 323 | # All upper elements removed ? |
|---|
| 324 | if ! $(upper_ele) { |
|---|
| 325 | # Create the relative paths to parents, number of elements in 'path_ele' |
|---|
| 326 | local result ; |
|---|
| 327 | for local i in $(path_ele) { |
|---|
| 328 | path = [ parent $(path) ] ; |
|---|
| 329 | result += $(path) ; |
|---|
| 330 | } |
|---|
| 331 | return $(result) ; |
|---|
| 332 | } |
|---|
| 333 | else { |
|---|
| 334 | error "$(upper_limit) is not prefix of $(path)" ; |
|---|
| 335 | } |
|---|
| 336 | } |
|---|
| 337 | |
|---|
| 338 | |
|---|
| 339 | # |
|---|
| 340 | # Search for 'pattern' in parent directories of 'dir', up till and including |
|---|
| 341 | # 'upper_limit', if it is specified, or till the filesystem root otherwise. |
|---|
| 342 | # |
|---|
| 343 | rule glob-in-parents ( dir : patterns + : upper-limit ? ) |
|---|
| 344 | { |
|---|
| 345 | local result ; |
|---|
| 346 | local parent-dirs = [ all-parents $(dir) : $(upper-limit) ] ; |
|---|
| 347 | |
|---|
| 348 | while $(parent-dirs) && ! $(result) |
|---|
| 349 | { |
|---|
| 350 | result = [ glob $(parent-dirs[1]) : $(patterns) ] ; |
|---|
| 351 | parent-dirs = $(parent-dirs[2-]) ; |
|---|
| 352 | } |
|---|
| 353 | return $(result) ; |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | # |
|---|
| 357 | # Assuming 'child' is a subdirectory of 'parent', return the relative |
|---|
| 358 | # path from 'parent' to 'child' |
|---|
| 359 | # |
|---|
| 360 | rule relative ( child parent ) |
|---|
| 361 | { |
|---|
| 362 | if $(parent) = "." |
|---|
| 363 | { |
|---|
| 364 | return $(child) ; |
|---|
| 365 | } |
|---|
| 366 | else |
|---|
| 367 | { |
|---|
| 368 | local split1 = [ regex.split $(parent) / ] ; |
|---|
| 369 | local split2 = [ regex.split $(child) / ] ; |
|---|
| 370 | |
|---|
| 371 | while $(split1) |
|---|
| 372 | { |
|---|
| 373 | if $(split1[1]) = $(split2[1]) |
|---|
| 374 | { |
|---|
| 375 | split1 = $(split1[2-]) ; |
|---|
| 376 | split2 = $(split2[2-]) ; |
|---|
| 377 | } |
|---|
| 378 | else |
|---|
| 379 | { |
|---|
| 380 | errors.error $(child) is not a subdir of $(parent) ; |
|---|
| 381 | } |
|---|
| 382 | } |
|---|
| 383 | return [ join $(split2) ] ; |
|---|
| 384 | } |
|---|
| 385 | } |
|---|
| 386 | |
|---|
| 387 | # Returns the minimal path to path2 that is relative path1. |
|---|
| 388 | # |
|---|
| 389 | rule relative-to ( path1 path2 ) |
|---|
| 390 | { |
|---|
| 391 | local root_1 = [ regex.split [ reverse $(path1) ] / ] ; |
|---|
| 392 | local split1 = [ regex.split $(path1) / ] ; |
|---|
| 393 | local split2 = [ regex.split $(path2) / ] ; |
|---|
| 394 | |
|---|
| 395 | while $(split1) && $(root_1) |
|---|
| 396 | { |
|---|
| 397 | if $(split1[1]) = $(split2[1]) |
|---|
| 398 | { |
|---|
| 399 | root_1 = $(root_1[2-]) ; |
|---|
| 400 | split1 = $(split1[2-]) ; |
|---|
| 401 | split2 = $(split2[2-]) ; |
|---|
| 402 | } |
|---|
| 403 | else |
|---|
| 404 | { |
|---|
| 405 | split1 = ; |
|---|
| 406 | } |
|---|
| 407 | } |
|---|
| 408 | return [ join . $(root_1) $(split2) ] ; |
|---|
| 409 | } |
|---|
| 410 | |
|---|
| 411 | # Returns the list of paths which are used by the operating system |
|---|
| 412 | # for looking up programs |
|---|
| 413 | rule programs-path ( ) |
|---|
| 414 | { |
|---|
| 415 | local result ; |
|---|
| 416 | local raw = [ modules.peek : PATH Path path ] ; |
|---|
| 417 | for local p in $(raw) |
|---|
| 418 | { |
|---|
| 419 | if $(p) |
|---|
| 420 | { |
|---|
| 421 | result += [ path.make $(p) ] ; |
|---|
| 422 | } |
|---|
| 423 | } |
|---|
| 424 | return $(result) ; |
|---|
| 425 | } |
|---|
| 426 | |
|---|
| 427 | rule make-NT ( native ) |
|---|
| 428 | { |
|---|
| 429 | local tokens = [ regex.split $(native) "[/\\]" ] ; |
|---|
| 430 | local result ; |
|---|
| 431 | |
|---|
| 432 | # Handle paths ending with slashes |
|---|
| 433 | if $(tokens[-1]) = "" |
|---|
| 434 | { |
|---|
| 435 | tokens = $(tokens[1--2]) ; # discard the empty element |
|---|
| 436 | } |
|---|
| 437 | |
|---|
| 438 | result = [ path.join $(tokens) ] ; |
|---|
| 439 | |
|---|
| 440 | if [ regex.match "(^.:)" : $(native) ] |
|---|
| 441 | { |
|---|
| 442 | result = /$(result) ; |
|---|
| 443 | } |
|---|
| 444 | |
|---|
| 445 | if $(native) = "" |
|---|
| 446 | { |
|---|
| 447 | result = "." ; |
|---|
| 448 | } |
|---|
| 449 | |
|---|
| 450 | return $(result) ; |
|---|
| 451 | } |
|---|
| 452 | |
|---|
| 453 | rule native-NT ( path ) |
|---|
| 454 | { |
|---|
| 455 | local result = [ MATCH "^/?(.*)" : $(path) ] ; |
|---|
| 456 | result = [ sequence.join [ regex.split $(result) "/" ] : "\\" ] ; |
|---|
| 457 | return $(result) ; |
|---|
| 458 | } |
|---|
| 459 | |
|---|
| 460 | rule make-UNIX ( native ) |
|---|
| 461 | { |
|---|
| 462 | # VP: I have no idea now 'native' can be empty here! But it can! |
|---|
| 463 | if $(native) = "" |
|---|
| 464 | { |
|---|
| 465 | errors.error "Empty path passed to 'make-UNIX'" ; |
|---|
| 466 | } |
|---|
| 467 | else |
|---|
| 468 | { |
|---|
| 469 | return [ NORMALIZE_PATH $(native:T) ] ; |
|---|
| 470 | } |
|---|
| 471 | } |
|---|
| 472 | |
|---|
| 473 | rule native-UNIX ( path ) |
|---|
| 474 | { |
|---|
| 475 | return $(path) ; |
|---|
| 476 | } |
|---|
| 477 | |
|---|
| 478 | rule make-CYGWIN ( path ) |
|---|
| 479 | { |
|---|
| 480 | return [ make-NT $(path) ] ; |
|---|
| 481 | } |
|---|
| 482 | |
|---|
| 483 | rule native-CYGWIN ( path ) |
|---|
| 484 | { |
|---|
| 485 | local result = $(path) ; |
|---|
| 486 | if [ regex.match "(^/.:)" : $(path) ] # win absolute |
|---|
| 487 | { |
|---|
| 488 | result = [ MATCH "^/?(.*)" : $(path) ] ; # remove leading '/' |
|---|
| 489 | } |
|---|
| 490 | return [ native-UNIX $(result) ] ; |
|---|
| 491 | } |
|---|
| 492 | |
|---|
| 493 | # |
|---|
| 494 | # split-VMS: splits input native path into |
|---|
| 495 | # device dir file (each part is optional), |
|---|
| 496 | # example: |
|---|
| 497 | # |
|---|
| 498 | # dev:[dir]file.c => dev: [dir] file.c |
|---|
| 499 | # |
|---|
| 500 | rule split-path-VMS ( native ) |
|---|
| 501 | { |
|---|
| 502 | local matches = [ MATCH ([a-zA-Z0-9_-]+:)?(\\[[^\]]*\\])?(.*)?$ : $(native) ] ; |
|---|
| 503 | local device = $(matches[1]) ; |
|---|
| 504 | local dir = $(matches[2]) ; |
|---|
| 505 | local file = $(matches[3]) ; |
|---|
| 506 | |
|---|
| 507 | return $(device) $(dir) $(file) ; |
|---|
| 508 | } |
|---|
| 509 | |
|---|
| 510 | # |
|---|
| 511 | # Converts a native VMS path into a portable path spec. |
|---|
| 512 | # |
|---|
| 513 | # Does not handle current-device absolute paths such |
|---|
| 514 | # as "[dir]File.c" as it is not clear how to represent |
|---|
| 515 | # them in the portable path notation. |
|---|
| 516 | # |
|---|
| 517 | # Adds a trailing dot (".") to the file part if no extension |
|---|
| 518 | # is present (helps when converting it back into native path). |
|---|
| 519 | # |
|---|
| 520 | rule make-VMS ( native ) |
|---|
| 521 | { |
|---|
| 522 | if [ MATCH ^(\\[[a-zA-Z0-9]) : $(native) ] |
|---|
| 523 | { |
|---|
| 524 | errors.error "Can't handle default-device absolute paths: " $(native) ; |
|---|
| 525 | } |
|---|
| 526 | |
|---|
| 527 | local parts = [ split-path-VMS $(native) ] ; |
|---|
| 528 | local device = $(parts[1]) ; |
|---|
| 529 | local dir = $(parts[2]) ; |
|---|
| 530 | local file = $(parts[3]) ; |
|---|
| 531 | local elems ; |
|---|
| 532 | |
|---|
| 533 | if $(device) |
|---|
| 534 | { |
|---|
| 535 | # |
|---|
| 536 | # rooted |
|---|
| 537 | # |
|---|
| 538 | elems = /$(device) ; |
|---|
| 539 | } |
|---|
| 540 | |
|---|
| 541 | if $(dir) = "[]" |
|---|
| 542 | { |
|---|
| 543 | # |
|---|
| 544 | # Special case: current directory |
|---|
| 545 | # |
|---|
| 546 | elems = $(elems) "." ; |
|---|
| 547 | } |
|---|
| 548 | else if $(dir) |
|---|
| 549 | { |
|---|
| 550 | dir = [ regex.replace $(dir) "\\[|\\]" "" ] ; |
|---|
| 551 | local dir_parts = [ regex.split $(dir) \\. ] ; |
|---|
| 552 | |
|---|
| 553 | if $(dir_parts[1]) = "" |
|---|
| 554 | { |
|---|
| 555 | # |
|---|
| 556 | # Relative path |
|---|
| 557 | # |
|---|
| 558 | dir_parts = $(dir_parts[2--1]) ; |
|---|
| 559 | } |
|---|
| 560 | |
|---|
| 561 | # |
|---|
| 562 | # replace "parent-directory" parts (- => ..) |
|---|
| 563 | # |
|---|
| 564 | dir_parts = [ regex.replace-list $(dir_parts) : - : .. ] ; |
|---|
| 565 | |
|---|
| 566 | elems = $(elems) $(dir_parts) ; |
|---|
| 567 | } |
|---|
| 568 | |
|---|
| 569 | if $(file) |
|---|
| 570 | { |
|---|
| 571 | if ! [ MATCH (\\.) : $(file) ] |
|---|
| 572 | { |
|---|
| 573 | # |
|---|
| 574 | # Always add "." to end of non-extension file |
|---|
| 575 | # |
|---|
| 576 | file = $(file). ; |
|---|
| 577 | } |
|---|
| 578 | elems = $(elems) $(file) ; |
|---|
| 579 | } |
|---|
| 580 | |
|---|
| 581 | local portable = [ path.join $(elems) ] ; |
|---|
| 582 | |
|---|
| 583 | return $(portable) ; |
|---|
| 584 | } |
|---|
| 585 | |
|---|
| 586 | # |
|---|
| 587 | # Converts a portable path spec into a native VMS path. |
|---|
| 588 | # |
|---|
| 589 | # Relies on having at least one dot (".") included in the file |
|---|
| 590 | # name to be able to differentiate it ftom the directory part. |
|---|
| 591 | # |
|---|
| 592 | rule native-VMS ( path ) |
|---|
| 593 | { |
|---|
| 594 | local device = "" ; |
|---|
| 595 | local dir = $(path) ; |
|---|
| 596 | local file = "" ; |
|---|
| 597 | local native ; |
|---|
| 598 | local split ; |
|---|
| 599 | |
|---|
| 600 | # |
|---|
| 601 | # Has device ? |
|---|
| 602 | # |
|---|
| 603 | if [ is-rooted $(dir) ] |
|---|
| 604 | { |
|---|
| 605 | split = [ MATCH ^/([^:]+:)/?(.*) : $(dir) ] ; |
|---|
| 606 | device = $(split[1]) ; |
|---|
| 607 | dir = $(split[2]) ; |
|---|
| 608 | } |
|---|
| 609 | |
|---|
| 610 | # |
|---|
| 611 | # Has file ? |
|---|
| 612 | # |
|---|
| 613 | # This is no exact science, just guess work: |
|---|
| 614 | # |
|---|
| 615 | # If the last part of the current path spec |
|---|
| 616 | # includes some chars, followed by a dot, |
|---|
| 617 | # optionally followed by more chars - |
|---|
| 618 | # then it is a file (keep your fingers crossed). |
|---|
| 619 | # |
|---|
| 620 | split = [ regex.split $(dir) / ] ; |
|---|
| 621 | local maybe_file = $(split[-1]) ; |
|---|
| 622 | |
|---|
| 623 | if [ MATCH ^([^.]+\\..*) : $(maybe_file) ] |
|---|
| 624 | { |
|---|
| 625 | file = $(maybe_file) ; |
|---|
| 626 | dir = [ sequence.join $(split[1--2]) : / ] ; |
|---|
| 627 | } |
|---|
| 628 | |
|---|
| 629 | # |
|---|
| 630 | # Has dir spec ? |
|---|
| 631 | # |
|---|
| 632 | if $(dir) = "." |
|---|
| 633 | { |
|---|
| 634 | dir = "[]" ; |
|---|
| 635 | } |
|---|
| 636 | else if $(dir) |
|---|
| 637 | { |
|---|
| 638 | dir = [ regex.replace $(dir) \\.\\. - ] ; |
|---|
| 639 | dir = [ regex.replace $(dir) / . ] ; |
|---|
| 640 | |
|---|
| 641 | if $(device) = "" |
|---|
| 642 | { |
|---|
| 643 | # |
|---|
| 644 | # Relative directory |
|---|
| 645 | # |
|---|
| 646 | dir = "."$(dir) ; |
|---|
| 647 | } |
|---|
| 648 | dir = "["$(dir)"]" ; |
|---|
| 649 | } |
|---|
| 650 | |
|---|
| 651 | native = [ sequence.join $(device) $(dir) $(file) ] ; |
|---|
| 652 | |
|---|
| 653 | return $(native) ; |
|---|
| 654 | } |
|---|
| 655 | |
|---|
| 656 | |
|---|
| 657 | rule __test__ ( ) { |
|---|
| 658 | |
|---|
| 659 | import assert ; |
|---|
| 660 | import errors : try catch ; |
|---|
| 661 | |
|---|
| 662 | assert.true is-rooted "/" ; |
|---|
| 663 | assert.true is-rooted "/foo" ; |
|---|
| 664 | assert.true is-rooted "/foo/bar" ; |
|---|
| 665 | assert.result : is-rooted "." ; |
|---|
| 666 | assert.result : is-rooted "foo" ; |
|---|
| 667 | assert.result : is-rooted "foo/bar" ; |
|---|
| 668 | |
|---|
| 669 | assert.true has-parent "foo" ; |
|---|
| 670 | assert.true has-parent "foo/bar" ; |
|---|
| 671 | assert.true has-parent "." ; |
|---|
| 672 | assert.result : has-parent "/" ; |
|---|
| 673 | |
|---|
| 674 | assert.result "." : basename "." ; |
|---|
| 675 | assert.result ".." : basename ".." ; |
|---|
| 676 | assert.result "foo" : basename "foo" ; |
|---|
| 677 | assert.result "foo" : basename "bar/foo" ; |
|---|
| 678 | assert.result "foo" : basename "gaz/bar/foo" ; |
|---|
| 679 | assert.result "foo" : basename "/gaz/bar/foo" ; |
|---|
| 680 | |
|---|
| 681 | assert.result "." : parent "foo" ; |
|---|
| 682 | assert.result "/" : parent "/foo" ; |
|---|
| 683 | assert.result "foo/bar" : parent "foo/bar/giz" ; |
|---|
| 684 | assert.result ".." : parent "." ; |
|---|
| 685 | assert.result ".." : parent "../foo" ; |
|---|
| 686 | assert.result "../../foo" : parent "../../foo/bar" ; |
|---|
| 687 | |
|---|
| 688 | |
|---|
| 689 | assert.result "." : reverse "." ; |
|---|
| 690 | assert.result ".." : reverse "foo" ; |
|---|
| 691 | assert.result "../../.." : reverse "foo/bar/giz" ; |
|---|
| 692 | |
|---|
| 693 | assert.result "foo" : join "foo" ; |
|---|
| 694 | assert.result "/foo" : join "/" "foo" ; |
|---|
| 695 | assert.result "foo/bar" : join "foo" "bar" ; |
|---|
| 696 | assert.result "foo/bar" : join "foo/giz" "../bar" ; |
|---|
| 697 | assert.result "foo/giz" : join "foo/bar/baz" "../../giz" ; |
|---|
| 698 | assert.result ".." : join "." ".." ; |
|---|
| 699 | assert.result ".." : join "foo" "../.." ; |
|---|
| 700 | assert.result "../.." : join "../foo" "../.." ; |
|---|
| 701 | assert.result "/foo" : join "/bar" "../foo" ; |
|---|
| 702 | assert.result "foo/giz" : join "foo/giz" "." ; |
|---|
| 703 | assert.result "." : join lib2 ".." ; |
|---|
| 704 | assert.result "/" : join "/a" ".." ; |
|---|
| 705 | |
|---|
| 706 | assert.result /a/b : join /a/b/c .. ; |
|---|
| 707 | |
|---|
| 708 | assert.result "foo/bar/giz" : join "foo" "bar" "giz" ; |
|---|
| 709 | assert.result "giz" : join "foo" ".." "giz" ; |
|---|
| 710 | assert.result "foo/giz" : join "foo" "." "giz" ; |
|---|
| 711 | |
|---|
| 712 | try ; |
|---|
| 713 | { |
|---|
| 714 | join "a" "/b" ; |
|---|
| 715 | } |
|---|
| 716 | catch only first element may be rooted ; |
|---|
| 717 | |
|---|
| 718 | local CWD = "/home/ghost/build" ; |
|---|
| 719 | assert.result : all-parents . : . : $(CWD) ; |
|---|
| 720 | assert.result . .. ../.. ../../.. : all-parents "Jamfile" : "" : $(CWD) ; |
|---|
| 721 | assert.result foo . .. ../.. ../../.. : all-parents "foo/Jamfile" : "" : $(CWD) ; |
|---|
| 722 | assert.result ../Work .. ../.. ../../.. : all-parents "../Work/Jamfile" : "" : $(CWD) ; |
|---|
| 723 | |
|---|
| 724 | local CWD = "/home/ghost" ; |
|---|
| 725 | assert.result . .. : all-parents "Jamfile" : "/home" : $(CWD) ; |
|---|
| 726 | assert.result . : all-parents "Jamfile" : "/home/ghost" : $(CWD) ; |
|---|
| 727 | |
|---|
| 728 | assert.result "c/d" : relative "a/b/c/d" "a/b" ; |
|---|
| 729 | assert.result "foo" : relative "foo" "." ; |
|---|
| 730 | |
|---|
| 731 | local save-os = [ modules.peek path : os ] ; |
|---|
| 732 | modules.poke path : os : NT ; |
|---|
| 733 | |
|---|
| 734 | assert.result "foo/bar/giz" : make "foo/bar/giz" ; |
|---|
| 735 | assert.result "foo/bar/giz" : make "foo\\bar\\giz" ; |
|---|
| 736 | assert.result "foo" : make "foo/." ; |
|---|
| 737 | assert.result "foo" : make "foo/bar/.." ; |
|---|
| 738 | assert.result "/D:/My Documents" : make "D:\\My Documents" ; |
|---|
| 739 | assert.result "/c:/boost/tools/build/new/project.jam" : make "c:\\boost\\tools\\build\\test\\..\\new\\project.jam" ; |
|---|
| 740 | |
|---|
| 741 | assert.result "foo\\bar\\giz" : native "foo/bar/giz" ; |
|---|
| 742 | assert.result "foo" : native "foo" ; |
|---|
| 743 | assert.result "D:\\My Documents\\Work" : native "/D:/My Documents/Work" ; |
|---|
| 744 | |
|---|
| 745 | modules.poke path : os : UNIX ; |
|---|
| 746 | |
|---|
| 747 | assert.result "foo/bar/giz" : make "foo/bar/giz" ; |
|---|
| 748 | assert.result "/sub1" : make "/sub1/." ; |
|---|
| 749 | assert.result "/sub1" : make "/sub1/sub2/.." ; |
|---|
| 750 | assert.result "sub1" : make "sub1/." ; |
|---|
| 751 | assert.result "sub1" : make "sub1/sub2/.." ; |
|---|
| 752 | assert.result "/foo/bar" : native "/foo/bar" ; |
|---|
| 753 | |
|---|
| 754 | modules.poke path : os : VMS ; |
|---|
| 755 | |
|---|
| 756 | # |
|---|
| 757 | # Don't really need to poke os before these |
|---|
| 758 | # |
|---|
| 759 | assert.result "disk:" "[dir]" "file" : split-path-VMS "disk:[dir]file" ; |
|---|
| 760 | assert.result "disk:" "[dir]" "" : split-path-VMS "disk:[dir]" ; |
|---|
| 761 | assert.result "disk:" "" "" : split-path-VMS "disk:" ; |
|---|
| 762 | assert.result "disk:" "" "file" : split-path-VMS "disk:file" ; |
|---|
| 763 | assert.result "" "[dir]" "file" : split-path-VMS "[dir]file" ; |
|---|
| 764 | assert.result "" "[dir]" "" : split-path-VMS "[dir]" ; |
|---|
| 765 | assert.result "" "" "file" : split-path-VMS "file" ; |
|---|
| 766 | assert.result "" "" "" : split-path-VMS "" ; |
|---|
| 767 | |
|---|
| 768 | # |
|---|
| 769 | # Special case: current directory |
|---|
| 770 | # |
|---|
| 771 | assert.result "" "[]" "" : split-path-VMS "[]" ; |
|---|
| 772 | assert.result "disk:" "[]" "" : split-path-VMS "disk:[]" ; |
|---|
| 773 | assert.result "" "[]" "file" : split-path-VMS "[]file" ; |
|---|
| 774 | assert.result "disk:" "[]" "file" : split-path-VMS "disk:[]file" ; |
|---|
| 775 | |
|---|
| 776 | # |
|---|
| 777 | # Make portable paths |
|---|
| 778 | # |
|---|
| 779 | assert.result "/disk:" : make "disk:" ; |
|---|
| 780 | assert.result "foo/bar/giz" : make "[.foo.bar.giz]" ; |
|---|
| 781 | assert.result "foo" : make "[.foo]" ; |
|---|
| 782 | assert.result "foo" : make "[.foo.bar.-]" ; |
|---|
| 783 | assert.result ".." : make "[.-]" ; |
|---|
| 784 | assert.result ".." : make "[-]" ; |
|---|
| 785 | assert.result "." : make "[]" ; |
|---|
| 786 | assert.result "giz.h" : make "giz.h" ; |
|---|
| 787 | assert.result "foo/bar/giz.h" : make "[.foo.bar]giz.h" ; |
|---|
| 788 | assert.result "/disk:/my_docs" : make "disk:[my_docs]" ; |
|---|
| 789 | assert.result "/disk:/boost/tools/build/new/project.jam" : make "disk:[boost.tools.build.test.-.new]project.jam" ; |
|---|
| 790 | |
|---|
| 791 | # |
|---|
| 792 | # Special case (adds '.' to end of file w/o extension to |
|---|
| 793 | # disambiguate from directory in portable path spec). |
|---|
| 794 | # |
|---|
| 795 | assert.result "Jamfile." : make "Jamfile" ; |
|---|
| 796 | assert.result "dir/Jamfile." : make "[.dir]Jamfile" ; |
|---|
| 797 | assert.result "/disk:/dir/Jamfile." : make "disk:[dir]Jamfile" ; |
|---|
| 798 | |
|---|
| 799 | # |
|---|
| 800 | # Make native paths |
|---|
| 801 | # |
|---|
| 802 | assert.result "disk:" : native "/disk:" ; |
|---|
| 803 | assert.result "[.foo.bar.giz]" : native "foo/bar/giz" ; |
|---|
| 804 | assert.result "[.foo]" : native "foo" ; |
|---|
| 805 | assert.result "[.-]" : native ".." ; |
|---|
| 806 | assert.result "[.foo.-]" : native "foo/.." ; |
|---|
| 807 | assert.result "[]" : native "." ; |
|---|
| 808 | assert.result "disk:[my_docs.work]" : native "/disk:/my_docs/work" ; |
|---|
| 809 | assert.result "giz.h" : native "giz.h" ; |
|---|
| 810 | assert.result "disk:Jamfile." : native "/disk:Jamfile." ; |
|---|
| 811 | assert.result "disk:[my_docs.work]Jamfile." : native "/disk:/my_docs/work/Jamfile." ; |
|---|
| 812 | |
|---|
| 813 | modules.poke path : os : $(save-os) ; |
|---|
| 814 | |
|---|
| 815 | } |
|---|