Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_33_1/tools/build/v2/util/path.jam @ 12

Last change on this file since 12 was 12, checked in by landauf, 17 years ago

added boost

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