blob: e968c752314d877b1c7ca5f1de98894141e12f25 [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2018 The ChromiumOS Authors
Alex Kleina2ceb192018-08-17 11:19:32 -06002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Choose the profile for a board that has been or is being setup."""
6
Alex Kleina2ceb192018-08-17 11:19:32 -06007import functools
Chris McDonald59650c32021-07-20 15:29:28 -06008import logging
Alex Kleina2ceb192018-08-17 11:19:32 -06009import os
Alex Klein9e7b29e2023-04-11 16:10:31 -060010from typing import Optional
Alex Kleina2ceb192018-08-17 11:19:32 -060011
Mike Frysinger06a51c82021-04-06 11:39:17 -040012from chromite.lib import build_target_lib
Alex Kleina2ceb192018-08-17 11:19:32 -060013from chromite.lib import commandline
14from chromite.lib import cros_build_lib
Alex Kleina2ceb192018-08-17 11:19:32 -060015from chromite.lib import osutils
16from chromite.lib import sysroot_lib
17
18
19# Default value constants.
Alex Klein1699fab2022-09-08 08:46:06 -060020_DEFAULT_PROFILE = "base"
Alex Kleina2ceb192018-08-17 11:19:32 -060021
22
23def PathPrefixDecorator(f):
Alex Klein1699fab2022-09-08 08:46:06 -060024 """Add a prefix to the path or paths returned by the decorated function.
Alex Kleina2ceb192018-08-17 11:19:32 -060025
Alex Klein9e7b29e2023-04-11 16:10:31 -060026 Will not prepend the prefix if the path already starts with the prefix, so
27 the decorator may be applied to functions that have mixed sources that may
Alex Klein1699fab2022-09-08 08:46:06 -060028 or may not already have applied them. This is especially useful for allowing
29 tests and CLI args a little more leniency in how paths are provided.
30 """
Alex Kleina2ceb192018-08-17 11:19:32 -060031
Alex Klein1699fab2022-09-08 08:46:06 -060032 @functools.wraps(f)
33 def wrapper(*args, **kwargs):
34 result = f(*args, **kwargs)
35 prefix = PathPrefixDecorator.prefix
Mike Frysinger18a4fe22022-04-21 21:12:23 -040036
Alex Klein1699fab2022-09-08 08:46:06 -060037 if not prefix or not result:
38 # Nothing to do.
39 return result
Mike Frysinger18a4fe22022-04-21 21:12:23 -040040
Alex Klein1699fab2022-09-08 08:46:06 -060041 # Convert Path objects to str.
42 if isinstance(prefix, os.PathLike):
43 prefix = str(prefix)
Alex Kleina2ceb192018-08-17 11:19:32 -060044
Alex Klein1699fab2022-09-08 08:46:06 -060045 if not isinstance(result, str):
46 # Transform each path in the collection.
47 new_result = []
48 for path in result:
49 prefixed_path = os.path.join(prefix, path.lstrip(os.sep))
50 new_result.append(
51 path if path.startswith(prefix) else prefixed_path
52 )
Alex Kleina2ceb192018-08-17 11:19:32 -060053
Alex Klein1699fab2022-09-08 08:46:06 -060054 return new_result
55 elif not result.startswith(prefix):
56 # Add the prefix.
57 return os.path.join(prefix, result.lstrip(os.sep))
Alex Kleina2ceb192018-08-17 11:19:32 -060058
Alex Klein1699fab2022-09-08 08:46:06 -060059 # An already prefixed path.
60 return result
61
62 return wrapper
63
Alex Kleina2ceb192018-08-17 11:19:32 -060064
65PathPrefixDecorator.prefix = None
66
67
68class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060069 """Base error for custom exceptions in this script."""
Alex Kleina2ceb192018-08-17 11:19:32 -060070
71
72class InvalidArgumentsError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060073 """Invalid arguments."""
Alex Kleina2ceb192018-08-17 11:19:32 -060074
75
76class MakeProfileIsNotLinkError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060077 """The make profile exists but is not a link."""
Alex Kleina2ceb192018-08-17 11:19:32 -060078
79
80class ProfileDirectoryNotFoundError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060081 """Unable to find the profile directory."""
Alex Kleina2ceb192018-08-17 11:19:32 -060082
83
84def ChooseProfile(board, profile):
Alex Klein1699fab2022-09-08 08:46:06 -060085 """Make the link to choose the profile, print relevant warnings.
Alex Kleina2ceb192018-08-17 11:19:32 -060086
Alex Klein1699fab2022-09-08 08:46:06 -060087 Args:
Trent Apted66736d82023-05-25 10:38:28 +100088 board: Board - the board being used.
89 profile: Profile - the profile being used.
Alex Kleina2ceb192018-08-17 11:19:32 -060090
Alex Klein1699fab2022-09-08 08:46:06 -060091 Raises:
Trent Apted66736d82023-05-25 10:38:28 +100092 OSError when the board's make_profile path exists and is not a link.
Alex Klein1699fab2022-09-08 08:46:06 -060093 """
94 if not os.path.isfile(os.path.join(profile.directory, "parent")):
95 logging.warning(
96 "Portage profile directory %s has no 'parent' file. "
97 "This likely means your profile directory is invalid and "
Jack Rosenthald05fb3c2023-06-16 19:46:32 -060098 "`cros build-packages` will fail.",
Alex Klein1699fab2022-09-08 08:46:06 -060099 profile.directory,
100 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600101
Alex Klein1699fab2022-09-08 08:46:06 -0600102 current_profile = None
103 if os.path.exists(board.make_profile):
Alex Klein9e7b29e2023-04-11 16:10:31 -0600104 # Only try to read if it exists; we only want it to raise an error when
105 # the path exists and is not a link.
Alex Klein1699fab2022-09-08 08:46:06 -0600106 try:
107 current_profile = os.readlink(board.make_profile)
108 except OSError:
109 raise MakeProfileIsNotLinkError(
110 "%s is not a link." % board.make_profile
111 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600112
Alex Klein1699fab2022-09-08 08:46:06 -0600113 if current_profile == profile.directory:
114 # The existing link is what we were going to make, so nothing to do.
115 return
116 elif current_profile is not None:
117 # It exists and is changing, emit warning.
118 fmt = {"board": board.board_variant, "profile": profile.name}
119 msg = (
Alex Klein9e7b29e2023-04-11 16:10:31 -0600120 "You are switching profiles for a board that is already setup. "
121 "This can cause trouble for Portage. If you experience problems "
Jack Rosenthald05fb3c2023-06-16 19:46:32 -0600122 "with `cros build-packages` you may need to run:\n"
Alex Klein1699fab2022-09-08 08:46:06 -0600123 "\t'setup_board --board %(board)s --force --profile %(profile)s'\n"
124 "\nAlternatively, you can correct the dependency graph by using "
125 "'emerge-%(board)s -c' or 'emerge-%(board)s -C <ebuild>'."
126 )
127 logging.warning(msg, fmt)
Alex Kleina2ceb192018-08-17 11:19:32 -0600128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 # Make the symlink, overwrites existing link if one already exists.
130 osutils.SafeSymlink(profile.directory, board.make_profile, sudo=True)
Alex Kleina2ceb192018-08-17 11:19:32 -0600131
Alex Klein1699fab2022-09-08 08:46:06 -0600132 # Update the profile override value.
133 if profile.override:
134 board.profile_override = profile.override
Alex Kleina2ceb192018-08-17 11:19:32 -0600135
136
137class Profile(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600138 """Simple data container class for the profile data."""
Alex Kleina2ceb192018-08-17 11:19:32 -0600139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 def __init__(self, name, directory, override):
141 self.name = name
142 self._directory = directory
143 self.override = override
144
145 @property
146 @PathPrefixDecorator
147 def directory(self):
148 return self._directory
Alex Kleina2ceb192018-08-17 11:19:32 -0600149
150
151def _GetProfile(opts, board):
Alex Klein1699fab2022-09-08 08:46:06 -0600152 """Get the profile list."""
153 # Determine the override value - which profile is being selected.
154 override = opts.profile if opts.profile else board.profile_override
Alex Kleina2ceb192018-08-17 11:19:32 -0600155
Alex Klein1699fab2022-09-08 08:46:06 -0600156 profile = _DEFAULT_PROFILE
157 profile_directory = None
Alex Kleina2ceb192018-08-17 11:19:32 -0600158
Alex Klein1699fab2022-09-08 08:46:06 -0600159 if override and os.path.exists(override):
160 profile_directory = os.path.abspath(override)
161 profile = os.path.basename(profile_directory)
162 elif override:
163 profile = override
Alex Kleina2ceb192018-08-17 11:19:32 -0600164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 if profile_directory is None:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600166 # Build profile directories in reverse order, so we can search from most
167 # to least specific.
Alex Klein1699fab2022-09-08 08:46:06 -0600168 profile_dirs = [
169 "%s/profiles/%s" % (overlay, profile)
170 for overlay in reversed(board.overlays)
171 ]
Alex Kleina2ceb192018-08-17 11:19:32 -0600172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 for profile_dir in profile_dirs:
174 if os.path.isdir(profile_dir):
175 profile_directory = profile_dir
176 break
177 else:
178 searched = ", ".join(profile_dirs)
179 raise ProfileDirectoryNotFoundError(
180 "Profile directory not found, searched in (%s)." % searched
181 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600182
Alex Klein1699fab2022-09-08 08:46:06 -0600183 return Profile(profile, profile_directory, override)
Alex Kleina2ceb192018-08-17 11:19:32 -0600184
185
186class Board(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600187 """Manage the board arguments and configs."""
Alex Kleina2ceb192018-08-17 11:19:32 -0600188
Alex Klein1699fab2022-09-08 08:46:06 -0600189 # Files located on the board.
190 MAKE_PROFILE = "%(board_root)s/etc/portage/make.profile"
Alex Kleina2ceb192018-08-17 11:19:32 -0600191
Alex Klein9e7b29e2023-04-11 16:10:31 -0600192 def __init__(
193 self,
194 board: Optional[str] = None,
195 variant: Optional[str] = None,
196 board_root: Optional[str] = None,
197 ):
Alex Klein1699fab2022-09-08 08:46:06 -0600198 """Board constructor.
Alex Kleina2ceb192018-08-17 11:19:32 -0600199
Alex Klein1699fab2022-09-08 08:46:06 -0600200 board [+ variant] is given preference when both board and board_root are
201 provided.
Alex Kleina2ceb192018-08-17 11:19:32 -0600202
Alex Klein1699fab2022-09-08 08:46:06 -0600203 Preconditions:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600204 Either board and build_root are not None, or board_root is not None.
205 With board + build_root we can construct the board root.
206 With the board root we can have the board directory.
Alex Kleina2ceb192018-08-17 11:19:32 -0600207
Alex Klein1699fab2022-09-08 08:46:06 -0600208 Args:
Alex Klein9e7b29e2023-04-11 16:10:31 -0600209 board: The board name.
210 variant: The variant name. TODO: Deprecate?
211 board_root: The boards fully qualified build directory path.
Alex Klein1699fab2022-09-08 08:46:06 -0600212 """
213 if not board and not board_root:
214 # Enforce preconditions.
215 raise InvalidArgumentsError(
Trent Apted66736d82023-05-25 10:38:28 +1000216 "Either board or board_root must be provided."
Alex Klein1699fab2022-09-08 08:46:06 -0600217 )
218 elif board:
219 # The board and variant can be specified separately, or can both be
220 # contained in the board name, separated by an underscore.
221 board_split = board.split("_")
222 variant_default = variant
Alex Kleina2ceb192018-08-17 11:19:32 -0600223
Alex Klein1699fab2022-09-08 08:46:06 -0600224 self._board_root = None
225 else:
226 self._board_root = os.path.normpath(board_root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600227
Alex Klein1699fab2022-09-08 08:46:06 -0600228 board_split = os.path.basename(self._board_root).split("_")
229 variant_default = None
Alex Kleina2ceb192018-08-17 11:19:32 -0600230
Alex Klein1699fab2022-09-08 08:46:06 -0600231 self.board = board_split.pop(0)
232 self.variant = board_split.pop(0) if board_split else variant_default
Alex Kleina2ceb192018-08-17 11:19:32 -0600233
Alex Klein1699fab2022-09-08 08:46:06 -0600234 if self.variant:
235 self.board_variant = "%s_%s" % (self.board, self.variant)
236 else:
237 self.board_variant = self.board
Alex Kleina2ceb192018-08-17 11:19:32 -0600238
Alex Klein1699fab2022-09-08 08:46:06 -0600239 self.make_profile = self.MAKE_PROFILE % {"board_root": self.root}
Alex Klein9e7b29e2023-04-11 16:10:31 -0600240 # This must come after the arguments required to build each variant of
241 # the build root have been processed.
Alex Klein1699fab2022-09-08 08:46:06 -0600242 self._sysroot_config = sysroot_lib.Sysroot(self.root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600243
Alex Klein1699fab2022-09-08 08:46:06 -0600244 @property
245 @PathPrefixDecorator
246 def root(self):
247 if self._board_root:
248 return self._board_root
Alex Kleina2ceb192018-08-17 11:19:32 -0600249
Alex Klein1699fab2022-09-08 08:46:06 -0600250 return build_target_lib.get_default_sysroot_path(self.board_variant)
Alex Kleina2ceb192018-08-17 11:19:32 -0600251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 @property
253 @PathPrefixDecorator
254 def overlays(self):
255 return self._sysroot_config.GetStandardField(
256 sysroot_lib.STANDARD_FIELD_BOARD_OVERLAY
257 ).split()
Alex Kleina2ceb192018-08-17 11:19:32 -0600258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 @property
260 def profile_override(self):
261 return self._sysroot_config.GetCachedField("PROFILE_OVERRIDE")
Alex Kleina2ceb192018-08-17 11:19:32 -0600262
Alex Klein1699fab2022-09-08 08:46:06 -0600263 @profile_override.setter
264 def profile_override(self, value):
265 self._sysroot_config.SetCachedField("PROFILE_OVERRIDE", value)
Alex Kleina2ceb192018-08-17 11:19:32 -0600266
267
268def _GetBoard(opts):
Alex Klein1699fab2022-09-08 08:46:06 -0600269 """Factory method to build a Board from the parsed CLI arguments."""
270 return Board(
271 board=opts.board, variant=opts.variant, board_root=opts.board_root
272 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600273
274
275def GetParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600276 """ArgumentParser builder and argument definitions."""
277 parser = commandline.ArgumentParser(description=__doc__)
278 parser.add_argument(
279 "-b",
280 "--board",
281 default=os.environ.get("DEFAULT_BOARD"),
282 help="The name of the board to set up.",
283 )
284 parser.add_argument(
285 "-r",
286 "--board-root",
287 type="path",
288 help="Board root where the profile should be created.",
289 )
290 parser.add_argument(
291 "-p", "--profile", help="The portage configuration profile to use."
292 )
293 parser.add_argument("--variant", help="Board variant.")
Alex Kleina2ceb192018-08-17 11:19:32 -0600294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 group = parser.add_argument_group("Advanced options")
296 group.add_argument(
297 "--filesystem-prefix",
298 type="path",
Trent Apted66736d82023-05-25 10:38:28 +1000299 help="Force filesystem accesses to be prefixed by the given path.",
Alex Klein1699fab2022-09-08 08:46:06 -0600300 )
301 return parser
Alex Kleina2ceb192018-08-17 11:19:32 -0600302
303
304def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600305 """Parse and validate the arguments."""
306 parser = GetParser()
307 opts = parser.parse_args(argv)
Alex Kleina2ceb192018-08-17 11:19:32 -0600308
Alex Klein1699fab2022-09-08 08:46:06 -0600309 # See Board.__init__ Preconditions.
310 board_valid = opts.board is not None
311 board_root_valid = opts.board_root and os.path.exists(opts.board_root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 if not board_valid and not board_root_valid:
314 parser.error("Either board or board_root must be provided.")
Alex Kleina2ceb192018-08-17 11:19:32 -0600315
Alex Klein1699fab2022-09-08 08:46:06 -0600316 PathPrefixDecorator.prefix = opts.filesystem_prefix
317 del opts.filesystem_prefix
Alex Kleina2ceb192018-08-17 11:19:32 -0600318
Alex Klein1699fab2022-09-08 08:46:06 -0600319 opts.Freeze()
320 return opts
Alex Kleina2ceb192018-08-17 11:19:32 -0600321
322
323def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600324 # Parse arguments.
325 opts = ParseArgs(argv)
Alex Kleina2ceb192018-08-17 11:19:32 -0600326
Alex Klein1699fab2022-09-08 08:46:06 -0600327 # Build and validate the board and profile.
328 board = _GetBoard(opts)
Alex Kleina2ceb192018-08-17 11:19:32 -0600329
Alex Klein1699fab2022-09-08 08:46:06 -0600330 if not os.path.exists(board.root):
331 cros_build_lib.Die(
Trent Apted66736d82023-05-25 10:38:28 +1000332 "The board has not been setup, please run setup_board first."
Alex Klein1699fab2022-09-08 08:46:06 -0600333 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600334
Alex Klein1699fab2022-09-08 08:46:06 -0600335 try:
336 profile = _GetProfile(opts, board)
337 except ProfileDirectoryNotFoundError as e:
338 cros_build_lib.Die(e)
Alex Kleina2ceb192018-08-17 11:19:32 -0600339
Alex Klein1699fab2022-09-08 08:46:06 -0600340 # Change the profile to the selected.
341 logging.info("Selecting profile: %s for %s", profile.directory, board.root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600342
Alex Klein1699fab2022-09-08 08:46:06 -0600343 try:
344 ChooseProfile(board, profile)
345 except MakeProfileIsNotLinkError as e:
346 cros_build_lib.Die(e)