diff options
-rw-r--r-- | tools/clang/empty_string/EmptyStringConverter.cpp | 196 | ||||
-rw-r--r-- | tools/clang/empty_string/Makefile | 23 | ||||
-rw-r--r-- | tools/clang/empty_string/tests/test-expected.cc | 38 | ||||
-rw-r--r-- | tools/clang/empty_string/tests/test-original.cc | 38 | ||||
-rwxr-xr-x | tools/clang/scripts/run_tool.py | 282 | ||||
-rwxr-xr-x | tools/clang/scripts/test_tool.py | 114 | ||||
-rwxr-xr-x | tools/clang/scripts/update.sh | 17 |
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}" |