# Path: tasks/packaging/model.py
# class PackageRecipe:
#     """
#     Data structure for holding information about a recipe for building
#     distribution packages.
#     """
# 
#     # Whether this recipe is enabled.
#     enabled: bool
# 
#     # Architecture, e.g. amd64, arm64v8, arm32v7.
#     architecture: str
# 
#     # List of operating system distributions, e.g. stretch, buster.
#     distributions: List[str]
# 
#     # List of package flavors, e.g. minimal, standard, full.
#     flavors: List[str]
# 
# class PackageSpecification:
#     """
#     Data structure for holding information about individual distribution
#     packages.
#     """
# 
#     # Package flavor, e.g. minimal, standard, full.
#     flavor: str
# 
#     # Package version.
#     version: str
# 
#     # Distribution name, e.g. stretch, buster.
#     distribution: str
# 
#     # Architecture, e.g. amd64, arm64v8, arm32v7.
#     architecture: str
# 
#     # Computed package name, taking `flavor` into account.
#     name: str = None
# 
#     # List of Python `extra` labels, taking `flavor` into account.
#     features: str = None
# 
#     # Map architecture labels to Debian distribution package architecture name.
#     debian_architecture_map: ClassVar = {
#         "arm64v8": "arm64",
#         "arm32v7": "armhf",
#     }
# 
#     def __post_init__(self):
#         self.resolve()
#         self.validate()
# 
#     def validate(self):
#         """
#         Sanity checks. All designated specification fields must be set and not
#         be empty.
#         """
#         for field in dataclasses.fields(self):
#             value = getattr(self, field.name)
#             if value is None:
#                 raise ValueError(
#                     f'Package specification field "{field.name}" is required.'
#                 )
# 
#     def resolve(self):
#         """
#         Resolve the designated package flavor to a list of Python package
#         `extra` labels.
#         """
#         if self.flavor == "minimal":
#             self.name = "kotori-minimal"
#             self.features = "daq"
#         elif self.flavor == "standard":
#             self.name = "kotori-standard"
#             self.features = "daq,daq_geospatial,export"
#         elif self.flavor == "full":
#             self.name = "kotori"
#             self.features = "daq,daq_geospatial,export,plotting,firmware,scientific"
#         else:
#             raise ValueError("Unknown package flavor")
# 
#     @property
#     def debian_architecture(self):
#         """
#         Resolve the architecture label to a Debian package architecture suffix.
#         """
#         return self.debian_architecture_map.get(self.architecture, self.architecture)
# 
#     def deb_name(self):
#         """
#         Compute the full name of the Debian `.deb` package.
#         """
#         return f"{self.name}_{self.version}-1~{self.distribution}_{self.debian_architecture}.deb"
# 
# Path: tasks/util.py
# def print_header(label, char="-"):
#     length = max(len(label), 42)
#     print(char * length)
#     print(label.center(length))
#     print(char * length)
# 
# def run_commands(commands):
#     if isinstance(commands, str):
#         commands = [commands]
#     for command in commands:
#         command = command.strip()
#         print(command)
#         exitcode = os.system(command)
#         if exitcode != 0:
#             raise SystemExit("Command failed")
# 
# def task(f: F) -> F:
#     f.__annotations__ = {}
#     return invoke.task(f)


import dataclasses
import json
import os
from pathlib import Path
from invoke import Context
from tasks.packaging.model import PackageRecipe, PackageSpecification
from tasks.util import print_header, run_commands, task

    def deb(self, spec: PackageSpecification):

        # Compute package name and path to `.deb` file.
        package_name = spec.deb_name()
        package_file = Path("dist") / package_name

        # Sanity checks.
        if package_file.exists():
            print(f"Package {package_file} already exists, skipping.")
            return

        print_header(
            f"Building package {spec.name} for {spec.distribution} on {spec.architecture}"
        )
        print(json.dumps(dataclasses.asdict(spec), indent=4))

        # Build Linux distribution package within Docker container.
        os.environ["DOCKER_BUILDKIT"] = "0"
        command = f"""
            docker build \
                --tag {self.build_container_name(spec)} \
                --build-arg BASE_IMAGE=ephemeral/{spec.distribution}-{spec.architecture}-baseline:latest \
                --build-arg NAME={spec.name} \
                --build-arg FEATURES={spec.features} \
                --build-arg VERSION={spec.version} \
                --build-arg DISTRIBUTION={spec.distribution} \
                --build-arg ARCHITECTURE={spec.architecture} \
                --file packaging/dockerfiles/debian-package.dockerfile .
        """

# Path: tasks/packaging/model.py
# class PackageRecipe:
#     """
#     Data structure for holding information about a recipe for building
#     distribution packages.
#     """
# 
#     # Whether this recipe is enabled.
#     enabled: bool
# 
#     # Architecture, e.g. amd64, arm64v8, arm32v7.
#     architecture: str
# 
#     # List of operating system distributions, e.g. stretch, buster.
#     distributions: List[str]
# 
#     # List of package flavors, e.g. minimal, standard, full.
#     flavors: List[str]
# 
# class PackageSpecification:
#     """
#     Data structure for holding information about individual distribution
#     packages.
#     """
# 
#     # Package flavor, e.g. minimal, standard, full.
#     flavor: str
# 
#     # Package version.
#     version: str
# 
#     # Distribution name, e.g. stretch, buster.
#     distribution: str
# 
#     # Architecture, e.g. amd64, arm64v8, arm32v7.
#     architecture: str
# 
#     # Computed package name, taking `flavor` into account.
#     name: str = None
# 
#     # List of Python `extra` labels, taking `flavor` into account.
#     features: str = None
# 
#     # Map architecture labels to Debian distribution package architecture name.
#     debian_architecture_map: ClassVar = {
#         "arm64v8": "arm64",
#         "arm32v7": "armhf",
#     }
# 
#     def __post_init__(self):
#         self.resolve()
#         self.validate()
# 
#     def validate(self):
#         """
#         Sanity checks. All designated specification fields must be set and not
#         be empty.
#         """
#         for field in dataclasses.fields(self):
#             value = getattr(self, field.name)
#             if value is None:
#                 raise ValueError(
#                     f'Package specification field "{field.name}" is required.'
#                 )
# 
#     def resolve(self):
#         """
#         Resolve the designated package flavor to a list of Python package
#         `extra` labels.
#         """
#         if self.flavor == "minimal":
#             self.name = "kotori-minimal"
#             self.features = "daq"
#         elif self.flavor == "standard":
#             self.name = "kotori-standard"
#             self.features = "daq,daq_geospatial,export"
#         elif self.flavor == "full":
#             self.name = "kotori"
#             self.features = "daq,daq_geospatial,export,plotting,firmware,scientific"
#         else:
#             raise ValueError("Unknown package flavor")
# 
#     @property
#     def debian_architecture(self):
#         """
#         Resolve the architecture label to a Debian package architecture suffix.
#         """
#         return self.debian_architecture_map.get(self.architecture, self.architecture)
# 
#     def deb_name(self):
#         """
#         Compute the full name of the Debian `.deb` package.
#         """
#         return f"{self.name}_{self.version}-1~{self.distribution}_{self.debian_architecture}.deb"
# 
# Path: tasks/util.py
# def print_header(label, char="-"):
#     length = max(len(label), 42)
#     print(char * length)
#     print(label.center(length))
#     print(char * length)
# 
# def run_commands(commands):
#     if isinstance(commands, str):
#         commands = [commands]
#     for command in commands:
#         command = command.strip()
#         print(command)
#         exitcode = os.system(command)
#         if exitcode != 0:
#             raise SystemExit("Command failed")
# 
# def task(f: F) -> F:
#     f.__annotations__ = {}
#     return invoke.task(f)


# Path: tasks/packaging/ospackage.py
import dataclasses
import json
import os
from pathlib import Path
from invoke import Context
from tasks.packaging.model import PackageRecipe, PackageSpecification
from tasks.util import print_header, run_commands, task


    def deb(self, spec: PackageSpecification):

        # Compute package name and path to `.deb` file.
        package_name = spec.deb_name()
        package_file = Path("dist") / package_name

        # Sanity checks.
        if package_file.exists():
            print(f"Package {package_file} already exists, skipping.")
            return

        print_header(
            f"Building package {spec.name} for {spec.distribution} on {spec.architecture}"
        )
        print(json.dumps(dataclasses.asdict(spec), indent=4))

        # Build Linux distribution package within Docker container.
        os.environ["DOCKER_BUILDKIT"] = "0"
        command = f"""
            docker build \
                --tag {self.build_container_name(spec)} \
                --build-arg BASE_IMAGE=ephemeral/{spec.distribution}-{spec.architecture}-baseline:latest \
                --build-arg NAME={spec.name} \
                --build-arg FEATURES={spec.features} \
                --build-arg VERSION={spec.version} \
                --build-arg DISTRIBUTION={spec.distribution} \
                --build-arg ARCHITECTURE={spec.architecture} \
                --file packaging/dockerfiles/debian-package.dockerfile .
        """

        run_commands(command)