# Copyright 2012 Steven Watanabe # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) import os ; import targets ; import project ; import "class" : new ; import virtual-target ; import configure ; import path ; import property ; import property-set ; import common ; rule get-root-project ( project ) { # Find the root project. local root-project = $(project) ; root-project = [ $(root-project).project-module ] ; while [ project.attribute $(root-project) parent-module ] && [ project.attribute $(root-project) parent-module ] != user-config && [ project.attribute $(root-project) parent-module ] != project-config { root-project = [ project.attribute $(root-project) parent-module ] ; } return $(root-project) ; } TOUCH = [ common.file-touch-command ] ; actions touch { $(TOUCH) "$(<)" } rule can-symlink ( project : ps ) { if ! $(.can-symlink) { local root-project = [ get-root-project $(project) ] ; local source-target = [ new file-target test-symlink-source : : $(project) : [ new action : link.touch ] ] ; local target = [ new file-target test-symlink : : $(project) : [ new action $(source-target) : link.mklink ] ] ; if [ configure.try-build $(target) : $(ps) : "symlinks supported" ] { .can-symlink = true ; } else { .can-symlink = false ; } } if $(.can-symlink) = true { return true ; } } if [ os.name ] = NT { # Test for Windows junctions (mklink /J) rule can-junction ( project : ps ) { if ! $(.can-junction) { local root-project = [ get-root-project $(project) ] ; local source-target = [ new file-target test-junction-source : : $(project) : [ new action : common.mkdir ] ] ; local target = [ new file-target test-junction : : $(project) : [ new action $(source-target) : link.junction ] ] ; if [ configure.try-build $(target) : $(ps) : "junctions supported" ] { .can-junction = true ; } else { .can-junction = false ; } } if $(.can-junction) = true { return true ; } } } else { .can-junction = false ; rule can-junction ( project : ps ) { } } rule can-hardlink ( project : ps ) { if ! $(.can-hardlink) { local root-project = [ get-root-project $(project) ] ; local source-target = [ new file-target test-hardlink-source : : $(project) : [ new action : link.touch ] ] ; # Use so that the destination link is created # in a different directory. AFS refuses to make hard links # between files in different directories, so we want to check # it. local target = [ new file-target test-hardlink : : $(project) : [ new action $(source-target) : link.hardlink : [ new property-set symlink ] ] ] ; if [ configure.try-build $(target) : $(ps) : "hardlinks supported" ] { .can-hardlink = true ; } else { .can-hardlink = false ; } } if $(.can-hardlink) = true { return true ; } } class file-or-directory-reference : basic-target { import virtual-target ; import property-set ; import path ; rule construct ( name : source-targets * : property-set ) { return [ property-set.empty ] [ virtual-target.from-file $(self.name) : [ location ] : $(self.project) ] ; } # Returns true if the referred file really exists. rule exists ( ) { location ; return $(self.file-path) ; } # Returns the location of target. Needed by 'testing.jam'. rule location ( ) { if ! $(self.file-location) { local source-location = [ $(self.project).get source-location ] ; for local src-dir in $(source-location) { if ! $(self.file-location) { local location = [ path.root $(self.name) $(src-dir) ] ; if [ path.exists [ path.native $(location) ] ] { self.file-location = $(src-dir) ; self.file-path = $(location) ; } } } } return $(self.file-location) ; } } class symlink-target-class : basic-target { import path ; import virtual-target ; import link ; import os ; import type ; rule construct ( name : source-target : property-set ) { local location = [ path.join [ $(source-target).path ] [ $(source-target).name ] ] ; local files = [ path.glob-tree $(location) : * ] ; local targets ; # If we have symlinks, don't bother checking # for hardlinks and junctions. if ! [ link.can-symlink $(self.project) : $(property-set) ] { link.can-junction $(self.project) : $(property-set) ; link.can-hardlink $(self.project) : $(property-set) ; } if [ $(property-set).get ] { property-set = [ property-set.create [ property.select : [ $(property-set).raw ] ] ] ; } else { local path,relative-to-build-dir = [ $(property-set).target-path ] ; local path = $(path,relative-to-build-dir[1]) ; local relative-to-build-dir = $(path,relative-to-build-dir[2]) ; if $(relative-to-build-dir) { path = [ path.join [ $(self.project).build-dir ] $(path) ] ; } property-set = [ property-set.create $(path) ] ; } local a = [ new non-scanning-action $(source-target) : link.do-link-recursively : $(property-set) ] ; local t = [ new notfile-target $(name) : $(self.project) : $(a) ] ; return [ property-set.empty ] [ virtual-target.register $(t) ] ; } } rule do-file-link { local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ; local source = [ path.native [ path.relative-to [ path.pwd ] $(>) ] ] ; local old-source = [ on $(target) return $(LINK-SOURCE) ] ; if $(old-source) { import errors ; errors.user-error Cannot create link $(target) to $(source). : Link previously defined to another file, $(old-source[1]). ; } LINK-SOURCE on $(target) = $(source) $(.current-target) ; LOCATE on $(target) = . ; DEPENDS $(.current-target) : $(target) ; if $(.can-symlink) = true { DEPENDS $(target) : $(source) ; link.mklink $(target) : $(source) ; } else if $(.can-hardlink) = true { DEPENDS $(target) : $(source) ; link.hardlink $(target) : $(source) ; } else { DEPENDS $(target) : $(source) ; common.copy $(target) : $(source) ; } } rule do-link { local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ; local source = [ path.native [ path.relative-to [ path.pwd ] $(>) ] ] ; local relative = [ path.native [ path.relative-to [ path.parent $(<) ] $(>) ] ] ; if ! [ on $(target) return $(MKLINK_OR_DIR) ] { LOCATE on $(target) = . ; DEPENDS $(.current-target) : $(target) ; mklink-or-dir $(target) : $(source) ; } if [ os.name ] = NT { if $(.can-symlink) = true { MKLINK_OR_DIR on $(target) = mklink /D \"$(target)\" \"$(relative)\" ; } else { # This function should only be called # if either symlinks or junctions are supported. # To get here $(.can-junction) must be true. mklink-opt = /J ; MKLINK_OR_DIR on $(target) = mklink /J \"$(target)\" \"$(source)\" ; } } else { MKLINK_OR_DIR on $(target) = ln -s $(relative) $(target) ; } } rule force-update { local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ; ALWAYS $(target) ; } rule do-split { local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ; if ! [ on $(target) return $(MKLINK_OR_DIR) ] { LOCATE on $(target) = . ; DEPENDS $(.current-target) : $(target) ; common.mkdir $(target) ; } MKLINK_OR_DIR on $(target) = mkdir \"$(target)\" ; } rule do-rm { local target = [ path.native [ path.relative-to [ path.pwd ] $(<) ] ] ; ALWAYS $(target) ; RM on $(target) = rmdir ; link.rm $(target) ; } rule mklink-or-dir { NOUPDATE $(<) ; } actions mklink-or-dir { $(MKLINK_OR_DIR) } rule link-entries ( target : files * : split ? : deleted ? ) { for local s in $(files) { local t = [ path.join $(target) [ path.basename $(s) ] ] ; if ! $(.known-dirs.$(t)) { local t = [ path.native [ path.relative-to [ path.pwd ] $(t) ] ] ; local s = [ path.native [ path.relative-to [ path.pwd ] $(target) ] ] ; LOCATE on $(t) = . ; DEPENDS $(t) : $(s) ; NOUPDATE $(s) ; } if $(split) { link-recursively $(t) : $(s) : : $(deleted) ; } else { link-entries $(t) : [ path.glob $(s) : * ] ; } } if ! $(.known-dirs.$(target)) { .known-dirs.$(target) += $(files) ; .known-dirs.base.$(target) = $(.current-target) ; } } rule link-recursively ( target : source : no-recurse ? : deleted ? ) { if $(deleted) { force-update $(target) ; } local split ; if [ CHECK_IF_FILE [ path.native $(source) ] ] { do-file-link $(target) : $(source) ; } else if $(.known-dirs.$(target)) && ! $(no-recurse) { split = true ; if ! $(.split-dirs.$(target)) { if [ READLINK [ path.native $(target) ] ] { if ! $(deleted) { do-rm $(target) ; deleted = true ; .deleted-dirs.$(target) = true ; } } local .current-target = $(.known-dirs.base.$(target)) ; for local s in $(.known-dirs.$(target)) { local t = [ path.join $(target) [ path.basename $(s) ] ] ; link-recursively $(t) : $(s) : flat : $(deleted) ; } do-split $(target) ; } else if $(.deleted-dirs.$(target)) { deleted = true ; } } else if [ path.exists [ path.native $(target) ] ] && ! $(deleted) { local link-target = [ READLINK [ path.native $(target) ] ] ; if $(link-target) { local full-path = [ path.root [ path.make $(link-target) ] [ path.parent $(target) ] ] ; # HACK: Take advantage of the fact that path.glob # normalizes its arguments. If full-path and # source are different, but both are empty, they # will compare equal, but that's okay because # for the purposes of this module, empty directories # are equivalent. if [ path.glob $(full-path) : * ] != [ path.glob $(source) : * ] { if ! $(deleted) { do-rm $(target) ; deleted = true ; .deleted-dirs.$(target) = true ; } do-split $(target) ; split = true ; } } else { do-split $(target) ; split = true ; } } else if $(.can-symlink) = false && $(.can-junction) = false { if [ READLINK [ path.native $(target) ] ] { if ! $(deleted) { do-rm $(target) ; deleted = true ; .deleted-dirs.$(target) = true ; } } do-split $(target) ; split = true ; } else { do-link $(target) : $(source) ; } if $(split) { .split-dirs.$(target) = true ; } if ! $(no-recurse) { link-entries $(target) : [ path.glob $(source) : * ] : $(split) : $(deleted) ; } } rule do-link-recursively ( target : source : properties * ) { local target-path = [ property.select : $(properties) ] ; local source-path = [ on $(source) return $(LOCATE) ] [ on $(source) return $(SEARCH) ] ; local absolute-target = [ path.root [ path.join [ path.make $(target-path[1]:G=) ] [ path.basename [ path.make $(source:G=) ] ] ] [ path.pwd ] ] ; local absolute-source = [ path.root [ path.root [ path.make $(source:G=) ] [ path.make $(source-path[1]) ] ] [ path.pwd ] ] ; local .current-target = $(target) ; link-recursively $(absolute-target) : $(absolute-source) ; } rule mklink { local target-path = [ on $(<) return $(LOCATE) ] [ on $(<) return $(SEARCH) ] . ; local source-path = [ on $(>) return $(LOCATE) ] [ on $(>) return $(SEARCH) ] . ; local relative-path = [ path.relative-to [ path.parent [ path.join [ path.root [ path.make $(target-path[1]) ] [ path.pwd ] ] [ path.make $(<:G=) ] ] ] [ path.join [ path.root [ path.make $(source-path[1]) ] [ path.pwd ] ] [ path.make $(>:G=) ] ] ] ; PATH_TO_SOURCE on $(<) = [ path.native $(relative-path) ] ; } if [ os.name ] = NT { actions junction { if exist "$(<)" rmdir "$(<)" mklink /J "$(<)" "$(>)" } actions mklink { if exist "$(<)" del "$(<)" mklink "$(<)" "$(PATH_TO_SOURCE)" } actions hardlink { if exist "$(<)" del "$(<)" mklink /H "$(<)" "$(>)" } actions rm { rmdir "$(<)" } } else { actions mklink { ln -f -s "$(PATH_TO_SOURCE)" "$(<)" } actions hardlink { ln -f "$(>)" "$(<)" } actions rm { rm "$(<)" } } rule link-directory ( name : sources : requirements * : default-build * : usage-requirements * ) { local project = [ project.current ] ; sources = [ new file-or-directory-reference $(sources) : $(project) ] ; targets.main-target-alternative $(sources) ; return [ targets.main-target-alternative [ new symlink-target-class $(name) : $(project) : [ targets.main-target-sources $(sources) : $(name) : no-renaming ] : [ targets.main-target-requirements $(requirements) : $(project) ] : [ targets.main-target-default-build : $(project) ] : [ targets.main-target-usage-requirements $(usage-requirements) : $(project) ] ] ] ; } IMPORT $(__name__) : link-directory : : link-directory ;