Exemple #1
0
def defineOption(name, default):
	''' Define an option controlling some behaviour of the build. 
	
	A default value is provided, which can be overridden on individual targets, or globally throughout the build 
	using `setGlobalOption`.
	
	This method is typically used only when implementing a new kind of target, and often nested in an ``Options`` class 
	under the target, e.g.::
	
		class MyTarget(BaseTarget):
			class Options:
				""" Options for customizing the behaviour of this target. To set an option on a specific target call 
				`xpybuild.basetarget.BaseTarget.option` or to se a global default use `xpybuild.propertysupport.setGlobalOption`. 
				"""
			
				myOption = defineOption("MyTarget.myOption", 123)
				"""
				Configures the XXX. 
				"""
	
	Unlike properties, option values are not accessed by ``${...}`` expansion, but rather using ``self.options`` 
	from any `xpybuild.basetarget.BaseTarget` subclass implementation. 
	
	@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. If you need a callable, try to use named functions rather than 
	lambdas so that the string representation is human-friendly. 
	If a string is provided, any properties will be expanded (use ``${{}`` to escape any literal ``{`` characters). 
	
	:returns: A new instance of `Option`. 
	'''
	BuildInitializationContext._defineOption(name, default)
	return Option(name, default)
Exemple #2
0
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

	@returns: The resolved property value. 
	"""
	init = BuildInitializationContext.getBuildInitializationContext()
	if init: return init.defineProperty(name, default, lambda v: BuildInitializationContext.getBuildInitializationContext().expandPropertyValues(v))
Exemple #3
0
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 = BuildInitializationContext.getBuildInitializationContext()
	if init:
		init.enableEnvironmentPropertyOverrides(prefix)
Exemple #4
0
	def _coerceToValidValue(value):
		value = BuildInitializationContext.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))
Exemple #5
0
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)

	@returns: The resolved property value. 
	"""

	# Expands properties, then checks that it's one of the acceptible values
	def _coerceToValidValue(value):
		value = BuildInitializationContext.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 = BuildInitializationContext.getBuildInitializationContext()
	if init:
		return init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
Exemple #6
0
	def _coerceToValidValue(value):
		value = BuildInitializationContext.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))
Exemple #7
0
def setGlobalOption(key, value):
	"""
	Globally override the default for an option throughout this build.
	
	:param obj value: If a string is provided, any properties will be expanded (use ``${{}`` to escape any literal ``{`` 
	characters). 

	"""
	init = BuildInitializationContext.getBuildInitializationContext()
	if init:
		init.setGlobalOption(key, value)
Exemple #8
0
def getPropertyValue(propertyName) -> object:
	""" Return the current value of the given property (can only be used during build file parsing).
	
	Where possible, instead of using this method defer property resolution until 
	the build phase (after all files have been parsed) and use `xpybuild.buildcontext.BuildContext.getPropertyValue` instead.
	
	For Boolean properties this will be a python Boolean, for everything else it will be a string. 
	"""
	context = BuildInitializationContext.getBuildInitializationContext()
	assert context, 'getProperty can only be used during build file initialization phase'
	return context.getPropertyValue(propertyName)
Exemple #9
0
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 = BuildInitializationContext.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)) 
Exemple #10
0
	def _coerceToValidValue(value):
		value = BuildInitializationContext.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
Exemple #11
0
def definePathProperty(name, default, mustExist=False):
	""" Define a string property that will be converted to an absolute 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)
	
	@returns: The resolved property value. 
	
	"""

	# Expands properties, makes the path absolute, checks that it looks sensible and (if needed) whether the path exists
	def _coerceToValidValue(value):
		value = BuildInitializationContext.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 = BuildInitializationContext.getBuildInitializationContext()
	if init: return init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
Exemple #12
0
def expandListProperty(propertyName) -> typing.List[str]:
	""" Utility method for use during build file parsing  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 = BuildInitializationContext.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)
Exemple #13
0
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

	@returns: The resolved property value. 
	"""

	# Expands property values, then converts to a boolean
	def _coerceToValidValue(value):
		value = BuildInitializationContext.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 = BuildInitializationContext.getBuildInitializationContext()
	if init:
		return init.defineProperty(name, default, coerceToValidValue=_coerceToValidValue)
Exemple #14
0
def definePropertiesFromFile(propertiesFile, prefix=None, excludeLines=None, conditions=None):
	"""
	Defines a set of string properties by reading a .properties file. 
	
	:param str propertiesFile: The file to include properties from (can include ${...} variables)

	:param str prefix: if specified, this prefix will be added to the start of all property names from this file

	:param list(str) excludeLines: a string of list of strings to search for, any KEY containing these strings will be ignored
	
	:param set(str) conditions: 
	
		An optional set or list of lower_case string conditions that can appear in property 
		keys to dyamically filter based on the platform and what kind of build is being performed. 
		
		For example ``MY_PROPERTY<windows>=bar``. Each line is only *included* if the condition matches one of the condition 
		strings passed to this function.
		
		For more advanced cases, a Python eval string can be specified, evaluated in a scope that includes the 
		``conditions`` set, a reference to the ``context`` (for property lookups), the ``IS_WINDOWS`` constant, and the 
		Python ``import_module`` function for accessing anything else. You cannot use the ``=`` character anywhere in the 
		eval string (since this is a properties file, after all!), but in most cases using the ``in`` operator is more 
		useful anyway. For example:: 
		
			MY_PROPERTY<      IS_WINDOWS and 'debug' not in conditions  > = windows-release.dll
			MY_PROPERTY< not (IS_WINDOWS and 'debug' not in conditions) > = everything-else.dll
			
	"""
	if conditions: assert not isinstance(conditions,str), 'conditions parameter must be a list'
	__log.info('Defining properties from file: %s', propertiesFile)
	context = BuildInitializationContext.getBuildInitializationContext()
	
	propertiesFile = context.getFullPath(propertiesFile, BuildFileLocation(raiseOnError=True).buildDir)
	try:
		f = open(propertiesFile, 'r') 
	except Exception as e:
		raise BuildException('Failed to open properties file "%s"'%(propertiesFile), causedBy=True)
	missingKeysFound = set()
	try:
		
		for key,value,lineNo in parsePropertiesFile(f, excludeLines=excludeLines):
			__log.debug('definePropertiesFromFile: expanding %s=%s', key, value)
			
			if '<' in key and conditions is not None:
				c = re.search('<([^>]+)>', key)
				if not c:
					raise BuildException('Error processing properties file, malformed <condition> line at %s'%formatFileLocation(propertiesFile, lineNo), causedBy=True)
				key = key.replace('<'+c.group(1)+'>', '')
				if '<' in key: raise BuildException('Error processing properties file, malformed line with multiple <condition> items at %s'%formatFileLocation(propertiesFile, lineNo), causedBy=True)
				
				condfilters = c.group(1)
				if '(' in condfilters or any([(' ' in x.strip()) for x in condfilters.split(',')]): # spaces (other than as "," delimiters are a clue this is more than a condition list
					# treat it as an eval string
					env = {
						'importlib':importlib, # in case they want to import anything else
						'import_module':importlib.import_module,
						'conditions':set(conditions),
						'context':context, # allows accessing properties and anything else that's needed
						'IS_WINDOWS':IS_WINDOWS,
					}
					try:
						matches = bool(eval(condfilters, env))
					except Exception as e:
						raise BuildException('Error processing properties file, malformed Python eval string in <condition> line at %s'%formatFileLocation(propertiesFile, lineNo), causedBy=True)
					__log.critical('Got: %s, %r', condfilters, eval(condfilters, env))
					
				else: # fall back to the quicker and simpler common case
					matches = True
					for cond in condfilters.split(','): # the ability to have a comma-separated list with "AND" semantics is undocumented (but is used in at least one place)
						if cond.strip() not in conditions:
							matches = False
							break
				if not matches:
					__log.debug('definePropertiesFromFile ignoring line that does not match condition: %s'%key)
					missingKeysFound.add(key)
					continue
				else:
					missingKeysFound.discard(key) # optimization to keep this list small
			
			if prefix: key = prefix+key 
			# TODO: make this work better by allowing props to be expanded using a local set of props from this file in addition to build props
			
			try:
				value = context.expandPropertyValues(value)
				context.defineProperty(key, value, debug=True, location=formatFileLocation(propertiesFile, lineNo))
			except BuildException as e:
				raise BuildException('Error processing properties file %s'%formatFileLocation(propertiesFile, lineNo), causedBy=True)
	finally:
		f.close()
	
	# ensure there the same set of properties is always defined regardless of conditions - 
	# otherwise it's easy for typos or latent bugs to creep in
	for k in missingKeysFound:
		try:
			context.getPropertyValue(k)
		except BuildException as e:
			raise BuildException('Error processing properties file %s: no property key found for "%s" matched any of the conditions: %s'%(
				propertiesFile, k, conditions), causedBy=False)