From dacb3fa8ca39613b1af5efde9db1ab35de11ff7f Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Wed, 12 Jul 2023 20:12:52 -0300 Subject: [PATCH] meta: write out metadata links Signed-off-by: Sergio Schvezov --- snapcraft/meta/snap_yaml.py | 41 ++++- tests/spread/general/metadata-links/task.yaml | 8 + tests/unit/meta/test_snap_yaml.py | 162 ++++++++++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) diff --git a/snapcraft/meta/snap_yaml.py b/snapcraft/meta/snap_yaml.py index c1915f4915..618984b1ce 100644 --- a/snapcraft/meta/snap_yaml.py +++ b/snapcraft/meta/snap_yaml.py @@ -26,7 +26,7 @@ from pydantic_yaml import YamlModel from snapcraft import errors -from snapcraft.projects import App, Project +from snapcraft.projects import App, Project, UniqueStrList from snapcraft.utils import get_ld_library_paths, process_version @@ -176,6 +176,41 @@ def get_content_dirs(self, installed_path: Path) -> Set[Path]: return content_dirs +class Links(_SnapMetadataModel): + """Metadata links used in snaps.""" + + contact: Optional[UniqueStrList] + donation: Optional[UniqueStrList] + issues: Optional[UniqueStrList] + source_code: Optional[UniqueStrList] + website: Optional[UniqueStrList] + + @staticmethod + def _normalize_value( + value: Optional[Union[str, UniqueStrList]] + ) -> Optional[List[str]]: + if isinstance(value, str): + value = [value] + return value + + @classmethod + def from_project(cls, project: Project) -> "Links": + """Create Links from a Project.""" + return cls( + contact=cls._normalize_value(project.contact), + donation=cls._normalize_value(project.donation), + issues=cls._normalize_value(project.issues), + source_code=cls._normalize_value(project.source_code), + website=cls._normalize_value(project.website), + ) + + def __bool__(self) -> bool: + """Return True if any of the Links attributes are set.""" + return any( + [self.contact, self.donation, self.issues, self.source_code, self.website] + ) + + class SnapMetadata(_SnapMetadataModel): """The snap.yaml model. @@ -210,6 +245,7 @@ class Config: layout: Optional[Dict[str, Dict[str, str]]] system_usernames: Optional[Dict[str, Any]] provenance: Optional[str] + links: Optional[Links] @classmethod def unmarshal(cls, data: Dict[str, Any]) -> "SnapMetadata": @@ -403,6 +439,8 @@ def write(project: Project, prime_dir: Path, *, arch: str): # project provided assumes and computed assumes total_assumes = sorted(project.assumes + list(assumes)) + links = Links.from_project(project) + snap_metadata = SnapMetadata( name=project.name, title=project.title, @@ -425,6 +463,7 @@ def write(project: Project, prime_dir: Path, *, arch: str): layout=project.layout, system_usernames=project.system_usernames, provenance=project.provenance, + links=links if links else None, ) if project.passthrough: for name, value in project.passthrough.items(): diff --git a/tests/spread/general/metadata-links/task.yaml b/tests/spread/general/metadata-links/task.yaml index 47730acec2..41b2976613 100644 --- a/tests/spread/general/metadata-links/task.yaml +++ b/tests/spread/general/metadata-links/task.yaml @@ -6,10 +6,18 @@ environment: prepare: | snap install yq + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + set_base snapcraft.yaml + restore: | snapcraft clean rm -rf ./*.snap + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + restore_yaml snapcraft.yaml + execute: | # Create a snap to trigger `snap pack`. snapcraft diff --git a/tests/unit/meta/test_snap_yaml.py b/tests/unit/meta/test_snap_yaml.py index d04a8cd408..b5148863f5 100644 --- a/tests/unit/meta/test_snap_yaml.py +++ b/tests/unit/meta/test_snap_yaml.py @@ -124,6 +124,111 @@ def test_assumes(simple_project, new_dir): ) +def test_links_scalars(simple_project, new_dir): + snap_yaml.write( + simple_project( + contact="me@acme.com", + issues="https://hubhub.com/issues", + donation="https://moneyfornothing.com", + source_code="https://closed.acme.com", + website="https://acme.com", + ), + prime_dir=Path(new_dir), + arch="amd64", + ) + yaml_file = Path("meta/snap.yaml") + assert yaml_file.is_file() + + content = yaml_file.read_text() + assert content == textwrap.dedent( + """\ + name: mytest + version: 1.29.3 + summary: Single-line elevator pitch for your amazing snap + description: test-description + architectures: + - amd64 + base: core22 + apps: + app1: + command: bin/mytest + confinement: strict + grade: stable + environment: + LD_LIBRARY_PATH: ${SNAP_LIBRARY_PATH}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} + PATH: $SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH + links: + contact: + - me@acme.com + donation: + - https://moneyfornothing.com + issues: + - https://hubhub.com/issues + source-code: + - https://closed.acme.com + website: + - https://acme.com + """ + ) + + +def test_links_lists(simple_project, new_dir): + snap_yaml.write( + simple_project( + contact=[ + "me@acme.com", + "you@acme.com", + ], + issues=[ + "https://hubhub.com/issues", + "https://corner.com/issues", + ], + donation=["https://moneyfornothing.com", "https://prince.com"], + source_code="https://closed.acme.com", + website="https://acme.com", + ), + prime_dir=Path(new_dir), + arch="amd64", + ) + yaml_file = Path("meta/snap.yaml") + assert yaml_file.is_file() + + content = yaml_file.read_text() + assert content == textwrap.dedent( + """\ + name: mytest + version: 1.29.3 + summary: Single-line elevator pitch for your amazing snap + description: test-description + architectures: + - amd64 + base: core22 + apps: + app1: + command: bin/mytest + confinement: strict + grade: stable + environment: + LD_LIBRARY_PATH: ${SNAP_LIBRARY_PATH}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} + PATH: $SNAP/usr/sbin:$SNAP/usr/bin:$SNAP/sbin:$SNAP/bin:$PATH + links: + contact: + - me@acme.com + - you@acme.com + donation: + - https://moneyfornothing.com + - https://prince.com + issues: + - https://hubhub.com/issues + - https://corner.com/issues + source-code: + - https://closed.acme.com + website: + - https://acme.com + """ + ) + + @pytest.fixture def complex_project(): snapcraft_yaml = textwrap.dedent( @@ -1025,3 +1130,60 @@ def test_architectures_all(simple_project, new_dir): "${SNAP_LIBRARY_PATH}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}:" "$SNAP/lib:$SNAP/usr/lib\n" ) in content + + +############## +# Test Links # +############## + + +def test_links_for_scalars(simple_project): + project = simple_project( + contact="me@acme.com", + issues="https://hubhub.com/issues", + donation="https://moneyfornothing.com", + source_code="https://closed.acme.com", + website="https://acme.com", + ) + + links = snap_yaml.Links.from_project(project) + + assert links.contact == [project.contact] + assert links.issues == [project.issues] + assert links.donation == [project.donation] + assert links.source_code == [project.source_code] + assert links.website == [project.website] + + assert bool(links) is True + + +def test_links_for_lists(simple_project): + project = simple_project( + contact=[ + "me@acme.com", + "you@acme.com", + ], + issues=[ + "https://hubhub.com/issues", + "https://corner.com/issues", + ], + donation=["https://moneyfornothing.com", "https://prince.com"], + ) + + links = snap_yaml.Links.from_project(project) + + assert links.contact == project.contact + assert links.issues == project.issues + assert links.donation == project.donation + assert links.source_code is None + assert links.website is None + + assert bool(links) is True + + +def test_no_links(simple_project): + project = simple_project() + + links = snap_yaml.Links.from_project(project) + + assert bool(links) is False