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