66'''West project commands'''
77
88import argparse
9+ import asyncio
910import logging
1011import os
1112import shlex
@@ -1710,16 +1711,15 @@ def do_add_parser(self, parser_adder):
17101711 parser .add_argument ('projects' , metavar = 'PROJECT' , nargs = '*' ,
17111712 help = '''projects (by name or path) to operate on;
17121713 defaults to active cloned projects''' )
1714+ parser .add_argument ('-j' , '--jobs' , nargs = '?' , const = - 1 ,
1715+ default = 1 , type = int , action = 'store' ,
1716+ help = '''Use multiple jobs to parallelize commands.
1717+ Pass no number or -1 to run commands on all cores.''' )
17131718 return parser
17141719
1715- def do_run (self , args , user_args ):
1716- failed = []
1717- group_set = set (args .groups )
1718- env = os .environ .copy ()
1719- for project in self ._cloned_projects (args , only_active = not args .all ):
1720- if group_set and not group_set .intersection (set (project .groups )):
1721- continue
1722-
1720+ async def run_for_project (self , project , args , semaphore ):
1721+ async with semaphore :
1722+ env = os .environ .copy ()
17231723 env ["WEST_PROJECT_NAME" ] = project .name
17241724 env ["WEST_PROJECT_PATH" ] = project .path
17251725 env ["WEST_PROJECT_ABSPATH" ] = project .abspath if project .abspath else ''
@@ -1729,12 +1729,39 @@ def do_run(self, args, user_args):
17291729
17301730 cwd = args .cwd if args .cwd else project .abspath
17311731
1732- self .banner (
1733- f'running "{ args .subcommand } " in { project .name_and_path } :' )
1734- rc = subprocess .Popen (args .subcommand , shell = True , env = env ,
1735- cwd = cwd ).wait ()
1736- if rc :
1737- failed .append (project )
1732+ self .banner (f'running "{ args .subcommand } " in { project .name_and_path } :' ,
1733+ end = ('\r ' if self .jobs > 1 else '\n ' ))
1734+ proc = await asyncio .create_subprocess_shell (
1735+ args .subcommand ,
1736+ cwd = cwd , env = env , shell = True ,
1737+ stdout = asyncio .subprocess .PIPE if self .jobs > 1 else None ,
1738+ stderr = asyncio .subprocess .PIPE if self .jobs > 1 else None )
1739+
1740+ if self .jobs > 1 :
1741+ (out , err ) = await proc .communicate ()
1742+
1743+ self .banner (f'finished "{ args .subcommand } " in { project .name_and_path } :' )
1744+ sys .stdout .write (out .decode ())
1745+ sys .stderr .write (err .decode ())
1746+
1747+ return proc .returncode
1748+
1749+ return await proc .wait ()
1750+
1751+ def do_run (self , args , unknown ):
1752+ group_set = set (args .groups )
1753+ projects = [p for p in self ._cloned_projects (args , only_active = not args .all )
1754+ if not group_set or group_set .intersection (set (p .groups ))]
1755+
1756+ asyncio .run (self .do_run_async (args , projects ))
1757+
1758+ async def do_run_async (self , args , projects ):
1759+ self .jobs = args .jobs if args .jobs > 0 else os .cpu_count () or sys .maxsize
1760+ sem = asyncio .Semaphore (self .jobs )
1761+
1762+ rcs = await asyncio .gather (* [self .run_for_project (p , args , sem ) for p in projects ])
1763+
1764+ failed = [p for (p , rc ) in zip (projects , rcs ) if rc ]
17381765 self ._handle_failed (args , failed )
17391766
17401767GREP_EPILOG = '''
0 commit comments