blob: 665d8907b667ccb9e3dd6f72f7b887705f0ec822 [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
Trent Apted7d2777b2023-06-29 13:35:03 +100027import sys
Trent Aptedbd3cb412023-08-18 11:37:02 +100028from typing import List, Optional, Protocol
Trent Apted7d2777b2023-06-29 13:35:03 +100029
30from chromite.lib import build_target_lib
31from chromite.lib import commandline
32from chromite.lib import constants
33from chromite.lib import cros_build_lib
Trent Apted7d2777b2023-06-29 13:35:03 +100034from chromite.lib import osutils
Trent Apted7d2777b2023-06-29 13:35:03 +100035from chromite.lib import sysroot_lib
Trent Aptedbd3cb412023-08-18 11:37:02 +100036from chromite.service import sdk_subtools
Trent Apted7d2777b2023-06-29 13:35:03 +100037
38
39assert sys.version_info >= (3, 8), "build_sdk_subtools uses Python 3.8 features"
40
41
42# Affects where building occurs (e.g. /build/amd64-subtools-host) if not
43# overridden by --output-dir. Note this will be a chroot.
44SUBTOOLS_OUTPUT_DIR = "amd64-subtools-host"
45
Trent Apted7d2777b2023-06-29 13:35:03 +100046# Flag passed to subprocesses in chroots that might not yet be set up as a
47# subtools chroot.
48_RELAUNCH_FOR_SETUP_FLAG = "--relaunch-for-setup"
49
Trent Apted16007b82023-07-06 14:51:57 +100050# Used to populate a test manifest in /etc/cros/standalone-packages.d/.
51# Ebuilds will later be updated to provide these files instead.
Trent Aptedbd3cb412023-08-18 11:37:02 +100052_TEST_PACKAGE = (
53 sdk_subtools.SUBTOOLS_EXPORTS_CONFIG_DIR / "shellcheck.textproto"
54)
Trent Apted16007b82023-07-06 14:51:57 +100055_TEST_PACKAGE_CONTENTS = """\
56# proto-file: chromiumos/build/api/subtools.proto
57# proto-message: chromiumos.build.api.SubtoolPackage
58name: "shellcheck"
59type: EXPORT_CIPD
60max_files: 2
61paths: [{
62 input: "/usr/bin/shellcheck"
63},{
64 input: "/usr/share/doc/*/LICENSE.gz"
65 ebuild_filter: "dev-util/shellcheck"
66}]
67"""
68
Trent Apted7d2777b2023-06-29 13:35:03 +100069
70class Options(Protocol):
71 """Protocol to formalize commandline arguments."""
72
Trent Apted7d2777b2023-06-29 13:35:03 +100073 clean: bool
74 setup_chroot: bool
75 update_packages: bool
76 relaunch_for_setup: bool
77 output_dir: Path
78 packages: List[str]
79 jobs: int
80
81 def Freeze(self) -> None:
82 pass
83
84
85def get_parser() -> commandline.ArgumentParser:
86 """Returns the cmdline argparser, populates the options and descriptions."""
87 parser = commandline.ArgumentParser(description=__doc__)
88
Trent Apted9b8b13d2023-08-04 16:49:04 +100089 parser.add_bool_argument(
90 "--clean",
Trent Apted7d2777b2023-06-29 13:35:03 +100091 False,
92 "Remove the subtools chroot and re-extract the SDK.",
93 "Re-use an existing subtools chroot.",
94 )
95
Trent Apted9b8b13d2023-08-04 16:49:04 +100096 parser.add_bool_argument(
97 "--setup-chroot",
Trent Apted7d2777b2023-06-29 13:35:03 +100098 True,
99 "Look for a newer base SDK and set it up as a subtools SDK.",
100 "Don't look for a newer base SDK and assume the chroot is setup.",
101 )
102
Trent Apted9b8b13d2023-08-04 16:49:04 +1000103 parser.add_bool_argument(
104 "--update-packages",
Trent Apted7d2777b2023-06-29 13:35:03 +1000105 True,
106 "Update and install packages before looking for things to export.",
107 "Only export packages already installed in the subtools SDK.",
108 )
109
110 parser.add_argument(
111 "--output-dir",
112 type=osutils.ExpandPath,
113 metavar="PATH",
114 help=f"Extract SDK and build in chroot (e.g. {SUBTOOLS_OUTPUT_DIR}).",
115 )
116
117 parser.add_argument(
118 "packages",
119 nargs="*",
120 default=["virtual/target-sdk-subtools"],
121 help="Packages to build before looking for export candidates.",
122 )
123
124 parser.add_argument(
125 "--jobs",
126 "-j",
127 type=int,
128 default=os.cpu_count(),
129 help="Number of packages to build in parallel. (Default: %(default)s)",
130 )
131
132 parser.add_argument(
133 _RELAUNCH_FOR_SETUP_FLAG,
134 action="store_true",
135 default=False,
136 help=argparse.SUPPRESS,
137 )
138
139 # TODO(b/277992359): Consider possibly relevant flags from build_packages:
140 # * --rebuild_revdeps=no: don't rebuild reverse dependencies.
141 # * --skip-toolchain-update? Likely no - the SDK is our toolchain.
142 # * --withdebugsymbols
143 # * --rebuild=no "Automatically rebuild dependencies"
144 # * --backtrack
145 # * --bazel "Use Bazel to build packages"
146
147 return parser
148
149
150def parse_args(argv: Optional[List[str]]) -> Options:
151 """Parse and validate CLI arguments."""
152
153 parser = get_parser()
154 opts: Options = parser.parse_args(argv)
Trent Apted7d2777b2023-06-29 13:35:03 +1000155 opts.Freeze()
156 return opts
157
158
Trent Apted7d2777b2023-06-29 13:35:03 +1000159def _setup_base_sdk(
160 build_target: build_target_lib.BuildTarget,
161 setup_chroot: bool,
162) -> None:
163 """SetupBoard workalike that converts a regular SDK into a subtools chroot.
164
165 Runs inside the /build/amd64-subtools-host subtools SDK chroot.
166 """
167 cros_build_lib.AssertInsideChroot()
168 cros_build_lib.AssertRootUser()
169
Trent Aptedbd3cb412023-08-18 11:37:02 +1000170 sdk_subtools.setup_base_sdk(build_target, setup_chroot)
Trent Apted7d2777b2023-06-29 13:35:03 +1000171
172 if setup_chroot:
Trent Apted16007b82023-07-06 14:51:57 +1000173 _TEST_PACKAGE.write_text(_TEST_PACKAGE_CONTENTS, encoding="utf-8")
Trent Apted7d2777b2023-06-29 13:35:03 +1000174
175
Trent Apted16007b82023-07-06 14:51:57 +1000176def _run_inside_subtools_chroot(opts: Options) -> None:
177 """Steps that build_sdk_subtools performs once it is in its chroot."""
Trent Apted16007b82023-07-06 14:51:57 +1000178 if opts.update_packages:
Trent Aptedbd3cb412023-08-18 11:37:02 +1000179 try:
180 sdk_subtools.update_packages(opts.packages, opts.jobs)
181 except sysroot_lib.PackageInstallError as e:
182 cros_build_lib.Die(e)
Trent Apted16007b82023-07-06 14:51:57 +1000183
Trent Aptedbd3cb412023-08-18 11:37:02 +1000184 sdk_subtools.bundle_and_export()
Trent Apted16007b82023-07-06 14:51:57 +1000185
186
Trent Apted7d2777b2023-06-29 13:35:03 +1000187def main(argv: Optional[List[str]] = None) -> Optional[int]:
188 opts = parse_args(argv)
189 return build_sdk_subtools(opts, argv if argv else [])
190
191
192def build_sdk_subtools(opts: Options, argv: List[str]) -> int:
193 """Executes SDK subtools builder steps according to `opts`."""
194 # BuildTarget needs a str, but opts.output_dir is osutils.ExpandPath.
195 custom_output_dir = str(opts.output_dir) if opts.output_dir else None
196 build_target = build_target_lib.BuildTarget(
197 name=SUBTOOLS_OUTPUT_DIR, build_root=custom_output_dir
198 )
199
200 # If the process is in the subtools chroot, we must assume it's already set
201 # up (we are in it). So start building.
Trent Aptedbd3cb412023-08-18 11:37:02 +1000202 if sdk_subtools.is_inside_subtools_chroot() and not opts.relaunch_for_setup:
Trent Apted16007b82023-07-06 14:51:57 +1000203 _run_inside_subtools_chroot(opts)
Trent Apted7d2777b2023-06-29 13:35:03 +1000204 return 0
205
206 # Otherwise, we have the option to set it up. Then restart inside it. The
207 # setup runs `cros_sdk` to get a base SDK, creates an SDK subprocess to set
208 # it up as a subtools SDK, then restarts inside the subtools SDK.
209 if cros_build_lib.IsInsideChroot():
210 if opts.relaunch_for_setup:
211 # This is the subprocess of the not-in-chroot path used to convert
212 # the base SDK to a subtools SDK (within the chroot).
213 _setup_base_sdk(build_target, opts.setup_chroot)
214 return 0
215 else:
216 cros_build_lib.Die(
217 "build_sdk_subtools must be run outside the chroot."
218 )
219
220 logging.info("Initializing subtools builder in %s", build_target.root)
221 subtools_chroot = os.path.join(
222 constants.DEFAULT_CHROOT_PATH, build_target.root.lstrip("/")
223 )
224 chroot_args = ["--chroot", subtools_chroot]
225
226 if opts.setup_chroot:
227 # Get an SDK. TODO(b/277992359):
228 # - Fetch an SDK version pinned by pupr rather than the default.
229 # - Should this use cros_sdk_lib directly?
230
231 # Pass "--skip-chroot-upgrade": the SDK should initially be used
232 # "as-is", but later steps may upgrade packages in the subtools deptree.
233 cros_sdk_args = ["--create", "--skip-chroot-upgrade"]
234 cros_sdk_args += ["--delete"] if opts.clean else []
235 cros_sdk = cros_build_lib.run(
236 ["cros_sdk"] + chroot_args + cros_sdk_args,
237 check=False,
238 cwd=constants.SOURCE_ROOT,
239 )
240 if cros_sdk.returncode != 0:
241 return cros_sdk.returncode
242
243 # Invoke `_setup_base_sdk()` inside the SDK.
244 setup_base_sdk = cros_build_lib.sudo_run(
245 ["build_sdk_subtools"] + argv + [_RELAUNCH_FOR_SETUP_FLAG],
246 check=False,
247 enter_chroot=True,
248 chroot_args=chroot_args,
249 cwd=constants.SOURCE_ROOT,
250 )
251 if setup_base_sdk.returncode != 0:
252 return setup_base_sdk.returncode
253
254 raise commandline.ChrootRequiredError(
255 ["build_sdk_subtools"] + argv, chroot_args=chroot_args
256 )