blob: c32a095cf50d5f0dcb72d5d70d51b10ea6118000 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Kuang-che Wu39ffd992024-10-18 23:32:08 +080015import contextlib
Mike Frysingerb5d075d2021-03-01 00:56:38 -050016import multiprocessing
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import optparse
Mike Frysinger64477332023-08-21 21:20:32 -040018import os
Colin Cross5acde752012-03-28 20:15:45 -070019import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020
Colin Cross5acde752012-03-28 20:15:45 -070021from error import InvalidProjectGroupsError
Mike Frysinger64477332023-08-21 21:20:32 -040022from error import NoSuchProjectError
Jason Changf9aacd42023-08-03 14:38:00 -070023from error import RepoExitError
Mike Frysinger64477332023-08-21 21:20:32 -040024from event_log import EventLog
Mike Frysingerb5d075d2021-03-01 00:56:38 -050025import progress
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026
David Pursehouseb148ac92012-11-16 09:33:39 +090027
Mike Frysingerdf8b1cb2021-07-26 15:59:20 -040028# Are we generating man-pages?
Gavin Makea2e3302023-03-11 06:46:20 +000029GENERATE_MANPAGES = os.environ.get("_REPO_GENERATE_MANPAGES_") == " indeed! "
Mike Frysingerdf8b1cb2021-07-26 15:59:20 -040030
31
Mike Frysinger7c871162021-02-16 01:45:39 -050032# Number of projects to submit to a single worker process at a time.
33# This number represents a tradeoff between the overhead of IPC and finer
34# grained opportunity for parallelism. This particular value was chosen by
35# iterating through powers of two until the overall performance no longer
36# improved. The performance of this batch size is not a function of the
37# number of cores on the system.
38WORKER_BATCH_SIZE = 32
39
40
Mike Frysinger6a2400a2021-02-16 01:43:31 -050041# How many jobs to run in parallel by default? This assumes the jobs are
42# largely I/O bound and do not hit the network.
43DEFAULT_LOCAL_JOBS = min(os.cpu_count(), 8)
44
45
Jason Changf9aacd42023-08-03 14:38:00 -070046class UsageError(RepoExitError):
47 """Exception thrown with invalid command usage."""
48
49
Mike Frysingerd4aee652023-10-19 05:13:32 -040050class Command:
Gavin Makea2e3302023-03-11 06:46:20 +000051 """Base class for any command line action in repo."""
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052
Gavin Makea2e3302023-03-11 06:46:20 +000053 # Singleton for all commands to track overall repo command execution and
54 # provide event summary to callers. Only used by sync subcommand currently.
55 #
56 # NB: This is being replaced by git trace2 events. See git_trace2_event_log.
57 event_log = EventLog()
Mike Frysingerd88b3692021-06-14 16:09:29 -040058
Gavin Makea2e3302023-03-11 06:46:20 +000059 # Whether this command is a "common" one, i.e. whether the user would
60 # commonly use it or it's a more uncommon command. This is used by the help
61 # command to show short-vs-full summaries.
62 COMMON = False
Mike Frysinger4f210542021-06-14 16:05:19 -040063
Gavin Makea2e3302023-03-11 06:46:20 +000064 # Whether this command supports running in parallel. If greater than 0,
65 # it is the number of parallel jobs to default to.
66 PARALLEL_JOBS = None
Mike Frysinger6a2400a2021-02-16 01:43:31 -050067
Gavin Makea2e3302023-03-11 06:46:20 +000068 # Whether this command supports Multi-manifest. If False, then main.py will
69 # iterate over the manifests and invoke the command once per (sub)manifest.
70 # This is only checked after calling ValidateOptions, so that partially
71 # migrated subcommands can set it to False.
72 MULTI_MANIFEST_SUPPORT = True
LaMont Jonescc879a92021-11-18 22:40:18 +000073
Kuang-che Wu39ffd992024-10-18 23:32:08 +080074 # Shared data across parallel execution workers.
75 _parallel_context = None
76
77 @classmethod
78 def get_parallel_context(cls):
79 assert cls._parallel_context is not None
80 return cls._parallel_context
81
Gavin Makea2e3302023-03-11 06:46:20 +000082 def __init__(
83 self,
84 repodir=None,
85 client=None,
86 manifest=None,
Gavin Makea2e3302023-03-11 06:46:20 +000087 git_event_log=None,
88 outer_client=None,
89 outer_manifest=None,
90 ):
91 self.repodir = repodir
92 self.client = client
93 self.outer_client = outer_client or client
94 self.manifest = manifest
Gavin Makea2e3302023-03-11 06:46:20 +000095 self.git_event_log = git_event_log
96 self.outer_manifest = outer_manifest
Mike Frysingerd58d0dd2021-06-14 16:17:27 -040097
Gavin Makea2e3302023-03-11 06:46:20 +000098 # Cache for the OptionParser property.
99 self._optparse = None
Mike Frysingerd58d0dd2021-06-14 16:17:27 -0400100
Gavin Makea2e3302023-03-11 06:46:20 +0000101 def WantPager(self, _opt):
102 return False
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700103
Gavin Makea2e3302023-03-11 06:46:20 +0000104 def ReadEnvironmentOptions(self, opts):
105 """Set options from environment variables."""
David Pursehouseb148ac92012-11-16 09:33:39 +0900106
Gavin Makea2e3302023-03-11 06:46:20 +0000107 env_options = self._RegisteredEnvironmentOptions()
David Pursehouseb148ac92012-11-16 09:33:39 +0900108
Gavin Makea2e3302023-03-11 06:46:20 +0000109 for env_key, opt_key in env_options.items():
110 # Get the user-set option value if any
111 opt_value = getattr(opts, opt_key)
David Pursehouseb148ac92012-11-16 09:33:39 +0900112
Gavin Makea2e3302023-03-11 06:46:20 +0000113 # If the value is set, it means the user has passed it as a command
114 # line option, and we should use that. Otherwise we can try to set
115 # it with the value from the corresponding environment variable.
116 if opt_value is not None:
117 continue
David Pursehouseb148ac92012-11-16 09:33:39 +0900118
Gavin Makea2e3302023-03-11 06:46:20 +0000119 env_value = os.environ.get(env_key)
120 if env_value is not None:
121 setattr(opts, opt_key, env_value)
David Pursehouseb148ac92012-11-16 09:33:39 +0900122
Gavin Makea2e3302023-03-11 06:46:20 +0000123 return opts
David Pursehouseb148ac92012-11-16 09:33:39 +0900124
Gavin Makea2e3302023-03-11 06:46:20 +0000125 @property
126 def OptionParser(self):
127 if self._optparse is None:
128 try:
129 me = "repo %s" % self.NAME
130 usage = self.helpUsage.strip().replace("%prog", me)
131 except AttributeError:
132 usage = "repo %s" % self.NAME
133 epilog = (
134 "Run `repo help %s` to view the detailed manual." % self.NAME
135 )
136 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
137 self._CommonOptions(self._optparse)
138 self._Options(self._optparse)
139 return self._optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140
Gavin Makea2e3302023-03-11 06:46:20 +0000141 def _CommonOptions(self, p, opt_v=True):
142 """Initialize the option parser with common options.
Mike Frysinger9180a072021-04-13 14:57:40 -0400143
Gavin Makea2e3302023-03-11 06:46:20 +0000144 These will show up for *all* subcommands, so use sparingly.
145 NB: Keep in sync with repo:InitParser().
146 """
147 g = p.add_option_group("Logging options")
148 opts = ["-v"] if opt_v else []
149 g.add_option(
150 *opts,
151 "--verbose",
152 dest="output_mode",
153 action="store_true",
154 help="show all output",
155 )
156 g.add_option(
157 "-q",
158 "--quiet",
159 dest="output_mode",
160 action="store_false",
161 help="only show errors",
162 )
Mike Frysinger9180a072021-04-13 14:57:40 -0400163
Gavin Makea2e3302023-03-11 06:46:20 +0000164 if self.PARALLEL_JOBS is not None:
165 default = "based on number of CPU cores"
166 if not GENERATE_MANPAGES:
167 # Only include active cpu count if we aren't generating man
168 # pages.
169 default = f"%default; {default}"
170 p.add_option(
171 "-j",
172 "--jobs",
173 type=int,
174 default=self.PARALLEL_JOBS,
175 help=f"number of jobs to run in parallel (default: {default})",
176 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177
Gavin Makea2e3302023-03-11 06:46:20 +0000178 m = p.add_option_group("Multi-manifest options")
179 m.add_option(
180 "--outer-manifest",
181 action="store_true",
182 default=None,
183 help="operate starting at the outermost manifest",
184 )
185 m.add_option(
186 "--no-outer-manifest",
187 dest="outer_manifest",
188 action="store_false",
189 help="do not operate on outer manifests",
190 )
191 m.add_option(
192 "--this-manifest-only",
193 action="store_true",
194 default=None,
195 help="only operate on this (sub)manifest",
196 )
197 m.add_option(
198 "--no-this-manifest-only",
199 "--all-manifests",
200 dest="this_manifest_only",
201 action="store_false",
202 help="operate on this manifest and its submanifests",
203 )
LaMont Jonescc879a92021-11-18 22:40:18 +0000204
Gavin Makea2e3302023-03-11 06:46:20 +0000205 def _Options(self, p):
206 """Initialize the option parser with subcommand-specific options."""
Mike Frysinger9180a072021-04-13 14:57:40 -0400207
Gavin Makea2e3302023-03-11 06:46:20 +0000208 def _RegisteredEnvironmentOptions(self):
209 """Get options that can be set from environment variables.
David Pursehouseb148ac92012-11-16 09:33:39 +0900210
Gavin Makea2e3302023-03-11 06:46:20 +0000211 Return a dictionary mapping environment variable name
212 to option key name that it can override.
David Pursehouseb148ac92012-11-16 09:33:39 +0900213
Gavin Makea2e3302023-03-11 06:46:20 +0000214 Example: {'REPO_MY_OPTION': 'my_option'}
David Pursehouseb148ac92012-11-16 09:33:39 +0900215
Gavin Makea2e3302023-03-11 06:46:20 +0000216 Will allow the option with key value 'my_option' to be set
217 from the value in the environment variable named 'REPO_MY_OPTION'.
David Pursehouseb148ac92012-11-16 09:33:39 +0900218
Gavin Makea2e3302023-03-11 06:46:20 +0000219 Note: This does not work properly for options that are explicitly
220 set to None by the user, or options that are defined with a
221 default value other than None.
David Pursehouseb148ac92012-11-16 09:33:39 +0900222
Gavin Makea2e3302023-03-11 06:46:20 +0000223 """
224 return {}
David Pursehouseb148ac92012-11-16 09:33:39 +0900225
Gavin Makea2e3302023-03-11 06:46:20 +0000226 def Usage(self):
227 """Display usage and terminate."""
228 self.OptionParser.print_usage()
Jason Changf9aacd42023-08-03 14:38:00 -0700229 raise UsageError()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Gavin Makea2e3302023-03-11 06:46:20 +0000231 def CommonValidateOptions(self, opt, args):
232 """Validate common options."""
233 opt.quiet = opt.output_mode is False
234 opt.verbose = opt.output_mode is True
235 if opt.outer_manifest is None:
236 # By default, treat multi-manifest instances as a single manifest
237 # from the user's perspective.
238 opt.outer_manifest = True
Mike Frysinger9180a072021-04-13 14:57:40 -0400239
Gavin Makea2e3302023-03-11 06:46:20 +0000240 def ValidateOptions(self, opt, args):
241 """Validate the user options & arguments before executing.
Mike Frysingerae6cb082019-08-27 01:10:59 -0400242
Gavin Makea2e3302023-03-11 06:46:20 +0000243 This is meant to help break the code up into logical steps. Some tips:
244 * Use self.OptionParser.error to display CLI related errors.
245 * Adjust opt member defaults as makes sense.
246 * Adjust the args list, but do so inplace so the caller sees updates.
247 * Try to avoid updating self state. Leave that to Execute.
248 """
Mike Frysingerae6cb082019-08-27 01:10:59 -0400249
Gavin Makea2e3302023-03-11 06:46:20 +0000250 def Execute(self, opt, args):
251 """Perform the action, after option parsing is complete."""
252 raise NotImplementedError
Conley Owens971de8e2012-04-16 10:36:08 -0700253
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800254 @classmethod
255 @contextlib.contextmanager
256 def ParallelContext(cls):
257 """Obtains the context, which is shared to ExecuteInParallel workers.
258
259 Callers can store data in the context dict before invocation of
260 ExecuteInParallel. The dict will then be shared to child workers of
261 ExecuteInParallel.
262 """
263 assert cls._parallel_context is None
264 cls._parallel_context = {}
265 try:
266 yield
267 finally:
268 cls._parallel_context = None
269
270 @classmethod
Kuang-che Wu8da48612024-10-22 21:04:41 +0800271 def _InitParallelWorker(cls, context, initializer):
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800272 cls._parallel_context = context
Kuang-che Wu8da48612024-10-22 21:04:41 +0800273 if initializer:
274 initializer()
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800275
276 @classmethod
Gavin Makea2e3302023-03-11 06:46:20 +0000277 def ExecuteInParallel(
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800278 cls,
279 jobs,
280 func,
281 inputs,
282 callback,
283 output=None,
284 ordered=False,
285 chunksize=WORKER_BATCH_SIZE,
Kuang-che Wu8da48612024-10-22 21:04:41 +0800286 initializer=None,
Gavin Makea2e3302023-03-11 06:46:20 +0000287 ):
288 """Helper for managing parallel execution boiler plate.
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500289
Gavin Makea2e3302023-03-11 06:46:20 +0000290 For subcommands that can easily split their work up.
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500291
Gavin Makea2e3302023-03-11 06:46:20 +0000292 Args:
293 jobs: How many parallel processes to use.
294 func: The function to apply to each of the |inputs|. Usually a
295 functools.partial for wrapping additional arguments. It will be
296 run in a separate process, so it must be pickalable, so nested
297 functions won't work. Methods on the subcommand Command class
298 should work.
299 inputs: The list of items to process. Must be a list.
300 callback: The function to pass the results to for processing. It
301 will be executed in the main thread and process the results of
302 |func| as they become available. Thus it may be a local nested
303 function. Its return value is passed back directly. It takes
304 three arguments:
305 - The processing pool (or None with one job).
306 - The |output| argument.
307 - An iterator for the results.
308 output: An output manager. May be progress.Progess or
309 color.Coloring.
310 ordered: Whether the jobs should be processed in order.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800311 chunksize: The number of jobs processed in batch by parallel
312 workers.
Kuang-che Wu8da48612024-10-22 21:04:41 +0800313 initializer: Worker initializer.
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500314
Gavin Makea2e3302023-03-11 06:46:20 +0000315 Returns:
316 The |callback| function's results are returned.
317 """
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800318 try:
Gavin Makea2e3302023-03-11 06:46:20 +0000319 # NB: Multiprocessing is heavy, so don't spin it up for one job.
320 if len(inputs) == 1 or jobs == 1:
321 return callback(None, output, (func(x) for x in inputs))
322 else:
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800323 with multiprocessing.Pool(
324 jobs,
Kuang-che Wu8da48612024-10-22 21:04:41 +0800325 initializer=cls._InitParallelWorker,
326 initargs=(cls._parallel_context, initializer),
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800327 ) as pool:
Gavin Makea2e3302023-03-11 06:46:20 +0000328 submit = pool.imap if ordered else pool.imap_unordered
329 return callback(
330 pool,
331 output,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800332 submit(func, inputs, chunksize=chunksize),
Gavin Makea2e3302023-03-11 06:46:20 +0000333 )
334 finally:
335 if isinstance(output, progress.Progress):
336 output.end()
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800337
Gavin Makea2e3302023-03-11 06:46:20 +0000338 def _ResetPathToProjectMap(self, projects):
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545339 self._by_path = {p.worktree: p for p in projects}
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000340
Gavin Makea2e3302023-03-11 06:46:20 +0000341 def _UpdatePathToProjectMap(self, project):
342 self._by_path[project.worktree] = project
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000343
Gavin Makea2e3302023-03-11 06:46:20 +0000344 def _GetProjectByPath(self, manifest, path):
345 project = None
346 if os.path.exists(path):
347 oldpath = None
348 while path and path != oldpath and path != manifest.topdir:
349 try:
350 project = self._by_path[path]
351 break
352 except KeyError:
353 oldpath = path
354 path = os.path.dirname(path)
355 if not project and path == manifest.topdir:
356 try:
357 project = self._by_path[path]
358 except KeyError:
359 pass
360 else:
361 try:
362 project = self._by_path[path]
363 except KeyError:
364 pass
365 return project
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366
Gavin Makea2e3302023-03-11 06:46:20 +0000367 def GetProjects(
368 self,
369 args,
370 manifest=None,
371 groups="",
372 missing_ok=False,
373 submodules_ok=False,
374 all_manifests=False,
375 ):
376 """A list of projects that match the arguments.
Colin Cross5acde752012-03-28 20:15:45 -0700377
Gavin Makea2e3302023-03-11 06:46:20 +0000378 Args:
379 args: a list of (case-insensitive) strings, projects to search for.
380 manifest: an XmlManifest, the manifest to use, or None for default.
381 groups: a string, the manifest groups in use.
382 missing_ok: a boolean, whether to allow missing projects.
383 submodules_ok: a boolean, whether to allow submodules.
384 all_manifests: a boolean, if True then all manifests and
385 submanifests are used. If False, then only the local
386 (sub)manifest is used.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700387
Gavin Makea2e3302023-03-11 06:46:20 +0000388 Returns:
389 A list of matching Project instances.
390 """
391 if all_manifests:
392 if not manifest:
393 manifest = self.manifest.outer_client
394 all_projects_list = manifest.all_projects
395 else:
396 if not manifest:
397 manifest = self.manifest
398 all_projects_list = manifest.projects
399 result = []
400
401 if not groups:
402 groups = manifest.GetGroupsStr()
403 groups = [x for x in re.split(r"[,\s]+", groups) if x]
404
405 if not args:
406 derived_projects = {}
407 for project in all_projects_list:
408 if submodules_ok or project.sync_s:
409 derived_projects.update(
410 (p.name, p) for p in project.GetDerivedSubprojects()
411 )
412 all_projects_list.extend(derived_projects.values())
413 for project in all_projects_list:
414 if (missing_ok or project.Exists) and project.MatchesGroups(
415 groups
416 ):
417 result.append(project)
418 else:
419 self._ResetPathToProjectMap(all_projects_list)
420
421 for arg in args:
422 # We have to filter by manifest groups in case the requested
423 # project is checked out multiple times or differently based on
424 # them.
425 projects = [
426 project
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +0100427 for project in manifest.GetProjectsWithName(
Gavin Makea2e3302023-03-11 06:46:20 +0000428 arg, all_manifests=all_manifests
429 )
430 if project.MatchesGroups(groups)
431 ]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700432
Gavin Makea2e3302023-03-11 06:46:20 +0000433 if not projects:
434 path = os.path.abspath(arg).replace("\\", "/")
435 tree = manifest
436 if all_manifests:
437 # Look for the deepest matching submanifest.
438 for tree in reversed(list(manifest.all_manifests)):
439 if path.startswith(tree.topdir):
440 break
441 project = self._GetProjectByPath(tree, path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442
Gavin Makea2e3302023-03-11 06:46:20 +0000443 # If it's not a derived project, update path->project
444 # mapping and search again, as arg might actually point to
445 # a derived subproject.
446 if (
447 project
448 and not project.Derived
449 and (submodules_ok or project.sync_s)
450 ):
451 search_again = False
452 for subproject in project.GetDerivedSubprojects():
453 self._UpdatePathToProjectMap(subproject)
454 search_again = True
455 if search_again:
456 project = (
457 self._GetProjectByPath(manifest, path)
458 or project
459 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700460
Gavin Makea2e3302023-03-11 06:46:20 +0000461 if project:
462 projects = [project]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700463
Gavin Makea2e3302023-03-11 06:46:20 +0000464 if not projects:
465 raise NoSuchProjectError(arg)
David James8d201162013-10-11 17:03:19 -0700466
Gavin Makea2e3302023-03-11 06:46:20 +0000467 for project in projects:
468 if not missing_ok and not project.Exists:
469 raise NoSuchProjectError(
470 "%s (%s)"
471 % (arg, project.RelPath(local=not all_manifests))
472 )
473 if not project.MatchesGroups(groups):
474 raise InvalidProjectGroupsError(arg)
David James8d201162013-10-11 17:03:19 -0700475
Gavin Makea2e3302023-03-11 06:46:20 +0000476 result.extend(projects)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700477
Gavin Makea2e3302023-03-11 06:46:20 +0000478 def _getpath(x):
479 return x.relpath
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700480
Gavin Makea2e3302023-03-11 06:46:20 +0000481 result.sort(key=_getpath)
482 return result
LaMont Jonescc879a92021-11-18 22:40:18 +0000483
Gavin Makea2e3302023-03-11 06:46:20 +0000484 def FindProjects(self, args, inverse=False, all_manifests=False):
485 """Find projects from command line arguments.
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800486
Gavin Makea2e3302023-03-11 06:46:20 +0000487 Args:
488 args: a list of (case-insensitive) strings, projects to search for.
489 inverse: a boolean, if True, then projects not matching any |args|
490 are returned.
491 all_manifests: a boolean, if True then all manifests and
492 submanifests are used. If False, then only the local
493 (sub)manifest is used.
494 """
495 result = []
496 patterns = [re.compile(r"%s" % a, re.IGNORECASE) for a in args]
497 for project in self.GetProjects("", all_manifests=all_manifests):
498 paths = [project.name, project.RelPath(local=not all_manifests)]
499 for pattern in patterns:
500 match = any(pattern.search(x) for x in paths)
501 if not inverse and match:
502 result.append(project)
503 break
504 if inverse and match:
505 break
506 else:
507 if inverse:
508 result.append(project)
509 result.sort(
510 key=lambda project: (project.manifest.path_prefix, project.relpath)
511 )
512 return result
LaMont Jonescc879a92021-11-18 22:40:18 +0000513
Gavin Makea2e3302023-03-11 06:46:20 +0000514 def ManifestList(self, opt):
515 """Yields all of the manifests to traverse.
516
517 Args:
518 opt: The command options.
519 """
520 top = self.outer_manifest
521 if not opt.outer_manifest or opt.this_manifest_only:
522 top = self.manifest
523 yield top
524 if not opt.this_manifest_only:
Jason R. Coombs8dd85212023-10-20 06:48:20 -0400525 yield from top.all_children
LaMont Jonescc879a92021-11-18 22:40:18 +0000526
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528class InteractiveCommand(Command):
Gavin Makea2e3302023-03-11 06:46:20 +0000529 """Command which requires user interaction on the tty and must not run
530 within a pager, even if the user asks to.
531 """
David Pursehouse819827a2020-02-12 15:20:19 +0900532
Gavin Makea2e3302023-03-11 06:46:20 +0000533 def WantPager(self, _opt):
534 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700535
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537class PagedCommand(Command):
Gavin Makea2e3302023-03-11 06:46:20 +0000538 """Command which defaults to output in a pager, as its display tends to be
539 larger than one screen full.
540 """
David Pursehouse819827a2020-02-12 15:20:19 +0900541
Gavin Makea2e3302023-03-11 06:46:20 +0000542 def WantPager(self, _opt):
543 return True
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800544
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700545
Mike Frysingerd4aee652023-10-19 05:13:32 -0400546class MirrorSafeCommand:
Gavin Makea2e3302023-03-11 06:46:20 +0000547 """Command permits itself to run within a mirror, and does not require a
548 working directory.
549 """