main
py 138 lines 4.14 KB
Raw
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: Apache-2.0
3 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
4 """Generate docker/versions.json from Dockerfile ARG defaults.
5
6 This script parses the Dockerfile and extracts ARG defaults to create
7 a bake-native versions.json file that can be used directly with:
8 docker buildx bake -f docker/docker-bake.hcl -f docker/versions.json
9
10 Usage:
11 python tools/generate_versions_json.py [--check]
12
13 Options:
14 --check Verify versions.json matches Dockerfile (for CI validation)
15
16 Requirements:
17 pip install dockerfile-parse
18 """
19
20 import json
21 import sys
22 from pathlib import Path
23
24 from dockerfile_parse import DockerfileParser
25
26 REPO_ROOT = Path(__file__).resolve().parent.parent
27 DOCKERFILE = REPO_ROOT / "docker" / "Dockerfile"
28 VERSIONS_JSON = REPO_ROOT / "docker" / "versions.json"
29
30 # Map Dockerfile ARG names (lowercase) to bake variable names (uppercase)
31 # This matches docker-bake.hcl variable naming convention
32 BAKE_VAR_NAMES = {
33 "torch_cuda_arch_list": "TORCH_CUDA_ARCH_LIST",
34 "max_jobs": "MAX_JOBS",
35 "nvcc_threads": "NVCC_THREADS",
36 }
37
38
39 def parse_dockerfile_args(dockerfile_path: Path) -> dict[str, str]:
40 """Extract all ARG defaults from Dockerfile using dockerfile-parse."""
41 parser = DockerfileParser(path=str(dockerfile_path))
42
43 # Extract ARGs from structure (more reliable for multi-stage Dockerfiles)
44 args = {}
45 for item in parser.structure:
46 if item["instruction"] != "ARG":
47 continue
48
49 value = item["value"]
50 if "=" not in value:
51 continue
52
53 # Parse ARG NAME=value (handle quotes)
54 name, _, default = value.partition("=")
55 name = name.strip()
56
57 if name in args:
58 # Keep first occurrence
59 continue
60
61 # Strip surrounding quotes if present
62 default = default.strip()
63 if (default.startswith('"') and default.endswith('"')) or (
64 default.startswith("'") and default.endswith("'")
65 ):
66 default = default[1:-1]
67
68 if default:
69 args[name] = default
70
71 # Resolve variable interpolation (e.g., ${CUDA_VERSION} -> 12.9.1)
72 resolved = {}
73 for name, value in args.items():
74 if "${" in value:
75 # Substitute ${VAR} references with their values
76 for ref_name, ref_value in args.items():
77 value = value.replace(f"${{{ref_name}}}", ref_value)
78 # Skip if still has unresolved references (no default available)
79 if "${" not in value:
80 resolved[name] = value
81
82 return resolved
83
84
85 def generate_bake_native_json(args: dict[str, str]) -> dict:
86 """Generate bake-native JSON structure."""
87 variables = {}
88 for name, value in args.items():
89 # Use uppercase bake variable name if mapped, otherwise keep as-is
90 bake_name = BAKE_VAR_NAMES.get(name, name)
91 variables[bake_name] = {"default": value}
92
93 return {
94 "_comment": (
95 "Auto-generated from Dockerfile ARGs. "
96 "Do not edit manually. Run: python tools/generate_versions_json.py"
97 ),
98 "variable": variables,
99 }
100
101
102 def main():
103 check_mode = "--check" in sys.argv
104
105 # Parse Dockerfile
106 args = parse_dockerfile_args(DOCKERFILE)
107
108 # Generate bake-native JSON
109 data = generate_bake_native_json(args)
110 new_content = json.dumps(data, indent=2) + "\n"
111
112 if check_mode:
113 # Verify existing file matches
114 if not VERSIONS_JSON.exists():
115 print(f"ERROR: {VERSIONS_JSON} does not exist")
116 sys.exit(1)
117
118 existing_content = VERSIONS_JSON.read_text()
119 if existing_content != new_content:
120 print("ERROR: docker/versions.json is out of sync with Dockerfile")
121 print("Run: python tools/generate_versions_json.py")
122 sys.exit(1)
123
124 print("✅ docker/versions.json is in sync with Dockerfile")
125 sys.exit(0)
126
127 # Write versions.json
128 VERSIONS_JSON.write_text(new_content)
129 print(f"✅ Generated {VERSIONS_JSON}")
130
131 # Print summary
132 print("\nExtracted versions:")
133 for name, value in args.items():
134 print(f" {name}: {value}")
135
136
137 if __name__ == "__main__":
138 main()