Skip to content

Commit ac0db92

Browse files
danakjcopybara-github
authored andcommitted
Build Crubit's rs_bindings_from_cc when building the Rust toolchain
We use the new cargo-based build for Crubit. The build is allowed to fail still, without causing the package to fail, since we need to wait until we can rely on the cargo-based Crubit build to remain green and working with the latest Clang and Rust revisions. Bug: 40226863 Change-Id: I43134d740729e8d88dd2448a0adf3fbef6179808 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5888109 Commit-Queue: danakj <danakj@chromium.org> Reviewed-by: Łukasz Anforowicz <lukasza@chromium.org> Cr-Commit-Position: refs/heads/main@{#1362506} NOKEYCHECK=True GitOrigin-RevId: 9a318f60c017464420bb99359a294b5007e13664
1 parent bc7804b commit ac0db92

File tree

3 files changed

+177
-195
lines changed

3 files changed

+177
-195
lines changed

build_crubit.py

Lines changed: 163 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@
44
# found in the LICENSE file.
55
'''Builds the Crubit tool.
66
7-
!!! DO NOT USE IN PRODUCTION
8-
Builds the Crubit tool (an experiment for Rust/C++ FFI bindings generation).
9-
10-
This script clones the Crubit repository, checks it out to a defined revision,
11-
and then uses Bazel to build Crubit.
7+
Builds the Crubit tools for generating Rust/C++ bindings.
8+
9+
This script must be run after //tools/rust/build_rust.py as it uses the outputs
10+
of that script in the compilation of Crubit. It uses:
11+
- The LLVM and Clang libraries and headers in `RUST_HOST_LLVM_INSTALL_DIR`.
12+
- The rust toolchain binaries and libraries in `RUST_TOOLCHAIN_OUT_DIR`.
13+
14+
This script:
15+
- Clones the Abseil repository, checks out a defined revision.
16+
- Builds Abseil with Cmake.
17+
- Clones the Crubit repository, checks out a defined revision.
18+
- Builds Crubit's rs_bindings_from_cc with Cargo.
19+
- Adds rs_bindings_from_cc and the Crubit support libraries into the
20+
toolchain package in `RUST_TOOLCHAIN_OUT_DIR`.
21+
22+
The cc_bindings_from_rs binary is not yet built, as there's no Cargo rules to build it yet.
1223
'''
1324

1425
import argparse
15-
import collections
16-
import hashlib
1726
import os
1827
import platform
1928
import shutil
20-
import stat
21-
import string
22-
import subprocess
2329
import sys
2430

2531
from pathlib import Path
@@ -29,186 +35,177 @@
2935
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'clang',
3036
'scripts'))
3137

32-
from update import (CLANG_REVISION, CLANG_SUB_REVISION, LLVM_BUILD_DIR)
33-
from build import (LLVM_BOOTSTRAP_INSTALL_DIR, DownloadDebianSysroot)
34-
35-
from update_rust import (CHROMIUM_DIR, CRUBIT_REVISION, THIRD_PARTY_DIR)
36-
37-
BAZEL_DIR = os.path.join(CHROMIUM_DIR, 'tools', 'bazel')
38-
CRUBIT_SRC_DIR = os.path.join(THIRD_PARTY_DIR, 'crubit', 'src')
39-
40-
41-
def BazelExe():
42-
if sys.platform == 'darwin':
43-
if platform.machine() == 'arm64':
44-
return os.path.join(BAZEL_DIR, 'mac-arm64', 'bazel')
45-
else:
46-
return os.path.join(BAZEL_DIR, 'mac-amd64', 'bazel')
47-
elif sys.platform == 'win32':
48-
return os.path.join(BAZEL_DIR, 'windows-amd64', 'bazel.exe')
49-
else:
50-
return os.path.join(BAZEL_DIR, 'linux-amd64', 'bazel')
51-
52-
53-
def RunCommand(command, env=None, cwd=None, fail_hard=True):
54-
print('Running', command)
55-
if subprocess.run(command, env=env, cwd=cwd,
56-
shell=sys.platform == 'win32').returncode == 0:
57-
return True
58-
print('Failed.')
59-
if fail_hard:
60-
raise RuntimeError(f"Failed to run {command}")
61-
return False
62-
63-
64-
def CheckoutCrubit(commit, dir):
65-
"""Checkout the Crubit repo at a certain git commit in dir. Any local
66-
modifications in dir will be lost."""
67-
68-
print('Checking out crubit repo %s into %s' % (commit, dir))
69-
70-
# Try updating the current repo if it exists and has no local diff.
71-
if os.path.isdir(dir):
72-
os.chdir(dir)
73-
# git diff-index --quiet returns success when there is no diff.
74-
# Also check that the first commit is reachable.
75-
if (RunCommand(['git', 'diff-index', '--quiet', 'HEAD'],
76-
fail_hard=False)
77-
and RunCommand(['git', 'fetch'], fail_hard=False)
78-
and RunCommand(['git', 'checkout', commit], fail_hard=False)):
79-
return
80-
81-
# If we can't use the current repo, delete it.
82-
os.chdir(CHROMIUM_DIR) # Can't remove dir if we're in it.
83-
print('Removing %s.' % dir)
84-
RmTree(dir)
85-
86-
clone_cmd = ['git', 'clone', 'https://github.com/google/crubit.git', dir]
87-
88-
if RunCommand(clone_cmd, fail_hard=False):
89-
os.chdir(dir)
90-
if RunCommand(['git', 'checkout', commit], fail_hard=False):
91-
return
92-
93-
print('CheckoutCrubit failed.')
94-
sys.exit(1)
95-
96-
97-
def BuildCrubit():
98-
# TODO(crbug.com/40229251): Use locally built Rust instead of having
99-
# Bazel always download the whole Rust toolchain from the internet.
100-
101-
# This environment variable is consumed by crubit/bazel/llvm.bzl and will
102-
# configure Crubit's build to include and link against LLVM+Clang headers
103-
# and libraries built when building Chromium toolchain. (Instead of
104-
# downloading LLVM+Clang and building it during Crubit build.)
105-
env = {"LLVM_INSTALL_PATH": LLVM_BOOTSTRAP_INSTALL_DIR}
106-
107-
# Use the compiler and linker from `LLVM_BUILD_DIR`.
108-
#
109-
# Note that we use `bin/clang` from `LLVM_BUILD_DIR`, but depend on headers
110-
# and libraries from `LLVM_BOOTSTRAP_INSTALL_DIR`. The former helps ensure
111-
# that we use the same compiler as the final one used elsewhere in Chromium.
112-
# The latter is needed, because the headers+libraries are not available
113-
# anywhere else.
114-
clang_path = os.path.join(LLVM_BUILD_DIR, "bin", "clang")
115-
env["CXX"] = f"{clang_path}++"
116-
env["LD"] = f"{clang_path}++"
117-
# CC is set via `--repo_env` rather than via `env` to ensure that we
118-
# override the defaults from `crubit/.bazelrc`.
119-
extra_args = [
120-
"--repo_env=CC=", # Unset/ignore the value set via crubit/.bazelrc
121-
f"--repo_env=CC={clang_path}",
122-
]
123-
124-
if sys.platform.startswith('linux'):
125-
# Include and link against the C++ stdlib from the sysroot.
126-
sysroot = DownloadDebianSysroot('amd64')
127-
sysroot_flag = (f'--sysroot={sysroot}' if sysroot else '')
128-
env["BAZEL_CXXOPTS"] = sysroot_flag
129-
env["BAZEL_LINKOPTS"] = f"{sysroot_flag}:-static-libstdc++"
130-
env["BAZEL_LINKLIBS"] = f"-lm"
131-
132-
# Run bazel build ...
133-
args = [
134-
BazelExe(), "build", "rs_bindings_from_cc:rs_bindings_from_cc_impl"
38+
from build import (AddCMakeToPath, AddZlibToPath, CheckoutGitRepo,
39+
DownloadDebianSysroot, RunCommand, THIRD_PARTY_DIR)
40+
from update import (RmTree)
41+
42+
from build_rust import (RUST_HOST_LLVM_INSTALL_DIR)
43+
from update_rust import (CHROMIUM_DIR, ABSL_REVISION, CRUBIT_REVISION,
44+
RUST_TOOLCHAIN_OUT_DIR)
45+
46+
ABSL_GIT = 'https://github.com/abseil/abseil-cpp'
47+
CRUBIT_GIT = 'https://github.com/google/crubit'
48+
49+
ABSL_SRC_DIR = os.path.join(CHROMIUM_DIR, 'third_party',
50+
'rust-toolchain-intermediate', 'absl')
51+
ABSL_INSTALL_DIR = os.path.join(ABSL_SRC_DIR, 'install')
52+
CRUBIT_SRC_DIR = os.path.join(CHROMIUM_DIR, 'third_party',
53+
'rust-toolchain-intermediate', 'crubit')
54+
55+
EXE = '.exe' if sys.platform == 'win32' else ''
56+
57+
58+
def BuildAbsl(env, debug):
59+
os.chdir(ABSL_SRC_DIR)
60+
61+
configure_cmd = [
62+
'cmake',
63+
'-B',
64+
'out',
65+
'-GNinja',
66+
# Because Crubit is built with C++20.
67+
'-DCMAKE_CXX_STANDARD=20',
68+
f'-DCMAKE_INSTALL_PREFIX={ABSL_INSTALL_DIR}',
69+
'-DABSL_PROPAGATE_CXX_STD=ON',
70+
'-DABSL_BUILD_TESTING=OFF',
71+
'-DABSL_USE_GOOGLETEST_HEAD=OFF',
72+
# LLVM is built with static CRT. Make Abseil match it.
73+
'-DABSL_MSVC_STATIC_RUNTIME=ON',
13574
]
136-
RunCommand(args + extra_args, env=env, cwd=CRUBIT_SRC_DIR)
75+
if not debug:
76+
configure_cmd.append('-DCMAKE_BUILD_TYPE=Release')
13777

78+
RunCommand(configure_cmd, setenv=True, env=env)
79+
build_cmd = ['cmake', '--build', 'out', '--target', 'all']
80+
RunCommand(build_cmd, setenv=True, env=env)
81+
install_cmd = ['cmake', '--install', 'out']
82+
RunCommand(install_cmd, setenv=True, env=env)
13883

139-
def InstallCrubit(install_dir):
140-
assert os.path.isdir(install_dir)
84+
os.chdir(CHROMIUM_DIR)
14185

142-
print('Installing crubit binaries to %s' % install_dir)
14386

144-
BAZEL_BIN_DIR = os.path.join(CRUBIT_SRC_DIR, "bazel-bin")
145-
SOURCE_PATH = os.path.join(BAZEL_BIN_DIR, "rs_bindings_from_cc",
146-
"rs_bindings_from_cc_impl")
147-
TARGET_PATH = os.path.join(install_dir, "rs_bindings_from_cc")
148-
shutil.copyfile(SOURCE_PATH, TARGET_PATH)
87+
def BuildCrubit(env, debug):
88+
os.chdir(CRUBIT_SRC_DIR)
14989

150-
# Change from r-xr-xr-x to rwxrwxr-x, so that future copies will work fine.
151-
os.chmod(TARGET_PATH,
152-
stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH)
90+
CRUBIT_BINS = ['rs_bindings_from_cc']
15391

92+
build_cmd = ['cargo', 'build']
93+
for bin in CRUBIT_BINS:
94+
build_cmd += ['--bin', bin]
95+
if not debug:
96+
build_cmd.append('--release')
97+
RunCommand(build_cmd, setenv=True, env=env)
15498

155-
def CleanBazel():
156-
RunCommand([BazelExe(), "clean", "--expunge"], cwd=CRUBIT_SRC_DIR)
99+
print(f'Installing Crubit to {RUST_TOOLCHAIN_OUT_DIR} ...')
100+
target_dir = os.path.join(CRUBIT_SRC_DIR, 'target',
101+
'debug' if debug else 'release')
102+
for bin in CRUBIT_BINS:
103+
bin = bin + EXE
104+
shutil.copy(os.path.join(target_dir, bin),
105+
os.path.join(RUST_TOOLCHAIN_OUT_DIR, 'bin', bin))
157106

107+
support_build_dir = os.path.join(CRUBIT_SRC_DIR, 'support')
108+
support_out_dir = os.path.join(RUST_TOOLCHAIN_OUT_DIR, 'lib', 'crubit')
109+
if os.path.exists(support_out_dir):
110+
RmTree(support_out_dir)
111+
shutil.copytree(support_build_dir, support_out_dir)
158112

159-
def ShutdownBazel():
160-
RunCommand([BazelExe(), "shutdown"], cwd=CRUBIT_SRC_DIR)
161-
162-
163-
def WritableDir(d):
164-
""" Utility function to use as `argparse` `type` to verify that the argument
165-
is a writeable dir (and resolve it as an absolute path). """
166-
167-
try:
168-
real_d = os.path.realpath(d)
169-
except Exception as e:
170-
raise ArgumentTypeError(f"realpath failed: {e}")
171-
if not os.path.isdir(real_d):
172-
raise ArgumentTypeError(f"Not a directory: {d}")
173-
if not os.access(real_d, os.W_OK):
174-
raise ArgumentTypeError(f"Cannot write to: {d}")
175-
return real_d
113+
os.chdir(CHROMIUM_DIR)
176114

177115

178116
def main():
179117
parser = argparse.ArgumentParser(
180118
description='Build and package Crubit tools')
181-
parser.add_argument('-v',
182-
'--verbose',
183-
action='count',
184-
help='run subcommands with verbosity')
185-
parser.add_argument(
186-
'--install-to',
187-
type=WritableDir,
188-
help='skip Crubit git checkout. Useful for trying local changes')
189-
parser.add_argument(
190-
'--skip-clean',
191-
action='store_true',
192-
help='skip cleanup. Useful for retrying/rebuilding local changes')
193119
parser.add_argument(
194120
'--skip-checkout',
195121
action='store_true',
196-
help='skip Crubit git checkout. Useful for trying local changes')
122+
help=('skip checking out source code. Useful for trying local'
123+
'changes'))
124+
parser.add_argument('--debug',
125+
action='store_true',
126+
help=('build Crubit in debug mode'))
197127
args, rest = parser.parse_known_args()
128+
assert (not rest)
198129

199130
if not args.skip_checkout:
200-
CheckoutCrubit(CRUBIT_REVISION, CRUBIT_SRC_DIR)
131+
CheckoutGitRepo("absl", ABSL_GIT, ABSL_REVISION, ABSL_SRC_DIR)
132+
CheckoutGitRepo("crubit", CRUBIT_GIT, CRUBIT_REVISION, CRUBIT_SRC_DIR)
133+
if sys.platform.startswith('linux'):
134+
arch = 'arm64' if platform.machine() == 'aarch64' else 'amd64'
135+
sysroot = DownloadDebianSysroot(arch, args.skip_checkout)
136+
137+
llvm_bin_dir = os.path.join(RUST_HOST_LLVM_INSTALL_DIR, 'bin')
138+
rust_bin_dir = os.path.join(RUST_TOOLCHAIN_OUT_DIR, 'bin')
139+
140+
AddCMakeToPath()
141+
142+
env = os.environ
143+
144+
path_trailing_sep = os.pathsep if env['PATH'] else ''
145+
env['PATH'] = (f'{llvm_bin_dir}{os.pathsep}'
146+
f'{rust_bin_dir}{path_trailing_sep}'
147+
f'{env["PATH"]}')
148+
149+
if sys.platform == 'win32':
150+
# CMake on Windows doesn't like depot_tools's ninja.bat wrapper.
151+
ninja_dir = os.path.join(THIRD_PARTY_DIR, 'ninja')
152+
env['PATH'] = f'{ninja_dir}{os.pathsep}{env["PATH"]}'
201153

202-
try:
203-
if not args.skip_clean:
204-
CleanBazel()
154+
env['CXXFLAGS'] = ''
155+
env['RUSTFLAGS'] = ''
205156

206-
BuildCrubit()
157+
if sys.platform == 'win32':
158+
env['CC'] = 'clang-cl'
159+
env['CXX'] = 'clang-cl'
160+
else:
161+
env['CC'] = 'clang'
162+
env['CXX'] = 'clang++'
163+
164+
# We link with lld via clang, except on windows where we point to lld-link
165+
# directly.
166+
if sys.platform == 'win32':
167+
env['RUSTFLAGS'] += f' -Clinker=lld-link'
168+
else:
169+
env['RUSTFLAGS'] += f' -Clinker=clang'
170+
env['RUSTFLAGS'] += f' -Clink-arg=-fuse-ld=lld'
171+
172+
if sys.platform == 'win32':
173+
# LLVM is built with static CRT. Make Rust match it.
174+
env['RUSTFLAGS'] += f' -Ctarget-feature=+crt-static'
207175

208-
if args.install_to:
209-
InstallCrubit(args.install_to)
210-
finally:
211-
ShutdownBazel()
176+
if sys.platform.startswith('linux'):
177+
sysroot_flag = (f'--sysroot={sysroot}' if sysroot else '')
178+
env['CXXFLAGS'] += f" {sysroot_flag}"
179+
env['RUSTFLAGS'] += f" -Clink-arg={sysroot_flag}"
180+
181+
if sys.platform == 'darwin':
182+
import subprocess
183+
# The system/xcode compiler would find system SDK correctly, but
184+
# the Clang we've built does not. See
185+
# https://github.com/llvm/llvm-project/issues/45225
186+
sdk_path = subprocess.check_output(['xcrun', '--show-sdk-path'],
187+
text=True).rstrip()
188+
env['CXXFLAGS'] += f' -isysroot {sdk_path}'
189+
env['RUSTFLAGS'] += f' -Clink-arg=-isysroot -Clink-arg={sdk_path}'
190+
191+
if sys.platform == 'win32':
192+
# LLVM depends on Zlib.
193+
zlib_dir = AddZlibToPath(dry_run=args.skip_checkout)
194+
env['CXXFLAGS'] += f' /I{zlib_dir}'
195+
env['RUSTFLAGS'] += f' -Clink-arg=/LIBPATH:{zlib_dir}'
196+
# Prevent deprecation warnings.
197+
env['CXXFLAGS'] += ' /D_CRT_SECURE_NO_DEPRECATE'
198+
199+
BuildAbsl(env, args.debug)
200+
201+
env['ABSL_INCLUDE_PATH'] = os.path.join(ABSL_INSTALL_DIR, 'include')
202+
env['ABSL_LIB_STATIC_PATH'] = os.path.join(ABSL_INSTALL_DIR, 'lib')
203+
env['CLANG_INCLUDE_PATH'] = os.path.join(RUST_HOST_LLVM_INSTALL_DIR,
204+
'include')
205+
env['CLANG_LIB_STATIC_PATH'] = os.path.join(RUST_HOST_LLVM_INSTALL_DIR,
206+
'lib')
207+
208+
BuildCrubit(env, args.debug)
212209

213210
return 0
214211

0 commit comments

Comments
 (0)