11from __future__ import annotations # This enables postponed evaluation of type annotations. Required for typing.TYPE_CHECKING. See https://peps.python.org/pep-0563/
2- from typing import TYPE_CHECKING , List , Union , cast , Dict , Any
2+ from typing import TYPE_CHECKING , List , Union , cast , Dict , Any , TypeVar , Callable , Sequence , Optional
33import shutil
44from tempfile import TemporaryDirectory
55import subprocess
1212
1313if TYPE_CHECKING :
1414 from github import WorkflowRun , Repository
15-
15+
1616
1717script_path = Path (__file__ ).resolve ()
1818root_path = script_path .parent .parent .parent
@@ -30,7 +30,7 @@ def get_check_runs(self: Repository.Repository, ref: str, **kwargs: str) -> Pagi
3030 f"{ self .url } /commits/{ ref } /check-runs" ,
3131 firstParams = None ,
3232 list_item = "check_runs" )
33-
33+
3434 Repository .Repository = MyRepository
3535
3636 from github import WorkflowRun , Artifact
@@ -51,7 +51,7 @@ def download_logs(self, path: Path) -> None:
5151 if self ._requester ._Requester__auth is not None : # type: ignore
5252 headers ["Authorization" ] = f"{ self ._requester ._Requester__auth .token_type } { self ._requester ._Requester__auth .token } " # type: ignore
5353 headers ["User-Agent" ] = self ._requester ._Requester__userAgent # type: ignore
54-
54+
5555 resp = requests .get (url , headers = headers , allow_redirects = True )
5656
5757 if resp .status_code != 200 :
@@ -70,7 +70,7 @@ def download_artifacts(self, path: Path) -> None:
7070 if self ._requester ._Requester__auth is not None : # type: ignore
7171 headers ["Authorization" ] = f"{ self ._requester ._Requester__auth .token_type } { self ._requester ._Requester__auth .token } " # type: ignore
7272 headers ["User-Agent" ] = self ._requester ._Requester__userAgent # type: ignore
73-
73+
7474 resp = requests .get (artifact .archive_download_url , headers = headers , allow_redirects = True )
7575
7676 if resp .status_code != 200 :
@@ -93,15 +93,15 @@ def download_artifact(self, name: str, path: Path) -> None:
9393 if self ._requester ._Requester__auth is not None : # type: ignore
9494 headers ["Authorization" ] = f"{ self ._requester ._Requester__auth .token_type } { self ._requester ._Requester__auth .token } " # type: ignore
9595 headers ["User-Agent" ] = self ._requester ._Requester__userAgent # type: ignore
96-
96+
9797 resp = requests .get (artifact .archive_download_url , headers = headers , allow_redirects = True )
9898
9999 if resp .status_code != 200 :
100100 raise Exception (f"Unable to download artifact ${ artifact .name } . Received status code { resp .status_code } { resp .reason } " )
101101
102102 with (path / f"{ artifact .name } .zip" ).open ("wb" ) as f :
103103 f .write (resp .content )
104-
104+
105105
106106 WorkflowRun .WorkflowRun = MyWorkflowRun
107107
@@ -124,12 +124,16 @@ def make(self, directory: Path, workflow_runs: List[WorkflowRun.WorkflowRun]) ->
124124 elif action_type == "workflow-artifact" :
125125 actions .append (WorkflowArtifactAction (workflow_runs , ** cast (Dict [str , Any ], action_args )))
126126 elif action_type == "shell" :
127- actions .append (ShellAction (action_args ))
127+ modifiers : List [Callable [[str ], str ]] = [
128+ lambda cmd : re .sub (pattern = r"\${{\s*coding-standards\.root\s*}}" , repl = str (root_path ), string = cmd ),
129+ lambda cmd : re .sub (pattern = r"\${{\s*layout\.root\s*}}" , repl = str (directory ), string = cmd )
130+ ]
131+ actions .append (ShellAction (action_args , modifiers = modifiers ))
128132 elif action_type == "file" :
129133 actions .append (FileAction (action_args ))
130134 else :
131135 raise Exception (f"Unknown action type { action_type } " )
132-
136+
133137 artifacts .append (ReleaseArtifact (artifact , actions , self .skip_checks ))
134138
135139 for artifact in artifacts :
@@ -153,7 +157,7 @@ def run(self) -> List[Path]:
153157 print (f"Downloading logs for { workflow_run .name } " )
154158 workflow_run .download_logs (Path (self .temp_workdir .name )) # type: ignore
155159 return list (map (Path , Path (self .temp_workdir .name ).glob ("**/*" )))
156-
160+
157161class WorkflowArtifactAction ():
158162
159163 def __init__ (self , workflow_runs : List [WorkflowRun .WorkflowRun ], ** kwargs : str ) -> None :
@@ -176,17 +180,29 @@ def run(self) -> List[Path]:
176180 print (f"Downloading artifacts for { workflow_run .name } to { self .temp_workdir .name } " )
177181 workflow_run .download_artifacts (Path (self .temp_workdir .name )) # type: ignore
178182 return list (map (Path , Path (self .temp_workdir .name ).glob ("**/*" )))
179-
183+
180184class ShellAction ():
181- def __init__ (self , command : str ) -> None :
185+ def __init__ (self , command : str , ** kwargs : Any ) -> None :
182186 self .command = command .strip ()
183187 self .temp_workdir = TemporaryDirectory ()
188+ self .options = kwargs
189+
190+ def _rewrite_command (self ) -> str :
191+ E = TypeVar ("E" )
192+ R = TypeVar ("R" )
193+ def lfold (fn : Callable [[R , E ], R ], lst : Sequence [E ], init : R ) -> R :
194+ return lfold (fn , lst [1 :], fn (init , lst [0 ])) if lst else init
195+ if 'modifiers' in self .options :
196+ return lfold (lambda acc , x : x (acc ), self .options ['modifiers' ], self .command )
197+ else :
198+ return self .command
184199
185200 def run (self ) -> List [Path ]:
186- concrete_command = re .sub (pattern = r"\${{\s*coding-standards\.root\s*}}" , repl = str (root_path ), string = self .command )
201+ #concrete_command = re.sub(pattern=r"\${{\s*coding-standards\.root\s*}}", repl=str(root_path), string=self.command)
202+ concrete_command = self ._rewrite_command ()
187203 subprocess .run (concrete_command , cwd = self .temp_workdir .name , check = True , shell = True )
188204 return list (map (Path , Path (self .temp_workdir .name ).glob ("**/*" )))
189-
205+
190206class FileAction ():
191207 def __init__ (self , path : Path ) -> None :
192208 self .path = path
@@ -200,7 +216,7 @@ def __init__(self, name: str, actions: List[Union[WorkflowLogAction, WorkflowArt
200216 self .actions = actions
201217 self .allow_no_files = allow_no_files
202218
203- def make (self , directory : Path ) -> Path :
219+ def make (self , directory : Path ) -> Optional [ Path ] :
204220 files : list [Path ] = [file for action in self .actions for file in action .run ()]
205221 if len (files ) == 0 :
206222 if not self .allow_no_files :
@@ -212,8 +228,8 @@ def make(self, directory: Path) -> Path:
212228 extension = "" .join (self .name .suffixes )[1 :]
213229 if not extension in ["zip" , "tar" , "tar.gz" , "tar.bz2" , "tar.xz" ]:
214230 raise Exception (f"Artifact { self .name } is not a support archive file, but has multiple files associated with it!" )
215-
216- ext_format_map = {
231+
232+ ext_format_map = {
217233 "zip" : "zip" ,
218234 "tar" : "tar" ,
219235 "tar.gz" : "gztar" ,
@@ -225,7 +241,7 @@ def make(self, directory: Path) -> Path:
225241 temp_dir_path = Path (temp_dir )
226242 for file in files :
227243 shutil .copy (file , temp_dir_path / file .name )
228-
244+
229245 return Path (shutil .make_archive (str (directory / self .name .with_suffix ("" )), ext_format_map [extension ], root_dir = temp_dir_path ))
230246
231247def main (args : 'argparse.Namespace' ) -> int :
@@ -248,13 +264,13 @@ def main(args: 'argparse.Namespace') -> int:
248264 if len (pull_candidates ) != 1 :
249265 print (f"Error: expected exactly one PR for SHA { args .head_sha } , but found { len (pull_candidates )} " , file = sys .stderr )
250266 return 1
251-
267+
252268 pull_request = pull_candidates [0 ]
253269
254270 if pull_request .state != "open" :
255271 print (f"Error: PR { pull_request .url } is not open" , file = sys .stderr )
256272 return 1
257-
273+
258274 print (f"Found PR { pull_request .url } based on { pull_request .base .ref } " )
259275
260276 rc_branch_regex = r"^rc/(?P<version>.*)$"
@@ -286,7 +302,7 @@ def main(args: 'argparse.Namespace') -> int:
286302
287303 action_workflow_run_url_regex = r"^https://(?P<github_url>[^/]+)/(?P<owner>[^/]+)/(?P<repo>[^/]+)/actions/runs/(?P<run_id>\d+)$"
288304 action_workflow_job_run_url_regex = r"^https://(?P<github_url>[^/]+)/(?P<owner>[^/]+)/(?P<repo>[^/]+)/actions/runs/(?P<run_id>\d+)/job/(?P<job_id>\d+)$"
289-
305+
290306 workflow_runs : List [WorkflowRun .WorkflowRun ] = []
291307 for check_run in check_runs : # type: ignore
292308 check_run = cast (CheckRun .CheckRun , check_run )
@@ -306,7 +322,7 @@ def main(args: 'argparse.Namespace') -> int:
306322 else :
307323 print (f"Unable to handle checkrun { check_run .name } with id { check_run .id } with { check_run .details_url } " )
308324 return 1
309-
325+
310326 print ("Filtering workflow runs to only include the latest run for each workflow." )
311327 workflow_runs_per_id : Dict [int , WorkflowRun .WorkflowRun ] = {}
312328 for workflow_run in workflow_runs :
0 commit comments