mirror of
				https://github.com/factoriotools/factorio-docker.git
				synced 2025-10-31 17:08:08 +01:00 
			
		
		
		
	refactor: Unify build system for regular and rootless images
- Create build-unified.py that handles both regular and rootless builds - Convert build.py and build-rootless.py to wrapper scripts for backwards compatibility - Update CI workflow to use unified build command - Add BUILD_MIGRATION.md documentation - Eliminate code duplication between build scripts - Support flexible build options: --rootless, --both, --only-stable-latest This maintains all existing functionality while providing a cleaner, more maintainable build system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										12
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/docker-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -20,18 +20,10 @@ jobs: | ||||
|       - name: Set up QEMU | ||||
|         uses: docker/setup-qemu-action@v3 | ||||
|  | ||||
|       - name: build and push | ||||
|       - name: build and push all images | ||||
|         if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }} | ||||
|         env: | ||||
|           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||
|           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||
|         run: | | ||||
|           ./build.py --push-tags --multiarch | ||||
|  | ||||
|       - name: build and push rootless images | ||||
|         if: ${{ env.DOCKER_USERNAME != '' && env.DOCKER_PASSWORD != '' }} | ||||
|         env: | ||||
|           DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} | ||||
|           DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} | ||||
|         run: | | ||||
|           ./build-rootless.py --push-tags --multiarch | ||||
|           ./build-unified.py --push-tags --multiarch --both | ||||
							
								
								
									
										61
									
								
								BUILD_MIGRATION.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								BUILD_MIGRATION.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| # Build System Migration Guide | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| The build system has been unified into a single script (`build-unified.py`) that can build both regular and rootless Docker images. This replaces the separate `build.py` and `build-rootless.py` scripts. | ||||
|  | ||||
| ## New Unified Build Script | ||||
|  | ||||
| ### Usage | ||||
|  | ||||
| ```bash | ||||
| # Build only regular images (default behavior) | ||||
| ./build-unified.py | ||||
|  | ||||
| # Build only rootless images | ||||
| ./build-unified.py --rootless | ||||
|  | ||||
| # Build both regular and rootless images | ||||
| ./build-unified.py --both | ||||
|  | ||||
| # Build with multi-architecture support | ||||
| ./build-unified.py --multiarch --both | ||||
|  | ||||
| # Build and push to Docker Hub | ||||
| ./build-unified.py --push-tags --multiarch --both | ||||
|  | ||||
| # Build only stable/latest versions (useful for rootless) | ||||
| ./build-unified.py --rootless --only-stable-latest --push-tags --multiarch | ||||
| ``` | ||||
|  | ||||
| ### Options | ||||
|  | ||||
| - `--push-tags`: Push images to Docker Hub (requires DOCKER_USERNAME and DOCKER_PASSWORD env vars) | ||||
| - `--multiarch`: Build multi-architecture images (linux/amd64 and linux/arm64) | ||||
| - `--rootless`: Build only rootless images | ||||
| - `--both`: Build both regular and rootless images | ||||
| - `--only-stable-latest`: Build only versions tagged as 'stable' or 'latest' | ||||
|  | ||||
| ## Migration Path | ||||
|  | ||||
| ### For CI/CD | ||||
|  | ||||
| The GitHub Actions workflow has been updated to use: | ||||
| ```yaml | ||||
| ./build-unified.py --push-tags --multiarch --both | ||||
| ``` | ||||
|  | ||||
| ### For Local Development | ||||
|  | ||||
| Replace old commands with new ones: | ||||
|  | ||||
| | Old Command | New Command | | ||||
| |-------------|-------------| | ||||
| | `./build.py` | `./build-unified.py` | | ||||
| | `./build.py --multiarch` | `./build-unified.py --multiarch` | | ||||
| | `./build-rootless.py` | `./build-unified.py --rootless` | | ||||
| | `./build.py && ./build-rootless.py` | `./build-unified.py --both` | | ||||
|  | ||||
| ## Backwards Compatibility | ||||
|  | ||||
| The original `build.py` and `build-rootless.py` scripts are now wrappers that call the unified script with appropriate arguments to maintain backwards compatibility. | ||||
							
								
								
									
										
											BIN
										
									
								
								__pycache__/build-rootless.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/build-rootless.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								__pycache__/build-unified.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								__pycache__/build-unified.cpython-312.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1,128 +1,15 @@ | ||||
| #!/usr/bin/env python3 | ||||
| """ | ||||
| Legacy wrapper script for backwards compatibility. | ||||
| This script now calls build-unified.py to build rootless images. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import json | ||||
| import subprocess | ||||
| import shutil | ||||
| import sys | ||||
| import tempfile | ||||
|  | ||||
| # Convert arguments and pass to unified script with --rootless flag | ||||
| args = ["./build-unified.py", "--rootless", "--only-stable-latest"] | ||||
| args.extend(sys.argv[1:]) | ||||
|  | ||||
| PLATFORMS = [ | ||||
|     "linux/arm64", | ||||
|     "linux/amd64", | ||||
| ] | ||||
|  | ||||
|  | ||||
| def create_builder(build_dir, builder_name, platform): | ||||
|     check_exists_command = ["docker", "buildx", "inspect", builder_name] | ||||
|     if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0: | ||||
|         create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name] | ||||
|         try: | ||||
|             subprocess.run(create_command, cwd=build_dir, check=True) | ||||
|         except subprocess.CalledProcessError: | ||||
|             print("Creating builder failed") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push_multiarch(build_dir, build_args, push): | ||||
|     builder_name = "factoriotools-rootless-multiarch" | ||||
|     platform=",".join(PLATFORMS) | ||||
|     create_builder(build_dir, builder_name, platform) | ||||
|     build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args | ||||
|     if push: | ||||
|         build_command.append("--push") | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Build and push of rootless image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def build_singlearch(build_dir, build_args): | ||||
|     build_command = ["docker", "build"] + build_args | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Build of rootless image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def push_singlearch(tags): | ||||
|     for tag in tags: | ||||
|         try: | ||||
|             subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"], | ||||
|                             check=True) | ||||
|         except subprocess.CalledProcessError: | ||||
|             print("Docker push failed") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push(sha256, version, tags, push, multiarch): | ||||
|     build_dir = tempfile.mktemp() | ||||
|     shutil.copytree("docker", build_dir) | ||||
|     # Use the rootless Dockerfile | ||||
|     build_args = ["-f", "Dockerfile.rootless", "--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] | ||||
|     for tag in tags: | ||||
|         build_args.extend(["-t", f"factoriotools/factorio:{tag}"]) | ||||
|     if multiarch: | ||||
|         build_and_push_multiarch(build_dir, build_args, push) | ||||
|     else: | ||||
|         build_singlearch(build_dir, build_args) | ||||
|         if push: | ||||
|             push_singlearch(tags) | ||||
|  | ||||
|  | ||||
| def login(): | ||||
|     try: | ||||
|         username = os.environ["DOCKER_USERNAME"] | ||||
|         password = os.environ["DOCKER_PASSWORD"] | ||||
|         subprocess.run(["docker", "login", "-u", username, "-p", password], check=True) | ||||
|     except KeyError: | ||||
|         print("Username and password need to be given") | ||||
|         exit(1) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Docker login failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def generate_rootless_tags(original_tags): | ||||
|     """Generate rootless-specific tags from original tags""" | ||||
|     rootless_tags = [] | ||||
|     for tag in original_tags: | ||||
|         # Add -rootless suffix to each tag | ||||
|         rootless_tags.append(f"{tag}-rootless") | ||||
|     return rootless_tags | ||||
|  | ||||
|  | ||||
| def main(push_tags=False, multiarch=False): | ||||
|     with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle: | ||||
|         builddata = json.load(file_handle) | ||||
|  | ||||
|     if push_tags: | ||||
|         login() | ||||
|  | ||||
|     # Build only the latest stable and experimental versions for rootless | ||||
|     versions_to_build = [] | ||||
|      | ||||
|     # Find latest stable and experimental versions | ||||
|     for version, buildinfo in builddata.items(): | ||||
|         if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]: | ||||
|             versions_to_build.append((version, buildinfo)) | ||||
|      | ||||
|     for version, buildinfo in versions_to_build: | ||||
|         sha256 = buildinfo["sha256"] | ||||
|         original_tags = buildinfo["tags"] | ||||
|         rootless_tags = generate_rootless_tags(original_tags) | ||||
|         build_and_push(sha256, version, rootless_tags, push_tags, multiarch) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     push_tags = False | ||||
|     multiarch = False | ||||
|     for arg in sys.argv[1:]: | ||||
|         if arg == "--push-tags": | ||||
|             push_tags = True | ||||
|         elif arg == "--multiarch": | ||||
|             multiarch = True | ||||
|     main(push_tags, multiarch) | ||||
| # Execute the unified build script | ||||
| sys.exit(subprocess.call(args)) | ||||
							
								
								
									
										157
									
								
								build-unified.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										157
									
								
								build-unified.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| #!/usr/bin/env python3 | ||||
|  | ||||
| import os | ||||
| import json | ||||
| import subprocess | ||||
| import shutil | ||||
| import sys | ||||
| import tempfile | ||||
| import argparse | ||||
|  | ||||
|  | ||||
| PLATFORMS = [ | ||||
|     "linux/arm64", | ||||
|     "linux/amd64", | ||||
| ] | ||||
|  | ||||
|  | ||||
| def create_builder(build_dir, builder_name, platform): | ||||
|     check_exists_command = ["docker", "buildx", "inspect", builder_name] | ||||
|     if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0: | ||||
|         create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name] | ||||
|         try: | ||||
|             subprocess.run(create_command, cwd=build_dir, check=True) | ||||
|         except subprocess.CalledProcessError: | ||||
|             print("Creating builder failed") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push_multiarch(build_dir, build_args, push, builder_suffix=""): | ||||
|     builder_name = f"factoriotools{builder_suffix}-multiarch" | ||||
|     platform = ",".join(PLATFORMS) | ||||
|     create_builder(build_dir, builder_name, platform) | ||||
|     build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args | ||||
|     if push: | ||||
|         build_command.append("--push") | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print(f"Build and push of {builder_suffix or 'regular'} image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def build_singlearch(build_dir, build_args, image_type="regular"): | ||||
|     build_command = ["docker", "build"] + build_args | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print(f"Build of {image_type} image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def push_singlearch(tags): | ||||
|     for tag in tags: | ||||
|         try: | ||||
|             subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"], | ||||
|                             check=True) | ||||
|         except subprocess.CalledProcessError: | ||||
|             print("Docker push failed") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push(sha256, version, tags, push, multiarch, dockerfile="Dockerfile", builder_suffix=""): | ||||
|     build_dir = tempfile.mktemp() | ||||
|     shutil.copytree("docker", build_dir) | ||||
|     build_args = ["-f", dockerfile, "--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] | ||||
|     for tag in tags: | ||||
|         build_args.extend(["-t", f"factoriotools/factorio:{tag}"]) | ||||
|      | ||||
|     image_type = "rootless" if "rootless" in dockerfile.lower() else "regular" | ||||
|      | ||||
|     if multiarch: | ||||
|         build_and_push_multiarch(build_dir, build_args, push, builder_suffix) | ||||
|     else: | ||||
|         build_singlearch(build_dir, build_args, image_type) | ||||
|         if push: | ||||
|             push_singlearch(tags) | ||||
|  | ||||
|  | ||||
| def login(): | ||||
|     try: | ||||
|         username = os.environ["DOCKER_USERNAME"] | ||||
|         password = os.environ["DOCKER_PASSWORD"] | ||||
|         subprocess.run(["docker", "login", "-u", username, "-p", password], check=True) | ||||
|     except KeyError: | ||||
|         print("Username and password need to be given") | ||||
|         exit(1) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Docker login failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def generate_rootless_tags(original_tags): | ||||
|     """Generate rootless-specific tags from original tags""" | ||||
|     return [f"{tag}-rootless" for tag in original_tags] | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     parser = argparse.ArgumentParser(description='Build Factorio Docker images') | ||||
|     parser.add_argument('--push-tags', action='store_true', help='Push images to Docker Hub') | ||||
|     parser.add_argument('--multiarch', action='store_true', help='Build multi-architecture images') | ||||
|     parser.add_argument('--rootless', action='store_true', help='Build only rootless images') | ||||
|     parser.add_argument('--both', action='store_true', help='Build both regular and rootless images') | ||||
|     parser.add_argument('--only-stable-latest', action='store_true',  | ||||
|                         help='Build only stable and latest versions (for rootless by default)') | ||||
|      | ||||
|     args = parser.parse_args() | ||||
|      | ||||
|     # Default behavior: build regular images unless specified otherwise | ||||
|     build_regular = not args.rootless or args.both | ||||
|     build_rootless = args.rootless or args.both | ||||
|      | ||||
|     with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle: | ||||
|         builddata = json.load(file_handle) | ||||
|  | ||||
|     if args.push_tags: | ||||
|         login() | ||||
|  | ||||
|     # Filter versions if needed | ||||
|     versions_to_build = [] | ||||
|     for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True): | ||||
|         if args.only_stable_latest or (build_rootless and not build_regular): | ||||
|             # For rootless-only builds, default to stable/latest only | ||||
|             if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]: | ||||
|                 versions_to_build.append((version, buildinfo)) | ||||
|         else: | ||||
|             versions_to_build.append((version, buildinfo)) | ||||
|      | ||||
|     # Build regular images | ||||
|     if build_regular: | ||||
|         print("Building regular images...") | ||||
|         for version, buildinfo in versions_to_build: | ||||
|             sha256 = buildinfo["sha256"] | ||||
|             tags = buildinfo["tags"] | ||||
|             build_and_push(sha256, version, tags, args.push_tags, args.multiarch) | ||||
|      | ||||
|     # Build rootless images | ||||
|     if build_rootless: | ||||
|         print("Building rootless images...") | ||||
|         # For rootless, only build stable and latest unless building both | ||||
|         rootless_versions = [] | ||||
|         if not build_regular or args.only_stable_latest: | ||||
|             for version, buildinfo in builddata.items(): | ||||
|                 if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]: | ||||
|                     rootless_versions.append((version, buildinfo)) | ||||
|         else: | ||||
|             rootless_versions = versions_to_build | ||||
|              | ||||
|         for version, buildinfo in rootless_versions: | ||||
|             sha256 = buildinfo["sha256"] | ||||
|             original_tags = buildinfo["tags"] | ||||
|             rootless_tags = generate_rootless_tags(original_tags) | ||||
|             build_and_push(sha256, version, rootless_tags, args.push_tags, args.multiarch,  | ||||
|                          dockerfile="Dockerfile.rootless", builder_suffix="-rootless") | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     main() | ||||
							
								
								
									
										112
									
								
								build.py
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								build.py
									
									
									
									
									
								
							| @@ -1,109 +1,15 @@ | ||||
| #!/usr/bin/env python3 | ||||
| """ | ||||
| Legacy wrapper script for backwards compatibility. | ||||
| This script now calls build-unified.py to build regular images. | ||||
| """ | ||||
|  | ||||
| import os | ||||
| import json | ||||
| import subprocess | ||||
| import shutil | ||||
| import sys | ||||
| import tempfile | ||||
|  | ||||
| # Convert arguments and pass to unified script | ||||
| args = ["./build-unified.py"] | ||||
| args.extend(sys.argv[1:]) | ||||
|  | ||||
| PLATFORMS = [ | ||||
|     "linux/arm64", | ||||
|     "linux/amd64", | ||||
| ] | ||||
|  | ||||
|  | ||||
| def create_builder(build_dir, builder_name, platform): | ||||
|     check_exists_command = ["docker", "buildx", "inspect", builder_name] | ||||
|     if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0: | ||||
|         create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name] | ||||
|         try: | ||||
|             subprocess.run(create_command, cwd=build_dir, check=True) | ||||
|         except subprocess.CalledProcessError: | ||||
|             print("Creating builder failed") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push_multiarch(build_dir, build_args, push): | ||||
|     builder_name = "factoriotools-multiarch" | ||||
|     platform=",".join(PLATFORMS) | ||||
|     create_builder(build_dir, builder_name, platform) | ||||
|     build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args | ||||
|     if push: | ||||
|         build_command.append("--push") | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Build and push of image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def build_singlearch(build_dir, build_args): | ||||
|     build_command = ["docker", "build"] + build_args | ||||
|     try: | ||||
|         subprocess.run(build_command, cwd=build_dir, check=True) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Build of image failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def push_singlearch(tags): | ||||
|     for tag in tags: | ||||
|         try: | ||||
|             subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"], | ||||
|                             check=True) | ||||
|         except subprocess.CalledProcessError: | ||||
|             print("Docker push failed") | ||||
|             exit(1) | ||||
|  | ||||
|  | ||||
| def build_and_push(sha256, version, tags, push, multiarch): | ||||
|     build_dir = tempfile.mktemp() | ||||
|     shutil.copytree("docker", build_dir) | ||||
|     build_args = ["--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."] | ||||
|     for tag in tags: | ||||
|         build_args.extend(["-t", f"factoriotools/factorio:{tag}"]) | ||||
|     if multiarch: | ||||
|         build_and_push_multiarch(build_dir, build_args, push) | ||||
|     else: | ||||
|         build_singlearch(build_dir, build_args) | ||||
|         if push: | ||||
|             push_singlearch(tags) | ||||
|  | ||||
|  | ||||
| def login(): | ||||
|     try: | ||||
|         username = os.environ["DOCKER_USERNAME"] | ||||
|         password = os.environ["DOCKER_PASSWORD"] | ||||
|         subprocess.run(["docker", "login", "-u", username, "-p", password], check=True) | ||||
|     except KeyError: | ||||
|         print("Username and password need to be given") | ||||
|         exit(1) | ||||
|     except subprocess.CalledProcessError: | ||||
|         print("Docker login failed") | ||||
|         exit(1) | ||||
|  | ||||
|  | ||||
| def main(push_tags=False, multiarch=False): | ||||
|     with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle: | ||||
|         builddata = json.load(file_handle) | ||||
|  | ||||
|     if push_tags: | ||||
|         login() | ||||
|  | ||||
|     for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True): | ||||
|         sha256 = buildinfo["sha256"] | ||||
|         tags = buildinfo["tags"] | ||||
|         build_and_push(sha256, version, tags, push_tags, multiarch) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     push_tags = False | ||||
|     multiarch = False | ||||
|     for arg in sys.argv[1:]: | ||||
|         if arg == "--push-tags": | ||||
|             push_tags = True | ||||
|         elif arg == "--multiarch": | ||||
|             multiarch = True | ||||
|     main(push_tags, multiarch) | ||||
| # Execute the unified build script | ||||
| sys.exit(subprocess.call(args)) | ||||
		Reference in New Issue
	
	Block a user