def __validate_artifact_name(self, filename, configured_name=None): typecheck(filename, basestring) normalized = os.path.normpath(filename).replace("\\", "/") if filename != normalized: raise DefinitionError( "File '%s' is not a normalized path name. Please use '%s' " "instead." % (filename, normalized)) if filename.startswith("../") or filename.startswith("/"): raise DefinitionError( "File '%s' points outside the surrounding directory. To " "include a file from another directory, that directory must explicitly " "export it." % filename)
def __init__(self, artifact, root_dir, state_map, config): typecheck(artifact, Artifact) typecheck(root_dir, Directory) typecheck(state_map, _StateMap) self.artifact = artifact self.config = config real_name = state_map.real_name(config, artifact) if real_name is None: # We couldn't get this artifact's name, because some things the name # depends on are dirty. All the artifacts needed to compute this # artifact's name will be registered as inputs to the action which # creates this artifact, so we know that the creating action won't run # until we can compute this artifact's name, and we know that any # dependent actions won't run until the creating action runs. So we're # all good. assert artifact.action is not None self.timestamp = -1 self.is_dirty = True elif root_dir.exists(real_name): self.timestamp = root_dir.getmtime(real_name) self.is_dirty = self.__decide_if_dirty(state_map) elif artifact.action is not None: # Derived artifact doesn't exist yet. self.timestamp = -1 self.is_dirty = True else: raise DefinitionError( "The required source file '%s' does not exist." % artifact.filename)
def read_previous_output(self, artifact): if artifact.action is not self.__action: raise DefinitionError("%s is not an output of %s." % (artifact, self.__action)) real_name = artifact.real_name(self.read) if real_name is not None and self.__config.root_dir.exists(real_name): return self.__config.root_dir.read(real_name) else: return None
def derived_artifact(self, filename, action, configured_name=None): typecheck(filename, basestring) typecheck(action, Action) if filename in self.__derived_artifacts: raise DefinitionError( "Two different rules claim to build file '%s'. Conflicting rules are " "'%s' and '%s'." % (filename, action.rule.name, self.__derived_artifacts[filename].action.rule.name)) filename = os.path.normpath(filename).replace("\\", "/") result = Artifact(filename, action, configured_name=configured_name) self.__derived_artifacts[filename] = result return result
def import_(self, name): typecheck(name, str) if (self.__loader is None): raise DefinitionError("Imports must occur at file load time.") # Absolute imports start with "//". if name.startswith("//"): name = name[2:] else: name = self.__prefix + name (result, timestamp) = self.__loader.load_with_timestamp(name) if timestamp > self.__context.timestamp: self.__context.timestamp = timestamp return result
def artifact_state(self, config, artifact): typecheck(artifact, Artifact) while artifact.alt_artifact is not None: config = config.alt_configs.get(artifact.alt_config) if config is None: raise DefinitionError( "Artifact '%s' refers to unknown configuration '%s'." % artifact, artifact.alt_config) artifact = artifact.alt_artifact result = self.__artifacts.get((config, artifact)) if result is None: result = _ArtifactState(artifact, config.root_dir, self, config) self.__artifacts[(config, artifact)] = result return result
def load_with_timestamp(self, targetname): """Like load(), but returns a tuple where the second element is the target's timestamp. This is most-recent modification time of the SEBS file defining the target and those that it imports.""" typecheck(targetname, basestring) parts = targetname.rsplit(":", 1) (file, context) = self.load_file(parts[0]) if len(parts) == 1: return (file, context.timestamp) else: try: target = eval(parts[1], file.__dict__.copy()) except Exception, e: raise DefinitionError("%s: %s" % (targetname, e.message)) return (target, context.timestamp)
def update_readiness(self, state_map): """Update self.is_ready based on input. If no inputs are dirty, is_ready is set true, otherwise it is set false. This method returns true if is_ready was modified (from false to true), or false if it kept its previous value.""" typecheck(state_map, _StateMap) if self.is_ready: # Already ready. No change is possible. return False enumerator = _ArtifactEnumeratorImpl(state_map, self.config, self.action) self.action.command.enumerate_artifacts(enumerator) self.blocking = set() for input in enumerator.inputs: input_state = state_map.artifact_state(self.config, input) if input_state.is_dirty: # Input is dirty, therefore it must have an action. blocking_state = state_map.action_state( input_state.config, input_state.artifact.action) if blocking_state.is_ready and \ input_state.artifact not in blocking_state.outputs: raise DefinitionError( "%s is needed, but %s didn't generate it." % (input_state.config, input_state.artifact.action)) blocking_state.blocked.add(self) self.blocking.add(blocking_state) if len(self.blocking) > 0: # At least one input is still dirty. return False self.is_ready = True self.inputs = enumerator.inputs self.disk_inputs = enumerator.disk_inputs self.outputs = enumerator.outputs return True
def output_artifact(self, directory, filename, action, configured_name=None): typecheck(directory, basestring) self.__validate_artifact_name(filename, configured_name) typecheck(action, Action) if directory not in ("bin", "include", "lib", "share"): raise DefinitionError("'%s' is not a valid output directory." % directory) if configured_name is not None: # caihsiaoster: use tree structure for binary configured_name = [directory + "/" + self.directory + "/" ] + configured_name # configured_name = [directory + "/"] + configured_name return self.__loader.derived_artifact( # caihsiaoster: use tree structure for library os.path.join(directory, self.directory, filename), action, configured_name=configured_name)
def get_disk_directory_path(self, dirname): result = self.__working_dir.get_disk_path(dirname) if result is None: raise DefinitionError("Not a disk directory: " + dirname) else: return result
def load_file(self, filename): """Load a SEBS file. The filename is given relative to the root of the source tree (the "src" directory). Returns an object whose fields correspond to the globals defined in that file.""" typecheck(filename, basestring) normalized = os.path.normpath(filename).replace("\\", "/") if filename != normalized: raise DefinitionError( "'%s' is not a normalized path name. Please use '%s' instead." % (filename, normalized)) if filename.startswith("../") or filename.startswith("/"): raise DefinitionError("'%s' is not within src." % filename) if self.__root_dir.isdir("src/" + filename): filename = filename + "/SEBS" if filename in self.__loaded_files: existing = self.__loaded_files[filename] if existing is None: raise DefinitionError("File recursively imports itself: %s", filename) return existing context = _ContextImpl(self, filename, self.__root_dir) builtins = _Builtins(self, context) def run(): # TODO(kenton): Remove SEBS itself from PYTHONPATH before parsing, since # SEBS files should not be importing the SEBS implementation. vars = {"sebs": builtins} self.__root_dir.execfile(context.full_filename, vars) return vars self.__loaded_files[filename] = None try: vars = context.run(run) finally: del self.__loaded_files[filename] # Prohibit lazy imports. builtins.disable() # Copy the vars before deleting anything because any functions defined in # the file still hold a reference to the original map as part of their # environment, so modifying the original map could break those functions. vars = vars.copy() # Delete "builtins", but not if the user replaced them with their own defs. if "sebs" in vars and vars["sebs"] is builtins: del vars["sebs"] for name, value in vars.items(): if isinstance(value, Rule) and value.context is context: # Set label on rule instance. value.label = name if name.startswith("_"): # Delete private variable. del vars[name] build_file = BuildFile(vars) self.__loaded_files[filename] = (build_file, context) return (build_file, context)
def __validate_env_name(self, name): typecheck(name, basestring) if not name.replace("_", "").isalnum(): raise DefinitionError( "'%s' is not a valid environment variable name" % name)