From 77d3454d5a8e79cce8d543cd31cbf0a7b32b0e42 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 18 May 2023 20:01:46 -0500 Subject: [PATCH] meta: use build-for in snap.yaml architecture (#4150) Signed-off-by: Callahan Kovacs --- snapcraft/elf/elf_utils.py | 16 +++-- snapcraft/meta/snap_yaml.py | 15 ++++- snapcraft/parts/lifecycle.py | 9 +-- snapcraft/projects.py | 19 +++++- snapcraft/utils.py | 36 +++++++++-- tests/unit/elf/test_elf_utils.py | 21 +++++- tests/unit/linters/test_classic_linter.py | 16 +---- tests/unit/linters/test_library_linter.py | 35 ++-------- tests/unit/linters/test_linters.py | 11 +--- tests/unit/meta/test_snap_yaml.py | 78 +++++++++++++++-------- tests/unit/parts/test_lifecycle.py | 50 +++++++++++++++ tests/unit/test_projects.py | 26 ++++++++ tests/unit/test_utils.py | 22 ++++++- 13 files changed, 249 insertions(+), 105 deletions(-) diff --git a/snapcraft/elf/elf_utils.py b/snapcraft/elf/elf_utils.py index eae17a0314..2de36d2920 100644 --- a/snapcraft/elf/elf_utils.py +++ b/snapcraft/elf/elf_utils.py @@ -21,7 +21,7 @@ import platform from dataclasses import dataclass from pathlib import Path -from typing import Iterable, List, Set +from typing import Iterable, List, Optional, Set from craft_cli import emit from elftools.common.exceptions import ELFError @@ -127,9 +127,17 @@ def get_dynamic_linker(*, root_path: Path, snap_path: Path) -> str: return str(snap_path / arch_config.dynamic_linker) -def get_arch_triplet() -> str: - """Inform the arch triplet string for the current architecture.""" - arch = platform.machine() +def get_arch_triplet(arch: Optional[str] = None) -> str: + """Get the arch triplet string for an architecture. + + :param arch: Architecture to get the triplet of. If None, then get the arch triplet + of the host. + + :returns: The arch triplet. + """ + if not arch: + arch = platform.machine() + arch_config = _ARCH_CONFIG.get(arch) if not arch_config: raise RuntimeError(f"Arch triplet not defined for arch {arch!r}") diff --git a/snapcraft/meta/snap_yaml.py b/snapcraft/meta/snap_yaml.py index 0bcf7e0968..c1915f4915 100644 --- a/snapcraft/meta/snap_yaml.py +++ b/snapcraft/meta/snap_yaml.py @@ -374,13 +374,12 @@ def _get_grade(grade: Optional[str], build_base: Optional[str]) -> str: return grade -def write(project: Project, prime_dir: Path, *, arch: str, arch_triplet: str): +def write(project: Project, prime_dir: Path, *, arch: str): """Create a snap.yaml file. :param project: Snapcraft project. :param prime_dir: The directory containing the content to be snapped. :param arch: Target architecture the snap project is built to. - :param arch_triplet: Architecture triplet of the platform. """ meta_dir = prime_dir / "meta" meta_dir.mkdir(parents=True, exist_ok=True) @@ -395,6 +394,9 @@ def write(project: Project, prime_dir: Path, *, arch: str, arch_triplet: str): if project.hooks and any(h for h in project.hooks.values() if h.command_chain): assumes.add("command-chain") + # if arch is "all", do not include architecture-specific paths in the environment + arch_triplet = None if arch == "all" else project.get_build_for_arch_triplet() + environment = _populate_environment(project.environment, prime_dir, arch_triplet) version = process_version(project.version) @@ -449,7 +451,9 @@ def _repr_str(dumper, data): def _populate_environment( - environment: Optional[Dict[str, Optional[str]]], prime_dir: Path, arch_triplet: str + environment: Optional[Dict[str, Optional[str]]], + prime_dir: Path, + arch_triplet: Optional[str], ): """Populate default app environmental variables. @@ -457,6 +461,11 @@ def _populate_environment( - If LD_LIBRARY_PATH or PATH are defined, keep user-defined values. - If LD_LIBRARY_PATH or PATH are not defined, set to default values. - If LD_LIBRARY_PATH or PATH are null, do not use default values. + + :param environment: Dictionary of environment variables from the project. + :param prime_dir: The directory containing the content to be snapped. + :param arch_triplet: Architecture triplet of the target arch. If None, the + environment will not contain architecture-specific paths. """ if environment is None: return { diff --git a/snapcraft/parts/lifecycle.py b/snapcraft/parts/lifecycle.py index 777f8397f0..c096963238 100644 --- a/snapcraft/parts/lifecycle.py +++ b/snapcraft/parts/lifecycle.py @@ -410,12 +410,7 @@ def _generate_metadata( ) emit.progress("Generating snap metadata...") - snap_yaml.write( - project, - lifecycle.prime_dir, - arch=lifecycle.target_arch, - arch_triplet=lifecycle.target_arch_triplet, - ) + snap_yaml.write(project, lifecycle.prime_dir, arch=project.get_build_for()) emit.progress("Generated snap metadata", permanent=True) if parsed_args.enable_manifest: @@ -449,7 +444,7 @@ def _generate_manifest( manifest.write( project, lifecycle.prime_dir, - arch=lifecycle.target_arch, + arch=project.get_build_for(), parts=parts, start_time=start_time, image_information=image_information, diff --git a/snapcraft/projects.py b/snapcraft/projects.py index f9a2bb3d2b..c9a441efbf 100644 --- a/snapcraft/projects.py +++ b/snapcraft/projects.py @@ -26,8 +26,13 @@ from pydantic import PrivateAttr, conlist, constr from snapcraft import parts, utils +from snapcraft.elf.elf_utils import get_arch_triplet from snapcraft.errors import ProjectValidationError -from snapcraft.utils import get_effective_base, get_host_architecture +from snapcraft.utils import ( + convert_architecture_deb_to_platform, + get_effective_base, + get_host_architecture, +) class ProjectModel(pydantic.BaseModel): @@ -713,6 +718,18 @@ def get_build_for(self) -> str: # will not happen after schema validation raise RuntimeError("cannot determine build-for architecture") + def get_build_for_arch_triplet(self) -> Optional[str]: + """Get the architecture triplet for the first build-for architecture. + + :returns: The build-for arch triplet. If build-for is "all", then return None. + """ + arch = self.get_build_for() + + if arch != "all": + return get_arch_triplet(convert_architecture_deb_to_platform(arch)) + + return None + class _GrammarAwareModel(pydantic.BaseModel): class Config: diff --git a/snapcraft/utils.py b/snapcraft/utils.py index 42b975bd0d..060f008916 100644 --- a/snapcraft/utils.py +++ b/snapcraft/utils.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2021-2022 Canonical Ltd. +# Copyright 2021-2023 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -329,20 +329,42 @@ def humanize_list( return f"{humanized} {conjunction} {quoted_items[-1]}" -def get_common_ld_library_paths(prime_dir: Path, arch_triplet: str) -> List[str]: - """Return common existing PATH entries for a snap.""" +def get_common_ld_library_paths( + prime_dir: Path, arch_triplet: Optional[str] +) -> List[str]: + """Return common existing PATH entries for a snap. + + :param prime_dir: Path to the prime directory. + :param arch_triplet: Architecture triplet of target arch. If None, the list of paths + will not contain architecture-specific paths. + + :returns: List of common library paths in the prime directory that exist. + """ paths = [ prime_dir / "lib", prime_dir / "usr" / "lib", - prime_dir / "lib" / arch_triplet, - prime_dir / "usr" / "lib" / arch_triplet, ] + if arch_triplet: + paths.extend( + [ + prime_dir / "lib" / arch_triplet, + prime_dir / "usr" / "lib" / arch_triplet, + ] + ) + return [str(p) for p in paths if p.exists()] -def get_ld_library_paths(prime_dir: Path, arch_triplet: str) -> str: - """Return a usable in-snap LD_LIBRARY_PATH variable.""" +def get_ld_library_paths(prime_dir: Path, arch_triplet: Optional[str]) -> str: + """Return a usable in-snap LD_LIBRARY_PATH variable. + + :param prime_dir: Path to the prime directory. + :param arch_triplet: Architecture triplet of target arch. If None, LD_LIBRARY_PATH + will not contain architecture-specific paths. + + :returns: The LD_LIBRARY_PATH environment variable to be used for the snap. + """ paths = ["${SNAP_LIBRARY_PATH}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"] # Add the default LD_LIBRARY_PATH paths += get_common_ld_library_paths(prime_dir, arch_triplet) diff --git a/tests/unit/elf/test_elf_utils.py b/tests/unit/elf/test_elf_utils.py index f823383149..854199dc4a 100644 --- a/tests/unit/elf/test_elf_utils.py +++ b/tests/unit/elf/test_elf_utils.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2016-2022 Canonical Ltd. +# Copyright 2016-2023 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -159,13 +159,30 @@ class TestArchConfig: ("x86_64", "x86_64-linux-gnu"), ], ) - def test_get_arch_triplet(self, mocker, machine, expected_arch_triplet): + def test_get_arch_triplet_host(self, mocker, machine, expected_arch_triplet): """Verify `get_arch_triplet()` gets the host's architecture triplet.""" mocker.patch("snapcraft.elf.elf_utils.platform.machine", return_value=machine) arch_triplet = elf_utils.get_arch_triplet() assert arch_triplet == expected_arch_triplet + @pytest.mark.parametrize( + "machine, expected_arch_triplet", + [ + ("aarch64", "aarch64-linux-gnu"), + ("armv7l", "arm-linux-gnueabihf"), + ("ppc64le", "powerpc64le-linux-gnu"), + ("riscv64", "riscv64-linux-gnu"), + ("s390x", "s390x-linux-gnu"), + ("x86_64", "x86_64-linux-gnu"), + ], + ) + def test_get_arch_triplet(self, mocker, machine, expected_arch_triplet): + """Get the architecture triplet from the architecture passed as a parameter.""" + arch_triplet = elf_utils.get_arch_triplet(machine) + + assert arch_triplet == expected_arch_triplet + def test_get_arch_triplet_error(self, mocker): """Verify `get_arch_triplet()` raises an error for invalid machines.""" mocker.patch("snapcraft.elf.elf_utils.platform.machine", return_value="4004") diff --git a/tests/unit/linters/test_classic_linter.py b/tests/unit/linters/test_classic_linter.py index 3ebc8be4c4..6c6e7e7e25 100644 --- a/tests/unit/linters/test_classic_linter.py +++ b/tests/unit/linters/test_classic_linter.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022-2023 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -64,12 +64,7 @@ def test_classic_linter(mocker, new_dir, confinement, stage_libc, text): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") issues = linters.run_linters(new_dir, lint=None) @@ -131,12 +126,7 @@ def test_classic_linter_filter(mocker, new_dir): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") issues = linters.run_linters( new_dir, lint=projects.Lint(ignore=[{"classic": ["elf.*"]}]) diff --git a/tests/unit/linters/test_library_linter.py b/tests/unit/linters/test_library_linter.py index d10515ffd3..57fcca3fe9 100644 --- a/tests/unit/linters/test_library_linter.py +++ b/tests/unit/linters/test_library_linter.py @@ -54,12 +54,7 @@ def test_library_linter_missing_library(mocker, new_dir): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") issues = linters.run_linters(new_dir, lint=None) assert issues == [ @@ -114,12 +109,7 @@ def test_library_linter_unused_library(mocker, new_dir): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") issues = linters.run_linters(new_dir, lint=None) assert issues == [ @@ -160,12 +150,7 @@ def test_library_linter_filter_missing_library(mocker, new_dir, filter_name): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") issues = linters.run_linters( new_dir, lint=projects.Lint(ignore=[{filter_name: ["elf.*"]}]) @@ -210,12 +195,7 @@ def test_library_linter_filter_unused_library(mocker, new_dir, filter_name): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") issues = linters.run_linters( new_dir, lint=projects.Lint(ignore=[{filter_name: ["lib/libfoo.*"]}]) @@ -252,12 +232,7 @@ def test_library_linter_mixed_filters(mocker, new_dir): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") # lib/libfoo.so is an *unused* library, but here we filter out *missing* library # issues for this path. diff --git a/tests/unit/linters/test_linters.py b/tests/unit/linters/test_linters.py index ea6c7ae09e..2daf388518 100644 --- a/tests/unit/linters/test_linters.py +++ b/tests/unit/linters/test_linters.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022-2023 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -169,7 +169,6 @@ def test_run_linters(self, mocker, new_dir, linter_issue): project, prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) issues = linters.run_linters(new_dir, lint=None) @@ -199,7 +198,6 @@ def test_run_linters_ignore(self, mocker, new_dir, linter_issue): project, prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) lint = projects.Lint(ignore=["test"]) @@ -221,12 +219,7 @@ def test_run_linters_ignore_all_categories(self, mocker, new_dir, linter_issue): } project = projects.Project.unmarshal(yaml_data) - snap_yaml.write( - project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(project, prime_dir=Path(new_dir), arch="amd64") lint = projects.Lint(ignore=["test-1", "test-2"]) issues = linters.run_linters(new_dir, lint=lint) diff --git a/tests/unit/meta/test_snap_yaml.py b/tests/unit/meta/test_snap_yaml.py index fa05ac4b3b..5282046905 100644 --- a/tests/unit/meta/test_snap_yaml.py +++ b/tests/unit/meta/test_snap_yaml.py @@ -65,12 +65,7 @@ def _simple_project(**kwargs): def test_simple_snap_yaml(simple_project, new_dir): - snap_yaml.write( - simple_project(), - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(simple_project(), prime_dir=Path(new_dir), arch="amd64") yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -253,12 +248,7 @@ def complex_project(): def test_complex_snap_yaml(complex_project, new_dir): - snap_yaml.write( - complex_project, - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(complex_project, prime_dir=Path(new_dir), arch="amd64") yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -392,12 +382,7 @@ def test_hook_command_chain_assumes(simple_project, new_dir): }, } - snap_yaml.write( - simple_project(hooks=hooks), - prime_dir=Path(new_dir), - arch="amd64", - arch_triplet="x86_64-linux-gnu", - ) + snap_yaml.write(simple_project(hooks=hooks), prime_dir=Path(new_dir), arch="amd64") yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -485,7 +470,6 @@ def test_project_environment_ld_library_path_and_path_defined(simple_project, ne simple_project(environment=environment), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -520,7 +504,6 @@ def test_project_environment_ld_library_path_defined(simple_project, new_dir): simple_project(environment=environment), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -554,7 +537,6 @@ def test_project_environment_path_defined(simple_project, new_dir): simple_project(environment=environment), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -588,7 +570,6 @@ def test_project_environment_ld_library_path_null(simple_project, new_dir): simple_project(environment=environment), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -625,7 +606,6 @@ def test_version_git(simple_project, new_dir, mocker): simple_project(version="git"), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") @@ -695,7 +675,6 @@ def test_grade(grade, simple_project, new_dir): project=simple_project(grade=grade), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -711,7 +690,6 @@ def test_grade_default(emitter, simple_project, new_dir): project=simple_project(), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -729,7 +707,6 @@ def test_grade_build_base_devel(emitter, simple_project, new_dir): project=simple_project(build_base="devel"), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -931,7 +908,6 @@ def test_project_passthrough_snap_yaml(simple_project, new_dir): ), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -977,7 +953,6 @@ def test_app_passthrough_snap_yaml(simple_project, new_dir): ), prime_dir=Path(new_dir), arch="amd64", - arch_triplet="x86_64-linux-gnu", ) yaml_file = Path("meta/snap.yaml") assert yaml_file.is_file() @@ -1005,3 +980,50 @@ def test_app_passthrough_snap_yaml(simple_project, new_dir): PATH: $SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH """ ) + + +@pytest.mark.parametrize( + ["arch", "arch_triplet"], + [ + ("amd64", "x86_64-linux-gnu"), + ("arm64", "aarch64-linux-gnu"), + ("armhf", "arm-linux-gnueabihf"), + ("ppc64el", "powerpc64le-linux-gnu"), + ("s390x", "s390x-linux-gnu"), + ("riscv64", "riscv64-linux-gnu"), + ], +) +def test_architectures(arch, arch_triplet, simple_project, new_dir): + """LD_LIBRARY_PATH should contain paths of the architecture.""" + # create library directories + (new_dir / f"usr/lib/{arch_triplet}").mkdir(parents=True) + (new_dir / f"lib/{arch_triplet}").mkdir(parents=True) + + snap_yaml.write( + simple_project(architectures=[arch]), prime_dir=Path(new_dir), arch=arch + ) + + yaml_file = Path("meta/snap.yaml") + assert yaml_file.is_file() + content = yaml_file.read_text() + assert ( + "${SNAP_LIBRARY_PATH}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}:$SNAP/lib:" + f"$SNAP/usr/lib:$SNAP/lib/{arch_triplet}:$SNAP/usr/lib/{arch_triplet}\n" + ) in content + + +def test_architectures_all(simple_project, new_dir): + """LD_LIBRARY_PATH should not contain arch-specific paths when arch = "all".""" + # create library directories + (new_dir / "usr/lib/x86_64-linux-gnu").mkdir(parents=True) + (new_dir / "lib/x86_64-linux-gnu").mkdir(parents=True) + + snap_yaml.write(simple_project(), prime_dir=Path(new_dir), arch="all") + + yaml_file = Path("meta/snap.yaml") + assert yaml_file.is_file() + content = yaml_file.read_text() + assert ( + "${SNAP_LIBRARY_PATH}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}:" + "$SNAP/lib:$SNAP/usr/lib\n" + ) in content diff --git a/tests/unit/parts/test_lifecycle.py b/tests/unit/parts/test_lifecycle.py index bdb40265bc..369dbf88b3 100644 --- a/tests/unit/parts/test_lifecycle.py +++ b/tests/unit/parts/test_lifecycle.py @@ -1686,3 +1686,53 @@ def test_patch_elf(snapcraft_yaml, mocker, new_dir): elf_file_path=new_dir / "prime/elf.bin", ) ] + + +@pytest.mark.parametrize("build_for", ["amd64", "arm64", "all"]) +def test_lifecycle_write_metadata( + build_for, snapcraft_yaml, project_vars, new_dir, mocker +): + """Metadata and manifest should be written during the lifecycle.""" + yaml_data = { + "base": "core22", + "architectures": [{"build-on": "amd64", "build-for": build_for}], + } + project = Project.unmarshal(snapcraft_yaml(**yaml_data)) + mocker.patch("snapcraft.parts.PartsLifecycle.run") + mocker.patch("snapcraft.pack.pack_snap") + mock_write_metadata = mocker.patch("snapcraft.meta.snap_yaml.write") + mock_write_manifest = mocker.patch("snapcraft.meta.manifest.write") + + parsed_args = argparse.Namespace( + debug=False, + destructive_mode=True, + enable_manifest=True, + ua_token=None, + parts=[], + manifest_image_information=None, + ) + + parts_lifecycle._run_command( + "prime", + project=project, + parse_info={}, + assets_dir=Path(), + start_time=datetime.now(), + parallel_build_count=8, + parsed_args=parsed_args, + ) + + assert mock_write_metadata.mock_calls == [ + call(project, new_dir / "prime", arch=build_for) + ] + assert mock_write_manifest.mock_calls == [ + call( + project, + new_dir / "prime", + arch=build_for, + parts=mocker.ANY, + start_time=mocker.ANY, + image_information="{}", + primed_stage_packages=[], + ) + ] diff --git a/tests/unit/test_projects.py b/tests/unit/test_projects.py index 6954faf5de..28164cc928 100644 --- a/tests/unit/test_projects.py +++ b/tests/unit/test_projects.py @@ -1684,3 +1684,29 @@ def test_project_get_build_for(self, project_yaml_data): ) project = Project.unmarshal(data) assert project.get_build_for() == "armhf" + + def test_project_get_build_for_arch_triplet(self, project_yaml_data): + """Get architecture triplet for the build-for architecture.""" + data = project_yaml_data( + architectures=[ + {"build-on": ["arm64"], "build-for": ["armhf"]}, + ] + ) + + project = Project.unmarshal(data) + arch_triplet = project.get_build_for_arch_triplet() + + assert arch_triplet == "arm-linux-gnueabihf" + + def test_project_get_build_for_arch_triplet_all(self, project_yaml_data): + """When build-for = "all", the build-for arch triplet should be None.""" + data = project_yaml_data( + architectures=[ + {"build-on": ["arm64"], "build-for": ["all"]}, + ] + ) + + project = Project.unmarshal(data) + arch_triplet = project.get_build_for_arch_triplet() + + assert not arch_triplet diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 450aa9dc12..8039a6bb9a 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022-2023 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 3 as @@ -338,6 +338,26 @@ def test_get_ld_library_paths(tmp_path, lib_dirs, expected_env): assert utils.get_ld_library_paths(tmp_path, "i286-none-none") == expected_env +@pytest.mark.parametrize( + ["lib_dirs", "expected_env"], + [ + (["lib"], "$SNAP/lib"), + (["lib", "usr/lib"], "$SNAP/lib:$SNAP/usr/lib"), + (["lib/i286-none-none", "usr/lib/i286-none-none"], "$SNAP/lib:$SNAP/usr/lib"), + ], +) +def test_get_ld_library_paths_no_architecture(tmp_path, lib_dirs, expected_env): + """Do not include architecture-specfic paths if an architecture is not provided.""" + for lib_dir in lib_dirs: + (tmp_path / lib_dir).mkdir(parents=True) + + env = utils.get_ld_library_paths(tmp_path, None) + + assert env == ( + f"${{SNAP_LIBRARY_PATH}}${{LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}}:{expected_env}" + ) + + ################# # Get host tool # #################