Skip to content

Commit 49fc40c

Browse files
RobertGalatNordickartben
authored andcommitted
project.py: Add new 'joblib' dependency to parallelize update
The projects from manifests were updated one by one. The parallel aproach has potential to fully utilize CPU and network connection to speed up the update. Signed-off-by: Robert Gałat <[email protected]>
1 parent 2b18ae4 commit 49fc40c

File tree

3 files changed

+46
-15
lines changed

3 files changed

+46
-15
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dependencies = [
2121
"pykwalify",
2222
"setuptools",
2323
"packaging",
24+
"joblib",
2425
]
2526

2627
[project.license]

src/west/app/project.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
)
3737
from west.manifest import is_group as is_project_group
3838

39+
JOBLIB_PRESENT = True
40+
try:
41+
from joblib import Parallel, delayed
42+
except ModuleNotFoundError:
43+
JOBLIB_PRESENT = False
44+
3945
#
4046
# Project-related or multi-repo commands, like "init", "update",
4147
# "diff", etc.
@@ -952,6 +958,12 @@ def do_add_parser(self, parser_adder):
952958
parser.add_argument('--stats', action='store_true',
953959
help='''print performance statistics for
954960
update operations''')
961+
if JOBLIB_PRESENT:
962+
parser.add_argument('-j', '--jobs', nargs='?', const=-1,
963+
default=1, type=int, action='store',
964+
help='''Use multiple jobs to parallelize the update process.
965+
Pass -1 to use all available jobs.
966+
''')
955967

956968
group = parser.add_argument_group(
957969
title='local project clone caches',
@@ -1087,18 +1099,26 @@ def update_all(self):
10871099
import_flags=ImportFlag.FORCE_PROJECTS)
10881100

10891101
failed = []
1090-
for project in self.manifest.projects:
1102+
1103+
def project_update(project):
10911104
if (isinstance(project, ManifestProject) or
10921105
project.name in self.updated):
1093-
continue
1106+
return
10941107
try:
10951108
if not self.project_is_active(project):
10961109
self.dbg(f'{project.name}: skipping inactive project')
1097-
continue
1098-
self.update(project)
1110+
return
10991111
self.updated.add(project.name)
1112+
self.update(project)
11001113
except subprocess.CalledProcessError:
11011114
failed.append(project)
1115+
1116+
if not JOBLIB_PRESENT or self.args.jobs == 1:
1117+
for project in self.manifest.projects:
1118+
project_update(project)
1119+
else:
1120+
Parallel(n_jobs=self.args.jobs, require='sharedmem')(
1121+
delayed(project_update)(project) for project in self.manifest.projects)
11021122
self._handle_failed(self.args, failed)
11031123

11041124
def update_importer(self, project, path):
@@ -1159,13 +1179,22 @@ def update_some(self):
11591179
projects = self._projects(self.args.projects)
11601180

11611181
failed = []
1162-
for project in projects:
1182+
1183+
def project_update_some(project):
11631184
if isinstance(project, ManifestProject):
1164-
continue
1185+
return
11651186
try:
11661187
self.update(project)
11671188
except subprocess.CalledProcessError:
11681189
failed.append(project)
1190+
1191+
if not JOBLIB_PRESENT or self.args.jobs == 1:
1192+
for project in projects:
1193+
project_update_some(project)
1194+
else:
1195+
Parallel(n_jobs=self.args.jobs, require='sharedmem')(
1196+
delayed(project_update_some)(project) for project in projects)
1197+
11691198
self._handle_failed(self.args, failed)
11701199

11711200
def toplevel_projects(self):

tests/test_project.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -447,14 +447,14 @@ def test_grep(west_init_tmpdir):
447447

448448
assert re.search('west-commands', cmd('grep -- -- -commands'))
449449

450-
451-
def test_update_projects(west_init_tmpdir):
450+
@pytest.mark.parametrize("options", ["", "-j 1", "-j 2", "-j"])
451+
def test_update_projects(options, west_init_tmpdir):
452452
# Test the 'west update' command. It calls through to the same backend
453453
# functions that are used for automatic updates and 'west init'
454454
# reinitialization.
455455

456456
# create local repositories
457-
cmd('update')
457+
cmd('update ' + options)
458458

459459
# Add commits to the local repos.
460460
ur = update_helper(west_init_tmpdir)
@@ -644,7 +644,8 @@ def test_update_head_0(west_init_tmpdir):
644644
assert modified_files.strip() == "M CODEOWNERS", \
645645
'local zephyr change not preserved'
646646

647-
def test_update_some_with_imports(repos_tmpdir):
647+
@pytest.mark.parametrize("options", ["", "-j 1", "-j 2", "-j -1"])
648+
def test_update_some_with_imports(options, repos_tmpdir):
648649
# 'west update project1 project2' should work fine even when
649650
# imports are used, as long as the relevant projects are all
650651
# defined in the manifest repository.
@@ -685,19 +686,19 @@ def test_update_some_with_imports(repos_tmpdir):
685686
# Updating unknown projects should fail as always.
686687

687688
with pytest.raises(subprocess.CalledProcessError):
688-
cmd('update unknown-project', cwd=ws)
689+
cmd(f'update {options} unknown-project', cwd=ws)
689690

690691
# Updating a list of projects when some are resolved via project
691692
# imports must fail.
692693

693694
with pytest.raises(subprocess.CalledProcessError):
694-
cmd('update Kconfiglib net-tools', cwd=ws)
695+
cmd(f'update {options} Kconfiglib net-tools', cwd=ws)
695696

696697
# Updates of projects defined in the manifest repository or all
697698
# projects must succeed, and behave the same as if no imports
698699
# existed.
699700

700-
cmd('update net-tools', cwd=ws)
701+
cmd(f'update {options} net-tools', cwd=ws)
701702
with pytest.raises(ManifestImportFailed):
702703
Manifest.from_topdir(topdir=ws)
703704
manifest = Manifest.from_topdir(topdir=ws,
@@ -708,10 +709,10 @@ def test_update_some_with_imports(repos_tmpdir):
708709
assert net_tools_project.is_cloned()
709710
assert not zephyr_project.is_cloned()
710711

711-
cmd('update zephyr', cwd=ws)
712+
cmd(f'update {options} zephyr', cwd=ws)
712713
assert zephyr_project.is_cloned()
713714

714-
cmd('update', cwd=ws)
715+
cmd(f'update {options}', cwd=ws)
715716
manifest = Manifest.from_topdir(topdir=ws)
716717
assert manifest.get_projects(['Kconfiglib'])[0].is_cloned()
717718

0 commit comments

Comments
 (0)