33import os
44import stat
55import textwrap
6+ from concurrent .futures import ThreadPoolExecutor , as_completed
67from pathlib import Path
78from subprocess import Popen
89
@@ -46,10 +47,27 @@ def setup_scratch(
4647 That is to prevent namespace clashing with the blueapi application.
4748 """ )
4849 )
49- for repo in config .repositories :
50- local_directory = config .root / repo .name
51- ensure_repo (repo .remote_url , local_directory , repo .target_revision )
52- scratch_install (local_directory , timeout = install_timeout )
50+
51+ with ThreadPoolExecutor () as executor :
52+ futures = [
53+ executor .submit (
54+ ensure_repo ,
55+ repo .remote_url ,
56+ config .root / repo .name ,
57+ repo .target_revision ,
58+ )
59+ for repo in config .repositories
60+ ]
61+ for future in as_completed (futures ):
62+ try :
63+ future .result ()
64+ except Exception as exc :
65+ raise RuntimeError ("Failed to clone repositories" ) from exc
66+
67+ scratch_install (
68+ * (config .root / repo .name for repo in config .repositories ),
69+ timeout = install_timeout ,
70+ )
5371
5472
5573def ensure_repo (
@@ -69,7 +87,12 @@ def ensure_repo(
6987
7088 if not local_directory .exists ():
7189 LOGGER .info (f"Cloning { remote_url } " )
72- Repo .clone_from (remote_url , local_directory , branch = target_revision )
90+ Repo .clone_from (
91+ remote_url ,
92+ local_directory ,
93+ branch = target_revision ,
94+ filter = "blob:none" ,
95+ )
7396 LOGGER .info (f"Cloned { remote_url } -> { local_directory } " )
7497 elif local_directory .is_dir ():
7598 repo = Repo (local_directory )
@@ -93,34 +116,36 @@ def ensure_repo(
93116 )
94117
95118
96- def scratch_install (path : Path , timeout : float = _DEFAULT_INSTALL_TIMEOUT ) -> None :
119+ def scratch_install (* paths : Path , timeout : float = _DEFAULT_INSTALL_TIMEOUT ) -> None :
97120 """
98- Install a scratch package . Make blueapi aware of a repository checked out in
99- the scratch area. Make it automatically follow code changes to that repository
100- (pending a restart). Do not install any of the package's dependencies as they
121+ Install scratch packages . Make blueapi aware of repositories checked out in
122+ the scratch area. Make it automatically follow code changes to those repositories
123+ (pending a restart). Do not install any of the packages' dependencies as they
101124 may conflict with each other.
102125
103126 Args:
104- path: Path to the checked out repository
127+ paths: List of Paths to the checked out repositories
105128 timeout: Time to wait for installation subprocess
106129 """
130+ if not paths :
131+ return
132+ args = [
133+ "uv" ,
134+ "pip" ,
135+ "install" ,
136+ "--no-deps" ,
137+ ]
138+ for path in paths :
139+ _validate_directory (path )
140+ args .extend (["-e" , str (path )])
107141
108- _validate_directory (path )
109-
110- LOGGER .info (f"Installing { path } " )
111- process = Popen (
112- [
113- "uv" ,
114- "pip" ,
115- "install" ,
116- "--no-deps" ,
117- "-e" ,
118- str (path ),
119- ]
120- )
142+ LOGGER .info ("Installing packages" )
143+ process = Popen (args )
121144 process .wait (timeout = timeout )
122145 if process .returncode != 0 :
123- raise RuntimeError (f"Failed to install { path } : Exit Code: { process .returncode } " )
146+ raise RuntimeError (
147+ f"Failed to install packages: Exit Code: { process .returncode } "
148+ )
124149
125150
126151def _validate_root_directory (root_path : Path , required_gid : int | None ) -> None :
0 commit comments