blob: e2a420a93d15880b289ff2b1fca6096f50498a67 [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
Sarah Owenscecd1d82012-11-01 22:59:27 -070016from __future__ import print_function
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070017import fcntl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import re
19import os
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070020import select
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import sys
22import subprocess
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070023
24from color import Coloring
Shawn O. Pearce44469462009-03-03 17:51:01 -080025from command import Command, MirrorSafeCommand
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070027_CAN_COLOR = [
28 'branch',
29 'diff',
30 'grep',
31 'log',
32]
33
34class ForallColoring(Coloring):
35 def __init__(self, config):
36 Coloring.__init__(self, config, 'forall')
37 self.project = self.printer('project', attr='bold')
38
39
Shawn O. Pearce44469462009-03-03 17:51:01 -080040class Forall(Command, MirrorSafeCommand):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041 common = False
42 helpSummary = "Run a shell command in each project"
43 helpUsage = """
44%prog [<project>...] -c <command> [<arg>...]
Zhiguang Lia8864fb2013-03-15 10:32:10 +080045%prog -r str1 [str2] ... -c <command> [<arg>...]"
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046"""
47 helpDescription = """
48Executes the same shell command in each project.
49
Zhiguang Lia8864fb2013-03-15 10:32:10 +080050The -r option allows running the command only on projects matching
51regex or wildcard expression.
52
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070053Output Formatting
54-----------------
55
56The -p option causes '%prog' to bind pipes to the command's stdin,
57stdout and stderr streams, and pipe all output into a continuous
58stream that is displayed in a single pager session. Project headings
59are inserted before the output of each command is displayed. If the
60command produces no output in a project, no heading is displayed.
61
62The formatting convention used by -p is very suitable for some
63types of searching, e.g. `repo forall -p -c git log -SFoo` will
64print all commits that add or remove references to Foo.
65
66The -v option causes '%prog' to display stderr messages if a
67command produces output only on stderr. Normally the -p option
68causes command output to be suppressed until the command produces
69at least one byte of output on stdout.
70
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070071Environment
72-----------
Shawn O. Pearceff84fea2009-04-13 12:11:59 -070073
Shawn O. Pearce44469462009-03-03 17:51:01 -080074pwd is the project's working directory. If the current client is
75a mirror client, then pwd is the Git repository.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070076
77REPO_PROJECT is set to the unique name of the project.
78
Jeff Baileybe0e8ac2009-01-21 19:05:15 -050079REPO_PATH is the path relative the the root of the client.
80
81REPO_REMOTE is the name of the remote system from the manifest.
82
83REPO_LREV is the name of the revision from the manifest, translated
84to a local tracking branch. If you need to pass the manifest
85revision to a locally executed git command, use REPO_LREV.
86
87REPO_RREV is the name of the revision from the manifest, exactly
88as written in the manifest.
89
James W. Mills24c13082012-04-12 15:04:13 -050090REPO__* are any extra environment variables, specified by the
91"annotation" element under any project element. This can be useful
92for differentiating trees based on user-specific criteria, or simply
93annotating tree details.
94
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095shell positional arguments ($1, $2, .., $#) are set to any arguments
96following <command>.
97
Shawn O. Pearcedb45da12009-04-18 13:49:13 -070098Unless -p is used, stdin, stdout, stderr are inherited from the
99terminal and are not redirected.
Victor Boivie88b86722011-09-07 09:43:28 +0200100
101If -e is used, when a command exits unsuccessfully, '%prog' will abort
102without iterating through the remaining projects.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103"""
104
105 def _Options(self, p):
106 def cmd(option, opt_str, value, parser):
107 setattr(parser.values, option.dest, list(parser.rargs))
108 while parser.rargs:
109 del parser.rargs[0]
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800110 p.add_option('-r', '--regex',
111 dest='regex', action='store_true',
112 help="Execute the command only on projects matching regex or wildcard expression")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700113 p.add_option('-c', '--command',
114 help='Command (and arguments) to execute',
115 dest='command',
116 action='callback',
117 callback=cmd)
Victor Boivie88b86722011-09-07 09:43:28 +0200118 p.add_option('-e', '--abort-on-errors',
119 dest='abort_on_errors', action='store_true',
120 help='Abort if a command exits unsuccessfully')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700122 g = p.add_option_group('Output')
123 g.add_option('-p',
124 dest='project_header', action='store_true',
125 help='Show project headers before output')
126 g.add_option('-v', '--verbose',
127 dest='verbose', action='store_true',
128 help='Show command error messages')
129
130 def WantPager(self, opt):
131 return opt.project_header
132
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 def Execute(self, opt, args):
134 if not opt.command:
135 self.Usage()
136
137 cmd = [opt.command[0]]
138
139 shell = True
140 if re.compile(r'^[a-z0-9A-Z_/\.-]+$').match(cmd[0]):
141 shell = False
142
143 if shell:
144 cmd.append(cmd[0])
145 cmd.extend(opt.command[1:])
146
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700147 if opt.project_header \
148 and not shell \
149 and cmd[0] == 'git':
150 # If this is a direct git command that can enable colorized
151 # output and the user prefers coloring, add --color into the
152 # command line because we are going to wrap the command into
153 # a pipe and git won't know coloring should activate.
154 #
155 for cn in cmd[1:]:
156 if not cn.startswith('-'):
157 break
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900158 else:
159 cn = None
David Pursehouse4f7bdea2012-10-22 12:50:15 +0900160 # pylint: disable=W0631
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900161 if cn and cn in _CAN_COLOR:
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700162 class ColorCmd(Coloring):
163 def __init__(self, config, cmd):
164 Coloring.__init__(self, config, cmd)
165 if ColorCmd(self.manifest.manifestProject.config, cn).is_on:
166 cmd.insert(cmd.index(cn) + 1, '--color')
David Pursehouse4f7bdea2012-10-22 12:50:15 +0900167 # pylint: enable=W0631
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700168
Shawn O. Pearce44469462009-03-03 17:51:01 -0800169 mirror = self.manifest.IsMirror
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700170 out = ForallColoring(self.manifest.manifestProject.config)
Wink Savilleef9ce1d2009-04-21 10:00:16 -0700171 out.redirect(sys.stdout)
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700172
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173 rc = 0
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700174 first = True
175
Zhiguang Lia8864fb2013-03-15 10:32:10 +0800176 if not opt.regex:
177 projects = self.GetProjects(args)
178 else:
179 projects = self.FindProjects(args)
180
181 for project in projects:
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800182 env = os.environ.copy()
Shawn O. Pearce1775dbe2009-03-17 08:03:04 -0700183 def setenv(name, val):
184 if val is None:
185 val = ''
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800186 env[name] = val.encode()
Shawn O. Pearce1775dbe2009-03-17 08:03:04 -0700187
188 setenv('REPO_PROJECT', project.name)
189 setenv('REPO_PATH', project.relpath)
190 setenv('REPO_REMOTE', project.remote.name)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700191 setenv('REPO_LREV', project.GetRevisionId())
192 setenv('REPO_RREV', project.revisionExpr)
James W. Mills24c13082012-04-12 15:04:13 -0500193 for a in project.annotations:
194 setenv("REPO__%s" % (a.name), a.value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195
Shawn O. Pearce44469462009-03-03 17:51:01 -0800196 if mirror:
Shawn O. Pearce1775dbe2009-03-17 08:03:04 -0700197 setenv('GIT_DIR', project.gitdir)
Shawn O. Pearce44469462009-03-03 17:51:01 -0800198 cwd = project.gitdir
199 else:
200 cwd = project.worktree
201
Shawn O. Pearce1b5a4a02009-08-22 18:50:45 -0700202 if not os.path.exists(cwd):
203 if (opt.project_header and opt.verbose) \
204 or not opt.project_header:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700205 print('skipping %s/' % project.relpath, file=sys.stderr)
Shawn O. Pearce1b5a4a02009-08-22 18:50:45 -0700206 continue
207
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700208 if opt.project_header:
209 stdin = subprocess.PIPE
210 stdout = subprocess.PIPE
211 stderr = subprocess.PIPE
212 else:
213 stdin = None
214 stdout = None
215 stderr = None
216
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 p = subprocess.Popen(cmd,
Shawn O. Pearce44469462009-03-03 17:51:01 -0800218 cwd = cwd,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700219 shell = shell,
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700220 env = env,
221 stdin = stdin,
222 stdout = stdout,
223 stderr = stderr)
224
225 if opt.project_header:
226 class sfd(object):
227 def __init__(self, fd, dest):
228 self.fd = fd
229 self.dest = dest
230 def fileno(self):
231 return self.fd.fileno()
232
233 empty = True
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700234 errbuf = ''
235
236 p.stdin.close()
237 s_in = [sfd(p.stdout, sys.stdout),
238 sfd(p.stderr, sys.stderr)]
239
240 for s in s_in:
241 flags = fcntl.fcntl(s.fd, fcntl.F_GETFL)
242 fcntl.fcntl(s.fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
243
244 while s_in:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900245 in_ready, _out_ready, _err_ready = select.select(s_in, [], [])
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700246 for s in in_ready:
247 buf = s.fd.read(4096)
248 if not buf:
249 s.fd.close()
250 s_in.remove(s)
251 continue
252
253 if not opt.verbose:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900254 if s.fd != p.stdout:
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700255 errbuf += buf
256 continue
257
258 if empty:
259 if first:
260 first = False
261 else:
262 out.nl()
Jorge Gonzalez5bca9fc2013-06-03 12:00:06 -0700263
264 if mirror:
265 project_header_path = project.name
266 else:
267 project_header_path = project.relpath
268 out.project('project %s/', project_header_path)
Shawn O. Pearcedb45da12009-04-18 13:49:13 -0700269 out.nl()
270 out.flush()
271 if errbuf:
272 sys.stderr.write(errbuf)
273 sys.stderr.flush()
274 errbuf = ''
275 empty = False
276
277 s.dest.write(buf)
278 s.dest.flush()
279
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700280 r = p.wait()
Victor Boivie88b86722011-09-07 09:43:28 +0200281 if r != 0:
282 if r != rc:
283 rc = r
284 if opt.abort_on_errors:
285 print("error: %s: Aborting due to previous error" % project.relpath,
286 file=sys.stderr)
287 sys.exit(r)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700288 if rc != 0:
289 sys.exit(rc)