66import subprocess
77import tempfile
88from pathlib import Path
9- from typing import List , Optional
9+ from typing import Dict , List
10+ from venv import EnvBuilder
1011
1112import git
1213import yaml
@@ -130,6 +131,52 @@ def __init__(
130131 self .progressbar = None
131132 self .source_dir = tempfile .TemporaryDirectory ()
132133
134+ def setup_venv (self , source_dir : Path ):
135+ venv_task = self .progressbar .add_task (
136+ "[yellow]Setup venv" , total = 100 , visible = False
137+ )
138+ self .progressbar .update (
139+ venv_task ,
140+ description = "[yellow] Setting up venv for " + self .name + "..." ,
141+ completed = 0 ,
142+ visible = True ,
143+ )
144+ if (source_dir / "Makefile" ).exists :
145+ with contextlib .chdir (source_dir ):
146+ rc = self .run_command_with_callback (
147+ ["make" , "venv" ],
148+ lambda line : self .progressbar .update (venv_task , advance = 1 ),
149+ )
150+ elif (source_dir / "requirements.txt" ).exists :
151+ builder = EnvBuilder (system_site_packages = True , with_pip = True )
152+ builder .create (str (source_dir / "venv" ))
153+ self .progressbar .update (venv_task , completed = 10 )
154+ with contextlib .chdir (source_dir ):
155+ rc = self .run_command_with_callback (
156+ ["venv/bin/pip" , "install" , "-r" , "requirements.txt" ],
157+ lambda line : self .progressbar .update (venv_task , advance = 1 ),
158+ )
159+ else :
160+ raise ValueError (
161+ "--their-venv was provided but no Makefile or requirements.txt upstream"
162+ )
163+ if rc != 0 :
164+ self .progressbar .console .print (
165+ "[red]Error setting up venv for " + self .name
166+ )
167+ raise ValueError ("Venv setup failed" )
168+ self .progressbar .remove_task (venv_task )
169+
170+ def local_overrides (
171+ self , upstream : Path , downstream : Path , overrides : Dict [str , str ]
172+ ):
173+ for source , dest in overrides .items ():
174+ if (downstream / source ).exists ():
175+ dest_path = upstream / dest
176+ os .makedirs (dest_path .parent , exist_ok = True )
177+ self .progressbar .console .print ("[grey]Using our " + source )
178+ shutil .copy (downstream / source , dest_path )
179+
133180 def build (self ):
134181 with Progress (
135182 progress .TimeElapsedColumn (),
@@ -142,16 +189,19 @@ def build(self):
142189 with tempfile .TemporaryDirectory () as source_dir :
143190 source_dir = Path (source_dir )
144191 self .clone_source (source_dir )
145- # Do we have our own local config.yaml?
146- if (self .family_path / "config.yaml" ).exists ():
147- # If so, copy it over
148- os .makedirs (source_dir / "sources" , exist_ok = True )
149- shutil .copy (
150- self .family_path / "config.yaml" , source_dir / "sources"
151- )
192+ self .local_overrides (
193+ source_dir ,
194+ self .family_path ,
195+ {
196+ "config.yaml" : "sources/config.yaml" ,
197+ "requirements.txt" : "requirements.txt" ,
198+ },
199+ )
152200
153201 if not (source_dir / "sources" ).exists ():
154202 raise ValueError (f"Could not find sources directory in { self .name } " )
203+ if self .their_venv :
204+ self .setup_venv (source_dir )
155205
156206 # Locate the config.yaml file or first source
157207 arg = find_config_yaml (source_dir )
@@ -164,14 +214,18 @@ def build(self):
164214 arg = sources [0 ]
165215
166216 with contextlib .chdir (source_dir ):
167- buildcmd = ["gftools-builder" , str (arg )]
217+ if self .their_venv :
218+ buildcmd = ["venv/bin/gftools-builder" , str (arg )]
219+ else :
220+ buildcmd = ["gftools-builder" , str (arg )]
168221 self .run_build_command (buildcmd )
169222 self .copy_files ()
170223
171- def run_build_command (self , buildcmd ):
172- build_task = self .progressbar .add_task ("[green]Build " + self .name , total = 1 )
224+ def run_command_with_callback (self , cmd , callback ):
173225 process = subprocess .Popen (
174- buildcmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE
226+ cmd ,
227+ stdout = subprocess .PIPE ,
228+ stderr = subprocess .PIPE ,
175229 )
176230 sel = selectors .DefaultSelector ()
177231 sel .register (process .stdout , selectors .EVENT_READ )
@@ -185,12 +239,8 @@ def run_build_command(self, buildcmd):
185239 if not line :
186240 ok = False
187241 break
188- if key .fileobj is process .stdout and (
189- m := re .match (r"^\[(\d+)/(\d+)\]" , line .decode ("utf8" ))
190- ):
191- self .progressbar .update (
192- build_task , completed = int (m .group (1 )), total = int (m .group (2 ))
193- )
242+ if key .fileobj is process .stdout :
243+ callback (line )
194244 elif key .fileobj is process .stderr :
195245 stderrlines .append (line )
196246 else :
@@ -201,7 +251,18 @@ def run_build_command(self, buildcmd):
201251 self .progressbar .console .print (line .decode ("utf-8" ), end = "" )
202252 for line in stderrlines :
203253 self .progressbar .console .print ("[red]" + line .decode ("utf8" ), end = "" )
254+ return rc
255+
256+ def run_build_command (self , buildcmd ):
257+ build_task = self .progressbar .add_task ("[green]Build " + self .name , total = 1 )
258+
259+ def progress_callback (line ):
260+ if m := re .match (r"^\[(\d+)/(\d+)\]" , line .decode ("utf8" )):
261+ self .progressbar .update (
262+ build_task , completed = int (m .group (1 )), total = int (m .group (2 ))
263+ )
204264
265+ if self .run_command_with_callback (buildcmd , progress_callback ) != 0 :
205266 self .progressbar .console .print ("[red]Error building " + self .name )
206267 raise ValueError ("Build failed" )
207268 else :
0 commit comments