From 847de3a9dbe8db9593d81cf20783f65807554110 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:21:18 +0000 Subject: [PATCH 1/7] Initial plan From 79e84f5d5dfe1e323391aa568bc3b9d2ea807411 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:33:27 +0000 Subject: [PATCH 2/7] feat: allow devel in multi-base charm builds Allow `devel` (or `ubuntu@devel`, `opensuse@devel`, etc.) in the `build-on` entries of multi-base platform definitions, even when `build-for` uses a different stable base. For example, this is now valid: platforms: plucky: build-on: [devel:amd64] build-for: [ubuntu@24.04:amd64] Changes: - `_validate_base_definition`: Separate build-on and build-for base collection. Exclude devel-series `build-on` entries from the base-consistency check. - `get_platforms_charm_build_plan` product loop: Use the `build-on` entry's base as `build_base` when present (e.g. `ubuntu@devel` from `devel:amd64`), falling back to the platform-level `distro_base`. - Add four new parameterized test cases covering devel build-on scenarios. - Update hypothesis fuzz test filter to allow valid devel build-on entries. Agent-Logs-Url: https://github.com/canonical/craft-platforms/sessions/598be95c-71f9-48a0-9e52-a1ccc44951b3 Co-authored-by: lengau <4305943+lengau@users.noreply.github.com> --- craft_platforms/charm/_build.py | 23 +++++---- tests/unit/charm/test_build.py | 89 +++++++++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/craft_platforms/charm/_build.py b/craft_platforms/charm/_build.py index 95364ebc..7df6b4c3 100644 --- a/craft_platforms/charm/_build.py +++ b/craft_platforms/charm/_build.py @@ -86,14 +86,18 @@ def _validate_base_definition( # noqa: PLR0912 "the incompatible 'build-for' entry for the platform." ), ) - # create a set of the bases defined in the build-on and build-for entries + # create a set of the bases defined in the build-on and build-for entries. + # devel series bases in build-on are allowed to differ from build-for bases, + # so they are excluded from the consistency check. bases = set() - for entry in [ - *_utils.vectorize(platform.get("build-on", [platform_name])), - *_utils.vectorize(platform.get("build-for", [platform_name])), - ]: + for entry in _utils.vectorize(platform.get("build-for", [platform_name])): distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) bases.add(str(distro_base) if distro_base else None) + for entry in _utils.vectorize(platform.get("build-on", [platform_name])): + distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) + # Allow devel series in build-on to differ from the build-for base. + if distro_base is None or distro_base.series != "devel": + bases.add(str(distro_base) if distro_base else None) if len(bases) == 0: # an empty set means no bases are defined @@ -166,8 +170,9 @@ def _get_base_from_build_data( if platform_base: return platform_base - # build-on and build-for entries all have the same base, so we only - # need to check one of them + # When build-on and build-for entries share a common base, use it. + # If build-on has a devel base and build-for has a stable base, the product + # loop in get_platforms_charm_build_plan will use the per-entry build-on base. if platform: build_for_base, _ = _architectures.parse_base_and_architecture( arch=_utils.vectorize(platform["build-for"])[0] @@ -281,7 +286,7 @@ def get_platforms_charm_build_plan( _utils.vectorize(platform.get("build-on", [platform_name])), _utils.vectorize(platform.get("build-for", [platform_name])), ): - _, build_on_arch = _architectures.parse_base_and_architecture( + build_on_base, build_on_arch = _architectures.parse_base_and_architecture( arch=build_on ) if build_on_arch == "all": @@ -298,7 +303,7 @@ def get_platforms_charm_build_plan( platform=platform_name, build_on=build_on_arch, build_for=build_for_arch, - build_base=distro_base, + build_base=build_on_base if build_on_base is not None else distro_base, ), ) diff --git a/tests/unit/charm/test_build.py b/tests/unit/charm/test_build.py index 5c0b10f3..d9fd01db 100644 --- a/tests/unit/charm/test_build.py +++ b/tests/unit/charm/test_build.py @@ -528,6 +528,88 @@ def test_build_plans_success( ], id="multi-base-long-multi-build-on", ), + pytest.param( + None, + None, + { + "noble": { + "build-on": ["devel:amd64"], + "build-for": ["ubuntu@24.04:amd64"], + }, + }, + [ + craft_platforms.BuildInfo( + "noble", + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DistroBase("ubuntu", "devel"), + ), + ], + id="multi-base-devel-build-on", + ), + pytest.param( + None, + None, + { + "noble": { + "build-on": ["ubuntu@devel:amd64"], + "build-for": ["ubuntu@24.04:amd64"], + }, + }, + [ + craft_platforms.BuildInfo( + "noble", + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DistroBase("ubuntu", "devel"), + ), + ], + id="multi-base-ubuntu-at-devel-build-on", + ), + pytest.param( + None, + None, + { + "noble": { + "build-on": ["devel:amd64", "ubuntu@24.04:arm64"], + "build-for": ["ubuntu@24.04:amd64"], + }, + }, + [ + craft_platforms.BuildInfo( + "noble", + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DistroBase("ubuntu", "devel"), + ), + craft_platforms.BuildInfo( + "noble", + craft_platforms.DebianArchitecture("arm64"), + craft_platforms.DebianArchitecture("amd64"), + craft_platforms.DistroBase("ubuntu", "24.04"), + ), + ], + id="multi-base-devel-and-stable-build-on", + ), + pytest.param( + None, + None, + { + "noble": { + "build-on": ["devel:amd64"], + "build-for": ["ubuntu@24.04:all"], + }, + }, + [ + craft_platforms.BuildInfo( + "noble", + craft_platforms.DebianArchitecture("amd64"), + "all", + craft_platforms.DistroBase("ubuntu", "devel"), + ), + ], + id="multi-base-devel-build-on-all", + ), ], ) def test_build_plans_in_depth(base, build_base, platforms, expected): @@ -774,9 +856,10 @@ def test_fuzz_get_platforms_build_plan_single_base( build_ons=strategies.distro_series_arch_str(strategies.any_distro_base()), build_fors=strategies.distro_series_arch_str(strategies.any_distro_base()), ).filter( - lambda p: ( - {p["build-for"][0].partition(":")[0]} - == {on.partition(":")[0] for on in p["build-on"]} + lambda p: all( + on.partition(":")[0] == p["build-for"][0].partition(":")[0] + or on.partition(":")[0].endswith("@devel") + for on in p["build-on"] ) ), ), From 1101c6a3c6915d5e702fa2b3d4e21ae9e4610543 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:34:51 +0000 Subject: [PATCH 3/7] fix: also handle bare 'devel' in fuzz test filter Agent-Logs-Url: https://github.com/canonical/craft-platforms/sessions/598be95c-71f9-48a0-9e52-a1ccc44951b3 Co-authored-by: lengau <4305943+lengau@users.noreply.github.com> --- tests/unit/charm/test_build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/charm/test_build.py b/tests/unit/charm/test_build.py index d9fd01db..853c1e90 100644 --- a/tests/unit/charm/test_build.py +++ b/tests/unit/charm/test_build.py @@ -858,6 +858,7 @@ def test_fuzz_get_platforms_build_plan_single_base( ).filter( lambda p: all( on.partition(":")[0] == p["build-for"][0].partition(":")[0] + or on.partition(":")[0] == "devel" or on.partition(":")[0].endswith("@devel") for on in p["build-on"] ) From 3f7b26853497492a729ca4a86daffb6adea161ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:55:17 +0000 Subject: [PATCH 4/7] fix: reject mixed devel+stable bases in build-on Mixing a devel-series entry (e.g. `devel:amd64`) with a stable-series entry (e.g. `ubuntu@24.04:arm64`) in the same `build-on` list is now rejected, consistent with the existing rule that two different stable bases cannot coexist in `build-on`. - `_validate_base_definition`: track devel vs. non-devel build-on entries separately and raise `InvalidMultiBaseError` if both are present with explicit bases. - Move `multi-base-devel-and-stable-build-on` from the success parametrize list to the `test_build_plans_bad_base` list. Agent-Logs-Url: https://github.com/canonical/craft-platforms/sessions/1b3802d9-4240-4773-9111-640e9e5c43e0 Co-authored-by: lengau <4305943+lengau@users.noreply.github.com> --- craft_platforms/charm/_build.py | 41 +++++++++++++++++++++++++-------- tests/unit/charm/test_build.py | 38 +++++++++++------------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/craft_platforms/charm/_build.py b/craft_platforms/charm/_build.py index 7df6b4c3..098e246e 100644 --- a/craft_platforms/charm/_build.py +++ b/craft_platforms/charm/_build.py @@ -86,18 +86,41 @@ def _validate_base_definition( # noqa: PLR0912 "the incompatible 'build-for' entry for the platform." ), ) - # create a set of the bases defined in the build-on and build-for entries. - # devel series bases in build-on are allowed to differ from build-for bases, - # so they are excluded from the consistency check. - bases = set() + # Collect build-on entries, separating devel-series from non-devel. + # Devel-series build-on entries are allowed to differ from the build-for base, + # but mixing devel and non-devel (with an explicit base) in build-on is not + # allowed (for the same reason that mixing two different stable bases in + # build-on is not allowed). + has_devel_build_on = False + has_non_devel_base_build_on = False + non_devel_build_on_bases: set[str | None] = set() + for entry in _utils.vectorize(platform.get("build-on", [platform_name])): + distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) + if distro_base is not None and distro_base.series == "devel": + has_devel_build_on = True + else: + non_devel_build_on_bases.add(str(distro_base) if distro_base else None) + if distro_base is not None: + has_non_devel_base_build_on = True + + if has_devel_build_on and has_non_devel_base_build_on: + raise _errors.InvalidMultiBaseError( + message=( + f"Platform {platform_name!r} has mismatched bases in the 'build-on' " + "and 'build-for' entries." + ), + resolution=( + "Use the same base for all 'build-on' and 'build-for' entries for " + "the platform." + ), + ) + + # Combine the non-devel build-on bases with the build-for bases to check + # overall consistency. + bases: set[str | None] = set(non_devel_build_on_bases) for entry in _utils.vectorize(platform.get("build-for", [platform_name])): distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) bases.add(str(distro_base) if distro_base else None) - for entry in _utils.vectorize(platform.get("build-on", [platform_name])): - distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) - # Allow devel series in build-on to differ from the build-for base. - if distro_base is None or distro_base.series != "devel": - bases.add(str(distro_base) if distro_base else None) if len(bases) == 0: # an empty set means no bases are defined diff --git a/tests/unit/charm/test_build.py b/tests/unit/charm/test_build.py index 853c1e90..ba10c0ff 100644 --- a/tests/unit/charm/test_build.py +++ b/tests/unit/charm/test_build.py @@ -566,31 +566,6 @@ def test_build_plans_success( ], id="multi-base-ubuntu-at-devel-build-on", ), - pytest.param( - None, - None, - { - "noble": { - "build-on": ["devel:amd64", "ubuntu@24.04:arm64"], - "build-for": ["ubuntu@24.04:amd64"], - }, - }, - [ - craft_platforms.BuildInfo( - "noble", - craft_platforms.DebianArchitecture("amd64"), - craft_platforms.DebianArchitecture("amd64"), - craft_platforms.DistroBase("ubuntu", "devel"), - ), - craft_platforms.BuildInfo( - "noble", - craft_platforms.DebianArchitecture("arm64"), - craft_platforms.DebianArchitecture("amd64"), - craft_platforms.DistroBase("ubuntu", "24.04"), - ), - ], - id="multi-base-devel-and-stable-build-on", - ), pytest.param( None, None, @@ -769,6 +744,19 @@ def test_build_plans_in_depth(base, build_base, platforms, expected): "Use the same base for all 'build-on' and 'build-for' entries for the platform.", id="platform-base-with-incompatible-build-on", ), + pytest.param( + None, + None, + { + "noble": { + "build-on": ["devel:amd64", "ubuntu@24.04:arm64"], + "build-for": ["ubuntu@24.04:amd64"], + }, + }, + r"Platform 'noble' has mismatched bases in the 'build-on' and 'build-for' entries.", + "Use the same base for all 'build-on' and 'build-for' entries for the platform.", + id="devel-and-stable-mixed-build-on", + ), ], ) def test_build_plans_bad_base(base, build_base, platforms, error_msg, error_res): From 76796f1c90e750cbfdc95ca589ab0c3e257dd566 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:41:05 +0000 Subject: [PATCH 5/7] fix: correct Hypothesis filter for multi-base fuzz test The filter lambda had three issues: 1. `p["build-on"]` could be a scalar string from `platform_dict()`; iterating over it yielded characters, not build-on entries. 2. `p["build-for"][0]` gave the first *character* when `build-for` was a scalar string, not the first entry. 3. The filter allowed mixed devel + non-devel-with-explicit-base in `build-on`, but `_validate_base_definition` now rejects that. Replace the lambda with a `_is_valid_multi_base_platform_dict` helper that correctly vectorises both fields and enforces the same rules as the validator: either all build-on entries match the build-for base, or they are devel-series only (no mixing devel with explicit stable bases). Agent-Logs-Url: https://github.com/canonical/craft-platforms/sessions/53907464-0c84-44e1-9a54-c9720be85811 Co-authored-by: lengau <4305943+lengau@users.noreply.github.com> --- tests/unit/charm/test_build.py | 46 ++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/tests/unit/charm/test_build.py b/tests/unit/charm/test_build.py index ba10c0ff..73c47955 100644 --- a/tests/unit/charm/test_build.py +++ b/tests/unit/charm/test_build.py @@ -810,6 +810,43 @@ def _is_valid_platform(platforms): return True +def _is_valid_multi_base_platform_dict(p): + """Return True if the platform dict is consistent for multi-base builds. + + A valid multi-base platform dict must satisfy: + - All ``build-on`` entries either share the same base as ``build-for``, + or are devel-series entries (exactly ``"devel"`` or ``"*@devel"``). + - ``build-on`` must not mix devel-series entries with entries that carry + an explicit non-devel base, for the same reason that two different + stable bases in ``build-on`` are rejected. + """ + build_ons = p["build-on"] if isinstance(p["build-on"], list) else [p["build-on"]] + build_fors = p["build-for"] if isinstance(p["build-for"], list) else [p["build-for"]] + build_for_base = build_fors[0].partition(":")[0] + + devel_build_ons = [ + on + for on in build_ons + if on.partition(":")[0] == "devel" or on.partition(":")[0].endswith("@devel") + ] + # build-on entries that carry an explicit distro@series that is not devel + non_devel_with_base_build_ons = [ + on + for on in build_ons + if "@" in on.partition(":")[0] + and not on.partition(":")[0].endswith("@devel") + ] + + # Mixing devel and explicit non-devel bases in build-on is not allowed. + if devel_build_ons and non_devel_with_base_build_ons: + return False + + # All non-devel entries that carry an explicit base must match build-for. + return all( + on.partition(":")[0] == build_for_base for on in non_devel_with_base_build_ons + ) + + @given( base=strategies.real_distro_base(), platforms=strategies.platform( @@ -843,14 +880,7 @@ def test_fuzz_get_platforms_build_plan_single_base( values=strategies.platform_dict( build_ons=strategies.distro_series_arch_str(strategies.any_distro_base()), build_fors=strategies.distro_series_arch_str(strategies.any_distro_base()), - ).filter( - lambda p: all( - on.partition(":")[0] == p["build-for"][0].partition(":")[0] - or on.partition(":")[0] == "devel" - or on.partition(":")[0].endswith("@devel") - for on in p["build-on"] - ) - ), + ).filter(_is_valid_multi_base_platform_dict), ), ) def test_fuzz_get_platforms_build_plan_multi_base( From 542fbb642a4e84cc739bd588466ff40e0ba953f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:09:40 +0000 Subject: [PATCH 6/7] fix: resolve linting issues in _build.py and test_build.py - Use `Set[Optional[str]]` instead of `set[str | None]` for Python 3.8 compatibility (fixes ty and mypy errors on lines 96 and 120) - Add `Set` to the `typing` imports - Reformat `parse_base_and_architecture` assignment and ternary `build_base=` expression in _build.py to satisfy ruff format - Reformat `build_fors = (...)` assignment and list comprehension condition in test_build.py to satisfy ruff format Agent-Logs-Url: https://github.com/canonical/craft-platforms/sessions/8b45bd49-6831-4ba9-b5ed-f7d45cb14d3f Co-authored-by: lengau <4305943+lengau@users.noreply.github.com> --- craft_platforms/charm/_build.py | 14 ++++++++------ tests/unit/charm/test_build.py | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/craft_platforms/charm/_build.py b/craft_platforms/charm/_build.py index 098e246e..ac11a6d3 100644 --- a/craft_platforms/charm/_build.py +++ b/craft_platforms/charm/_build.py @@ -16,7 +16,7 @@ """Charmcraft-specific platforms information.""" import itertools -from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence +from typing import Any, Collection, Dict, Iterable, List, Optional, Sequence, Set from craft_platforms import ( _architectures, @@ -93,7 +93,7 @@ def _validate_base_definition( # noqa: PLR0912 # build-on is not allowed). has_devel_build_on = False has_non_devel_base_build_on = False - non_devel_build_on_bases: set[str | None] = set() + non_devel_build_on_bases: Set[Optional[str]] = set() for entry in _utils.vectorize(platform.get("build-on", [platform_name])): distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) if distro_base is not None and distro_base.series == "devel": @@ -117,7 +117,7 @@ def _validate_base_definition( # noqa: PLR0912 # Combine the non-devel build-on bases with the build-for bases to check # overall consistency. - bases: set[str | None] = set(non_devel_build_on_bases) + bases: Set[Optional[str]] = set(non_devel_build_on_bases) for entry in _utils.vectorize(platform.get("build-for", [platform_name])): distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) bases.add(str(distro_base) if distro_base else None) @@ -309,8 +309,8 @@ def get_platforms_charm_build_plan( _utils.vectorize(platform.get("build-on", [platform_name])), _utils.vectorize(platform.get("build-for", [platform_name])), ): - build_on_base, build_on_arch = _architectures.parse_base_and_architecture( - arch=build_on + build_on_base, build_on_arch = ( + _architectures.parse_base_and_architecture(arch=build_on) ) if build_on_arch == "all": raise ValueError( @@ -326,7 +326,9 @@ def get_platforms_charm_build_plan( platform=platform_name, build_on=build_on_arch, build_for=build_for_arch, - build_base=build_on_base if build_on_base is not None else distro_base, + build_base=build_on_base + if build_on_base is not None + else distro_base, ), ) diff --git a/tests/unit/charm/test_build.py b/tests/unit/charm/test_build.py index 73c47955..a5d5eff0 100644 --- a/tests/unit/charm/test_build.py +++ b/tests/unit/charm/test_build.py @@ -821,7 +821,9 @@ def _is_valid_multi_base_platform_dict(p): stable bases in ``build-on`` are rejected. """ build_ons = p["build-on"] if isinstance(p["build-on"], list) else [p["build-on"]] - build_fors = p["build-for"] if isinstance(p["build-for"], list) else [p["build-for"]] + build_fors = ( + p["build-for"] if isinstance(p["build-for"], list) else [p["build-for"]] + ) build_for_base = build_fors[0].partition(":")[0] devel_build_ons = [ @@ -833,8 +835,7 @@ def _is_valid_multi_base_platform_dict(p): non_devel_with_base_build_ons = [ on for on in build_ons - if "@" in on.partition(":")[0] - and not on.partition(":")[0].endswith("@devel") + if "@" in on.partition(":")[0] and not on.partition(":")[0].endswith("@devel") ] # Mixing devel and explicit non-devel bases in build-on is not allowed. From f5d20831344de3676fe2c1a1d2ac0723fb503893 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 05:36:27 +0000 Subject: [PATCH 7/7] fix: reject devel+base-less mixing in build-on entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the devel/non-devel mixing check in `_validate_base_definition` to also reject base-less `build-on` entries (which inherit a stable top-level base) when devel entries are present. Previously only entries with an explicit non-devel base were caught; now any non-devel entry — including bare architecture strings like "amd64" — triggers `InvalidMultiBaseError` when mixed with a devel entry. - Remove `has_non_devel_base_build_on`; use `non_devel_build_on_bases` (which already records `None` for base-less entries) as the guard. - Update `_is_valid_multi_base_platform_dict` fuzz-test filter to match the same rule: reject whenever devel and non-devel entries coexist in build-on. - Add `devel-and-base-less-mixed-build-on` test case covering the scenario described by the reviewer. Agent-Logs-Url: https://github.com/canonical/craft-platforms/sessions/7f809be5-d846-4c1f-9208-2a38e09e4062 Co-authored-by: lengau <4305943+lengau@users.noreply.github.com> --- craft_platforms/charm/_build.py | 11 ++++------ tests/unit/charm/test_build.py | 36 ++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/craft_platforms/charm/_build.py b/craft_platforms/charm/_build.py index ac11a6d3..f17ef942 100644 --- a/craft_platforms/charm/_build.py +++ b/craft_platforms/charm/_build.py @@ -88,11 +88,10 @@ def _validate_base_definition( # noqa: PLR0912 ) # Collect build-on entries, separating devel-series from non-devel. # Devel-series build-on entries are allowed to differ from the build-for base, - # but mixing devel and non-devel (with an explicit base) in build-on is not - # allowed (for the same reason that mixing two different stable bases in - # build-on is not allowed). + # but mixing devel with any non-devel entries (even base-less ones that inherit + # a stable top-level base) in build-on is not allowed, for the same reason + # that mixing two different stable bases in build-on is not allowed. has_devel_build_on = False - has_non_devel_base_build_on = False non_devel_build_on_bases: Set[Optional[str]] = set() for entry in _utils.vectorize(platform.get("build-on", [platform_name])): distro_base, _ = _architectures.parse_base_and_architecture(arch=entry) @@ -100,10 +99,8 @@ def _validate_base_definition( # noqa: PLR0912 has_devel_build_on = True else: non_devel_build_on_bases.add(str(distro_base) if distro_base else None) - if distro_base is not None: - has_non_devel_base_build_on = True - if has_devel_build_on and has_non_devel_base_build_on: + if has_devel_build_on and non_devel_build_on_bases: raise _errors.InvalidMultiBaseError( message=( f"Platform {platform_name!r} has mismatched bases in the 'build-on' " diff --git a/tests/unit/charm/test_build.py b/tests/unit/charm/test_build.py index a5d5eff0..3fe172d0 100644 --- a/tests/unit/charm/test_build.py +++ b/tests/unit/charm/test_build.py @@ -757,6 +757,19 @@ def test_build_plans_in_depth(base, build_base, platforms, expected): "Use the same base for all 'build-on' and 'build-for' entries for the platform.", id="devel-and-stable-mixed-build-on", ), + pytest.param( + "ubuntu@24.04", + None, + { + "my-platform": { + "build-on": ["amd64", "devel:arm64"], + "build-for": ["amd64"], + }, + }, + r"Platform 'my-platform' has mismatched bases in the 'build-on' and 'build-for' entries.", + "Use the same base for all 'build-on' and 'build-for' entries for the platform.", + id="devel-and-base-less-mixed-build-on", + ), ], ) def test_build_plans_bad_base(base, build_base, platforms, error_msg, error_res): @@ -816,9 +829,10 @@ def _is_valid_multi_base_platform_dict(p): A valid multi-base platform dict must satisfy: - All ``build-on`` entries either share the same base as ``build-for``, or are devel-series entries (exactly ``"devel"`` or ``"*@devel"``). - - ``build-on`` must not mix devel-series entries with entries that carry - an explicit non-devel base, for the same reason that two different - stable bases in ``build-on`` are rejected. + - ``build-on`` must not mix devel-series entries with any non-devel entries + (including base-less entries that would inherit a stable top-level base), + for the same reason that two different stable bases in ``build-on`` are + rejected. """ build_ons = p["build-on"] if isinstance(p["build-on"], list) else [p["build-on"]] build_fors = ( @@ -831,20 +845,18 @@ def _is_valid_multi_base_platform_dict(p): for on in build_ons if on.partition(":")[0] == "devel" or on.partition(":")[0].endswith("@devel") ] - # build-on entries that carry an explicit distro@series that is not devel - non_devel_with_base_build_ons = [ - on - for on in build_ons - if "@" in on.partition(":")[0] and not on.partition(":")[0].endswith("@devel") - ] + # build-on entries that are not devel-series (includes base-less entries) + non_devel_build_ons = [on for on in build_ons if on not in devel_build_ons] - # Mixing devel and explicit non-devel bases in build-on is not allowed. - if devel_build_ons and non_devel_with_base_build_ons: + # Mixing devel with any non-devel entries in build-on is not allowed. + if devel_build_ons and non_devel_build_ons: return False # All non-devel entries that carry an explicit base must match build-for. return all( - on.partition(":")[0] == build_for_base for on in non_devel_with_base_build_ons + on.partition(":")[0] == build_for_base + for on in non_devel_build_ons + if "@" in on.partition(":")[0] )