def include(file): """ Parse and register the targets and properties in in the specified xpybuild.py file. Targets should only be defined in files included using this method, not using python import statements. @param file: a path relative to the directory containing this file. """ from buildcontext import getBuildInitializationContext from utils.buildfilelocation import BuildFileLocation file = getBuildInitializationContext().expandPropertyValues(file) assert file.endswith( '.xpybuild.py') # enforce recommended naming convention filepath = getBuildInitializationContext().getFullPath( file, os.path.dirname(BuildFileLocation._currentBuildFile[-1])) BuildFileLocation._currentBuildFile.append( filepath) # add to stack of files being parsed namespace = {} execfile(filepath, namespace, namespace) del BuildFileLocation._currentBuildFile[-1] return namespace
def defineAtomicTargetGroup(*targets): """ The given targets must all be built before anything which depends on any of those targets. Returns the flattened list of targets. """ from buildcontext import getBuildInitializationContext targets = flatten(targets) getBuildInitializationContext().defineAtomicTargetGroup(targets) return targets
def defineStringProperty(name, default): """ Define a string property which can be used in ${...} substitution. Do not use this generic function for any properties representing a file system path, or a boolean/enumeration. @param name: The property name @param default: The default value of the propert (can contain other ${...} variables) If set to None, the property must be set on the command line each time """ init = getBuildInitializationContext() if init: init.defineProperty(name, default, lambda v: getBuildInitializationContext().expandPropertyValues(v))
def defineBooleanProperty(name, default=False): """ Defines a boolean property that will have a True or False value. @param name: The property name @param default: The default value (default = False) If set to None, the property must be set on the command line each time """ # Expands property values, then converts to a boolean def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues( str(value)) if value.lower() == 'true': return True if value.lower() == 'false' or value == '': return False raise BuildException( 'Invalid property value for "%s" - must be true or false' % (name)) init = getBuildInitializationContext() if init: init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
def tags(self, *tags): """ Append one or more 'tag' strings that will be associated with this target. These tags can be supplied on the command line to build associated groups of targets, or just provide a shorter, well-known, name. @param tags: the tag, tags or list of tags to add to the target. >>> BaseTarget('a',[]).tags('abc').getTags() <using test initialization context> <using test initialization context> ['abc', 'all'] >>> BaseTarget('a',[]).tags(['abc', 'def']).getTags() <using test initialization context> <using test initialization context> ['abc', 'def', 'all'] >>> BaseTarget('a',[]).tags('abc', 'def').tags('ghi').getTags() <using test initialization context> <using test initialization context> <using test initialization context> ['ghi', 'abc', 'def', 'all'] """ taglist = getStringList(list(tags)) self.__tags = taglist + self.__tags assert sorted(list(set(self.__tags))) == sorted(list( self.__tags)) # check for duplicates init = getBuildInitializationContext() if init: init.registerTags(self, taglist) # init will be None during doctests return self
def enableEnvironmentPropertyOverrides(prefix): """ Turns on support for value overrides for defined properties from the environment as well as from the command line. Allows any property value to be overridden from an environment variable, provided the env var begins with the specified prefix. This setting only affects properties defined after the point in the build files where it is called. Property values specified on the command line take precedence over env vars, which in turn take precedence over the defaults specified when properties are defined. @param prefix: The prefix added to the start of a build property name to form the name of the environment variable; the prefix is stripped from the env var name before it is compared with properties defined by the build. This is mandatory (cannot be empty) and should be set to a build-specific string (e.g. "XYZ_") in order to ensure that there is no chance of build properties being accidentally overridden. (e.g. many users have JAVA_HOME in their env but also in their build, however it may be important for them to have different values, and subtle bugs could result if the build property was able to be set implicitly from the environment). """ init = getBuildInitializationContext() if init: init.enableEnvironmentPropertyOverrides(prefix)
def setGlobalOption(key, value): """ Globally override the default for an option """ init = getBuildInitializationContext() if init: init.setGlobalOption(key, value)
def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(str(value)) if value.lower() == 'true': return True if value.lower() == 'false' or value=='': return False raise BuildException('Invalid property value for "%s" - must be true or false' % (name))
def defineStringProperty(name, default): """ Define a string property which can be used in ${...} substitution. Do not use this generic function for any properties representing a file system path, or a boolean/enumeration. @param name: The property name @param default: The default value of the propert (can contain other ${...} variables) If set to None, the property must be set on the command line each time """ init = getBuildInitializationContext() if init: init.defineProperty( name, default, lambda v: getBuildInitializationContext().expandPropertyValues(v))
def defineEnumerationProperty(name, default, enumValues): """ Defines a property that must take one of the specified values. @param name: The name of the property @param default: The default value of the property (can contain other ${...} variables) If set to None, the property must be set on the command line each time @param enumValues: A list of valid values for this property (can contain other ${...} variables) """ # Expands properties, then checks that it's one of the acceptible values def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if value in enumValues: return value # case-insensitive match for e in enumValues: if e.lower() == value.lower(): return e raise BuildException( 'Invalid property value for "%s" - value "%s" is not one of the allowed enumeration values: %s' % (name, value, enumValues)) init = getBuildInitializationContext() if init: init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
def defineEnumerationProperty(name, default, enumValues): """ Defines a property that must take one of the specified values. @param name: The name of the property @param default: The default value of the property (can contain other ${...} variables) If set to None, the property must be set on the command line each time @param enumValues: A list of valid values for this property (can contain other ${...} variables) """ # Expands properties, then checks that it's one of the acceptible values def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if value in enumValues: return value # case-insensitive match for e in enumValues: if e.lower()==value.lower(): return e raise BuildException('Invalid property value for "%s" - value "%s" is not one of the allowed enumeration values: %s' % (name, value, enumValues)) init = getBuildInitializationContext() if init: init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
def clearTags(self): """ Removes any tags other than "all" from this target """ init = getBuildInitializationContext() init.removeFromTags(self, self.__tags) self.__tags = ['all'] if 'all' in self.__tags else [] init.registerTags(self, self.__tags) return self
def definePropertiesFromFile(propertiesFile, prefix=None, excludeLines=None, conditions=None): """ Defines a set of properties from a .properties file @param propertiesFile: The file to include properties from (can include ${...} variables) @param prefix: if specified, this prefix will be added to the start of all property names from this file @param excludeLines: a string of list of strings to search for, any KEY containing these strings will be ignored @param conditions: an optional list of string conditions that can appear in property keys e.g. "FOO<condition>=bar" where lines with no condition in this list are ignored. Conditions are typically lowercase. """ if conditions: assert not isinstance( conditions, basestring), 'conditions parameter must be a list' __log.info('Defining properties from file: %s', propertiesFile) context = getBuildInitializationContext() propertiesFile = context.getFullPath( propertiesFile, BuildFileLocation(raiseOnError=True).buildDir) try: f = open(propertiesFile, 'r') except Exception, e: raise BuildException('Failed to open properties file "%s"' % (propertiesFile), causedBy=True)
def getPropertyValue(propertyName): """ Return the current value of the given property (can only be used during build file parsing). For Boolean properties this will be a python Boolean, for everything else it will be a string. """ context = getBuildInitializationContext() assert context, 'getProperty can only be used during build file initialization phase' return context.getPropertyValue(propertyName)
def disableInFullBuild(self): """ Stops this target from building in 'all' mode, therefore it must be called explicitly or via a tag. """ self.__tags = list(set(self.__tags) - set(['all'])) init = getBuildInitializationContext() init.removeFromTags(self, ['all']) return self
def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues( str(value)) if value.lower() == 'true': return True if value.lower() == 'false' or value == '': return False raise BuildException( 'Invalid property value for "%s" - must be true or false' % (name))
def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if value in enumValues: return value # case-insensitive match for e in enumValues: if e.lower()==value.lower(): return e raise BuildException('Invalid property value for "%s" - value "%s" is not one of the allowed enumeration values: %s' % (name, value, enumValues))
def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if value in enumValues: return value # case-insensitive match for e in enumValues: if e.lower() == value.lower(): return e raise BuildException( 'Invalid property value for "%s" - value "%s" is not one of the allowed enumeration values: %s' % (name, value, enumValues))
def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if not os.path.isabs(value): # must absolutize this, as otherwise it might be used from a build # file in a different location, resulting in the same property # resolving to different effective values in different places value = BuildFileLocation(raiseOnError=True).buildDir+'/'+value value = normpath(value).rstrip('/\\') if mustExist and not os.path.exists(value): raise BuildException('Invalid path property value for "%s" - path "%s" does not exist' % (name, value)) return value
def registerOutputDirProperties(*propertyNames): """ Registers the specified path property name(s) as being an output directory of this build, meaning that they will be created automatically at the beginning of the build process, and removed during a global clean. Typical usage is to call this just after definePathProperty. """ init = getBuildInitializationContext() if init: for p in propertyNames: p = init.getPropertyValue(p) if not os.path.isabs(p): raise BuildException('Only absolute path properties can be used as output dirs: "%s"'%p) init.registerOutputDir(normpath(p))
def defineOption(name, default): """ Define an option with a default (can be overridden globally or on targets). Options are not available for ${...} expansion (like properties), but rather as used for (optionally inheritably) settings that affect the behaviour of one or more targets. @param name: The option name @param default: The default value of the option """ init = getBuildInitializationContext() if init: init._defineOption(name, default)
def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if not os.path.isabs(value): # must absolutize this, as otherwise it might be used from a build # file in a different location, resulting in the same property # resolving to different effective values in different places value = BuildFileLocation(raiseOnError=True).buildDir + '/' + value value = normpath(value).rstrip('/\\') if mustExist and not os.path.exists(value): raise BuildException( 'Invalid path property value for "%s" - path "%s" does not exist' % (name, value)) return value
def registerOutputDirProperties(*propertyNames): """ Registers the specified path property name(s) as being an output directory of this build, meaning that they will be created automatically at the beginning of the build process, and removed during a global clean. Typical usage is to call this just after definePathProperty. """ init = getBuildInitializationContext() if init: for p in propertyNames: p = init.getPropertyValue(p) if not os.path.isabs(p): raise BuildException( 'Only absolute path properties can be used as output dirs: "%s"' % p) init.registerOutputDir(normpath(p))
def definePathProperty(name, default, mustExist=False): """ Define a property that corresponds to a path. Path is normalized and any trailing slashes are removed. An error is raised if the path does not exist when the property is defined if mustExist=True. Paths are always absolutized. For paths which represent output directories of this build, call registerOutputDirProperties afterwards. @param name: The name of the property @param default: The default path value of the property (can contain other ${...} variables). If a relative path, will be resolved relative to the build file in which it is defined. If set to None, the property must be set on the command line each time @param mustExist: True if it's an error to specify a directory that doesn't exist (will raise a BuildException) """ # Expands properties, makes the path absolute, checks that it looks sensible and (if needed) whether the path exists def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if not os.path.isabs(value): # must absolutize this, as otherwise it might be used from a build # file in a different location, resulting in the same property # resolving to different effective values in different places value = BuildFileLocation(raiseOnError=True).buildDir + '/' + value value = normpath(value).rstrip('/\\') if mustExist and not os.path.exists(value): raise BuildException( 'Invalid path property value for "%s" - path "%s" does not exist' % (name, value)) return value init = getBuildInitializationContext() if init: init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
def defineOption(name, default): """ Define an option with a default (can be overridden globally using setGlobalOption() or on individual targets). This method is typically used only when implementing a new kind of target. Options are not available for ${...} expansion (like properties), but rather as used for (optionally inheritably) settings that affect the behaviour of one or more targets. They are accessed using self.options in any target instance. @param name: The option name, which should be in lowercase, and contain a prefix specific to this target or group of targets, e.g. "java.home" @param default: The default value of the option """ init = getBuildInitializationContext() if init: init._defineOption(name, default)
def __init__(self, name, dependencies): """ Normal constructor, should only be called from sub-classes since this is a stub. @param name: a unique name for this target (may contain unexpanded ${...} variables). Should correspond to the file or directory which is created as a result of running this target. @param dependencies: a list of dependencies, which may need to be flattened/expanded by the build system; may be any combination of strings, PathSets and lists and may also contain unexpanded variables. """ self._optionsTargetOverridesUnresolved = { } # for target-specific option overrides. for internal use (by buildcontext), do not use self.__optionsResolved = None # gets assigned during end of initialization phase if isinstance(name, basestring): if '//' in name: raise BuildException( 'Invalid target name: double slashes are not permitted: %s' % name) if '\\' in name: raise BuildException( 'Invalid target name: backslashes are not permitted: %s' % name) self.__name = str(name) self.__path_src = name self.__tags = ['all'] self.__priority = 0.0 # default so we can go bigger or smaller self.log = logging.getLogger(self.__class__.__name__) init = getBuildInitializationContext() if not init: # doc-test mode self.location = BuildFileLocation(raiseOnError=False) else: self.location = BuildFileLocation(raiseOnError=True) init.registerTarget(self) # should ensure changes to the build file cause a rebuild? probs no need # PathSet will perform all necessary flattening etc self.__dependencies = PathSet(dependencies) self.__path = None # set by _resolveTargetPath self.__workDir = None
def defineBooleanProperty(name, default=False): """ Defines a boolean property that will have a True or False value. @param name: The property name @param default: The default value (default = False) If set to None, the property must be set on the command line each time """ # Expands property values, then converts to a boolean def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(str(value)) if value.lower() == 'true': return True if value.lower() == 'false' or value=='': return False raise BuildException('Invalid property value for "%s" - must be true or false' % (name)) init = getBuildInitializationContext() if init: init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
def expandListProperty(propertyName): """ Utility method for use during property and target definition that returns a list containing the values of the specified list property. This is useful for quickly defining multiple targets (e.g. file copies) based on a list defined as a property. @param propertyName: must end with [] e.g. 'MY_JARS[]' """ assert not propertyName.startswith('$') assert propertyName.endswith('[]') context = getBuildInitializationContext() # although this isn't a valid return value, it's best to avoid triggering the assertion # below to support doc-testing custom xpybuild files that happen to use this method if (not context) and 'doctest' in sys.argv[0]: return ['${%s}'%propertyName] assert context, 'expandListProperty utility can only be used during build file initialization phase' return context.expandListPropertyValue(propertyName)
def definePathProperty(name, default, mustExist=False): """ Define a property that corresponds to a path. Path is normalized and any trailing slashes are removed. An error is raised if the path does not exist when the property is defined if mustExist=True. Paths are always absolutized. For paths which represent output directories of this build, call registerOutputDirProperties afterwards. @param name: The name of the property @param default: The default path value of the property (can contain other ${...} variables). If a relative path, will be resolved relative to the build file in which it is defined. If set to None, the property must be set on the command line each time @param mustExist: True if it's an error to specify a directory that doesn't exist (will raise a BuildException) """ # Expands properties, makes the path absolute, checks that it looks sensible and (if needed) whether the path exists def _coerceToValidValue(value): value = getBuildInitializationContext().expandPropertyValues(value) if not os.path.isabs(value): # must absolutize this, as otherwise it might be used from a build # file in a different location, resulting in the same property # resolving to different effective values in different places value = BuildFileLocation(raiseOnError=True).buildDir+'/'+value value = normpath(value).rstrip('/\\') if mustExist and not os.path.exists(value): raise BuildException('Invalid path property value for "%s" - path "%s" does not exist' % (name, value)) return value init = getBuildInitializationContext() if init: init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
def expandListProperty(propertyName): """ Utility method for use during property and target definition that returns a list containing the values of the specified list property. This is useful for quickly defining multiple targets (e.g. file copies) based on a list defined as a property. @param propertyName: must end with [] e.g. 'MY_JARS[]' """ assert not propertyName.startswith('$') assert propertyName.endswith('[]') context = getBuildInitializationContext() # although this isn't a valid return value, it's best to avoid triggering the assertion # below to support doc-testing custom xpybuild files that happen to use this method if (not context) and 'doctest' in sys.argv[0]: return ['${%s}' % propertyName] assert context, 'expandListProperty utility can only be used during build file initialization phase' return context.expandListPropertyValue(propertyName)
def defineOption(name, default): """ Define an option with a default (can be overridden globally using setGlobalOption() or on individual targets). This method is typically used only when implementing a new kind of target. Options are not available for ${...} expansion (like properties), but rather as used for (optionally inheritably) settings that affect the behaviour of one or more targets. They are accessed using self.options in any target instance. @param name: The option name, which should usually be in lowerCamelCase, with a TitleCase prefix specific to this target or group of targets, often matching the target name, e.g. "Javac.compilerArgs". @param default: The default value of the option """ init = getBuildInitializationContext() if init: init._defineOption(name, default) elif 'doctest' not in sys.argv[0] and 'epydoc' not in sys.modules: # this check is so we notice if unfortunate module order causes us to try to # define options before we have a real context to put them in assert False, 'Cannot define options at this point in the build as there is no initialization build context active'
def definePropertiesFromFile(propertiesFile, prefix=None, excludeLines=None, conditions=None): """ Defines a set of properties from a .properties file @param propertiesFile: The file to include properties from (can include ${...} variables) @param prefix: if specified, this prefix will be added to the start of all property names from this file @param excludeLines: a string of list of strings to search for, any KEY containing these strings will be ignored @param conditions: an optional list of string conditions that can appear in property keys e.g. "FOO<condition>=bar" where lines with no condition in this list are ignored. Conditions are typically lowercase. """ if conditions: assert not isinstance(conditions,basestring), 'conditions parameter must be a list' __log.info('Defining properties from file: %s', propertiesFile) context = getBuildInitializationContext() propertiesFile = context.getFullPath(propertiesFile, BuildFileLocation(raiseOnError=True).buildDir) try: f = open(propertiesFile, 'r') except Exception, e: raise BuildException('Failed to open properties file "%s"'%(propertiesFile), causedBy=True)
def registerPreBuildCheck(fn): """ Defines a check which will be called after any clean but before any build actions take place. fn should be a functor that takes a context and raises a BuildException if the check fails. """ from buildcontext import getBuildInitializationContext getBuildInitializationContext().registerPreBuildCheck(fn)