blob: aa17f45cb88510ece3b18f185acba06415dc9ef8 [file] [log] [blame]
Alex Kleina2ceb192018-08-17 11:19:32 -06001# Copyright 2018 The Chromium OS Authors. All rights reserved.
2# 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
10
Mike Frysinger06a51c82021-04-06 11:39:17 -040011from chromite.lib import build_target_lib
Alex Kleina2ceb192018-08-17 11:19:32 -060012from chromite.lib import commandline
13from chromite.lib import cros_build_lib
Alex Kleina2ceb192018-08-17 11:19:32 -060014from chromite.lib import osutils
15from chromite.lib import sysroot_lib
16
17
18# Default value constants.
Alex Klein1699fab2022-09-08 08:46:06 -060019_DEFAULT_PROFILE = "base"
Alex Kleina2ceb192018-08-17 11:19:32 -060020
21
22def PathPrefixDecorator(f):
Alex Klein1699fab2022-09-08 08:46:06 -060023 """Add a prefix to the path or paths returned by the decorated function.
Alex Kleina2ceb192018-08-17 11:19:32 -060024
Alex Klein1699fab2022-09-08 08:46:06 -060025 Will not prepend the prefix if the path already starts with the prefix, so the
26 decorator may be applied to functions that have mixed sources that may
27 or may not already have applied them. This is especially useful for allowing
28 tests and CLI args a little more leniency in how paths are provided.
29 """
Alex Kleina2ceb192018-08-17 11:19:32 -060030
Alex Klein1699fab2022-09-08 08:46:06 -060031 @functools.wraps(f)
32 def wrapper(*args, **kwargs):
33 result = f(*args, **kwargs)
34 prefix = PathPrefixDecorator.prefix
Mike Frysinger18a4fe22022-04-21 21:12:23 -040035
Alex Klein1699fab2022-09-08 08:46:06 -060036 if not prefix or not result:
37 # Nothing to do.
38 return result
Mike Frysinger18a4fe22022-04-21 21:12:23 -040039
Alex Klein1699fab2022-09-08 08:46:06 -060040 # Convert Path objects to str.
41 if isinstance(prefix, os.PathLike):
42 prefix = str(prefix)
Alex Kleina2ceb192018-08-17 11:19:32 -060043
Alex Klein1699fab2022-09-08 08:46:06 -060044 if not isinstance(result, str):
45 # Transform each path in the collection.
46 new_result = []
47 for path in result:
48 prefixed_path = os.path.join(prefix, path.lstrip(os.sep))
49 new_result.append(
50 path if path.startswith(prefix) else prefixed_path
51 )
Alex Kleina2ceb192018-08-17 11:19:32 -060052
Alex Klein1699fab2022-09-08 08:46:06 -060053 return new_result
54 elif not result.startswith(prefix):
55 # Add the prefix.
56 return os.path.join(prefix, result.lstrip(os.sep))
Alex Kleina2ceb192018-08-17 11:19:32 -060057
Alex Klein1699fab2022-09-08 08:46:06 -060058 # An already prefixed path.
59 return result
60
61 return wrapper
62
Alex Kleina2ceb192018-08-17 11:19:32 -060063
64PathPrefixDecorator.prefix = None
65
66
67class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060068 """Base error for custom exceptions in this script."""
Alex Kleina2ceb192018-08-17 11:19:32 -060069
70
71class InvalidArgumentsError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060072 """Invalid arguments."""
Alex Kleina2ceb192018-08-17 11:19:32 -060073
74
75class MakeProfileIsNotLinkError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060076 """The make profile exists but is not a link."""
Alex Kleina2ceb192018-08-17 11:19:32 -060077
78
79class ProfileDirectoryNotFoundError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060080 """Unable to find the profile directory."""
Alex Kleina2ceb192018-08-17 11:19:32 -060081
82
83def ChooseProfile(board, profile):
Alex Klein1699fab2022-09-08 08:46:06 -060084 """Make the link to choose the profile, print relevant warnings.
Alex Kleina2ceb192018-08-17 11:19:32 -060085
Alex Klein1699fab2022-09-08 08:46:06 -060086 Args:
87 board: Board - the board being used.
88 profile: Profile - the profile being used.
Alex Kleina2ceb192018-08-17 11:19:32 -060089
Alex Klein1699fab2022-09-08 08:46:06 -060090 Raises:
91 OSError when the board's make_profile path exists and is not a link.
92 """
93 if not os.path.isfile(os.path.join(profile.directory, "parent")):
94 logging.warning(
95 "Portage profile directory %s has no 'parent' file. "
96 "This likely means your profile directory is invalid and "
97 "build_packages will fail.",
98 profile.directory,
99 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 current_profile = None
102 if os.path.exists(board.make_profile):
103 # Only try to read if it exists; we only want it to raise an error when the
104 # path exists and is not a link.
105 try:
106 current_profile = os.readlink(board.make_profile)
107 except OSError:
108 raise MakeProfileIsNotLinkError(
109 "%s is not a link." % board.make_profile
110 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 if current_profile == profile.directory:
113 # The existing link is what we were going to make, so nothing to do.
114 return
115 elif current_profile is not None:
116 # It exists and is changing, emit warning.
117 fmt = {"board": board.board_variant, "profile": profile.name}
118 msg = (
119 "You are switching profiles for a board that is already setup. This "
120 "can cause trouble for Portage. If you experience problems with "
121 "build_packages you may need to run:\n"
122 "\t'setup_board --board %(board)s --force --profile %(profile)s'\n"
123 "\nAlternatively, you can correct the dependency graph by using "
124 "'emerge-%(board)s -c' or 'emerge-%(board)s -C <ebuild>'."
125 )
126 logging.warning(msg, fmt)
Alex Kleina2ceb192018-08-17 11:19:32 -0600127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 # Make the symlink, overwrites existing link if one already exists.
129 osutils.SafeSymlink(profile.directory, board.make_profile, sudo=True)
Alex Kleina2ceb192018-08-17 11:19:32 -0600130
Alex Klein1699fab2022-09-08 08:46:06 -0600131 # Update the profile override value.
132 if profile.override:
133 board.profile_override = profile.override
Alex Kleina2ceb192018-08-17 11:19:32 -0600134
135
136class Profile(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600137 """Simple data container class for the profile data."""
Alex Kleina2ceb192018-08-17 11:19:32 -0600138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 def __init__(self, name, directory, override):
140 self.name = name
141 self._directory = directory
142 self.override = override
143
144 @property
145 @PathPrefixDecorator
146 def directory(self):
147 return self._directory
Alex Kleina2ceb192018-08-17 11:19:32 -0600148
149
150def _GetProfile(opts, board):
Alex Klein1699fab2022-09-08 08:46:06 -0600151 """Get the profile list."""
152 # Determine the override value - which profile is being selected.
153 override = opts.profile if opts.profile else board.profile_override
Alex Kleina2ceb192018-08-17 11:19:32 -0600154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 profile = _DEFAULT_PROFILE
156 profile_directory = None
Alex Kleina2ceb192018-08-17 11:19:32 -0600157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 if override and os.path.exists(override):
159 profile_directory = os.path.abspath(override)
160 profile = os.path.basename(profile_directory)
161 elif override:
162 profile = override
Alex Kleina2ceb192018-08-17 11:19:32 -0600163
Alex Klein1699fab2022-09-08 08:46:06 -0600164 if profile_directory is None:
165 # Build profile directories in reverse order so we can search from most to
166 # least specific.
167 profile_dirs = [
168 "%s/profiles/%s" % (overlay, profile)
169 for overlay in reversed(board.overlays)
170 ]
Alex Kleina2ceb192018-08-17 11:19:32 -0600171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 for profile_dir in profile_dirs:
173 if os.path.isdir(profile_dir):
174 profile_directory = profile_dir
175 break
176 else:
177 searched = ", ".join(profile_dirs)
178 raise ProfileDirectoryNotFoundError(
179 "Profile directory not found, searched in (%s)." % searched
180 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 return Profile(profile, profile_directory, override)
Alex Kleina2ceb192018-08-17 11:19:32 -0600183
184
185class Board(object):
Alex Klein1699fab2022-09-08 08:46:06 -0600186 """Manage the board arguments and configs."""
Alex Kleina2ceb192018-08-17 11:19:32 -0600187
Alex Klein1699fab2022-09-08 08:46:06 -0600188 # Files located on the board.
189 MAKE_PROFILE = "%(board_root)s/etc/portage/make.profile"
Alex Kleina2ceb192018-08-17 11:19:32 -0600190
Alex Klein1699fab2022-09-08 08:46:06 -0600191 def __init__(self, board=None, variant=None, board_root=None):
192 """Board constructor.
Alex Kleina2ceb192018-08-17 11:19:32 -0600193
Alex Klein1699fab2022-09-08 08:46:06 -0600194 board [+ variant] is given preference when both board and board_root are
195 provided.
Alex Kleina2ceb192018-08-17 11:19:32 -0600196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 Preconditions:
198 Either board and build_root are not None, or board_root is not None.
199 With board + build_root [+ variant] we can construct the board root.
200 With the board root we can have the board[_variant] directory.
Alex Kleina2ceb192018-08-17 11:19:32 -0600201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 Args:
203 board: str|None - The board name.
204 variant: str|None - The variant name.
205 board_root: str|None - The boards fully qualified build directory path.
206 """
207 if not board and not board_root:
208 # Enforce preconditions.
209 raise InvalidArgumentsError(
210 "Either board or board_root must be " "provided."
211 )
212 elif board:
213 # The board and variant can be specified separately, or can both be
214 # contained in the board name, separated by an underscore.
215 board_split = board.split("_")
216 variant_default = variant
Alex Kleina2ceb192018-08-17 11:19:32 -0600217
Alex Klein1699fab2022-09-08 08:46:06 -0600218 self._board_root = None
219 else:
220 self._board_root = os.path.normpath(board_root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600221
Alex Klein1699fab2022-09-08 08:46:06 -0600222 board_split = os.path.basename(self._board_root).split("_")
223 variant_default = None
Alex Kleina2ceb192018-08-17 11:19:32 -0600224
Alex Klein1699fab2022-09-08 08:46:06 -0600225 self.board = board_split.pop(0)
226 self.variant = board_split.pop(0) if board_split else variant_default
Alex Kleina2ceb192018-08-17 11:19:32 -0600227
Alex Klein1699fab2022-09-08 08:46:06 -0600228 if self.variant:
229 self.board_variant = "%s_%s" % (self.board, self.variant)
230 else:
231 self.board_variant = self.board
Alex Kleina2ceb192018-08-17 11:19:32 -0600232
Alex Klein1699fab2022-09-08 08:46:06 -0600233 self.make_profile = self.MAKE_PROFILE % {"board_root": self.root}
234 # This must come after the arguments required to build each variant of the
235 # build root have been processed.
236 self._sysroot_config = sysroot_lib.Sysroot(self.root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600237
Alex Klein1699fab2022-09-08 08:46:06 -0600238 @property
239 @PathPrefixDecorator
240 def root(self):
241 if self._board_root:
242 return self._board_root
Alex Kleina2ceb192018-08-17 11:19:32 -0600243
Alex Klein1699fab2022-09-08 08:46:06 -0600244 return build_target_lib.get_default_sysroot_path(self.board_variant)
Alex Kleina2ceb192018-08-17 11:19:32 -0600245
Alex Klein1699fab2022-09-08 08:46:06 -0600246 @property
247 @PathPrefixDecorator
248 def overlays(self):
249 return self._sysroot_config.GetStandardField(
250 sysroot_lib.STANDARD_FIELD_BOARD_OVERLAY
251 ).split()
Alex Kleina2ceb192018-08-17 11:19:32 -0600252
Alex Klein1699fab2022-09-08 08:46:06 -0600253 @property
254 def profile_override(self):
255 return self._sysroot_config.GetCachedField("PROFILE_OVERRIDE")
Alex Kleina2ceb192018-08-17 11:19:32 -0600256
Alex Klein1699fab2022-09-08 08:46:06 -0600257 @profile_override.setter
258 def profile_override(self, value):
259 self._sysroot_config.SetCachedField("PROFILE_OVERRIDE", value)
Alex Kleina2ceb192018-08-17 11:19:32 -0600260
261
262def _GetBoard(opts):
Alex Klein1699fab2022-09-08 08:46:06 -0600263 """Factory method to build a Board from the parsed CLI arguments."""
264 return Board(
265 board=opts.board, variant=opts.variant, board_root=opts.board_root
266 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600267
268
269def GetParser():
Alex Klein1699fab2022-09-08 08:46:06 -0600270 """ArgumentParser builder and argument definitions."""
271 parser = commandline.ArgumentParser(description=__doc__)
272 parser.add_argument(
273 "-b",
274 "--board",
275 default=os.environ.get("DEFAULT_BOARD"),
276 help="The name of the board to set up.",
277 )
278 parser.add_argument(
279 "-r",
280 "--board-root",
281 type="path",
282 help="Board root where the profile should be created.",
283 )
284 parser.add_argument(
285 "-p", "--profile", help="The portage configuration profile to use."
286 )
287 parser.add_argument("--variant", help="Board variant.")
Alex Kleina2ceb192018-08-17 11:19:32 -0600288
Alex Klein1699fab2022-09-08 08:46:06 -0600289 group = parser.add_argument_group("Advanced options")
290 group.add_argument(
291 "--filesystem-prefix",
292 type="path",
293 help="Force filesystem accesses to be prefixed by the " "given path.",
294 )
295 return parser
Alex Kleina2ceb192018-08-17 11:19:32 -0600296
297
298def ParseArgs(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600299 """Parse and validate the arguments."""
300 parser = GetParser()
301 opts = parser.parse_args(argv)
Alex Kleina2ceb192018-08-17 11:19:32 -0600302
Alex Klein1699fab2022-09-08 08:46:06 -0600303 # See Board.__init__ Preconditions.
304 board_valid = opts.board is not None
305 board_root_valid = opts.board_root and os.path.exists(opts.board_root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600306
Alex Klein1699fab2022-09-08 08:46:06 -0600307 if not board_valid and not board_root_valid:
308 parser.error("Either board or board_root must be provided.")
Alex Kleina2ceb192018-08-17 11:19:32 -0600309
Alex Klein1699fab2022-09-08 08:46:06 -0600310 PathPrefixDecorator.prefix = opts.filesystem_prefix
311 del opts.filesystem_prefix
Alex Kleina2ceb192018-08-17 11:19:32 -0600312
Alex Klein1699fab2022-09-08 08:46:06 -0600313 opts.Freeze()
314 return opts
Alex Kleina2ceb192018-08-17 11:19:32 -0600315
316
317def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600318 # Parse arguments.
319 opts = ParseArgs(argv)
Alex Kleina2ceb192018-08-17 11:19:32 -0600320
Alex Klein1699fab2022-09-08 08:46:06 -0600321 # Build and validate the board and profile.
322 board = _GetBoard(opts)
Alex Kleina2ceb192018-08-17 11:19:32 -0600323
Alex Klein1699fab2022-09-08 08:46:06 -0600324 if not os.path.exists(board.root):
325 cros_build_lib.Die(
326 "The board has not been setup, please run setup_board " "first."
327 )
Alex Kleina2ceb192018-08-17 11:19:32 -0600328
Alex Klein1699fab2022-09-08 08:46:06 -0600329 try:
330 profile = _GetProfile(opts, board)
331 except ProfileDirectoryNotFoundError as e:
332 cros_build_lib.Die(e)
Alex Kleina2ceb192018-08-17 11:19:32 -0600333
Alex Klein1699fab2022-09-08 08:46:06 -0600334 # Change the profile to the selected.
335 logging.info("Selecting profile: %s for %s", profile.directory, board.root)
Alex Kleina2ceb192018-08-17 11:19:32 -0600336
Alex Klein1699fab2022-09-08 08:46:06 -0600337 try:
338 ChooseProfile(board, profile)
339 except MakeProfileIsNotLinkError as e:
340 cros_build_lib.Die(e)