summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/clang/empty_string/EmptyStringConverter.cpp196
-rw-r--r--tools/clang/empty_string/Makefile23
-rw-r--r--tools/clang/empty_string/tests/test-expected.cc38
-rw-r--r--tools/clang/empty_string/tests/test-original.cc38
-rwxr-xr-xtools/clang/scripts/run_tool.py282
-rwxr-xr-xtools/clang/scripts/test_tool.py114
-rwxr-xr-xtools/clang/scripts/update.sh17
7 files changed, 706 insertions, 2 deletions
diff --git a/tools/clang/empty_string/EmptyStringConverter.cpp b/tools/clang/empty_string/EmptyStringConverter.cpp
new file mode 100644
index 0000000..a9cc4b0
--- /dev/null
+++ b/tools/clang/empty_string/EmptyStringConverter.cpp
@@ -0,0 +1,196 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This implements a Clang tool to convert all instances of std::string("") to
+// std::string(). The latter is more efficient (as std::string doesn't have to
+// take a copy of an empty string) and generates fewer instructions as well. It
+// should be run using the tools/clang/scripts/run_tool.py helper.
+
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/FrontendActions.h"
+#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/Support/CommandLine.h"
+
+using clang::ast_matchers::MatchFinder;
+using clang::ast_matchers::argumentCountIs;
+using clang::ast_matchers::bindTemporaryExpr;
+using clang::ast_matchers::constructorDecl;
+using clang::ast_matchers::constructExpr;
+using clang::ast_matchers::defaultArgExpr;
+using clang::ast_matchers::expr;
+using clang::ast_matchers::forEach;
+using clang::ast_matchers::has;
+using clang::ast_matchers::hasArgument;
+using clang::ast_matchers::hasDeclaration;
+using clang::ast_matchers::hasName;
+using clang::ast_matchers::id;
+using clang::ast_matchers::methodDecl;
+using clang::ast_matchers::newExpr;
+using clang::ast_matchers::ofClass;
+using clang::ast_matchers::stringLiteral;
+using clang::ast_matchers::varDecl;
+using clang::tooling::CommonOptionsParser;
+using clang::tooling::Replacement;
+using clang::tooling::Replacements;
+
+namespace {
+
+// Handles replacements for stack and heap-allocated instances, e.g.:
+// std::string a("");
+// std::string* b = new std::string("");
+class ConstructorCallback : public MatchFinder::MatchCallback {
+ public:
+ ConstructorCallback(Replacements* replacements)
+ : replacements_(replacements) {}
+
+ virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE;
+
+ private:
+ Replacements* const replacements_;
+};
+
+// Handles replacements for invocations of std::string("") in an initializer
+// list.
+class InitializerCallback : public MatchFinder::MatchCallback {
+ public:
+ InitializerCallback(Replacements* replacements)
+ : replacements_(replacements) {}
+
+ virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE;
+
+ private:
+ Replacements* const replacements_;
+};
+
+// Handles replacements for invocations of std::string("") in a temporary
+// context, e.g. FunctionThatTakesString(std::string("")). Note that this
+// handles implicits construction of std::string as well.
+class TemporaryCallback : public MatchFinder::MatchCallback {
+ public:
+ TemporaryCallback(Replacements* replacements) : replacements_(replacements) {}
+
+ virtual void run(const MatchFinder::MatchResult& result) LLVM_OVERRIDE;
+
+ private:
+ Replacements* const replacements_;
+};
+
+class EmptyStringConverter {
+ public:
+ explicit EmptyStringConverter(Replacements* replacements)
+ : constructor_callback_(replacements),
+ initializer_callback_(replacements),
+ temporary_callback_(replacements) {}
+
+ void SetupMatchers(MatchFinder* match_finder);
+
+ private:
+ ConstructorCallback constructor_callback_;
+ InitializerCallback initializer_callback_;
+ TemporaryCallback temporary_callback_;
+};
+
+void EmptyStringConverter::SetupMatchers(MatchFinder* match_finder) {
+ const auto& constructor_call =
+ id("call",
+ constructExpr(
+ hasDeclaration(methodDecl(ofClass(hasName("std::basic_string")))),
+ argumentCountIs(2),
+ hasArgument(0, id("literal", stringLiteral())),
+ hasArgument(1, defaultArgExpr())));
+
+ // Note that expr(has()) in the matcher is significant; the Clang AST wraps
+ // calls to the std::string constructor with exprWithCleanups nodes. Without
+ // the expr(has()) matcher, the first and last rules would not match anything!
+ match_finder->addMatcher(varDecl(forEach(expr(has(constructor_call)))),
+ &constructor_callback_);
+ match_finder->addMatcher(newExpr(has(constructor_call)),
+ &constructor_callback_);
+ match_finder->addMatcher(bindTemporaryExpr(has(constructor_call)),
+ &temporary_callback_);
+ match_finder->addMatcher(
+ constructorDecl(forEach(expr(has(constructor_call)))),
+ &initializer_callback_);
+}
+
+void ConstructorCallback::run(const MatchFinder::MatchResult& result) {
+ const clang::StringLiteral* literal =
+ result.Nodes.getNodeAs<clang::StringLiteral>("literal");
+ if (literal->getLength() > 0)
+ return;
+
+ const clang::CXXConstructExpr* call =
+ result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
+ clang::CharSourceRange range =
+ clang::CharSourceRange::getTokenRange(call->getParenRange());
+ replacements_->insert(Replacement(*result.SourceManager, range, ""));
+}
+
+void InitializerCallback::run(const MatchFinder::MatchResult& result) {
+ const clang::StringLiteral* literal =
+ result.Nodes.getNodeAs<clang::StringLiteral>("literal");
+ if (literal->getLength() > 0)
+ return;
+
+ const clang::CXXConstructExpr* call =
+ result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
+ replacements_->insert(Replacement(*result.SourceManager, call, ""));
+}
+
+void TemporaryCallback::run(const MatchFinder::MatchResult& result) {
+ const clang::StringLiteral* literal =
+ result.Nodes.getNodeAs<clang::StringLiteral>("literal");
+ if (literal->getLength() > 0)
+ return;
+
+ const clang::CXXConstructExpr* call =
+ result.Nodes.getNodeAs<clang::CXXConstructExpr>("call");
+ // Differentiate between explicit and implicit calls to std::string's
+ // constructor. An implicitly generated constructor won't have a valid
+ // source range for the parenthesis.
+ clang::SourceRange range = call->getParenRange();
+ if (range.isValid()) {
+ replacements_->insert(Replacement(*result.SourceManager, literal, ""));
+ } else {
+ replacements_->insert(
+ Replacement(*result.SourceManager, call, "std::string()"));
+ }
+}
+
+} // namespace
+
+static llvm::cl::extrahelp common_help(CommonOptionsParser::HelpMessage);
+
+int main(int argc, const char* argv[]) {
+ CommonOptionsParser options(argc, argv);
+ clang::tooling::ClangTool tool(options.getCompilations(),
+ options.getSourcePathList());
+
+ Replacements replacements;
+ EmptyStringConverter converter(&replacements);
+ MatchFinder match_finder;
+ converter.SetupMatchers(&match_finder);
+
+ int result =
+ tool.run(clang::tooling::newFrontendActionFactory(&match_finder));
+ if (result != 0)
+ return result;
+
+ // Each replacement line should have the following format:
+ // r:<file path>:<offset>:<length>:<replacement text>
+ // Only the <replacement text> field can contain embedded ":" characters.
+ // TODO(dcheng): Use a more clever serialization.
+ llvm::outs() << "==== BEGIN EDITS ====\n";
+ for (const Replacement& r : replacements) {
+ llvm::outs() << "r:" << r.getFilePath() << ":" << r.getOffset() << ":"
+ << r.getLength() << ":" << r.getReplacementText() << "\n";
+ }
+ llvm::outs() << "==== END EDITS ====\n";
+
+ return 0;
+}
diff --git a/tools/clang/empty_string/Makefile b/tools/clang/empty_string/Makefile
new file mode 100644
index 0000000..d446412
--- /dev/null
+++ b/tools/clang/empty_string/Makefile
@@ -0,0 +1,23 @@
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This Makefile requires the LLVM build system. In order to build this tool,
+# please run tools/clang/scripts/build_tool.py.
+
+CLANG_LEVEL := ../..
+
+TOOLNAME = empty_string
+
+NO_INSTALL = 1
+
+include $(CLANG_LEVEL)/../../Makefile.config
+
+LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc
+USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \
+ clangRewriteFrontend.a clangRewriteCore.a clangParse.a clangSema.a \
+ clangAnalysis.a clangAST.a clangASTMatchers.a clangEdit.a \
+ clangLex.a clangBasic.a
+
+include $(CLANG_LEVEL)/Makefile
+
diff --git a/tools/clang/empty_string/tests/test-expected.cc b/tools/clang/empty_string/tests/test-expected.cc
new file mode 100644
index 0000000..88877b1
--- /dev/null
+++ b/tools/clang/empty_string/tests/test-expected.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Test file for the empty string clang tool.
+
+#include <string>
+
+// Tests for std::string declarations.
+void TestDeclarations() { std::string a, b("abc"), c; }
+
+// Tests for std::string allocated with new.
+void TestNew() {
+ std::string* a = new std::string,
+ *b = new std::string("abc"),
+ *c = new std::string,
+ *d = new std::string();
+}
+
+// Tests for std::string construction in initializer lists.
+class TestInitializers {
+ public:
+ TestInitializers() {}
+ TestInitializers(bool) {}
+ TestInitializers(double) : b("cat"), c() {}
+
+ private:
+ std::string a;
+ std::string b;
+ std::string c;
+};
+
+// Tests for temporary std::strings.
+void TestTemporaries(const std::string& reference_argument,
+ const std::string value_argument) {
+ TestTemporaries(std::string(), std::string());
+ TestTemporaries(std::string(), std::string());
+}
diff --git a/tools/clang/empty_string/tests/test-original.cc b/tools/clang/empty_string/tests/test-original.cc
new file mode 100644
index 0000000..f86fd08
--- /dev/null
+++ b/tools/clang/empty_string/tests/test-original.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Test file for the empty string clang tool.
+
+#include <string>
+
+// Tests for std::string declarations.
+void TestDeclarations() { std::string a(""), b("abc"), c(""); }
+
+// Tests for std::string allocated with new.
+void TestNew() {
+ std::string* a = new std::string(""),
+ *b = new std::string("abc"),
+ *c = new std::string(""),
+ *d = new std::string();
+}
+
+// Tests for std::string construction in initializer lists.
+class TestInitializers {
+ public:
+ TestInitializers() : a("") {}
+ TestInitializers(bool) : a(""), b("") {}
+ TestInitializers(double) : a(""), b("cat"), c() {}
+
+ private:
+ std::string a;
+ std::string b;
+ std::string c;
+};
+
+// Tests for temporary std::strings.
+void TestTemporaries(const std::string& reference_argument,
+ const std::string value_argument) {
+ TestTemporaries("", "");
+ TestTemporaries(std::string(""), std::string(""));
+}
diff --git a/tools/clang/scripts/run_tool.py b/tools/clang/scripts/run_tool.py
new file mode 100755
index 0000000..6eed3ca
--- /dev/null
+++ b/tools/clang/scripts/run_tool.py
@@ -0,0 +1,282 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Wrapper script to help run clang tools across Chromium code.
+
+How to use this tool:
+If you want to run the tool across all Chromium code:
+run_tool.py <tool> <path/to/compiledb>
+
+If you only want to run the tool across just chrome/browser and content/browser:
+run_tool.py <tool> <path/to/compiledb> chrome/browser content/browser
+
+Please see https://code.google.com/p/chromium/wiki/ClangToolRefactoring for more
+information, which documents the entire automated refactoring flow in Chromium.
+
+Why use this tool:
+The clang tool implementation doesn't take advantage of multiple cores, and if
+it fails mysteriously in the middle, all the generated replacements will be
+lost.
+
+Unfortunately, if the work is simply sharded across multiple cores by running
+multiple RefactoringTools, problems arise when they attempt to rewrite a file at
+the same time. To work around that, clang tools that are run using this tool
+should output edits to stdout in the following format:
+
+==== BEGIN EDITS ====
+r:<file path>:<offset>:<length>:<replacement text>
+r:<file path>:<offset>:<length>:<replacement text>
+...etc...
+==== END EDITS ====
+
+Any generated edits are applied once the clang tool has finished running
+across Chromium, regardless of whether some instances failed or not.
+"""
+
+import collections
+import functools
+import multiprocessing
+import os.path
+import subprocess
+import sys
+
+
+Edit = collections.namedtuple(
+ 'Edit', ('edit_type', 'offset', 'length', 'replacement'))
+
+
+def _GetFilesFromGit(paths = None):
+ """Gets the list of files in the git repository.
+
+ Args:
+ paths: Prefix filter for the returned paths. May contain multiple entries.
+ """
+ args = ['git', 'ls-files']
+ if paths:
+ args.extend(paths)
+ command = subprocess.Popen(args, stdout=subprocess.PIPE)
+ output, _ = command.communicate()
+ return output.splitlines()
+
+
+def _ExtractEditsFromStdout(build_directory, stdout):
+ """Extracts generated list of edits from the tool's stdout.
+
+ The expected format is documented at the top of this file.
+
+ Args:
+ build_directory: Directory that contains the compile database. Used to
+ normalize the filenames.
+ stdout: The stdout from running the clang tool.
+
+ Returns:
+ A dictionary mapping filenames to the associated edits.
+ """
+ lines = stdout.splitlines()
+ start_index = lines.index('==== BEGIN EDITS ====')
+ end_index = lines.index('==== END EDITS ====')
+ edits = collections.defaultdict(list)
+ for line in lines[start_index + 1:end_index]:
+ try:
+ edit_type, path, offset, length, replacement = line.split(':', 4)
+ # Normalize the file path emitted by the clang tool to be relative to the
+ # current working directory.
+ path = os.path.relpath(os.path.join(build_directory, path))
+ edits[path].append(Edit(edit_type, int(offset), int(length), replacement))
+ except ValueError:
+ print 'Unable to parse edit: %s' % line
+ return edits
+
+
+def _ExecuteTool(toolname, build_directory, filename):
+ """Executes the tool.
+
+ This is defined outside the class so it can be pickled for the multiprocessing
+ module.
+
+ Args:
+ toolname: Path to the tool to execute.
+ build_directory: Directory that contains the compile database.
+ filename: The file to run the tool over.
+
+ Returns:
+ A dictionary that must contain the key "status" and a boolean value
+ associated with it.
+
+ If status is True, then the generated edits are stored with the key "edits"
+ in the dictionary.
+
+ Otherwise, the filename and the output from stderr are associated with the
+ keys "filename" and "stderr" respectively.
+ """
+ command = subprocess.Popen((toolname, '-p', build_directory, filename),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ stdout, stderr = command.communicate()
+ if command.returncode != 0:
+ return {'status': False, 'filename': filename, 'stderr': stderr}
+ else:
+ return {'status': True,
+ 'edits': _ExtractEditsFromStdout(build_directory, stdout)}
+
+
+class _CompilerDispatcher(object):
+ """Multiprocessing controller for running clang tools in parallel."""
+
+ def __init__(self, toolname, build_directory, filenames):
+ """Initializer method.
+
+ Args:
+ toolname: Path to the tool to execute.
+ build_directory: Directory that contains the compile database.
+ filenames: The files to run the tool over.
+ """
+ self.__toolname = toolname
+ self.__build_directory = build_directory
+ self.__filenames = filenames
+ self.__success_count = 0
+ self.__failed_count = 0
+ self.__edits = collections.defaultdict(list)
+
+ @property
+ def edits(self):
+ return self.__edits
+
+ def Run(self):
+ """Does the grunt work."""
+ pool = multiprocessing.Pool()
+ result_iterator = pool.imap_unordered(
+ functools.partial(_ExecuteTool, self.__toolname,
+ self.__build_directory),
+ self.__filenames)
+ for result in result_iterator:
+ self.__ProcessResult(result)
+ sys.stdout.write('\n')
+ sys.stdout.flush()
+
+ def __ProcessResult(self, result):
+ """Handles result processing.
+
+ Args:
+ result: The result dictionary returned by _ExecuteTool.
+ """
+ if result['status']:
+ self.__success_count += 1
+ for k, v in result['edits'].iteritems():
+ self.__edits[k].extend(v)
+ else:
+ self.__failed_count += 1
+ sys.stdout.write('\nFailed to process %s\n' % result['filename'])
+ sys.stdout.write(result['stderr'])
+ sys.stdout.write('\n')
+ percentage = (
+ float(self.__success_count + self.__failed_count) /
+ len(self.__filenames)) * 100
+ sys.stdout.write('Succeeded: %d, Failed: %d [%.2f%%]\r' % (
+ self.__success_count, self.__failed_count, percentage))
+ sys.stdout.flush()
+
+
+def _ApplyEdits(edits):
+ """Apply the generated edits.
+
+ Args:
+ edits: A dict mapping filenames to Edit instances that apply to that file.
+ """
+ edit_count = 0
+ for k, v in edits.iteritems():
+ # Sort the edits and iterate through them in reverse order. Sorting allows
+ # duplicate edits to be quickly skipped, while reversing means that
+ # subsequent edits don't need to have their offsets updated with each edit
+ # applied.
+ v.sort()
+ last_edit = None
+ with open(k, 'rb+') as f:
+ contents = bytearray(f.read())
+ for edit in reversed(v):
+ if edit == last_edit:
+ continue
+ last_edit = edit
+ contents[edit.offset:edit.offset + edit.length] = edit.replacement
+ if not edit.replacement:
+ _ExtendDeletionIfElementIsInList(contents, edit.offset)
+ edit_count += 1
+ f.seek(0)
+ f.truncate()
+ f.write(contents)
+ print 'Applied %d edits to %d files' % (edit_count, len(edits))
+
+
+_WHITESPACE_BYTES = frozenset((ord('\t'), ord('\n'), ord('\r'), ord(' ')))
+
+
+def _ExtendDeletionIfElementIsInList(contents, offset):
+ """Extends the range of a deletion if the deleted element was part of a list.
+
+ This rewriter helper makes it easy for refactoring tools to remove elements
+ from a list. Even if a matcher callback knows that it is removing an element
+ from a list, it may not have enough information to accurately remove the list
+ element; for example, another matcher callback may end up removing an adjacent
+ list element, or all the list elements may end up being removed.
+
+ With this helper, refactoring tools can simply remove the list element and not
+ worry about having to include the comma in the replacement.
+
+ Args:
+ contents: A bytearray with the deletion already applied.
+ offset: The offset in the bytearray where the deleted range used to be.
+ """
+ char_before = char_after = None
+ left_trim_count = 0
+ for byte in reversed(contents[:offset]):
+ left_trim_count += 1
+ if byte in _WHITESPACE_BYTES:
+ continue
+ if byte in (ord(','), ord(':'), ord('('), ord('{')):
+ char_before = chr(byte)
+ break
+
+ right_trim_count = 0
+ for byte in contents[offset:]:
+ right_trim_count += 1
+ if byte in _WHITESPACE_BYTES:
+ continue
+ if byte == ord(','):
+ char_after = chr(byte)
+ break
+
+ if char_before:
+ if char_after:
+ del contents[offset:offset + right_trim_count]
+ elif char_before in (',', ':'):
+ del contents[offset - left_trim_count:offset]
+
+
+def main(argv):
+ if len(argv) < 2:
+ print 'Usage: run_tool.py <clang tool> <compile DB> <path 1> <path 2> ...'
+ print ' <clang tool> is the clang tool that should be run.'
+ print ' <compile db> is the directory that contains the compile database'
+ print ' <path 1> <path2> ... can be used to filter what files are edited'
+ sys.exit(1)
+
+ filenames = frozenset(_GetFilesFromGit(argv[2:]))
+ # Filter out files that aren't C/C++/Obj-C/Obj-C++.
+ extensions = frozenset(('.c', '.cc', '.m', '.mm'))
+ dispatcher = _CompilerDispatcher(argv[0], argv[1],
+ [f for f in filenames
+ if os.path.splitext(f)[1] in extensions])
+ dispatcher.Run()
+ # Filter out edits to files that aren't in the git repository, since it's not
+ # useful to modify files that aren't under source control--typically, these
+ # are generated files or files in a git submodule that's not part of Chromium.
+ _ApplyEdits({k : v for k, v in dispatcher.edits.iteritems()
+ if k in filenames})
+ # TODO(dcheng): Consider clang-formatting the result to avoid egregious style
+ # violations.
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/clang/scripts/test_tool.py b/tools/clang/scripts/test_tool.py
new file mode 100755
index 0000000..d2b87a0e
--- /dev/null
+++ b/tools/clang/scripts/test_tool.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Test harness for chromium clang tools."""
+
+import difflib
+import glob
+import json
+import os
+import os.path
+import subprocess
+import shutil
+import sys
+
+
+def _GenerateCompileCommands(files):
+ """Returns a JSON string containing a compilation database for the input."""
+ return json.dumps([{'directory': '.',
+ 'command': 'clang++ -fsyntax-only -c %s' % f,
+ 'file': f} for f in files], indent=2)
+
+
+def _NumberOfTestsToString(tests):
+ """Returns an English describing the number of tests."""
+ return "%d test%s" % (tests, 's' if tests != 1 else '')
+
+
+def main(argv):
+ if len(argv) < 1:
+ print 'Usage: test_tool.py <clang tool>'
+ print ' <clang tool> is the clang tool to be tested.'
+ sys.exit(1)
+
+ tool_to_test = argv[0]
+ tools_clang_scripts_directory = os.path.dirname(os.path.realpath(__file__))
+ tools_clang_directory = os.path.dirname(tools_clang_scripts_directory)
+ test_directory_for_tool = os.path.join(
+ tools_clang_directory, tool_to_test, 'tests')
+ compile_database = os.path.join(test_directory_for_tool,
+ 'compile_commands.json')
+ source_files = glob.glob(os.path.join(test_directory_for_tool,
+ '*-original.cc'))
+ actual_files = ['-'.join([source_file.rsplit('-', 2)[0], 'actual.cc'])
+ for source_file in source_files]
+ expected_files = ['-'.join([source_file.rsplit('-', 2)[0], 'expected.cc'])
+ for source_file in source_files]
+
+ try:
+ # Set up the test environment.
+ for source, actual in zip(source_files, actual_files):
+ shutil.copyfile(source, actual)
+ # Stage the test files in the git index. If they aren't staged, then
+ # run_tools.py will skip them when applying replacements.
+ args = ['git', 'add']
+ args.extend(actual_files)
+ subprocess.check_call(args)
+ # Generate a temporary compilation database to run the tool over.
+ with open(compile_database, 'w') as f:
+ f.write(_GenerateCompileCommands(actual_files))
+
+ args = ['python',
+ os.path.join(tools_clang_scripts_directory, 'run_tool.py'),
+ tool_to_test,
+ test_directory_for_tool]
+ args.extend(actual_files)
+ run_tool = subprocess.Popen(args, stdout=subprocess.PIPE)
+ stdout, _ = run_tool.communicate()
+ if run_tool.returncode != 0:
+ print 'run_tool failed:\n%s' % stdout
+ sys.exit(1)
+
+ passed = 0
+ failed = 0
+ for expected, actual in zip(expected_files, actual_files):
+ print '[ RUN ] %s' % os.path.relpath(actual)
+ expected_output = actual_output = None
+ with open(expected, 'r') as f:
+ expected_output = f.readlines()
+ with open(actual, 'r') as f:
+ actual_output = f.readlines()
+ if actual_output != expected_output:
+ print '[ FAILED ] %s' % os.path.relpath(actual)
+ failed += 1
+ for line in difflib.unified_diff(expected_output, actual_output,
+ fromfile=os.path.relpath(expected),
+ tofile=os.path.relpath(actual)):
+ sys.stdout.write(line)
+ # Don't clean up the file on failure, so the results can be referenced
+ # more easily.
+ continue
+ print '[ OK ] %s' % os.path.relpath(actual)
+ passed += 1
+ os.remove(actual)
+
+ if failed == 0:
+ os.remove(compile_database)
+
+ print '[==========] %s ran.' % _NumberOfTestsToString(len(source_files))
+ if passed > 0:
+ print '[ PASSED ] %s.' % _NumberOfTestsToString(passed)
+ if failed > 0:
+ print '[ FAILED ] %s.' % _NumberOfTestsToString(failed)
+ finally:
+ # No matter what, unstage the git changes we made earlier to avoid polluting
+ # the index.
+ args = ['git', 'reset', '--quiet', 'HEAD']
+ args.extend(actual_files)
+ subprocess.call(args)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/clang/scripts/update.sh b/tools/clang/scripts/update.sh
index ed316be..0a33894 100755
--- a/tools/clang/scripts/update.sh
+++ b/tools/clang/scripts/update.sh
@@ -40,6 +40,8 @@ with_android=yes
# Mac builders.
is_asan_mac_builder_hackfix=
with_tools_extra=
+chrome_tools="plugins"
+
if [[ "${OS}" = "Darwin" ]]; then
with_android=
fi
@@ -69,6 +71,14 @@ while [[ $# > 0 ]]; do
--with-tools-extra)
with_tools_extra=yes
;;
+ --with-chrome-tools)
+ shift
+ if [[ $# == 0 ]]; then
+ echo "--with-chrome-tools requires an argument."
+ exit 1
+ fi
+ chrome_tools=$1
+ ;;
--help)
echo "usage: $0 [--force-local-build] [--mac-only] [--run-tests] "
echo "--bootstrap: First build clang with CC, then with itself."
@@ -79,6 +89,10 @@ while [[ $# > 0 ]]; do
echo "--is-asan-mac-builder-hackfix: Use older Clang" \
"to build ASan on Mac."
echo "--with-tools-extra: Also build the clang-tools-extra repository."
+ echo "--with-chrome-tools: Select which chrome tools to build." \
+ "Defaults to plugins."
+ echo " Example: --with-chrome-tools 'plugins empty-string'"
+ echo
exit 1
;;
esac
@@ -375,10 +389,9 @@ fi
# Build Chrome-specific clang tools. Paths in this list should be relative to
# tools/clang.
-CHROME_TOOL_DIRS="plugins"
# For each tool directory, copy it into the clang tree and use clang's build
# system to compile it.
-for CHROME_TOOL_DIR in ${CHROME_TOOL_DIRS}; do
+for CHROME_TOOL_DIR in ${chrome_tools}; do
TOOL_SRC_DIR="${THIS_DIR}/../${CHROME_TOOL_DIR}"
TOOL_DST_DIR="${LLVM_DIR}/tools/clang/tools/chrome-${CHROME_TOOL_DIR}"
TOOL_BUILD_DIR="${LLVM_BUILD_DIR}/tools/clang/tools/chrome-${CHROME_TOOL_DIR}"