blob: be2d6a6e535772f264c0efa223b6012c6e704af5 [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
15import os
16import optparse
Conley Owensd21720d2012-04-16 11:02:21 -070017import platform
Colin Cross5acde752012-03-28 20:15:45 -070018import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import sys
20
David Rileye0684ad2017-04-05 00:02:59 -070021from event_log import EventLog
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022from error import NoSuchProjectError
Colin Cross5acde752012-03-28 20:15:45 -070023from error import InvalidProjectGroupsError
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024
David Pursehouseb148ac92012-11-16 09:33:39 +090025
Mike Frysinger7c871162021-02-16 01:45:39 -050026# Number of projects to submit to a single worker process at a time.
27# This number represents a tradeoff between the overhead of IPC and finer
28# grained opportunity for parallelism. This particular value was chosen by
29# iterating through powers of two until the overall performance no longer
30# improved. The performance of this batch size is not a function of the
31# number of cores on the system.
32WORKER_BATCH_SIZE = 32
33
34
Mike Frysinger6a2400a2021-02-16 01:43:31 -050035# How many jobs to run in parallel by default? This assumes the jobs are
36# largely I/O bound and do not hit the network.
37DEFAULT_LOCAL_JOBS = min(os.cpu_count(), 8)
38
39
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040class Command(object):
41 """Base class for any command line action in repo.
42 """
43
44 common = False
David Rileye0684ad2017-04-05 00:02:59 -070045 event_log = EventLog()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046 manifest = None
47 _optparse = None
48
Mike Frysinger6a2400a2021-02-16 01:43:31 -050049 # Whether this command supports running in parallel. If greater than 0,
50 # it is the number of parallel jobs to default to.
51 PARALLEL_JOBS = None
52
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -070053 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070054 return False
55
David Pursehouseb148ac92012-11-16 09:33:39 +090056 def ReadEnvironmentOptions(self, opts):
57 """ Set options from environment variables. """
58
59 env_options = self._RegisteredEnvironmentOptions()
60
61 for env_key, opt_key in env_options.items():
62 # Get the user-set option value if any
63 opt_value = getattr(opts, opt_key)
64
65 # If the value is set, it means the user has passed it as a command
66 # line option, and we should use that. Otherwise we can try to set it
67 # with the value from the corresponding environment variable.
68 if opt_value is not None:
69 continue
70
71 env_value = os.environ.get(env_key)
72 if env_value is not None:
73 setattr(opts, opt_key, env_value)
74
75 return opts
76
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077 @property
78 def OptionParser(self):
79 if self._optparse is None:
80 try:
81 me = 'repo %s' % self.NAME
82 usage = self.helpUsage.strip().replace('%prog', me)
83 except AttributeError:
84 usage = 'repo %s' % self.NAME
Mike Frysinger72ebf192020-02-19 01:20:18 -050085 epilog = 'Run `repo help %s` to view the detailed manual.' % self.NAME
86 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
Mike Frysinger9180a072021-04-13 14:57:40 -040087 self._CommonOptions(self._optparse)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088 self._Options(self._optparse)
89 return self._optparse
90
Mike Frysinger9180a072021-04-13 14:57:40 -040091 def _CommonOptions(self, p, opt_v=True):
92 """Initialize the option parser with common options.
93
94 These will show up for *all* subcommands, so use sparingly.
95 NB: Keep in sync with repo:InitParser().
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070096 """
Mike Frysinger9180a072021-04-13 14:57:40 -040097 g = p.add_option_group('Logging options')
98 opts = ['-v'] if opt_v else []
99 g.add_option(*opts, '--verbose',
100 dest='output_mode', action='store_true',
101 help='show all output')
102 g.add_option('-q', '--quiet',
103 dest='output_mode', action='store_false',
104 help='only show errors')
105
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500106 if self.PARALLEL_JOBS is not None:
107 p.add_option(
108 '-j', '--jobs',
109 type=int, default=self.PARALLEL_JOBS,
110 help='number of jobs to run in parallel (default: %s)' % self.PARALLEL_JOBS)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111
Mike Frysinger9180a072021-04-13 14:57:40 -0400112 def _Options(self, p):
113 """Initialize the option parser with subcommand-specific options."""
114
David Pursehouseb148ac92012-11-16 09:33:39 +0900115 def _RegisteredEnvironmentOptions(self):
116 """Get options that can be set from environment variables.
117
118 Return a dictionary mapping environment variable name
119 to option key name that it can override.
120
121 Example: {'REPO_MY_OPTION': 'my_option'}
122
123 Will allow the option with key value 'my_option' to be set
124 from the value in the environment variable named 'REPO_MY_OPTION'.
125
126 Note: This does not work properly for options that are explicitly
127 set to None by the user, or options that are defined with a
128 default value other than None.
129
130 """
131 return {}
132
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 def Usage(self):
134 """Display usage and terminate.
135 """
136 self.OptionParser.print_usage()
137 sys.exit(1)
138
Mike Frysinger9180a072021-04-13 14:57:40 -0400139 def CommonValidateOptions(self, opt, args):
140 """Validate common options."""
141 opt.quiet = opt.output_mode is False
142 opt.verbose = opt.output_mode is True
143
Mike Frysingerae6cb082019-08-27 01:10:59 -0400144 def ValidateOptions(self, opt, args):
145 """Validate the user options & arguments before executing.
146
147 This is meant to help break the code up into logical steps. Some tips:
148 * Use self.OptionParser.error to display CLI related errors.
149 * Adjust opt member defaults as makes sense.
150 * Adjust the args list, but do so inplace so the caller sees updates.
151 * Try to avoid updating self state. Leave that to Execute.
152 """
153
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 def Execute(self, opt, args):
155 """Perform the action, after option parsing is complete.
156 """
157 raise NotImplementedError
Conley Owens971de8e2012-04-16 10:36:08 -0700158
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800159 def _ResetPathToProjectMap(self, projects):
160 self._by_path = dict((p.worktree, p) for p in projects)
161
162 def _UpdatePathToProjectMap(self, project):
163 self._by_path[project.worktree] = project
164
Simran Basib9a1b732015-08-20 12:19:28 -0700165 def _GetProjectByPath(self, manifest, path):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800166 project = None
167 if os.path.exists(path):
168 oldpath = None
David Pursehouse5a2517f2020-02-12 14:55:01 +0900169 while (path and
170 path != oldpath and
171 path != manifest.topdir):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800172 try:
173 project = self._by_path[path]
174 break
175 except KeyError:
176 oldpath = path
177 path = os.path.dirname(path)
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700178 if not project and path == manifest.topdir:
179 try:
180 project = self._by_path[path]
181 except KeyError:
182 pass
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800183 else:
184 try:
185 project = self._by_path[path]
186 except KeyError:
187 pass
188 return project
189
Simran Basib9a1b732015-08-20 12:19:28 -0700190 def GetProjects(self, args, manifest=None, groups='', missing_ok=False,
191 submodules_ok=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192 """A list of projects that match the arguments.
193 """
Simran Basib9a1b732015-08-20 12:19:28 -0700194 if not manifest:
195 manifest = self.manifest
196 all_projects_list = manifest.projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197 result = []
198
Simran Basib9a1b732015-08-20 12:19:28 -0700199 mp = manifest.manifestProject
Colin Cross5acde752012-03-28 20:15:45 -0700200
Graham Christensen0369a062015-07-29 17:02:54 -0500201 if not groups:
Raman Tenneti080877e2021-03-09 15:19:06 -0800202 groups = manifest.GetGroupsStr()
David Pursehouse1d947b32012-10-25 12:23:11 +0900203 groups = [x for x in re.split(r'[,\s]+', groups) if x]
Colin Cross5acde752012-03-28 20:15:45 -0700204
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 if not args:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800206 derived_projects = {}
207 for project in all_projects_list:
208 if submodules_ok or project.sync_s:
209 derived_projects.update((p.name, p)
210 for p in project.GetDerivedSubprojects())
211 all_projects_list.extend(derived_projects.values())
212 for project in all_projects_list:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700213 if (missing_ok or project.Exists) and project.MatchesGroups(groups):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 result.append(project)
215 else:
David James8d201162013-10-11 17:03:19 -0700216 self._ResetPathToProjectMap(all_projects_list)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218 for arg in args:
Mike Frysingere778e572019-10-04 14:21:41 -0400219 # We have to filter by manifest groups in case the requested project is
220 # checked out multiple times or differently based on them.
221 projects = [project for project in manifest.GetProjectsWithName(arg)
222 if project.MatchesGroups(groups)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
David James8d201162013-10-11 17:03:19 -0700224 if not projects:
Anthony Newnamdf14a702011-01-09 17:31:57 -0800225 path = os.path.abspath(arg).replace('\\', '/')
Simran Basib9a1b732015-08-20 12:19:28 -0700226 project = self._GetProjectByPath(manifest, path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800228 # If it's not a derived project, update path->project mapping and
229 # search again, as arg might actually point to a derived subproject.
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700230 if (project and not project.Derived and (submodules_ok or
231 project.sync_s)):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800232 search_again = False
233 for subproject in project.GetDerivedSubprojects():
234 self._UpdatePathToProjectMap(subproject)
235 search_again = True
236 if search_again:
Simran Basib9a1b732015-08-20 12:19:28 -0700237 project = self._GetProjectByPath(manifest, path) or project
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
David James8d201162013-10-11 17:03:19 -0700239 if project:
240 projects = [project]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
David James8d201162013-10-11 17:03:19 -0700242 if not projects:
243 raise NoSuchProjectError(arg)
244
245 for project in projects:
246 if not missing_ok and not project.Exists:
Mike Frysingere778e572019-10-04 14:21:41 -0400247 raise NoSuchProjectError('%s (%s)' % (arg, project.relpath))
David James8d201162013-10-11 17:03:19 -0700248 if not project.MatchesGroups(groups):
249 raise InvalidProjectGroupsError(arg)
250
251 result.extend(projects)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
253 def _getpath(x):
254 return x.relpath
255 result.sort(key=_getpath)
256 return result
257
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900258 def FindProjects(self, args, inverse=False):
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800259 result = []
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900260 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800261 for project in self.GetProjects(''):
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900262 for pattern in patterns:
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900263 match = pattern.search(project.name) or pattern.search(project.relpath)
264 if not inverse and match:
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800265 result.append(project)
266 break
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900267 if inverse and match:
268 break
269 else:
270 if inverse:
271 result.append(project)
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800272 result.sort(key=lambda project: project.relpath)
273 return result
274
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700275
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700276class InteractiveCommand(Command):
277 """Command which requires user interaction on the tty and
278 must not run within a pager, even if the user asks to.
279 """
David Pursehouse819827a2020-02-12 15:20:19 +0900280
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700281 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700282 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700283
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700284
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285class PagedCommand(Command):
286 """Command which defaults to output in a pager, as its
287 display tends to be larger than one screen full.
288 """
David Pursehouse819827a2020-02-12 15:20:19 +0900289
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700290 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700291 return True
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800292
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700293
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800294class MirrorSafeCommand(object):
295 """Command permits itself to run within a mirror,
296 and does not require a working directory.
297 """
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700298
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700299
Dan Willemsen79360642015-08-31 15:45:06 -0700300class GitcAvailableCommand(object):
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700301 """Command that requires GITC to be available, but does
302 not require the local client to be a GITC client.
303 """
Dan Willemsen79360642015-08-31 15:45:06 -0700304
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700305
Dan Willemsen79360642015-08-31 15:45:06 -0700306class GitcClientCommand(object):
307 """Command that requires the local client to be a GITC
308 client.
309 """