4545import gzip
4646import json
4747import os
48+ import re
4849import shutil
4950import sys
5051import tarfile
5152import urllib .parse
5253import urllib .request
54+ from pathlib import Path
5355
5456import mx
5557import mx_urlrewrites
56- import mx_util
5758
5859
5960SUITE = mx .suite ('graalpython' )
61+ GRAALOS_VERSIONS_PATH = Path (__file__ ).parent / "graalos_versions.json"
6062
6163
6264def run (* args , ** kwargs ):
6365 from mx_graalpython import run
6466 return run (* args , ** kwargs )
6567
6668
67- def _download_graalos_standalone_artifact (source , target ):
69+ def _load_graalos_overlay_urls (ci_overlays_dir : Path ):
70+ overlay_path = ci_overlays_dir / "graalpython.jsonnet"
71+ if not overlay_path .is_file ():
72+ mx .abort (f"Expected sibling ci-overlays checkout at { overlay_path } " )
73+
74+ content = overlay_path .read_text (encoding = "utf-8" )
75+ urls = {}
76+ for env_name , version_name in (
77+ ("GRAALPY_GRAALOS_TOOLCHAIN_URL" , "toolchain" ),
78+ ("GRAALPY_GRAALOS_RUNTIME_URL" , "runtime" ),
79+ ):
80+ match = re .search (rf'{ env_name } :\s*"([^"]+)"' , content )
81+ if not match :
82+ mx .abort (f"Could not find { env_name } in { overlay_path } " )
83+ urls [version_name ] = match .group (1 )
84+ return urls
85+
86+
87+ def update_graalos_versions ():
88+ ci_overlays_dir = Path (SUITE .dir ).parent / "ci-overlays"
89+ overlay_urls = _load_graalos_overlay_urls (ci_overlays_dir )
90+ versions = {
91+ name : resolve_latest_graalos_artifact_name (url )
92+ for name , url in overlay_urls .items ()
93+ }
94+ content = json .dumps (versions , indent = 2 , sort_keys = True )
95+ content += "\n "
96+ mx .update_file (GRAALOS_VERSIONS_PATH .as_posix (), content , showDiff = True )
97+ SUITE .vc .git_command (SUITE .dir , ["add" , GRAALOS_VERSIONS_PATH .relative_to (SUITE .dir )], abortOnError = True )
98+
99+
100+ def resolve_latest_graalos_artifact_name (source , on_fail = mx .abort ):
68101 source = mx_urlrewrites .rewriteurl (source )
69- if "artifact/latest" in source :
70- artifact_base_url = os .environ .get ("GRAALPY_GRAALOS_ARTIFACT_BASE_URL" )
71- if not artifact_base_url :
72- mx .abort ("GRAALPY_GRAALOS_ARTIFACT_BASE_URL must be set to resolve GraalOS artifact metadata" )
73- with urllib .request .urlopen (urllib .request .Request (source , headers = {"Accept" : "application/json" })) as response :
74- metadata = json .loads (response .read ().decode ("utf-8" ))
75- artifact_name = metadata .get ("artifactName" )
76- if not artifact_name :
77- mx .abort (f"GraalOS artifact metadata does not contain artifactName: { source } " )
78- if script := os .environ .get ("ARTIFACT_DOWNLOAD_SCRIPT" ):
79- run ([sys .executable , script , artifact_name , target ])
80- return
81- source = urllib .parse .urljoin (artifact_base_url , artifact_name )
102+ with urllib .request .urlopen (urllib .request .Request (source , headers = {"Accept" : "application/json" })) as response :
103+ metadata = json .loads (response .read ().decode ("utf-8" ))
104+ artifact_name = metadata .get ("artifactName" )
105+ if not artifact_name :
106+ on_fail (f"GraalOS artifact metadata does not contain artifactName: { source } " )
107+ return artifact_name
108+
109+
110+ def load_graalos_versions (on_fail = mx .abort ):
111+ try :
112+ with open (GRAALOS_VERSIONS_PATH , "r" , encoding = "utf-8" ) as fp :
113+ versions = json .load (fp )
114+ except FileNotFoundError :
115+ on_fail (f"GraalOS versions file not found: { GRAALOS_VERSIONS_PATH } " )
116+ except json .JSONDecodeError as exc :
117+ on_fail (f"Could not parse GraalOS versions file { GRAALOS_VERSIONS_PATH } : { exc } " )
118+
119+ for key in ("toolchain" , "runtime" ):
120+ value = versions .get (key )
121+ if not isinstance (value , str ) or not value :
122+ on_fail (f"GraalOS versions file must contain a non-empty string for { key !r} : { GRAALOS_VERSIONS_PATH } " )
123+ return versions
124+
125+
126+ def _download_graalos_standalone_artifact (artifact_name , target : Path , on_fail = mx .abort ):
127+ if script := os .environ .get ("ARTIFACT_DOWNLOAD_SCRIPT" ):
128+ run ([sys .executable , script , artifact_name , str (target )])
129+ return
130+
131+ artifact_base_url = os .environ .get ("GRAALPY_GRAALOS_ARTIFACT_BASE_URL" )
132+ if not artifact_base_url :
133+ on_fail ("GRAALPY_GRAALOS_ARTIFACT_BASE_URL must be set to download GraalOS artifacts" )
134+
135+ source = urllib .parse .urljoin (mx_urlrewrites .rewriteurl (artifact_base_url ), artifact_name )
82136
83137 mx .log (f"Downloading { source } to { target } " )
84138 with urllib .request .urlopen (source ) as response , open (target , "wb" ) as fp :
85139 shutil .copyfileobj (response , fp )
86140
87141
88- def _extract_tarball (tarball , destination , strip_components = 0 ):
89- if os . path . isdir ( destination ):
142+ def _extract_tarball (tarball : Path , destination : Path , strip_components = 0 , on_fail = mx . abort ):
143+ if destination . is_dir ( ):
90144 shutil .rmtree (destination )
91- mx_util . ensure_dir_exists ( destination )
145+ destination . mkdir ( parents = True , exist_ok = True )
92146 if strip_components == 0 :
93- mx .Extractor .create (tarball ).extract (destination )
147+ mx .Extractor .create (str ( tarball )) .extract (str ( destination ) )
94148 return
95149 if strip_components < 0 :
96150 mx .abort (f"strip_components must not be negative: { strip_components } " )
97151
98- with tarfile .open (tarball ) as archive :
152+ with tarfile .open (str ( tarball ) ) as archive :
99153 for member in archive :
100154 original_name = member .name
101155 stripped_name = "/" .join (original_name .split ("/" )[strip_components :])
102156 if not stripped_name :
103157 continue
104158 if not mx .Extractor ._is_sane_name (stripped_name ): # pylint: disable=protected-access
105- mx . abort (f"Refusing to extract unsafe archive entry after stripping: { original_name } " )
159+ on_fail (f"Refusing to extract unsafe archive entry after stripping: { original_name } " )
106160 member .name = stripped_name
107- archive .extract (member , destination )
161+ archive .extract (member , str ( destination ) )
108162 member .name = original_name
109163 os .utime (destination , None )
110164
111165
112- def _find_graalos_runtime_home (runtime_root ):
113- default = os . path . join ( runtime_root , "opt" , "graalos" )
114- if os . path . isdir ( default ):
166+ def _find_graalos_runtime_home (runtime_root : Path , on_fail = mx . abort ):
167+ default = runtime_root / "opt" / "graalos"
168+ if default . is_dir ( ):
115169 return default
116170 for root , dirs , _ in os .walk (runtime_root ):
117- if os .path .basename (root ) == "opt" and "graalos" in dirs :
118- return os .path .join (root , "graalos" )
119- mx .abort (f"Could not find opt/graalos in extracted GraalOS runtime artifact: { runtime_root } " )
171+ root_path = Path (root )
172+ if root_path .name == "opt" and "graalos" in dirs :
173+ return root_path / "graalos"
174+ on_fail (f"Could not find opt/graalos in extracted GraalOS runtime artifact: { runtime_root } " )
120175
121176
122- def _ensure_graalos_runtime_inputs (runtime_home ):
123- graalhost_dir = os . path . join ( runtime_home , "graalhost" )
177+ def _ensure_graalos_runtime_inputs (runtime_home : Path , on_fail = mx . abort ):
178+ graalhost_dir = runtime_home / "graalhost"
124179 required = [
125- os . path . join ( graalhost_dir , "graalhost" ) ,
126- os . path . join ( graalhost_dir , "libc.so" ) ,
180+ graalhost_dir / "graalhost" ,
181+ graalhost_dir / "libc.so" ,
127182 ]
128- libbinsweep = os . path . join ( graalhost_dir , "libbinsweep.so" )
129- if not os . path . exists (libbinsweep ):
130- optional_libbinsweep = os . path . join ( graalhost_dir , "optional" , "libbinsweep.so.gz" )
131- if os . path . exists (optional_libbinsweep ):
183+ libbinsweep = graalhost_dir / "libbinsweep.so"
184+ if not libbinsweep . exists ():
185+ optional_libbinsweep = graalhost_dir / "optional" / "libbinsweep.so.gz"
186+ if optional_libbinsweep . exists ():
132187 with gzip .open (optional_libbinsweep , "rb" ) as src , open (libbinsweep , "wb" ) as dst :
133188 shutil .copyfileobj (src , dst )
134189 required .append (libbinsweep )
135- missing = [path for path in required if not os . path .exists (path )]
190+ missing = [path for path in required if not path .exists ()]
136191 if missing :
137- mx . abort ("Extracted GraalOS runtime artifact is missing required files:\n " + "\n " .join (missing ))
192+ on_fail ("Extracted GraalOS runtime artifact is missing required files:\n " + "\n " .join ([ str ( p ) for p in missing ] ))
138193
139194
140- def graalpy_graalos_standalone_build_and_test (report = None ):
195+ def graalpy_graalos_standalone_build_and_test (report = None , on_fail = mx . abort ):
141196 del report # This gate executes an in-sandbox smoke test directly instead of using the source-tree test runner.
142- toolchain_url = os .environ .get ("GRAALPY_GRAALOS_TOOLCHAIN_URL" )
143- runtime_url = os .environ .get ("GRAALPY_GRAALOS_RUNTIME_URL" )
144- if not toolchain_url or not runtime_url :
145- mx .log ("Skipping GRAALPY_NATIVE_GRAALOS_STANDALONE build: GraalOS artifact URLs are not configured" )
197+ artifact_base_url = os .environ .get ("GRAALPY_GRAALOS_ARTIFACT_BASE_URL" )
198+ if not artifact_base_url :
199+ mx .log ("Skipping GRAALPY_NATIVE_GRAALOS_STANDALONE build: GRAALPY_GRAALOS_ARTIFACT_BASE_URL is not configured" )
146200 return
201+ versions = load_graalos_versions (on_fail = on_fail )
147202
148- work_dir = os . path . join (SUITE .dir , "mxbuild" , "graalos-standalone-ci" )
149- if os . path . isdir ( work_dir ):
203+ work_dir = Path (SUITE .dir ) / "mxbuild" / "graalos-standalone-ci"
204+ if work_dir . is_dir ( ):
150205 shutil .rmtree (work_dir )
151- mx_util . ensure_dir_exists ( work_dir )
152-
153- graalvm_tarball = os . path . join ( work_dir , "graalvm.tar.gz" )
154- runtime_tarball = os . path . join ( work_dir , "graalos-runtime.tar.gz" )
155- graalvm_home = os . path . join ( work_dir , "graalvm" )
156- runtime_root = os . path . join ( work_dir , "runtime" )
157-
158- _download_graalos_standalone_artifact (toolchain_url , graalvm_tarball )
159- _extract_tarball (graalvm_tarball , graalvm_home , strip_components = 1 )
160- musl_toolchain = os . path . join ( graalvm_home , "lib" , "toolchains" , "musl-swcfi" )
161- if not os . path . exists ( os . path . join ( graalvm_home , "bin" , mx .exe_suffix ("java" ))):
162- mx . abort (f"Extracted GraalOS toolchain artifact does not contain bin/java: { graalvm_home } " )
163- if not os . path . isdir ( musl_toolchain ):
164- mx . abort (f"Extracted GraalOS toolchain artifact does not contain musl-swcfi toolchain: { musl_toolchain } " )
165-
166- _download_graalos_standalone_artifact (runtime_url , runtime_tarball )
167- _extract_tarball (runtime_tarball , runtime_root )
168- graalos_runtime_home = _find_graalos_runtime_home (runtime_root )
169- _ensure_graalos_runtime_inputs (graalos_runtime_home )
206+ work_dir . mkdir ( parents = True , exist_ok = True )
207+
208+ graalvm_tarball = work_dir / "graalvm.tar.gz"
209+ runtime_tarball = work_dir / "graalos-runtime.tar.gz"
210+ graalvm_home = work_dir / "graalvm"
211+ runtime_root = work_dir / "runtime"
212+
213+ _download_graalos_standalone_artifact (versions [ "toolchain" ] , graalvm_tarball , on_fail = on_fail )
214+ _extract_tarball (graalvm_tarball , graalvm_home , strip_components = 1 , on_fail = on_fail )
215+ musl_toolchain = graalvm_home / "lib" / "toolchains" / "musl-swcfi"
216+ if not ( graalvm_home / "bin" / mx .exe_suffix ("java" )). is_file ( ):
217+ on_fail (f"Extracted GraalOS toolchain artifact does not contain bin/java: { graalvm_home } " )
218+ if not musl_toolchain . is_dir ( ):
219+ on_fail (f"Extracted GraalOS toolchain artifact does not contain musl-swcfi toolchain: { musl_toolchain } " )
220+
221+ _download_graalos_standalone_artifact (versions [ "runtime" ] , runtime_tarball , on_fail = on_fail )
222+ _extract_tarball (runtime_tarball , runtime_root , on_fail = on_fail )
223+ graalos_runtime_home = _find_graalos_runtime_home (runtime_root , on_fail = on_fail )
224+ _ensure_graalos_runtime_inputs (graalos_runtime_home , on_fail = on_fail )
170225
171226 from mx_graalpython import extend_os_env , run_mx , _graalpy_launcher
172227 env = extend_os_env (
173- JAVA_HOME = graalvm_home ,
174- MUSL_TOOLCHAIN = musl_toolchain ,
175- GRAALOS_TOOLCHAIN_PATH = musl_toolchain ,
176- GRAALOS_RUNTIME_HOME = graalos_runtime_home ,
228+ JAVA_HOME = str ( graalvm_home ) ,
229+ MUSL_TOOLCHAIN = str ( musl_toolchain ) ,
230+ GRAALOS_TOOLCHAIN_PATH = str ( musl_toolchain ) ,
231+ GRAALOS_RUNTIME_HOME = str ( graalos_runtime_home ) ,
177232 NATIVE_IMAGE_EXPERIMENTAL_OPTIONS_ARE_FATAL = "false" ,
178233 )
179- run_mx ([
234+ result = run_mx ([
180235 "--multitarget=linux-amd64-musl-swcfi" ,
181236 "build" ,
182237 "--target" , "GRAALPY_NATIVE_GRAALOS_STANDALONE" ,
183- ], env = env )
184-
185- standalone_home = os .path .join (SUITE .dir , "mxbuild" , "linux-amd64" , "GRAALPY_NATIVE_GRAALOS_STANDALONE" )
186- launcher = os .path .join (standalone_home , "bin" , _graalpy_launcher ())
187- if not os .path .exists (launcher ):
188- mx .abort (f"GRAALPY_NATIVE_GRAALOS_STANDALONE launcher was not built: { launcher } " )
189-
190- test_path = os .path .join (
191- SUITE .dir ,
192- "graalpython" ,
193- "com.oracle.graal.python.test" ,
194- "src" ,
195- "tests" ,
196- "test_graalos_standalone.py" ,
197- )
238+ ], env = env , nonZeroIsFatal = (on_fail == mx .abort )) # pylint: disable=comparison-with-callable
239+ if result != 0 :
240+ on_fail ("Building GRAALPY_NATIVE_GRAALOS_STANDALONE failed" )
241+
242+ standalone_home = Path (SUITE .dir ) / "mxbuild" / "linux-amd64" / "GRAALPY_NATIVE_GRAALOS_STANDALONE"
243+ launcher = standalone_home / "bin" / _graalpy_launcher ()
244+ if not launcher .exists ():
245+ on_fail (f"GRAALPY_NATIVE_GRAALOS_STANDALONE launcher was not built: { launcher } " )
246+
247+ test_path = Path (SUITE .dir ) / "graalpython" / "com.oracle.graal.python.test" / "src" / "tests" / "test_graalos_standalone.py"
198248 with open (test_path , "r" , encoding = "utf-8" ) as f :
199249 smoke_test = f .read ()
200250 smoke_test += """
@@ -205,4 +255,6 @@ def graalpy_graalos_standalone_build_and_test(report=None):
205255"""
206256 smoke_test_arg = base64 .b64encode (smoke_test .encode ("utf-8" )).decode ("ascii" )
207257 smoke_test_command = f"import base64; exec(base64.b64decode({ smoke_test_arg !r} ).decode('utf-8'))"
208- run ([launcher , "-c" , smoke_test_command ], env = env )
258+ result = run ([str (launcher ), "-c" , smoke_test_command ], env = env , nonZeroIsFatal = (on_fail == mx .abort )) # pylint: disable=comparison-with-callable
259+ if result != 0 :
260+ on_fail ("Testing GraalOS standalone failed" )
0 commit comments