blob: 7737ec71466e01233b4d1a1aba343ce3f706a337 [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 Frysinger6a2400a2021-02-16 01:43:31 -050026# How many jobs to run in parallel by default? This assumes the jobs are
27# largely I/O bound and do not hit the network.
28DEFAULT_LOCAL_JOBS = min(os.cpu_count(), 8)
29
30
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031class Command(object):
32 """Base class for any command line action in repo.
33 """
34
35 common = False
David Rileye0684ad2017-04-05 00:02:59 -070036 event_log = EventLog()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037 manifest = None
38 _optparse = None
39
Mike Frysinger6a2400a2021-02-16 01:43:31 -050040 # Whether this command supports running in parallel. If greater than 0,
41 # it is the number of parallel jobs to default to.
42 PARALLEL_JOBS = None
43
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -070044 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070045 return False
46
David Pursehouseb148ac92012-11-16 09:33:39 +090047 def ReadEnvironmentOptions(self, opts):
48 """ Set options from environment variables. """
49
50 env_options = self._RegisteredEnvironmentOptions()
51
52 for env_key, opt_key in env_options.items():
53 # Get the user-set option value if any
54 opt_value = getattr(opts, opt_key)
55
56 # If the value is set, it means the user has passed it as a command
57 # line option, and we should use that. Otherwise we can try to set it
58 # with the value from the corresponding environment variable.
59 if opt_value is not None:
60 continue
61
62 env_value = os.environ.get(env_key)
63 if env_value is not None:
64 setattr(opts, opt_key, env_value)
65
66 return opts
67
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068 @property
69 def OptionParser(self):
70 if self._optparse is None:
71 try:
72 me = 'repo %s' % self.NAME
73 usage = self.helpUsage.strip().replace('%prog', me)
74 except AttributeError:
75 usage = 'repo %s' % self.NAME
Mike Frysinger72ebf192020-02-19 01:20:18 -050076 epilog = 'Run `repo help %s` to view the detailed manual.' % self.NAME
77 self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070078 self._Options(self._optparse)
79 return self._optparse
80
81 def _Options(self, p):
82 """Initialize the option parser.
83 """
Mike Frysinger6a2400a2021-02-16 01:43:31 -050084 if self.PARALLEL_JOBS is not None:
85 p.add_option(
86 '-j', '--jobs',
87 type=int, default=self.PARALLEL_JOBS,
88 help='number of jobs to run in parallel (default: %s)' % self.PARALLEL_JOBS)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070089
David Pursehouseb148ac92012-11-16 09:33:39 +090090 def _RegisteredEnvironmentOptions(self):
91 """Get options that can be set from environment variables.
92
93 Return a dictionary mapping environment variable name
94 to option key name that it can override.
95
96 Example: {'REPO_MY_OPTION': 'my_option'}
97
98 Will allow the option with key value 'my_option' to be set
99 from the value in the environment variable named 'REPO_MY_OPTION'.
100
101 Note: This does not work properly for options that are explicitly
102 set to None by the user, or options that are defined with a
103 default value other than None.
104
105 """
106 return {}
107
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108 def Usage(self):
109 """Display usage and terminate.
110 """
111 self.OptionParser.print_usage()
112 sys.exit(1)
113
Mike Frysingerae6cb082019-08-27 01:10:59 -0400114 def ValidateOptions(self, opt, args):
115 """Validate the user options & arguments before executing.
116
117 This is meant to help break the code up into logical steps. Some tips:
118 * Use self.OptionParser.error to display CLI related errors.
119 * Adjust opt member defaults as makes sense.
120 * Adjust the args list, but do so inplace so the caller sees updates.
121 * Try to avoid updating self state. Leave that to Execute.
122 """
123
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124 def Execute(self, opt, args):
125 """Perform the action, after option parsing is complete.
126 """
127 raise NotImplementedError
Conley Owens971de8e2012-04-16 10:36:08 -0700128
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800129 def _ResetPathToProjectMap(self, projects):
130 self._by_path = dict((p.worktree, p) for p in projects)
131
132 def _UpdatePathToProjectMap(self, project):
133 self._by_path[project.worktree] = project
134
Simran Basib9a1b732015-08-20 12:19:28 -0700135 def _GetProjectByPath(self, manifest, path):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800136 project = None
137 if os.path.exists(path):
138 oldpath = None
David Pursehouse5a2517f2020-02-12 14:55:01 +0900139 while (path and
140 path != oldpath and
141 path != manifest.topdir):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800142 try:
143 project = self._by_path[path]
144 break
145 except KeyError:
146 oldpath = path
147 path = os.path.dirname(path)
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700148 if not project and path == manifest.topdir:
149 try:
150 project = self._by_path[path]
151 except KeyError:
152 pass
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800153 else:
154 try:
155 project = self._by_path[path]
156 except KeyError:
157 pass
158 return project
159
Simran Basib9a1b732015-08-20 12:19:28 -0700160 def GetProjects(self, args, manifest=None, groups='', missing_ok=False,
161 submodules_ok=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700162 """A list of projects that match the arguments.
163 """
Simran Basib9a1b732015-08-20 12:19:28 -0700164 if not manifest:
165 manifest = self.manifest
166 all_projects_list = manifest.projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167 result = []
168
Simran Basib9a1b732015-08-20 12:19:28 -0700169 mp = manifest.manifestProject
Colin Cross5acde752012-03-28 20:15:45 -0700170
Graham Christensen0369a062015-07-29 17:02:54 -0500171 if not groups:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700172 groups = mp.config.GetString('manifest.groups')
Colin Crossc39864f2012-04-23 13:41:58 -0700173 if not groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500174 groups = 'default,platform-' + platform.system().lower()
David Pursehouse1d947b32012-10-25 12:23:11 +0900175 groups = [x for x in re.split(r'[,\s]+', groups) if x]
Colin Cross5acde752012-03-28 20:15:45 -0700176
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 if not args:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800178 derived_projects = {}
179 for project in all_projects_list:
180 if submodules_ok or project.sync_s:
181 derived_projects.update((p.name, p)
182 for p in project.GetDerivedSubprojects())
183 all_projects_list.extend(derived_projects.values())
184 for project in all_projects_list:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700185 if (missing_ok or project.Exists) and project.MatchesGroups(groups):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 result.append(project)
187 else:
David James8d201162013-10-11 17:03:19 -0700188 self._ResetPathToProjectMap(all_projects_list)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189
190 for arg in args:
Mike Frysingere778e572019-10-04 14:21:41 -0400191 # We have to filter by manifest groups in case the requested project is
192 # checked out multiple times or differently based on them.
193 projects = [project for project in manifest.GetProjectsWithName(arg)
194 if project.MatchesGroups(groups)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
David James8d201162013-10-11 17:03:19 -0700196 if not projects:
Anthony Newnamdf14a702011-01-09 17:31:57 -0800197 path = os.path.abspath(arg).replace('\\', '/')
Simran Basib9a1b732015-08-20 12:19:28 -0700198 project = self._GetProjectByPath(manifest, path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800200 # If it's not a derived project, update path->project mapping and
201 # search again, as arg might actually point to a derived subproject.
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700202 if (project and not project.Derived and (submodules_ok or
203 project.sync_s)):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800204 search_again = False
205 for subproject in project.GetDerivedSubprojects():
206 self._UpdatePathToProjectMap(subproject)
207 search_again = True
208 if search_again:
Simran Basib9a1b732015-08-20 12:19:28 -0700209 project = self._GetProjectByPath(manifest, path) or project
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210
David James8d201162013-10-11 17:03:19 -0700211 if project:
212 projects = [project]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213
David James8d201162013-10-11 17:03:19 -0700214 if not projects:
215 raise NoSuchProjectError(arg)
216
217 for project in projects:
218 if not missing_ok and not project.Exists:
Mike Frysingere778e572019-10-04 14:21:41 -0400219 raise NoSuchProjectError('%s (%s)' % (arg, project.relpath))
David James8d201162013-10-11 17:03:19 -0700220 if not project.MatchesGroups(groups):
221 raise InvalidProjectGroupsError(arg)
222
223 result.extend(projects)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
225 def _getpath(x):
226 return x.relpath
227 result.sort(key=_getpath)
228 return result
229
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900230 def FindProjects(self, args, inverse=False):
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800231 result = []
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900232 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800233 for project in self.GetProjects(''):
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900234 for pattern in patterns:
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900235 match = pattern.search(project.name) or pattern.search(project.relpath)
236 if not inverse and match:
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800237 result.append(project)
238 break
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900239 if inverse and match:
240 break
241 else:
242 if inverse:
243 result.append(project)
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800244 result.sort(key=lambda project: project.relpath)
245 return result
246
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248class InteractiveCommand(Command):
249 """Command which requires user interaction on the tty and
250 must not run within a pager, even if the user asks to.
251 """
David Pursehouse819827a2020-02-12 15:20:19 +0900252
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700253 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700254 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257class PagedCommand(Command):
258 """Command which defaults to output in a pager, as its
259 display tends to be larger than one screen full.
260 """
David Pursehouse819827a2020-02-12 15:20:19 +0900261
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700262 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700263 return True
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800264
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700265
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800266class MirrorSafeCommand(object):
267 """Command permits itself to run within a mirror,
268 and does not require a working directory.
269 """
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700270
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700271
Dan Willemsen79360642015-08-31 15:45:06 -0700272class GitcAvailableCommand(object):
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700273 """Command that requires GITC to be available, but does
274 not require the local client to be a GITC client.
275 """
Dan Willemsen79360642015-08-31 15:45:06 -0700276
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700277
Dan Willemsen79360642015-08-31 15:45:06 -0700278class GitcClientCommand(object):
279 """Command that requires the local client to be a GITC
280 client.
281 """