66'''West project commands'''
77
88import argparse
9+ import asyncio
910import logging
1011import os
1112import shlex
@@ -1809,16 +1810,15 @@ def do_add_parser(self, parser_adder):
18091810 parser .add_argument ('projects' , metavar = 'PROJECT' , nargs = '*' ,
18101811 help = '''projects (by name or path) to operate on;
18111812 defaults to active cloned projects''' )
1813+ parser .add_argument ('-j' , '--jobs' , nargs = '?' , const = - 1 ,
1814+ default = 1 , type = int , action = 'store' ,
1815+ help = '''Use multiple jobs to parallelize commands.
1816+ Pass no number or -1 to run commands on all cores.''' )
18121817 return parser
18131818
1814- def do_run (self , args , user_args ):
1815- failed = []
1816- group_set = set (args .groups )
1817- env = os .environ .copy ()
1818- for project in self ._cloned_projects (args , only_active = not args .all ):
1819- if group_set and not group_set .intersection (set (project .groups )):
1820- continue
1821-
1819+ async def run_for_project (self , project , args , semaphore ):
1820+ async with semaphore :
1821+ env = os .environ .copy ()
18221822 env ["WEST_PROJECT_NAME" ] = project .name
18231823 env ["WEST_PROJECT_PATH" ] = project .path
18241824 env ["WEST_PROJECT_ABSPATH" ] = project .abspath if project .abspath else ''
@@ -1828,12 +1828,39 @@ def do_run(self, args, user_args):
18281828
18291829 cwd = args .cwd if args .cwd else project .abspath
18301830
1831- self .banner (
1832- f'running "{ args .subcommand } " in { project .name_and_path } :' )
1833- rc = subprocess .Popen (args .subcommand , shell = True , env = env ,
1834- cwd = cwd ).wait ()
1835- if rc :
1836- failed .append (project )
1831+ self .banner (f'running "{ args .subcommand } " in { project .name_and_path } :' ,
1832+ end = ('\r ' if self .jobs > 1 else '\n ' ))
1833+ proc = await asyncio .create_subprocess_shell (
1834+ args .subcommand ,
1835+ cwd = cwd , env = env , shell = True ,
1836+ stdout = asyncio .subprocess .PIPE if self .jobs > 1 else None ,
1837+ stderr = asyncio .subprocess .PIPE if self .jobs > 1 else None )
1838+
1839+ if self .jobs > 1 :
1840+ (out , err ) = await proc .communicate ()
1841+
1842+ self .banner (f'finished "{ args .subcommand } " in { project .name_and_path } :' )
1843+ sys .stdout .write (out .decode ())
1844+ sys .stderr .write (err .decode ())
1845+
1846+ return proc .returncode
1847+
1848+ return await proc .wait ()
1849+
1850+ def do_run (self , args , unknown ):
1851+ group_set = set (args .groups )
1852+ projects = [p for p in self ._cloned_projects (args , only_active = not args .all )
1853+ if not group_set or group_set .intersection (set (p .groups ))]
1854+
1855+ asyncio .run (self .do_run_async (args , projects ))
1856+
1857+ async def do_run_async (self , args , projects ):
1858+ self .jobs = args .jobs if args .jobs > 0 else os .cpu_count () or sys .maxsize
1859+ sem = asyncio .Semaphore (self .jobs )
1860+
1861+ rcs = await asyncio .gather (* [self .run_for_project (p , args , sem ) for p in projects ])
1862+
1863+ failed = [p for (p , rc ) in zip (projects , rcs ) if rc ]
18371864 self ._handle_failed (args , failed )
18381865
18391866GREP_EPILOG = '''
0 commit comments