Files
carbon-lang/scripts/create_compdb.py
T
Chandler CarruthandJon Meow 05261b7fe7 Remove the LLVM bootstrap and use Hombrew installed LLVM-12. (#551)
Now that LLVM 12 has been released we no longer have any need to
bootstrap LLVM to get the desired featureset. LLVM 12 is available
widely, including in Homebrew across multiple platforms and in the
GitHub action runners.

Sadly, the Linux distribution builds of LLVM-12 are largely broken and
not as useful for us. The Homebrew Linux install was also broken
originally, but I've worked extensively with the Homebrew folks to get
the Linux install into a really good shape. It should now work reliably.

There are two primary bugs in Linux LLVM packages that need to be fixed
before we can just use them:

- https://bugs.llvm.org/show_bug.cgi?id=43604
- https://bugs.llvm.org/show_bug.cgi?id=46321

Once those are addressed and point releases with the fixes widely
available we can further simplify things.

Even with the need to use Homebrew installs, using the released LLVM has
the extra advantage of making it easy to properly support Darwin ARM and
I've added that configuration so that I can test things there.

Last but not least, this will significantly shrink our build outputs
which should allow building much more in continuous integration on
GitHub actions without exceeding the action cache size limits. I've even
added several tweaks and adjustments to the compile and build flags to
improve the build performance and reduce the build output size.

Once this is landed and stable, we can consider adding the refactoring
tooling back to our CI.

One of the biggest downsides of this path is that our CI has to download
and install the LLVM toolchain from Homebrew on each run. This is pretty
slow (takes a couple of minutes). But it is a fixed overhead -- it won't
get worse over time. Eventually, we can either look at a much fancier
action configuration to avoid this or hopefully the Debian packages will
get updated and we can move back to those.

The bootstrapping has served us long enough at this point. We can
resurrect it if we ever find a compelling reason for breaking off of the
latest LLVM release as our host toolchain.

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
2021-06-09 10:00:46 -07:00

159 lines
5.4 KiB
Python
Executable File

#!/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 = ["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",
# Workaround for https://github.com/bazelbuild/bazel/issues/8900
"--incompatible_display_source_file_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)