Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-13 08:55:22

0001 #!/usr/bin/env python3
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 # Package configurations
0032 #
0033 # Each entry maps a spack package name to:
0034 #   repo_url:           upstream git repository
0035 #   repo_dir:           local clone directory name
0036 #   version_to_tag:     convert spack version string to upstream git tag
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 # Git helpers
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 # Version extraction from container files
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     # Match exact package name followed by @version (avoid e.g. acts-dd4hep matching dd4hep)
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     # Try packages.yaml first (newer format, ~v24.03+)
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     # Try dev/spack.yaml (middle era, ~v23.05–v24.02)
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     # Try spack.yaml or spack.yml at root (earliest tags, ~v23.03)
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 # Main
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     # Clone repos
0230     clone_if_missing(repo_url, repo_dir)
0231     clone_if_missing("https://github.com/eic/containers.git", "containers")
0232 
0233     # Get all upstream tags with dates
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     # Build lookup: tag_name -> "tag|date" line
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     # Get container tags (all stable tags)
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     # For each container tag, extract the package version and look up its upstream tag date
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()