|
|
@@ -0,0 +1,156 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+
|
|
|
+"""Create a compilation database for Clang tools like `clangd`.
|
|
|
+
|
|
|
+If you want `clangd` to be able to index this project, run this script from
|
|
|
+the workspace root to generate a rich compilation database. After the first
|
|
|
+run, you should only need to run it if you encounter `clangd` problems, or if
|
|
|
+you want `clangd` to build an up-to-date index of the entire project. Note
|
|
|
+that in the latter case you may need to manually clear and rebuild clangd's
|
|
|
+index after running this script.
|
|
|
+
|
|
|
+Note that this script will build generated files in the Carbon project and
|
|
|
+otherwise touch the Bazel build. It works to do the minimum amount necessary.
|
|
|
+Once setup, generally subsequent builds, even of small parts of the project,
|
|
|
+different configurations, or that hit errors won't disrupt things. But, if
|
|
|
+you do hit errors, you can get things back to a good state by fixing the
|
|
|
+build of generated files and re-running this script.
|
|
|
+"""
|
|
|
+
|
|
|
+__copyright__ = """
|
|
|
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
|
+Exceptions. See /LICENSE for license information.
|
|
|
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
+"""
|
|
|
+
|
|
|
+import json
|
|
|
+import os
|
|
|
+import re
|
|
|
+import shutil
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+from pathlib import Path
|
|
|
+
|
|
|
+# Change the working directory to the repository root so that the remaining
|
|
|
+# operations reliably operate relative to that root.
|
|
|
+os.chdir(Path(__file__).parent.parent)
|
|
|
+directory = Path.cwd()
|
|
|
+
|
|
|
+# We use the `BAZEL` environment variable if present. If not, then we try to
|
|
|
+# use `bazelisk` and then `bazel`.
|
|
|
+bazel = os.environ.get("BAZEL")
|
|
|
+if not bazel:
|
|
|
+ bazel = "bazelisk"
|
|
|
+ if not shutil.which(bazel):
|
|
|
+ bazel = "bazel"
|
|
|
+ if not shutil.which(bazel):
|
|
|
+ sys.exit("Unable to run Bazel")
|
|
|
+
|
|
|
+# Load compiler flags. We do this first in order to fail fast if not run from
|
|
|
+# the workspace root.
|
|
|
+print("Reading the arguments to use...")
|
|
|
+try:
|
|
|
+ with open("compile_flags.txt") as flag_file:
|
|
|
+ arguments = [line.strip() for line in flag_file]
|
|
|
+except FileNotFoundError:
|
|
|
+ sys.exit(Path(sys.argv[0]).name + " must be run from the project root")
|
|
|
+
|
|
|
+# Prepend the `clang` executable path to the arguments that looks into our
|
|
|
+# downloaded Clang toolchain.
|
|
|
+arguments = [str(Path("bazel-clang-toolchain/bin/clang"))] + arguments
|
|
|
+
|
|
|
+print("Building compilation database...")
|
|
|
+
|
|
|
+# Find all of the C++ source files that we expect to compile cleanly as
|
|
|
+# stand-alone files. This is a bit simpler than scraping the actual compile
|
|
|
+# actions and allows us to directly index header-only libraries easily and
|
|
|
+# pro-actively index the specific headers in the project.
|
|
|
+source_files_query = subprocess.run(
|
|
|
+ [
|
|
|
+ bazel,
|
|
|
+ "query",
|
|
|
+ "--keep_going",
|
|
|
+ "--output=location",
|
|
|
+ 'filter(".*\\.(h|cpp|cc|c|cxx)$", kind("source file", deps(//...)))',
|
|
|
+ ],
|
|
|
+ check=True,
|
|
|
+ stdout=subprocess.PIPE,
|
|
|
+ stderr=subprocess.DEVNULL,
|
|
|
+ universal_newlines=True,
|
|
|
+).stdout
|
|
|
+source_files = [
|
|
|
+ Path(line.split(":")[0]) for line in source_files_query.splitlines()
|
|
|
+]
|
|
|
+
|
|
|
+# Filter into the Carbon source files that we'll find directly in the
|
|
|
+# workspace, and LLVM source files that need to be mapped through the merged
|
|
|
+# LLVM tree in Bazel's execution root.
|
|
|
+carbon_files = [
|
|
|
+ f.relative_to(directory)
|
|
|
+ for f in source_files
|
|
|
+ if f.parts[: len(directory.parts)] == directory.parts
|
|
|
+]
|
|
|
+llvm_files = [
|
|
|
+ Path("bazel-execroot/external").joinpath(
|
|
|
+ *f.parts[f.parts.index("llvm-project") :]
|
|
|
+ )
|
|
|
+ for f in source_files
|
|
|
+ if "llvm-project" in f.parts
|
|
|
+]
|
|
|
+print(
|
|
|
+ "Found %d Carbon source files and %d LLVM source files..."
|
|
|
+ % (len(carbon_files), len(llvm_files))
|
|
|
+)
|
|
|
+
|
|
|
+# Now collect the generated file labels.
|
|
|
+generated_file_labels = subprocess.run(
|
|
|
+ [
|
|
|
+ bazel,
|
|
|
+ "query",
|
|
|
+ "--keep_going",
|
|
|
+ "--output=label",
|
|
|
+ (
|
|
|
+ 'filter(".*\\.(h|cpp|cc|c|cxx|def|inc)$",'
|
|
|
+ 'kind("generated file", deps(//...)))'
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ check=True,
|
|
|
+ stdout=subprocess.PIPE,
|
|
|
+ stderr=subprocess.DEVNULL,
|
|
|
+ universal_newlines=True,
|
|
|
+).stdout.splitlines()
|
|
|
+print("Found %d generated files..." % (len(generated_file_labels),))
|
|
|
+
|
|
|
+# Directly build these labels so that indexing can find them. Allow this to
|
|
|
+# fail in case there are build errors in the client, and just warn the user
|
|
|
+# that they may be missing generated files.
|
|
|
+print("Building the generated files so that tools can find them...")
|
|
|
+subprocess.run([bazel, "build", "--keep_going"] + generated_file_labels)
|
|
|
+
|
|
|
+
|
|
|
+# Manually translate the label to a user friendly path into the Bazel output
|
|
|
+# symlinks.
|
|
|
+def _label_to_path(s):
|
|
|
+ # Map external repositories to their part of the output tree.
|
|
|
+ s = re.sub(r"^@([^/]+)//", r"bazel-bin/external/\1/", s)
|
|
|
+ # Map this repository to the root of the output tree.
|
|
|
+ s = s if not s.startswith("//") else "bazel-bin/" + s[len("//") :]
|
|
|
+ # Replace the colon used to mark the package name with a slash.
|
|
|
+ s = s.replace(":", "/")
|
|
|
+ # Convert to a native path.
|
|
|
+ return Path(s)
|
|
|
+
|
|
|
+
|
|
|
+generated_files = [_label_to_path(label) for label in generated_file_labels]
|
|
|
+
|
|
|
+# Generate compile_commands.json with an entry for each C++ input.
|
|
|
+entries = [
|
|
|
+ {
|
|
|
+ "directory": str(directory),
|
|
|
+ "file": str(f),
|
|
|
+ "arguments": arguments + [str(f)],
|
|
|
+ }
|
|
|
+ for f in carbon_files + llvm_files + generated_files
|
|
|
+]
|
|
|
+with open("compile_commands.json", "w") as json_file:
|
|
|
+ json.dump(entries, json_file, indent=2)
|