mirror of
https://github.com/esphome/esphome.git
synced 2024-11-03 08:50:30 +01:00
178 lines
5.7 KiB
Python
Executable File
178 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from dataclasses import dataclass
|
|
import subprocess
|
|
import argparse
|
|
import platform
|
|
import shlex
|
|
import re
|
|
import sys
|
|
|
|
|
|
CHANNEL_DEV = 'dev'
|
|
CHANNEL_BETA = 'beta'
|
|
CHANNEL_RELEASE = 'release'
|
|
CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE]
|
|
|
|
ARCH_AMD64 = 'amd64'
|
|
ARCH_ARMV7 = 'armv7'
|
|
ARCH_AARCH64 = 'aarch64'
|
|
ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64]
|
|
|
|
TYPE_DOCKER = 'docker'
|
|
TYPE_HA_ADDON = 'ha-addon'
|
|
TYPE_LINT = 'lint'
|
|
TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT]
|
|
|
|
|
|
BASE_VERSION = "3.6.0"
|
|
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag")
|
|
parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for")
|
|
parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run")
|
|
parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them")
|
|
subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True)
|
|
build_parser = subparsers.add_parser("build", help="Build the image")
|
|
push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub")
|
|
manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images")
|
|
|
|
|
|
|
|
# only lists some possibilities, doesn't have to be perfect
|
|
# https://stackoverflow.com/a/45125525
|
|
UNAME_TO_ARCH = {
|
|
"x86_64": ARCH_AMD64,
|
|
"aarch64": ARCH_AARCH64,
|
|
"aarch64_be": ARCH_AARCH64,
|
|
"arm": ARCH_ARMV7,
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DockerParams:
|
|
build_from: str
|
|
build_to: str
|
|
manifest_to: str
|
|
dockerfile: str
|
|
|
|
@classmethod
|
|
def for_type_arch(cls, build_type, arch):
|
|
prefix = {
|
|
TYPE_DOCKER: "esphome/esphome",
|
|
TYPE_HA_ADDON: "esphome/esphome-hassio",
|
|
TYPE_LINT: "esphome/esphome-lint"
|
|
}[build_type]
|
|
build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}"
|
|
build_to = f"{prefix}-{arch}"
|
|
dockerfile = {
|
|
TYPE_DOCKER: "docker/Dockerfile",
|
|
TYPE_HA_ADDON: "docker/Dockerfile.hassio",
|
|
TYPE_LINT: "docker/Dockerfile.lint",
|
|
}[build_type]
|
|
return cls(
|
|
build_from=build_from,
|
|
build_to=build_to,
|
|
manifest_to=prefix,
|
|
dockerfile=dockerfile
|
|
)
|
|
|
|
|
|
def main():
|
|
args = parser.parse_args()
|
|
|
|
def run_command(*cmd, ignore_error: bool = False):
|
|
print(f"$ {shlex.join(list(cmd))}")
|
|
if not args.dry_run:
|
|
rc = subprocess.call(list(cmd))
|
|
if rc != 0 and not ignore_error:
|
|
print("Command failed")
|
|
sys.exit(1)
|
|
|
|
# detect channel from tag
|
|
match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag)
|
|
if match is None:
|
|
channel = CHANNEL_DEV
|
|
elif match.group(1) is None:
|
|
channel = CHANNEL_RELEASE
|
|
else:
|
|
channel = CHANNEL_BETA
|
|
|
|
tags_to_push = [args.tag]
|
|
if channel == CHANNEL_DEV:
|
|
tags_to_push.append("dev")
|
|
elif channel == CHANNEL_BETA:
|
|
tags_to_push.append("beta")
|
|
elif channel == CHANNEL_RELEASE:
|
|
# Additionally push to beta
|
|
tags_to_push.append("beta")
|
|
tags_to_push.append("latest")
|
|
|
|
if args.command == "build":
|
|
# 1. pull cache image
|
|
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
|
cache_tag = {
|
|
CHANNEL_DEV: "dev",
|
|
CHANNEL_BETA: "beta",
|
|
CHANNEL_RELEASE: "latest",
|
|
}[channel]
|
|
cache_img = f"ghcr.io/{params.build_to}:{cache_tag}"
|
|
run_command("docker", "pull", cache_img, ignore_error=True)
|
|
|
|
# 2. register QEMU binfmt (if not host arch)
|
|
is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch
|
|
if not is_native:
|
|
run_command(
|
|
"docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2",
|
|
"--reset", "-p", "yes"
|
|
)
|
|
|
|
# 3. build
|
|
run_command(
|
|
"docker", "build",
|
|
"--build-arg", f"BUILD_FROM={params.build_from}",
|
|
"--build-arg", f"BUILD_VERSION={args.tag}",
|
|
"--tag", f"{params.build_to}:{args.tag}",
|
|
"--cache-from", cache_img,
|
|
"--file", params.dockerfile,
|
|
"."
|
|
)
|
|
elif args.command == "push":
|
|
params = DockerParams.for_type_arch(args.build_type, args.arch)
|
|
imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push]
|
|
imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push]
|
|
src = imgs[0]
|
|
# 1. tag images
|
|
for img in imgs[1:]:
|
|
run_command(
|
|
"docker", "tag", src, img
|
|
)
|
|
# 2. push images
|
|
for img in imgs:
|
|
run_command(
|
|
"docker", "push", img
|
|
)
|
|
elif args.command == "manifest":
|
|
manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to
|
|
|
|
targets = [f"{manifest}:{tag}" for tag in tags_to_push]
|
|
targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push]
|
|
# 1. Create manifests
|
|
for target in targets:
|
|
cmd = ["docker", "manifest", "create", target]
|
|
for arch in ARCHS:
|
|
src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}"
|
|
if target.startswith("ghcr.io"):
|
|
src = f"ghcr.io/{src}"
|
|
cmd.append(src)
|
|
run_command(*cmd)
|
|
# 2. Push manifests
|
|
for target in targets:
|
|
run_command(
|
|
"docker", "manifest", "push", target
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|