File indexing completed on 2026-05-13 08:55:22
0001
0002 """
0003 Extract package versions used in eic/containers repository tags.
0004
0005 Instead of running singularity/spack inside containers, this script checks out
0006 the eic/containers repo at each tag and reads the package version directly from
0007 either spack-environment/packages.yaml (newer format) or
0008 spack-environment/dev/spack.yaml (older format) or spack.yaml (earliest tags).
0009
0010 Produces the same output files as versions.sh / versions_dd4hep.sh:
0011 - tags: all upstream release tags with dates
0012 - eic_container_tags: filtered eic/containers tags with dates
0013 - result: upstream version tag + date for each container tag
0014
0015 Usage:
0016 python versions.py [package]
0017
0018 package: acts (default), dd4hep, or any spack package name
0019 """
0020
0021 import argparse
0022 import os
0023 import re
0024 import subprocess
0025 import sys
0026
0027 import yaml
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039 def _identity_version_to_tag(version):
0040 """v{version} — used by acts and most projects."""
0041 return f"v{version}"
0042
0043
0044 def _dd4hep_version_to_tag(version):
0045 """
0046 DD4hep spack version '1.25.1' -> git tag 'v01-25-01'.
0047 Each dot-separated component is zero-padded to 2 digits, joined by '-'.
0048 """
0049 parts = version.split(".")
0050 return "v" + "-".join(p.zfill(2) for p in parts)
0051
0052
0053 PACKAGES = {
0054 "acts": {
0055 "repo_url": "https://github.com/acts-project/acts",
0056 "repo_dir": "acts",
0057 "version_to_tag": _identity_version_to_tag,
0058 },
0059 "dd4hep": {
0060 "repo_url": "https://github.com/AIDASoft/DD4hep.git",
0061 "repo_dir": "DD4hep",
0062 "version_to_tag": _dd4hep_version_to_tag,
0063 },
0064 }
0065
0066
0067
0068
0069
0070
0071 def run(cmd, **kwargs):
0072 """Run a shell command and return stdout."""
0073 result = subprocess.run(cmd, capture_output=True, text=True, **kwargs)
0074 if result.returncode != 0:
0075 print(f"Command failed: {' '.join(cmd)}", file=sys.stderr)
0076 print(result.stderr, file=sys.stderr)
0077 return result.stdout
0078
0079
0080 def clone_if_missing(url, path):
0081 """Clone a git repository if the directory doesn't already exist."""
0082 if not os.path.isdir(path):
0083 print(f"Cloning {url}...")
0084 run(["git", "clone", url, path])
0085 else:
0086 print(f"Using existing {path}")
0087
0088
0089 def get_tags_with_dates(repo_path):
0090 """
0091 Get all tags with their creator dates from a git repo.
0092 Returns list of "tag|date" strings.
0093 """
0094 output = run(
0095 ["git", "-C", repo_path, "for-each-ref",
0096 "--format=%(refname:short)|%(creatordate:format:%b %d %Y %H:%M:%S)",
0097 "refs/tags/*"]
0098 )
0099 return [line for line in output.strip().splitlines() if line]
0100
0101
0102 def git_show(repo_path, ref, filepath):
0103 """Show file content at a given git ref. Returns None if file doesn't exist."""
0104 result = subprocess.run(
0105 ["git", "-C", repo_path, "show", f"{ref}:{filepath}"],
0106 capture_output=True, text=True,
0107 )
0108 if result.returncode != 0:
0109 return None
0110 return result.stdout
0111
0112
0113
0114
0115
0116
0117 def extract_version_from_spack_yaml(content, spack_name):
0118 """
0119 Extract version from spack.yaml / spack-environment/dev/spack.yaml format:
0120 spack:
0121 specs:
0122 - <spack_name>@<version> ...
0123 """
0124 data = yaml.safe_load(content)
0125 if not data:
0126 return None
0127 specs = data.get("spack", {}).get("specs", [])
0128
0129 pattern = re.compile(r"^" + re.escape(spack_name) + r"@([^\s]+)")
0130 for spec in specs:
0131 m = pattern.match(spec)
0132 if m:
0133 return m.group(1)
0134 return None
0135
0136
0137 def extract_version_from_packages_yaml(content, spack_name):
0138 """
0139 Extract version from packages.yaml format:
0140 packages:
0141 <spack_name>:
0142 require:
0143 - '@<version>'
0144 - ...
0145 """
0146 data = yaml.safe_load(content)
0147 if not data:
0148 return None
0149 pkg = data.get("packages", {}).get(spack_name, {})
0150 require = pkg.get("require", [])
0151 for entry in require:
0152 if isinstance(entry, str):
0153 m = re.match(r"'?@([0-9][^']*)'?", entry)
0154 if m:
0155 return m.group(1)
0156 elif isinstance(entry, dict):
0157 spec = entry.get("spec", "")
0158 m = re.match(r"'?@([0-9][^']*)'?", spec)
0159 if m:
0160 return m.group(1)
0161 return None
0162
0163
0164 def get_package_version(containers_path, tag, spack_name):
0165 """
0166 Try to extract a spack package version from a container tag.
0167 Searches multiple file locations in order.
0168 """
0169
0170 content = git_show(containers_path, tag, "spack-environment/packages.yaml")
0171 if content:
0172 version = extract_version_from_packages_yaml(content, spack_name)
0173 if version:
0174 return version
0175
0176
0177 content = git_show(containers_path, tag, "spack-environment/dev/spack.yaml")
0178 if content:
0179 version = extract_version_from_spack_yaml(content, spack_name)
0180 if version:
0181 return version
0182
0183
0184 for filename in ("spack.yaml", "spack.yml"):
0185 content = git_show(containers_path, tag, filename)
0186 if content:
0187 version = extract_version_from_spack_yaml(content, spack_name)
0188 if version:
0189 return version
0190
0191 return None
0192
0193
0194
0195
0196
0197
0198 def main():
0199 parser = argparse.ArgumentParser(
0200 description="Extract package versions from eic/containers tags."
0201 )
0202 parser.add_argument(
0203 "package",
0204 nargs="?",
0205 default="acts",
0206 help="Spack package name (default: acts). "
0207 f"Preconfigured: {', '.join(sorted(PACKAGES))}. "
0208 "Other names will be looked up with v{{version}} tag convention.",
0209 )
0210 args = parser.parse_args()
0211
0212 spack_name = args.package
0213 pkg_config = PACKAGES.get(spack_name, {
0214 "repo_url": None,
0215 "repo_dir": None,
0216 "version_to_tag": _identity_version_to_tag,
0217 })
0218
0219 repo_url = pkg_config["repo_url"]
0220 repo_dir = pkg_config["repo_dir"]
0221 version_to_tag = pkg_config["version_to_tag"]
0222
0223 if repo_url is None:
0224 print(f"No upstream repository configured for '{spack_name}'.", file=sys.stderr)
0225 print(f"Known packages: {', '.join(sorted(PACKAGES))}", file=sys.stderr)
0226 print("Add an entry to PACKAGES in this script, or specify a known package.", file=sys.stderr)
0227 sys.exit(1)
0228
0229
0230 clone_if_missing(repo_url, repo_dir)
0231 clone_if_missing("https://github.com/eic/containers.git", "containers")
0232
0233
0234 print(f"Fetching {spack_name} tags from {repo_dir}...")
0235 upstream_tags_lines = get_tags_with_dates(repo_dir)
0236 with open("tags", "w") as f:
0237 for line in upstream_tags_lines:
0238 print(line)
0239 f.write(line + "\n")
0240
0241
0242 upstream_tag_lookup = {}
0243 for line in upstream_tags_lines:
0244 tag, date = line.split("|", 1)
0245 upstream_tag_lookup[tag] = line
0246
0247
0248 print("Fetching container tags...")
0249 all_container_lines = get_tags_with_dates("containers")
0250 eic_container_tags = []
0251 for line in all_container_lines:
0252 tag = line.split("|")[0]
0253 if "stable" not in tag:
0254 continue
0255 eic_container_tags.append(line)
0256
0257 with open("eic_container_tags", "w") as f:
0258 for line in eic_container_tags:
0259 print(line)
0260 f.write(line + "\n")
0261
0262
0263 print(f"Extracting {spack_name} versions from container tags...")
0264 results = []
0265 for line in eic_container_tags:
0266 container_tag = line.split("|")[0]
0267 version = get_package_version("containers", container_tag, spack_name)
0268 if version is None:
0269 print(f" {container_tag}: {spack_name} version not found", file=sys.stderr)
0270 continue
0271
0272 upstream_tag = version_to_tag(version)
0273 if upstream_tag in upstream_tag_lookup:
0274 result_line = upstream_tag_lookup[upstream_tag]
0275 results.append(result_line)
0276 print(f" {container_tag} -> {spack_name} {version}: {result_line}")
0277 else:
0278 print(f" {container_tag} -> {spack_name} {version}: "
0279 f"tag {upstream_tag} not found in {repo_dir} repo",
0280 file=sys.stderr)
0281
0282 with open("result", "w") as f:
0283 for line in results:
0284 print(line)
0285 f.write(line + "\n")
0286
0287 print(f"\nDone. {len(results)} results written.")
0288
0289
0290 if __name__ == "__main__":
0291 main()