1# Copyright 2012 Steven Watanabe
2# Distributed under the Boost Software License, Version 1.0.
3# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
4
5import os ;
6import targets ;
7import project ;
8import "class" : new ;
9import virtual-target ;
10import configure ;
11import path ;
12import property ;
13import property-set ;
14import common ;
15
16rule get-root-project ( project )
17{
18    # Find the root project.
19    local root-project = $(project) ;
20    root-project = [ $(root-project).project-module ] ;
21    while
22        [ project.attribute $(root-project) parent-module ] &&
23        [ project.attribute $(root-project) parent-module ] != user-config &&
24        [ project.attribute $(root-project) parent-module ] != project-config
25    {
26        root-project = [ project.attribute $(root-project) parent-module ] ;
27    }
28    return $(root-project) ;
29}
30
31TOUCH = [ common.file-touch-command ] ;
32
33actions touch {
34   $(TOUCH) "$(<)"
35}
36
37rule can-symlink ( project )
38{
39    if ! $(.can-symlink)
40    {
41        local root-project = [ get-root-project $(project) ] ;
42
43        local source-target = [ new file-target test-symlink-source : :
44            $(project) : [ new action : link.touch ] ] ;
45        local target = [ new file-target test-symlink : :
46            $(project) : [ new action $(source-target) : link.mklink ] ] ;
47
48        if [ configure.try-build $(target) : [ property-set.empty ] : "symlinks supported" ]
49        {
50            .can-symlink = true ;
51        }
52        else
53        {
54            .can-symlink = false ;
55        }
56    }
57    if $(.can-symlink) = true
58    {
59        return true ;
60    }
61}
62
63if [ os.name ] = NT
64{
65
66# Test for Windows junctions (mklink /J)
67rule can-junction ( project )
68{
69    if ! $(.can-junction)
70    {
71        local root-project = [ get-root-project $(project) ] ;
72
73        local source-target = [ new file-target test-junction-source : :
74            $(project) : [ new action : common.mkdir ] ] ;
75        local target = [ new file-target test-junction : :
76            $(project) : [ new action $(source-target) : link.junction ] ] ;
77
78        if [ configure.try-build $(target) : [ property-set.empty ] : "junctions supported" ]
79        {
80            .can-junction = true ;
81        }
82        else
83        {
84            .can-junction = false ;
85        }
86    }
87    if $(.can-junction) = true
88    {
89        return true ;
90    }
91}
92
93}
94else
95{
96
97.can-junction = false ;
98
99rule can-junction ( project )
100{
101}
102
103}
104
105rule can-hardlink ( project )
106{
107    if ! $(.can-hardlink)
108    {
109        local root-project = [ get-root-project $(project) ] ;
110
111        local source-target = [ new file-target test-hardlink-source : :
112            $(project) : [ new action : link.touch ] ] ;
113        # Use <location-prefix> so that the destination link is created
114        # in a different directory. AFS refuses to make hard links
115        # between files in different directories, so we want to check
116        # it.
117        local target = [ new file-target test-hardlink : :
118            $(project) : [ new action $(source-target) : link.hardlink
119            : [ new property-set <location-prefix>symlink ]
120            ] ] ;
121
122        if [ configure.try-build $(target) : [ property-set.empty ] : "hardlinks supported" ]
123        {
124            .can-hardlink = true ;
125        }
126        else
127        {
128            .can-hardlink = false ;
129        }
130    }
131    if $(.can-hardlink) = true
132    {
133        return true ;
134    }
135}
136
137class file-or-directory-reference : basic-target
138{
139    import virtual-target ;
140    import property-set ;
141    import path ;
142
143    rule construct ( name : source-targets * : property-set )
144    {
145        return [ property-set.empty ] [ virtual-target.from-file $(self.name) :
146            [ location ] : $(self.project) ] ;
147    }
148
149    # Returns true if the referred file really exists.
150    rule exists ( )
151    {
152        location ;
153        return $(self.file-path) ;
154    }
155
156    # Returns the location of target. Needed by 'testing.jam'.
157    rule location ( )
158    {
159        if ! $(self.file-location)
160        {
161            local source-location = [ $(self.project).get source-location ] ;
162            for local src-dir in $(source-location)
163            {
164                if ! $(self.file-location)
165                {
166                    local location = [ path.root $(self.name) $(src-dir) ] ;
167                    if [ path.exists [ path.native $(location) ] ]
168                    {
169                         self.file-location = $(src-dir) ;
170                         self.file-path = $(location) ;
171                    }
172                }
173            }
174        }
175        return $(self.file-location) ;
176    }
177}
178
179class symlink-target-class : basic-target
180{
181    import path ;
182    import virtual-target ;
183    import link ;
184    import os ;
185    import type ;
186    rule construct ( name : source-target : property-set )
187    {
188        local location = [ path.join
189            [ $(source-target).path ] [ $(source-target).name ] ] ;
190        local files = [ path.glob-tree $(location) : * ] ;
191        local targets ;
192
193        # If we have symlinks, don't bother checking
194        # for hardlinks and junctions.
195        if ! [ link.can-symlink $(self.project) ]
196        {
197            link.can-junction $(self.project) ;
198            link.can-hardlink $(self.project) ;
199        }
200
201        if [ $(property-set).get <location> ]
202        {
203            property-set = [ property-set.create
204                [ property.select <location> : [ $(property-set).raw ] ] ] ;
205        }
206        else
207        {
208            local path,relative-to-build-dir = [ $(property-set).target-path ] ;
209            local path = $(path,relative-to-build-dir[1]) ;
210            local relative-to-build-dir = $(path,relative-to-build-dir[2]) ;
211
212            if $(relative-to-build-dir)
213            {
214                path = [ path.join [ $(self.project).build-dir ] $(path) ] ;
215            }
216
217            property-set = [ property-set.create <location>$(path) ] ;
218        }
219
220        local a = [ new non-scanning-action $(source-target) :
221            link.do-link-recursively : $(property-set) ] ;
222
223        local t = [ new notfile-target $(name)
224            : $(self.project) : $(a) ] ;
225
226        return [ property-set.empty ] [ virtual-target.register $(t) ] ;
227    }
228}
229
230rule do-file-link
231{
232    local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ;
233    local source = [ path.native [ path.relative-to [ path.pwd ] $(>) ] ] ;
234    local old-source = [ on $(target) return $(LINK-SOURCE) ] ;
235    if $(old-source)
236    {
237        import errors ;
238        errors.user-error
239            Cannot create link $(target) to $(source). :
240            Link previously defined to another file, $(old-source[1]). ;
241    }
242    LINK-SOURCE on $(target) = $(source) $(.current-target) ;
243    LOCATE on $(target) = . ;
244    DEPENDS $(.current-target) : $(target) ;
245    if $(.can-symlink) = true
246    {
247        DEPENDS $(target) : $(source) ;
248        link.mklink $(target) : $(source) ;
249    }
250    else if $(.can-hardlink) = true
251    {
252        DEPENDS $(target) : $(source) ;
253        link.hardlink $(target) : $(source) ;
254    }
255    else
256    {
257        DEPENDS $(target) : $(source) ;
258        common.copy $(target) : $(source) ;
259    }
260}
261
262rule do-link
263{
264    local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ;
265    local source = [ path.native [ path.relative-to [ path.pwd ] $(>) ] ] ;
266    local relative = [ path.native [ path.relative-to [ path.parent $(<) ] $(>) ] ] ;
267    if ! [ on $(target) return $(MKLINK_OR_DIR) ]
268    {
269        LOCATE on $(target) = . ;
270        DEPENDS $(.current-target) : $(target) ;
271        mklink-or-dir $(target) : $(source) ;
272    }
273    if [ os.name ] = NT
274    {
275        if $(.can-symlink) = true
276        {
277            MKLINK_OR_DIR on $(target) = mklink /D \"$(target)\" \"$(relative)\" ;
278        }
279        else
280        {
281            # This function should only be called
282            # if either symlinks or junctions are supported.
283            # To get here $(.can-junction) must be true.
284            mklink-opt = /J ;
285            MKLINK_OR_DIR on $(target) = mklink /J \"$(target)\" \"$(source)\" ;
286        }
287    }
288    else
289    {
290        MKLINK_OR_DIR on $(target) = ln -s $(relative) $(target)  ;
291    }
292}
293
294rule force-update
295{
296    local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ;
297    ALWAYS $(target) ;
298}
299
300rule do-split
301{
302    local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ;
303    if ! [ on $(target) return $(MKLINK_OR_DIR) ]
304    {
305        LOCATE on $(target) = . ;
306        DEPENDS $(.current-target) : $(target) ;
307        common.mkdir $(target) ;
308    }
309    MKLINK_OR_DIR on $(target) = mkdir \"$(target)\" ;
310}
311
312rule do-rm
313{
314    local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ;
315    ALWAYS $(target) ;
316    RM on $(target) = rmdir ;
317    link.rm $(target) ;
318}
319
320rule mklink-or-dir
321{
322    NOUPDATE $(<) ;
323}
324
325actions mklink-or-dir
326{
327    $(MKLINK_OR_DIR)
328}
329
330rule link-entries ( target : files * : split ? : deleted ? )
331{
332    for local s in $(files)
333    {
334        local t = [ path.join $(target) [ path.basename $(s) ] ] ;
335        if ! $(.known-dirs.$(t))
336        {
337            local t = [ path.native [ path.relative-to [ path.pwd ] $(t) ] ] ;
338            local s = [ path.native [ path.relative-to [ path.pwd ] $(target) ] ] ;
339            LOCATE on $(t) = . ;
340            DEPENDS $(t) : $(s) ;
341            NOUPDATE $(s) ;
342        }
343        if $(split)
344        {
345            link-recursively $(t) : $(s) : : $(deleted) ;
346        }
347        else
348        {
349            link-entries $(t) : [ path.glob $(s) : * ] ;
350        }
351    }
352    if ! $(.known-dirs.$(target))
353    {
354        .known-dirs.$(target) += $(files) ;
355        .known-dirs.base.$(target) = $(.current-target) ;
356    }
357}
358
359rule link-recursively ( target : source : no-recurse ? : deleted ? )
360{
361    if $(deleted) {
362        force-update $(target) ;
363    }
364
365    local split ;
366    if [ CHECK_IF_FILE [ path.native $(source) ] ]
367    {
368        do-file-link $(target) : $(source) ;
369    }
370    else if $(.known-dirs.$(target)) && ! $(no-recurse)
371    {
372        split = true ;
373        if ! $(.split-dirs.$(target))
374        {
375            if [ READLINK [ path.native $(target) ] ]
376            {
377                if ! $(deleted) {
378                    do-rm $(target) ;
379                    deleted = true ;
380                    .deleted-dirs.$(target) = true ;
381                }
382            }
383            local .current-target = $(.known-dirs.base.$(target)) ;
384            for local s in $(.known-dirs.$(target))
385            {
386                local t = [ path.join $(target) [ path.basename $(s) ] ] ;
387                link-recursively $(t) : $(s) : flat : $(deleted) ;
388            }
389            do-split $(target) ;
390        }
391        else if $(.deleted-dirs.$(target))
392        {
393            deleted = true ;
394        }
395    }
396    else if [ path.exists [ path.native $(target) ] ] && ! $(deleted)
397    {
398        local link-target = [ READLINK [ path.native $(target) ] ] ;
399        if $(link-target)
400        {
401            local full-path =
402                [ path.root [ path.make $(link-target) ] [ path.parent $(target) ] ] ;
403            # HACK: Take advantage of the fact that path.glob
404            # normalizes its arguments.  If full-path and
405            # source are different, but both are empty, they
406            # will compare equal, but that's okay because
407            # for the purposes of this module, empty directories
408            # are equivalent.
409            if [ path.glob $(full-path) : * ] != [ path.glob $(source) : * ]
410            {
411                if ! $(deleted) {
412                    do-rm $(target) ;
413                    deleted = true ;
414                    .deleted-dirs.$(target) = true ;
415                }
416                do-split $(target) ;
417                split = true ;
418            }
419        }
420        else
421        {
422            do-split $(target) ;
423            split = true ;
424        }
425    }
426    else if $(.can-symlink) = false && $(.can-junction) = false
427    {
428        if [ READLINK [ path.native $(target) ] ]
429        {
430            if ! $(deleted) {
431                do-rm $(target) ;
432                deleted = true ;
433                .deleted-dirs.$(target) = true ;
434            }
435        }
436        do-split $(target) ;
437        split = true ;
438    }
439    else
440    {
441        do-link $(target) : $(source) ;
442    }
443
444    if $(split)
445    {
446        .split-dirs.$(target) = true ;
447    }
448
449    if ! $(no-recurse)
450    {
451        link-entries $(target) : [ path.glob $(source) : * ] : $(split) : $(deleted) ;
452    }
453}
454
455rule do-link-recursively ( target : source : properties * )
456{
457    local target-path = [ property.select <location> : $(properties) ] ;
458    local source-path = [ on $(source) return $(LOCATE) ] [ on $(source) return $(SEARCH) ] ;
459
460    local absolute-target = [ path.root
461        [ path.join [ path.make $(target-path[1]:G=) ]
462                    [ path.basename [ path.make $(source:G=) ] ] ]
463        [ path.pwd ] ] ;
464
465    local absolute-source = [ path.root
466        [ path.root [ path.make $(source:G=) ]
467                    [ path.make $(source-path[1]) ] ]
468        [ path.pwd ] ] ;
469
470    local .current-target = $(target) ;
471
472    link-recursively $(absolute-target) : $(absolute-source) ;
473}
474
475rule mklink
476{
477    local target-path = [ on $(<) return $(LOCATE) ] [ on $(<) return $(SEARCH) ] . ;
478    local source-path = [ on $(>) return $(LOCATE) ] [ on $(>) return $(SEARCH) ] . ;
479    local relative-path = [ path.relative-to
480        [ path.parent [ path.join [ path.root [ path.make $(target-path[1]) ] [ path.pwd ] ] [ path.make $(<:G=) ] ] ]
481        [ path.join [ path.root [ path.make $(source-path[1]) ] [ path.pwd ] ] [ path.make $(>:G=) ] ] ] ;
482
483    PATH_TO_SOURCE on $(<) = [ path.native $(relative-path) ] ;
484}
485
486if [ os.name ] = NT
487{
488
489actions junction
490{
491    if exist "$(<)" rmdir "$(<)"
492    mklink /J "$(<)" "$(>)"
493}
494
495actions mklink
496{
497    if exist "$(<)" del "$(<)"
498    mklink "$(<)" "$(PATH_TO_SOURCE)"
499}
500
501actions hardlink
502{
503    if exist "$(<)" del "$(<)"
504    mklink /H "$(<)" "$(>)"
505}
506
507actions rm
508{
509    rmdir "$(<)"
510}
511
512}
513else
514{
515
516actions mklink
517{
518    ln -f -s "$(PATH_TO_SOURCE)" "$(<)"
519}
520
521actions hardlink
522{
523    ln -f "$(>)" "$(<)"
524}
525
526actions rm
527{
528    rm "$(<)"
529}
530
531}
532
533rule link-directory ( name : sources : requirements * : default-build * : usage-requirements * )
534{
535    local project = [ project.current ] ;
536    sources = [ new file-or-directory-reference $(sources) : $(project) ] ;
537    targets.main-target-alternative $(sources) ;
538    return [ targets.main-target-alternative
539        [ new symlink-target-class $(name) : $(project)
540            : [ targets.main-target-sources $(sources) : $(name) : no-renaming ]
541            : [ targets.main-target-requirements $(requirements) : $(project) ]
542            : [ targets.main-target-default-build : $(project) ]
543            : [ targets.main-target-usage-requirements $(usage-requirements) :
544                $(project) ] ] ] ;
545}
546
547IMPORT $(__name__) : link-directory : : link-directory ;
548