1# Copyright 2002, 2003 Dave Abrahams 2# Copyright 2002, 2005, 2006 Rene Rivera 3# Copyright 2002, 2003, 2004, 2005, 2006 Vladimir Prus 4# Distributed under the Boost Software License, Version 1.0. 5# (See accompanying file LICENSE_1_0.txt or copy at 6# http://www.boost.org/LICENSE_1_0.txt) 7 8# Implements project representation and loading. Each project is represented by: 9# - a module where all the Jamfile content lives. 10# - an instance of 'project-attributes' class. 11# (given a module name, can be obtained using the 'attributes' rule) 12# - an instance of 'project-target' class (from targets.jam) 13# (given a module name, can be obtained using the 'target' rule) 14# 15# Typically, projects are created as result of loading a Jamfile, which is done 16# by rules 'load' and 'initialize', below. First, a module is prepared and a new 17# project-attributes instance is created. Some rules necessary for all projects 18# are added to the module (see the 'project-rules' module). Default project 19# attributes are set (inheriting parent project attributes, if it exists). After 20# that the Jamfile is read. It can declare its own attributes using the 21# 'project' rule which will be combined with any already set. 22# 23# The 'project' rule can also declare a project id which will be associated with 24# the project module. 25# 26# Besides Jamfile projects, we also support 'standalone' projects created by 27# calling 'initialize' in an arbitrary module and not specifying the project's 28# location. After the call, the module can call the 'project' rule, declare main 29# targets and behave as a regular project except that, since it is not 30# associated with any location, it should only declare prebuilt targets. 31# 32# The list of all loaded Jamfiles is stored in the .project-locations variable. 33# It is possible to obtain a module name for a location using the 'module-name' 34# rule. Standalone projects are not recorded and can only be referenced using 35# their project id. 36 37import "class" : new ; 38import modules ; 39import path ; 40import print ; 41import property-set ; 42import sequence ; 43 44 45.debug-loading = [ MATCH ^(--debug-loading)$ : [ modules.peek : ARGV ] ] ; 46 47 48# Loads the Jamfile at the given location. After loading, project global file 49# and Jamfiles needed by the requested one will be loaded recursively. If the 50# Jamfile at that location is loaded already, does nothing. Returns the project 51# module for the Jamfile. 52# 53rule load ( jamfile-location : synthesize ? ) 54{ 55 local module-name = [ module-name $(jamfile-location) ] ; 56 # If Jamfile is already loaded, do not try again. 57 if ! $(module-name) in $(.jamfile-modules) 58 { 59 if $(.debug-loading) 60 { 61 ECHO Loading Jamfile at '$(jamfile-location)' ; 62 } 63 64 load-jamfile $(jamfile-location) : $(module-name) : $(synthesize) ; 65 66 # We want to make sure that child project are loaded only after parent 67 # projects. In particular, because parent projects define attributes 68 # which are then inherited by children, and we do not want children to 69 # be loaded before parent has defined everything. 70 # 71 # While "build-project" and "use-project" can potentially refer to child 72 # projects from parent projects, we do not immediately load child 73 # projects when seeing those attributes. Instead, we record the minimal 74 # information to be used only later. 75 load-used-projects $(module-name) ; 76 } 77 return $(module-name) ; 78} 79 80 81rule load-used-projects ( module-name ) 82{ 83 local used = [ modules.peek $(module-name) : .used-projects ] ; 84 local location = [ attribute $(module-name) location ] ; 85 while $(used) 86 { 87 local id = $(used[1]) ; 88 local where = [ path.make $(used[2]) ] ; 89 register-id $(id) : [ load [ path.root $(where) $(location) ] ] ; 90 used = $(used[3-]) ; 91 } 92} 93 94 95# Note the use of character groups, as opposed to listing 'Jamroot' and 96# 'jamroot'. With the latter, we would get duplicate matches on Windows and 97# would have to eliminate duplicates. 98JAMROOT ?= [ modules.peek : JAMROOT ] ; 99JAMROOT ?= project-root.jam [Jj]amroot [Jj]amroot.jam ; 100 101 102# Loads parent of Jamfile at 'location'. Issues an error if nothing is found. 103# 104rule load-parent ( location ) 105{ 106 local found = [ path.glob-in-parents $(location) : $(JAMROOT) $(JAMFILE) ] ; 107 if ! $(found) 108 { 109 import errors ; 110 errors.error Could not find parent "for" project at '$(location)' : 111 Did not find Jamfile.jam or Jamroot.jam "in" any parent directory. ; 112 } 113 return [ load $(found[1]:D) ] ; 114} 115 116 117# Returns the project module corresponding to the given project-id or plain 118# directory name. Returns nothing if such a project can not be found. 119# 120rule find ( name : current-location ) 121{ 122 local project-module ; 123 124 # Try interpreting name as project id. 125 if [ path.is-rooted $(name) ] 126 { 127 project-module = $($(name).jamfile-module) ; 128 } 129 130 if ! $(project-module) 131 { 132 local location = [ path.root [ path.make $(name) ] $(current-location) ] 133 ; 134 135 # If no project is registered for the given location, try to load it. 136 # First see if we have a Jamfile. If not, then see if we might have a 137 # project root willing to act as a Jamfile. In that case, project root 138 # must be placed in the directory referred to by id. 139 140 project-module = [ module-name $(location) ] ; 141 if ! $(project-module) in $(.jamfile-modules) 142 { 143 if [ path.glob $(location) : $(JAMROOT) $(JAMFILE) ] 144 { 145 project-module = [ load $(location) ] ; 146 } 147 else 148 { 149 project-module = ; 150 } 151 } 152 } 153 154 return $(project-module) ; 155} 156 157 158# Returns the name of the module corresponding to 'jamfile-location'. If no 159# module corresponds to that location yet, associates the default module name 160# with that location. 161# 162rule module-name ( jamfile-location ) 163{ 164 if ! $(.module.$(jamfile-location)) 165 { 166 # Root the path, so that locations are always unambiguous. Without this, 167 # we can not decide if '../../exe/program1' and '.' are the same paths. 168 local normalized = [ path.root $(jamfile-location) [ path.pwd ] ] ; 169 170 # Quick & dirty fix to get the same module name when we supply two 171 # equivalent location paths, e.g. 'd:\Foo' & 'D:\fOo\bar\..' on Windows. 172 # Note that our current implementation will not work correctly if the 173 # given location references an empty folder, but in that case any later 174 # attempt to load a Jamfile from this location will fail anyway. 175 # FIXME: Implement this cleanly. Support for this type of path 176 # normalization already exists internally in Boost Jam and the current 177 # fix relies on the GLOB builtin rule using that support. Most likely we 178 # just need to add a new builtin rule to do this explicitly. 179 normalized = [ NORMALIZE_PATH $(normalized) ] ; 180 local glob-result = [ GLOB [ path.native $(normalized) ] : * ] ; 181 if $(glob-result) 182 { 183 normalized = $(glob-result[1]:D) ; 184 } 185 .module.$(jamfile-location) = Jamfile<$(normalized)> ; 186 } 187 return $(.module.$(jamfile-location)) ; 188} 189 190 191# Default patterns to search for the Jamfiles to use for build declarations. 192# 193JAMFILE = [ modules.peek : JAMFILE ] ; 194JAMFILE ?= [Bb]uild.jam [Jj]amfile.v2 [Jj]amfile [Jj]amfile.jam ; 195 196 197# Find the Jamfile at the given location. This returns the exact names of all 198# the Jamfiles in the given directory. The optional parent-root argument causes 199# this to search not the given directory but the ones above it up to the 200# parent-root directory. 201# 202rule find-jamfile ( 203 dir # The directory(s) to look for a Jamfile. 204 parent-root ? # Optional flag indicating to search for the parent Jamfile. 205 : no-errors ? 206 ) 207{ 208 # Glob for all the possible Jamfiles according to the match pattern. 209 # 210 local jamfile-glob = ; 211 if $(parent-root) 212 { 213 if ! $(.parent-jamfile.$(dir)) 214 { 215 .parent-jamfile.$(dir) = [ path.glob-in-parents $(dir) : $(JAMFILE) 216 ] ; 217 } 218 jamfile-glob = $(.parent-jamfile.$(dir)) ; 219 } 220 else 221 { 222 if ! $(.jamfile.$(dir)) 223 { 224 .jamfile.$(dir) = [ path.glob $(dir) : $(JAMFILE) ] ; 225 } 226 jamfile-glob = $(.jamfile.$(dir)) ; 227 228 } 229 230 local jamfile-to-load = $(jamfile-glob) ; 231 # Multiple Jamfiles found in the same place. Warn about this and ensure we 232 # use only one of them. As a temporary convenience measure, if there is 233 # Jamfile.v2 among found files, suppress the warning and use it. 234 # 235 if $(jamfile-to-load[2-]) 236 { 237 local v2-jamfiles = [ MATCH ^(.*[Jj]amfile\\.v2)|(.*[Bb]uild\\.jam)$ : 238 $(jamfile-to-load) ] ; 239 240 if $(v2-jamfiles) && ! $(v2-jamfiles[2]) 241 { 242 jamfile-to-load = $(v2-jamfiles) ; 243 } 244 else 245 { 246 local jamfile = [ path.basename $(jamfile-to-load[1]) ] ; 247 ECHO "warning: Found multiple Jamfiles at '"$(dir)"'!" 248 "Loading the first one: '$(jamfile)'." ; 249 } 250 251 jamfile-to-load = $(jamfile-to-load[1]) ; 252 } 253 254 # Could not find it, error. 255 # 256 if ! $(no-errors) && ! $(jamfile-to-load) 257 { 258 import errors ; 259 errors.error Unable to load Jamfile. 260 : Could not find a Jamfile in directory '$(dir)'. 261 : Attempted to find it with pattern '$(JAMFILE:J=" ")'. 262 : Please consult the documentation at 'http://www.boost.org'. ; 263 } 264 265 return $(jamfile-to-load) ; 266} 267 268 269# Load a Jamfile at the given directory. Returns nothing. Will attempt to load 270# the file as indicated by the JAMFILE patterns. Effect of calling this rule 271# twice with the same 'dir' is undefined. 272# 273local rule load-jamfile ( dir : jamfile-module : synthesize ? ) 274{ 275 # See if the Jamfile is where it should be. 276 # 277 local jamfile-to-load = [ path.glob $(dir) : $(JAMROOT) ] ; 278 if ! $(jamfile-to-load) 279 { 280 jamfile-to-load = [ find-jamfile $(dir) : $(synthesize) ] ; 281 } 282 283 if $(jamfile-to-load[2]) 284 { 285 import errors ; 286 errors.error "Multiple Jamfiles found at '$(dir)'" : 287 "Filenames are: " $(jamfile-to-load:D=) ; 288 } 289 290 if ! $(jamfile-to-load) && $(synthesize) 291 { 292 jamfile-to-load = $(dir)/@ ; 293 } 294 295 # Now load the Jamfile in its own context. 296 # The call to 'initialize' may load the parent Jamfile, which might contain 297 # a 'use-project' or a 'project.load' call, causing a second attempt to load 298 # the same project we are loading now. Checking inside .jamfile-modules 299 # prevents that second attempt from messing things up. 300 if ! $(jamfile-module) in $(.jamfile-modules) 301 { 302 local previous-project = $(.current-project) ; 303 304 # Initialize the Jamfile module before loading. 305 initialize $(jamfile-module) : [ path.parent $(jamfile-to-load) ] : 306 $(jamfile-to-load:BS) ; 307 308 if ! $(jamfile-module) in $(.jamfile-modules) 309 { 310 .jamfile-modules += $(jamfile-module) ; 311 312 local saved-project = $(.current-project) ; 313 314 mark-as-user $(jamfile-module) ; 315 if $(jamfile-to-load:B) = "@" 316 { 317 # Not a real jamfile to load. Synthsize the load. 318 modules.poke $(jamfile-module) : __name__ : $(jamfile-module) ; 319 modules.poke $(jamfile-module) : __file__ : [ path.native $(jamfile-to-load) ] ; 320 modules.poke $(jamfile-module) : __binding__ : [ path.native $(jamfile-to-load) ] ; 321 } 322 else 323 { 324 modules.load $(jamfile-module) : [ path.native $(jamfile-to-load) ] 325 : . ; 326 if [ MATCH ^($(JAMROOT))$ : $(jamfile-to-load:BS) ] 327 { 328 jamfile = [ find-jamfile $(dir) : no-errors ] ; 329 if $(jamfile) 330 { 331 load-aux $(jamfile-module) : [ path.native $(jamfile) ] ; 332 } 333 } 334 } 335 336 # Now do some checks. 337 if $(.current-project) != $(saved-project) 338 { 339 import errors ; 340 errors.error 341 The value of the .current-project variable has magically 342 : changed after loading a Jamfile. This means some of the 343 : targets might be defined in the wrong project. 344 : after loading $(jamfile-module) 345 : expected value $(saved-project) 346 : actual value $(.current-project) ; 347 } 348 349 end-load $(previous-project) ; 350 351 if $(.global-build-dir) 352 { 353 if [ attribute $(jamfile-module) location ] && ! [ attribute 354 $(jamfile-module) id ] 355 { 356 local project-root = [ attribute $(jamfile-module) 357 project-root ] ; 358 if $(project-root) = $(dir) 359 { 360 ECHO "warning: the --build-dir option was specified" ; 361 ECHO "warning: but Jamroot at '$(dir)'" ; 362 ECHO "warning: specified no project id" ; 363 ECHO "warning: the --build-dir option will be ignored" ; 364 } 365 } 366 } 367 } 368 } 369} 370 371 372# Called when done loading a project module. Restores the current project to its 373# previous value and does some additional checking to make sure our 'currently 374# loaded project' identifier does not get left with an invalid value. 375# 376rule end-load ( previous-project ? ) 377{ 378 if ! $(.current-project) 379 { 380 import errors ; 381 errors.error Ending project loading requested when there was no project 382 currently being loaded. ; 383 } 384 385 if ! $(previous-project) && $(.saved-current-project) 386 { 387 import errors ; 388 errors.error Ending project loading requested with no 'previous project' 389 when there were other projects still marked as being loaded 390 recursively. ; 391 } 392 393 .current-project = $(previous-project) ; 394} 395 396 397rule mark-as-user ( module-name ) 398{ 399 if USER_MODULE in [ RULENAMES ] 400 { 401 USER_MODULE $(module-name) ; 402 } 403} 404 405 406rule load-aux ( module-name : file ) 407{ 408 mark-as-user $(module-name) ; 409 410 module $(module-name) 411 { 412 include $(2) ; 413 local rules = [ RULENAMES $(1) ] ; 414 IMPORT $(1) : $(rules) : $(1) : $(1).$(rules) ; 415 } 416} 417 418 419.global-build-dir = [ MATCH ^--build-dir=(.*)$ : [ modules.peek : ARGV ] ] ; 420if $(.global-build-dir) 421{ 422 # If the option is specified several times, take the last value. 423 .global-build-dir = [ path.make $(.global-build-dir[-1]) ] ; 424} 425 426 427# Initialize the module for a project. 428# 429rule initialize ( 430 module-name # The name of the project module. 431 : location ? # The location (directory) of the project to initialize. If 432 # not specified, a standalone project will be initialized. 433 : basename ? 434 ) 435{ 436 if $(.debug-loading) 437 { 438 ECHO "Initializing project '$(module-name)'" ; 439 } 440 441 local jamroot ; 442 443 local parent-module ; 444 if $(module-name) = test-config 445 { 446 # No parent. 447 } 448 else if $(module-name) = site-config 449 { 450 parent-module = test-config ; 451 } 452 else if $(module-name) = user-config 453 { 454 parent-module = site-config ; 455 } 456 else if $(module-name) = project-config 457 { 458 parent-module = user-config ; 459 } 460 else if $(location) && ! [ MATCH ^($(JAMROOT))$ : $(basename) ] 461 { 462 # We search for parent/jamroot only if this is a jamfile project, i.e. 463 # if is not a standalone or a jamroot project. 464 parent-module = [ load-parent $(location) ] ; 465 } 466 else if $(location) 467 { 468 # We have a jamroot project. Inherit from user-config (or project-config 469 # if it exists). 470 if $(project-config.attributes) 471 { 472 parent-module = project-config ; 473 } 474 else 475 { 476 parent-module = user-config ; 477 } 478 jamroot = true ; 479 } 480 481 # TODO: need to consider if standalone projects can do anything but define 482 # prebuilt targets. If so, we need to give them a more sensible "location", 483 # so that source paths are correct. 484 location ?= "" ; 485 # Create the module for the Jamfile first. 486 module $(module-name) 487 { 488 } 489 490 # load-parent can end up loading this module again. Make sure this is not 491 # duplicated. 492 if ! $($(module-name).attributes) 493 { 494 $(module-name).attributes = [ new project-attributes $(location) 495 $(module-name) ] ; 496 local attributes = $($(module-name).attributes) ; 497 498 if $(location) 499 { 500 $(attributes).set source-location : [ path.make $(location) ] : 501 exact ; 502 } 503 else 504 { 505 local cfgs = project site test user ; 506 if ! $(module-name) in $(cfgs)-config 507 { 508 # This is a standalone project with known location. Set its 509 # source location so it can declare targets. This is needed so 510 # you can put a .jam file with your sources and use it via 511 # 'using'. Standard modules (in the 'tools' subdir) may not 512 # assume source dir is set. 513 local s = [ modules.binding $(module-name) ] ; 514 if ! $(s) 515 { 516 import errors ; 517 errors.error Could not determine project location 518 $(module-name) ; 519 } 520 $(attributes).set source-location : $(s:D) : exact ; 521 } 522 } 523 524 $(attributes).set requirements : [ property-set.empty ] : exact ; 525 $(attributes).set usage-requirements : [ property-set.empty ] : exact ; 526 527 # Import rules common to all project modules from project-rules module, 528 # defined at the end of this file. 529 local rules = [ RULENAMES project-rules ] ; 530 IMPORT project-rules : $(rules) : $(module-name) : $(rules) ; 531 532 if $(parent-module) 533 { 534 inherit-attributes $(module-name) : $(parent-module) ; 535 $(attributes).set parent-module : $(parent-module) : exact ; 536 } 537 538 if $(jamroot) 539 { 540 $(attributes).set project-root : $(location) : exact ; 541 if ! $(.first-project-root) 542 { 543 .first-project-root = $(module-name) ; 544 } 545 } 546 547 local parent ; 548 if $(parent-module) 549 { 550 parent = [ target $(parent-module) ] ; 551 } 552 553 if ! $(.target.$(module-name)) 554 { 555 local requirements = [ attribute $(module-name) requirements ] ; 556 .target.$(module-name) = [ new project-target $(module-name) : 557 $(module-name) $(parent) : $(requirements) ] ; 558 559 if $(.debug-loading) 560 { 561 ECHO Assigned project target $(.target.$(module-name)) to 562 '$(module-name)' ; 563 } 564 } 565 } 566 567 .current-project = [ target $(module-name) ] ; 568} 569 570 571# Make 'project-module' inherit attributes of project root and parent module. 572# 573rule inherit-attributes ( project-module : parent-module ) 574{ 575 local attributes = $($(project-module).attributes) ; 576 local pattributes = [ attributes $(parent-module) ] ; 577 # Parent module might be locationless configuration module. 578 if [ modules.binding $(parent-module) ] 579 { 580 $(attributes).set parent : 581 [ path.parent [ path.make [ modules.binding $(parent-module) ] ] ] ; 582 } 583 $(attributes).set project-root : 584 [ $(pattributes).get project-root ] : exact ; 585 $(attributes).set default-build : 586 [ $(pattributes).get default-build ] ; 587 $(attributes).set requirements : 588 [ $(pattributes).get requirements ] : exact ; 589 $(attributes).set usage-requirements : 590 [ $(pattributes).get usage-requirements ] : exact ; 591 592 local parent-build-dir = [ $(pattributes).get build-dir ] ; 593 if $(parent-build-dir) 594 { 595 # Have to compute relative path from parent dir to our dir. Convert both 596 # paths to absolute, since we cannot find relative path from ".." to 597 # ".". 598 599 local location = [ attribute $(project-module) location ] ; 600 local parent-location = [ attribute $(parent-module) location ] ; 601 602 local pwd = [ path.pwd ] ; 603 local parent-dir = [ path.root $(parent-location) $(pwd) ] ; 604 local our-dir = [ path.root $(location) $(pwd) ] ; 605 $(attributes).set build-dir : [ path.join $(parent-build-dir) 606 [ path.relative $(our-dir) $(parent-dir) ] ] : exact ; 607 } 608} 609 610 611# Returns whether the given string is a valid registered project id. 612# 613rule is-registered-id ( id ) 614{ 615 return $($(id).jamfile-module) ; 616} 617 618 619# Associate the given id with the given project module. Returns the possibly 620# corrected project id. 621# 622rule register-id ( id : module ) 623{ 624 id = [ path.root $(id) / ] ; 625 626 if [ MATCH (//) : $(id) ] 627 { 628 import errors ; 629 errors.user-error Project id may not contain two consecutive slash 630 characters (project id: '$(id)'). ; 631 } 632 633 local orig-module = $($(id).jamfile-module) ; 634 if $(orig-module) && $(orig-module) != $(module) 635 { 636 local new-file = [ modules.peek $(module) : __file__ ] ; 637 local new-location = [ project.attribute $(module) location ] ; 638 639 local orig-file = [ modules.peek $(orig-module) : __file__ ] ; 640 local orig-main-id = [ project.attribute $(orig-module) id ] ; 641 local orig-location = [ project.attribute $(orig-module) location ] ; 642 local orig-project = [ target $(orig-module) ] ; 643 local orig-name = [ $(orig-project).name ] ; 644 645 import errors ; 646 errors.user-error Attempt to redeclare already registered project id 647 '$(id)'. 648 : Original project: 649 : " " Name: $(orig-name:E=---) 650 : " " Module: $(orig-module) 651 : " " Main id: $(orig-main-id:E=---) 652 : " " File: $(orig-file:E=---) 653 : " " Location: $(orig-location:E=---) 654 : New project: 655 : " " Module: $(module) 656 : " " File: $(new-file:E=---) 657 : " " Location: $(new-location:E=---) ; 658 } 659 660 $(id).jamfile-module = $(module) ; 661 return $(id) ; 662} 663 664 665# Class keeping all the attributes of a project. 666# 667# The standard attributes are "id", "location", "project-root", "parent" 668# "requirements", "default-build", "source-location" and "projects-to-build". 669# 670class project-attributes 671{ 672 import path ; 673 import print ; 674 import project ; 675 import property ; 676 import property-set ; 677 import sequence ; 678 679 rule __init__ ( location project-module ) 680 { 681 self.location = $(location) ; 682 self.project-module = $(project-module) ; 683 } 684 685 # Set the named attribute from the specification given by the user. The 686 # value actually set may be different. 687 # 688 rule set ( attribute : specification * 689 : exact ? # Sets value from 'specification' without any processing. 690 ) 691 { 692 if $(exact) 693 { 694 self.$(attribute) = $(specification) ; 695 } 696 else if $(attribute) = "requirements" 697 { 698 local result = [ property-set.refine-from-user-input 699 $(self.requirements) : $(specification) 700 : $(self.project-module) : $(self.location) ] ; 701 702 if $(result[1]) = "@error" 703 { 704 import errors : error : errors.error ; 705 errors.error Requirements for project at '$(self.location)' 706 conflict with parent's. : Explanation: $(result[2-]) ; 707 } 708 709 self.requirements = $(result) ; 710 } 711 else if $(attribute) = "usage-requirements" 712 { 713 local unconditional ; 714 for local p in $(specification) 715 { 716 local split = [ property.split-conditional $(p) ] ; 717 split ?= nothing $(p) ; 718 unconditional += $(split[2]) ; 719 } 720 721 local non-free = [ property.remove free : $(unconditional) ] ; 722 if $(non-free) 723 { 724 import errors : error : errors.error ; 725 errors.error usage-requirements $(specification) have non-free 726 properties $(non-free) ; 727 } 728 local t = [ property.translate-paths $(specification) : 729 $(self.location) ] ; 730 if $(self.usage-requirements) 731 { 732 self.usage-requirements = [ property-set.create 733 [ $(self.usage-requirements).raw ] $(t) ] ; 734 } 735 else 736 { 737 self.usage-requirements = [ property-set.create $(t) ] ; 738 } 739 } 740 else if $(attribute) = "default-build" 741 { 742 self.default-build = [ property.make $(specification) ] ; 743 } 744 else if $(attribute) = "source-location" 745 { 746 self.source-location = ; 747 for local src-path in $(specification) 748 { 749 self.source-location += [ path.root [ path.make $(src-path) ] 750 $(self.location) ] ; 751 } 752 } 753 else if $(attribute) = "build-dir" 754 { 755 self.build-dir = [ path.root [ path.make $(specification) ] 756 $(self.location) ] ; 757 } 758 else if $(attribute) = "id" 759 { 760 self.id = [ project.register-id $(specification) : 761 $(self.project-module) ] ; 762 } 763 else if ! $(attribute) in "default-build" "location" "parent" 764 "projects-to-build" "project-root" "source-location" 765 { 766 import errors : error : errors.error ; 767 errors.error Invalid project attribute '$(attribute)' specified for 768 project at '$(self.location)' ; 769 } 770 else 771 { 772 self.$(attribute) = $(specification) ; 773 } 774 } 775 776 # Returns the value of the given attribute. 777 # 778 rule get ( attribute ) 779 { 780 return $(self.$(attribute)) ; 781 } 782 783 # Returns whether these attributes belong to a Jamroot project module. 784 # 785 rule is-jamroot ( ) 786 { 787 if $(self.location) && $(self.project-root) = $(self.location) 788 { 789 return true ; 790 } 791 } 792 793 # Prints the project attributes. 794 # 795 rule print ( ) 796 { 797 local id = '$(self.id)' ; 798 print.section $(id:E=(none)) ; 799 print.list-start ; 800 print.list-item "Parent project:" $(self.parent:E=(none)) ; 801 print.list-item "Requirements:" [ $(self.requirements).raw ] ; 802 print.list-item "Default build:" $(self.default-build) ; 803 print.list-item "Source location:" $(self.source-location) ; 804 print.list-item "Projects to build:" [ sequence.insertion-sort 805 $(self.projects-to-build) ] ; 806 print.list-end ; 807 } 808} 809 810 811# Returns the build directory for standalone projects 812# 813rule standalone-build-dir ( ) 814{ 815 project = [ target $(.first-project-root) ] ; 816 return [ path.join [ $(project).build-dir ] standalone ] ; 817} 818 819# Returns the project which is currently being loaded. 820# 821rule current ( ) 822{ 823 if ! $(.current-project) 824 { 825 import errors ; 826 errors.error Reference to the project currently being loaded requested 827 when there was no project module being loaded. ; 828 } 829 return $(.current-project) ; 830} 831 832 833# Temporarily changes the current project to 'project'. Should be followed by 834# 'pop-current'. 835# 836rule push-current ( project ) 837{ 838 .saved-current-project += $(.current-project) ; 839 .current-project = $(project) ; 840} 841 842 843rule pop-current ( ) 844{ 845 .current-project = $(.saved-current-project[-1]) ; 846 .saved-current-project = $(.saved-current-project[1--2]) ; 847} 848 849 850# Returns the project-attribute instance for the specified Jamfile module. 851# 852rule attributes ( project ) 853{ 854 return $($(project).attributes) ; 855} 856 857 858# Returns the value of the specified attribute in the specified Jamfile module. 859# 860rule attribute ( project attribute ) 861{ 862 return [ $($(project).attributes).get $(attribute) ] ; 863} 864 865 866# Returns whether a project module is one of Boost Build's configuration 867# modules. 868# 869rule is-config-module ( project ) 870{ 871 local cfgs = project site test user ; 872 if $(project) in $(cfgs)-config 873 { 874 return true ; 875 } 876} 877 878 879# Returns whether a project module is a Jamroot project module. 880# 881rule is-jamroot-module ( project ) 882{ 883 return [ $($(project).attributes).is-jamroot ] ; 884} 885 886 887# Returns a project's parent jamroot module. Returns nothing if there is no such 888# module, i.e. if this is a standalone project or one of the internal Boost 889# Build configuration projects. 890# 891rule get-jamroot-module ( project ) 892{ 893 local jamroot-location = [ attribute $(project) project-root ] ; 894 if $(jamroot-location) 895 { 896 return [ module-name $(jamroot-location) ] ; 897 } 898} 899 900 901# Returns the project target corresponding to the 'project-module'. 902# 903rule target ( project-module ) 904{ 905 if ! $(.target.$(project-module)) 906 { 907 import errors ; 908 errors.user-error Project target requested but not yet assigned for 909 module '$(project-module)'. ; 910 } 911 return $(.target.$(project-module)) ; 912} 913 914 915# Defines a Boost.Build extension project. Such extensions usually contain 916# library targets and features that can be used by many people. Even though 917# extensions are really projects, they can be initialized as a module would be 918# with the "using" (project.project-rules.using) mechanism. 919# 920rule extension ( id space ? : options * : * ) 921{ 922 # The caller is a standalone module for the extension. 923 local mod = [ CALLER_MODULE ] ; 924 925 # We need to do the rest within the extension module. 926 module $(mod) 927 { 928 import path ; 929 930 # Find the root project. 931 local root-project = [ project.current ] ; 932 root-project = [ $(root-project).project-module ] ; 933 while 934 [ project.attribute $(root-project) parent-module ] && 935 [ project.attribute $(root-project) parent-module ] != user-config 936 { 937 root-project = [ project.attribute $(root-project) parent-module ] ; 938 } 939 940 # Default to creating extensions in /ext/.. project space. 941 local id = $(1[1]) ; 942 local space = $(1[2]) ; 943 space ?= ext ; 944 945 # Create the project data, and bring in the project rules into the 946 # module. 947 project.initialize $(__name__) : [ path.join [ project.attribute 948 $(root-project) location ] $(space:L) $(id:L) ] ; 949 950 # Create the project itself, i.e. the attributes. 951 project /$(space:L)/$(id:L) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : 952 $(9) : $(10) : $(11) : $(12) : $(13) : $(14) : $(15) : $(16) : $(17) 953 : $(18) : $(19) ; 954 local attributes = [ project.attributes $(__name__) ] ; 955 956 # Inherit from the root project of whomever is defining us. 957 project.inherit-attributes $(__name__) : $(root-project) ; 958 $(attributes).set parent-module : $(root-project) : exact ; 959 } 960} 961 962 963rule glob-internal ( project : wildcards + : excludes * : rule-name ) 964{ 965 local location = [ $(project).get source-location ] ; 966 967 local result ; 968 local paths = [ path.$(rule-name) $(location) : 969 [ sequence.transform path.make : $(wildcards) ] : 970 [ sequence.transform path.make : $(excludes) ] ] ; 971 if $(wildcards:D) || $(rule-name) != glob 972 { 973 # The paths we have found are relative to the current directory, but the 974 # names specified in the sources list are assumed to be relative to the 975 # source directory of the corresponding project. So, just make the names 976 # absolute. 977 for local p in $(paths) 978 { 979 # If the path is below source location, use relative path. 980 # Otherwise, use full path just to avoid any ambiguities. 981 local rel = [ path.relative $(p) $(location) : no-error ] ; 982 if $(rel) = not-a-child 983 { 984 result += [ path.root $(p) [ path.pwd ] ] ; 985 } 986 else 987 { 988 result += $(rel) ; 989 } 990 } 991 } 992 else 993 { 994 # There were no wildcards in the directory path, so the files are all in 995 # the source directory of the project. Just drop the directory, instead 996 # of making paths absolute. 997 result = $(paths:D="") ; 998 } 999 1000 return $(result) ; 1001} 1002 1003 1004rule glob-path-root ( root path ) 1005{ 1006 return [ path.root $(path) $(root) ] ; 1007} 1008 1009rule glob-internal-ex ( project : paths + : wildcards + : excludes * : rule-name ) 1010{ 1011 # Make the paths we search in absolute, if they aren't already absolute. 1012 # If the given paths are relative, they will be relative to the source 1013 # directory. So that's what we root against. 1014 local source-location 1015 = [ path.root [ $(project).get source-location ] [ path.pwd ] ] ; 1016 local search-paths 1017 = [ sequence.transform project.glob-path-root $(source-location) : $(paths) ] ; 1018 paths 1019 = [ path.$(rule-name) $(search-paths) : $(wildcards) : $(excludes) ] ; 1020 # The paths we have found are absolute, but the names specified in the 1021 # sources list are assumed to be relative to the source directory of the 1022 # corresponding project. Make the results relative to the source again. 1023 local result 1024 = [ sequence.transform path.relative-to $(source-location) : $(paths) ] ; 1025 1026 return $(result) ; 1027} 1028 1029 1030# This module defines rules common to all projects. 1031# 1032module project-rules 1033{ 1034 import modules ; 1035 1036 rule using ( toolset-module : * ) 1037 { 1038 import toolset ; 1039 1040 local saved-project = [ modules.peek project : .current-project ] ; 1041 1042 # Temporarily change the search path so the module referred to by 1043 # 'using' can be placed in the same directory as Jamfile. User will 1044 # expect the module to be found even though the directory is not in 1045 # BOOST_BUILD_PATH. 1046 local x = [ modules.peek : BOOST_BUILD_PATH ] ; 1047 local caller = [ CALLER_MODULE ] ; 1048 local caller-location = [ modules.binding $(caller) ] ; 1049 modules.poke : BOOST_BUILD_PATH : $(caller-location:D) $(x) ; 1050 toolset.using $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : 1051 $(9) : $(10) : $(11) : $(12) : $(13) : $(14) : $(15) : $(16) : $(17) 1052 : $(18) : $(19) ; 1053 modules.poke : BOOST_BUILD_PATH : $(x) ; 1054 1055 # The above might have clobbered .current-project in case it caused a 1056 # new project instance to be created (which would then automatically 1057 # get set as the 'current' project). Restore the correct value so any 1058 # main targets declared after this do not get mapped to the loaded 1059 # module's project. 1060 modules.poke project : .current-project : $(saved-project) ; 1061 } 1062 1063 rule import ( * : * : * ) 1064 { 1065 local caller = [ CALLER_MODULE ] ; 1066 local saved-project = [ modules.peek project : .current-project ] ; 1067 module $(caller) 1068 { 1069 modules.import $(1) : $(2) : $(3) ; 1070 } 1071 1072 # The above might have clobbered .current-project in case it caused a 1073 # new project instance to be created (which would then automatically 1074 # get set as the 'current' project). Restore the correct value so any 1075 # main targets declared after this do not get mapped to the loaded 1076 # module's project. 1077 modules.poke project : .current-project : $(saved-project) ; 1078 } 1079 1080 rule project ( id ? : options * : * ) 1081 { 1082 import path ; 1083 import project ; 1084 1085 local caller = [ CALLER_MODULE ] ; 1086 local attributes = [ project.attributes $(caller) ] ; 1087 if $(id) 1088 { 1089 $(attributes).set id : $(id) ; 1090 } 1091 1092 local explicit-build-dir ; 1093 1094 for n in 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1095 { 1096 local option = $($(n)) ; 1097 if $(option) 1098 { 1099 $(attributes).set $(option[1]) : $(option[2-]) ; 1100 } 1101 if $(option[1]) = "build-dir" 1102 { 1103 explicit-build-dir = [ path.make $(option[2-]) ] ; 1104 } 1105 } 1106 1107 # If '--build-dir' is specified, change the build dir for the project. 1108 local global-build-dir = [ modules.peek project : .global-build-dir ] ; 1109 1110 if $(global-build-dir) 1111 { 1112 local location = [ $(attributes).get location ] ; 1113 # Project with an empty location is a 'standalone' project such as 1114 # user-config or qt. It has no build dir. If we try to set build dir 1115 # for user-config, we shall then try to inherit it, with either 1116 # weird or wrong consequences. 1117 if $(location) && $(location) = [ $(attributes).get project-root ] 1118 { 1119 # Re-read the project id, since it might have been modified a 1120 # bit when setting the project's id attribute, e.g. might have 1121 # been prefixed by a slash if it was not already. 1122 id = [ $(attributes).get id ] ; 1123 # This is Jamroot. 1124 if $(id) 1125 { 1126 if $(explicit-build-dir) && 1127 [ path.is-rooted $(explicit-build-dir) ] 1128 { 1129 import errors ; 1130 errors.user-error Absolute directory specified via 1131 'build-dir' project attribute : Do not know how to 1132 combine that with the --build-dir option. ; 1133 } 1134 # Strip the leading slash from id. 1135 local rid = [ MATCH ^/(.*) : $(id) ] ; 1136 local p = [ path.join $(global-build-dir) $(rid) 1137 $(explicit-build-dir) ] ; 1138 1139 $(attributes).set build-dir : $(p) : exact ; 1140 } 1141 } 1142 else 1143 { 1144 # Not Jamroot. 1145 if $(explicit-build-dir) 1146 { 1147 import errors ; 1148 errors.user-error When --build-dir is specified, the 1149 'build-dir' project : attribute is allowed only for 1150 top-level 'project' invocations ; 1151 } 1152 } 1153 } 1154 } 1155 1156 # Declare and set a project global constant. Project global constants are 1157 # normal variables but should not be changed. They are applied to every 1158 # child Jamfile. 1159 # 1160 rule constant ( name : value + ) 1161 { 1162 import project ; 1163 local caller = [ CALLER_MODULE ] ; 1164 local p = [ project.target $(caller) ] ; 1165 $(p).add-constant $(name) : $(value) ; 1166 } 1167 1168 # Declare and set a project global constant, whose value is a path. The path 1169 # is adjusted to be relative to the invocation directory. The given value 1170 # path is taken to be either absolute, or relative to this project root. 1171 # 1172 rule path-constant ( name : value + ) 1173 { 1174 import project ; 1175 local caller = [ CALLER_MODULE ] ; 1176 local p = [ project.target $(caller) ] ; 1177 $(p).add-constant $(name) : $(value) : path ; 1178 } 1179 1180 rule use-project ( id : where ) 1181 { 1182 # See comment in 'load' for explanation. 1183 local caller = [ CALLER_MODULE ] ; 1184 modules.poke $(caller) : .used-projects : [ modules.peek $(caller) : 1185 .used-projects ] $(id) $(where) ; 1186 } 1187 1188 rule build-project ( dir ) 1189 { 1190 import project ; 1191 local caller = [ CALLER_MODULE ] ; 1192 local attributes = [ project.attributes $(caller) ] ; 1193 local now = [ $(attributes).get projects-to-build ] ; 1194 $(attributes).set projects-to-build : $(now) $(dir) ; 1195 } 1196 1197 rule explicit ( target-names * ) 1198 { 1199 import project ; 1200 # If 'explicit' is used in a helper rule defined in Jamroot and 1201 # inherited by children, then most of the time we want 'explicit' to 1202 # operate on the Jamfile where the helper rule is invoked. 1203 local t = [ project.current ] ; 1204 for local n in $(target-names) 1205 { 1206 $(t).mark-target-as-explicit $(n) ; 1207 } 1208 } 1209 1210 rule always ( target-names * ) 1211 { 1212 import project ; 1213 local t = [ project.current ] ; 1214 for local n in $(target-names) 1215 { 1216 $(t).mark-target-as-always $(n) ; 1217 } 1218 } 1219 1220 rule glob ( wildcards + : excludes * ) 1221 { 1222 import project ; 1223 return [ project.glob-internal [ project.current ] : $(wildcards) : 1224 $(excludes) : glob ] ; 1225 } 1226 1227 rule glob-tree ( wildcards + : excludes * ) 1228 { 1229 import project ; 1230 if $(wildcards:D) || $(excludes:D) 1231 { 1232 import errors ; 1233 errors.user-error The patterns to 'glob-tree' may not include 1234 directory ; 1235 } 1236 return [ project.glob-internal [ project.current ] : $(wildcards) : 1237 $(excludes) : glob-tree ] ; 1238 } 1239 1240 rule glob-ex ( paths + : wildcards + : excludes * ) 1241 { 1242 import project ; 1243 return [ project.glob-internal-ex [ project.current ] 1244 : $(paths) : $(wildcards) : $(excludes) : glob ] ; 1245 } 1246 1247 rule glob-tree-ex ( paths + : wildcards + : excludes * ) 1248 { 1249 import project ; 1250 return [ project.glob-internal-ex [ project.current ] 1251 : $(paths) : $(wildcards) : $(excludes) : glob-tree ] ; 1252 } 1253 1254 # Calculates conditional requirements for multiple requirements at once. 1255 # This is a shorthand to reduce duplication and to keep an inline 1256 # declarative syntax. For example: 1257 # 1258 # lib x : x.cpp : [ conditional <toolset>gcc <variant>debug : 1259 # <define>DEBUG_EXCEPTION <define>DEBUG_TRACE ] ; 1260 # 1261 rule conditional ( condition + : requirements * ) 1262 { 1263 local condition = $(condition:J=,) ; 1264 if [ MATCH (:) : $(condition) ] 1265 { 1266 return $(condition)$(requirements) ; 1267 } 1268 else 1269 { 1270 return $(condition):$(requirements) ; 1271 } 1272 } 1273 1274 rule option ( name : value ) 1275 { 1276 local m = [ CALLER_MODULE ] ; 1277 local cfgs = project site test user ; 1278 if ! $(m) in $(cfgs)-config 1279 { 1280 import errors ; 1281 errors.error The 'option' rule may only be used "in" Boost Build 1282 configuration files. ; 1283 } 1284 import option ; 1285 option.set $(name) : $(value) ; 1286 } 1287} 1288