#============================================================================ # Helper rules # Copyright (C)2003 by Matze Braun # Copyright (C)2004 by Eric Sunshine # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Library General Public License as published by # the Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This library is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public # License for more details. # # You should have received a copy of the GNU Library General Public License # along with this library; if not, write to the Free Software Foundation, # Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # #============================================================================ SED ?= sed ; DEEPCOPY ?= "cp -R" ; DELTREE ?= "rm -rf" ; # The -f option to `cp' is not supported on older platforms. # The convolution of the conditional arises because CP is defined in Jambase as # two tokens rather than a single string, so we must check the tokens # individually; yet we also check it as a simple string for future robustness. if $(CP) = "cp -f" || $(CP[1]) = "cp" && $(CP[2]) = "-f" { CP = cp ; } ## IncludeDir [ dir [ : target [ : options ]]] ## Specify the location of a directory containing header files for a target, ## or for the whole project if no target is given. "dir" is a list of ## components composing the path. This rule will automatically generate the ## -I compiler flags and makes sure the dependency scanner is able to locate ## your header files. "dir" is assumed to be relative to the current ## subdirectory specified with the SubDir rule unless the "literal" ## option is given, in which case "dir" is used literally. If "dir" is ## omitted, then the current subdirectory specified with SubDir is used as ## the header directory. An omitted "dir" and the "literal" option are ## mutually exclusive. You may invoke this rule multiple times to specify ## any number of header file directories. ## Options: ## literal: "dir" is to be used literally without any interpretation. ## transient: "dir" is to be used at build-time only; and should not be ## recorded in any generated resources, such as project files. ## ## Implementation: The directory is simply added to the HDRS variable which ## is respected by all Jam rules. rule IncludeDir { local dir = $(1) ; local target = $(2) ; local options = $(3) ; CheckOptions literal transient : $(options) : $(dir) ; if ! $(dir) { dir = $(SUBDIR) ; } else if ! [ IsElem literal : $(options) ] { dir = $(SUBDIR) $(dir) ; } dir = [ ConcatDirs $(dir) ] ; if $(target) { local o ; for o in $($(target)_OBJECTS) { CCHDRS on $(o) += [ FIncludes $(dir) ] ; } } else { HDRS += $(dir) ; } } ## Wildcard [ dir : ] patterns ## Create a list of files in a directory which match the pattern. You can ## optionally specify a subdirectory. The files will be returned with ## stripped pathnames. The difference from GLOB is that this rule respects ## subdirectories which may have been entered with the SubDir rule. rule Wildcard { local files dir sdir wildcards ; # Is a directory given? if $(>) { dir = $(<)/ ; sdir = [ ConcatDirs $(<) ] ; wildcards = $(>) ; } else { dir = "" ; sdir = "" ; wildcards = $(<) ; } files = [ GLOB [ ConcatDirs $(SUBDIR) $(dir) ] : $(wildcards) ] ; return $(files:BSR=$(sdir)) ; } ## Recurse [ rule ] : types [ : prefix ] ## Recursively scan current directory, $(SUBDIR), for files matching 'types' ## and invoke 'rule' for each file which matches one of the 'types'. ## 'types' is a list of file extensions (with the leading dot). 'rule' will ## be invoked with two arguments: (1) the basename of the file including the ## extension, (2) a list of the path components from the current directory ## to the file's directory. When 'rule' is invoked, it will see a $(SUBDIR) ## value of the directory containing the file (as if the rule had been ## invoked from within the file's directory). 'prefix' is an optional list ## of path components which will be prepended to rule's second argument. ## Returns the list of visited files. It is legal to omit 'rule', if you ## are interested only in obtaining the list of files matching 'types'. rule Recurse { local innerrule = $(1) ; local types = $(2) ; local prefix = $(3) ; local files = [ GLOB $(SUBDIR) : * ] ; local visited ; local i ; for i in $(files) { if [ IsElem $(i:S) : $(types) ] { visited += [ FDirName $(prefix) $(i:BS) ] ; if $(innerrule) { $(innerrule) $(i:BS) : $(prefix) ; } } else { if ! [ IsElem $(i:BS) : $(DOT) $(DOTDOT) ] { local SUBDIR = $(i) ; # Called rules see this new temporary value. visited += [ Recurse $(innerrule) : $(types) : $(prefix) $(i:BS) ] ; } } } return $(visited) ; } ## ResponseFile file : [ items [ : options [ : directory [ : delim ]]]] ## Jam places a fairly restrictive limit on the length of the command string ## emitted by an 'actions' block. If the limit is exceeded, Jam rudely ## aborts. This problem is easily triggered when actions are invoked ## 'together' but not 'piecemeal'; especially when the command arguments ## involve many lengthy pathnames. To work around this type of problem, ## some tools allow the client to furnish a file containing information ## which would otherwise be specified via the command-line. This is often ## called a "response file". The ResponseFile rule can be used to create a ## response file named 'file' in 'directory' containing 'items', one per ## line. As a convenience, if 'directory' is not specified, and if the ## MakeLocate rule has not already been invoked for 'file' or LOCATE has not ## been set for 'file', then the file is placed in $(LOCATE_TARGET). If ## there is a possibility that the same 'file' name might be used in other ## contexts, be sure to grist it appropriately to avoid conflicts. This ## rule assumes that 'items' contains bound entries unless the "notfile" ## option is specified, in which case the NotFile rule is automatically ## invoked for each item. This rule may be invoked multiple times for the ## same 'file' in order to populate the file incrementally. As an internal ## optimization to keep performance relatively sane, ResponseFile ## temporarily inserts 'delim' between 'items' when emitting them, and then ## substitutes newline for 'delim' just before writing the items to ## 'file'. 'delim' must be a one-character string. If not specified, "@" is ## used. If "@" is likely to appear in 'items', then choose a different ## character for 'delim'; one which is known to not appear in 'items'. The ## rule returns 'file' to make it convenient to daisy-chain with invocations ## of other rules, such as RmTemps, Depends, or Always. ## Options: ## notfile: Invoke NotFile for each item automatically; otherwise, assume ## that each item is a bound file. rule ResponseFile { local file = $(1) ; local items = $(2) ; local options = $(3) ; local dir = $(4) ; local delim = $(5) ; CheckOptions notfile : $(options) : $(file) ; if ! $(delim) { delim = "@" ; } DELIM on $(file) = $(delim) ; local firsttime = no ; if ! [ IsElem $(file) : $(RESPONSE_FILE_REGISTRY) ] { firsttime = yes ; RESPONSE_FILE_REGISTRY += $(file) ; } if ! $(items) && $(firsttime) = yes { items = "" ; # Force file creation even if list is empty. options += notfile ; } if [ IsElem notfile : $(options) ] && $(items) { NotFile $(items) ; } if $(dir) { MakeLocate $(file) : $(dir) ; } else { local target_dir = [ on $(file) GetVar LOCATE ] ; if ! $(target_dir) { MakeLocate $(file) : $(LOCATE_TARGET) ; } } local i ; for i in $(items) { if $(firsttime) = yes { ResponseFile1 $(file) : $(i) ; firsttime = no ; } else { ResponseFile2 $(file) : $(i) ; } } return $(file) ; } actions ResponseFile1 { echo '$(>)' > $(<) } actions piecemeal together quietly ResponseFile2 { echo '$(>)$(DELIM)' | $(SED) 's/$(DELIM) /$(DELIM)/g' | tr '$(DELIM)' ' ' >> $(<) } ## Sort list ## Given a list of items, returns a list containing the items sorted ## alphabetically. rule Sort { local i sorted ; for i in $(<) { local inserted = no ; local j accum ; for j in $(sorted) { if $(inserted) != yes && $(i:L) < $(j:L) { accum += $(i) ; inserted = yes ; } accum += $(j) ; } if $(inserted) != yes { accum += $(i) ; } sorted = $(accum) ; } return $(sorted) ; } ## StripCommon list1 : list2 ## Strips from the beginning of list1 the items which it has in common with ## the beginning of list2 and returns what remains of list1. rule StripCommon { local l = $(<) ; local r = $(>) ; FStripCommon l : r ; return $(l) ; } ## MasterHeader header [ : files [ : pre-boilerplate [ : post-boilerplate ## [ : options ]]]] ## Given a list of 'files', construct a 'header' file which #includes those ## files. If 'header' does not already have a suffix, ".h" will be ## appended. The generated header will be emitted to $(LOCATE_TARGET), and ## will be protected against multiple-inclusion via the standard ## #ifndef __HEADER_H__ / #define / #endif mechanism. If provided, ## 'pre-boilerplate' will be inserted verbatim immediately after the opening ## multiple-inclusion protection, but before the first #include. Likewise, ## 'post-boilerplate' will be inserted verbatim after the last #include, but ## before the closing multiple-inclusion protection. If the boilerplate ## arguments are lists, the items will be emitted one per line. 'files' ## is sorted before the #include statements are generated, unless the ## "nosort" option is given. For convenience, the gristed 'header' is ## returned. Also sets up the following pseudo-targets: ## ## masterheaders: Synthesize all requested master headers. ## cleanmasterheaders: Delete synthesized files. ## freezemasterheaders: Copy synthesized files to back into the source ## tree at $(SUBDIR). ## ## Options: ## nosort: Do not sort 'files'. rule MasterHeader { local header = [ FAppendSuffix $(1) : .h ] ; local files = $(2) ; local boilerpre = $(3) ; local boilerpost = $(4) ; local options = $(5) ; local target = $(header:G=masterheader) ; local protect = "__$(header:US=)_H__" ; CheckOptions nosort : $(options) : $(header) ; if ! [ IsElem nosort : $(options) ] { files = [ Sort $(files) ] ; } Always $(target) ; ResponseFile $(target) : "/* $(header) -- Generated automatically; do not edit. */" "#ifndef $(protect)" "#define $(protect)" $(boilerpre) "#include \"$(files)\"" $(boilerpost) "#endif /* $(protect) */" : notfile ; Depends masterheaders : $(target) ; Clean cleanmasterheaders : $(target) ; Clean clean : cleanmasterheaders ; local frozen = $(target:G=frozenmasterheader) ; MakeLocate $(frozen) : $(SUBDIR) ; Depends $(frozen) : $(target) ; Copy $(frozen) : $(target) ; Depends freezemasterheaders : $(frozen) ; if $(MASTER_HEADER_GLOBAL_TARGETS) != yes { MASTER_HEADER_GLOBAL_TARGETS = yes ; Always masterheaders ; NotFile masterheaders ; Help masterheaders : "Generate master header files" ; Always freezemasterheaders ; NotFile freezemasterheaders ; Help freezemasterheaders : "Copy generated master headers to source tree" ; } return $(target) ; } ## DirectoryMasterHeaders dirs [ : pre-boilerplate [ : post-boilerplate ## [ : options [ : rejects ]]]] ## A convenience wrapper around MasterHeader which generates a set of master ## header files for each directory in 'dirs', which are assumed to be ## subdirectories of the current directory. For each item in 'dirs', the ## subdirectory is recursively scanned for files, and MasterHeader is ## invoked with the gleaned file list. The generated header for a directory ## is emitted to the current directory; not within the subdirectory. The ## optional 'rejects' is a list of header files which should not be emitted ## to the synthesized master headers. 'pre-boilerplate', ## 'post-boilerplate', and 'options' carry the same interpretation as for ## MasterHeader. rule DirectoryMasterHeaders { local dirs = $(1) ; local boilerpre = $(2) ; local boilerpost = $(3) ; local options = $(4) ; local rejects = $(5) ; local masters ; local d ; for d in $(dirs) { local files ; { local SUBDIR = [ ConcatDirs $(SUBDIR) $(d) ] ; # Recurse from here... files = [ Recurse : .h : $(d) ] ; } if $(rejects) { files = [ Filter $(files) : $(rejects) ] ; } masters += [ MasterHeader $(d) : $(files) : $(boilerpre) : $(boilerpost) : $(options) ] ; } return $(masters) ; } ## Prefix list : prefix ## Adds a prefix to a all elements in list. rule Prefix { return $(>)$(<) ; } if $(JAMVERSION) >= 2.5 { ## IsElem element : list ## Returns "true" if the element is in the list. Otherwise nothing is ## returned. rule IsElem { local i ; for i in $(>) { if $(i) = $(<) { return "true" ; } } return ; } } else { # Jam <2.4's return statement doesn't exit the function rule IsElem { local i result ; for i in $(>) { if $(i) = $(<) { result = "true" ; $(>) = ; } } return $(result) ; } } ## Filter list : filter ## Returns the list without the words contained in filter. rule Filter { local i result ; for i in $(<) { if ! [ IsElem $(i) : $(>) ] { result += $(i) ; } } return $(result) ; } ## RemoveDups list ## Removes duplicates in the list (this function tries to preserve the list ## order) rule RemoveDups { local i result ; for i in $(<) { if ! [ IsElem $(i) : $(result) ] { result += $(i) ; } } return $(result) ; } ## Reverse list ## Reverse the order of items in the list. rule Reverse { local result ; for i in $(<) { result = $(i) $(result) ; } return $(result) ; } ## GetVar argument ## Simply returns the value of the variable with name argument. ## This is useful to query on target variables: ## bla = [ on TARGET GetVar CFlags ] ; rule GetVar { return $($(<)) ; } ## ConcatDirs dirs ## Concatenates a set of directories. This is a substitute for FDirName in ## Jambase. It works also correctly for several rooted paths, where FDirName ## fails. ## The advantage over $(dir1)/$(dir2) is that this also works correctly if ## $(dir1) or $(dir2) is not set. rule ConcatDirs { local i ; local result = $(<[1]) ; if ! $(result) { $result = "" ; } local dir1 dir2 ; for i in $(<[2-]) { # eleminate multiple slashes because jam is somewhat buggy here dir1 = [ MATCH (.*[^/]?) : $(result) ] ; dir2 = [ MATCH ([^/].*) : $(i) ] ; if ! $(dir1) { dir1 = "" ; } if $(dir1) != "" { dir1 = $(dir1)/ ; } if ! $(dir2) { dir2 = "" ; } result = $(dir1)$(dir2) ; } return $(result) ; } ## SplitToList var [ : separator ] ## Splits the value of var into a list using space as the separator unless ## an alterante separator is specified. ## IMPLEMENTATION NOTE ## When Jam sees an invocation of the `Match' function, it treats its first ## argument as a literal regular expression, and does not do any variable ## interpolation. This means that an expression, such as "(.*)$(sep)(.*)" ## will not be interpreted as expected; it will instead be interpreted as an ## invalid regex. To work around this limitation, we invoke `Match' ## indirectly. rule SplitToList { local list = ; local matcher = Match ; # See IMPLEMENTATION NOTE above. local unsplit = $(<) ; local sep = $(2) ; if ! $(sep) { sep = " " ; } while $(unsplit) != "" { local split = [ $(matcher) "(.*)$(sep)(.*)" : $(unsplit) ] ; if $(split[1]) = "" { list += $(unsplit) ; unsplit = "" ; } else { list += $(split[2]) ; unsplit = $(split[1]) ; } } return [ Reverse $(list) ] ; } ## Copy target : source ## Copy source to target. actions Copy { $(RM) $(<) $(CP) $(>) $(<) } ## Move target : source ## Move (or rename) source to target. actions ignore Move { $(MV) $(>) $(<) }