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 , on_fail = mx .abort ):
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- on_fail ("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- on_fail (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 , on_fail = mx .abort ):
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 :])
@@ -104,99 +158,93 @@ def _extract_tarball(tarball, destination, strip_components=0, on_fail=mx.abort)
104158 if not mx .Extractor ._is_sane_name (stripped_name ): # pylint: disable=protected-access
105159 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 , on_fail = mx .abort ):
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" )
171+ root_path = Path (root )
172+ if root_path .name == "opt" and "graalos" in dirs :
173+ return root_path / "graalos"
119174 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 , on_fail = mx .abort ):
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- on_fail ("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
140195def 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 )
206+ work_dir . mkdir ( parents = True , exist_ok = True )
152207
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" )
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"
157212
158- _download_graalos_standalone_artifact (toolchain_url , graalvm_tarball , on_fail = on_fail )
213+ _download_graalos_standalone_artifact (versions [ "toolchain" ] , graalvm_tarball , on_fail = on_fail )
159214 _extract_tarball (graalvm_tarball , graalvm_home , strip_components = 1 , on_fail = on_fail )
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" ))):
215+ musl_toolchain = graalvm_home / "lib" / "toolchains" / "musl-swcfi"
216+ if not ( graalvm_home / "bin" / mx .exe_suffix ("java" )). is_file ( ):
162217 on_fail (f"Extracted GraalOS toolchain artifact does not contain bin/java: { graalvm_home } " )
163- if not os . path . isdir ( musl_toolchain ):
218+ if not musl_toolchain . is_dir ( ):
164219 on_fail (f"Extracted GraalOS toolchain artifact does not contain musl-swcfi toolchain: { musl_toolchain } " )
165220
166- _download_graalos_standalone_artifact (runtime_url , runtime_tarball , on_fail = on_fail )
221+ _download_graalos_standalone_artifact (versions [ "runtime" ] , runtime_tarball , on_fail = on_fail )
167222 _extract_tarball (runtime_tarball , runtime_root , on_fail = on_fail )
168223 graalos_runtime_home = _find_graalos_runtime_home (runtime_root , on_fail = on_fail )
169224 _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 )
179234 result = run_mx ([
180235 "--multitarget=linux-amd64-musl-swcfi" ,
181236 "build" ,
182237 "--target" , "GRAALPY_NATIVE_GRAALOS_STANDALONE" ,
183- ], env = env , nonZeroIsFatal = (on_fail == mx .abort ))
238+ ], env = env , nonZeroIsFatal = (on_fail == mx .abort )) # pylint: disable=comparison-with-callable
184239 if result != 0 :
185240 on_fail ("Building GRAALPY_NATIVE_GRAALOS_STANDALONE failed" )
186241
187- standalone_home = os . path . join (SUITE .dir , "mxbuild" , "linux-amd64" , "GRAALPY_NATIVE_GRAALOS_STANDALONE" )
188- launcher = os . path . join ( standalone_home , "bin" , _graalpy_launcher () )
189- if not os . path . exists (launcher ):
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 ():
190245 on_fail (f"GRAALPY_NATIVE_GRAALOS_STANDALONE launcher was not built: { launcher } " )
191246
192- test_path = os .path .join (
193- SUITE .dir ,
194- "graalpython" ,
195- "com.oracle.graal.python.test" ,
196- "src" ,
197- "tests" ,
198- "test_graalos_standalone.py" ,
199- )
247+ test_path = Path (SUITE .dir ) / "graalpython" / "com.oracle.graal.python.test" / "src" / "tests" / "test_graalos_standalone.py"
200248 with open (test_path , "r" , encoding = "utf-8" ) as f :
201249 smoke_test = f .read ()
202250 smoke_test += """
@@ -207,6 +255,6 @@ def graalpy_graalos_standalone_build_and_test(report=None, on_fail=mx.abort):
207255"""
208256 smoke_test_arg = base64 .b64encode (smoke_test .encode ("utf-8" )).decode ("ascii" )
209257 smoke_test_command = f"import base64; exec(base64.b64decode({ smoke_test_arg !r} ).decode('utf-8'))"
210- result = run ([launcher , "-c" , smoke_test_command ], env = env , nonZeroIsFatal = (on_fail == mx .abort ))
258+ result = run ([str ( launcher ) , "-c" , smoke_test_command ], env = env , nonZeroIsFatal = (on_fail == mx .abort )) # pylint: disable=comparison-with-callable
211259 if result != 0 :
212260 on_fail ("Testing GraalOS standalone failed" )
0 commit comments