summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2021-02-16 13:51:44 -0500
committerMike Frysinger <vapier@google.com>2021-02-22 22:58:30 +0000
commitfbab6065d44072d33b2fbe61f604f24397ea2de4 (patch)
tree92c355b192637e0a8a639170d1459e2763122cba
parent15e807cf3c5d3bf7e142f74edea219514caa749a (diff)
downloadgit-repo-fbab6065d44072d33b2fbe61f604f24397ea2de4.tar.gz
forall: rewrite parallel logic
This fixes intermingling of parallel jobs and simplifies the code by switching to subprocess.run. This also provides stable output in the order of projects by returning the output as a string that the main loop outputs. This drops support for interactive commands, but it's unclear if anyone was relying on that, and the default behavior (-j2) made that unreliable. If it turns out someone still wants this, we can look at readding it. Change-Id: I7555b4e7a15aad336667292614f730fb7a90bd26 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297482 Reviewed-by: Chris Mcdonald <cjmcdonald@google.com> Tested-by: Mike Frysinger <vapier@google.com>
-rw-r--r--subcmds/forall.py106
1 files changed, 40 insertions, 66 deletions
diff --git a/subcmds/forall.py b/subcmds/forall.py
index b874b6d2..4ea7db66 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -13,6 +13,7 @@
13# limitations under the License. 13# limitations under the License.
14 14
15import errno 15import errno
16import io
16import multiprocessing 17import multiprocessing
17import re 18import re
18import os 19import os
@@ -22,7 +23,6 @@ import subprocess
22 23
23from color import Coloring 24from color import Coloring
24from command import DEFAULT_LOCAL_JOBS, Command, MirrorSafeCommand, WORKER_BATCH_SIZE 25from command import DEFAULT_LOCAL_JOBS, Command, MirrorSafeCommand, WORKER_BATCH_SIZE
25import platform_utils
26 26
27_CAN_COLOR = [ 27_CAN_COLOR = [
28 'branch', 28 'branch',
@@ -241,7 +241,18 @@ without iterating through the remaining projects.
241 DoWorkWrapper, 241 DoWorkWrapper,
242 self.ProjectArgs(projects, mirror, opt, cmd, shell, config), 242 self.ProjectArgs(projects, mirror, opt, cmd, shell, config),
243 chunksize=WORKER_BATCH_SIZE) 243 chunksize=WORKER_BATCH_SIZE)
244 for r in results_it: 244 first = True
245 for (r, output) in results_it:
246 if output:
247 if first:
248 first = False
249 elif opt.project_header:
250 print()
251 # To simplify the DoWorkWrapper, take care of automatic newlines.
252 end = '\n'
253 if output[-1] == '\n':
254 end = ''
255 print(output, end=end)
245 rc = rc or r 256 rc = rc or r
246 if r != 0 and opt.abort_on_errors: 257 if r != 0 and opt.abort_on_errors:
247 raise Exception('Aborting due to previous error') 258 raise Exception('Aborting due to previous error')
@@ -326,73 +337,36 @@ def DoWork(project, mirror, opt, cmd, shell, cnt, config):
326 # Allow the user to silently ignore missing checkouts so they can run on 337 # Allow the user to silently ignore missing checkouts so they can run on
327 # partial checkouts (good for infra recovery tools). 338 # partial checkouts (good for infra recovery tools).
328 if opt.ignore_missing: 339 if opt.ignore_missing:
329 return 0 340 return (0, '')
341
342 output = ''
330 if ((opt.project_header and opt.verbose) 343 if ((opt.project_header and opt.verbose)
331 or not opt.project_header): 344 or not opt.project_header):
332 print('skipping %s/' % project['relpath'], file=sys.stderr) 345 output = 'skipping %s/' % project['relpath']
333 return 1 346 return (1, output)
334 347
335 if opt.project_header: 348 if opt.verbose:
336 stdin = subprocess.PIPE 349 stderr = subprocess.STDOUT
337 stdout = subprocess.PIPE
338 stderr = subprocess.PIPE
339 else: 350 else:
340 stdin = None 351 stderr = subprocess.DEVNULL
341 stdout = None
342 stderr = None
343
344 p = subprocess.Popen(cmd,
345 cwd=cwd,
346 shell=shell,
347 env=env,
348 stdin=stdin,
349 stdout=stdout,
350 stderr=stderr)
351 352
353 result = subprocess.run(
354 cmd, cwd=cwd, shell=shell, env=env, check=False,
355 encoding='utf-8', errors='replace',
356 stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=stderr)
357
358 output = result.stdout
352 if opt.project_header: 359 if opt.project_header:
353 out = ForallColoring(config) 360 if output:
354 out.redirect(sys.stdout) 361 buf = io.StringIO()
355 empty = True 362 out = ForallColoring(config)
356 errbuf = '' 363 out.redirect(buf)
357 364 if mirror:
358 p.stdin.close() 365 project_header_path = project['name']
359 s_in = platform_utils.FileDescriptorStreams.create() 366 else:
360 s_in.add(p.stdout, sys.stdout, 'stdout') 367 project_header_path = project['relpath']
361 s_in.add(p.stderr, sys.stderr, 'stderr') 368 out.project('project %s/' % project_header_path)
362 369 out.nl()
363 while not s_in.is_done: 370 buf.write(output)
364 in_ready = s_in.select() 371 output = buf.getvalue()
365 for s in in_ready: 372 return (result.returncode, output)
366 buf = s.read().decode()
367 if not buf:
368 s_in.remove(s)
369 s.close()
370 continue
371
372 if not opt.verbose:
373 if s.std_name == 'stderr':
374 errbuf += buf
375 continue
376
377 if empty and out:
378 if not cnt == 0:
379 out.nl()
380
381 if mirror:
382 project_header_path = project['name']
383 else:
384 project_header_path = project['relpath']
385 out.project('project %s/', project_header_path)
386 out.nl()
387 out.flush()
388 if errbuf:
389 sys.stderr.write(errbuf)
390 sys.stderr.flush()
391 errbuf = ''
392 empty = False
393
394 s.dest.write(buf)
395 s.dest.flush()
396
397 r = p.wait()
398 return r