Source code for craft_platforms._platforms

# This file is part of craft-platforms.
#
# Copyright 2024 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License version 3, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Platform related models."""

import itertools
import typing
from typing import Dict, List, Optional, Sequence, Tuple, Union

import annotated_types
from typing_extensions import Annotated

from craft_platforms import _architectures, _buildinfo, _distro, _errors, _utils

PlatformDict = typing.TypedDict(
    "PlatformDict",
    {
        "build-on": Union[Sequence[str], str],
        "build-for": Union[Annotated[Sequence[str], annotated_types.Len(1)], str],
    },
)
"""The platforms where an artifact is built and where the resulting artifact runs."""


Platforms = Dict[Union[_architectures.DebianArchitecture, str], Optional[PlatformDict]]
"""A mapping of platforms names to ``PlatformDicts``.

A ``PlatformDict`` is not required if the platform name is a supported Debian architecture.
"""


[docs]def get_platforms_build_plan( base: Union[str, _distro.DistroBase], platforms: Platforms, build_base: Optional[str] = None, ) -> Sequence[_buildinfo.BuildInfo]: """Generate the build plan for a platforms-based artefact.""" if isinstance(base, _distro.DistroBase): distro_base = base else: distro_base = _distro.DistroBase.from_str(build_base or base) build_plan: List[_buildinfo.BuildInfo] = [] for platform_name, platform in platforms.items(): if platform is None: # This is a workaround for Python 3.10. # In python 3.12+ we can just check: # `if platform_name not in _architectures.DebianArchitecture` try: architecture = _architectures.DebianArchitecture(platform_name) except ValueError: raise _errors.InvalidPlatformNameError( f"Platform name {platform_name!r} is not a valid Debian architecture. " "Specify a build-on and build-for.", ) from None build_plan.append( _buildinfo.BuildInfo( platform=platform_name, build_on=architecture, build_for=architecture, build_base=distro_base, ), ) else: for build_on, build_for in itertools.product( _utils.vectorize(platform["build-on"]), _utils.vectorize(platform.get("build-for", [platform_name])), ): build_plan.append( _buildinfo.BuildInfo( platform=platform_name, build_on=_architectures.DebianArchitecture(build_on), build_for=( "all" if build_for == "all" else _architectures.DebianArchitecture(build_for) ), build_base=distro_base, ), ) build_for_archs = {info.build_for for info in build_plan} if "all" in build_for_archs: platforms_with_all = { info.platform for info in build_plan if info.build_for == "all" } if len(platforms_with_all) > 1: raise _errors.AllSinglePlatformError(platforms_with_all) if len(build_for_archs) > 1: raise _errors.AllOnlyBuildError(platforms_with_all) return build_plan
def parse_base_and_name(platform_name: str) -> Tuple[Optional[_distro.DistroBase], str]: """Get the platform name and optional base from a platform name. The platform name may have an optional base prefix as '[<base>:]<platform>'. :param platform_name: The name of the platform. :returns: A tuple of the DistroBase and the platform name. :raises ValueError: If the base is invalid. """ if ":" in platform_name: base_str, _, name = platform_name.partition(":") base = _distro.DistroBase.from_str(base_str) else: base = None name = platform_name return base, name