Chromium Code Reviews| Index: pylib/gyp/input.py |
| =================================================================== |
| --- pylib/gyp/input.py (revision 1400) |
| +++ pylib/gyp/input.py (working copy) |
| @@ -537,274 +537,278 @@ |
| assert False |
| input_str = str(input) |
| + if IsStrCanonicalInt(input_str): |
| + return int(input_str) |
| + |
| # Do a quick scan to determine if an expensive regex search is warranted. |
| - if expansion_symbol in input_str: |
| - # Get the entire list of matches as a list of MatchObject instances. |
| - # (using findall here would return strings instead of MatchObjects). |
| - matches = [match for match in variable_re.finditer(input_str)] |
| - else: |
| - matches = None |
| + if expansion_symbol not in input_str: |
| + return input_str |
| + # Get the entire list of matches as a list of MatchObject instances. |
| + # (using findall here would return strings instead of MatchObjects). |
| + matches = [match for match in variable_re.finditer(input_str)] |
| + if not matches: |
| + return input_str |
| + |
| output = input_str |
| - if matches: |
| - # Reverse the list of matches so that replacements are done right-to-left. |
| - # That ensures that earlier replacements won't mess up the string in a |
| - # way that causes later calls to find the earlier substituted text instead |
| - # of what's intended for replacement. |
| - matches.reverse() |
| - for match_group in matches: |
| - match = match_group.groupdict() |
| - gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| - "Matches: %s" % repr(match)) |
| - # match['replace'] is the substring to look for, match['type'] |
| - # is the character code for the replacement type (< > <! >! <| >| <@ |
| - # >@ <!@ >!@), match['is_array'] contains a '[' for command |
| - # arrays, and match['content'] is the name of the variable (< >) |
| - # or command to run (<! >!). match['command_string'] is an optional |
| - # command string. Currently, only 'pymod_do_main' is supported. |
| + # Reverse the list of matches so that replacements are done right-to-left. |
|
Nico
2012/05/28 21:54:50
Rietveld doesn't show it well, but from here on ev
|
| + # That ensures that earlier replacements won't mess up the string in a |
| + # way that causes later calls to find the earlier substituted text instead |
| + # of what's intended for replacement. |
| + matches.reverse() |
| + for match_group in matches: |
| + match = match_group.groupdict() |
| + gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| + "Matches: %s" % repr(match)) |
| + # match['replace'] is the substring to look for, match['type'] |
| + # is the character code for the replacement type (< > <! >! <| >| <@ |
| + # >@ <!@ >!@), match['is_array'] contains a '[' for command |
| + # arrays, and match['content'] is the name of the variable (< >) |
| + # or command to run (<! >!). match['command_string'] is an optional |
| + # command string. Currently, only 'pymod_do_main' is supported. |
| - # run_command is true if a ! variant is used. |
| - run_command = '!' in match['type'] |
| - command_string = match['command_string'] |
| + # run_command is true if a ! variant is used. |
| + run_command = '!' in match['type'] |
| + command_string = match['command_string'] |
| - # file_list is true if a | variant is used. |
| - file_list = '|' in match['type'] |
| + # file_list is true if a | variant is used. |
| + file_list = '|' in match['type'] |
| - # Capture these now so we can adjust them later. |
| - replace_start = match_group.start('replace') |
| - replace_end = match_group.end('replace') |
| + # Capture these now so we can adjust them later. |
| + replace_start = match_group.start('replace') |
| + replace_end = match_group.end('replace') |
| - # Find the ending paren, and re-evaluate the contained string. |
| - (c_start, c_end) = FindEnclosingBracketGroup(input_str[replace_start:]) |
| + # Find the ending paren, and re-evaluate the contained string. |
| + (c_start, c_end) = FindEnclosingBracketGroup(input_str[replace_start:]) |
| - # Adjust the replacement range to match the entire command |
| - # found by FindEnclosingBracketGroup (since the variable_re |
| - # probably doesn't match the entire command if it contained |
| - # nested variables). |
| - replace_end = replace_start + c_end |
| + # Adjust the replacement range to match the entire command |
| + # found by FindEnclosingBracketGroup (since the variable_re |
| + # probably doesn't match the entire command if it contained |
| + # nested variables). |
| + replace_end = replace_start + c_end |
| - # Find the "real" replacement, matching the appropriate closing |
| - # paren, and adjust the replacement start and end. |
| - replacement = input_str[replace_start:replace_end] |
| + # Find the "real" replacement, matching the appropriate closing |
| + # paren, and adjust the replacement start and end. |
| + replacement = input_str[replace_start:replace_end] |
| - # Figure out what the contents of the variable parens are. |
| - contents_start = replace_start + c_start + 1 |
| - contents_end = replace_end - 1 |
| - contents = input_str[contents_start:contents_end] |
| + # Figure out what the contents of the variable parens are. |
| + contents_start = replace_start + c_start + 1 |
| + contents_end = replace_end - 1 |
| + contents = input_str[contents_start:contents_end] |
| - # Do filter substitution now for <|(). |
| - # Admittedly, this is different than the evaluation order in other |
| - # contexts. However, since filtration has no chance to run on <|(), |
| - # this seems like the only obvious way to give them access to filters. |
| - if file_list: |
| - processed_variables = copy.deepcopy(variables) |
| - ProcessListFiltersInDict(contents, processed_variables) |
| - # Recurse to expand variables in the contents |
| - contents = ExpandVariables(contents, phase, |
| - processed_variables, build_file) |
| - else: |
| - # Recurse to expand variables in the contents |
| - contents = ExpandVariables(contents, phase, variables, build_file) |
| + # Do filter substitution now for <|(). |
| + # Admittedly, this is different than the evaluation order in other |
| + # contexts. However, since filtration has no chance to run on <|(), |
| + # this seems like the only obvious way to give them access to filters. |
| + if file_list: |
| + processed_variables = copy.deepcopy(variables) |
| + ProcessListFiltersInDict(contents, processed_variables) |
| + # Recurse to expand variables in the contents |
| + contents = ExpandVariables(contents, phase, |
| + processed_variables, build_file) |
| + else: |
| + # Recurse to expand variables in the contents |
| + contents = ExpandVariables(contents, phase, variables, build_file) |
| - # Strip off leading/trailing whitespace so that variable matches are |
| - # simpler below (and because they are rarely needed). |
| - contents = contents.strip() |
| + # Strip off leading/trailing whitespace so that variable matches are |
| + # simpler below (and because they are rarely needed). |
| + contents = contents.strip() |
| - # expand_to_list is true if an @ variant is used. In that case, |
| - # the expansion should result in a list. Note that the caller |
| - # is to be expecting a list in return, and not all callers do |
| - # because not all are working in list context. Also, for list |
| - # expansions, there can be no other text besides the variable |
| - # expansion in the input string. |
| - expand_to_list = '@' in match['type'] and input_str == replacement |
| + # expand_to_list is true if an @ variant is used. In that case, |
| + # the expansion should result in a list. Note that the caller |
| + # is to be expecting a list in return, and not all callers do |
| + # because not all are working in list context. Also, for list |
| + # expansions, there can be no other text besides the variable |
| + # expansion in the input string. |
| + expand_to_list = '@' in match['type'] and input_str == replacement |
| - if run_command or file_list: |
| - # Find the build file's directory, so commands can be run or file lists |
| - # generated relative to it. |
| - build_file_dir = os.path.dirname(build_file) |
| - if build_file_dir == '': |
| - # If build_file is just a leaf filename indicating a file in the |
| - # current directory, build_file_dir might be an empty string. Set |
| - # it to None to signal to subprocess.Popen that it should run the |
| - # command in the current directory. |
| - build_file_dir = None |
| + if run_command or file_list: |
| + # Find the build file's directory, so commands can be run or file lists |
| + # generated relative to it. |
| + build_file_dir = os.path.dirname(build_file) |
| + if build_file_dir == '': |
| + # If build_file is just a leaf filename indicating a file in the |
| + # current directory, build_file_dir might be an empty string. Set |
| + # it to None to signal to subprocess.Popen that it should run the |
| + # command in the current directory. |
| + build_file_dir = None |
| - # Support <|(listfile.txt ...) which generates a file |
| - # containing items from a gyp list, generated at gyp time. |
| - # This works around actions/rules which have more inputs than will |
| - # fit on the command line. |
| - if file_list: |
| - if type(contents) == list: |
| - contents_list = contents |
| - else: |
| - contents_list = contents.split(' ') |
| - replacement = contents_list[0] |
| - path = replacement |
| - if not os.path.isabs(path): |
| - path = os.path.join(build_file_dir, path) |
| - f = gyp.common.WriteOnDiff(path) |
| - for i in contents_list[1:]: |
| - f.write('%s\n' % i) |
| - f.close() |
| + # Support <|(listfile.txt ...) which generates a file |
| + # containing items from a gyp list, generated at gyp time. |
| + # This works around actions/rules which have more inputs than will |
| + # fit on the command line. |
| + if file_list: |
| + if type(contents) == list: |
| + contents_list = contents |
| + else: |
| + contents_list = contents.split(' ') |
| + replacement = contents_list[0] |
| + path = replacement |
| + if not os.path.isabs(path): |
| + path = os.path.join(build_file_dir, path) |
| + f = gyp.common.WriteOnDiff(path) |
| + for i in contents_list[1:]: |
| + f.write('%s\n' % i) |
| + f.close() |
| - elif run_command: |
| - use_shell = True |
| - if match['is_array']: |
| - contents = eval(contents) |
| - use_shell = False |
| + elif run_command: |
| + use_shell = True |
| + if match['is_array']: |
| + contents = eval(contents) |
| + use_shell = False |
| - # Check for a cached value to avoid executing commands, or generating |
| - # file lists more than once. |
| - # TODO(http://code.google.com/p/gyp/issues/detail?id=112): It is |
| - # possible that the command being invoked depends on the current |
| - # directory. For that case the syntax needs to be extended so that the |
| - # directory is also used in cache_key (it becomes a tuple). |
| - # TODO(http://code.google.com/p/gyp/issues/detail?id=111): In theory, |
| - # someone could author a set of GYP files where each time the command |
| - # is invoked it produces different output by design. When the need |
| - # arises, the syntax should be extended to support no caching off a |
| - # command's output so it is run every time. |
| - cache_key = str(contents) |
| - cached_value = cached_command_results.get(cache_key, None) |
| - if cached_value is None: |
| - gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| - "Executing command '%s' in directory '%s'" % |
| - (contents,build_file_dir)) |
| + # Check for a cached value to avoid executing commands, or generating |
| + # file lists more than once. |
| + # TODO(http://code.google.com/p/gyp/issues/detail?id=112): It is |
| + # possible that the command being invoked depends on the current |
| + # directory. For that case the syntax needs to be extended so that the |
| + # directory is also used in cache_key (it becomes a tuple). |
| + # TODO(http://code.google.com/p/gyp/issues/detail?id=111): In theory, |
| + # someone could author a set of GYP files where each time the command |
| + # is invoked it produces different output by design. When the need |
| + # arises, the syntax should be extended to support no caching off a |
| + # command's output so it is run every time. |
| + cache_key = str(contents) |
| + cached_value = cached_command_results.get(cache_key, None) |
| + if cached_value is None: |
| + gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| + "Executing command '%s' in directory '%s'" % |
| + (contents,build_file_dir)) |
| - replacement = '' |
| + replacement = '' |
| - if command_string == 'pymod_do_main': |
| - # <!pymod_do_main(modulename param eters) loads |modulename| as a |
| - # python module and then calls that module's DoMain() function, |
| - # passing ["param", "eters"] as a single list argument. For modules |
| - # that don't load quickly, this can be faster than |
| - # <!(python modulename param eters). Do this in |build_file_dir|. |
| - oldwd = os.getcwd() # Python doesn't like os.open('.'): no fchdir. |
| - os.chdir(build_file_dir) |
| + if command_string == 'pymod_do_main': |
| + # <!pymod_do_main(modulename param eters) loads |modulename| as a |
| + # python module and then calls that module's DoMain() function, |
| + # passing ["param", "eters"] as a single list argument. For modules |
| + # that don't load quickly, this can be faster than |
| + # <!(python modulename param eters). Do this in |build_file_dir|. |
| + oldwd = os.getcwd() # Python doesn't like os.open('.'): no fchdir. |
| + os.chdir(build_file_dir) |
| - parsed_contents = shlex.split(contents) |
| - py_module = __import__(parsed_contents[0]) |
| - replacement = str(py_module.DoMain(parsed_contents[1:])).rstrip() |
| + parsed_contents = shlex.split(contents) |
| + py_module = __import__(parsed_contents[0]) |
| + replacement = str(py_module.DoMain(parsed_contents[1:])).rstrip() |
| - os.chdir(oldwd) |
| - assert replacement != None |
| - elif command_string: |
| - raise Exception("Unknown command string '%s' in '%s'." % |
| - (command_string, contents)) |
| - else: |
| - # Fix up command with platform specific workarounds. |
| - contents = FixupPlatformCommand(contents) |
| - p = subprocess.Popen(contents, shell=use_shell, |
| - stdout=subprocess.PIPE, |
| - stderr=subprocess.PIPE, |
| - stdin=subprocess.PIPE, |
| - cwd=build_file_dir) |
| + os.chdir(oldwd) |
| + assert replacement != None |
| + elif command_string: |
| + raise Exception("Unknown command string '%s' in '%s'." % |
| + (command_string, contents)) |
| + else: |
| + # Fix up command with platform specific workarounds. |
| + contents = FixupPlatformCommand(contents) |
| + p = subprocess.Popen(contents, shell=use_shell, |
| + stdout=subprocess.PIPE, |
| + stderr=subprocess.PIPE, |
| + stdin=subprocess.PIPE, |
| + cwd=build_file_dir) |
| - p_stdout, p_stderr = p.communicate('') |
| + p_stdout, p_stderr = p.communicate('') |
| - if p.wait() != 0 or p_stderr: |
| - sys.stderr.write(p_stderr) |
| - # Simulate check_call behavior, since check_call only exists |
| - # in python 2.5 and later. |
| - raise Exception("Call to '%s' returned exit status %d." % |
| - (contents, p.returncode)) |
| - replacement = p_stdout.rstrip() |
| + if p.wait() != 0 or p_stderr: |
| + sys.stderr.write(p_stderr) |
| + # Simulate check_call behavior, since check_call only exists |
| + # in python 2.5 and later. |
| + raise Exception("Call to '%s' returned exit status %d." % |
| + (contents, p.returncode)) |
| + replacement = p_stdout.rstrip() |
| - cached_command_results[cache_key] = replacement |
| - else: |
| - gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| - "Had cache value for command '%s' in directory '%s'" % |
| - (contents,build_file_dir)) |
| - replacement = cached_value |
| - |
| + cached_command_results[cache_key] = replacement |
| else: |
| - if not contents in variables: |
| - if contents[-1] in ['!', '/']: |
| - # In order to allow cross-compiles (nacl) to happen more naturally, |
| - # we will allow references to >(sources/) etc. to resolve to |
| - # and empty list if undefined. This allows actions to: |
| - # 'action!': [ |
| - # '>@(_sources!)', |
| - # ], |
| - # 'action/': [ |
| - # '>@(_sources/)', |
| - # ], |
| - replacement = [] |
| - else: |
| - raise KeyError, 'Undefined variable ' + contents + \ |
| - ' in ' + build_file |
| - else: |
| - replacement = variables[contents] |
| + gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| + "Had cache value for command '%s' in directory '%s'" % |
| + (contents,build_file_dir)) |
| + replacement = cached_value |
| - if isinstance(replacement, list): |
| - for item in replacement: |
| - if (not contents[-1] == '/' and |
| - not isinstance(item, str) and not isinstance(item, int)): |
| - raise TypeError, 'Variable ' + contents + \ |
| - ' must expand to a string or list of strings; ' + \ |
| - 'list contains a ' + \ |
| - item.__class__.__name__ |
| - # Run through the list and handle variable expansions in it. Since |
| - # the list is guaranteed not to contain dicts, this won't do anything |
| - # with conditions sections. |
| - ProcessVariablesAndConditionsInList(replacement, phase, variables, |
| - build_file) |
| - elif not isinstance(replacement, str) and \ |
| - not isinstance(replacement, int): |
| - raise TypeError, 'Variable ' + contents + \ |
| - ' must expand to a string or list of strings; ' + \ |
| - 'found a ' + replacement.__class__.__name__ |
| - |
| - if expand_to_list: |
| - # Expanding in list context. It's guaranteed that there's only one |
| - # replacement to do in |input_str| and that it's this replacement. See |
| - # above. |
| - if isinstance(replacement, list): |
| - # If it's already a list, make a copy. |
| - output = replacement[:] |
| + else: |
| + if not contents in variables: |
| + if contents[-1] in ['!', '/']: |
| + # In order to allow cross-compiles (nacl) to happen more naturally, |
| + # we will allow references to >(sources/) etc. to resolve to |
| + # and empty list if undefined. This allows actions to: |
| + # 'action!': [ |
| + # '>@(_sources!)', |
| + # ], |
| + # 'action/': [ |
| + # '>@(_sources/)', |
| + # ], |
| + replacement = [] |
| else: |
| - # Split it the same way sh would split arguments. |
| - output = shlex.split(str(replacement)) |
| + raise KeyError, 'Undefined variable ' + contents + \ |
| + ' in ' + build_file |
| else: |
| - # Expanding in string context. |
| - encoded_replacement = '' |
| - if isinstance(replacement, list): |
| - # When expanding a list into string context, turn the list items |
| - # into a string in a way that will work with a subprocess call. |
| - # |
| - # TODO(mark): This isn't completely correct. This should |
| - # call a generator-provided function that observes the |
| - # proper list-to-argument quoting rules on a specific |
| - # platform instead of just calling the POSIX encoding |
| - # routine. |
| - encoded_replacement = gyp.common.EncodePOSIXShellList(replacement) |
| - else: |
| - encoded_replacement = replacement |
| + replacement = variables[contents] |
| - output = output[:replace_start] + str(encoded_replacement) + \ |
| - output[replace_end:] |
| - # Prepare for the next match iteration. |
| - input_str = output |
| + if isinstance(replacement, list): |
| + for item in replacement: |
| + if (not contents[-1] == '/' and |
| + not isinstance(item, str) and not isinstance(item, int)): |
| + raise TypeError, 'Variable ' + contents + \ |
| + ' must expand to a string or list of strings; ' + \ |
| + 'list contains a ' + \ |
| + item.__class__.__name__ |
| + # Run through the list and handle variable expansions in it. Since |
| + # the list is guaranteed not to contain dicts, this won't do anything |
| + # with conditions sections. |
| + ProcessVariablesAndConditionsInList(replacement, phase, variables, |
| + build_file) |
| + elif not isinstance(replacement, str) and \ |
| + not isinstance(replacement, int): |
| + raise TypeError, 'Variable ' + contents + \ |
| + ' must expand to a string or list of strings; ' + \ |
| + 'found a ' + replacement.__class__.__name__ |
| - # Look for more matches now that we've replaced some, to deal with |
| - # expanding local variables (variables defined in the same |
| - # variables block as this one). |
| - gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| - "Found output %s, recursing." % repr(output)) |
| - if isinstance(output, list): |
| - if output and isinstance(output[0], list): |
| - # Leave output alone if it's a list of lists. |
| - # We don't want such lists to be stringified. |
| - pass |
| + if expand_to_list: |
| + # Expanding in list context. It's guaranteed that there's only one |
| + # replacement to do in |input_str| and that it's this replacement. See |
| + # above. |
| + if isinstance(replacement, list): |
| + # If it's already a list, make a copy. |
| + output = replacement[:] |
| else: |
| - new_output = [] |
| - for item in output: |
| - new_output.append( |
| - ExpandVariables(item, phase, variables, build_file)) |
| - output = new_output |
| + # Split it the same way sh would split arguments. |
| + output = shlex.split(str(replacement)) |
| else: |
| - output = ExpandVariables(output, phase, variables, build_file) |
| + # Expanding in string context. |
| + encoded_replacement = '' |
| + if isinstance(replacement, list): |
| + # When expanding a list into string context, turn the list items |
| + # into a string in a way that will work with a subprocess call. |
| + # |
| + # TODO(mark): This isn't completely correct. This should |
| + # call a generator-provided function that observes the |
| + # proper list-to-argument quoting rules on a specific |
| + # platform instead of just calling the POSIX encoding |
| + # routine. |
| + encoded_replacement = gyp.common.EncodePOSIXShellList(replacement) |
| + else: |
| + encoded_replacement = replacement |
| + output = output[:replace_start] + str(encoded_replacement) + \ |
| + output[replace_end:] |
| + # Prepare for the next match iteration. |
| + input_str = output |
| + |
| + # Look for more matches now that we've replaced some, to deal with |
| + # expanding local variables (variables defined in the same |
| + # variables block as this one). |
| + gyp.DebugOutput(gyp.DEBUG_VARIABLES, |
| + "Found output %s, recursing." % repr(output)) |
| + if isinstance(output, list): |
| + if output and isinstance(output[0], list): |
| + # Leave output alone if it's a list of lists. |
| + # We don't want such lists to be stringified. |
| + pass |
| + else: |
| + new_output = [] |
| + for item in output: |
| + new_output.append( |
| + ExpandVariables(item, phase, variables, build_file)) |
| + output = new_output |
| + else: |
| + output = ExpandVariables(output, phase, variables, build_file) |
| + |
| # Convert all strings that are canonically-represented integers into integers. |
| if isinstance(output, list): |
| for index in xrange(0, len(output)): |