Add most of base/ build/ buildtools/ testing/ third_party/googletest/
Enough to make ./tools/gn/bootstrap/bootstrap.py work on Linux.
Change-Id: I94de95f1ce87dd3672d1a99c62254edee8be45bd
Reviewed-on: https://gn-review.googlesource.com/1100
Reviewed-by: Petr Hosek <phosek@google.com>
Commit-Queue: Scott Graham <scottmg@chromium.org>
diff --git a/buildtools/clang_format/OWNERS b/buildtools/clang_format/OWNERS
new file mode 100644
index 0000000..a14b04c
--- /dev/null
+++ b/buildtools/clang_format/OWNERS
@@ -0,0 +1,2 @@
+nick@chromium.org
+thakis@chromium.org
diff --git a/buildtools/clang_format/README.chromium b/buildtools/clang_format/README.chromium
new file mode 100644
index 0000000..514b77c
--- /dev/null
+++ b/buildtools/clang_format/README.chromium
@@ -0,0 +1,15 @@
+Name: clang-format
+Short Name: clang-format
+URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-format/
+Version: 5.0.0
+Date: 14 Jun 2017
+Revision: See DEPS
+License: University of Illinois/NCSA Open Source License
+License File: NOT_SHIPPED
+Security Critical: No
+
+Description:
+A tool for formatting C++ code to style.
+
+Local Modifications:
+None
diff --git a/buildtools/clang_format/README.txt b/buildtools/clang_format/README.txt
new file mode 100644
index 0000000..29b446c
--- /dev/null
+++ b/buildtools/clang_format/README.txt
@@ -0,0 +1,33 @@
+This folder contains clang-format scripts. The binaries will be automatically
+downloaded from Google Storage by gclient runhooks for the current platform.
+
+For a walkthrough on how to maintain these binaries:
+ https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang_format_binaries.md
+
+To upload a file:
+ python ~/depot_tools/upload_to_google_storage.py -b chromium-clang-format <FILENAME>
+
+On Linux and Mac, check that clang-format has its +x bit set before you run this
+upload command. Don't upload Linux and Mac binaries from Windows, since
+upload_to_google_storage.py will not set the +x bit on google storage when it's
+run from Windows.
+
+To download a file given a .sha1 file:
+ python ~/depot_tools/download_from_google_storage.py -b chromium-clang-format -s <FILENAME>.sha1
+
+List the contents of GN's Google Storage bucket:
+ python ~/depot_tools/third_party/gsutil/gsutil ls gs://chromium-clang-format/
+
+To initialize gsutil's credentials:
+ python ~/depot_tools/third_party/gsutil/gsutil config
+
+ That will give a URL which you should log into with your web browser. The
+ username should be the one that is on the ACL for the "chromium-clang-format"
+ bucket (probably your @google.com address). Contact the build team for help
+ getting access if necessary.
+
+ Copy the code back to the command line util. Ignore the project ID (it's OK
+ to just leave blank when prompted).
+
+gsutil documentation:
+ https://developers.google.com/storage/docs/gsutil
diff --git a/buildtools/clang_format/script/CMakeLists.txt b/buildtools/clang_format/script/CMakeLists.txt
new file mode 100644
index 0000000..a13633e
--- /dev/null
+++ b/buildtools/clang_format/script/CMakeLists.txt
@@ -0,0 +1,39 @@
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_tool(clang-format
+ ClangFormat.cpp
+ )
+
+set(CLANG_FORMAT_LIB_DEPS
+ clangBasic
+ clangFormat
+ clangRewrite
+ clangToolingCore
+ )
+
+target_link_libraries(clang-format
+ ${CLANG_FORMAT_LIB_DEPS}
+ )
+
+if( LLVM_USE_SANITIZE_COVERAGE )
+ add_subdirectory(fuzzer)
+endif()
+
+install(PROGRAMS clang-format-bbedit.applescript
+ DESTINATION share/clang
+ COMPONENT clang-format)
+install(PROGRAMS clang-format-diff.py
+ DESTINATION share/clang
+ COMPONENT clang-format)
+install(PROGRAMS clang-format-sublime.py
+ DESTINATION share/clang
+ COMPONENT clang-format)
+install(PROGRAMS clang-format.el
+ DESTINATION share/clang
+ COMPONENT clang-format)
+install(PROGRAMS clang-format.py
+ DESTINATION share/clang
+ COMPONENT clang-format)
+install(PROGRAMS git-clang-format
+ DESTINATION bin
+ COMPONENT clang-format)
diff --git a/buildtools/clang_format/script/ClangFormat.cpp b/buildtools/clang_format/script/ClangFormat.cpp
new file mode 100644
index 0000000..14bff19
--- /dev/null
+++ b/buildtools/clang_format/script/ClangFormat.cpp
@@ -0,0 +1,388 @@
+//===-- clang-format/ClangFormat.cpp - Clang format tool ------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file implements a clang-format tool that automatically formats
+/// (fragments of) C++ code.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/Version.h"
+#include "clang/Format/Format.h"
+#include "clang/Rewrite/Core/Rewriter.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Signals.h"
+
+using namespace llvm;
+using clang::tooling::Replacements;
+
+static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden);
+
+// Mark all our options with this category, everything else (except for -version
+// and -help) will be hidden.
+static cl::OptionCategory ClangFormatCategory("Clang-format options");
+
+static cl::list<unsigned>
+ Offsets("offset",
+ cl::desc("Format a range starting at this byte offset.\n"
+ "Multiple ranges can be formatted by specifying\n"
+ "several -offset and -length pairs.\n"
+ "Can only be used with one input file."),
+ cl::cat(ClangFormatCategory));
+static cl::list<unsigned>
+ Lengths("length",
+ cl::desc("Format a range of this length (in bytes).\n"
+ "Multiple ranges can be formatted by specifying\n"
+ "several -offset and -length pairs.\n"
+ "When only a single -offset is specified without\n"
+ "-length, clang-format will format up to the end\n"
+ "of the file.\n"
+ "Can only be used with one input file."),
+ cl::cat(ClangFormatCategory));
+static cl::list<std::string>
+LineRanges("lines", cl::desc("<start line>:<end line> - format a range of\n"
+ "lines (both 1-based).\n"
+ "Multiple ranges can be formatted by specifying\n"
+ "several -lines arguments.\n"
+ "Can't be used with -offset and -length.\n"
+ "Can only be used with one input file."),
+ cl::cat(ClangFormatCategory));
+static cl::opt<std::string>
+ Style("style",
+ cl::desc(clang::format::StyleOptionHelpDescription),
+ cl::init("file"), cl::cat(ClangFormatCategory));
+static cl::opt<std::string>
+FallbackStyle("fallback-style",
+ cl::desc("The name of the predefined style used as a\n"
+ "fallback in case clang-format is invoked with\n"
+ "-style=file, but can not find the .clang-format\n"
+ "file to use.\n"
+ "Use -fallback-style=none to skip formatting."),
+ cl::init("LLVM"), cl::cat(ClangFormatCategory));
+
+static cl::opt<std::string>
+AssumeFileName("assume-filename",
+ cl::desc("When reading from stdin, clang-format assumes this\n"
+ "filename to look for a style config file (with\n"
+ "-style=file) and to determine the language."),
+ cl::init("<stdin>"), cl::cat(ClangFormatCategory));
+
+static cl::opt<bool> Inplace("i",
+ cl::desc("Inplace edit <file>s, if specified."),
+ cl::cat(ClangFormatCategory));
+
+static cl::opt<bool> OutputXML("output-replacements-xml",
+ cl::desc("Output replacements as XML."),
+ cl::cat(ClangFormatCategory));
+static cl::opt<bool>
+ DumpConfig("dump-config",
+ cl::desc("Dump configuration options to stdout and exit.\n"
+ "Can be used with -style option."),
+ cl::cat(ClangFormatCategory));
+static cl::opt<unsigned>
+ Cursor("cursor",
+ cl::desc("The position of the cursor when invoking\n"
+ "clang-format from an editor integration"),
+ cl::init(0), cl::cat(ClangFormatCategory));
+
+static cl::opt<bool> SortIncludes(
+ "sort-includes",
+ cl::desc("If set, overrides the include sorting behavior determined by the "
+ "SortIncludes style flag"),
+ cl::cat(ClangFormatCategory));
+
+static cl::list<std::string> FileNames(cl::Positional, cl::desc("[<file> ...]"),
+ cl::cat(ClangFormatCategory));
+
+namespace clang {
+namespace format {
+
+static FileID createInMemoryFile(StringRef FileName, MemoryBuffer *Source,
+ SourceManager &Sources, FileManager &Files,
+ vfs::InMemoryFileSystem *MemFS) {
+ MemFS->addFileNoOwn(FileName, 0, Source);
+ return Sources.createFileID(Files.getFile(FileName), SourceLocation(),
+ SrcMgr::C_User);
+}
+
+// Parses <start line>:<end line> input to a pair of line numbers.
+// Returns true on error.
+static bool parseLineRange(StringRef Input, unsigned &FromLine,
+ unsigned &ToLine) {
+ std::pair<StringRef, StringRef> LineRange = Input.split(':');
+ return LineRange.first.getAsInteger(0, FromLine) ||
+ LineRange.second.getAsInteger(0, ToLine);
+}
+
+static bool fillRanges(MemoryBuffer *Code,
+ std::vector<tooling::Range> &Ranges) {
+ IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
+ new vfs::InMemoryFileSystem);
+ FileManager Files(FileSystemOptions(), InMemoryFileSystem);
+ DiagnosticsEngine Diagnostics(
+ IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
+ new DiagnosticOptions);
+ SourceManager Sources(Diagnostics, Files);
+ FileID ID = createInMemoryFile("<irrelevant>", Code, Sources, Files,
+ InMemoryFileSystem.get());
+ if (!LineRanges.empty()) {
+ if (!Offsets.empty() || !Lengths.empty()) {
+ errs() << "error: cannot use -lines with -offset/-length\n";
+ return true;
+ }
+
+ for (unsigned i = 0, e = LineRanges.size(); i < e; ++i) {
+ unsigned FromLine, ToLine;
+ if (parseLineRange(LineRanges[i], FromLine, ToLine)) {
+ errs() << "error: invalid <start line>:<end line> pair\n";
+ return true;
+ }
+ if (FromLine > ToLine) {
+ errs() << "error: start line should be less than end line\n";
+ return true;
+ }
+ SourceLocation Start = Sources.translateLineCol(ID, FromLine, 1);
+ SourceLocation End = Sources.translateLineCol(ID, ToLine, UINT_MAX);
+ if (Start.isInvalid() || End.isInvalid())
+ return true;
+ unsigned Offset = Sources.getFileOffset(Start);
+ unsigned Length = Sources.getFileOffset(End) - Offset;
+ Ranges.push_back(tooling::Range(Offset, Length));
+ }
+ return false;
+ }
+
+ if (Offsets.empty())
+ Offsets.push_back(0);
+ if (Offsets.size() != Lengths.size() &&
+ !(Offsets.size() == 1 && Lengths.empty())) {
+ errs() << "error: number of -offset and -length arguments must match.\n";
+ return true;
+ }
+ for (unsigned i = 0, e = Offsets.size(); i != e; ++i) {
+ if (Offsets[i] >= Code->getBufferSize()) {
+ errs() << "error: offset " << Offsets[i] << " is outside the file\n";
+ return true;
+ }
+ SourceLocation Start =
+ Sources.getLocForStartOfFile(ID).getLocWithOffset(Offsets[i]);
+ SourceLocation End;
+ if (i < Lengths.size()) {
+ if (Offsets[i] + Lengths[i] > Code->getBufferSize()) {
+ errs() << "error: invalid length " << Lengths[i]
+ << ", offset + length (" << Offsets[i] + Lengths[i]
+ << ") is outside the file.\n";
+ return true;
+ }
+ End = Start.getLocWithOffset(Lengths[i]);
+ } else {
+ End = Sources.getLocForEndOfFile(ID);
+ }
+ unsigned Offset = Sources.getFileOffset(Start);
+ unsigned Length = Sources.getFileOffset(End) - Offset;
+ Ranges.push_back(tooling::Range(Offset, Length));
+ }
+ return false;
+}
+
+static void outputReplacementXML(StringRef Text) {
+ // FIXME: When we sort includes, we need to make sure the stream is correct
+ // utf-8.
+ size_t From = 0;
+ size_t Index;
+ while ((Index = Text.find_first_of("\n\r<&", From)) != StringRef::npos) {
+ outs() << Text.substr(From, Index - From);
+ switch (Text[Index]) {
+ case '\n':
+ outs() << " ";
+ break;
+ case '\r':
+ outs() << " ";
+ break;
+ case '<':
+ outs() << "<";
+ break;
+ case '&':
+ outs() << "&";
+ break;
+ default:
+ llvm_unreachable("Unexpected character encountered!");
+ }
+ From = Index + 1;
+ }
+ outs() << Text.substr(From);
+}
+
+static void outputReplacementsXML(const Replacements &Replaces) {
+ for (const auto &R : Replaces) {
+ outs() << "<replacement "
+ << "offset='" << R.getOffset() << "' "
+ << "length='" << R.getLength() << "'>";
+ outputReplacementXML(R.getReplacementText());
+ outs() << "</replacement>\n";
+ }
+}
+
+// Returns true on error.
+static bool format(StringRef FileName) {
+ if (!OutputXML && Inplace && FileName == "-") {
+ errs() << "error: cannot use -i when reading from stdin.\n";
+ return false;
+ }
+ // On Windows, overwriting a file with an open file mapping doesn't work,
+ // so read the whole file into memory when formatting in-place.
+ ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =
+ !OutputXML && Inplace ? MemoryBuffer::getFileAsStream(FileName) :
+ MemoryBuffer::getFileOrSTDIN(FileName);
+ if (std::error_code EC = CodeOrErr.getError()) {
+ errs() << EC.message() << "\n";
+ return true;
+ }
+ std::unique_ptr<llvm::MemoryBuffer> Code = std::move(CodeOrErr.get());
+ if (Code->getBufferSize() == 0)
+ return false; // Empty files are formatted correctly.
+ std::vector<tooling::Range> Ranges;
+ if (fillRanges(Code.get(), Ranges))
+ return true;
+ StringRef AssumedFileName = (FileName == "-") ? AssumeFileName : FileName;
+
+ llvm::Expected<FormatStyle> FormatStyle =
+ getStyle(Style, AssumedFileName, FallbackStyle, Code->getBuffer());
+ if (!FormatStyle) {
+ llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
+ return true;
+ }
+
+ if (SortIncludes.getNumOccurrences() != 0)
+ FormatStyle->SortIncludes = SortIncludes;
+ unsigned CursorPosition = Cursor;
+ Replacements Replaces = sortIncludes(*FormatStyle, Code->getBuffer(), Ranges,
+ AssumedFileName, &CursorPosition);
+ auto ChangedCode = tooling::applyAllReplacements(Code->getBuffer(), Replaces);
+ if (!ChangedCode) {
+ llvm::errs() << llvm::toString(ChangedCode.takeError()) << "\n";
+ return true;
+ }
+ // Get new affected ranges after sorting `#includes`.
+ Ranges = tooling::calculateRangesAfterReplacements(Replaces, Ranges);
+ FormattingAttemptStatus Status;
+ Replacements FormatChanges = reformat(*FormatStyle, *ChangedCode, Ranges,
+ AssumedFileName, &Status);
+ Replaces = Replaces.merge(FormatChanges);
+ if (OutputXML) {
+ outs() << "<?xml version='1.0'?>\n<replacements "
+ "xml:space='preserve' incomplete_format='"
+ << (Status.FormatComplete ? "false" : "true") << "'";
+ if (!Status.FormatComplete)
+ outs() << " line=" << Status.Line;
+ outs() << ">\n";
+ if (Cursor.getNumOccurrences() != 0)
+ outs() << "<cursor>"
+ << FormatChanges.getShiftedCodePosition(CursorPosition)
+ << "</cursor>\n";
+
+ outputReplacementsXML(Replaces);
+ outs() << "</replacements>\n";
+ } else {
+ IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
+ new vfs::InMemoryFileSystem);
+ FileManager Files(FileSystemOptions(), InMemoryFileSystem);
+ DiagnosticsEngine Diagnostics(
+ IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
+ new DiagnosticOptions);
+ SourceManager Sources(Diagnostics, Files);
+ FileID ID = createInMemoryFile(AssumedFileName, Code.get(), Sources, Files,
+ InMemoryFileSystem.get());
+ Rewriter Rewrite(Sources, LangOptions());
+ tooling::applyAllReplacements(Replaces, Rewrite);
+ if (Inplace) {
+ if (Rewrite.overwriteChangedFiles())
+ return true;
+ } else {
+ if (Cursor.getNumOccurrences() != 0) {
+ outs() << "{ \"Cursor\": "
+ << FormatChanges.getShiftedCodePosition(CursorPosition)
+ << ", \"IncompleteFormat\": "
+ << (Status.FormatComplete ? "false" : "true");
+ if (!Status.FormatComplete)
+ outs() << ", \"Line\": " << Status.Line;
+ outs() << " }\n";
+ }
+ Rewrite.getEditBuffer(ID).write(outs());
+ }
+ }
+ return false;
+}
+
+} // namespace format
+} // namespace clang
+
+static void PrintVersion() {
+ raw_ostream &OS = outs();
+ OS << clang::getClangToolFullVersion("clang-format") << '\n';
+}
+
+int main(int argc, const char **argv) {
+ llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
+
+ cl::HideUnrelatedOptions(ClangFormatCategory);
+
+ cl::SetVersionPrinter(PrintVersion);
+ cl::ParseCommandLineOptions(
+ argc, argv,
+ "A tool to format C/C++/Java/JavaScript/Objective-C/Protobuf code.\n\n"
+ "If no arguments are specified, it formats the code from standard input\n"
+ "and writes the result to the standard output.\n"
+ "If <file>s are given, it reformats the files. If -i is specified\n"
+ "together with <file>s, the files are edited in-place. Otherwise, the\n"
+ "result is written to the standard output.\n");
+
+ if (Help)
+ cl::PrintHelpMessage();
+
+ if (DumpConfig) {
+ llvm::Expected<clang::format::FormatStyle> FormatStyle =
+ clang::format::getStyle(
+ Style, FileNames.empty() ? AssumeFileName : FileNames[0],
+ FallbackStyle);
+ if (!FormatStyle) {
+ llvm::errs() << llvm::toString(FormatStyle.takeError()) << "\n";
+ return 1;
+ }
+ std::string Config = clang::format::configurationAsText(*FormatStyle);
+ outs() << Config << "\n";
+ return 0;
+ }
+
+ bool Error = false;
+ switch (FileNames.size()) {
+ case 0:
+ Error = clang::format::format("-");
+ break;
+ case 1:
+ Error = clang::format::format(FileNames[0]);
+ break;
+ default:
+ if (!Offsets.empty() || !Lengths.empty() || !LineRanges.empty()) {
+ errs() << "error: -offset, -length and -lines can only be used for "
+ "single file.\n";
+ return 1;
+ }
+ for (unsigned i = 0; i < FileNames.size(); ++i)
+ Error |= clang::format::format(FileNames[i]);
+ break;
+ }
+ return Error ? 1 : 0;
+}
+
diff --git a/buildtools/clang_format/script/clang-format-bbedit.applescript b/buildtools/clang_format/script/clang-format-bbedit.applescript
new file mode 100644
index 0000000..fa88fe9
--- /dev/null
+++ b/buildtools/clang_format/script/clang-format-bbedit.applescript
@@ -0,0 +1,27 @@
+-- In this file, change "/path/to/" to the path where you installed clang-format
+-- and save it to ~/Library/Application Support/BBEdit/Scripts. You can then
+-- select the script from the Script menu and clang-format will format the
+-- selection. Note that you can rename the menu item by renaming the script, and
+-- can assign the menu item a keyboard shortcut in the BBEdit preferences, under
+-- Menus & Shortcuts.
+on urlToPOSIXPath(theURL)
+ return do shell script "python -c \"import urllib, urlparse, sys; print urllib.unquote(urlparse.urlparse(sys.argv[1])[2])\" " & quoted form of theURL
+end urlToPOSIXPath
+
+tell application "BBEdit"
+ set selectionOffset to characterOffset of selection
+ set selectionLength to length of selection
+ set fileURL to URL of text document 1
+end tell
+
+set filePath to urlToPOSIXPath(fileURL)
+set newContents to do shell script "/path/to/clang-format -offset=" & selectionOffset & " -length=" & selectionLength & " " & quoted form of filePath
+
+tell application "BBEdit"
+ -- "set contents of text document 1 to newContents" scrolls to the bottom while
+ -- replacing a selection flashes a bit but doesn't affect the scroll position.
+ set currentLength to length of contents of text document 1
+ select characters 1 thru currentLength of text document 1
+ set text of selection to newContents
+ select characters selectionOffset thru (selectionOffset + selectionLength - 1) of text document 1
+end tell
diff --git a/buildtools/clang_format/script/clang-format-diff.py b/buildtools/clang_format/script/clang-format-diff.py
new file mode 100755
index 0000000..ffa30e7
--- /dev/null
+++ b/buildtools/clang_format/script/clang-format-diff.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+#
+#===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+r"""
+ClangFormat Diff Reformatter
+============================
+
+This script reads input from a unified diff and reformats all the changed
+lines. This is useful to reformat all the lines touched by a specific patch.
+Example usage for git/svn users:
+
+ git diff -U0 --no-color HEAD^ | clang-format-diff.py -p1 -i
+ svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
+
+"""
+
+import argparse
+import difflib
+import re
+import string
+import subprocess
+import StringIO
+import sys
+
+
+def main():
+ parser = argparse.ArgumentParser(description=
+ 'Reformat changed lines in diff. Without -i '
+ 'option just output the diff that would be '
+ 'introduced.')
+ parser.add_argument('-i', action='store_true', default=False,
+ help='apply edits to files instead of displaying a diff')
+ parser.add_argument('-p', metavar='NUM', default=0,
+ help='strip the smallest prefix containing P slashes')
+ parser.add_argument('-regex', metavar='PATTERN', default=None,
+ help='custom pattern selecting file paths to reformat '
+ '(case sensitive, overrides -iregex)')
+ parser.add_argument('-iregex', metavar='PATTERN', default=
+ r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc|js|ts|proto'
+ r'|protodevel|java)',
+ help='custom pattern selecting file paths to reformat '
+ '(case insensitive, overridden by -regex)')
+ parser.add_argument('-sort-includes', action='store_true', default=False,
+ help='let clang-format sort include blocks')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='be more verbose, ineffective without -i')
+ parser.add_argument('-style',
+ help='formatting style to apply (LLVM, Google, Chromium, '
+ 'Mozilla, WebKit)')
+ parser.add_argument('-binary', default='clang-format',
+ help='location of binary to use for clang-format')
+ args = parser.parse_args()
+
+ # Extract changed lines for each file.
+ filename = None
+ lines_by_file = {}
+ for line in sys.stdin:
+ match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
+ if match:
+ filename = match.group(2)
+ if filename == None:
+ continue
+
+ if args.regex is not None:
+ if not re.match('^%s$' % args.regex, filename):
+ continue
+ else:
+ if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
+ continue
+
+ match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
+ if match:
+ start_line = int(match.group(1))
+ line_count = 1
+ if match.group(3):
+ line_count = int(match.group(3))
+ if line_count == 0:
+ continue
+ end_line = start_line + line_count - 1;
+ lines_by_file.setdefault(filename, []).extend(
+ ['-lines', str(start_line) + ':' + str(end_line)])
+
+ # Reformat files containing changes in place.
+ for filename, lines in lines_by_file.iteritems():
+ if args.i and args.verbose:
+ print 'Formatting', filename
+ command = [args.binary, filename]
+ if args.i:
+ command.append('-i')
+ if args.sort_includes:
+ command.append('-sort-includes')
+ command.extend(lines)
+ if args.style:
+ command.extend(['-style', args.style])
+ p = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=None, stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ sys.exit(p.returncode);
+
+ if not args.i:
+ with open(filename) as f:
+ code = f.readlines()
+ formatted_code = StringIO.StringIO(stdout).readlines()
+ diff = difflib.unified_diff(code, formatted_code,
+ filename, filename,
+ '(before formatting)', '(after formatting)')
+ diff_string = string.join(diff, '')
+ if len(diff_string) > 0:
+ sys.stdout.write(diff_string)
+
+if __name__ == '__main__':
+ main()
diff --git a/buildtools/clang_format/script/clang-format-sublime.py b/buildtools/clang_format/script/clang-format-sublime.py
new file mode 100644
index 0000000..16ff56e
--- /dev/null
+++ b/buildtools/clang_format/script/clang-format-sublime.py
@@ -0,0 +1,58 @@
+# This file is a minimal clang-format sublime-integration. To install:
+# - Change 'binary' if clang-format is not on the path (see below).
+# - Put this file into your sublime Packages directory, e.g. on Linux:
+# ~/.config/sublime-text-2/Packages/User/clang-format-sublime.py
+# - Add a key binding:
+# { "keys": ["ctrl+shift+c"], "command": "clang_format" },
+#
+# With this integration you can press the bound key and clang-format will
+# format the current lines and selections for all cursor positions. The lines
+# or regions are extended to the next bigger syntactic entities.
+#
+# It operates on the current, potentially unsaved buffer and does not create
+# or save any files. To revert a formatting, just undo.
+
+from __future__ import print_function
+import sublime
+import sublime_plugin
+import subprocess
+
+# Change this to the full path if clang-format is not on the path.
+binary = 'clang-format'
+
+# Change this to format according to other formatting styles. See the output of
+# 'clang-format --help' for a list of supported styles. The default looks for
+# a '.clang-format' or '_clang-format' file to indicate the style that should be
+# used.
+style = 'file'
+
+class ClangFormatCommand(sublime_plugin.TextCommand):
+ def run(self, edit):
+ encoding = self.view.encoding()
+ if encoding == 'Undefined':
+ encoding = 'utf-8'
+ regions = []
+ command = [binary, '-style', style]
+ for region in self.view.sel():
+ regions.append(region)
+ region_offset = min(region.a, region.b)
+ region_length = abs(region.b - region.a)
+ command.extend(['-offset', str(region_offset),
+ '-length', str(region_length),
+ '-assume-filename', str(self.view.file_name())])
+ old_viewport_position = self.view.viewport_position()
+ buf = self.view.substr(sublime.Region(0, self.view.size()))
+ p = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, stdin=subprocess.PIPE)
+ output, error = p.communicate(buf.encode(encoding))
+ if error:
+ print(error)
+ self.view.replace(
+ edit, sublime.Region(0, self.view.size()),
+ output.decode(encoding))
+ self.view.sel().clear()
+ for region in regions:
+ self.view.sel().add(region)
+ # FIXME: Without the 10ms delay, the viewport sometimes jumps.
+ sublime.set_timeout(lambda: self.view.set_viewport_position(
+ old_viewport_position, False), 10)
diff --git a/buildtools/clang_format/script/clang-format-test.el b/buildtools/clang_format/script/clang-format-test.el
new file mode 100644
index 0000000..0e1f4dd
--- /dev/null
+++ b/buildtools/clang_format/script/clang-format-test.el
@@ -0,0 +1,126 @@
+;;; clang-format-test.el --- unit tests for clang-format.el -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2017 Google Inc.
+
+;; Author: Philipp Stephani <phst@google.com>
+
+;; This file is distributed under the University of Illinois Open Source
+;; License. See LICENSE.TXT for details.
+
+;;; Commentary:
+
+;; Unit tests for clang-format.el.
+
+;;; Code:
+
+(require 'clang-format)
+
+(require 'cl-lib)
+(require 'ert)
+(require 'pcase)
+
+(ert-deftest clang-format-buffer--buffer-encoding ()
+ "Tests that encoded text is handled properly."
+ (cl-letf* ((call-process-args nil)
+ ((symbol-function 'call-process-region)
+ (lambda (&rest args)
+ (push args call-process-args)
+ (pcase-exhaustive args
+ (`(,_start ,_end ,_program ,_delete (,stdout ,_stderr)
+ ,_display . ,_args)
+ (with-current-buffer stdout
+ (insert "<?xml version='1.0'?>
+<replacements xml:space='preserve' incomplete_format='false'>
+<replacement offset='4' length='0'> </replacement>
+<replacement offset='10' length='0'> </replacement>
+</replacements>
+"))
+ 0)))))
+ (with-temp-buffer
+ (let ((buffer-file-name "foo.cpp")
+ (buffer-file-coding-system 'utf-8-with-signature-dos)
+ (default-process-coding-system 'latin-1-unix))
+ (insert "ä =ö;\nü= ß;\n")
+ (goto-char (point-min))
+ (end-of-line)
+ (clang-format-buffer))
+ (should (equal (buffer-string) "ä = ö;\nü = ß;\n"))
+ (should (eolp))
+ (should (equal (buffer-substring (point) (point-max))
+ "\nü = ß;\n")))
+ (should-not (cdr call-process-args))
+ (pcase-exhaustive call-process-args
+ (`((,start ,end ,_program ,delete (,_stdout ,_stderr) ,display . ,args))
+ (should-not start)
+ (should-not end)
+ (should-not delete)
+ (should-not display)
+ (should (equal args
+ '("-output-replacements-xml" "-assume-filename" "foo.cpp"
+ "-style" "file"
+ ;; Beginning of buffer, no byte-order mark.
+ "-offset" "0"
+ ;; We have two lines with 2×2 bytes for the umlauts,
+ ;; 1 byte for the line ending, and 3 bytes for the
+ ;; other ASCII characters each.
+ "-length" "16"
+ ;; Length of a single line (without line ending).
+ "-cursor" "7")))))))
+
+(ert-deftest clang-format-buffer--process-encoding ()
+ "Tests that text is sent to the clang-format process in the
+right encoding."
+ (cl-letf* ((hexdump (executable-find "hexdump"))
+ (original-call-process-region
+ (symbol-function 'call-process-region))
+ (call-process-inputs nil)
+ ;; We redirect the input to hexdump so that we have guaranteed
+ ;; ASCII output.
+ ((symbol-function 'call-process-region)
+ (lambda (&rest args)
+ (pcase-exhaustive args
+ (`(,start ,end ,_program ,_delete (,stdout ,_stderr)
+ ,_display . ,_args)
+ (with-current-buffer stdout
+ (insert "<?xml version='1.0'?>
+<replacements xml:space='preserve' incomplete_format='false'>
+</replacements>
+"))
+ (let ((stdin (current-buffer)))
+ (with-temp-buffer
+ (prog1
+ (let ((stdout (current-buffer)))
+ (with-current-buffer stdin
+ (funcall original-call-process-region
+ start end hexdump nil stdout nil
+ "-v" "-e" "/1 \"%02x \"")))
+ (push (buffer-string) call-process-inputs)))))))))
+ (skip-unless hexdump)
+ (with-temp-buffer
+ (let ((buffer-file-name "foo.cpp")
+ (buffer-file-coding-system 'utf-8-with-signature-dos)
+ (default-process-coding-system 'latin-1-unix))
+ (insert "ä\n")
+ (clang-format-buffer))
+ (should (equal (buffer-string) "ä\n"))
+ (should (eobp)))
+ (should (equal call-process-inputs '("c3 a4 0a ")))))
+
+(ert-deftest clang-format-buffer--end-to-end ()
+ "End-to-end test for ‘clang-format-buffer’.
+Actually calls the clang-format binary."
+ (skip-unless (file-executable-p clang-format-executable))
+ (with-temp-buffer
+ (let ((buffer-file-name "foo.cpp")
+ (buffer-file-coding-system 'utf-8-with-signature-dos)
+ (default-process-coding-system 'latin-1-unix))
+ (insert "ä =ö;\nü= ß;\n")
+ (goto-char (point-min))
+ (end-of-line)
+ (clang-format-buffer))
+ (should (equal (buffer-string) "ä = ö;\nü = ß;\n"))
+ (should (eolp))
+ (should (equal (buffer-substring (point) (point-max))
+ "\nü = ß;\n"))))
+
+;;; clang-format-test.el ends here
diff --git a/buildtools/clang_format/script/clang-format.el b/buildtools/clang_format/script/clang-format.el
new file mode 100644
index 0000000..aa9c3ff
--- /dev/null
+++ b/buildtools/clang_format/script/clang-format.el
@@ -0,0 +1,193 @@
+;;; clang-format.el --- Format code using clang-format -*- lexical-binding: t; -*-
+
+;; Keywords: tools, c
+;; Package-Requires: ((cl-lib "0.3"))
+
+;;; Commentary:
+
+;; This package allows to filter code through clang-format to fix its formatting.
+;; clang-format is a tool that formats C/C++/Obj-C code according to a set of
+;; style options, see <http://clang.llvm.org/docs/ClangFormatStyleOptions.html>.
+;; Note that clang-format 3.4 or newer is required.
+
+;; clang-format.el is available via MELPA and can be installed via
+;;
+;; M-x package-install clang-format
+;;
+;; when ("melpa" . "http://melpa.org/packages/") is included in
+;; `package-archives'. Alternatively, ensure the directory of this
+;; file is in your `load-path' and add
+;;
+;; (require 'clang-format)
+;;
+;; to your .emacs configuration.
+
+;; You may also want to bind `clang-format-region' to a key:
+;;
+;; (global-set-key [C-M-tab] 'clang-format-region)
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'xml)
+
+(defgroup clang-format nil
+ "Format code using clang-format."
+ :group 'tools)
+
+(defcustom clang-format-executable
+ (or (executable-find "clang-format")
+ "clang-format")
+ "Location of the clang-format executable.
+
+A string containing the name or the full path of the executable."
+ :group 'clang-format
+ :type '(file :must-match t)
+ :risky t)
+
+(defcustom clang-format-style "file"
+ "Style argument to pass to clang-format.
+
+By default clang-format will load the style configuration from
+a file named .clang-format located in one of the parent directories
+of the buffer."
+ :group 'clang-format
+ :type 'string
+ :safe #'stringp)
+(make-variable-buffer-local 'clang-format-style)
+
+(defun clang-format--extract (xml-node)
+ "Extract replacements and cursor information from XML-NODE."
+ (unless (and (listp xml-node) (eq (xml-node-name xml-node) 'replacements))
+ (error "Expected <replacements> node"))
+ (let ((nodes (xml-node-children xml-node))
+ (incomplete-format (xml-get-attribute xml-node 'incomplete_format))
+ replacements
+ cursor)
+ (dolist (node nodes)
+ (when (listp node)
+ (let* ((children (xml-node-children node))
+ (text (car children)))
+ (cl-case (xml-node-name node)
+ ('replacement
+ (let* ((offset (xml-get-attribute-or-nil node 'offset))
+ (length (xml-get-attribute-or-nil node 'length)))
+ (when (or (null offset) (null length))
+ (error "<replacement> node does not have offset and length attributes"))
+ (when (cdr children)
+ (error "More than one child node in <replacement> node"))
+
+ (setq offset (string-to-number offset))
+ (setq length (string-to-number length))
+ (push (list offset length text) replacements)))
+ ('cursor
+ (setq cursor (string-to-number text)))))))
+
+ ;; Sort by decreasing offset, length.
+ (setq replacements (sort (delq nil replacements)
+ (lambda (a b)
+ (or (> (car a) (car b))
+ (and (= (car a) (car b))
+ (> (cadr a) (cadr b)))))))
+
+ (list replacements cursor (string= incomplete-format "true"))))
+
+(defun clang-format--replace (offset length &optional text)
+ "Replace the region defined by OFFSET and LENGTH with TEXT.
+OFFSET and LENGTH are measured in bytes, not characters. OFFSET
+is a zero-based file offset, assuming ‘utf-8-unix’ coding."
+ (let ((start (clang-format--filepos-to-bufferpos offset 'exact 'utf-8-unix))
+ (end (clang-format--filepos-to-bufferpos (+ offset length) 'exact
+ 'utf-8-unix)))
+ (goto-char start)
+ (delete-region start end)
+ (when text
+ (insert text))))
+
+;; ‘bufferpos-to-filepos’ and ‘filepos-to-bufferpos’ are new in Emacs 25.1.
+;; Provide fallbacks for older versions.
+(defalias 'clang-format--bufferpos-to-filepos
+ (if (fboundp 'bufferpos-to-filepos)
+ 'bufferpos-to-filepos
+ (lambda (position &optional _quality _coding-system)
+ (1- (position-bytes position)))))
+
+(defalias 'clang-format--filepos-to-bufferpos
+ (if (fboundp 'filepos-to-bufferpos)
+ 'filepos-to-bufferpos
+ (lambda (byte &optional _quality _coding-system)
+ (byte-to-position (1+ byte)))))
+
+;;;###autoload
+(defun clang-format-region (start end &optional style)
+ "Use clang-format to format the code between START and END according to STYLE.
+If called interactively uses the region or the current statement if there
+is no active region. If no style is given uses `clang-format-style'."
+ (interactive
+ (if (use-region-p)
+ (list (region-beginning) (region-end))
+ (list (point) (point))))
+
+ (unless style
+ (setq style clang-format-style))
+
+ (let ((file-start (clang-format--bufferpos-to-filepos start 'approximate
+ 'utf-8-unix))
+ (file-end (clang-format--bufferpos-to-filepos end 'approximate
+ 'utf-8-unix))
+ (cursor (clang-format--bufferpos-to-filepos (point) 'exact 'utf-8-unix))
+ (temp-buffer (generate-new-buffer " *clang-format-temp*"))
+ (temp-file (make-temp-file "clang-format"))
+ ;; Output is XML, which is always UTF-8. Input encoding should match
+ ;; the encoding used to convert between buffer and file positions,
+ ;; otherwise the offsets calculated above are off. For simplicity, we
+ ;; always use ‘utf-8-unix’ and ignore the buffer coding system.
+ (default-process-coding-system '(utf-8-unix . utf-8-unix)))
+ (unwind-protect
+ (let ((status (call-process-region
+ nil nil clang-format-executable
+ nil `(,temp-buffer ,temp-file) nil
+
+ "-output-replacements-xml"
+ "-assume-filename" (or (buffer-file-name) "")
+ "-style" style
+ "-offset" (number-to-string file-start)
+ "-length" (number-to-string (- file-end file-start))
+ "-cursor" (number-to-string cursor)))
+ (stderr (with-temp-buffer
+ (unless (zerop (cadr (insert-file-contents temp-file)))
+ (insert ": "))
+ (buffer-substring-no-properties
+ (point-min) (line-end-position)))))
+ (cond
+ ((stringp status)
+ (error "(clang-format killed by signal %s%s)" status stderr))
+ ((not (zerop status))
+ (error "(clang-format failed with code %d%s)" status stderr)))
+
+ (cl-destructuring-bind (replacements cursor incomplete-format)
+ (with-current-buffer temp-buffer
+ (clang-format--extract (car (xml-parse-region))))
+ (save-excursion
+ (dolist (rpl replacements)
+ (apply #'clang-format--replace rpl)))
+ (when cursor
+ (goto-char (clang-format--filepos-to-bufferpos cursor 'exact
+ 'utf-8-unix)))
+ (if incomplete-format
+ (message "(clang-format: incomplete (syntax errors)%s)" stderr)
+ (message "(clang-format: success%s)" stderr))))
+ (delete-file temp-file)
+ (when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
+
+;;;###autoload
+(defun clang-format-buffer (&optional style)
+ "Use clang-format to format the current buffer according to STYLE."
+ (interactive)
+ (clang-format-region (point-min) (point-max) style))
+
+;;;###autoload
+(defalias 'clang-format 'clang-format-region)
+
+(provide 'clang-format)
+;;; clang-format.el ends here
diff --git a/buildtools/clang_format/script/clang-format.py b/buildtools/clang_format/script/clang-format.py
new file mode 100644
index 0000000..ae8a6eb
--- /dev/null
+++ b/buildtools/clang_format/script/clang-format.py
@@ -0,0 +1,116 @@
+# This file is a minimal clang-format vim-integration. To install:
+# - Change 'binary' if clang-format is not on the path (see below).
+# - Add to your .vimrc:
+#
+# map <C-I> :pyf <path-to-this-file>/clang-format.py<cr>
+# imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr>
+#
+# The first line enables clang-format for NORMAL and VISUAL mode, the second
+# line adds support for INSERT mode. Change "C-I" to another binding if you
+# need clang-format on a different key (C-I stands for Ctrl+i).
+#
+# With this integration you can press the bound key and clang-format will
+# format the current line in NORMAL and INSERT mode or the selected region in
+# VISUAL mode. The line or region is extended to the next bigger syntactic
+# entity.
+#
+# You can also pass in the variable "l:lines" to choose the range for
+# formatting. This variable can either contain "<start line>:<end line>" or
+# "all" to format the full file. So, to format the full file, write a function
+# like:
+# :function FormatFile()
+# : let l:lines="all"
+# : pyf <path-to-this-file>/clang-format.py
+# :endfunction
+#
+# It operates on the current, potentially unsaved buffer and does not create
+# or save any files. To revert a formatting, just undo.
+from __future__ import print_function
+
+import difflib
+import json
+import platform
+import subprocess
+import sys
+import vim
+
+# set g:clang_format_path to the path to clang-format if it is not on the path
+# Change this to the full path if clang-format is not on the path.
+binary = 'clang-format'
+if vim.eval('exists("g:clang_format_path")') == "1":
+ binary = vim.eval('g:clang_format_path')
+
+# Change this to format according to other formatting styles. See the output of
+# 'clang-format --help' for a list of supported styles. The default looks for
+# a '.clang-format' or '_clang-format' file to indicate the style that should be
+# used.
+style = 'file'
+fallback_style = None
+if vim.eval('exists("g:clang_format_fallback_style")') == "1":
+ fallback_style = vim.eval('g:clang_format_fallback_style')
+
+def get_buffer(encoding):
+ if platform.python_version_tuple()[0] == '3':
+ return vim.current.buffer
+ return [ line.decode(encoding) for line in vim.current.buffer ]
+
+def main():
+ # Get the current text.
+ encoding = vim.eval("&encoding")
+ buf = get_buffer(encoding)
+ text = '\n'.join(buf)
+
+ # Determine range to format.
+ if vim.eval('exists("l:lines")') == '1':
+ lines = vim.eval('l:lines')
+ else:
+ lines = '%s:%s' % (vim.current.range.start + 1, vim.current.range.end + 1)
+
+ # Determine the cursor position.
+ cursor = int(vim.eval('line2byte(line("."))+col(".")')) - 2
+ if cursor < 0:
+ print('Couldn\'t determine cursor position. Is your file empty?')
+ return
+
+ # Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format.
+ startupinfo = None
+ if sys.platform.startswith('win32'):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+ startupinfo.wShowWindow = subprocess.SW_HIDE
+
+ # Call formatter.
+ command = [binary, '-style', style, '-cursor', str(cursor)]
+ if lines != 'all':
+ command.extend(['-lines', lines])
+ if fallback_style:
+ command.extend(['-fallback-style', fallback_style])
+ if vim.current.buffer.name:
+ command.extend(['-assume-filename', vim.current.buffer.name])
+ p = subprocess.Popen(command,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ stdin=subprocess.PIPE, startupinfo=startupinfo)
+ stdout, stderr = p.communicate(input=text.encode(encoding))
+
+ # If successful, replace buffer contents.
+ if stderr:
+ print(stderr)
+
+ if not stdout:
+ print(
+ 'No output from clang-format (crashed?).\n'
+ 'Please report to bugs.llvm.org.'
+ )
+ else:
+ lines = stdout.decode(encoding).split('\n')
+ output = json.loads(lines[0])
+ lines = lines[1:]
+ sequence = difflib.SequenceMatcher(None, buf, lines)
+ for op in reversed(sequence.get_opcodes()):
+ if op[0] is not 'equal':
+ vim.current.buffer[op[1]:op[2]] = lines[op[3]:op[4]]
+ if output.get('IncompleteFormat'):
+ print('clang-format: incomplete (syntax errors)')
+ vim.command('goto %d' % (output['Cursor'] + 1))
+
+main()
diff --git a/buildtools/clang_format/script/fuzzer/CMakeLists.txt b/buildtools/clang_format/script/fuzzer/CMakeLists.txt
new file mode 100644
index 0000000..c7772fc
--- /dev/null
+++ b/buildtools/clang_format/script/fuzzer/CMakeLists.txt
@@ -0,0 +1,11 @@
+set(LLVM_LINK_COMPONENTS support)
+
+add_clang_executable(clang-format-fuzzer
+ EXCLUDE_FROM_ALL
+ ClangFormatFuzzer.cpp
+ )
+
+target_link_libraries(clang-format-fuzzer
+ ${CLANG_FORMAT_LIB_DEPS}
+ LLVMFuzzer
+ )
diff --git a/buildtools/clang_format/script/fuzzer/ClangFormatFuzzer.cpp b/buildtools/clang_format/script/fuzzer/ClangFormatFuzzer.cpp
new file mode 100644
index 0000000..5334ce8
--- /dev/null
+++ b/buildtools/clang_format/script/fuzzer/ClangFormatFuzzer.cpp
@@ -0,0 +1,26 @@
+//===-- ClangFormatFuzzer.cpp - Fuzz the Clang format tool ----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief This file implements a function that runs Clang format on a single
+/// input. This function is then linked into the Fuzzer library.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/Format/Format.h"
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
+ // FIXME: fuzz more things: different styles, different style features.
+ std::string s((const char *)data, size);
+ auto Style = getGoogleStyle(clang::format::FormatStyle::LK_Cpp);
+ Style.ColumnLimit = 60;
+ applyAllReplacements(s, clang::format::reformat(
+ Style, s, {clang::tooling::Range(0, s.size())}));
+ return 0;
+}
diff --git a/buildtools/clang_format/script/git-clang-format b/buildtools/clang_format/script/git-clang-format
new file mode 100755
index 0000000..3d1ba8a
--- /dev/null
+++ b/buildtools/clang_format/script/git-clang-format
@@ -0,0 +1,551 @@
+#!/usr/bin/env python
+#
+#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+r"""
+clang-format git integration
+============================
+
+This file provides a clang-format integration for git. Put it somewhere in your
+path and ensure that it is executable. Then, "git clang-format" will invoke
+clang-format on the changes in current files or a specific commit.
+
+For further details, run:
+git clang-format -h
+
+Requires Python 2.7
+"""
+
+from __future__ import print_function
+import argparse
+import collections
+import contextlib
+import errno
+import os
+import re
+import subprocess
+import sys
+
+usage = 'git clang-format [OPTIONS] [<commit>] [<commit>] [--] [<file>...]'
+
+desc = '''
+If zero or one commits are given, run clang-format on all lines that differ
+between the working directory and <commit>, which defaults to HEAD. Changes are
+only applied to the working directory.
+
+If two commits are given (requires --diff), run clang-format on all lines in the
+second <commit> that differ from the first <commit>.
+
+The following git-config settings set the default of the corresponding option:
+ clangFormat.binary
+ clangFormat.commit
+ clangFormat.extension
+ clangFormat.style
+'''
+
+# Name of the temporary index file in which save the output of clang-format.
+# This file is created within the .git directory.
+temp_index_basename = 'clang-format-index'
+
+
+Range = collections.namedtuple('Range', 'start, count')
+
+
+def main():
+ config = load_git_config()
+
+ # In order to keep '--' yet allow options after positionals, we need to
+ # check for '--' ourselves. (Setting nargs='*' throws away the '--', while
+ # nargs=argparse.REMAINDER disallows options after positionals.)
+ argv = sys.argv[1:]
+ try:
+ idx = argv.index('--')
+ except ValueError:
+ dash_dash = []
+ else:
+ dash_dash = argv[idx:]
+ argv = argv[:idx]
+
+ default_extensions = ','.join([
+ # From clang/lib/Frontend/FrontendOptions.cpp, all lower case
+ 'c', 'h', # C
+ 'm', # ObjC
+ 'mm', # ObjC++
+ 'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp', # C++
+ # Other languages that clang-format supports
+ 'proto', 'protodevel', # Protocol Buffers
+ 'java', # Java
+ 'js', # JavaScript
+ 'ts', # TypeScript
+ ])
+
+ p = argparse.ArgumentParser(
+ usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=desc)
+ p.add_argument('--binary',
+ default=config.get('clangformat.binary', 'clang-format'),
+ help='path to clang-format'),
+ p.add_argument('--commit',
+ default=config.get('clangformat.commit', 'HEAD'),
+ help='default commit to use if none is specified'),
+ p.add_argument('--diff', action='store_true',
+ help='print a diff instead of applying the changes')
+ p.add_argument('--extensions',
+ default=config.get('clangformat.extensions',
+ default_extensions),
+ help=('comma-separated list of file extensions to format, '
+ 'excluding the period and case-insensitive')),
+ p.add_argument('-f', '--force', action='store_true',
+ help='allow changes to unstaged files')
+ p.add_argument('-p', '--patch', action='store_true',
+ help='select hunks interactively')
+ p.add_argument('-q', '--quiet', action='count', default=0,
+ help='print less information')
+ p.add_argument('--style',
+ default=config.get('clangformat.style', None),
+ help='passed to clang-format'),
+ p.add_argument('-v', '--verbose', action='count', default=0,
+ help='print extra information')
+ # We gather all the remaining positional arguments into 'args' since we need
+ # to use some heuristics to determine whether or not <commit> was present.
+ # However, to print pretty messages, we make use of metavar and help.
+ p.add_argument('args', nargs='*', metavar='<commit>',
+ help='revision from which to compute the diff')
+ p.add_argument('ignored', nargs='*', metavar='<file>...',
+ help='if specified, only consider differences in these files')
+ opts = p.parse_args(argv)
+
+ opts.verbose -= opts.quiet
+ del opts.quiet
+
+ commits, files = interpret_args(opts.args, dash_dash, opts.commit)
+ if len(commits) > 1:
+ if not opts.diff:
+ die('--diff is required when two commits are given')
+ else:
+ if len(commits) > 2:
+ die('at most two commits allowed; %d given' % len(commits))
+ changed_lines = compute_diff_and_extract_lines(commits, files)
+ if opts.verbose >= 1:
+ ignored_files = set(changed_lines)
+ filter_by_extension(changed_lines, opts.extensions.lower().split(','))
+ if opts.verbose >= 1:
+ ignored_files.difference_update(changed_lines)
+ if ignored_files:
+ print('Ignoring changes in the following files (wrong extension):')
+ for filename in ignored_files:
+ print(' %s' % filename)
+ if changed_lines:
+ print('Running clang-format on the following files:')
+ for filename in changed_lines:
+ print(' %s' % filename)
+ if not changed_lines:
+ print('no modified files to format')
+ return
+ # The computed diff outputs absolute paths, so we must cd before accessing
+ # those files.
+ cd_to_toplevel()
+ if len(commits) > 1:
+ old_tree = commits[1]
+ new_tree = run_clang_format_and_save_to_tree(changed_lines,
+ revision=commits[1],
+ binary=opts.binary,
+ style=opts.style)
+ else:
+ old_tree = create_tree_from_workdir(changed_lines)
+ new_tree = run_clang_format_and_save_to_tree(changed_lines,
+ binary=opts.binary,
+ style=opts.style)
+ if opts.verbose >= 1:
+ print('old tree: %s' % old_tree)
+ print('new tree: %s' % new_tree)
+ if old_tree == new_tree:
+ if opts.verbose >= 0:
+ print('clang-format did not modify any files')
+ elif opts.diff:
+ print_diff(old_tree, new_tree)
+ else:
+ changed_files = apply_changes(old_tree, new_tree, force=opts.force,
+ patch_mode=opts.patch)
+ if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1:
+ print('changed files:')
+ for filename in changed_files:
+ print(' %s' % filename)
+
+
+def load_git_config(non_string_options=None):
+ """Return the git configuration as a dictionary.
+
+ All options are assumed to be strings unless in `non_string_options`, in which
+ is a dictionary mapping option name (in lower case) to either "--bool" or
+ "--int"."""
+ if non_string_options is None:
+ non_string_options = {}
+ out = {}
+ for entry in run('git', 'config', '--list', '--null').split('\0'):
+ if entry:
+ name, value = entry.split('\n', 1)
+ if name in non_string_options:
+ value = run('git', 'config', non_string_options[name], name)
+ out[name] = value
+ return out
+
+
+def interpret_args(args, dash_dash, default_commit):
+ """Interpret `args` as "[commits] [--] [files]" and return (commits, files).
+
+ It is assumed that "--" and everything that follows has been removed from
+ args and placed in `dash_dash`.
+
+ If "--" is present (i.e., `dash_dash` is non-empty), the arguments to its
+ left (if present) are taken as commits. Otherwise, the arguments are checked
+ from left to right if they are commits or files. If commits are not given,
+ a list with `default_commit` is used."""
+ if dash_dash:
+ if len(args) == 0:
+ commits = [default_commit]
+ else:
+ commits = args
+ for commit in commits:
+ object_type = get_object_type(commit)
+ if object_type not in ('commit', 'tag'):
+ if object_type is None:
+ die("'%s' is not a commit" % commit)
+ else:
+ die("'%s' is a %s, but a commit was expected" % (commit, object_type))
+ files = dash_dash[1:]
+ elif args:
+ commits = []
+ while args:
+ if not disambiguate_revision(args[0]):
+ break
+ commits.append(args.pop(0))
+ if not commits:
+ commits = [default_commit]
+ files = args
+ else:
+ commits = [default_commit]
+ files = []
+ return commits, files
+
+
+def disambiguate_revision(value):
+ """Returns True if `value` is a revision, False if it is a file, or dies."""
+ # If `value` is ambiguous (neither a commit nor a file), the following
+ # command will die with an appropriate error message.
+ run('git', 'rev-parse', value, verbose=False)
+ object_type = get_object_type(value)
+ if object_type is None:
+ return False
+ if object_type in ('commit', 'tag'):
+ return True
+ die('`%s` is a %s, but a commit or filename was expected' %
+ (value, object_type))
+
+
+def get_object_type(value):
+ """Returns a string description of an object's type, or None if it is not
+ a valid git object."""
+ cmd = ['git', 'cat-file', '-t', value]
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ return None
+ return stdout.strip()
+
+
+def compute_diff_and_extract_lines(commits, files):
+ """Calls compute_diff() followed by extract_lines()."""
+ diff_process = compute_diff(commits, files)
+ changed_lines = extract_lines(diff_process.stdout)
+ diff_process.stdout.close()
+ diff_process.wait()
+ if diff_process.returncode != 0:
+ # Assume error was already printed to stderr.
+ sys.exit(2)
+ return changed_lines
+
+
+def compute_diff(commits, files):
+ """Return a subprocess object producing the diff from `commits`.
+
+ The return value's `stdin` file object will produce a patch with the
+ differences between the working directory and the first commit if a single
+ one was specified, or the difference between both specified commits, filtered
+ on `files` (if non-empty). Zero context lines are used in the patch."""
+ git_tool = 'diff-index'
+ if len(commits) > 1:
+ git_tool = 'diff-tree'
+ cmd = ['git', git_tool, '-p', '-U0'] + commits + ['--']
+ cmd.extend(files)
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ p.stdin.close()
+ return p
+
+
+def extract_lines(patch_file):
+ """Extract the changed lines in `patch_file`.
+
+ The return value is a dictionary mapping filename to a list of (start_line,
+ line_count) pairs.
+
+ The input must have been produced with ``-U0``, meaning unidiff format with
+ zero lines of context. The return value is a dict mapping filename to a
+ list of line `Range`s."""
+ matches = {}
+ for line in patch_file:
+ match = re.search(r'^\+\+\+\ [^/]+/(.*)', line)
+ if match:
+ filename = match.group(1).rstrip('\r\n')
+ match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line)
+ if match:
+ start_line = int(match.group(1))
+ line_count = 1
+ if match.group(3):
+ line_count = int(match.group(3))
+ if line_count > 0:
+ matches.setdefault(filename, []).append(Range(start_line, line_count))
+ return matches
+
+
+def filter_by_extension(dictionary, allowed_extensions):
+ """Delete every key in `dictionary` that doesn't have an allowed extension.
+
+ `allowed_extensions` must be a collection of lowercase file extensions,
+ excluding the period."""
+ allowed_extensions = frozenset(allowed_extensions)
+ for filename in list(dictionary.keys()):
+ base_ext = filename.rsplit('.', 1)
+ if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions:
+ del dictionary[filename]
+
+
+def cd_to_toplevel():
+ """Change to the top level of the git repository."""
+ toplevel = run('git', 'rev-parse', '--show-toplevel')
+ os.chdir(toplevel)
+
+
+def create_tree_from_workdir(filenames):
+ """Create a new git tree with the given files from the working directory.
+
+ Returns the object ID (SHA-1) of the created tree."""
+ return create_tree(filenames, '--stdin')
+
+
+def run_clang_format_and_save_to_tree(changed_lines, revision=None,
+ binary='clang-format', style=None):
+ """Run clang-format on each file and save the result to a git tree.
+
+ Returns the object ID (SHA-1) of the created tree."""
+ def iteritems(container):
+ try:
+ return container.iteritems() # Python 2
+ except AttributeError:
+ return container.items() # Python 3
+ def index_info_generator():
+ for filename, line_ranges in iteritems(changed_lines):
+ if revision:
+ git_metadata_cmd = ['git', 'ls-tree',
+ '%s:%s' % (revision, os.path.dirname(filename)),
+ os.path.basename(filename)]
+ git_metadata = subprocess.Popen(git_metadata_cmd, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ stdout = git_metadata.communicate()[0]
+ mode = oct(int(stdout.split()[0], 8))
+ else:
+ mode = oct(os.stat(filename).st_mode)
+ # Adjust python3 octal format so that it matches what git expects
+ if mode.startswith('0o'):
+ mode = '0' + mode[2:]
+ blob_id = clang_format_to_blob(filename, line_ranges,
+ revision=revision,
+ binary=binary,
+ style=style)
+ yield '%s %s\t%s' % (mode, blob_id, filename)
+ return create_tree(index_info_generator(), '--index-info')
+
+
+def create_tree(input_lines, mode):
+ """Create a tree object from the given input.
+
+ If mode is '--stdin', it must be a list of filenames. If mode is
+ '--index-info' is must be a list of values suitable for "git update-index
+ --index-info", such as "<mode> <SP> <sha1> <TAB> <filename>". Any other mode
+ is invalid."""
+ assert mode in ('--stdin', '--index-info')
+ cmd = ['git', 'update-index', '--add', '-z', mode]
+ with temporary_index_file():
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+ for line in input_lines:
+ p.stdin.write('%s\0' % line)
+ p.stdin.close()
+ if p.wait() != 0:
+ die('`%s` failed' % ' '.join(cmd))
+ tree_id = run('git', 'write-tree')
+ return tree_id
+
+
+def clang_format_to_blob(filename, line_ranges, revision=None,
+ binary='clang-format', style=None):
+ """Run clang-format on the given file and save the result to a git blob.
+
+ Runs on the file in `revision` if not None, or on the file in the working
+ directory if `revision` is None.
+
+ Returns the object ID (SHA-1) of the created blob."""
+ clang_format_cmd = [binary]
+ if style:
+ clang_format_cmd.extend(['-style='+style])
+ clang_format_cmd.extend([
+ '-lines=%s:%s' % (start_line, start_line+line_count-1)
+ for start_line, line_count in line_ranges])
+ if revision:
+ clang_format_cmd.extend(['-assume-filename='+filename])
+ git_show_cmd = ['git', 'cat-file', 'blob', '%s:%s' % (revision, filename)]
+ git_show = subprocess.Popen(git_show_cmd, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ git_show.stdin.close()
+ clang_format_stdin = git_show.stdout
+ else:
+ clang_format_cmd.extend([filename])
+ git_show = None
+ clang_format_stdin = subprocess.PIPE
+ try:
+ clang_format = subprocess.Popen(clang_format_cmd, stdin=clang_format_stdin,
+ stdout=subprocess.PIPE)
+ if clang_format_stdin == subprocess.PIPE:
+ clang_format_stdin = clang_format.stdin
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ die('cannot find executable "%s"' % binary)
+ else:
+ raise
+ clang_format_stdin.close()
+ hash_object_cmd = ['git', 'hash-object', '-w', '--path='+filename, '--stdin']
+ hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout,
+ stdout=subprocess.PIPE)
+ clang_format.stdout.close()
+ stdout = hash_object.communicate()[0]
+ if hash_object.returncode != 0:
+ die('`%s` failed' % ' '.join(hash_object_cmd))
+ if clang_format.wait() != 0:
+ die('`%s` failed' % ' '.join(clang_format_cmd))
+ if git_show and git_show.wait() != 0:
+ die('`%s` failed' % ' '.join(git_show_cmd))
+ return stdout.rstrip('\r\n')
+
+
+@contextlib.contextmanager
+def temporary_index_file(tree=None):
+ """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting
+ the file afterward."""
+ index_path = create_temporary_index(tree)
+ old_index_path = os.environ.get('GIT_INDEX_FILE')
+ os.environ['GIT_INDEX_FILE'] = index_path
+ try:
+ yield
+ finally:
+ if old_index_path is None:
+ del os.environ['GIT_INDEX_FILE']
+ else:
+ os.environ['GIT_INDEX_FILE'] = old_index_path
+ os.remove(index_path)
+
+
+def create_temporary_index(tree=None):
+ """Create a temporary index file and return the created file's path.
+
+ If `tree` is not None, use that as the tree to read in. Otherwise, an
+ empty index is created."""
+ gitdir = run('git', 'rev-parse', '--git-dir')
+ path = os.path.join(gitdir, temp_index_basename)
+ if tree is None:
+ tree = '--empty'
+ run('git', 'read-tree', '--index-output='+path, tree)
+ return path
+
+
+def print_diff(old_tree, new_tree):
+ """Print the diff between the two trees to stdout."""
+ # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output
+ # is expected to be viewed by the user, and only the former does nice things
+ # like color and pagination.
+ #
+ # We also only print modified files since `new_tree` only contains the files
+ # that were modified, so unmodified files would show as deleted without the
+ # filter.
+ subprocess.check_call(['git', 'diff', '--diff-filter=M', old_tree, new_tree,
+ '--'])
+
+
+def apply_changes(old_tree, new_tree, force=False, patch_mode=False):
+ """Apply the changes in `new_tree` to the working directory.
+
+ Bails if there are local changes in those files and not `force`. If
+ `patch_mode`, runs `git checkout --patch` to select hunks interactively."""
+ changed_files = run('git', 'diff-tree', '--diff-filter=M', '-r', '-z',
+ '--name-only', old_tree,
+ new_tree).rstrip('\0').split('\0')
+ if not force:
+ unstaged_files = run('git', 'diff-files', '--name-status', *changed_files)
+ if unstaged_files:
+ print('The following files would be modified but '
+ 'have unstaged changes:', file=sys.stderr)
+ print(unstaged_files, file=sys.stderr)
+ print('Please commit, stage, or stash them first.', file=sys.stderr)
+ sys.exit(2)
+ if patch_mode:
+ # In patch mode, we could just as well create an index from the new tree
+ # and checkout from that, but then the user will be presented with a
+ # message saying "Discard ... from worktree". Instead, we use the old
+ # tree as the index and checkout from new_tree, which gives the slightly
+ # better message, "Apply ... to index and worktree". This is not quite
+ # right, since it won't be applied to the user's index, but oh well.
+ with temporary_index_file(old_tree):
+ subprocess.check_call(['git', 'checkout', '--patch', new_tree])
+ index_tree = old_tree
+ else:
+ with temporary_index_file(new_tree):
+ run('git', 'checkout-index', '-a', '-f')
+ return changed_files
+
+
+def run(*args, **kwargs):
+ stdin = kwargs.pop('stdin', '')
+ verbose = kwargs.pop('verbose', True)
+ strip = kwargs.pop('strip', True)
+ for name in kwargs:
+ raise TypeError("run() got an unexpected keyword argument '%s'" % name)
+ p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate(input=stdin)
+ if p.returncode == 0:
+ if stderr:
+ if verbose:
+ print('`%s` printed to stderr:' % ' '.join(args), file=sys.stderr)
+ print(stderr.rstrip(), file=sys.stderr)
+ if strip:
+ stdout = stdout.rstrip('\r\n')
+ return stdout
+ if verbose:
+ print('`%s` returned %s' % (' '.join(args), p.returncode), file=sys.stderr)
+ if stderr:
+ print(stderr.rstrip(), file=sys.stderr)
+ sys.exit(2)
+
+
+def die(message):
+ print('error:', message, file=sys.stderr)
+ sys.exit(2)
+
+
+if __name__ == '__main__':
+ main()