#!/usr/bin/env python3 """Upload artifact to Gitea Actions runtime, falling back to local save. Usage: upload_artifacts.py [...] """ import glob import json import os import sys import urllib.request import urllib.error HERE = os.path.dirname(os.path.abspath(__file__)) ARTIFACTS_DIR = os.path.join(os.path.dirname(HERE), "artifacts") def _upload_via_runtime(artifact_name, payload, runtime_url, runtime_token): """Upload via Gitea Actions runtime API.""" url = f"{runtime_url}/_apis/artifact/build/{artifact_name}/{artifact_name}/upload" headers = { "Authorization": f"Bearer {runtime_token}", "Content-Type": "application/octet-stream", "Accept": "*/*", } req = urllib.request.Request(url, data=payload, headers=headers, method="PUT") with urllib.request.urlopen(req) as resp: return resp.status in (200, 201, 204) def _save_locally(artifact_name, payload): """Fallback: save to local filesystem.""" os.makedirs(ARTIFACTS_DIR, exist_ok=True) dest = os.path.join(ARTIFACTS_DIR, f"{artifact_name}.zip") with open(dest, "wb") as f: f.write(payload) return dest def _resolve_files(patterns): """Resolve glob patterns into actual file paths.""" files = [] for p in patterns: expanded = glob.glob(p) if expanded: files.extend(expanded) else: # Maybe it's a literal path files.append(p) return files def main(): if len(sys.argv) < 3: print(f"Usage: {sys.argv[0]} [...]") sys.exit(1) artifact_name = sys.argv[1] file_or_globs = sys.argv[2:] # Resolve all files files = _resolve_files(file_or_globs) # Check that at least one file exists missing = [f for f in files if not os.path.isfile(f)] for f in missing: print(f"[upload-artifact] WARNING: file not found (skipped): {f}") existing = [f for f in files if os.path.isfile(f)] if not existing: print(f"[upload-artifact] ERROR: no valid files found in: {' '.join(file_or_globs)}") sys.exit(1) # For individual upload: save each file separately if "wheels-individual" in artifact_name: for f in existing: name = os.path.basename(f) individual_name = f"{artifact_name}-{name}" with open(f, "rb") as fh: payload = fh.read() runtime_url = os.environ.get("ACTIONS_RUNTIME_URL", "") runtime_token = os.environ.get("ACTIONS_RUNTIME_TOKEN", "") if runtime_url and runtime_token: try: _upload_via_runtime(individual_name, payload, runtime_url, runtime_token) print(f"[upload-artifact] Uploaded {f} ({len(payload)} bytes)") continue except Exception as e: print(f"[upload-artifact] Runtime upload failed for {f}: {e}") dest = _save_locally(individual_name, payload) print(f"[upload-artifact] Saved {f} to {dest} ({len(payload)} bytes)") print(f"[upload-artifact] Done — {len(existing)} file(s) uploaded.") return # Bundle upload: read first/only file file_path = existing[0] if len(existing) > 1: print(f"[upload-artifact] WARNING: bundle upload uses first file only: {existing[0]}") with open(file_path, "rb") as f: payload = f.read() runtime_url = os.environ.get("ACTIONS_RUNTIME_URL", "") runtime_token = os.environ.get("ACTIONS_RUNTIME_TOKEN", "") if runtime_url and runtime_token: try: ok = _upload_via_runtime(artifact_name, payload, runtime_url, runtime_token) if ok: print(f"[upload-artifact] Uploaded {file_path} ({len(payload)} bytes)") return except urllib.error.URLError as e: print(f"[upload-artifact] Runtime upload failed (URLError): {e.reason}") except Exception as e: print(f"[upload-artifact] Runtime upload failed: {e}") # Fallback dest = _save_locally(artifact_name, payload) print(f"[upload-artifact] Saved artifact to {dest} ({len(payload)} bytes)") print("[upload-artifact] Done — 1 file(s) in '{}'.".format(artifact_name)) if __name__ == "__main__": main()