def _action(target, source, env): # prepare the line separator linesep = env['LINESEPARATOR'] if linesep is None: linesep = LINESEP # os.linesep elif is_String(linesep): pass elif isinstance(linesep, Value): linesep = linesep.get_text_contents() else: raise SCons.Errors.UserError('unexpected type/class for LINESEPARATOR: %s' % repr(linesep), None) if 'b' in TEXTFILE_FILE_WRITE_MODE: linesep = to_bytes(linesep) # create a dictionary to use for the substitutions if 'SUBST_DICT' not in env: subs = None # no substitutions else: subst_dict = env['SUBST_DICT'] if is_Dict(subst_dict): subst_dict = list(subst_dict.items()) elif is_Sequence(subst_dict): pass else: raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence') subs = [] for (k, value) in subst_dict: if callable(value): value = value() if is_String(value): value = env.subst(value) else: value = str(value) subs.append((k, value)) # write the file try: if SCons.Util.PY3: target_file = open(target[0].get_path(), TEXTFILE_FILE_WRITE_MODE, newline='') else: target_file = open(target[0].get_path(), TEXTFILE_FILE_WRITE_MODE) except (OSError, IOError): raise SCons.Errors.UserError("Can't write target file %s" % target[0]) # separate lines by 'linesep' only if linesep is not empty lsep = None for line in source: if lsep: target_file.write(lsep) target_file.write(_do_subst(line, subs)) lsep = linesep target_file.close()
def substitute(self, args, lvars): """Substitute expansions in an argument or list of arguments. This serves as a wrapper for splitting up a string into separate tokens. """ if is_String(args) and not isinstance(args, CmdStringHolder): args = str(args) # In case it's a UserString. try: def sub_match(match): return self.conv(self.expand(match.group(1), lvars)) result = _dollar_exps.sub(sub_match, args) except TypeError: # If the internal conversion routine doesn't return # strings (it could be overridden to return Nodes, for # example), then the 1.5.2 re module will throw this # exception. Back off to a slower, general-purpose # algorithm that works for all data types. args = _separate_args.findall(args) result = [] for a in args: result.append(self.conv(self.expand(a, lvars))) if len(result) == 1: result = result[0] else: result = ''.join(map(str, result)) return result else: return self.expand(args, lvars)
def expand(self, s, lvars): """Expand a single "token" as necessary, returning an appropriate string containing the expansion. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): return s if s0 != '$': return s if s1 == '$': return '$' elif s1 in '()': return s else: key = s[1:] if key[0] == '{' or key.find('.') >= 0: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception, e: if e.__class__ in AllowableExceptions: return '' raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif not NameError in AllowableExceptions: raise_exception(NameError(key), lvars['TARGETS'], s) else: return '' # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. # # This could potentially be optimized by only # copying lvars when s contains more expansions, # but lvars is usually supposed to be pretty # small, and deeply nested variable expansions # are probably more the exception than the norm, # so it should be tolerable for now. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' return self.substitute(s, lv)
def _arg2builder(env, arg): """Convert an argument to a builder object it refers. :Parameters: env : SCons.Environment.Environment a SCons Environment object arg an argument to be converted to a real builder, may be builder name or a builder object. What we need in several places of this tool is a builder which has an emitter. The user, however, will likely provide just a builder's name, not the builder object. The purpose of this function is to resolve these names to real builder objects. It also unwraps any CompositeBuilders and so on so the returned object is an instance of SCons.Builder.BuilderBase. """ from SCons.Util import is_String import SCons.Builder if is_String(arg): arg = env['BUILDERS'].get(arg) if arg is None: return arg if not isinstance(arg, SCons.Builder.BuilderBase): try: arg = arg.builder except AttributeError: arg = None return arg
def _action(target, source, env): # prepare the line separator linesep = env['LINESEPARATOR'] if linesep is None: linesep = os.linesep elif is_String(linesep): pass elif isinstance(linesep, Value): linesep = linesep.get_text_contents() else: raise SCons.Errors.UserError( 'unexpected type/class for LINESEPARATOR: %s' % repr(linesep), None) # create a dictionary to use for the substitutions if 'SUBST_DICT' not in env: subs = None # no substitutions else: d = env['SUBST_DICT'] if is_Dict(d): d = list(d.items()) elif is_Sequence(d): pass else: raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence') subs = [] for (k,v) in d: if callable(v): v = v() if is_String(v): v = env.subst(v) else: v = str(v) subs.append((k,v)) # write the file try: fd = open(target[0].get_path(), "wb") except (OSError, IOError, e): raise SCons.Errors.UserError("Can't write target file %s" % target[0]) # separate lines by 'linesep' only if linesep is not empty lsep = None for s in source: if lsep: fd.write(lsep) fd.write(_do_subst(s, subs)) lsep = linesep fd.close()
def execute(self, target, source, env): """Execute a command action. This will handle lists of commands as well as individual commands, because construction variable substitution may turn a single "command" into a list. This means that this class can actually handle lists of commands, even though that's not how we use it externally. """ from SCons.Subst import escape_list from SCons.Util import is_String, is_List, flatten try: shell = env['SHELL'] except KeyError: raise SCons.Errors.UserError('Missing SHELL construction variable.') try: spawn = env['SPAWN'] except KeyError: raise SCons.Errors.UserError('Missing SPAWN construction variable.') escape = env.get('ESCAPE', lambda x: x) try: ENV = env['ENV'] except KeyError: global default_ENV if not default_ENV: import SCons.Environment default_ENV = SCons.Environment.Environment()['ENV'] ENV = default_ENV # Ensure that the ENV values are all strings: for key, value in ENV.items(): if not is_String(value): if is_List(value): # If the value is a list, then we assume it is a # path list, because that's a pretty common list-like # value to stick in an environment variable: value = flatten(value) ENV[key] = string.join(map(str, value), os.pathsep) else: # If it isn't a string or a list, then we just coerce # it to a string, which is the proper way to handle # Dir and File instances and will produce something # reasonable for just about everything else: ENV[key] = str(value) cmd_list, ignore, silent = self.process(target, map(rfile, source), env) # Use len() to filter out any "command" that's zero-length. for cmd_line in filter(len, cmd_list): # Escape the command line for the interpreter we are using. cmd_line = escape_list(cmd_line, escape) result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) if not ignore and result: return result return 0
def expand(self, s, lvars, within_list): """Expand a single "token" as necessary, appending the expansion to the current result. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): self.append(s) return if s0 != '$': self.append(s) return if s1 == '$': self.append('$') elif s1 == '(': self.open_strip('$(') elif s1 == ')': self.close_strip('$)') else: key = s[1:] if key[0] == '{' or key.find('.') >= 0: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception, e: if e.__class__ in AllowableExceptions: return raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif not NameError in AllowableExceptions: raise_exception(NameError(), lvars['TARGETS'], s) else: return # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' self.substitute(s, lv, 0) self.this_word()
def scons_subst_once(strSubst, env, key): """Perform single (non-recursive) substitution of a single construction variable keyword. This is used when setting a variable when copying or overriding values in an Environment. We want to capture (expand) the old value before we override it, so people can do things like: env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') We do this with some straightforward, brute-force code here... """ if type(strSubst) == types.StringType and string.find(strSubst, "$") < 0: return strSubst matchlist = ["$" + key, "${" + key + "}"] val = env.get(key, "") def sub_match(match, val=val, matchlist=matchlist): a = match.group(1) if a in matchlist: a = val if is_Sequence(a): return string.join(map(str, a)) else: return str(a) if is_Sequence(strSubst): result = [] for arg in strSubst: if is_String(arg): if arg in matchlist: arg = val if is_Sequence(arg): result.extend(arg) else: result.append(arg) else: result.append(_dollar_exps.sub(sub_match, arg)) else: result.append(arg) return result elif is_String(strSubst): return _dollar_exps.sub(sub_match, strSubst) else: return strSubst
def add_new_word(self, x): if not self.in_strip or self.mode != SUBST_SIG: literal = self.literal(x) x = self.conv(x) if is_String(x): x = CmdStringHolder(x, literal) self[-1].append(x) self.append = self.add_to_current_word
def _action(target, source, env): # prepare the line separator linesep = env["LINESEPARATOR"] if linesep is None: linesep = os.linesep elif is_String(linesep): pass elif isinstance(linesep, Value): linesep = linesep.get_text_contents() else: raise SCons.Errors.UserError("unexpected type/class for LINESEPARATOR: %s" % repr(linesep), None) # create a dictionary to use for the substitutions if "SUBST_DICT" not in env: subs = None # no substitutions else: d = env["SUBST_DICT"] if is_Dict(d): d = list(d.items()) elif is_Sequence(d): pass else: raise SCons.Errors.UserError("SUBST_DICT must be dict or sequence") subs = [] for (k, v) in d: if callable(v): v = v() if is_String(v): v = env.subst(v) else: v = str(v) subs.append((k, v)) # write the file try: fd = open(target[0].get_path(), "wb") except (OSError, IOError), e: raise SCons.Errors.UserError("Can't write target file %s" % target[0])
def add_to_current_word(self, x): """Append the string x to the end of the current last word in the result. If that is not possible, then just add it as a new word. Make sure the entire concatenated string inherits the object attributes of x (in particular, the escape function) by wrapping it as CmdStringHolder.""" if not self.in_strip or self.mode != SUBST_SIG: try: current_word = self[-1][-1] except IndexError: self.add_new_word(x) else: # All right, this is a hack and it should probably # be refactored out of existence in the future. # The issue is that we want to smoosh words together # and make one file name that gets escaped if # we're expanding something like foo$EXTENSION, # but we don't want to smoosh them together if # it's something like >$TARGET, because then we'll # treat the '>' like it's part of the file name. # So for now, just hard-code looking for the special # command-line redirection characters... try: last_char = str(current_word)[-1] except IndexError: last_char = '\0' if last_char in '<>|': self.add_new_word(x) else: y = current_word + x # We used to treat a word appended to a literal # as a literal itself, but this caused problems # with interpreting quotes around space-separated # targets on command lines. Removing this makes # none of the "substantive" end-to-end tests fail, # so we'll take this out but leave it commented # for now in case there's a problem not covered # by the test cases and we need to resurrect this. #literal1 = self.literal(self[-1][-1]) #literal2 = self.literal(x) y = self.conv(y) if is_String(y): #y = CmdStringHolder(y, literal1 or literal2) y = CmdStringHolder(y, None) self[-1][-1] = y
def expand(self, s, lvars, within_list): """Expand a single "token" as necessary, appending the expansion to the current result. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): self.append(s) return if s0 != '$': self.append(s) return if s1 == '$': self.append('$') elif s1 == '(': self.open_strip('$(') elif s1 == ')': self.close_strip('$)') else: key = s[1:] if key[0] == '{' or string.find(key, '.') >= 0: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except AttributeError, e: raise SCons.Errors.UserError, \ "Error trying to evaluate `%s': %s" % (s, e) except (IndexError, NameError, TypeError): return except SyntaxError, e: if self.target: raise SCons.Errors.BuildError, ( self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e, s)) else: raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % ( e, s)
def test_is_String(self): assert is_String("") assert is_String(UserString('')) try: class mystr(str): pass except TypeError: pass else: assert is_String(mystr('')) assert not is_String({}) assert not is_String([]) assert not is_String(())
def expand(self, s, lvars, within_list): """Expand a single "token" as necessary, appending the expansion to the current result. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): self.append(s) return if s0 != '$': self.append(s) return if s1 == '$': self.append('$') elif s1 == '(': self.open_strip('$(') elif s1 == ')': self.close_strip('$)') else: key = s[1:] if key[0] == '{' or string.find(key, '.') >= 0: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except AttributeError, e: raise SCons.Errors.UserError, \ "Error trying to evaluate `%s': %s" % (s, e) except (IndexError, NameError, TypeError): return except SyntaxError,e: if self.target: raise SCons.Errors.BuildError, (self.target[0], "Syntax error `%s' trying to evaluate `%s'" % (e,s)) else: raise SCons.Errors.UserError, "Syntax error `%s' trying to evaluate `%s'" % (e,s)
def substitute(self, args, lvars, within_list): """Substitute expansions in an argument or list of arguments. This serves as a wrapper for splitting up a string into separate tokens. """ if is_String(args) and not isinstance(args, CmdStringHolder): args = _separate_args.findall(args) for a in args: if a[0] in ' \t\n\r\f\v': if '\n' in a: self.next_line() elif within_list: self.append(a) else: self.next_word() else: self.expand(a, lvars, within_list) else: self.expand(args, lvars, within_list)
def setup_scons_entities(env): try: shell = env['SHELL'] except KeyError: raise SCons.Errors.UserError('Missing SHELL construction variable.') try: spawn = env['SPAWN'] except KeyError: raise SCons.Errors.UserError('Missing SPAWN construction variable.') else: if type(spawn) == 'str': spawn = env.subst(spawn, raw=1, conv=lambda x: x) escape = env.get('ESCAPE', lambda x: x) try: ENV = env['ENV'] except KeyError: import SCons.Environment ENV = SCons.Environment.Environment()['ENV'] # Ensure that the ENV values are all strings: for key, value in list(ENV.items()): if not is_String(value): if is_List(value): # If the value is a list, then we assume it is a # path list, because that's a pretty common list-like # value to stick in an environment variable: value = flatten_sequence(value) ENV[key] = os.pathsep.join(map(str, value)) else: # If it isn't a string or a list, then we just coerce # it to a string, which is the proper way to handle # Dir and File instances and will produce something # reasonable for just about everything else: ENV[key] = str(value) return (shell, spawn, escape, ENV.copy())
def _action(target, source, env): # prepare the line seperator linesep = source.pop(0) if linesep == None: linesep = os.linesep elif not is_String(linesep): if not isinstance(linesep, Value): raise TypeError, \ 'unexpected type/class for LineSeperator: %s' % repr(linesep) else: linesep = linesep.get_contents() # write the file fd = open(target[0].get_path(), "w") # seperate lines by 'linesep' only if linesep is not empty if linesep: lsep = '' for s in source: fd.writelines((lsep, s.get_contents())) lsep = linesep else: for s in source: fd.write(s.get_contents()) fd.close()
def get_command(env, node, action): # pylint: disable=too-many-branches """Get the command to execute for node.""" if node.env: sub_env = node.env else: sub_env = env executor = node.get_executor() if executor is not None: tlist = executor.get_all_targets() slist = executor.get_all_sources() else: if hasattr(node, "target_peers"): tlist = node.target_peers else: tlist = [node] slist = node.sources # Retrieve the repository file for all sources slist = [rfile(s) for s in slist] # Get the dependencies for all targets implicit = list({dep for tgt in tlist for dep in get_dependencies(tgt)}) # Generate a real CommandAction if isinstance(action, SCons.Action.CommandGeneratorAction): # pylint: disable=protected-access action = action._generate(tlist, slist, sub_env, 1, executor=executor) rule = "CMD" # Actions like CommandAction have a method called process that is # used by SCons to generate the cmd_line they need to run. So # check if it's a thing like CommandAction and call it if we can. if hasattr(action, "process"): cmd_list, _, _ = action.process(tlist, slist, sub_env, executor=executor) # Despite being having "list" in it's name this member is not # actually a list. It's the pre-subst'd string of the command. We # use it to determine if the command we generated needs to use a # custom Ninja rule. By default this redirects CC/CXX commands to # CMD_W_DEPS but the user can inject custom Ninja rules and tie # them to commands by using their pre-subst'd string. rule = __NINJA_RULE_MAPPING.get(action.cmd_list, "CMD") cmd = _string_from_cmd_list(cmd_list[0]) else: # Anything else works with genstring, this is most commonly hit by # ListActions which essentially call process on all of their # commands and concatenate it for us. genstring = action.genstring(tlist, slist, sub_env) # Detect if we have a custom rule for this # "ListActionCommandAction" type thing. rule = __NINJA_RULE_MAPPING.get(genstring, "CMD") if executor is not None: cmd = sub_env.subst(genstring, executor=executor) else: cmd = sub_env.subst(genstring, target=tlist, source=slist) # Since we're only enabling Ninja for developer builds right # now we skip all Manifest related work on Windows as it's not # necessary. We shouldn't have gotten here but on Windows # SCons has a ListAction which shows as a # CommandGeneratorAction for linking. That ListAction ends # with a FunctionAction (embedManifestExeCheck, # embedManifestDllCheck) that simply say "does # target[0].manifest exist?" if so execute the real command # action underlying me, otherwise do nothing. # # Eventually we'll want to find a way to translate this to # Ninja but for now, and partially because the existing Ninja # generator does so, we just disable it all together. cmd = cmd.replace("\n", " && ").strip() if env["PLATFORM"] == "win32" and ("embedManifestExeCheck" in cmd or "embedManifestDllCheck" in cmd): cmd = " && ".join(cmd.split(" && ")[0:-1]) if cmd.endswith("&&"): cmd = cmd[0:-2].strip() outputs = get_outputs(node) if rule == "CMD_W_DEPS": # When using a depfile Ninja can only have a single output but # SCons will usually have emitted an output for every thing a # command will create because it's caching is much more # complex than Ninja's. This includes things like DWO # files. Here we make sure that Ninja only ever sees one # target when using a depfile. It will still have a command # that will create all of the outputs but most targets don't # depend direclty on DWO files and so this assumption is # safe to make. outputs = outputs[0:1] command_env = getattr(node.attributes, "NINJA_ENV_ENV", "") # If win32 and rule == CMD_W_DEPS then we don't want to calculate # an environment for this command. It's a compile command and # compiledb doesn't support shell syntax on Windows. We need the # shell syntax to use environment variables on Windows so we just # skip this platform / rule combination to keep the compiledb # working. # # On POSIX we can still set environment variables even for compile # commands so we do so. if not command_env and not (env["PLATFORM"] == "win32" and rule == "CMD_W_DEPS"): ENV = get_default_ENV(sub_env) # This is taken wholesale from SCons/Action.py # # Ensure that the ENV values are all strings: for key, value in ENV.items(): if not is_String(value): if is_List(value): # If the value is a list, then we assume it is a # path list, because that's a pretty common list-like # value to stick in an environment variable: value = flatten_sequence(value) value = os.pathsep.join(map(str, value)) else: # If it isn't a string or a list, then we just coerce # it to a string, which is the proper way to handle # Dir and File instances and will produce something # reasonable for just about everything else: value = str(value) if env["PLATFORM"] == "win32": command_env += "set '{}={}' && ".format(key, value) else: command_env += "{}={} ".format(key, value) setattr(node.attributes, "NINJA_ENV_ENV", command_env) ninja_build = { "outputs": outputs, "implicit": implicit, "rule": rule, "variables": { "cmd": command_env + cmd }, } # Don't use sub_env here because we require that NINJA_POOL be set # on a per-builder call basis to prevent accidental strange # behavior like env['NINJA_POOL'] = 'console' and sub_env can be # the global Environment object if node.env is None. # Example: # # Allowed: # # env.Command("ls", NINJA_POOL="ls_pool") # # Not allowed and ignored: # # env["NINJA_POOL"] = "ls_pool" # env.Command("ls") # if node.env and node.env.get("NINJA_POOL", None) is not None: ninja_build["pool"] = node.env["NINJA_POOL"] return ninja_build
def _node(env, path): if is_String(path): return env.File(path) else: return path
def execute(self, target, source, env): """Execute a command action. This will handle lists of commands as well as individual commands, because construction variable substitution may turn a single "command" into a list. This means that this class can actually handle lists of commands, even though that's not how we use it externally. """ from SCons.Subst import escape_list from SCons.Util import is_String, is_List, flatten try: shell = env['SHELL'] except KeyError: raise SCons.Errors.UserError( 'Missing SHELL construction variable.') try: spawn = env['SPAWN'] except KeyError: raise SCons.Errors.UserError( 'Missing SPAWN construction variable.') escape = env.get('ESCAPE', lambda x: x) try: ENV = env['ENV'] except KeyError: global default_ENV if not default_ENV: import SCons.Environment default_ENV = SCons.Environment.Environment()['ENV'] ENV = default_ENV # Ensure that the ENV values are all strings: for key, value in ENV.items(): if not is_String(value): if is_List(value): # If the value is a list, then we assume it is a # path list, because that's a pretty common list-like # value to stick in an environment variable: value = flatten(value) ENV[key] = string.join(map(str, value), os.pathsep) else: # If it isn't a string or a list, then we just coerce # it to a string, which is the proper way to handle # Dir and File instances and will produce something # reasonable for just about everything else: ENV[key] = str(value) cmd_list, ignore, silent = self.process(target, map(rfile, source), env) # Use len() to filter out any "command" that's zero-length. for cmd_line in filter(len, cmd_list): # Escape the command line for the interpreter we are using. cmd_line = escape_list(cmd_line, escape) result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) if not ignore and result: return result return 0
def expand(self, s, lvars): """Expand a single "token" as necessary, returning an appropriate string containing the expansion. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): return s if s0 != '$': return s if s1 == '$': return '$' elif s1 in '()': return s else: key = s[1:] if key[0] == '{' or '.' in key: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return '' raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif not NameError in AllowableExceptions: raise_exception(NameError(key), lvars['TARGETS'], s) else: return '' # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. # # This could potentially be optimized by only # copying lvars when s contains more expansions, # but lvars is usually supposed to be pretty # small, and deeply nested variable expansions # are probably more the exception than the norm, # so it should be tolerable for now. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' return self.substitute(s, lv) elif is_Sequence(s): def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): return conv(substitute(l, lvars)) return list(map(func, s)) elif callable(s): try: s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) except TypeError: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: return s s = self.conv(s) return self.substitute(s, lvars) elif s is None: return '' else: return s
# find a __builtins__ value in the global dictionary used for eval(), # it copies the current global values for you. Avoid this by # setting it explicitly and then deleting, so we don't pollute the # construction environment Dictionary(ies) that are typically used # for expansion. gvars['__builtins__'] = __builtins__ ss = StringSubber(env, mode, conv, gvars) result = ss.substitute(strSubst, lvars) try: del gvars['__builtins__'] except KeyError: pass if is_String(result): # Remove $(-$) pairs and any stuff in between, # if that's appropriate. remove = _regex_remove[mode] if remove: result = remove.sub('', result) if mode != SUBST_RAW: # Compress strings of white space characters into # a single space. result = _space_sep.sub(' ', result).strip() elif is_Sequence(result): remove = _list_remove[mode] if remove: result = remove(result) return result
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): """Expand a string or list containing construction variable substitutions. This is the work-horse function for substitutions in file names and the like. The companion scons_subst_list() function (below) handles separating command lines into lists of arguments, so see that function if that's what you're looking for. """ if isinstance(strSubst, str) and strSubst.find('$') < 0: return strSubst class StringSubber(object): """A class to construct the results of a scons_subst() call. This binds a specific construction environment, mode, target and source with two methods (substitute() and expand()) that handle the expansion. """ def __init__(self, env, mode, conv, gvars): self.env = env self.mode = mode self.conv = conv self.gvars = gvars def expand(self, s, lvars): """Expand a single "token" as necessary, returning an appropriate string containing the expansion. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): return s if s0 != '$': return s if s1 == '$': # In this case keep the double $'s which we'll later # swap for a single dollar sign as we need to retain # this information to properly avoid matching "$("" when # the actual text was "$$("" (or "$)"" when "$$)"" ) return '$$' elif s1 in '()': return s else: key = s[1:] if key[0] == '{' or '.' in key: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return '' raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif NameError not in AllowableExceptions: raise_exception(NameError(key), lvars['TARGETS'], s) else: return '' # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. # # This could potentially be optimized by only # copying lvars when s contains more expansions, # but lvars is usually supposed to be pretty # small, and deeply nested variable expansions # are probably more the exception than the norm, # so it should be tolerable for now. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' return self.substitute(s, lv) elif is_Sequence(s): def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): return conv(substitute(l, lvars)) return list(map(func, s)) elif callable(s): try: s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) except TypeError: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: return s s = self.conv(s) return self.substitute(s, lvars) elif s is None: return '' else: return s def substitute(self, args, lvars): """Substitute expansions in an argument or list of arguments. This serves as a wrapper for splitting up a string into separate tokens. """ if is_String(args) and not isinstance(args, CmdStringHolder): args = str(args) # In case it's a UserString. try: def sub_match(match): return self.conv(self.expand(match.group(1), lvars)) result = _dollar_exps.sub(sub_match, args) except TypeError: # If the internal conversion routine doesn't return # strings (it could be overridden to return Nodes, for # example), then the 1.5.2 re module will throw this # exception. Back off to a slower, general-purpose # algorithm that works for all data types. args = _separate_args.findall(args) result = [] for a in args: result.append(self.conv(self.expand(a, lvars))) if len(result) == 1: result = result[0] else: result = ''.join(map(str, result)) return result else: return self.expand(args, lvars) if conv is None: conv = _strconv[mode] # Doing this every time is a bit of a waste, since the Executor # has typically already populated the OverrideEnvironment with # $TARGET/$SOURCE variables. We're keeping this (for now), though, # because it supports existing behavior that allows us to call # an Action directly with an arbitrary target+source pair, which # we use in Tool/tex.py to handle calling $BIBTEX when necessary. # If we dropped that behavior (or found another way to cover it), # we could get rid of this call completely and just rely on the # Executor setting the variables. if 'TARGET' not in lvars: d = subst_dict(target, source) if d: lvars = lvars.copy() lvars.update(d) # We're (most likely) going to eval() things. If Python doesn't # find a __builtins__ value in the global dictionary used for eval(), # it copies the current global values for you. Avoid this by # setting it explicitly and then deleting, so we don't pollute the # construction environment Dictionary(ies) that are typically used # for expansion. gvars['__builtins__'] = __builtins__ ss = StringSubber(env, mode, conv, gvars) result = ss.substitute(strSubst, lvars) try: del gvars['__builtins__'] except KeyError: pass res = result if is_String(result): # Remove $(-$) pairs and any stuff in between, # if that's appropriate. remove = _regex_remove[mode] if remove: if mode == SUBST_SIG: result = _list_remove[mode](remove.split(result)) if result is None: raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res) result = ' '.join(result) else: result = remove.sub('', result) if mode != SUBST_RAW: # Compress strings of white space characters into # a single space. result = _space_sep.sub(' ', result).strip() # Now replace escaped $'s currently "$$" # This is needed because we now retain $$ instead of # replacing them during substition to avoid # improperly trying to escape "$$(" as being "$(" result = result.replace('$$', '$') elif is_Sequence(result): remove = _list_remove[mode] if remove: result = remove(result) if result is None: raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res)) return result
def expand(self, s, lvars, within_list): """Expand a single "token" as necessary, appending the expansion to the current result. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): self.append(s) return if s0 != '$': self.append(s) return if s1 == '$': self.append('$') elif s1 == '(': self.open_strip('$(') elif s1 == ')': self.close_strip('$)') else: key = s[1:] if key[0] == '{' or key.find('.') >= 0: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif not NameError in AllowableExceptions: raise_exception(NameError(), lvars['TARGETS'], s) else: return # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' self.substitute(s, lv, 0) self.this_word() elif is_Sequence(s): for a in s: self.substitute(a, lvars, 1) self.next_word() elif callable(s): try: s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) except TypeError: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: self.append(s) return s = self.conv(s) self.substitute(s, lvars, within_list) elif s is None: self.this_word() else: self.append(s)
def expand(self, s, lvars, within_list): """Expand a single "token" as necessary, appending the expansion to the current result. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): self.append(s) return if s0 != '$': self.append(s) return if s1 == '$': self.append('$') elif s1 == '(': self.open_strip('$(') elif s1 == ')': self.close_strip('$)') else: key = s[1:] if key[0] == '{' or key.find('.') >= 0: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif NameError not in AllowableExceptions: raise_exception(NameError(), lvars['TARGETS'], s) else: return # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' self.substitute(s, lv, 0) self.this_word() elif is_Sequence(s): for a in s: self.substitute(a, lvars, 1) self.next_word() elif callable(s): try: s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) except TypeError: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: self.append(s) return s = self.conv(s) self.substitute(s, lvars, within_list) elif s is None: self.this_word() else: self.append(s)
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): """Expand a string or list containing construction variable substitutions. This is the work-horse function for substitutions in file names and the like. The companion scons_subst_list() function (below) handles separating command lines into lists of arguments, so see that function if that's what you're looking for. """ if isinstance(strSubst, str) and strSubst.find('$') < 0: return strSubst class StringSubber(object): """A class to construct the results of a scons_subst() call. This binds a specific construction environment, mode, target and source with two methods (substitute() and expand()) that handle the expansion. """ def __init__(self, env, mode, conv, gvars): self.env = env self.mode = mode self.conv = conv self.gvars = gvars def expand(self, s, lvars): """Expand a single "token" as necessary, returning an appropriate string containing the expansion. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): return s if s0 != '$': return s if s1 == '$': return '$' elif s1 in '()': return s else: key = s[1:] if key[0] == '{' or '.' in key: if key[0] == '{': key = key[1:-1] try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return '' raise_exception(e, lvars['TARGETS'], s) else: if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] elif not NameError in AllowableExceptions: raise_exception(NameError(key), lvars['TARGETS'], s) else: return '' # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. # # This could potentially be optimized by only # copying lvars when s contains more expansions, # but lvars is usually supposed to be pretty # small, and deeply nested variable expansions # are probably more the exception than the norm, # so it should be tolerable for now. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' return self.substitute(s, lv) elif is_Sequence(s): def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): return conv(substitute(l, lvars)) return list(map(func, s)) elif callable(s): try: s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) except TypeError: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: return s s = self.conv(s) return self.substitute(s, lvars) elif s is None: return '' else: return s def substitute(self, args, lvars): """Substitute expansions in an argument or list of arguments. This serves as a wrapper for splitting up a string into separate tokens. """ if is_String(args) and not isinstance(args, CmdStringHolder): args = str(args) # In case it's a UserString. try: def sub_match(match): return self.conv(self.expand(match.group(1), lvars)) result = _dollar_exps.sub(sub_match, args) except TypeError: # If the internal conversion routine doesn't return # strings (it could be overridden to return Nodes, for # example), then the 1.5.2 re module will throw this # exception. Back off to a slower, general-purpose # algorithm that works for all data types. args = _separate_args.findall(args) result = [] for a in args: result.append(self.conv(self.expand(a, lvars))) if len(result) == 1: result = result[0] else: result = ''.join(map(str, result)) return result else: return self.expand(args, lvars) if conv is None: conv = _strconv[mode] # Doing this every time is a bit of a waste, since the Executor # has typically already populated the OverrideEnvironment with # $TARGET/$SOURCE variables. We're keeping this (for now), though, # because it supports existing behavior that allows us to call # an Action directly with an arbitrary target+source pair, which # we use in Tool/tex.py to handle calling $BIBTEX when necessary. # If we dropped that behavior (or found another way to cover it), # we could get rid of this call completely and just rely on the # Executor setting the variables. if 'TARGET' not in lvars: d = subst_dict(target, source) if d: lvars = lvars.copy() lvars.update(d) # We're (most likely) going to eval() things. If Python doesn't # find a __builtins__ value in the global dictionary used for eval(), # it copies the current global values for you. Avoid this by # setting it explicitly and then deleting, so we don't pollute the # construction environment Dictionary(ies) that are typically used # for expansion. gvars['__builtins__'] = __builtins__ ss = StringSubber(env, mode, conv, gvars) result = ss.substitute(strSubst, lvars) try: del gvars['__builtins__'] except KeyError: pass res = result if is_String(result): # Remove $(-$) pairs and any stuff in between, # if that's appropriate. remove = _regex_remove[mode] if remove: if mode == SUBST_SIG: result = _list_remove[mode](remove.split(result)) if result is None: raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res) result = ' '.join(result) else: result = remove.sub('', result) if mode != SUBST_RAW: # Compress strings of white space characters into # a single space. result = _space_sep.sub(' ', result).strip() elif is_Sequence(result): remove = _list_remove[mode] if remove: result = remove(result) if result is None: raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res)) return result
def expand(self, s, lvars, within_list): """Expand a single "token" as necessary, appending the expansion to the current result. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): self.append(s) return if s0 != '$': self.append(s) return if s1 == '$': self.append('$') elif s1 == '(': self.open_strip('$(') elif s1 == ')': self.close_strip('$)') else: key = s[1:] if key[0] == '{' or key.find('.') >= 0: if key[0] == '{': key = key[1:-1] # Store for error messages if we fail to expand the # value old_s = s s = None if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] else: try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return raise_exception(e, lvars['TARGETS'], old_s) if s is None and NameError not in AllowableExceptions: raise_exception(NameError(), lvars['TARGETS'], old_s) elif s is None: return # If the string is already full expanded there's no # need to continue recursion. if self.expanded(s): self.append(s) return # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' self.substitute(s, lv, 0) self.this_word() elif is_Sequence(s): for a in s: self.substitute(a, lvars, 1) self.next_word() elif callable(s): # SCons has the unusual Null class where any __getattr__ call returns it's self, # which does not work the signature module, and the Null class returns an empty # string if called on, so we make an exception in this condition for Null class if (isinstance(s, SCons.Util.Null) or set(signature(s).parameters.keys()) == set( ['target', 'source', 'env', 'for_signature'])): s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) else: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: self.append(s) return s = self.conv(s) self.substitute(s, lvars, within_list) elif s is None: self.this_word() else: self.append(s)
def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): """Expand a string or list containing construction variable substitutions. This is the work-horse function for substitutions in file names and the like. The companion scons_subst_list() function (below) handles separating command lines into lists of arguments, so see that function if that's what you're looking for. """ if (isinstance(strSubst, str) and '$' not in strSubst) or isinstance( strSubst, CmdStringHolder): return strSubst if conv is None: conv = _strconv[mode] # Doing this every time is a bit of a waste, since the Executor # has typically already populated the OverrideEnvironment with # $TARGET/$SOURCE variables. We're keeping this (for now), though, # because it supports existing behavior that allows us to call # an Action directly with an arbitrary target+source pair, which # we use in Tool/tex.py to handle calling $BIBTEX when necessary. # If we dropped that behavior (or found another way to cover it), # we could get rid of this call completely and just rely on the # Executor setting the variables. if 'TARGET' not in lvars: d = subst_dict(target, source) if d: lvars = lvars.copy() lvars.update(d) # We're (most likely) going to eval() things. If Python doesn't # find a __builtins__ value in the global dictionary used for eval(), # it copies the current global values for you. Avoid this by # setting it explicitly and then deleting, so we don't pollute the # construction environment Dictionary(ies) that are typically used # for expansion. gvars['__builtins__'] = __builtins__ ss = StringSubber(env, mode, conv, gvars) result = ss.substitute(strSubst, lvars) try: del gvars['__builtins__'] except KeyError: pass res = result if is_String(result): # Remove $(-$) pairs and any stuff in between, # if that's appropriate. remove = _regex_remove[mode] if remove: if mode == SUBST_SIG: result = _list_remove[mode](remove.split(result)) if result is None: raise SCons.Errors.UserError("Unbalanced $(/$) in: " + res) result = ' '.join(result) else: result = remove.sub('', result) if mode != SUBST_RAW: # Compress strings of white space characters into # a single space. result = _space_sep.sub(' ', result).strip() # Now replace escaped $'s currently "$$" # This is needed because we now retain $$ instead of # replacing them during substition to avoid # improperly trying to escape "$$(" as being "$(" result = result.replace('$$', '$') elif is_Sequence(result): remove = _list_remove[mode] if remove: result = remove(result) if result is None: raise SCons.Errors.UserError("Unbalanced $(/$) in: " + str(res)) return result
def expand(self, s, lvars): """Expand a single "token" as necessary, returning an appropriate string containing the expansion. This handles expanding different types of things (strings, lists, callables) appropriately. It calls the wrapper substitute() method to re-expand things as necessary, so that the results of expansions of side-by-side strings still get re-evaluated separately, not smushed together. """ if is_String(s): try: s0, s1 = s[:2] except (IndexError, ValueError): return s if s0 != '$': return s if s1 == '$': # In this case keep the double $'s which we'll later # swap for a single dollar sign as we need to retain # this information to properly avoid matching "$("" when # the actual text was "$$("" (or "$)"" when "$$)"" ) return '$$' elif s1 in '()': return s else: key = s[1:] if key[0] == '{' or '.' in key: if key[0] == '{': key = key[1:-1] # Store for error messages if we fail to expand the # value old_s = s s = None if key in lvars: s = lvars[key] elif key in self.gvars: s = self.gvars[key] else: try: s = eval(key, self.gvars, lvars) except KeyboardInterrupt: raise except Exception as e: if e.__class__ in AllowableExceptions: return '' raise_exception(e, lvars['TARGETS'], old_s) if s is None and NameError not in AllowableExceptions: raise_exception(NameError(key), lvars['TARGETS'], old_s) elif s is None: return '' # Before re-expanding the result, handle # recursive expansion by copying the local # variable dictionary and overwriting a null # string for the value of the variable name # we just expanded. # # This could potentially be optimized by only # copying lvars when s contains more expansions, # but lvars is usually supposed to be pretty # small, and deeply nested variable expansions # are probably more the exception than the norm, # so it should be tolerable for now. lv = lvars.copy() var = key.split('.')[0] lv[var] = '' return self.substitute(s, lv) elif is_Sequence(s): def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): return conv(substitute(l, lvars)) return list(map(func, s)) elif callable(s): # SCons has the unusual Null class where any __getattr__ call returns it's self, # which does not work the signature module, and the Null class returns an empty # string if called on, so we make an exception in this condition for Null class if (isinstance(s, SCons.Util.Null) or set(signature(s).parameters.keys()) == set( ['target', 'source', 'env', 'for_signature'])): s = s(target=lvars['TARGETS'], source=lvars['SOURCES'], env=self.env, for_signature=(self.mode != SUBST_CMD)) else: # This probably indicates that it's a callable # object that doesn't match our calling arguments # (like an Action). if self.mode == SUBST_RAW: return s s = self.conv(s) return self.substitute(s, lvars) elif s is None: return '' else: return s