summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Pfaffe <philip.pfaffe@kit.edu>2016-03-22 13:33:54 +0100
committerKim Grasman <kim.grasman@gmail.com>2018-09-29 18:35:42 +0200
commit0a54b0ef992e4adca68d812dcb42edc88f5c1613 (patch)
tree2358e3e04db40778168af987f73a9e189b1a99c5
parentdbab9201143deb07f63688d4c6e3402dbd2ca7fe (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-xfix_includes.py30
-rwxr-xr-xfix_includes_test.py96
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.