Bug 1376803 - add support of ./mach clang-format -p <file/dir> r?gps draft
authorSylvestre Ledru <sledru@mozilla.com>
Thu, 29 Jun 2017 09:35:23 -0700
changeset 605798 b23dd6076cd760341c8f0edf20f17d23c55037a1
parent 602136 ca0ea2a926124ee1648e2b9db9ab7fb7f3c81bbb
child 636598 25884b307ccfa8857ea030acacb6aafbba579533
push id67522
push userbmo:sledru@mozilla.com
push dateSun, 09 Jul 2017 15:44:17 +0000
reviewersgps
bugs1376803
milestone55.0a1
Bug 1376803 - add support of ./mach clang-format -p <file/dir> r?gps MozReview-Commit-ID: nmAqNsSWho
tools/mach_commands.py
--- a/tools/mach_commands.py
+++ b/tools/mach_commands.py
@@ -164,19 +164,23 @@ class PastebinProvider(object):
             return 1
         return 0
 
 
 @CommandProvider
 class FormatProvider(MachCommandBase):
     @Command('clang-format', category='misc',
              description='Run clang-format on current changes')
-    @CommandArgument('--show', '-s', action='store_true',
+    @CommandArgument('--show', '-s', action='store_true', default=False,
                      help='Show diff output on instead of applying changes')
-    def clang_format(self, show=False):
+    @CommandArgument('--path', '-p', nargs='+', default=None,
+                     help='Specify the path(s) to reformat')
+    def clang_format(self, show, path):
+        # Run clang-format or clang-format-diff on the local changes
+        # or files/directories
         import urllib2
 
         plat = platform.system()
         fmt = plat.lower() + "/clang-format-5.0~svn297730"
         fmt_diff = "clang-format-diff-5.0~svn297730"
 
         # We are currently using an unmodified snapshot of upstream clang-format.
         # This is a temporary work around until clang 5.0 has been released with our changes.
@@ -188,29 +192,35 @@ class FormatProvider(MachCommandBase):
                 print("Unsupported platform " + plat + "/" + arch +
                       ". Supported platforms are Windows/*, Linux/x86_64 and Darwin/x86_64")
                 return 1
 
         os.chdir(self.topsrcdir)
         self.prompt = True
 
         try:
-            if not self.locate_or_fetch(fmt):
+            clang_format = self.locate_or_fetch(fmt)
+            if not clang_format:
                 return 1
             clang_format_diff = self.locate_or_fetch(fmt_diff, python_script=True)
             if not clang_format_diff:
                 return 1
 
         except urllib2.HTTPError as e:
             print("HTTP error {0}: {1}".format(e.code, e.reason))
             return 1
 
-        return self.run_clang_format_diff(clang_format_diff, show)
+        if path is None:
+            return self.run_clang_format_diff(clang_format_diff, show)
+        else:
+            return self.run_clang_format_path(clang_format, show, path)
 
     def locate_or_fetch(self, root, python_script=False):
+        # Download the clang-format binary & python clang-format-diff if doesn't
+        # exists
         import urllib2
         import hashlib
         bin_sha = {
             "Windows": "0cbfc306df48f01bfe804e5e89cef73b3abe8f884fb7a5208f8895897f19ec45c13760787298192bd37de057d0ded091640c7d504438e06ec880f071a38db89c",  # noqa: E501
             "Linux": "e6da4f6df074bfb15caefcf7767eb5670c02bb4768ba86ae4ab6b35235b53db012900a4f9e9a950ee140158a19532a71f21b986f511826bebc16f2ef83984e57",  # noqa: E501
             "Darwin": "18000940a11e5ab0c1fe950d4360292216c8e963dd708679c4c5fb8cc845f5919cef3f58a7e092555b8ea6b8d8a809d66153ea6d1e7c226a2c4f2b0b7ad1b2f3",  # noqa: E501
             "python_script": "34b6934a48a263ea3f88d48c2981d61ae6698823cfa689b9b0c8a607c224437ca0b9fdd434d260bd790d52a98455e2c2e2c745490d327ba84b4e22b7bb55b757",  # noqa: E501
         }
@@ -242,42 +252,100 @@ class FormatProvider(MachCommandBase):
             with open(temp, "wb") as fh:
                 fh.write(data)
                 fh.close()
             os.chmod(temp, os.stat(temp).st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
             os.rename(temp, target)
         return target
 
     def run_clang_format_diff(self, clang_format_diff, show):
+        # Run clang-format on the diff
+        # Note that this will potentially miss a lot things
         from subprocess import Popen, PIPE
 
         if os.path.exists(".hg"):
             diff_process = Popen(["hg", "diff", "-U0", "-r", ".^",
                                   "--include", "glob:**.c", "--include", "glob:**.cpp",
                                   "--include", "glob:**.h",
                                   "--exclude", "listfile:.clang-format-ignore"], stdout=PIPE)
         else:
             git_process = Popen(["git", "diff", "--no-color", "-U0", "HEAD^"], stdout=PIPE)
             try:
-                diff_process = Popen(["filterdiff", "--include=*.h", "--include=*.cpp",
+                diff_process = Popen(["filterdiff", "--include=*.h",
+                                      "--include=*.cpp", "--include=*.c",
                                       "--exclude-from-file=.clang-format-ignore"],
                                      stdin=git_process.stdout, stdout=PIPE)
             except OSError as e:
                 if e.errno == errno.ENOENT:
                     print("Can't find filterdiff. Please install patchutils.")
                 else:
                     print("OSError {0}: {1}".format(e.code, e.reason))
                 return 1
 
         args = [sys.executable, clang_format_diff, "-p1"]
         if not show:
             args.append("-i")
         cf_process = Popen(args, stdin=diff_process.stdout)
         return cf_process.communicate()[0]
 
+    def generate_path_list(self, paths):
+        pathToThirdparty = os.path.join(self.topsrcdir,
+                                        "tools",
+                                        "rewriting",
+                                        "ThirdPartyPaths.txt")
+        with open(pathToThirdparty) as f:
+            # Normalize the path (no trailing /)
+            ignored_dir = tuple(d.rstrip('/') for d in f.read().splitlines())
+
+        extensions = ('.cpp', '.c', '.h')
+
+        path_list = []
+        for f in paths:
+            if f.startswith(ignored_dir):
+                print("clang-format: Ignored third party code '{0}'".format(f))
+                continue
+
+            if os.path.isdir(f):
+                # Processing a directory, generate the file list
+                for folder, subs, files in os.walk(f):
+                    subs.sort()
+                    for filename in sorted(files):
+                        f_in_dir = os.path.join(folder, filename)
+                        if f_in_dir.endswith(extensions):
+                            # Supported extension
+                            path_list.append(f_in_dir)
+            else:
+                if f.endswith(extensions):
+                    path_list.append(f)
+
+        return path_list
+
+    def run_clang_format_path(self, clang_format, show, paths):
+        # Run clang-format on files or directories directly
+        from subprocess import Popen
+
+        args = [clang_format, "-i"]
+
+        path_list = self.generate_path_list(paths)
+
+        if path_list == []:
+            return
+
+        args += path_list
+
+        # Run clang-format
+        cf_process = Popen(args)
+        if show:
+            # show the diff
+            if os.path.exists(".hg"):
+                cf_process = Popen(["hg", "diff"] + path_list)
+            else:
+                cf_process = Popen(["git", "diff"] + path_list)
+        return cf_process.communicate()[0]
+
 
 def mozregression_import():
     # Lazy loading of mozregression.
     # Note that only the mach_interface module should be used from this file.
     try:
         import mozregression.mach_interface
     except ImportError:
         return None