diff options
author | Philip Pfaffe <philip.pfaffe@kit.edu> | 2016-03-22 13:33:54 +0100 |
---|---|---|
committer | Kim Grasman <kim.grasman@gmail.com> | 2018-09-29 18:35:42 +0200 |
commit | 0a54b0ef992e4adca68d812dcb42edc88f5c1613 (patch) | |
tree | 2358e3e04db40778168af987f73a9e189b1a99c5 | |
parent | dbab9201143deb07f63688d4c6e3402dbd2ca7fe (diff) |
[iwyu_tool] Add --basedir argument
Enable specifiying a base dir relative to which the IWYU output should
be interpreted.
With the path normalization that happens when interpreting IWYU output,
we now need to normalize command-line arguments as well, in order for
filtering to work correctly.
Let test framework simulate a cwd to be able to test this.
-rwxr-xr-x | fix_includes.py | 30 | ||||
-rwxr-xr-x | fix_includes_test.py | 96 |
2 files changed, 115 insertions, 11 deletions
diff --git a/fix_includes.py b/fix_includes.py index fc223e8..a647782 100755 --- a/fix_includes.py +++ b/fix_includes.py @@ -320,7 +320,7 @@ class IWYUOutputParser(object): self.filename = '<unknown file>' self.lines_by_section = {} # key is an RE, value is a list of lines - def _ProcessOneLine(self, line): + def _ProcessOneLine(self, line, basedir=None): """Reads one line of input, updates self, and returns False at EORecord. If the line matches one of the hard-coded section names, updates @@ -347,7 +347,8 @@ class IWYUOutputParser(object): if m: # Check or set the filename (if the re has a group, it's for filename). if section_re.groups >= 1: - this_filename = m.group(1) + this_filename = NormalizeFilePath(basedir, m.group(1)) + if (self.current_section is not None and this_filename != self.filename): raise FixIncludesError('"%s" section for %s comes after "%s" for %s' @@ -398,7 +399,8 @@ class IWYUOutputParser(object): FixIncludesError: for malformed-looking lines in the iwyu output. """ for line in iwyu_output: - if not self._ProcessOneLine(line): # returns False at end-of-record + if not self._ProcessOneLine(line, flags.basedir): + # returns False at end-of-record break else: # for/else return None # at EOF @@ -2202,7 +2204,7 @@ def FixManyFiles(iwyu_records, flags): return files_fixed -def ProcessIWYUOutput(f, files_to_process, flags): +def ProcessIWYUOutput(f, files_to_process, flags, cwd): """Fix the #include and forward-declare lines as directed by f. Given a file object that has the output of the include_what_you_use @@ -2215,12 +2217,17 @@ def ProcessIWYUOutput(f, files_to_process, flags): flags: commandline flags, as parsed by optparse. The only flag we use directly is flags.ignore_re, to indicate files not to process; we also pass the flags to other routines. + cwd: the current working directory, externalized for testing. Returns: The number of files that had to be modified (because they weren't already all correct). In dry_run mode, returns the number of files that would have been modified. """ + if files_to_process is not None: + files_to_process = [NormalizeFilePath(cwd, fname) + for fname in files_to_process] + # First collect all the iwyu data from stdin. # Maintain sort order by using OrderedDict instead of dict @@ -2234,7 +2241,7 @@ def ProcessIWYUOutput(f, files_to_process, flags): except FixIncludesError as why: print('ERROR: %s' % why) continue - filename = iwyu_record.filename + filename = NormalizeFilePath(flags.basedir, iwyu_record.filename) if files_to_process is not None and filename not in files_to_process: print('(skipping %s: not listed on commandline)' % filename) continue @@ -2267,6 +2274,11 @@ def ProcessIWYUOutput(f, files_to_process, flags): return FixManyFiles(contentful_records, flags) +def NormalizeFilePath(basedir, filename): + if basedir and not os.path.isabs(filename): + return os.path.normpath(os.path.join(basedir, filename)) + return filename + def SortIncludesInFiles(files_to_process, flags): """For each file in files_to_process, sort its #includes. @@ -2285,6 +2297,7 @@ def SortIncludesInFiles(files_to_process, flags): """ sort_only_iwyu_records = [] for filename in files_to_process: + filename = NormalizeFilePath(flags.basedir, filename) # An empty iwyu record has no adds or deletes, so its only effect # is to cause us to sort the #include lines. (Since fix_includes # gets all its knowledge of where forward-declare lines are from @@ -2358,6 +2371,11 @@ def main(argv): parser.add_option('--nokeep_iwyu_namespace_format', action='store_false', dest='keep_iwyu_namespace_format') + parser.add_option('--basedir', '-p', default=None, + help=('Specify the base directory. fix_includes will ' + 'interpret non-absolute filenames relative to this ' + 'path.')) + (flags, files_to_modify) = parser.parse_args(argv[1:]) if files_to_modify: files_to_modify = set(files_to_modify) @@ -2375,7 +2393,7 @@ def main(argv): sys.exit('FATAL ERROR: -s flag requires a list of filenames') return SortIncludesInFiles(files_to_modify, flags) else: - return ProcessIWYUOutput(sys.stdin, files_to_modify, flags) + return ProcessIWYUOutput(sys.stdin, files_to_modify, flags, cwd=os.getcwd()) if __name__ == '__main__': diff --git a/fix_includes_test.py b/fix_includes_test.py index 45a8d8d..a8c1236 100755 --- a/fix_includes_test.py +++ b/fix_includes_test.py @@ -41,6 +41,7 @@ class FakeFlags(object): self.separate_project_includes = None self.keep_iwyu_namespace_format = False self.reorder = True + self.basedir = None class FixIncludesBase(unittest.TestCase): @@ -119,7 +120,7 @@ class FixIncludesBase(unittest.TestCase): self.expected_after_map[filename] = expected_after_contents def ProcessAndTest(self, iwyu_output, cmdline_files=None, unedited_files=[], - expected_num_modified_files=None): + expected_num_modified_files=None, cwd=None): """For all files mentioned in iwyu_output, compare expected and actual. Arguments: @@ -132,6 +133,8 @@ class FixIncludesBase(unittest.TestCase): but fix_files has chosen not to edit for some reason. expected_num_modified_files: what we expect ProcessIWYUOutput to return. If None, suppress this check. + cwd: working directory passed to ProcessIWYUOutput, used to normalize + paths in cmdline_files. If None, no normalization occurs. """ filenames = re.findall('^(\S+) should add these lines:', iwyu_output, re.M) if not filenames: # This is the other possible starting-line @@ -140,13 +143,15 @@ class FixIncludesBase(unittest.TestCase): expected_after = [] for filename in fix_includes.OrderedSet(filenames): # uniquify + filename = fix_includes.NormalizeFilePath(self.flags.basedir, filename) if filename not in unedited_files: expected_after.extend(self.expected_after_map[filename]) iwyu_output_as_file = StringIO(iwyu_output) num_modified_files = fix_includes.ProcessIWYUOutput(iwyu_output_as_file, cmdline_files, - self.flags) + self.flags, + cwd=cwd) if expected_after != self.actual_after_contents: print("=== Expected:") @@ -245,7 +250,7 @@ int main() { return 0; } # we can't use the normal ProcessAndTest. iwyu_output_as_file = StringIO(iwyu_output) num_modified_files = fix_includes.ProcessIWYUOutput(iwyu_output_as_file, - None, self.flags) + None, self.flags, None) self.assertListEqual([], self.actual_after_contents) # 'no diffs' self.assertEqual(0, num_modified_files) @@ -269,7 +274,7 @@ int main() { return 0; } # we can't use the normal ProcessAndTest. iwyu_output_as_file = StringIO(iwyu_output) num_modified_files = fix_includes.ProcessIWYUOutput(iwyu_output_as_file, - None, self.flags) + None, self.flags, None) self.assertListEqual([], self.actual_after_contents) # 'no diffs' self.assertEqual(0, num_modified_files) @@ -3831,7 +3836,7 @@ The full include-list for dry_run: """ self.RegisterFileContents({'dry_run': infile}) num_modified_files = fix_includes.ProcessIWYUOutput( - StringIO(iwyu_output), ['dry_run'], self.flags) + StringIO(iwyu_output), ['dry_run'], self.flags, None) self.assertListEqual([], self.actual_after_contents) self.assertEqual(1, num_modified_files) @@ -3976,6 +3981,87 @@ namespace ns { namespace ns4 { class Baz; } } self.RegisterFileContents({'add_fwd_declare_keep_iwyu_namespace': infile}) self.ProcessAndTest(iwyu_output, expected_num_modified_files=1) + def testBasedir(self): + self.flags.basedir = "/project/build/" + iwyu_output = """\ +../src/source.cc should add these lines: + +../src/source.cc should remove these lines: +- #include <unused.h> // lines 1-1 + +The full include-list for ../src/source.cc: +#include <used.h> +--- +""" + infile = """\ +#include <unused.h> ///- +#include <used.h> + +int main() { return 0; } +""" + self.RegisterFileContents({'/project/src/source.cc': infile}) + self.ProcessAndTest(iwyu_output, expected_num_modified_files=1) + + def testBasedirWithFilesToProcess(self): + self.flags.basedir = "/project/build/" + iwyu_output = """\ +../src/changed.cc should add these lines: + +../src/changed.cc should remove these lines: +- #include <unused.h> // lines 1-1 + +The full include-list for ../src/changed.cc: +#include <used.h> +--- +""" + changed_file = """\ +#include <unused.h> ///- +#include <used.h> + +int main() { return 0; } +""" + unchanged_file = """\ +#include <unused.h> +#include <used.h> + +int main() { return 0; } +""" + + iwyu_output += iwyu_output.replace('changed.cc', 'unchanged.cc') + + self.RegisterFileContents({ + '/project/src/changed.cc': changed_file, + '/project/src/unchanged.cc': unchanged_file + }) + self.ProcessAndTest(iwyu_output, cmdline_files=['/project/src/changed.cc'], + unedited_files=['/project/src/unchanged.cc']) + + def testBasedirWithRelativeCmdlineFiles(self): + self.flags.basedir = "/project/build/" + iwyu_output = """\ +../src/changed.cc should add these lines: + +../src/changed.cc should remove these lines: +- #include <unused.h> // lines 1-1 + +The full include-list for ../src/changed.cc: +#include <used.h> +--- +""" + changed_file = """\ +#include <unused.h> ///- +#include <used.h> + +int main() { return 0; } +""" + + self.RegisterFileContents({ + # File path is normalized to absolute by ProcessIWYUOutput. + '/project/src/changed.cc': changed_file, + }) + self.ProcessAndTest(iwyu_output, cmdline_files=['changed.cc'], + cwd='/project/src') + def testMain(self): """Make sure calling main doesn't crash. Inspired by a syntax-error bug.""" # Give an empty stdin so we don't actually try to parse anything. |