def include(file): """ Parse and register the targets and properties in in the specified ``XXX.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 xpybuild.buildcontext import getBuildInitializationContext from xpybuild.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 = {} exec(compile(open(filepath, "rb").read(), filepath, 'exec'), namespace, namespace) del BuildFileLocation._currentBuildFile[-1] return namespace
def registerBuildLoadPostProcessor(fn): """ Add a callback function that will be invoked just after the build files have been parsed and all targets added, to allow for custom post processing logic to execute before the clean/build. This is useful for performing global customizations of targets throughout the build, such as adding tags or additional target options. For example, to override the default retry setting and add a tag to all defined targets of a particular Python class:: def demoPostProcessor(context): for target in context.targets().values(): if isinstance(target, MyCustomTarget) or 'FooBar' in target.name: # Override options for these targets target.option(BaseTarget.Options.failureRetries, 2) # Add additional tags for these targets target.tags('my-post-processed-tag') registerBuildLoadPostProcessor(demoPostProcessor) :param Callable[xpybuild.buildcontext.BuildInitializationContext] fn: A function that takes a context as its argument. You may wish to use ``BuildInitializationContext`` methods such as `xpybuild.buildcontext.BuildInitializationContext.targets` from your function. """ from xpybuild.buildcontext import getBuildInitializationContext if not fn: return getBuildInitializationContext()._buildParsePostProcessors.append(fn)
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 xpybuild.buildcontext import getBuildInitializationContext targets = flatten(targets) getBuildInitializationContext().defineAtomicTargetGroup(targets) return targets
def registerPreBuildCheck(fn): """ Defines a check which will be called after any clean but before any build actions take place, to provide fail-fast behaviour if something is wrong. See also `registerBuildLoadPostProcessor`. :param Callable[xpybuild.buildcontext.BuildInitializationContext] fn: A function that takes a context as its argument, and raises a BuildException if the check fails. """ from xpybuild.buildcontext import getBuildInitializationContext getBuildInitializationContext().registerPreBuildCheck(fn)
def clearTags(self): """Called by build file authors to removes all tags other than ``all`` from this target. See `tag`. :return: Returns the same target instance it was called on, to permit fluent calling. """ init = getBuildInitializationContext() init.removeFromTags(self, self.__tags) self.__tags = ['full'] if 'full' in self.__tags else [] init.registerTags(self, self.__tags) return self
def disableInFullBuild(self): """Called by build file authors to configure this target to not build in ``all`` mode, so that it will only be built if the target name or tag is specified on the command line (or if pulled in by a dependency). This is useful for targets that perform operations such as configuring developer IDEs which would not be needed in the main build, or for expensive parts of the build that are often not needed such as generation of installers. See also `tag`. :return: Returns the same target instance it was called on, to permit fluent calling. """ self.__tags = list(set(self.__tags) - {'full'}) init = getBuildInitializationContext() init.removeFromTags(self, ['full']) return self
def tags(self, *tags: str): """Called by build file authors to append one or more tags to this target to make groups of related targets easier to build (or just to provide a shorter alias for the target on the command line). @param tags: The tag, tags or list of tags to add to the target. :return: Returns the same target instance it was called on, to permit fluent calling. >>> BaseTarget('a',[]).tags('abc').getTags() <using test initialization context> <using test initialization context> ['abc', 'full'] >>> BaseTarget('a',[]).tags(['abc', 'def']).getTags() <using test initialization context> <using test initialization context> ['abc', 'def', 'full'] >>> BaseTarget('a',[]).tags('abc', 'def').tags('ghi').getTags() <using test initialization context> <using test initialization context> <using test initialization context> ['ghi', 'abc', 'def', 'full'] """ 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 __init__(self, name, dependencies): self.__getAttrImpl = { 'path': lambda: self.__returnOrRaiseIfNone( self.__path, 'Target path has not yet been resolved by this phase of the build process: %s' % self), 'name': lambda: self.__name, 'options': lambda: self.__returnOrRaiseIfNone( self.__optionsResolved, "Cannot read the value of basetarget.targetOptions during the initialization phase of the build as the resolved option values are not yet available" ), 'workDir': lambda: self.__workDir, 'type': lambda: self.__class__.__name__, 'baseDir': lambda: self.location.buildDir, } 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, str): 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 = BaseTarget._normalizeTargetName(str(name)) self.__path_src = name self.__tags = ['full'] self.__priority = 0.0 # default so we can go bigger or smaller self.log = logging.getLogger(self.__class__.__name__) # put the class first, since it results in better ordering (e.g. for errors) # use a space to delimit these to make it easier to copy to the clipboard by double-clicking self.__stringvalue = f'<{self.type}> {self.name}' init = getBuildInitializationContext() if not init: # doc-test mode self.location = BuildFileLocation(raiseOnError=False) else: self.location = BuildFileLocation(raiseOnError=True) init.registerTarget(self) # this can throw # 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 self.__registeredImplicitInputs = [] # aliases for pre-3.0 self.addHashableImplicitInputOption = self.registerImplicitInputOption self.addHashableImplicitInput = self.registerImplicitInput