88import os
99import sys
1010import subprocess
11- import tarfile
1211import asyncio
1312import json
1413from dataclasses import dataclass
@@ -21,11 +20,6 @@ USER_AGENT = "gh-distribute-toolchain by swiftwasm/swiftwasm-build"
2120
2221@dataclass
2322class Secrets :
24- DARWIN_TOOLCHAIN_APPLICATION_CERT : str
25- DARWIN_TOOLCHAIN_INSTALLER_CERT : str
26- DARWIN_TOOLCHAIN_NOTARIZE_EMAIL : str
27- DARWIN_TOOLCHAIN_NOTARIZE_TEAM_ID : str
28- DARWIN_TOOLCHAIN_NOTARIZE_PASSWORD : str
2923 GITHUB_TOKEN : str
3024
3125 @staticmethod
@@ -40,16 +34,6 @@ class Secrets:
4034 github_token = result .stdout .decode ("utf-8" ).strip ()
4135
4236 return Secrets (
43- DARWIN_TOOLCHAIN_APPLICATION_CERT = Secrets .env_value (
44- "DARWIN_TOOLCHAIN_APPLICATION_CERT" ),
45- DARWIN_TOOLCHAIN_INSTALLER_CERT = Secrets .env_value (
46- "DARWIN_TOOLCHAIN_INSTALLER_CERT" ),
47- DARWIN_TOOLCHAIN_NOTARIZE_EMAIL = Secrets .env_value (
48- "DARWIN_TOOLCHAIN_NOTARIZE_EMAIL" ),
49- DARWIN_TOOLCHAIN_NOTARIZE_TEAM_ID = Secrets .env_value (
50- "DARWIN_TOOLCHAIN_NOTARIZE_TEAM_ID" ),
51- DARWIN_TOOLCHAIN_NOTARIZE_PASSWORD = Secrets .env_value (
52- "DARWIN_TOOLCHAIN_NOTARIZE_PASSWORD" ),
5337 GITHUB_TOKEN = github_token ,
5438 )
5539
@@ -58,130 +42,6 @@ class Secrets:
5842 return os .environ .get (key )
5943
6044
61- class DarwinToolchainPackaging :
62- def __init__ (self , secrets : Secrets ,
63- dry_run : bool = False , verbose : bool = False ):
64- self .secrets = secrets
65- self .dry_run = dry_run
66- self .verbose = verbose
67-
68- async def package (self , toolchain_dir : str , tag_name : str , pkg_path : str ,
69- swift_source_dir : str ):
70- self .update_info_plist (toolchain_dir )
71- self .sign_darwin_toolchain (toolchain_dir )
72- await self .create_installer (toolchain_dir , pkg_path , tag_name , swift_source_dir )
73-
74- def update_info_plist (self , toolchain_dir : str ):
75- display_name = os .environ .get ("DARWIN_TOOLCHAIN_DISPLAY_NAME" )
76- info_plist = f"{ toolchain_dir } /Info.plist"
77- if display_name :
78- subprocess .check_output ([
79- "/usr/libexec/PlistBuddy" ,
80- "-c" , f"Set DisplayName { display_name } " , info_plist
81- ])
82-
83- display_name_short = os .environ .get (
84- "DARWIN_TOOLCHAIN_DISPLAY_NAME_SHORT" )
85- if display_name_short :
86- subprocess .check_output ([
87- "/usr/libexec/PlistBuddy" ,
88- "-c" , f"Set ShortDisplayName { display_name_short } " , info_plist
89- ])
90-
91- def sign_darwin_toolchain (self , toolchain_dir : str ):
92- if self .secrets .DARWIN_TOOLCHAIN_APPLICATION_CERT is None :
93- raise Exception ("Missing DARWIN_TOOLCHAIN_APPLICATION_CERT" )
94-
95- codesign_args = [
96- "/usr/bin/codesign" ,
97- "--force" , "--verify" , "--verbose" , "--deep" ,
98- "--options" , "runtime" , "--timestamp" ,
99- "--sign" , self .secrets .DARWIN_TOOLCHAIN_APPLICATION_CERT
100- ]
101-
102- for root , dirs , files in os .walk (toolchain_dir ):
103- for file in files :
104- path = os .path .join (root , file )
105- if not self .is_macho_binary (path ):
106- continue
107- self .subprocess_run (codesign_args + [path ], check = True )
108-
109- self .subprocess_run (codesign_args + [toolchain_dir ], check = True )
110-
111- async def create_installer (self , toolchain_dir : str , pkg_path : str ,
112- tag_name : str , swift_source_dir : str ):
113- toolchain_name = tag_name
114- toolchain_installer_package = pkg_path
115- toolchain_install_location = (
116- f"/Library/Developer/Toolchains/{ toolchain_name } .xctoolchain" )
117- toolchain_version = subprocess .check_output ([
118- "/usr/libexec/PlistBuddy" ,
119- "-c" , "Print Version string" ,
120- f"{ toolchain_dir } /Info.plist"
121- ]).decode ("utf-8" )
122- toolchain_bundle_identifier = subprocess .check_output ([
123- "/usr/libexec/PlistBuddy" ,
124- "-c" , "Print CFBundleIdentifier string" ,
125- f"{ toolchain_dir } /Info.plist"
126- ]).decode ("utf-8" )
127-
128- self .subprocess_run ([
129- f"{ swift_source_dir } /utils/toolchain-installer" , toolchain_dir ,
130- toolchain_bundle_identifier ,
131- self .secrets .DARWIN_TOOLCHAIN_INSTALLER_CERT ,
132- toolchain_installer_package ,
133- toolchain_install_location ,
134- toolchain_version ,
135- f"{ swift_source_dir } /utils/darwin-installer-scripts"
136- ], check = True )
137-
138- await self .check_async_subprocess (
139- "xcrun" , "notarytool" , "submit" ,
140- toolchain_installer_package ,
141- "--wait" ,
142- "--apple-id" , self .secrets .DARWIN_TOOLCHAIN_NOTARIZE_EMAIL ,
143- "--team-id" , self .secrets .DARWIN_TOOLCHAIN_NOTARIZE_TEAM_ID ,
144- "--password" , self .secrets .DARWIN_TOOLCHAIN_NOTARIZE_PASSWORD )
145-
146- self .subprocess_run (["xcrun" , "stapler" , "staple" ,
147- toolchain_installer_package ], check = True )
148-
149- def subprocess_run (self , args , ** kwargs ):
150- """
151- Run a non-mutating subprocess and print the command if
152- verbose or dry_run is True.
153- """
154- if self .verbose or self .dry_run :
155- print (" " .join (args ))
156- return subprocess .run (args , ** kwargs )
157-
158- async def check_async_subprocess (self , program , * args ):
159- if self .verbose or self .dry_run :
160- print (f"[async] { program } { ' ' .join (args )} " )
161- if self .dry_run :
162- return
163- proc = await asyncio .subprocess .create_subprocess_exec (program , * args )
164- retcode = await proc .wait ()
165- if retcode != 0 :
166- raise Exception (f"Failed to execute: { ' ' .join (args )} " )
167-
168- def is_macho_binary (self , file_path ):
169- if not os .path .exists (file_path ):
170- return False
171- magic_bytes = None
172- with open (file_path , mode = "rb" ) as f :
173- magic_bytes = f .read (4 )
174- macho_bytes = [
175- [0xca , 0xfe , 0xba , 0xbe ], # Mach-O Fat Binary
176- [0xcf , 0xfa , 0xed , 0xfe ], # Mach-O 64-bit executable
177- [0xce , 0xfa , 0xed , 0xfe ], # Mach-O 32-bit executable
178- ]
179- for b in macho_bytes :
180- if magic_bytes == bytes (b ):
181- return True
182- return False
183-
184-
18545class GitHub :
18646 def __init__ (self , token : str , repo : str = "swiftwasm/swiftwasm-build" ):
18747 self .token = token
@@ -348,13 +208,14 @@ class Distribution:
348208 async def run (self , options ):
349209 downloads = []
350210 for artifact in self .toolchain_artifacts (options ):
351- if options . only_swift_sdk :
352- if not artifact [" name" ]. endswith ( "-artifactbundle" ):
353- print ( f"Skipping { artifact [ 'name' ] } because it's not an"
354- " artifactbundle for --only-swift-sdk" )
355- continue
211+ if not artifact [ "name" ]. endswith ( "-artifactbundle" ) :
212+ print ( f"Skipping { artifact [' name' ] } because it's not an"
213+ " artifactbundle for --only-swift-sdk" )
214+ continue
215+
356216 _ , scheme , _ = derive_platform_suffix_and_scheme (
357217 artifact ["name" ], options .scheme )
218+
358219 if options .scheme != scheme :
359220 print (f"Skipping { artifact ['name' ]} because it's not scheme { options .scheme } " )
360221 # Skip unrelated artifact
@@ -388,22 +249,16 @@ class Distribution:
388249 # Create tag and release on GitHub
389250 if not tag_name :
390251 for artifact , artifact_path in downloaded_paths :
391- if artifact ["name" ].endswith ("-installable" ):
392- artifact_path = self .unpack_toolchain (artifact_path )
393- tag_name = self .guess_tag_name (artifact_path )
394- break
395- elif artifact ["name" ].endswith ("-artifactbundle" ):
396- platform_suffix , _ , target_triple = derive_platform_suffix_and_scheme (
397- artifact ["name" ], options .scheme )
398- bundle_path = await self .unpack_artifactbundle (artifact_path )
399- dirents = os .listdir (bundle_path )
400- dirents .remove ("info.json" )
401- sdk_artifact_id = dirents [0 ]
402- if not platform_suffix :
403- tag_name = "swift-wasm-" + sdk_artifact_id .removesuffix ("-" + target_triple )
404- else :
405- tag_name = "swift-wasm-" + sdk_artifact_id .removesuffix ("-wasm" )
406- break
252+ if not artifact ["name" ].endswith ("-artifactbundle" ):
253+ continue
254+ platform_suffix , _ , target_triple = derive_platform_suffix_and_scheme (
255+ artifact ["name" ], options .scheme )
256+ bundle_path = await self .unpack_artifactbundle (artifact_path )
257+ dirents = os .listdir (bundle_path )
258+ dirents .remove ("info.json" )
259+ sdk_artifact_id = dirents [0 ]
260+ tag_name = "swift-wasm-" + sdk_artifact_id .removesuffix ("-" + target_triple )
261+ break
407262
408263 if not tag_name :
409264 raise Exception ("Could not determine tag name" )
@@ -425,10 +280,7 @@ class Distribution:
425280 artifact , artifact_path = downloaded
426281 platform_suffix , scheme , target_triple = derive_platform_suffix_and_scheme (
427282 artifact ["name" ], options .scheme )
428- if not platform_suffix :
429- artifact_format = SDKArtifactFormatV3 (target_triple )
430- else :
431- artifact_format = SDKArtifactFormatV2 (platform_suffix )
283+ artifact_format = SDKArtifactFormatV3 (target_triple )
432284 bundle_path = await self .unpack_artifactbundle (artifact_path )
433285 package = await self .package_artifactbundle (
434286 bundle_path , tag_name , artifact_format )
@@ -462,73 +314,6 @@ class Distribution:
462314 self .update_release_notes_with_checksums (
463315 release , swift_version , swiftwasm_build_version )
464316
465- if options .only_swift_sdk :
466- return
467-
468- # Package and upload toolchains
469- for artifact , artifact_path in downloaded_paths :
470- import shutil
471- if not artifact ["name" ].endswith ("-installable" ):
472- continue
473-
474- platform_suffix , _ , _ = derive_platform_suffix_and_scheme (
475- artifact ["name" ], options .scheme )
476- artifact_path = self .unpack_toolchain (artifact_path )
477- package , artifact_path = await self .package_toolchain (
478- artifact_path , tag_name , platform_suffix )
479- if options .dry_run :
480- print (f"Skip uploading actual artifact \" { package } \" " )
481- continue
482- self .upload_to_release (package , release )
483- if options .optimize_disk_footprint :
484- print (f"Removing { artifact_path } to optimize disk footprint" )
485- shutil .rmtree (artifact_path )
486-
487- def guess_tag_name (self , path ):
488- return os .path .basename (path )
489-
490- async def package_toolchain (self , artifact_path : str , tag_name : str ,
491- platform_suffix : str ):
492- print (f"Packaging { artifact_path } " )
493- import shutil
494- dest_dir = os .path .dirname (artifact_path )
495- artifacts_path = os .path .dirname (dest_dir )
496-
497- if os .path .basename (artifact_path ) != tag_name :
498- # e.g.
499- # dest_dir:
500- # <run-id>/swift-wasm-DEVELOPMENT-SNAPSHOT-amazonlinux2_x86_64
501- # artifact_path:
502- # <dest-dir>/swift-wasm-DEVELOPMENT-SNAPSHOT-2023-6-25-a
503- # dest_path:
504- # <dest-dir>/swift-wasm-DEVELOPMENT-SNAPSHOT-2023-6-25-b
505- print (f"Re-package { artifact_path } as { tag_name } " )
506- dest_path = os .path .join (dest_dir , tag_name )
507- shutil .rmtree (dest_path , ignore_errors = True )
508- shutil .move (artifact_path , dest_path )
509- artifact_path = dest_path
510-
511- if platform_suffix .startswith ("macos_" ):
512- pkg_path = os .path .join (
513- artifacts_path , f"{ tag_name } -{ platform_suffix } .pkg" )
514- if os .path .exists (pkg_path ):
515- return [pkg_path , artifact_path ]
516- swift_source_dir = os .path .join (self .checkout_dir , "swift" )
517- await DarwinToolchainPackaging (
518- self .secrets , self .dry_run , self .verbose
519- ).package (artifact_path , tag_name , pkg_path , swift_source_dir )
520- return [pkg_path , artifact_path ]
521- else :
522- tarball_path = os .path .join (
523- artifacts_path , f"{ tag_name } -{ platform_suffix } .tar.gz" )
524- if os .path .exists (tarball_path ):
525- return [tarball_path , artifact_path ]
526- tar_args = ["tar" , "cfz" , tarball_path ,
527- "-C" , os .path .dirname (artifact_path ),
528- os .path .basename (artifact_path )]
529- self .subprocess_run (tar_args , check = True )
530- return [tarball_path , artifact_path ]
531-
532317 async def package_artifactbundle (
533318 self , artifact_path : str , tag_name : str ,
534319 artifact_format : SDKArtifactFormat
@@ -624,29 +409,6 @@ class Distribution:
624409 "unzip" , "-q" , bundlezip_path , "-d" , os .path .dirname (bundle_path ))
625410 return bundle_path
626411
627- def unpack_toolchain (self , zip_tarball ):
628- tarball_name , tarball_path = self .unzip_artifact (zip_tarball )
629-
630- # Remove the .tar.gz extension
631- tarball_basename = os .path .splitext (
632- os .path .splitext (tarball_name )[0 ])[0 ]
633- tarball_output_dir = os .path .join (
634- os .path .dirname (tarball_path ), tarball_basename )
635- if not os .path .exists (tarball_output_dir ):
636- os .makedirs (tarball_output_dir , exist_ok = True )
637- print (f"Extracting { tarball_path } ..." )
638- with tarfile .open (tarball_path , "r:gz" ) as tar :
639- tar .extractall (tarball_output_dir )
640- else :
641- print (f"Tarball already extracted at { tarball_output_dir } " )
642-
643- dirents = os .listdir (tarball_output_dir )
644- if len (dirents ) != 1 :
645- raise Exception ((
646- f"Unexpected number of files in { tarball_output_dir } :"
647- f" { len (dirents )} (expected 1)" ))
648- return os .path .join (tarball_output_dir , dirents [0 ])
649-
650412 async def download_artifact (self , artifact ):
651413 if not os .path .exists (self .artifacts_dir ):
652414 os .makedirs (self .artifacts_dir , exist_ok = True )
@@ -785,8 +547,7 @@ You can install Swift SDKs for WebAssembly using the following commands:
785547 artifacts = self .github .list_artifacts (self .run_id )
786548 for artifact in artifacts :
787549 artifact_name = artifact ["name" ]
788- should_yield = artifact_name .endswith ("-artifactbundle" ) or \
789- artifact_name .endswith ("-installable" )
550+ should_yield = artifact_name .endswith ("-artifactbundle" )
790551 if should_yield :
791552 yield artifact
792553
@@ -830,18 +591,12 @@ def derive_platform_suffix_and_scheme(
830591 e.g.
831592 "main-wasm32-unknown-wasi-artifactbundle", scheme="main"
832593 -> [None, "main", "wasm32-unknown-wasi"]
833- "macos_x86_64-main-artifactbundle", scheme="main"
834- -> ["macos_x86_64", "main", "wasm32-unknown-wasi"]
835- "ubuntu22.04_x86_64-installable", scheme="main"
836- -> ["ubuntu22.04_x86_64", "main", "wasm32-unknown-wasi"]
837594 """
838595
839596 # v3 artifact name: <scheme>-<target-triple>-artifactbundle
840- # v2 artifact name: <platform-suffix>-<scheme>-(installable|artifactbundle)
841- # v1 artifact name: <platform-suffix>-installable
842597 # Note that <scheme> can contain '-'.
843598 name : str = artifact_name
844- artifact_suffixes = ("-installable" , "- artifactbundle" )
599+ artifact_suffixes = ("-artifactbundle" )
845600 if not name .endswith (artifact_suffixes ):
846601 raise Exception ((f"Unexpected artifact name { name } "
847602 f", expected to have one of \" { artifact_suffixes } \" "
@@ -857,14 +612,6 @@ def derive_platform_suffix_and_scheme(
857612 target_triple = rest [:- len ("-artifactbundle" )]
858613 return [None , target_scheme , target_triple ]
859614
860- components = name .split ("-" )
861- if len (components ) >= 3 :
862- scheme = "-" .join (components [1 :- 1 ])
863- return [components [0 ], scheme , "wasm32-unknown-wasi" ]
864- else :
865- # Assume legacy representation only used for the main scheme
866- return [components [0 ], "main" , "wasm32-unknown-wasi" ]
867-
868615
869616def latest_success_run_id (gh : GitHub , workflow_name : str , branch : str , scheme : str ):
870617 """
@@ -879,7 +626,7 @@ def latest_success_run_id(gh: GitHub, workflow_name: str, branch: str, scheme: s
879626 artifacts = gh .list_artifacts (run ["id" ])
880627 for artifact in artifacts :
881628 artifact_name = artifact ["name" ]
882- if not artifact_name .endswith ("-installable" ) and not artifact_name . endswith ( "- artifactbundle" ):
629+ if not artifact_name .endswith ("-artifactbundle" ):
883630 continue
884631 _ , artifact_scheme , _ = derive_platform_suffix_and_scheme (artifact_name , scheme )
885632 if artifact_scheme == scheme :
0 commit comments