[CI/Build][Docker] Add centralized version manifest for Docker builds (#31492)
Signed-off-by: Mritunjay Sharma <mritunjay.sharma@chainguard.dev>
This commit is contained in:
committed by
GitHub
parent
2b99f210f5
commit
9e078d0582
139
tools/generate_versions_json.py
Executable file
139
tools/generate_versions_json.py
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Generate docker/versions.json from Dockerfile ARG defaults.
|
||||
|
||||
This script parses the Dockerfile and extracts ARG defaults to create
|
||||
a bake-native versions.json file that can be used directly with:
|
||||
docker buildx bake -f docker/docker-bake.hcl -f docker/versions.json
|
||||
|
||||
Usage:
|
||||
python tools/generate_versions_json.py [--check]
|
||||
|
||||
Options:
|
||||
--check Verify versions.json matches Dockerfile (for CI validation)
|
||||
|
||||
Requirements:
|
||||
pip install dockerfile-parse
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from dockerfile_parse import DockerfileParser
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
DOCKERFILE = REPO_ROOT / "docker" / "Dockerfile"
|
||||
VERSIONS_JSON = REPO_ROOT / "docker" / "versions.json"
|
||||
|
||||
# Map Dockerfile ARG names (lowercase) to bake variable names (uppercase)
|
||||
# This matches docker-bake.hcl variable naming convention
|
||||
BAKE_VAR_NAMES = {
|
||||
"torch_cuda_arch_list": "TORCH_CUDA_ARCH_LIST",
|
||||
"max_jobs": "MAX_JOBS",
|
||||
"nvcc_threads": "NVCC_THREADS",
|
||||
}
|
||||
|
||||
|
||||
def parse_dockerfile_args(dockerfile_path: Path) -> dict[str, str]:
|
||||
"""Extract all ARG defaults from Dockerfile using dockerfile-parse."""
|
||||
parser = DockerfileParser(path=str(dockerfile_path))
|
||||
|
||||
# Extract ARGs from structure (more reliable for multi-stage Dockerfiles)
|
||||
args = {}
|
||||
for item in parser.structure:
|
||||
if item["instruction"] != "ARG":
|
||||
continue
|
||||
|
||||
value = item["value"]
|
||||
if "=" not in value:
|
||||
continue
|
||||
|
||||
# Parse ARG NAME=value (handle quotes)
|
||||
name, _, default = value.partition("=")
|
||||
name = name.strip()
|
||||
|
||||
if name in args:
|
||||
# Keep first occurrence
|
||||
continue
|
||||
|
||||
# Strip surrounding quotes if present
|
||||
default = default.strip()
|
||||
if (default.startswith('"') and default.endswith('"')) or (
|
||||
default.startswith("'") and default.endswith("'")
|
||||
):
|
||||
default = default[1:-1]
|
||||
|
||||
if default:
|
||||
args[name] = default
|
||||
|
||||
# Resolve variable interpolation (e.g., ${CUDA_VERSION} -> 12.9.1)
|
||||
resolved = {}
|
||||
for name, value in args.items():
|
||||
if "${" in value:
|
||||
# Substitute ${VAR} references with their values
|
||||
for ref_name, ref_value in args.items():
|
||||
value = value.replace(f"${{{ref_name}}}", ref_value)
|
||||
# Skip if still has unresolved references (no default available)
|
||||
if "${" not in value:
|
||||
resolved[name] = value
|
||||
|
||||
return resolved
|
||||
|
||||
|
||||
def generate_bake_native_json(args: dict[str, str]) -> dict:
|
||||
"""Generate bake-native JSON structure."""
|
||||
variables = {}
|
||||
for name, value in args.items():
|
||||
# Use uppercase bake variable name if mapped, otherwise keep as-is
|
||||
bake_name = BAKE_VAR_NAMES.get(name, name)
|
||||
variables[bake_name] = {"default": value}
|
||||
|
||||
return {
|
||||
"_comment": (
|
||||
"Auto-generated from Dockerfile ARGs. "
|
||||
"Do not edit manually. Run: python tools/generate_versions_json.py"
|
||||
),
|
||||
"variable": variables,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
check_mode = "--check" in sys.argv
|
||||
|
||||
# Parse Dockerfile
|
||||
args = parse_dockerfile_args(DOCKERFILE)
|
||||
|
||||
# Generate bake-native JSON
|
||||
data = generate_bake_native_json(args)
|
||||
new_content = json.dumps(data, indent=2) + "\n"
|
||||
|
||||
if check_mode:
|
||||
# Verify existing file matches
|
||||
if not VERSIONS_JSON.exists():
|
||||
print(f"ERROR: {VERSIONS_JSON} does not exist")
|
||||
sys.exit(1)
|
||||
|
||||
existing_content = VERSIONS_JSON.read_text()
|
||||
if existing_content != new_content:
|
||||
print("ERROR: docker/versions.json is out of sync with Dockerfile")
|
||||
print("Run: python tools/generate_versions_json.py")
|
||||
sys.exit(1)
|
||||
|
||||
print("✅ docker/versions.json is in sync with Dockerfile")
|
||||
sys.exit(0)
|
||||
|
||||
# Write versions.json
|
||||
VERSIONS_JSON.write_text(new_content)
|
||||
print(f"✅ Generated {VERSIONS_JSON}")
|
||||
|
||||
# Print summary
|
||||
print("\nExtracted versions:")
|
||||
for name, value in args.items():
|
||||
print(f" {name}: {value}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user