blob: 2ff0a3447e7388acb7448b99c2e46645b95daf4f [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import os
17import optparse
Conley Owensd21720d2012-04-16 11:02:21 -070018import platform
Colin Cross5acde752012-03-28 20:15:45 -070019import re
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import sys
21
22from 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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026class Command(object):
27 """Base class for any command line action in repo.
28 """
29
30 common = False
31 manifest = None
32 _optparse = None
33
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -070034 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070035 return False
36
David Pursehouseb148ac92012-11-16 09:33:39 +090037 def ReadEnvironmentOptions(self, opts):
38 """ Set options from environment variables. """
39
40 env_options = self._RegisteredEnvironmentOptions()
41
42 for env_key, opt_key in env_options.items():
43 # Get the user-set option value if any
44 opt_value = getattr(opts, opt_key)
45
46 # If the value is set, it means the user has passed it as a command
47 # line option, and we should use that. Otherwise we can try to set it
48 # with the value from the corresponding environment variable.
49 if opt_value is not None:
50 continue
51
52 env_value = os.environ.get(env_key)
53 if env_value is not None:
54 setattr(opts, opt_key, env_value)
55
56 return opts
57
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058 @property
59 def OptionParser(self):
60 if self._optparse is None:
61 try:
62 me = 'repo %s' % self.NAME
63 usage = self.helpUsage.strip().replace('%prog', me)
64 except AttributeError:
65 usage = 'repo %s' % self.NAME
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -070066 self._optparse = optparse.OptionParser(usage=usage)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067 self._Options(self._optparse)
68 return self._optparse
69
70 def _Options(self, p):
71 """Initialize the option parser.
72 """
73
David Pursehouseb148ac92012-11-16 09:33:39 +090074 def _RegisteredEnvironmentOptions(self):
75 """Get options that can be set from environment variables.
76
77 Return a dictionary mapping environment variable name
78 to option key name that it can override.
79
80 Example: {'REPO_MY_OPTION': 'my_option'}
81
82 Will allow the option with key value 'my_option' to be set
83 from the value in the environment variable named 'REPO_MY_OPTION'.
84
85 Note: This does not work properly for options that are explicitly
86 set to None by the user, or options that are defined with a
87 default value other than None.
88
89 """
90 return {}
91
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070092 def Usage(self):
93 """Display usage and terminate.
94 """
95 self.OptionParser.print_usage()
96 sys.exit(1)
97
98 def Execute(self, opt, args):
99 """Perform the action, after option parsing is complete.
100 """
101 raise NotImplementedError
Conley Owens971de8e2012-04-16 10:36:08 -0700102
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800103 def _ResetPathToProjectMap(self, projects):
104 self._by_path = dict((p.worktree, p) for p in projects)
105
106 def _UpdatePathToProjectMap(self, project):
107 self._by_path[project.worktree] = project
108
Simran Basib9a1b732015-08-20 12:19:28 -0700109 def _GetProjectByPath(self, manifest, path):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800110 project = None
111 if os.path.exists(path):
112 oldpath = None
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700113 while path and \
114 path != oldpath and \
115 path != manifest.topdir:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800116 try:
117 project = self._by_path[path]
118 break
119 except KeyError:
120 oldpath = path
121 path = os.path.dirname(path)
Mark E. Hamiltonf9fe3e12016-02-23 18:10:42 -0700122 if not project and path == manifest.topdir:
123 try:
124 project = self._by_path[path]
125 except KeyError:
126 pass
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800127 else:
128 try:
129 project = self._by_path[path]
130 except KeyError:
131 pass
132 return project
133
Simran Basib9a1b732015-08-20 12:19:28 -0700134 def GetProjects(self, args, manifest=None, groups='', missing_ok=False,
135 submodules_ok=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136 """A list of projects that match the arguments.
137 """
Simran Basib9a1b732015-08-20 12:19:28 -0700138 if not manifest:
139 manifest = self.manifest
140 all_projects_list = manifest.projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141 result = []
142
Simran Basib9a1b732015-08-20 12:19:28 -0700143 mp = manifest.manifestProject
Colin Cross5acde752012-03-28 20:15:45 -0700144
Graham Christensen0369a062015-07-29 17:02:54 -0500145 if not groups:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700146 groups = mp.config.GetString('manifest.groups')
Colin Crossc39864f2012-04-23 13:41:58 -0700147 if not groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500148 groups = 'default,platform-' + platform.system().lower()
David Pursehouse1d947b32012-10-25 12:23:11 +0900149 groups = [x for x in re.split(r'[,\s]+', groups) if x]
Colin Cross5acde752012-03-28 20:15:45 -0700150
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151 if not args:
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800152 derived_projects = {}
153 for project in all_projects_list:
154 if submodules_ok or project.sync_s:
155 derived_projects.update((p.name, p)
156 for p in project.GetDerivedSubprojects())
157 all_projects_list.extend(derived_projects.values())
158 for project in all_projects_list:
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700159 if (missing_ok or project.Exists) and project.MatchesGroups(groups):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 result.append(project)
161 else:
David James8d201162013-10-11 17:03:19 -0700162 self._ResetPathToProjectMap(all_projects_list)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700163
164 for arg in args:
Simran Basib9a1b732015-08-20 12:19:28 -0700165 projects = manifest.GetProjectsWithName(arg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166
David James8d201162013-10-11 17:03:19 -0700167 if not projects:
Anthony Newnamdf14a702011-01-09 17:31:57 -0800168 path = os.path.abspath(arg).replace('\\', '/')
Simran Basib9a1b732015-08-20 12:19:28 -0700169 project = self._GetProjectByPath(manifest, path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800171 # If it's not a derived project, update path->project mapping and
172 # search again, as arg might actually point to a derived subproject.
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700173 if (project and not project.Derived and (submodules_ok or
174 project.sync_s)):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800175 search_again = False
176 for subproject in project.GetDerivedSubprojects():
177 self._UpdatePathToProjectMap(subproject)
178 search_again = True
179 if search_again:
Simran Basib9a1b732015-08-20 12:19:28 -0700180 project = self._GetProjectByPath(manifest, path) or project
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
David James8d201162013-10-11 17:03:19 -0700182 if project:
183 projects = [project]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184
David James8d201162013-10-11 17:03:19 -0700185 if not projects:
186 raise NoSuchProjectError(arg)
187
188 for project in projects:
189 if not missing_ok and not project.Exists:
190 raise NoSuchProjectError(arg)
191 if not project.MatchesGroups(groups):
192 raise InvalidProjectGroupsError(arg)
193
194 result.extend(projects)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
196 def _getpath(x):
197 return x.relpath
198 result.sort(key=_getpath)
199 return result
200
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900201 def FindProjects(self, args, inverse=False):
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800202 result = []
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900203 patterns = [re.compile(r'%s' % a, re.IGNORECASE) for a in args]
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800204 for project in self.GetProjects(''):
David Pursehouse84c4d3c2013-04-30 10:57:37 +0900205 for pattern in patterns:
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900206 match = pattern.search(project.name) or pattern.search(project.relpath)
207 if not inverse and match:
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800208 result.append(project)
209 break
Takeshi Kanemoto1f056442016-01-26 14:11:35 +0900210 if inverse and match:
211 break
212 else:
213 if inverse:
214 result.append(project)
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800215 result.sort(key=lambda project: project.relpath)
216 return result
217
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700218
David Pursehouse4f7bdea2012-10-22 12:50:15 +0900219# pylint: disable=W0223
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900220# Pylint warns that the `InteractiveCommand` and `PagedCommand` classes do not
221# override method `Execute` which is abstract in `Command`. Since that method
222# is always implemented in classes derived from `InteractiveCommand` and
223# `PagedCommand`, this warning can be suppressed.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224class InteractiveCommand(Command):
225 """Command which requires user interaction on the tty and
226 must not run within a pager, even if the user asks to.
227 """
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700228 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700229 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700231
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232class PagedCommand(Command):
233 """Command which defaults to output in a pager, as its
234 display tends to be larger than one screen full.
235 """
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700236 def WantPager(self, _opt):
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700237 return True
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800238
David Pursehouse4f7bdea2012-10-22 12:50:15 +0900239# pylint: enable=W0223
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900240
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700241
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800242class MirrorSafeCommand(object):
243 """Command permits itself to run within a mirror,
244 and does not require a working directory.
245 """
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700246
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700247
Dan Willemsen79360642015-08-31 15:45:06 -0700248class GitcAvailableCommand(object):
Dan Willemsen9ff2ece2015-08-31 15:45:06 -0700249 """Command that requires GITC to be available, but does
250 not require the local client to be a GITC client.
251 """
Dan Willemsen79360642015-08-31 15:45:06 -0700252
Mark E. Hamilton8ccfa742016-02-10 10:44:30 -0700253
Dan Willemsen79360642015-08-31 15:45:06 -0700254class GitcClientCommand(object):
255 """Command that requires the local client to be a GITC
256 client.
257 """