# Copyright (C) Vladimir Prus 2002. Permission to copy, use, modify, sell and # distribute this software is granted provided this copyright notice appears in # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. # There are three kinds of targets: "abstract", which correspond to # targets explicitly defined in Jamfile; "virtual", which correspond # to possible build product with defined properties and "actual", which # are targets in Jam sense. The "virtual" targets are generated during # search for the best transformation sequence, and some of them can be # later 'actualized'. # # Abstract targets are represented by classes derived from 'abstract-target' class. # The first abstract target is 'project-target', which is created for each # Jamfile, and can be obtained by the 'target' rule in the Jamfile's module. # (see project.jam). # # Project targets keep a list of 'main-target' instances. # A main target is what the user explicitly defines in a Jamfile. It is # possible to have several definitions for a main target, for example to have # different lists of sources for different platforms. So, main targets # keep a list of alternatives. # # Each alternative is an instance of 'abstract-target'. When a main target # subvariant is defined by some rule, that rule will decide what class to # use, create an instance of that class and add it to the list of alternatives # for the main target. # # Rules supplied by the build system will use only targets derived # from 'basic-target' class, which will provide some default behaviour. # There will be two classes derived from it, 'make-target', created by the # 'make' rule, and 'typed-target', created by rules such as 'exe' and 'dll'. import sequence ; import class : class ; import regex ; import property ; import errors ; # Base class for all abstract targets. rule abstract-target ( name # name of the target in Jamfile : project # the project module where the target is declared ) { # Note: it might seem that we don't need either name or project at all. # However, there are places where we really need it. One example is error # messages which should name problematic targets. Another is setting correct # paths for sources and generated files. self.name = $(name) ; self.project = $(project) ; # Returns a user-readable name for this target. rule full-name ( ) { local location = [ $(self.project).location ] ; return $(location)/$(self.name) ; } # Takes properties in split form ("foo bar"). # Generates virtual targets for this abstract targets which are matching # 'properties' as closely as possible. It 'properties' are not specified, # default values are used. If it not possible to build anything because # of some problem returns a list with "@error" as the first element # and explanation in all others. (CONSIDER: need some utilities for # this method of error reporting? 'is-error'?) rule generate ( properties * ) { errors.error "method should be defined in derived classes" ; } } class abstract-target ; # Project target class (derived from 'abstract-target') rule project-target ( name : project : requirements * : default-build * ) { abstract-target.__init__ $(name) : $(project) ; self.requirements = $(requirements) ; self.default-build = $(default-build) ; # Generates all possible targets contained in this project. rule generate ( properties * ) { local result ; for local name in $(self.main-targets) { local t = [ main-target $(name) ] ; result += [ $(t).generate $(properties) ] ; } for local pn in [ $(self.project).subprojects ] { local p = [ project.module-name $(pn) ] ; local t = [ $(p).target ] ; result += [ $(t).generate $(properties) ] ; } return $(result) ; } # Returns a 'main-target' class instance corresponding to the 'name'. # Creates the instance if needed. rule main-target ( name ) { if ! $(self.main-target.$(name)) { self.main-targets += $(name) ; self.main-target.$(name) = [ new main-target $(name) : $(self.project) ] ; } return $(self.main-target.$(name)) ; } # Tells if a main target with the specified name exists. rule has-main-target ( name ) { if $(self.main-target.$(name)) { return true ; } } } class project-target : abstract-target ; # A named top-level target in Jamfile rule main-target ( name : project ) { import errors : error ; abstract-target.__init__ $(name) : $(project) ; # Add a new alternative for this target rule add-alternative ( target ) { self.alternatives += $(target) ; } # Select an alternative for this main target, by finding all alternatives # which requirements are satisfied by 'properties' and picking the one with # longest requirements set. # Returns the result of calling 'generate' on that alternative. rule generate ( properties * ) { # At this stage we just try to generate each variant and if # more than one succeeds, consider it as an error. local alternatives ; for local v in $(self.alternatives) { local vtargets = [ $(v).generate $(properties) ] ; if $(vtargets) && $(vtargets[1]) != "@error" { alternatives += $(v) $(vtargets) "@" ; } } if ! $(alternatives) { # TODO: Should print a name error "No viable alternative found for main target" [ full-name ] ; } # We have "@" at the end. Another one means two or more alts. if "@" in $(alternatives[1--2]) { error "Ambiguous alternatives for main target" [ full-name ] ; } # Now return virtual targets for the only alternative return $(alternatives[2--2]) ; } } class main-target : abstract-target ; # Provides default implementation for building source targets and # similar activities. # Sources may be either files, or target ids. The former have the form # location@project-id/target-id, or # location@project-id/target-id/foo # The second variant specifies additional properties that should be used # when building the target. rule basic-target ( name : project : sources * : requirements * : default-build * ) { import build-request ; abstract-target.__init__ $(name) : $(project) ; self.sources = $(sources) ; self.requirements = $(requirements) ; self.default-build = $(default-build) ; # Applies default-build if 'properties' are empty. # Generates sources. Calls 'construct' # This method should not be overriden. # # Note: historical perspectives of this rule are not clear.... # since generators will be allowed to change requirements as they # search from targets to sources, we won't be able to call # generate on sources right here, because we don't know properties # that should be used. rule generate ( properties * ) { if ! $(properties) { # CONSIDER: I'm really not sure if this is correct... properties = [ build-request.expand $(self.default-build) ] ; local result = ; for local p in $(properties) { result += [ generate [ feature.split $(p) ] ] ; } return $(result) ; } else { property-path = [ property.as-path $(properties) ] ; if ! $(property-path) { property-path = X ; } if ! $(self.generated.$(property-path)) { local rproperties = [ property.refine $(properties) : $(self.requirements) ] ; if $(rproperties[1]) = "@error" { print.wrapped-text "Cannot satisfy request to build" [ full-name ] "with properties " $(properties) ; print.wrapped-text "Explanation:" $(rproperties[2-]) ; EXIT ; } # TODO: issue a warning when requirements change properties, but # link-compatibility is still not broken. local source-targets ; for local s in $(self.sources) { # Try treating this source as reference to main target local more-targets = [ generate-source $(s) : $(properties) ] ; if $(more-targets) { source-targets += $(more-targets) ; } else { # Just a source file source-targets += [ new virtual-target $(s) : $(self.project) ] ; } } self.generated.$(property-path) = [ construct $(source-targets) : $(properties) ] ; } return $(self.generated.$(property-path)) ; } } # Given a source specification, generates virtual targets for that source. # If the source does not correspond to any main target, returns nothing. rule generate-source ( source : properties * ) { # Separate target name from properties override local split = [ MATCH "([^<]*)(/<.*)?" : $(source) ] ; local id = $(split[1]) ; local sproperties = ; if $(split[2]) { sproperties = [ feature.split $(split[2]) ] ; } # Check is such target exists local main-target = [ project.find-target $(id) : [ $(self.project).location ] ] ; if $(main-target) { # Apply source-specific properties local rproperties = [ property.refine $(properties) : $(sproperties) ] ; if $(rproperties[1]) = "@error" { error "When building" [ full-name ] " with properties " $(properties) "Invalid properties specified for " $(source) ":" $(rproperties[2-]) ; } return [ $(main-target).generate $(rproperties) ] ; } } # Constructs the virtual targets for this abstract targets and # the dependecy graph. Returns the list of virtual targets. # Should be overrided in derived classes. rule construct ( source-targets * : properties * ) { errors.error "method should be defined in derived classes" ; } } class basic-target : abstract-target ; # Class which represents a virtual target rule virtual-target ( name : project : subvariant * # Property sets which define this subvariant ) { self.name = $(name) ; self.subvariant = $(subvariant) ; self.project = $(project) ; self.includes = ; self.dependencies = ; self.action = ; self.actual-name = ; # Name of the target rule name ( ) { return $(self.name) ; } # Property set that distinguished different variants of a target. # May be a subset of the property set that is used for building. # Determines the location of target, in an unspecified way. rule subvariant ( ) { return $(self.subvariant) ; } # Project where this target was declared rule project ( ) { return $(self.project) ; } rule includes ( ) { return $(self.includes) ; } rule add_includes ( i + ) { self.includes = [ sequence.merge $(self.includes) : [ sequence.insertion-sort $(i) ] ] ; } rule dependencies ( ) { return $(self.dependencies) ; } rule depends ( d + ) { self.dependencies = [ sequence.merge $(self.dependencies) : [ sequence.insertion-sort $(d) ] ] ; } # If 'a' is supplied, sets action to 'a'. # Returns the action currently set. rule action ( a ? ) { if $(a) { self.action = $(a) ; } return $(self.action) ; } # Generates all the actual targets and build instructions needed to build # this target. Returns the actual target name. Can be called several times. # Does no processing for other targets that 'action' will generate. # Rationale: we might need only one file from the set created by an # action, and there's no need to run the action if the file is up-to-date, # only because some other file in set is out-of-date. rule actualize ( ) { if ! $(self.actual-name) { self.actual-name = [ actual-name ] ; for local i in $(dependencies) { DEPENDS $(name) : [ $(i).actualize ] ; } for local i in $(includes) { Includes $(name) : [ $(i).actualize ] ; } local a = [ action ] ; if $(a) { $(a).actualize ; local subvariant = [ $(a).properties ] ; local path = [ os.path.join [ $(self.project).location ] "bin" [ property.as-path $(subvariant) ] ] ; path = [ os.path.native $(path) ] ; LOCATE on $(self.actual-name) = $(path) ; DEPENDS $(self.actual-name) : $(path) ; utility.MkDir $(path) ; utility.Clean clean : $(self.actual-name) ; } else { SEARCH on $(self.actual-name) = [ os.path.native [ $(self.project).source-location ] ] ; } } return $(self.actual-name) ; } # private: rule actual-name ( ) { if ! $(self.actual-name) { local project-location = [ $(self.project).location ] ; local location-grist = [ sequence.join [ regex.split $(project-location) "/" ] : "!" ] ; local property-grist = [ property.as-path $(self.subvariant) ] ; # Set empty value to avoid eating adjacent text local grist = $(location-grist)/$(property-grist) ; if ! $(self.subvariant) { grist = $(location-grist) ; } self.actual-name = <$(grist)>$(self.name) ; } return $(self.actual-name) ; } } class virtual-target ; # Class which represents an action. # Both 'targets' and 'sources' should list instances of 'virtual-target'. # Action name should name a rule with this prototype # rule action_name ( targets + : sources * : properties * ) # Targets and sources are passed as actual jam targets. The rule may # not establish dependency relationship, but should do everything else. rule action ( targets + : sources * : action_name : properties * ) { self.targets = $(targets) ; self.sources = $(sources) ; self.action_name = $(action_name) ; self.properties = $(properties) ; rule targets ( ) { return $(self.targets) ; } rule sources ( ) { return $(self.sources) ; } rule action_name ( ) { return $(self.action_name) ; } rule properties ( ) { return $(self.properties) ; } # Generates actual build instructions. rule actualize ( ) { local actual_targets ; for local i in [ targets ] { actual_targets += [ $(i).actualize ] ; } local actual_sources ; for local i in [ sources ] { actual_sources += [ $(i).actualize ] ; } DEPENDS $(actual_targets) : $(actual_sources) ; $(self.action_name) $(actual_targets) : $(actual_sources) : [ properties ] ; } } class action ;