|
4 | 4 | # found in the LICENSE file.
|
5 | 5 | '''Builds the Crubit tool.
|
6 | 6 |
|
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. |
12 | 23 | '''
|
13 | 24 |
|
14 | 25 | import argparse
|
15 |
| -import collections |
16 |
| -import hashlib |
17 | 26 | import os
|
18 | 27 | import platform
|
19 | 28 | import shutil
|
20 |
| -import stat |
21 |
| -import string |
22 |
| -import subprocess |
23 | 29 | import sys
|
24 | 30 |
|
25 | 31 | from pathlib import Path
|
|
29 | 35 | os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'clang',
|
30 | 36 | 'scripts'))
|
31 | 37 |
|
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', |
135 | 74 | ]
|
136 |
| - RunCommand(args + extra_args, env=env, cwd=CRUBIT_SRC_DIR) |
| 75 | + if not debug: |
| 76 | + configure_cmd.append('-DCMAKE_BUILD_TYPE=Release') |
137 | 77 |
|
| 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) |
138 | 83 |
|
139 |
| -def InstallCrubit(install_dir): |
140 |
| - assert os.path.isdir(install_dir) |
| 84 | + os.chdir(CHROMIUM_DIR) |
141 | 85 |
|
142 |
| - print('Installing crubit binaries to %s' % install_dir) |
143 | 86 |
|
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) |
149 | 89 |
|
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'] |
153 | 91 |
|
| 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) |
154 | 98 |
|
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)) |
157 | 106 |
|
| 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) |
158 | 112 |
|
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) |
176 | 114 |
|
177 | 115 |
|
178 | 116 | def main():
|
179 | 117 | parser = argparse.ArgumentParser(
|
180 | 118 | 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') |
193 | 119 | parser.add_argument(
|
194 | 120 | '--skip-checkout',
|
195 | 121 | 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')) |
197 | 127 | args, rest = parser.parse_known_args()
|
| 128 | + assert (not rest) |
198 | 129 |
|
199 | 130 | 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"]}' |
201 | 153 |
|
202 |
| - try: |
203 |
| - if not args.skip_clean: |
204 |
| - CleanBazel() |
| 154 | + env['CXXFLAGS'] = '' |
| 155 | + env['RUSTFLAGS'] = '' |
205 | 156 |
|
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' |
207 | 175 |
|
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) |
212 | 209 |
|
213 | 210 | return 0
|
214 | 211 |
|
|
0 commit comments