blob: ddfb655d93b1d237d96fd98422faec4703abb3c2 [file] [log] [blame]
Trent Apted7d2777b2023-06-29 13:35:03 +10001# Copyright 2023 The ChromiumOS Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""build_sdk_subtools rebuilds binary packages exported by the subtools builder.
6
7The build_sdk_subtools process takes (copies) an amd64-host base SDK, compiles
8and installs additional packages needed by subtools, then creates relocatable
9binary subtool bundles that can be consumed by other build hosts and developer
10machines.
11
12If build_sdk_subtools has already been invoked for the provided chroot, all
13non-toolchain packages in the subtools deptree that have updated revisions or
14changed USE flags will be rebuilt, along with reverse dependencies.
15
16Packages (e.g. an ebuild) provide manifests that describes how files, once
17installed, are to be bundled and exported.
18
19If packages are specified in the command line, only consider the deptree from
20those specific packages rather than all of virtual/target-sdk-subtools.
21"""
22
23import argparse
24import logging
25import os
26from pathlib import Path
27import shutil
28import sys
29from typing import Dict, List, Optional, Protocol, Union
30
31from chromite.lib import build_target_lib
32from chromite.lib import commandline
33from chromite.lib import constants
34from chromite.lib import cros_build_lib
35from chromite.lib import cros_sdk_lib
36from chromite.lib import osutils
37from chromite.lib import portage_util
38from chromite.lib import sysroot_lib
39from chromite.service import sysroot
40
41
42assert sys.version_info >= (3, 8), "build_sdk_subtools uses Python 3.8 features"
43
44
45# Affects where building occurs (e.g. /build/amd64-subtools-host) if not
46# overridden by --output-dir. Note this will be a chroot.
47SUBTOOLS_OUTPUT_DIR = "amd64-subtools-host"
48
49# Version file that identifies a chroot setup as a subtools chroot.
50SUBTOOLS_CHROOT_VERSION_FILE = Path("/etc/cros_subtools_chroot_version")
51
52# Packages that the subtools builder should never rebuild. This is a superset of
53# sysroot._CRITICAL_SDK_PACKAGES.
54EXCLUDE_PACKAGES = (
55 "dev-embedded/hps-sdk",
56 "dev-lang/rust",
57 "dev-lang/go",
58 "sys-libs/glibc",
59 "sys-devel/gcc",
60 "sys-devel/binutils",
61)
62
63# Flag passed to subprocesses in chroots that might not yet be set up as a
64# subtools chroot.
65_RELAUNCH_FOR_SETUP_FLAG = "--relaunch-for-setup"
66
67
68class Options(Protocol):
69 """Protocol to formalize commandline arguments."""
70
71 build_run_config: sysroot.BuildPackagesRunConfig
72 clean: bool
73 setup_chroot: bool
74 update_packages: bool
75 relaunch_for_setup: bool
76 output_dir: Path
77 packages: List[str]
78 jobs: int
79
80 def Freeze(self) -> None:
81 pass
82
83
84def get_parser() -> commandline.ArgumentParser:
85 """Returns the cmdline argparser, populates the options and descriptions."""
86 parser = commandline.ArgumentParser(description=__doc__)
87
Trent Apted9b8b13d2023-08-04 16:49:04 +100088 parser.add_bool_argument(
89 "--clean",
Trent Apted7d2777b2023-06-29 13:35:03 +100090 False,
91 "Remove the subtools chroot and re-extract the SDK.",
92 "Re-use an existing subtools chroot.",
93 )
94
Trent Apted9b8b13d2023-08-04 16:49:04 +100095 parser.add_bool_argument(
96 "--setup-chroot",
Trent Apted7d2777b2023-06-29 13:35:03 +100097 True,
98 "Look for a newer base SDK and set it up as a subtools SDK.",
99 "Don't look for a newer base SDK and assume the chroot is setup.",
100 )
101
Trent Apted9b8b13d2023-08-04 16:49:04 +1000102 parser.add_bool_argument(
103 "--update-packages",
Trent Apted7d2777b2023-06-29 13:35:03 +1000104 True,
105 "Update and install packages before looking for things to export.",
106 "Only export packages already installed in the subtools SDK.",
107 )
108
109 parser.add_argument(
110 "--output-dir",
111 type=osutils.ExpandPath,
112 metavar="PATH",
113 help=f"Extract SDK and build in chroot (e.g. {SUBTOOLS_OUTPUT_DIR}).",
114 )
115
116 parser.add_argument(
117 "packages",
118 nargs="*",
119 default=["virtual/target-sdk-subtools"],
120 help="Packages to build before looking for export candidates.",
121 )
122
123 parser.add_argument(
124 "--jobs",
125 "-j",
126 type=int,
127 default=os.cpu_count(),
128 help="Number of packages to build in parallel. (Default: %(default)s)",
129 )
130
131 parser.add_argument(
132 _RELAUNCH_FOR_SETUP_FLAG,
133 action="store_true",
134 default=False,
135 help=argparse.SUPPRESS,
136 )
137
138 # TODO(b/277992359): Consider possibly relevant flags from build_packages:
139 # * --rebuild_revdeps=no: don't rebuild reverse dependencies.
140 # * --skip-toolchain-update? Likely no - the SDK is our toolchain.
141 # * --withdebugsymbols
142 # * --rebuild=no "Automatically rebuild dependencies"
143 # * --backtrack
144 # * --bazel "Use Bazel to build packages"
145
146 return parser
147
148
149def parse_args(argv: Optional[List[str]]) -> Options:
150 """Parse and validate CLI arguments."""
151
152 parser = get_parser()
153 opts: Options = parser.parse_args(argv)
154
155 # Although `BuildPackages` is not used, sharing a config allows better
156 # sharing of subcommands and concepts.
157 opts.build_run_config = sysroot.BuildPackagesRunConfig(
158 packages=opts.packages,
159 jobs=opts.jobs,
160 usepkg=False,
161 clean_build=False,
162 eclean=False,
163 rebuild_dep=False,
164 )
165 opts.Freeze()
166 return opts
167
168
169def _is_inside_subtools_chroot() -> bool:
170 """Returns True if we are inside subtools chroot."""
171 return SUBTOOLS_CHROOT_VERSION_FILE.exists()
172
173
174def _assert_inside_subtools_chroot() -> None:
175 """Die if not _is_inside_subtools_chroot()."""
176 if not _is_inside_subtools_chroot():
177 cros_build_lib.Die("Not in subtools SDK")
178
179
180def _setup_base_sdk(
181 build_target: build_target_lib.BuildTarget,
182 setup_chroot: bool,
183) -> None:
184 """SetupBoard workalike that converts a regular SDK into a subtools chroot.
185
186 Runs inside the /build/amd64-subtools-host subtools SDK chroot.
187 """
188 cros_build_lib.AssertInsideChroot()
189 cros_build_lib.AssertRootUser()
190
191 # "Convert" the SDK into a subtools SDK.
192 if not _is_inside_subtools_chroot():
193 # Copy the sentinel file that chromite uses to indicate the chroot's
194 # duality. The file is copied (not moved) so that other chromite tooling
195 # continues to work.
196 shutil.copy(
197 cros_sdk_lib.CHROOT_VERSION_FILE, SUBTOOLS_CHROOT_VERSION_FILE
198 )
199
200 if setup_chroot:
201 # TODO(b/277992359): Additional setup here, e.g., packages, base layout.
202 logging.info("Setting up subtools SDK in %s.", build_target.root)
203
204
205def _run_system_emerge(
206 emerge_cmd: List[Union[str, Path]],
207 extra_env: Dict[str, str],
208 use_goma: bool,
209 use_remoteexec: bool,
210 reason: str,
211) -> None:
212 """Runs an emerge command, updating the live system."""
213 extra_env = extra_env.copy()
214 with osutils.TempDir() as tempdir:
215 extra_env[constants.CROS_METRICS_DIR_ENVVAR] = tempdir
216 with sysroot.RemoteExecution(use_goma, use_remoteexec):
217 logging.info("Merging %s now.", reason)
218 try:
219 # TODO(b/277992359): Bazel.
220 cros_build_lib.sudo_run(
221 emerge_cmd,
222 preserve_env=True,
223 extra_env=extra_env,
224 )
225 logging.info("Merging %s complete.", reason)
226 except cros_build_lib.RunCommandError as e:
227 failed_pkgs = portage_util.ParseDieHookStatusFile(tempdir)
228 logging.error("Merging %s failed on %s", reason, failed_pkgs)
229 raise sysroot_lib.PackageInstallError(
230 f"Merging {reason} failed",
231 e.result,
232 exception=e,
233 packages=failed_pkgs,
234 )
235
236
237def _build_sdk_packages(config: sysroot.BuildPackagesRunConfig) -> None:
238 """The BuildPackages workalike for installing into the staging SDK."""
239 _assert_inside_subtools_chroot()
240 cros_build_lib.AssertNonRootUser()
241
242 try:
243 # sysroot.BuildPackages can't (yet?) be used here, because it _only_
244 # supports cross-compilation. SDK package management is currently all
245 # handled by src/scripts/sdk_lib/make_chroot.sh (b/191307774).
246
247 emerge = [constants.CHROMITE_BIN_DIR / "parallel_emerge"]
248 extra_env = config.GetExtraEnv()
249 emerge_flags = config.GetEmergeFlags()
250 exclude_pkgs = " ".join(EXCLUDE_PACKAGES)
251 emerge_flags.extend(
252 [
253 f"--useoldpkg-atoms={exclude_pkgs}",
254 f"--rebuild-exclude={exclude_pkgs}",
255 ]
256 )
257 cmd = emerge + emerge_flags + config.GetPackages()
258 _run_system_emerge(
259 cmd,
260 extra_env,
261 config.use_goma,
262 config.use_remoteexec,
263 reason="subtools builder SDK packages",
264 )
265
266 except sysroot_lib.PackageInstallError as e:
267 cros_build_lib.Die(e)
268
269
270def main(argv: Optional[List[str]] = None) -> Optional[int]:
271 opts = parse_args(argv)
272 return build_sdk_subtools(opts, argv if argv else [])
273
274
275def build_sdk_subtools(opts: Options, argv: List[str]) -> int:
276 """Executes SDK subtools builder steps according to `opts`."""
277 # BuildTarget needs a str, but opts.output_dir is osutils.ExpandPath.
278 custom_output_dir = str(opts.output_dir) if opts.output_dir else None
279 build_target = build_target_lib.BuildTarget(
280 name=SUBTOOLS_OUTPUT_DIR, build_root=custom_output_dir
281 )
282
283 # If the process is in the subtools chroot, we must assume it's already set
284 # up (we are in it). So start building.
285 if _is_inside_subtools_chroot() and not opts.relaunch_for_setup:
286 if opts.update_packages:
287 _build_sdk_packages(opts.build_run_config)
288 return 0
289
290 # Otherwise, we have the option to set it up. Then restart inside it. The
291 # setup runs `cros_sdk` to get a base SDK, creates an SDK subprocess to set
292 # it up as a subtools SDK, then restarts inside the subtools SDK.
293 if cros_build_lib.IsInsideChroot():
294 if opts.relaunch_for_setup:
295 # This is the subprocess of the not-in-chroot path used to convert
296 # the base SDK to a subtools SDK (within the chroot).
297 _setup_base_sdk(build_target, opts.setup_chroot)
298 return 0
299 else:
300 cros_build_lib.Die(
301 "build_sdk_subtools must be run outside the chroot."
302 )
303
304 logging.info("Initializing subtools builder in %s", build_target.root)
305 subtools_chroot = os.path.join(
306 constants.DEFAULT_CHROOT_PATH, build_target.root.lstrip("/")
307 )
308 chroot_args = ["--chroot", subtools_chroot]
309
310 if opts.setup_chroot:
311 # Get an SDK. TODO(b/277992359):
312 # - Fetch an SDK version pinned by pupr rather than the default.
313 # - Should this use cros_sdk_lib directly?
314
315 # Pass "--skip-chroot-upgrade": the SDK should initially be used
316 # "as-is", but later steps may upgrade packages in the subtools deptree.
317 cros_sdk_args = ["--create", "--skip-chroot-upgrade"]
318 cros_sdk_args += ["--delete"] if opts.clean else []
319 cros_sdk = cros_build_lib.run(
320 ["cros_sdk"] + chroot_args + cros_sdk_args,
321 check=False,
322 cwd=constants.SOURCE_ROOT,
323 )
324 if cros_sdk.returncode != 0:
325 return cros_sdk.returncode
326
327 # Invoke `_setup_base_sdk()` inside the SDK.
328 setup_base_sdk = cros_build_lib.sudo_run(
329 ["build_sdk_subtools"] + argv + [_RELAUNCH_FOR_SETUP_FLAG],
330 check=False,
331 enter_chroot=True,
332 chroot_args=chroot_args,
333 cwd=constants.SOURCE_ROOT,
334 )
335 if setup_base_sdk.returncode != 0:
336 return setup_base_sdk.returncode
337
338 raise commandline.ChrootRequiredError(
339 ["build_sdk_subtools"] + argv, chroot_args=chroot_args
340 )