From 7bd0d3a38d678daea1490f1715c3afc0406eb7be Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 6 Dec 2023 11:02:02 -0600 Subject: [PATCH 001/151] build(deps): bump craft-providers to 1.20.1 (#4462) Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 3dca540179..4ecf874559 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -15,7 +15,7 @@ craft-archives==1.1.3 craft-cli==1.2.0 craft-grammar==1.1.1 craft-parts==1.19.7 -craft-providers==1.19.2 +craft-providers==1.20.1 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.13 diff --git a/requirements.txt b/requirements.txt index d8d4dde21f..7856dde0ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ craft-archives==1.1.3 craft-cli==1.2.0 craft-grammar==1.1.1 craft-parts==1.19.7 -craft-providers==1.19.2 +craft-providers==1.20.1 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.13 From 4aa5f63cbc99c1706d9363c3511167fd9f5d7ca1 Mon Sep 17 00:00:00 2001 From: Callahan Date: Fri, 5 Jan 2024 07:53:18 -0600 Subject: [PATCH 002/151] chore: remove deprecated parameter `build-base` (#4505) Signed-off-by: Callahan Kovacs --- snapcraft/parts/lifecycle.py | 1 - tests/unit/conftest.py | 2 +- tests/unit/parts/test_lifecycle.py | 3 --- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/snapcraft/parts/lifecycle.py b/snapcraft/parts/lifecycle.py index bb104b6637..2eff42764e 100644 --- a/snapcraft/parts/lifecycle.py +++ b/snapcraft/parts/lifecycle.py @@ -565,7 +565,6 @@ def _run_in_provider( project_name=project.name, project_path=project_path, base_configuration=base_configuration, - build_base=build_base.value, instance_name=instance_name, allow_unstable=allow_unstable, ) as instance: diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 880a7aec41..dad56c2d7c 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -354,7 +354,7 @@ def launched_environment( project_name: str, project_path: Path, base_configuration: Base, - build_base: str, + build_base: Optional[str] = None, instance_name: str, allow_unstable: bool = False, ): diff --git a/tests/unit/parts/test_lifecycle.py b/tests/unit/parts/test_lifecycle.py index 3197bc6d2a..34c9ee7c09 100644 --- a/tests/unit/parts/test_lifecycle.py +++ b/tests/unit/parts/test_lifecycle.py @@ -1226,7 +1226,6 @@ def test_lifecycle_run_in_provider_default( project_name="mytest", project_path=ANY, base_configuration=mock_base_configuration, - build_base="22.04", instance_name="test-instance-name", allow_unstable=False, ) @@ -1350,7 +1349,6 @@ def test_lifecycle_run_in_provider_all_options( project_name="mytest", project_path=ANY, base_configuration=mock_base_configuration, - build_base="22.04", instance_name="test-instance-name", allow_unstable=False, ) @@ -1452,7 +1450,6 @@ def test_lifecycle_run_in_provider( project_name="mytest", project_path=ANY, base_configuration=mock_base_configuration, - build_base=BuilddBaseAlias.JAMMY.value, instance_name="test-instance-name", allow_unstable=False, ) From 8c2c801e7beda637e185e576969fd8ffaa9f76d7 Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Fri, 15 Mar 2024 18:20:08 -0300 Subject: [PATCH 003/151] build(deps): bump craft-providers to 1.20.2 (#4675) Signed-off-by: Sergio Schvezov --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 4ecf874559..06ea0457d0 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -15,7 +15,7 @@ craft-archives==1.1.3 craft-cli==1.2.0 craft-grammar==1.1.1 craft-parts==1.19.7 -craft-providers==1.20.1 +craft-providers==1.20.2 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.13 diff --git a/requirements.txt b/requirements.txt index 7856dde0ad..6328a931ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ craft-archives==1.1.3 craft-cli==1.2.0 craft-grammar==1.1.1 craft-parts==1.19.7 -craft-providers==1.20.1 +craft-providers==1.20.2 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.13 From 6a388877fbc7ef4c77ab831be584bd8c90c7ceed Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 23 Jul 2024 07:13:02 -0500 Subject: [PATCH 004/151] chore: add issue number to overriding the command_groups property (#4934) The task to refactor commands in Snapcraft is tracked in #4911. Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index f7e4e72999..29a9f1683d 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -146,10 +146,10 @@ def _configure_services(self, provider_name: str | None) -> None: super()._configure_services(provider_name) + # TODO: remove this override (#4911) @property def command_groups(self): """Short-circuit the standard command groups for now.""" - # TODO: Remove this once we've got lifecycle commands and version migrated. return self._command_groups @override From 4ee3c1493aec801c9b1e6737e51c877b3e03dc79 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 25 Jul 2024 08:18:05 -0500 Subject: [PATCH 005/151] tests: add spread tests for validation-sets (#4924) Adds a spread test for `edit-validation-sets` and `list-validation-sets`. Signed-off-by: Callahan Kovacs Co-authored-by: Alex Lowe --- .github/workflows/spread.yml | 4 +- spread.yaml | 9 ++++ .../store => store/basic-workflow}/task.yaml | 8 +--- tests/spread/store/validation-sets/editor.sh | 12 ++++++ tests/spread/store/validation-sets/task.yaml | 41 +++++++++++++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) rename tests/spread/{general/store => store/basic-workflow}/task.yaml (91%) create mode 100755 tests/spread/store/validation-sets/editor.sh create mode 100644 tests/spread/store/validation-sets/task.yaml diff --git a/.github/workflows/spread.yml b/.github/workflows/spread.yml index 4bcfaa18e8..b6ae331d1b 100644 --- a/.github/workflows/spread.yml +++ b/.github/workflows/spread.yml @@ -110,10 +110,12 @@ jobs: name: Run spread env: SPREAD_GOOGLE_KEY: ${{ secrets.SPREAD_GOOGLE_KEY }} + SNAPCRAFT_ASSERTION_KEY: "${{ secrets.SNAPCRAFT_ASSERTION_KEY }}" SNAPCRAFT_STORE_CREDENTIALS_STAGING: "${{ secrets.SNAPCRAFT_STORE_CREDENTIALS_STAGING }}" SNAPCRAFT_STORE_CREDENTIALS_STAGING_CANDID: "${{ secrets.SNAPCRAFT_STORE_CREDENTIALS_STAGING_CANDID }}" SNAPCRAFT_STORE_CREDENTIALS_STAGING_LEGACY: "${{ secrets.SNAPCRAFT_STORE_CREDENTIALS_STAGING_LEGACY }}" - run: spread google:ubuntu-22.04-64:tests/spread/general/store + run: | + spread google:ubuntu-22.04-64:tests/spread/store/ - name: Discard spread workers if: always() diff --git a/spread.yaml b/spread.yaml index 4ebd722482..039ef727b3 100644 --- a/spread.yaml +++ b/spread.yaml @@ -377,6 +377,15 @@ suites: sudo apt-get install git sudo apt-mark auto git + tests/spread/store/: + summary: tests of store-related snapcraft commands + manual: true + environment: + STORE_DASHBOARD_URL: https://dashboard.staging.snapcraft.io + STORE_API_URL: https://api.staging.snapcraft.io + STORE_UPLOAD_URL: https://storage.staging.snapcraftcontent.com + UBUNTU_ONE_SSO_URL: https://login.staging.ubuntu.com + docs/howto/code/: summary: tests how-to guides from the docs systems: diff --git a/tests/spread/general/store/task.yaml b/tests/spread/store/basic-workflow/task.yaml similarity index 91% rename from tests/spread/general/store/task.yaml rename to tests/spread/store/basic-workflow/task.yaml index 2a41cf3f47..c6035c2bcf 100644 --- a/tests/spread/general/store/task.yaml +++ b/tests/spread/store/basic-workflow/task.yaml @@ -1,6 +1,4 @@ -summary: Test the store workflow - -manual: true +summary: Test the store workflow to register, upload, and release a snap environment: # use a core22 snap with components but no component hooks @@ -9,10 +7,6 @@ environment: SNAPCRAFT_STORE_CREDENTIALS/ubuntu_one: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING})" SNAPCRAFT_STORE_CREDENTIALS/legacy: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING_LEGACY})" SNAPCRAFT_STORE_CREDENTIALS/candid: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING_CANDID})" - STORE_DASHBOARD_URL: https://dashboard.staging.snapcraft.io - STORE_API_URL: https://api.staging.snapcraft.io - STORE_UPLOAD_URL: https://storage.staging.snapcraftcontent.com - UBUNTU_ONE_SSO_URL: https://login.staging.ubuntu.com prepare: | if [[ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]]; then diff --git a/tests/spread/store/validation-sets/editor.sh b/tests/spread/store/validation-sets/editor.sh new file mode 100755 index 0000000000..a38c96be47 --- /dev/null +++ b/tests/spread/store/validation-sets/editor.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +validation_set_file="$1" + +# flip-flop between two valid revisions of `test-snapcraft-assertions` in the staging store: 1 and 2 +if grep -q "^ revision:.*1" "$validation_set_file"; then + (( revision=2 )) +else + (( revision=1 )) +fi + +sed -i "s/ revision:.*/ revision: $revision/g" "$validation_set_file" diff --git a/tests/spread/store/validation-sets/task.yaml b/tests/spread/store/validation-sets/task.yaml new file mode 100644 index 0000000000..277ee9d8e1 --- /dev/null +++ b/tests/spread/store/validation-sets/task.yaml @@ -0,0 +1,41 @@ +summary: test the validation-set commands + +environment: + SNAPCRAFT_ASSERTION_KEY: "$(HOST: echo ${SNAPCRAFT_ASSERTION_KEY})" + SNAPCRAFT_STORE_CREDENTIALS: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING})" + +prepare: | + if [[ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]]; then + ERROR "No credentials set in env SNAPCRAFT_STORE_CREDENTIALS" + fi + + if [[ -z "$SNAPCRAFT_ASSERTION_KEY" ]]; then + ERROR "No gpg key set in env SNAPCRAFT_ASSERTION_KEY" + fi + + # setup snap gpg dir + mkdir -p "$HOME/.snap/gnupg" + chmod 700 "$HOME/.snap/gnupg" + + # import a registered key + echo "$SNAPCRAFT_ASSERTION_KEY" | base64 --decode > store-key.txt + gpg --homedir "$HOME/.snap/gnupg" --import store-key.txt + rm -f store-key.txt + + snap install yq + +execute: | + # ensure snapcraft is logged in and can access the store + snapcraft whoami + + # snapcraft will use a fake file editor + export EDITOR="$PWD/editor.sh" + + snapcraft edit-validation-sets "$(snapcraft whoami | yq .id)" testset 1 --key-name testspreadkey + + snapcraft list-validation-sets | MATCH testset + +restore: | + rm -rf "$HOME/.snap/gnupg" + + snap remove --purge yq From feed34ebac8472954d438e2d70beeae0d3ae640e Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 30 Jul 2024 12:36:22 -0500 Subject: [PATCH 006/151] fix: use correct types for validation set models (#4928) - Use the correct data types for API calls - Switch to pydantic models - Catch model errors locally rather than relying on the store to validate the data - Make the boilerplate validation set valid Signed-off-by: Callahan Kovacs --- snapcraft/commands/validation_sets.py | 62 ++-- snapcraft_legacy/cli/assertions.py | 3 +- snapcraft_legacy/storeapi/v2/_api_schema.py | 74 +---- .../storeapi/v2/validation_sets.py | 280 ++++++++---------- tests/legacy/unit/store/test_store_client.py | 4 +- .../unit/store/v2/test_validation_sets.py | 215 +++++++------- tests/unit/commands/test_validation_sets.py | 189 +++++++----- 7 files changed, 404 insertions(+), 423 deletions(-) diff --git a/snapcraft/commands/validation_sets.py b/snapcraft/commands/validation_sets.py index 2f01221dc0..50f1ef97ed 100644 --- a/snapcraft/commands/validation_sets.py +++ b/snapcraft/commands/validation_sets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -24,14 +24,17 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Optional +import craft_application.util import yaml from craft_application.commands import AppCommand +from craft_application.errors import CraftValidationError from craft_cli import emit from overrides import overrides from snapcraft import errors, utils from snapcraft_legacy._store import StoreClientCLI from snapcraft_legacy.storeapi.errors import StoreValidationSetsError +from snapcraft_legacy.storeapi.v2 import validation_sets if TYPE_CHECKING: import argparse @@ -40,7 +43,7 @@ _VALIDATIONS_SETS_SNAPS_TEMPLATE = textwrap.dedent( """\ snaps: - # - name: # The name of the snap. + - name: hello # The name of the snap. # id: # The ID of the snap. Optional, defaults to the current ID for # the provided name. # presence: [required|optional|invalid] # Optional, defaults to required. @@ -102,7 +105,9 @@ def run(self, parsed_args: "argparse.Namespace"): validation_sets_path.write_text(validation_sets_template, encoding="utf-8") edited_validation_sets = edit_validation_sets(validation_sets_path) - if edited_validation_sets == yaml.safe_load(validation_sets_template): + if edited_validation_sets == validation_sets.EditableBuildAssertion( + **yaml.safe_load(validation_sets_template) + ): emit.message("No changes made") return @@ -119,7 +124,7 @@ def run(self, parsed_args: "argparse.Namespace"): "Do you wish to amend the validation set?" ): raise errors.SnapcraftError( - "Operation aborted" + "operation aborted" ) from validation_error edited_validation_sets = edit_validation_sets(validation_sets_path) finally: @@ -127,32 +132,43 @@ def run(self, parsed_args: "argparse.Namespace"): def _submit_validation_set( - edited_validation_sets: Dict[str, Any], + edited_validation_sets: validation_sets.EditableBuildAssertion, key_name: Optional[str], store_client: StoreClientCLI, ) -> None: + emit.debug(f"Posting assertion to build: {edited_validation_sets.json()}") build_assertion = store_client.post_validation_sets_build_assertion( - validation_sets=edited_validation_sets + validation_sets=edited_validation_sets.marshal() ) - signed_validation_sets = _sign_assertion( - build_assertion.marshal(), key_name=key_name + build_assertion_dict = build_assertion.marshal_scalars_as_strings() + + signed_validation_sets = _sign_assertion(build_assertion_dict, key_name=key_name) + + emit.debug("Posting signed validation sets.") + response = store_client.post_validation_sets( + signed_validation_sets=signed_validation_sets ) - store_client.post_validation_sets(signed_validation_sets=signed_validation_sets) + emit.debug(f"Response: {response.json()}") def _generate_template( - asserted_validation_sets, *, account_id: str, set_name: str, sequence: str + asserted_validation_sets: validation_sets.ValidationSets, + *, + account_id: str, + set_name: str, + sequence: str, ) -> str: """Generate a template to edit asserted_validation_sets.""" try: # assertions should only have one item since a specific # sequence was requested. - revision = asserted_validation_sets.assertions[0].revision + revision = asserted_validation_sets.assertions[0].headers.revision snaps = yaml.dump( { "snaps": [ - s.marshal() for s in asserted_validation_sets.assertions[0].snaps + s.marshal() + for s in asserted_validation_sets.assertions[0].headers.snaps ] }, default_flow_style=False, @@ -174,7 +190,9 @@ def _generate_template( return unverified_validation_sets -def edit_validation_sets(validation_sets_path: Path) -> Dict[str, Any]: +def edit_validation_sets( + validation_sets_path: Path, +) -> validation_sets.EditableBuildAssertion: """Spawn an editor to modify the validation-sets.""" editor_cmd = os.getenv("EDITOR", "vi") @@ -182,23 +200,29 @@ def edit_validation_sets(validation_sets_path: Path) -> Dict[str, Any]: with emit.pause(): subprocess.run([editor_cmd, validation_sets_path], check=True) try: - edited_validation_sets = yaml.safe_load( - validation_sets_path.read_text(encoding="utf-8") + with validation_sets_path.open() as file: + data = craft_application.util.safe_yaml_load(file) + edited_validation_sets = validation_sets.EditableBuildAssertion.from_yaml_data( + data=data, + # filepath is only shown for pydantic errors and snapcraft should + # not expose the temp file name + filepath=Path("validation-sets"), ) return edited_validation_sets - except yaml.YAMLError as yaml_error: - emit.message(f"A YAML parsing error occurred {yaml_error!s}") + except (yaml.YAMLError, CraftValidationError) as err: + emit.message(f"{err!s}") if not utils.confirm_with_user("Do you wish to amend the validation set?"): - raise errors.SnapcraftError("Operation aborted") from yaml_error + raise errors.SnapcraftError("operation aborted") from err def _sign_assertion(assertion: Dict[str, Any], *, key_name: Optional[str]) -> bytes: + emit.debug("Signing assertion.") cmdline = ["snap", "sign"] if key_name: cmdline += ["-k", key_name] snap_sign = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE) signed_assertion, _ = snap_sign.communicate(input=json.dumps(assertion).encode()) if snap_sign.returncode != 0: - raise errors.SnapcraftError("Failed to sign assertion") + raise errors.SnapcraftError("failed to sign assertion") return signed_assertion diff --git a/snapcraft_legacy/cli/assertions.py b/snapcraft_legacy/cli/assertions.py index c4b73937cb..8177264ed8 100644 --- a/snapcraft_legacy/cli/assertions.py +++ b/snapcraft_legacy/cli/assertions.py @@ -135,7 +135,8 @@ def list_validation_sets(name, sequence): else: headers = ["Account-ID", "Name", "Sequence", "Revision", "When"] assertions = list() - for assertion in asserted_validation_sets.assertions: + for assertion_header in asserted_validation_sets.assertions: + assertion = assertion_header.headers assertions.append( [ assertion.account_id, diff --git a/snapcraft_legacy/storeapi/v2/_api_schema.py b/snapcraft_legacy/storeapi/v2/_api_schema.py index 8e56fca0e4..1bde67d5e9 100644 --- a/snapcraft_legacy/storeapi/v2/_api_schema.py +++ b/snapcraft_legacy/storeapi/v2/_api_schema.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2020 Canonical Ltd +# Copyright 2020,2024 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 @@ -275,75 +275,3 @@ "required": ["account", "channels", "packages", "permissions"], "type": "object", } - -# https://dashboard.snapcraft.io/docs/v2/en/validation-sets.html -BUILD_ASSERTION_JSONSCHEMA: Dict[str, Any] = { - "properties": { - "account-id": { - "description": 'The "account-id" assertion header', - "type": "string", - }, - "authority-id": { - "description": 'The "authority-id" assertion header', - "type": "string", - }, - "name": {"description": 'The "name" assertion header', "type": "string"}, - "revision": { - "description": 'The "revision" assertion header', - "type": "string", - }, - "sequence": { - "description": 'The "sequence" assertion header', - "type": "string", - }, - "series": {"description": 'The "series" assertion header', "type": "string"}, - "snaps": { - "items": { - "description": "List of snaps in a Validation Set assertion", - "properties": { - "id": { - "description": "Snap ID", - "maxLength": 100, - "type": "string", - }, - "name": { - "description": "Snap name", - "maxLength": 100, - "type": "string", - }, - "presence": { - "description": "Snap presence", - "enum": ["required", "optional", "invalid"], - "type": "string", - }, - "revision": {"description": "Snap revision", "type": "string"}, - }, - "required": ["name"], - "type": "object", - }, - "minItems": 1, - "type": "array", - }, - "timestamp": { - "description": 'The "timestamp" assertion header', - "type": "string", - }, - "type": { - "const": "validation-set", - "description": 'The "type" assertion header', - "type": "string", - }, - }, - "required": [ - "type", - "authority-id", - "series", - "account-id", - "name", - "sequence", - # "revision", - "timestamp", - "snaps", - ], - "type": "object", -} diff --git a/snapcraft_legacy/storeapi/v2/validation_sets.py b/snapcraft_legacy/storeapi/v2/validation_sets.py index ad4c5e0ca8..3dbe84f6b9 100644 --- a/snapcraft_legacy/storeapi/v2/validation_sets.py +++ b/snapcraft_legacy/storeapi/v2/validation_sets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright (C) 2021 Canonical Ltd +# Copyright (C) 2021,2024 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 @@ -14,161 +14,135 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from typing import Any, Dict, List, Optional +from typing import Any, Annotated, Dict, Literal, TYPE_CHECKING +import numbers +import pydantic -import jsonschema +from craft_application import models -from ._api_schema import BUILD_ASSERTION_JSONSCHEMA +if TYPE_CHECKING: + SnapName = str + SnapId = str +else: + SnapName = pydantic.constr(max_length=40) + SnapId = pydantic.constr(max_length=40) -class Snap: + +def cast_dict_scalars_to_strings(data: dict) -> dict[str, Any]: + """Cast all scalars in a dictionary to strings. + + Supported scalar types are str, bool, and numbers. + """ + return {_to_string(key): _to_string(value) for key, value in data.items()} + + +def _to_string( + data: dict | list | str | int | float | str | bool | None +) -> dict[str, Any] | list | str | None: + """Recurse through nested dicts and lists and cast scalar values to strings. + + Supported scalar types are str, bool, and numbers. + """ + # start with a string as it is the most common scenario + if isinstance(data, str): + return data + + if isinstance(data, dict): + return {_to_string(key): _to_string(value) for key, value in data.items()} + + if isinstance(data, list): + return [_to_string(i) for i in data] + + if isinstance(data, (numbers.Number, bool)): + return str(data) + + return data + + +class Snap(models.CraftBaseModel): """Represent a Snap in a Validation Set.""" - @classmethod - def unmarshal(cls, payload: Dict[str, Any]) -> "Snap": - jsonschema.validate( - payload, BUILD_ASSERTION_JSONSCHEMA["properties"]["snaps"]["items"] - ) - - return cls( - name=payload["name"], - snap_id=payload.get("id"), - presence=payload.get("presence"), - revision=payload.get("revision"), - ) - - def marshal(self) -> Dict[str, Any]: - payload = {"name": self.name} - - if self.snap_id is not None: - payload["id"] = self.snap_id - - if self.presence is not None: - payload["presence"] = self.presence - - if self.revision is not None: - payload["revision"] = self.revision - - return payload - - def __eq__(self, other) -> bool: - if not isinstance(other, Snap): - return False - - return ( - self.name == other.name - and self.snap_id == other.snap_id - and self.presence == other.presence - and self.revision == other.revision - ) - - def __repr__(self) -> str: - return f"<{self.__class__.__name__}: {self.name!r}>" - - def __init__( - self, - name: str, - snap_id: Optional[str] = None, - presence: Optional[str] = None, - revision: Optional[str] = None, - ): - self.name = name - self.snap_id = snap_id - self.presence = presence - self.revision = revision - - -class BuildAssertion: - """Represent Validation Set assertion headers ready local signing.""" - - @classmethod - def unmarshal(cls, payload: Dict[str, Any]) -> "BuildAssertion": - jsonschema.validate(payload, BUILD_ASSERTION_JSONSCHEMA) - - return cls( - account_id=payload["account-id"], - authority_id=payload["authority-id"], - name=payload["name"], - revision=payload.get("revision", "0"), - sequence=payload["sequence"], - series=payload["series"], - snaps=[Snap.unmarshal(s) for s in payload["snaps"]], - timestamp=payload["timestamp"], - assertion_type=payload["type"], - ) - - def marshal(self) -> Dict[str, Any]: - return { - "account-id": self.account_id, - "authority-id": self.authority_id, - "name": self.name, - "revision": self.revision, - "sequence": self.sequence, - "snaps": [s.marshal() for s in self.snaps], - "timestamp": self.timestamp, - "type": self.assertion_type, - "series": self.series, - } - - def __eq__(self, other) -> bool: - if not isinstance(other, BuildAssertion): - return False - - return ( - self.name == other.name - and self.account_id == other.account_id - and self.authority_id == other.authority_id - and self.revision == other.revision - and self.sequence == other.sequence - and self.snaps == other.snaps - and self.timestamp == other.timestamp - and self.assertion_type == other.assertion_type - and self.series == other.series - ) - - def __repr__(self) -> str: - return f"<{self.__class__.__name__}: {self.name!r}>" - - def __init__( - self, - *, - account_id: str, - authority_id: str, - name: str, - revision: str, - sequence: str, - snaps: List[Snap], - timestamp: str, - assertion_type: str, - series: str = "16", - ): - self.account_id = account_id - self.authority_id = authority_id - self.name = name - self.revision = revision - self.sequence = sequence - self.snaps = snaps - self.series = series - self.timestamp = timestamp - self.assertion_type = assertion_type - - -class ValidationSets: - @classmethod - def unmarshal(cls, payload: Dict[str, Any]) -> "ValidationSets": - return cls( - assertions=[ - BuildAssertion.unmarshal(a["headers"]) - for a in payload.get("assertions", list()) - ] - ) - - def marshal(self) -> Dict[str, Any]: - return {"assertions": [{"headers": a.marshal()} for a in self.assertions]} - - def __repr__(self) -> str: - assertion_count = len(self.assertions) - return f"<{self.__class__.__name__}: assertions {assertion_count!r}>" - - def __init__(self, assertions: List[BuildAssertion]) -> None: - self.assertions = assertions + name: SnapName + """Snap name""" + + id: SnapId | None + """Snap ID""" + + presence: Literal["required", "optional", "invalid"] | None + """Snap presence""" + + revision: int | None + """Snap revision""" + + +class EditableBuildAssertion(models.CraftBaseModel): + """Subset of a build assertion that can be edited by the user. + + https://dashboard.snapcraft.io/docs/reference/v2/en/validation-sets.html#request-json-schema + """ + account_id: str + """The "account-id" assertion header""" + + name: str + """The "name" assertion header""" + + revision: str | None + """The "revision" assertion header""" + + sequence: int + """The "sequence" assertion header""" + + snaps: Annotated[list[Snap], pydantic.Field(min_items=1)] + """List of snaps in a Validation Set assertion""" + + def marshal_scalars_as_strings(self) -> Dict[str, Any]: + """Marshal the object where all scalars are represented as strings.""" + data = self.marshal() + return cast_dict_scalars_to_strings(data) + + +class BuildAssertion(EditableBuildAssertion): + """Full build assertion header for a Validation Set. + + https://dashboard.snapcraft.io/docs/reference/v2/en/validation-sets.html#response-json-schema + """ + authority_id: str + """The "authority-id" assertion header""" + + series: str + """The "series" assertion header""" + + sign_key_sha3_384: str | None + """Signing key ID.""" + + timestamp: str + """The "timestamp" assertion header""" + + type: Literal["validation-set"] + """The "type" assertion header""" + + @pydantic.root_validator(pre=True) + def remove_sign_key(cls, values): + """Accept but always ignore the sign key. + + The API can return and accept the sign key but the previous implementation + ignored it. + """ + values.pop("sign-key-sha3-384", None) + return values + + +class Headers(models.CraftBaseModel): + """Assertion headers for a validation set.""" + headers: BuildAssertion + """Assertion headers""" + + +class ValidationSets(models.CraftBaseModel): + """Validation sets. + + https://dashboard.snapcraft.io/docs/reference/v2/en/validation-sets.html#id4 + """ + assertions: list[Headers] + """List of validation-set assertions""" diff --git a/tests/legacy/unit/store/test_store_client.py b/tests/legacy/unit/store/test_store_client.py index ef89aa0eb1..3309bf13bf 100644 --- a/tests/legacy/unit/store/test_store_client.py +++ b/tests/legacy/unit/store/test_store_client.py @@ -438,7 +438,7 @@ def test_post_valid_assertion(self): self.assertThat(vs, IsInstance(validation_sets.ValidationSets)) self.assertThat(vs.assertions, HasLength(1)) - self.assertThat(vs.assertions[0], Equals(build_assertion)) + self.assertThat(vs.assertions[0].headers, Equals(build_assertion)) def test_post_invalid_assertion(self): build_assertion = self.client.post_validation_sets_build_assertion( @@ -516,7 +516,7 @@ def test_get_validation_sets_by_name(self): self.assertThat(vs, IsInstance(validation_sets.ValidationSets)) self.expectThat(vs.assertions, HasLength(1)) - self.expectThat(vs.assertions[0].name, Equals("acme-cert-2020-10")) + self.expectThat(vs.assertions[0].headers.name, Equals("acme-cert-2020-10")) def test_get_invalid_sequence(self): raised = self.assertRaises( diff --git a/tests/legacy/unit/store/v2/test_validation_sets.py b/tests/legacy/unit/store/v2/test_validation_sets.py index e63db9188f..2820efab48 100644 --- a/tests/legacy/unit/store/v2/test_validation_sets.py +++ b/tests/legacy/unit/store/v2/test_validation_sets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright (C) 2021 Canonical Ltd +# Copyright (C) 2021,2024 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 @@ -19,127 +19,136 @@ from snapcraft_legacy.storeapi.v2 import validation_sets -@pytest.mark.parametrize("snap_id", (None, "snap_id")) -@pytest.mark.parametrize("presence", (None, "required", "optional", "invalid")) -@pytest.mark.parametrize("revision", ("42", None)) -def test_snap(snap_id, presence, revision): - payload = { - "name": "set-1", +@pytest.fixture() +def fake_snap_data(): + return { + "name": "snap-name", + "id": "snap-id", + "presence": "required", + "revision": 42, } - if presence is not None: - payload["presence"] = presence - if snap_id is not None: - payload["id"] = snap_id +@pytest.fixture() +def fake_snap(fake_snap_data): + return validation_sets.Snap.unmarshal(fake_snap_data) - if revision is not None: - payload["revision"] = revision - s = validation_sets.Snap.unmarshal(payload) +@pytest.fixture() +def fake_editable_build_assertion_data(fake_snap_data): + return { + "account-id": "account-id-1", + "name": "validation-set-1", + "revision": "revision-1", + "sequence": 1, + "snaps": [fake_snap_data], + } + - assert repr(s) == "" - assert s.name == payload["name"] - assert s.snap_id == payload.get("id") - assert s.presence == payload.get("presence") - assert s.revision == payload.get("revision") - assert s.marshal() == payload +@pytest.fixture() +def fake_editable_build_assertion(fake_editable_build_assertion_data): + return validation_sets.EditableBuildAssertion.unmarshal( + fake_editable_build_assertion_data + ) -@pytest.mark.parametrize("revision", ("42", None)) -def test_build_assertion(revision): - payload = { +@pytest.fixture() +def fake_build_assertion_data(fake_snap_data): + return { "account-id": "account-id-1", - "authority-id": "authority-id-1", "name": "validation-set-1", - "sequence": "1", + "revision": "revision-1", + "sequence": 1, + "snaps": [fake_snap_data], + "authority-id": "authority-id-1", "series": "16", - "snaps": [validation_sets.Snap(name="snap-name").marshal()], + "sign-key-sha3-384": "sign-key-1", "timestamp": "2020-10-29T16:36:56Z", "type": "validation-set", } - if revision is not None: - payload["revision"] = revision - s = validation_sets.BuildAssertion.unmarshal(payload) - - assert repr(s) == "" - assert s.account_id == payload["account-id"] - assert s.authority_id == payload["authority-id"] - assert s.name == payload["name"] - assert s.sequence == payload.get("sequence") - assert s.series == payload.get("series") - assert s.snaps == [validation_sets.Snap(name="snap-name")] - assert s.timestamp == "2020-10-29T16:36:56Z" - - if revision is None: - payload["revision"] = "0" - - assert s.marshal() == payload - - -def test_validation_sets(): - payload = { - "assertions": [ - { - "headers": { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "1", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-1").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - } - }, +@pytest.fixture() +def fake_build_assertion(fake_build_assertion_data): + return validation_sets.BuildAssertion.unmarshal(fake_build_assertion_data) + + +@pytest.mark.parametrize( + ("input_dict", "expected"), + [ + pytest.param({}, {}, id="empty"), + pytest.param( + {False: False, True: True}, + {"False": "False", "True": "True"}, + id="boolean values", + ), + pytest.param( + {0: 0, None: None, "dict": {}, "list": [], "str": ""}, + ({"0": "0", None: None, "dict": {}, "list": [], "str": ""}), + id="none-like values", + ), + pytest.param( + {10: 10, 20.0: 20.0, "30": "30", True: True}, + {"10": "10", "20.0": "20.0", "30": "30", "True": "True"}, + id="scalar values", + ), + pytest.param( + {"foo": {"bar": [1, 2.0], "baz": {"qux": True}}}, + {"foo": {"bar": ["1", "2.0"], "baz": {"qux": "True"}}}, + id="nested data structures", + ), + ], +) +def test_cast_dict_scalars_to_strings(input_dict, expected): + actual = validation_sets.cast_dict_scalars_to_strings(input_dict) + + assert actual == expected + + +def test_ignore_sign_key(fake_build_assertion): + """Ignore the sign key when unmarshalling.""" + assert not fake_build_assertion.sign_key_sha3_384 + + +def test_editable_build_assertion_marshal_as_str(fake_editable_build_assertion): + """Cast all scalars to string when marshalling.""" + data = fake_editable_build_assertion.marshal_scalars_as_strings() + + assert data == { + "account-id": "account-id-1", + "name": "validation-set-1", + "revision": "revision-1", + "sequence": "1", + "snaps": [ { - "headers": { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "3", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-2").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - }, + "id": "snap-id", + "name": "snap-name", + "presence": "required", + "revision": "42", }, - ] + ], } - s = validation_sets.ValidationSets.unmarshal(payload) - - assert repr(s) == "" - assert s.assertions[0] == validation_sets.BuildAssertion.unmarshal( - { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "1", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-1").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - }, - ) - assert s.assertions[1] == validation_sets.BuildAssertion.unmarshal( - { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "3", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-2").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - } - ) +def test_build_assertion_marshal_as_str(fake_build_assertion): + """Cast all scalars to string when marshalling.""" + data = fake_build_assertion.marshal_scalars_as_strings() - assert s.marshal() == payload + assert data == { + "account-id": "account-id-1", + "authority-id": "authority-id-1", + "name": "validation-set-1", + "revision": "revision-1", + "sequence": "1", + "series": "16", + "snaps": [ + { + "id": "snap-id", + "name": "snap-name", + "presence": "required", + "revision": "42", + }, + ], + "timestamp": "2020-10-29T16:36:56Z", + "type": "validation-set", + } diff --git a/tests/unit/commands/test_validation_sets.py b/tests/unit/commands/test_validation_sets.py index f840a27c9f..1e69a278d1 100644 --- a/tests/unit/commands/test_validation_sets.py +++ b/tests/unit/commands/test_validation_sets.py @@ -32,63 +32,71 @@ # Fixtures # ############ +VALIDATION_SET_DATA = { + "assertions": [ + { + "headers": { + "account-id": "AccountIDXXXOfTheRequestingUserX", + "authority-id": "AccountIDXXXOfTheRequestingUserX", + "name": "certification-x1", + "revision": "222", + "sequence": "9", + "series": "16", + "sign-key-sha3-384": "XSignXKeyXHashXXXXXXXXXXX", + "snaps": [ + { + "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", + "name": "snap-name-1", + "presence": "optional", + "revision": "10", + }, + { + "id": "XXSnapIDForXSnapName2XXXXXXXXXXX", + "name": "snap-name-2", + }, + ], + "timestamp": "2020-10-29T16:36:56Z", + "type": "validation-set", + } + }, + ] +} + @pytest.fixture -def validation_sets_payload(): - return validation_sets.ValidationSets.unmarshal( - { - "assertions": [ - { - "headers": { - "account-id": "AccountIDXXXOfTheRequestingUserX", - "authority-id": "AccountIDXXXOfTheRequestingUserX", - "name": "certification-x1", - "revision": "222", - "sequence": "9", - "series": "16", - "sign-key-sha3-384": "XSignXKeyXHashXXXXXXXXXXX", - "snaps": [ - { - "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", - "name": "snap-name-1", - "presence": "optional", - }, - { - "id": "XXSnapIDForXSnapName2XXXXXXXXXXX", - "name": "snap-name-2", - }, - ], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - } - }, - ] - } +def fake_validation_sets(): + return validation_sets.ValidationSets.unmarshal(VALIDATION_SET_DATA) + + +@pytest.fixture() +def fake_build_assertion(): + return validation_sets.BuildAssertion.unmarshal( + VALIDATION_SET_DATA["assertions"][0]["headers"] ) @pytest.fixture -def fake_dashboard_post_validation_sets(validation_sets_payload, mocker): +def fake_dashboard_post_validation_sets(fake_validation_sets, mocker): return mocker.patch.object( - StoreClientCLI, "post_validation_sets", return_value=validation_sets_payload + StoreClientCLI, "post_validation_sets", return_value=fake_validation_sets ) @pytest.fixture -def fake_dashboard_post_validation_sets_build_assertion( - validation_sets_payload, mocker -): +def fake_dashboard_post_validation_sets_build_assertion(fake_validation_sets, mocker): return mocker.patch.object( StoreClientCLI, "post_validation_sets_build_assertion", - return_value=validation_sets_payload.assertions[0], + return_value=validation_sets.BuildAssertion.unmarshal( + VALIDATION_SET_DATA["assertions"][0]["headers"] + ), ) @pytest.fixture -def fake_dashboard_get_validation_sets(validation_sets_payload, mocker): +def fake_dashboard_get_validation_sets(fake_validation_sets, mocker): return mocker.patch.object( - StoreClientCLI, "get_validation_sets", return_value=validation_sets_payload + StoreClientCLI, "get_validation_sets", return_value=fake_validation_sets ) @@ -104,19 +112,22 @@ def sign(assertion: Dict[str, Any], *, key_name: str) -> bytes: @pytest.fixture def edit_return_value(): - return { - "account-id": "AccountIDXXXOfTheRequestingUserX", - "name": "certification-x1", - "sequence": "9", - "snaps": [ - { - "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", - "name": "snap-name-1", - "presence": "required", - }, - {"id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2"}, - ], - } + return validation_sets.EditableBuildAssertion.unmarshal( + { + "account-id": "AccountIDXXXOfTheRequestingUserX", + "name": "certification-x1", + "sequence": "9", + "snaps": [ + { + "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", + "name": "snap-name-1", + "presence": "required", + "revision": "10", + }, + {"id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2"}, + ], + } + ) @pytest.fixture @@ -137,12 +148,13 @@ def fake_edit_validation_sets(mocker, edit_return_value): validation_sets={ "account-id": "AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", - "sequence": "9", + "sequence": 9, "snaps": [ { "name": "snap-name-1", "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "required", + "revision": 10, }, {"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}, ], @@ -150,13 +162,12 @@ def fake_edit_validation_sets(mocker, edit_return_value): ) EXPECTED_SIGNED_VALIDATION_SETS = ( - '{{"account-id": "AccountIDXXXOfTheRequestingUserX", "authority-id": ' - '"AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", ' - '"revision": "222", "sequence": "9", "snaps": [{{"name": "snap-name-1", ' - '"id": "XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "optional"}}, ' + '{{"account-id": "AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", ' + '"revision": "222", "sequence": "9", "snaps": [{{"name": "snap-name-1", "id": ' + '"XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "optional", "revision": "10"}}, ' '{{"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}}], ' - '"timestamp": "2020-10-29T16:36:56Z", "type": "validation-set", ' - '"series": "16"}}\n\nSIGNED{key_name}' + '"authority-id": "AccountIDXXXOfTheRequestingUserX", "series": "16", "timestamp": ' + '"2020-10-29T16:36:56Z", "type": "validation-set"}}\n\nSIGNED{key_name}' ) @@ -176,8 +187,8 @@ def test_edit_validation_sets_with_no_changes_to_existing_set( emitter, ): # Make it look like there were no edits. - edit_return_value["sequence"] = 9 - edit_return_value["snaps"][0]["presence"] = "optional" + edit_return_value.sequence = 9 + edit_return_value.snaps[0].presence = "optional" fake_edit_validation_sets.return_value = edit_return_value cmd = commands.StoreEditValidationSetsCommand(None) @@ -203,7 +214,8 @@ def test_edit_validation_sets_with_no_changes_to_existing_set( @pytest.mark.usefixtures("memory_keyring", "fake_edit_validation_sets") @pytest.mark.parametrize("key_name", [None, "general", "main"]) def test_edit_validation_sets_with_changes_to_existing_set( - validation_sets_payload, + fake_validation_sets, + fake_build_assertion, fake_dashboard_get_validation_sets, fake_dashboard_post_validation_sets_build_assertion, fake_dashboard_post_validation_sets, @@ -232,11 +244,11 @@ def test_edit_validation_sets_with_changes_to_existing_set( signed_validation_sets=EXPECTED_SIGNED_VALIDATION_SETS.format( key_name=key_name ).encode() - ) + ), ] assert fake_snap_sign.mock_calls == [ call( - validation_sets_payload.assertions[0].marshal(), + fake_build_assertion.marshal_scalars_as_strings(), key_name=key_name, ) ] @@ -244,7 +256,8 @@ def test_edit_validation_sets_with_changes_to_existing_set( @pytest.mark.usefixtures("memory_keyring", "fake_edit_validation_sets") def test_edit_validation_sets_with_errors_to_amend( - validation_sets_payload, + fake_validation_sets, + fake_build_assertion, fake_dashboard_get_validation_sets, fake_dashboard_post_validation_sets_build_assertion, fake_dashboard_post_validation_sets, @@ -260,7 +273,7 @@ def test_edit_validation_sets_with_errors_to_amend( ).encode(), ) ), - validation_sets_payload.assertions[0], + fake_build_assertion, ] confirm_mock = mocker.patch("snapcraft.utils.confirm_with_user", return_value=True) @@ -291,7 +304,7 @@ def test_edit_validation_sets_with_errors_to_amend( ] assert fake_snap_sign.mock_calls == [ call( - validation_sets_payload.assertions[0].marshal(), + fake_build_assertion.marshal_scalars_as_strings(), key_name=None, ) ] @@ -300,7 +313,7 @@ def test_edit_validation_sets_with_errors_to_amend( @pytest.mark.usefixtures("memory_keyring", "fake_edit_validation_sets") def test_edit_validation_sets_with_errors_not_amended( - validation_sets_payload, + fake_validation_sets, fake_dashboard_get_validation_sets, fake_dashboard_post_validation_sets_build_assertion, fake_dashboard_post_validation_sets, @@ -344,20 +357,52 @@ def test_edit_validation_sets_with_errors_not_amended( def test_edit_yaml_error_retry(mocker, tmp_path, monkeypatch): monkeypatch.setenv("EDITOR", "faux-vi") + tmp_file = tmp_path / "validation_sets_template" + confirm_mock = mocker.patch("snapcraft.utils.confirm_with_user", return_value=True) + expected_edited_assertion = validation_sets.EditableBuildAssertion.unmarshal( + { + "account-id": "test", + "name": "test", + "sequence": 1, + "snaps": [{"name": "core22"}], + } + ) + good_yaml = "{'account-id': 'test', 'name': 'test', 'sequence': 1, 'snaps': [{'name': 'core22'}]}" + bad_yaml = f"badyaml}} {good_yaml}" + data_write = [good_yaml, bad_yaml] + + def side_effect(*args, **kwargs): + tmp_file.write_text(data_write.pop(), encoding="utf-8") + subprocess_mock = mocker.patch("subprocess.run", side_effect=side_effect) + + assert edit_validation_sets(tmp_file) == expected_edited_assertion + assert confirm_mock.mock_calls == [call("Do you wish to amend the validation set?")] + assert subprocess_mock.mock_calls == [call(["faux-vi", tmp_file], check=True)] * 2 + + +def test_edit_pydantic_error_retry(mocker, tmp_path, monkeypatch): + monkeypatch.setenv("EDITOR", "faux-vi") tmp_file = tmp_path / "validation_sets_template" confirm_mock = mocker.patch("snapcraft.utils.confirm_with_user", return_value=True) - data_write = [ - "{good: yaml}", - "{{bad yaml {{", - ] + expected_edited_assertion = validation_sets.EditableBuildAssertion.unmarshal( + { + "account-id": "test", + "name": "test", + "sequence": 1, + "snaps": [{"name": "core22"}], + } + ) + good_yaml = "{'account-id': 'test', 'name': 'test', 'sequence': 1, 'snaps': [{'name': 'core22'}]}" + bad_yaml = "{'invalid-field': 'test'}" + data_write = [good_yaml, bad_yaml] def side_effect(*args, **kwargs): tmp_file.write_text(data_write.pop(), encoding="utf-8") subprocess_mock = mocker.patch("subprocess.run", side_effect=side_effect) - assert edit_validation_sets(tmp_file) == {"good": "yaml"} + assert edit_validation_sets(tmp_file) == expected_edited_assertion assert confirm_mock.mock_calls == [call("Do you wish to amend the validation set?")] assert subprocess_mock.mock_calls == [call(["faux-vi", tmp_file], check=True)] * 2 From aa6d721bfc6080f064786594dfeea994aa74ccc1 Mon Sep 17 00:00:00 2001 From: Aristo Chen Date: Thu, 1 Aug 2024 00:22:12 +0800 Subject: [PATCH 007/151] fix(kernel_plugin): ignoring missing files when removing symlinks (#4915) Signed-off-by: Aristo Chen --- snapcraft_legacy/plugins/v2/_kernel_build.py | 2 +- tests/legacy/unit/plugins/v2/test_kernel.py | 2 +- tests/unit/parts/plugins/test_kernel.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/snapcraft_legacy/plugins/v2/_kernel_build.py b/snapcraft_legacy/plugins/v2/_kernel_build.py index e1fd3d80c9..48ec7243b2 100644 --- a/snapcraft_legacy/plugins/v2/_kernel_build.py +++ b/snapcraft_legacy/plugins/v2/_kernel_build.py @@ -1111,7 +1111,7 @@ def _arrange_install_dir_cmd(install_dir: str) -> List[str]: # but snapd expects modules/ and firmware/ mv {install_dir}/lib/modules {install_dir}/ # remove symlinks modules/*/build and modules/*/source - rm {install_dir}/modules/*/build {install_dir}/modules/*/source + rm -f {install_dir}/modules/*/build {install_dir}/modules/*/source # if there is firmware dir, move it to snap root # this could have been from stage packages or from kernel build [ -d {install_dir}/lib/firmware ] && mv {install_dir}/lib/firmware {install_dir} diff --git a/tests/legacy/unit/plugins/v2/test_kernel.py b/tests/legacy/unit/plugins/v2/test_kernel.py index 939316c14b..0b7729ad8c 100644 --- a/tests/legacy/unit/plugins/v2/test_kernel.py +++ b/tests/legacy/unit/plugins/v2/test_kernel.py @@ -1768,7 +1768,7 @@ def _is_sub_array(array, sub_array): # but snapd expects modules/ and firmware/ mv ${SNAPCRAFT_PART_INSTALL}/lib/modules ${SNAPCRAFT_PART_INSTALL}/ # remove symlinks modules/*/build and modules/*/source - rm ${SNAPCRAFT_PART_INSTALL}/modules/*/build ${SNAPCRAFT_PART_INSTALL}/modules/*/source + rm -f ${SNAPCRAFT_PART_INSTALL}/modules/*/build ${SNAPCRAFT_PART_INSTALL}/modules/*/source # if there is firmware dir, move it to snap root # this could have been from stage packages or from kernel build [ -d ${SNAPCRAFT_PART_INSTALL}/lib/firmware ] && mv ${SNAPCRAFT_PART_INSTALL}/lib/firmware ${SNAPCRAFT_PART_INSTALL} diff --git a/tests/unit/parts/plugins/test_kernel.py b/tests/unit/parts/plugins/test_kernel.py index 0bc4a68166..09db04137b 100644 --- a/tests/unit/parts/plugins/test_kernel.py +++ b/tests/unit/parts/plugins/test_kernel.py @@ -1919,7 +1919,7 @@ def _is_sub_array(array, sub_array): # but snapd expects modules/ and firmware/ mv ${CRAFT_PART_INSTALL}/lib/modules ${CRAFT_PART_INSTALL}/ # remove symlinks modules/*/build and modules/*/source - rm ${CRAFT_PART_INSTALL}/modules/*/build ${CRAFT_PART_INSTALL}/modules/*/source + rm -f ${CRAFT_PART_INSTALL}/modules/*/build ${CRAFT_PART_INSTALL}/modules/*/source # if there is firmware dir, move it to snap root # this could have been from stage packages or from kernel build [ -d ${CRAFT_PART_INSTALL}/lib/firmware ] && mv ${CRAFT_PART_INSTALL}/lib/firmware ${CRAFT_PART_INSTALL} From 8f2afd58a985ae5cfd7538a1989963aa3b7347df Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 31 Jul 2024 17:00:59 -0500 Subject: [PATCH 008/151] build(deps): use the ruff snap (#4939) - use the new `ruff check` call - compatibility updates for ruff 0.5.4 - documentation changes Signed-off-by: Callahan Kovacs --- HACKING.md | 12 ++++++++++++ snapcraft/commands/remote.py | 6 ++++-- tests/unit/meta/test_appstream.py | 2 +- tox.ini | 4 ++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/HACKING.md b/HACKING.md index a7b33fe922..44622ea88d 100644 --- a/HACKING.md +++ b/HACKING.md @@ -34,6 +34,18 @@ lxc exec snapcraft-dev -- sudo -iu ubuntu bash Import your keys (`ssh-import-id`) and add a `Host` entry to your ssh config if you are interested in [Code's](https://snapcraft.io/code) [Remote-SSH](https://code.visualstudio.com/docs/remote/ssh) plugin. +### Tooling + +We use a large number of tools for our project. Most of these are installed for +you with tox, but you'll need to install: + +- Python 3.10 (default on Ubuntu 22.04, available on Ubuntu 24.04 through the + [deadsnakes](https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa) PPA) with setuptools. +- [tox](https://tox.wiki) version 3.8 or later +- [pyright](https://github.com/microsoft/pyright) (also available via snap: `snap install pyright`) +- [ruff](https://github.com/astral/ruff) (also available via snap: `snap install ruff`) +- [ShellCheck](https://www.shellcheck.net/) (also available via snap: `snap install shellcheck`) + ### Testing See the [Testing guide](TESTING.md). diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index a5c6d2be8a..4ee6b70261 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -147,7 +147,9 @@ def _validate(self, parsed_args: argparse.Namespace) -> None: retcode=77, ) - def _run(self, parsed_args: argparse.Namespace, **kwargs: Any) -> int | None: + def _run( # noqa: PLR0915 [too-many-statements] + self, parsed_args: argparse.Namespace, **kwargs: Any + ) -> int | None: """Run the remote-build command. :param parsed_args: Snapcraft's argument namespace. @@ -237,7 +239,7 @@ def _run(self, parsed_args: argparse.Namespace, **kwargs: Any) -> int | None: emit.progress("Cancelling builds.") builder.cancel_builds() returncode = 0 - except Exception: + except Exception: # noqa: BLE001 [blind-except] returncode = 1 # General error on any other exception if returncode != 75: # TimeoutError emit.progress("Cleaning up") diff --git a/tests/unit/meta/test_appstream.py b/tests/unit/meta/test_appstream.py index a7d6bcfc17..788640177c 100644 --- a/tests/unit/meta/test_appstream.py +++ b/tests/unit/meta/test_appstream.py @@ -801,7 +801,7 @@ def test_appstream_with_malformed_links(self): assert metadata.contact == ["rrroschan@gmail.com"] assert metadata.donation == ["https://www.buymeacoffee.com/alainm23"] assert metadata.website == ["https://hello.com"] - assert metadata.source_code == None + assert metadata.source_code is None assert metadata.issues == ["https://github.com/alainm23/planify/issues"] def test_appstream_parse_error(self): diff --git a/tox.ini b/tox.ini index a18d9fe950..bba9fe6575 100644 --- a/tox.ini +++ b/tox.ini @@ -143,8 +143,8 @@ allowlist_externals: ruff: ruff commands = black: black {tty:--color} {posargs} . - ruff: ruff --fix --respect-gitignore setup.py snapcraft tests tools - ruff: ruff --fix --config snapcraft_legacy/ruff.toml snapcraft_legacy tests/legacy + ruff: ruff check --fix --respect-gitignore setup.py snapcraft tests tools + ruff: ruff check --fix --config snapcraft_legacy/ruff.toml snapcraft_legacy tests/legacy codespell: codespell --toml {tox_root}/pyproject.toml --write-changes {posargs} [docs] # Sphinx documentation configuration From ac56253a057cf8ba6bed3268acd3774468243bb7 Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 31 Jul 2024 18:50:45 -0500 Subject: [PATCH 009/151] docs(changelog): add changelog notes for snapcraft 8.0.0-8.3.1 (#4947) Add changelog entries for all existing 8.x releases into readthedocs. Signed-off-by: Callahan Kovacs --- docs/howto/bases.rst | 1 + docs/reference/changelog.rst | 989 +++++++++++++++++++++++++++++++++++ docs/reference/index.rst | 1 + 3 files changed, 991 insertions(+) create mode 100644 docs/reference/changelog.rst diff --git a/docs/howto/bases.rst b/docs/howto/bases.rst index 75da3750c0..ab987d94ec 100644 --- a/docs/howto/bases.rst +++ b/docs/howto/bases.rst @@ -20,6 +20,7 @@ How to migrate to a newer base See `migrating bases`_ for details on migrating to a newer base. +.. _howto-deprecated-base: How to use a deprecated base ---------------------------- diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst new file mode 100644 index 0000000000..c64516bea5 --- /dev/null +++ b/docs/reference/changelog.rst @@ -0,0 +1,989 @@ +Changelog +********* + +.. + release template: + + X.Y.Z (YYYY-MMM-DD) + ------------------- + + Core + ==== + + # for everything related to the lifecycle of packing a snap + + Bases + ##### + + + """""""" + (order from newest base to oldest base) + + Plugins + ####### + + + """""""" + + List plugins + """""""""""" + + Extensions + ########## + + + """"""""""" + + Expand extensions + """"""""""""""""" + + List extensions + """"""""""""""" + + Metadata + ######## + + Sources + ####### + + Components + ########## + + Command line + ============ + + # for command line and UX changes + + Linter + ====== + + Init + ==== + + Metrics + ======= + + Names + ===== + + Remote build + ============ + + Store + ===== + + Documentation + ============= + + For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. + +8.3.1 (2024-Jul-08) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Support ``all`` as a target with ``build-for: [all]`` (`#4854`_). + +* Ensure Craft Providers provider (LXD or Multipass) is available before + launching a build environment. + +* Improve presentation of ``snapcraft.yaml`` model errors. + +Metadata +######## + +* Validate that ``update_contact``, ``donation``, ``vcs-browser``, + ``bugtracker``, and ``homepage`` fields adopted from an appstream metadata + file are valid URLs or email addresses. + +* Ensure that ``contact``, ``donation``, ``source-code``, ``issues``, and + ``website`` fields in a snapcraft.yaml take priority over appstream metadata + (`#4890`_). + +Remote build +============ + +* Require ``core20`` snaps to use the legacy remote builder (`#4886`_). + +* Allow building ``core22`` snaps with ``build-for: [all]``. + +* Support reading Launchpad credentials from the previous location (`#4889`_). + If launchpad credentials do not exist in the new location + (``$XDG_DATA_DIR/snapcraft/launchpad-credentials``) introduced in ``8.2.0``, + then load credentials from the previous location + (``$XDG_DATA_DIR/snapcraft/provider/launchpad/credentials``) and emit a + deprecation notice. + +.. note:: + + This behavior applies to the new and legacy remote builders. + +Documentation +============= + +* Add an :doc:`explanation` for the remote builders + (`#4842`_). + +* Update :doc:`reference`, + :doc:`how-to`, and + :doc:`explanation` for platforms and + architectures. + +For a complete list of commits, check out the `8.3.1`_ release on GitHub. + + +8.3.0 (2024-Jun-27) +------------------- + +Core +==== + +* Improve logging to show which package is being fetched. + +* Add support for parts to source ``7z`` archives. + +* Improve error messages when sources cannot be fetched. + +Bases +##### + +core24 +"""""" + +* Add support for ``core24-desktop`` snaps (`#4818`_). + +core22 +"""""" + +* Warn when multiple snaps are going to be built in destructive mode because + it may cause unexpected behavior (`#4685`_, `#4356`_). + +* Fix a regression where ``core22-desktop`` could not be built (`#4818`_). + +Plugins +####### + +Flutter +""""""" + +* Add ``curl`` as a ``build-package`` for ``flutter`` parts (`#4804`_). + +ROS 2 Jazzy +""""""""""" + +* Add support for the new ROS 2 Jazzy extension which lets you snap ROS 2 + applications on ``core24`` (`#4791`_). + +* Similar to ROS 2 Humble for ``core22``, content-sharing is supported + (`#4828`_). + +For more information, see https://snapcraft.io/docs/ros2-jazzy-extension and +https://snapcraft.io/docs/ros2-jazzy-content-extension. + +NPM +""" + +Various improvements for the ``core22`` and ``core24`` NPM plugins: + +* Accept NVM-style version identifiers for ``npm-node-version``. + +* Verify SHA256 checksums after node.js download + (`canonical/craft-parts#717`_). + +* Use new-style ``npm-install`` commands if the npm version is newer than + ``8.x``. + +* Set ``NODE_ENV`` to ``production`` by default. + +List plugins +"""""""""""" + +* Fix a bug where ``snapcraft list-plugins`` would fail to run in a ``core24`` + project directory (`#4830`_). + +* Update ``snapcraft list-plugins`` to show a list of ``core24`` plugins + instead of ``core22`` plugins when not in a project directory (`#4830`_). + +Extensions +########## + +Gnome +""""" + +* Make gnome extension stable for ``core24``. + +* Fix ``GI_TYPELIB_PATH`` and ``XDG_DATA_DIRS`` paths in the build environment + (`#4798`_). + +* Integrate with the ``gpu-2404`` SDK (`#4744`_). + +For more information, see the `gpu 2404 interface docs`_. + +KDE Neon 6 +"""""""""" + +* Fix paths to ``QtWebEngineProcess`` in the desktop launcher (`#4745`_). + +Expand extensions +""""""""""""""""" + +* Fix a bug where ``snapcraft expand-extensions`` could not parse a + ``snapcraft.yaml`` file containing the ``platforms`` keyword. + +Components +########## + +* Include the ``provenance`` keyword in a component's metadata from a + ``snapcraft.yaml`` file (`#4827`_). + +Metadata +######## + +Add support for adopting more metadata fields from a project's appstream file: + +* ``license`` +* ``contact`` +* ``source-code`` +* ``issues`` +* ``websites`` +* ``donations`` + +Metrics +####### + +* Add support for ``snapcraft metrics`` to retrieve the metrics + ``installed_base_by_architecture`` and + ``weekly_installed_base_by_architecture`` (`#4735`_). + +Names +##### + +* Add output formatting to ``snapcraft names`` with ``--format``. Supported + formats are ``table`` and ``json`` (`#4778`_). + +Init +#### + +* Update ``snapcraft init`` to create a ``core24`` project instead of a + ``core22`` project (`#4830`_) + +Documentation +############# + +* Update Snapcraft's documentation to use the `canonical-sphinx`_ theme. + +Add reference documentation for more plugins (`#4811`_): + +* ``ant`` +* ``autotools`` +* ``cmake`` +* ``dotnet`` +* ``go`` +* ``make`` +* ``meson`` +* ``nil`` +* ``npm`` +* ``qmake`` +* ``scons`` + +For a complete list of commits, check out the `8.3.0`_ release on GitHub. + +8.2.12 (2024-Jun-12) +-------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a bug where snaps would stage Python packages already included in the + ``core24`` base snap (`#4865`_). + +Store +===== + +* Fix a bug where store-related error messages would be presented as an + internal Snapcraft error. + +* Add a resolution and link to documentation for keyring errors. + +Documentation +============= + +* Fix Snapcraft's version in the readthedocs documentation. + +For a complete list of commits, check out the `8.2.12`_ release on GitHub. + +8.2.11 (2024-Jun-12) +-------------------- + +Core +==== + +Plugins +####### + +Dotnet +"""""" + +* Fix a regression where the ``dotnet`` plugin could not be used for + ``core22`` snaps (`#4825`_). + +For a complete list of commits, check out the `8.2.11`_ release on GitHub. + +8.2.10 (2024-Jun-03) +-------------------- + +Remote builder +============== + +* Fix a bug where comma-separated architectures in ``--build-for`` could not + be parsed (`#4780`_). + +* Fix a bug where ``core22`` snaps with a top level ``architectures`` keyword + could not be parsed (`#4780`_). + +* Fix a bug where remote build log files were incorrectly named (`#4781`_). + +* Retry more API calls to Launchpad (`canonical/craft-application#355`_). + +* Add an exponential backoff to API retries with a maximum total delay of + 62 seconds (`canonical/craft-application#355`_). + +* Fix a bug where the remote builder would not fail if no artefacts were + created (`#4783`_). + +For a complete list of commits, check out the `8.2.10`_ release on GitHub. + +8.2.9 (2024-May-28) +------------------- + +Core +==== + +Extensions +########## + +KDE Neon 6 +"""""""""" + +* Fix multiple issues to allow web processes to work correctly (`#4823`_). + +* Expose the ``libplas`` and ``liblapack`` provided by the ``kf6-core22{-sdk}`` + snaps (`#4823`_). + +For a complete list of commits, check out the `8.2.9`_ release on GitHub. + +8.2.8 (2024-May-17) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a behavior where shared libraries from the host were loaded for + classically confined snaps. + +.. note:: + + This is implemented with ``patchelf --no-default-lib`` when + ``enable-patchelf`` is defined. + +Plugins +####### + +Dotnet +"""""" + +* Disable the ``dotnet`` plugin for ``core24`` snaps due to a pending rewrite. + +For a complete list of commits, check out the `8.2.8`_ release on GitHub. + +8.2.7 (2024-May-09) +------------------- + +Core +==== + +* Add support for ``ignore-running`` in ``apps..refresh-mode`` in a + ``snapcraft.yaml`` file (`#4747`_). + +Remote build +============ + +* Fix a regression where remote build would fail to parse some + ``architectures`` definitions (`#4780`_). + +For a complete list of commits, check out the `8.2.7`_ release on GitHub. + +8.2.6 (2024-May-09) +------------------- + +Core +==== + +* Fix a regression where a directory could not be packaged as a snap + (`#4769`_). + +For a complete list of commits, check out the `8.2.6`_ release on GitHub. + +8.2.5 (2024-May-07) +------------------- + +Store +===== + +* Fix the same ``cryptography`` regression addressed in ``8.2.4`` but for + store-related operations. + +For a complete list of commits, check out the `8.2.5`_ release on GitHub. + +8.2.4 (2024-May-05) +------------------- + +* Fix a regression where Snapcraft would fail to run on some architectures due + to a ``cryptography`` dependency that attempted to load legacy algorithms + (`LP#2064639`_). + +For a complete list of commits, check out the `8.2.4`_ release on GitHub. + +8.2.3 (2024-May-01) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a bug where project variables were evaluated before extensions were + applied (`#4771`_). + +* Fix a bug where ``build-for`` project variables were evaluated based on the + host architecture (`#4770`_). + +For a complete list of commits, check out the `8.2.3`_ release on GitHub. + +8.2.2 (2024-Apr-30) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a bug where advanced grammar could not be combined with other data + (`#4764`_, `LP#2061603`_). + +For a complete list of commits, check out the `8.2.2`_ release on GitHub. + +8.2.1 (2024-Apr-25) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a bug where `project variables`_ were not evaluated inside a + ``snapcraft.yaml`` file and were not available as environment variables in + the build environment. + +* Fix a bug where `advanced grammar`_ was not evaluated in root-level part + keywords ``build-packages`` and ``build-snaps``. + +* Fix a bug where local key assets in ``snap/keys/`` were not used when + installing package repositories. + +Remote build +============ + +* Fix a bug where ``core24`` snaps could not use package repositories + because ``gpg`` and ``dirmngr`` were not installed in the remote build + environment. + +For a complete list of commits, check out the `8.2.1`_ release on GitHub. + +8.2.0 (2024-Apr-17) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Drop requirement for ``build-base: devel`` for ``core24`` snaps. + +core22 +"""""" + +* Extend `advanced grammar`_ for all part keywords except plugin-specific + keywords. + +Remote build +============ + +* Migrate to the upstream remote builder in `Craft Application`_. + +* Allow only one remote build is allowed per project. + +* Remove support for ``build-id`` with ``snapcraft remote-build --recover``. + +* Remove support for deprecated ``--build-on`` argument in favor of + ``--build-for``. + +* Move Launchpad credentials file from + ``$XDG_DATA_DIR/snapcraft/provider/launchpad/credentials`` + to ``$XDG_DATA_DIR/snapcraft/launchpad-credentials``. + +* Fail if snapcraft is in a shallowly-cloned git repository instead of falling + back to the legacy remote builder. + +.. note:: + + Reminder: Legacy remote-build behavior can be used for bases core22 and older + with the environment variable + ``SNAPCRAFT_REMOTE_BUILD_STRATEGY="force-fallback"``. See more information in + the :doc:`remote build` documentation. + +For a complete list of commits, check out the `8.2.0`_ release on GitHub. + +8.1.0 (2024-Apr-10) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Finalize internal refactor to use `Craft Application`_ to build ``core24`` + snaps. + +For more information on deprecations and changes, see the `core24 migration +guide`_. + +Plugins +####### + +Matter SDK +"""""""""" + +* Add new Matter SDK plugin for ``core22``. + +For more information, see the `Matter`_ website and the `Matter on Ubuntu`_ +docs. + +Maven +""""" + +* Add support for the Maven plugin for ``core22`` snaps. + +For more information, see :doc:`/reference/plugins/maven_plugin`. + +QMake +""""" + +* Add support for the QMake plugin for ``core22`` snaps. + +For more information, see https://snapcraft.io/docs/qmake-plugin. + +Colcon +"""""" + +* Set build type to ``RELEASE`` if it is not defined by ``colcon_cmake_args: + ["-DCMAKE_BUILD_TYPE="]``). + +Extensions +########## + +KDE Neon 6 +"""""""""" + +* Add new ``kde-neon-6`` extension for ``core22`` snaps that use Qt6 or the + KDE Neon 6 framework. + +Components +########## + +* Add support for creating components. + +* Components are parts of a snap that can be built and uploaded in + conjunction with a snap and later optionally installed beside it. + +For more information, see the :doc:`reference`, +:doc:`explanation`, and +:doc:`how-to` documentation pages. + +Remote build +============ + +* Add support for user-defined Launchpad projects projects, including + private projects. + +* This is configured via ``snapcraft remote-build --project ``. + +For a complete list of commits, check out the `8.1.0`_ release on GitHub. + +8.0.5 (2024-Mar-18) +------------------- + +Core +==== + +* Fix a bug where LXD versions with an "LTS" suffix could not be parsed. + +For a complete list of commits, check out the `8.0.5`_ release on GitHub. + +8.0.4 (2024-Mar-04) +------------------- + +Core +==== + +Bases +##### + +* Fix a bug where ``devel`` bases may not be fully validated. + +* Bump the LXD compatibility tag to ``v7``. + +core24 +"""""" + +* Use ``buildd`` daily images instead of ``ubuntu`` images for ``core24`` + bases and ``build-base: devel``. + +* Fix a bug where creating ``core24`` base images would fail because ``apt`` + would install packages interactively. + +For a complete list of commits, check out the `8.0.4`_ release on GitHub. + +8.0.3 (2024-Feb-09) +------------------- + +Core +==== + +* Add a warning that when a part uses ``override-prime`` it cannot use + ``enable-patchelf`` (`#4547`_). + +Bases +##### + +* Bump the LXD compatibility tag to ``v6``. + +* Stop updating ``apt`` source config files when ``build-base: devel`` + is defined. + +core24 +"""""" + +* Use the ``core24`` alias instead of the ``devel`` alias when retrieving LXD + images. + +Plugins +####### + +Ant +""" + +* Use the proxy environment variables ``http_proxy`` and ``https_proxy``. + +Remote build +============ + +* Fix a bug where ``--build-for`` and ``--build-on`` were not mutually + exclusive options. + +* Improve error messages and provide links to documentation when remote builds + fail (`#4517`_). + +* Fix a regression where comma-separated architectures in ``--build-on`` and + ``--build-for`` were not accepted (`#4516`_). + +For a complete list of commits, check out the `8.0.3`_ release on GitHub. + +8.0.2 (2024-Jan-23) +------------------- + +Core +==== + +* Fix a bug where Snapcraft fails to run on platforms where ``SSL_CERT_DIR`` is + not set (`#4510`_, `#4520`_). + +* Fix a decoding bug when logging malformed output from other processes, + typically during the ``build`` step (`#4515`_). + +For a complete list of commits, check out the `8.0.2`_ release on GitHub. + +8.0.1 (2024-Jan-03) +------------------- + +Remote build +============ + + +* Fix a bug where Snapcraft would not fail if the Launchpad build itself failed + for new and legacy remote builders (`#4142`_). + +* Fix a bug where large repos could not be pushed with the new remote builder + (`#4478`_). + +* Fallback to the legacy remote builder if the project is shallowly cloned + (`#4479`_). + +For a complete list of commits, check out the `8.0.1`_ release on GitHub. + +8.0.0 (2023-Dec-04) +------------------- + +Core +==== + +Bases +##### + +core22 +"""""" + +Add new environment variables for ``build-on`` and ``build-for`` architectures: + +* ``CRAFT_ARCH_TRIPLET_BUILD_FOR``, supersedes ``CRAFT_ARCH_TRIPLET`` +* ``CRAFT_ARCH_TRIPLET_BUILD_ON`` +* ``CRAFT_ARCH_BUILD_FOR``, supersedes ``CRAFT_TARGET_ARCH`` +* ``CRAFT_ARCH_BUILD_ON`` + +For more information, see :doc:`/reference/architectures`. + +core20 +"""""" + +Add new environment variables for ``build-on`` and ``build-for`` architectures: + +* ``SNAPCRAFT_ARCH_TRIPLET_BUILD_FOR``, supersedes ``SNAPCRAFT_ARCH_TRIPLET`` +* ``SNAPCRAFT_ARCH_TRIPLET_BUILD_ON`` +* ``SNAPCRAFT_ARCH_BUILD_FOR``, supersedes ``SNAPCRAFT_TARGET_ARCH`` +* ``SNAPCRAFT_ARCH_BUILD_ON`` + +For more information, see :doc:`/reference/architectures`. + +core18 +"""""" + +* Deprecate building snaps using the ``core18`` base. + +For more information on how to continue building snaps with the ``core18`` +base, see :ref:`this page`. + +Stage packages +############## + +* Support chiseled ``stage-packages``. This is useful for reducing the size of + the snap when creating :ref:`base snaps` or using a bare + base. + +For more information about chisel, see https://github.com/canonical/chisel + +Plugins +####### + +Rust +"""" + +* Use default rust toolchain with ``rustup``. + +* Add option ``rust-ignore-toolchain-file``. + +* Add option ``rust-inherit-ldflags``. + +* Add list ``rust-cargo-parameters``. + +For more information about the new options, see +:doc:`/common/craft-parts/reference/plugins/rust_plugin`. + +Kernel +"""""" + +* Generate kernel configs for Ubuntu 22.04 (Jammy). + +Python +"""""" + +* Add support for Python projects driven by a ``pyproject.toml``. + +For more information, see the `PEP 518`_ spec. + +ROS 2 +""""" + +* Add support for content sharing for core20 & core22 bases (ROS Noetic, Foxy, + Humble) and the ``colcon``, ``catkin``, and ``catkin-tools`` plugins + +For more information on ROS architecture, see the `ROS architectures with +snaps`_. + +More information on content-sharing, see: + +* https://snapcraft.io/docs/ros2-humble-content-extension +* https://snapcraft.io/docs/ros2-foxy-content-extension +* https://snapcraft.io/docs/ros-noetic-content-extension + +Command line +============ + +* Stream messages in the default ``brief`` mode + +* Improve presentation of build step prefixes + +Linter +====== + +* Suggest packages to add to ``stage-packages`` to satisfy a potential missing + library. + +Remote build +============ + +Introduce a new remote-builder for ``core24`` snaps: + +* Does not modify the project's ``snapcraft.yaml`` + +* Does not fetch and tarball remote sources before sending the project + to Launchpad + +* Require projects to be in the top-level of a fully-cloned (non-shallow) git + repository + +* Allow switching between the new and legacy remote builders with + the environment variable ``SNAPCRAFT_REMOTE_BUILD_STRATEGY``. + +For more information on the new remote-builder, how to switch between the +new and legacy remote builders, see :doc:`/explanation/remote-build`. + +Store +===== + +* Add a fallback to a file-based keyring when the system keyring cannot be + initialized, is not fully configured, or is otherwise not available. + +For more information on the file-based keyring, see +https://snapcraft.io/docs/snapcraft-authentication. + +For a complete list of commits, check out the `8.0.0`_ release on GitHub. + +.. _advanced grammar: https://snapcraft.io/docs/snapcraft-advanced-grammar +.. _canonical-sphinx: https://github.com/canonical/canonical-sphinx +.. _core24 migration guide: https://snapcraft.io/docs/migrate-core24 +.. _Craft Application: https://github.com/canonical/craft-application +.. _gpu 2404 interface docs: https://mir-server.io/docs/the-gpu-2404-snap-interface#heading--consuming-the-interface +.. _Matter: https://csa-iot.org/all-solutions/matter/ +.. _Matter on Ubuntu: https://canonical-matter.readthedocs-hosted.com/en/latest/ +.. _project variables: https://snapcraft.io/docs/parts-environment-variables +.. _Releases page: https://github.com/canonical/snapcraft/releases +.. _PEP 518: https://peps.python.org/pep-0518/ +.. _ROS architectures with snaps: https://ubuntu.com/robotics/docs/ros-architectures-with-snaps. + +.. _canonical/craft-application#355: https://github.com/canonical/craft-application/pull/355 +.. _canonical/craft-parts#717: https://github.com/canonical/craft-parts/issues/717 + +.. _LP#2061603: https://bugs.launchpad.net/snapcraft/+bug/2061603 +.. _LP#2064639: https://bugs.launchpad.net/snapcraft/+bug/2064639 + +.. _#4142: https://github.com/canonical/snapcraft/issues/4142 +.. _#4356: https://github.com/canonical/snapcraft/issues/4356 +.. _#4478: https://github.com/canonical/snapcraft/issues/4478 +.. _#4479: https://github.com/canonical/snapcraft/issues/4479 +.. _#4510: https://github.com/canonical/snapcraft/issues/4510 +.. _#4515: https://github.com/canonical/snapcraft/issues/4515 +.. _#4516: https://github.com/canonical/snapcraft/issues/4516 +.. _#4517: https://github.com/canonical/snapcraft/issues/4517 +.. _#4520: https://github.com/canonical/snapcraft/issues/4520 +.. _#4547: https://github.com/canonical/snapcraft/issues/4547 +.. _#4685: https://github.com/canonical/snapcraft/issues/4685 +.. _#4735: https://github.com/canonical/snapcraft/issues/4735 +.. _#4744: https://github.com/canonical/snapcraft/issues/4744 +.. _#4745: https://github.com/canonical/snapcraft/issues/4745 +.. _#4747: https://github.com/canonical/snapcraft/issues/4747 +.. _#4764: https://github.com/canonical/snapcraft/issues/4764 +.. _#4769: https://github.com/canonical/snapcraft/issues/4769 +.. _#4770: https://github.com/canonical/snapcraft/issues/4770 +.. _#4771: https://github.com/canonical/snapcraft/issues/4771 +.. _#4778: https://github.com/canonical/snapcraft/issues/4778 +.. _#4780: https://github.com/canonical/snapcraft/issues/4780 +.. _#4781: https://github.com/canonical/snapcraft/issues/4781 +.. _#4783: https://github.com/canonical/snapcraft/issues/4783 +.. _#4791: https://github.com/canonical/snapcraft/issues/4791 +.. _#4798: https://github.com/canonical/snapcraft/issues/4798 +.. _#4804: https://github.com/canonical/snapcraft/issues/4804 +.. _#4811: https://github.com/canonical/snapcraft/issues/4811 +.. _#4818: https://github.com/canonical/snapcraft/issues/4818 +.. _#4823: https://github.com/canonical/snapcraft/pull/4823 +.. _#4825: https://github.com/canonical/snapcraft/issues/4825 +.. _#4827: https://github.com/canonical/snapcraft/issues/4827 +.. _#4828: https://github.com/canonical/snapcraft/issues/4828 +.. _#4830: https://github.com/canonical/snapcraft/issues/4830 +.. _#4842: https://github.com/canonical/snapcraft/issues/4842 +.. _#4854: https://github.com/canonical/snapcraft/issues/4854 +.. _#4865: https://github.com/canonical/snapcraft/issues/4865 +.. _#4886: https://github.com/canonical/snapcraft/issues/4886 +.. _#4889: https://github.com/canonical/snapcraft/issues/4889 +.. _#4890: https://github.com/canonical/snapcraft/issues/4890 + +.. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 +.. _8.0.1: https://github.com/canonical/snapcraft/releases/tag/8.0.1 +.. _8.0.2: https://github.com/canonical/snapcraft/releases/tag/8.0.2 +.. _8.0.3: https://github.com/canonical/snapcraft/releases/tag/8.0.3 +.. _8.0.4: https://github.com/canonical/snapcraft/releases/tag/8.0.4 +.. _8.0.5: https://github.com/canonical/snapcraft/releases/tag/8.0.5 +.. _8.1.0: https://github.com/canonical/snapcraft/releases/tag/8.1.0 +.. _8.2.0: https://github.com/canonical/snapcraft/releases/tag/8.2.0 +.. _8.2.1: https://github.com/canonical/snapcraft/releases/tag/8.2.1 +.. _8.2.2: https://github.com/canonical/snapcraft/releases/tag/8.2.2 +.. _8.2.3: https://github.com/canonical/snapcraft/releases/tag/8.2.3 +.. _8.2.4: https://github.com/canonical/snapcraft/releases/tag/8.2.4 +.. _8.2.5: https://github.com/canonical/snapcraft/releases/tag/8.2.5 +.. _8.2.6: https://github.com/canonical/snapcraft/releases/tag/8.2.6 +.. _8.2.7: https://github.com/canonical/snapcraft/releases/tag/8.2.7 +.. _8.2.8: https://github.com/canonical/snapcraft/releases/tag/8.2.8 +.. _8.2.9: https://github.com/canonical/snapcraft/releases/tag/8.2.9 +.. _8.2.10: https://github.com/canonical/snapcraft/releases/tag/8.2.10 +.. _8.2.11: https://github.com/canonical/snapcraft/releases/tag/8.2.11 +.. _8.2.12: https://github.com/canonical/snapcraft/releases/tag/8.2.12 +.. _8.3.0: https://github.com/canonical/snapcraft/releases/tag/8.3.0 +.. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 diff --git a/docs/reference/index.rst b/docs/reference/index.rst index c534849d54..c765436735 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -8,6 +8,7 @@ Reference architectures bases + changelog commands components plugins From a689aba5ef4cc4cbbd0c9b9a76be401dd140b72e Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 16 Jul 2024 13:56:22 -0500 Subject: [PATCH 010/151] tests: disable `build-base: devel` spread tests (#4922) Disable `build-base: devel` spread tests until we have the resources to debug the underlying issue with 24.10 buildd images and `systemd-resolved` (#4921). We should be able to address this issue in 2024-Nov. Fixes #4910 (CRAFT-3105) Signed-off-by: Callahan Kovacs --- spread.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spread.yaml b/spread.yaml index 9eef183a25..07feaa003a 100644 --- a/spread.yaml +++ b/spread.yaml @@ -321,10 +321,11 @@ suites: - ubuntu-20.04* - ubuntu-22.04* - tests/spread/core-devel/: - summary: tests of devel base snaps - environment: - SNAPCRAFT_BUILD_ENVIRONMENT: "" +# 'build-base: devel' fails due to an issue with the 24.10 buildd image (#4921) +# tests/spread/core-devel/: +# summary: tests of devel base snaps +# environment: +# SNAPCRAFT_BUILD_ENVIRONMENT: "" # General, core suite tests/spread/cross-compile/: From 157d0a182a433f45db379ea174130db98775e0b3 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 25 Jul 2024 08:18:05 -0500 Subject: [PATCH 011/151] tests: add spread tests for validation-sets (#4924) Adds a spread test for `edit-validation-sets` and `list-validation-sets`. Signed-off-by: Callahan Kovacs Co-authored-by: Alex Lowe --- .github/workflows/spread.yml | 4 +- spread.yaml | 9 ++++ .../store => store/basic-workflow}/task.yaml | 8 +--- tests/spread/store/validation-sets/editor.sh | 12 ++++++ tests/spread/store/validation-sets/task.yaml | 41 +++++++++++++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) rename tests/spread/{general/store => store/basic-workflow}/task.yaml (91%) create mode 100755 tests/spread/store/validation-sets/editor.sh create mode 100644 tests/spread/store/validation-sets/task.yaml diff --git a/.github/workflows/spread.yml b/.github/workflows/spread.yml index 4bcfaa18e8..b6ae331d1b 100644 --- a/.github/workflows/spread.yml +++ b/.github/workflows/spread.yml @@ -110,10 +110,12 @@ jobs: name: Run spread env: SPREAD_GOOGLE_KEY: ${{ secrets.SPREAD_GOOGLE_KEY }} + SNAPCRAFT_ASSERTION_KEY: "${{ secrets.SNAPCRAFT_ASSERTION_KEY }}" SNAPCRAFT_STORE_CREDENTIALS_STAGING: "${{ secrets.SNAPCRAFT_STORE_CREDENTIALS_STAGING }}" SNAPCRAFT_STORE_CREDENTIALS_STAGING_CANDID: "${{ secrets.SNAPCRAFT_STORE_CREDENTIALS_STAGING_CANDID }}" SNAPCRAFT_STORE_CREDENTIALS_STAGING_LEGACY: "${{ secrets.SNAPCRAFT_STORE_CREDENTIALS_STAGING_LEGACY }}" - run: spread google:ubuntu-22.04-64:tests/spread/general/store + run: | + spread google:ubuntu-22.04-64:tests/spread/store/ - name: Discard spread workers if: always() diff --git a/spread.yaml b/spread.yaml index 07feaa003a..851683272d 100644 --- a/spread.yaml +++ b/spread.yaml @@ -377,6 +377,15 @@ suites: sudo apt-get install git sudo apt-mark auto git + tests/spread/store/: + summary: tests of store-related snapcraft commands + manual: true + environment: + STORE_DASHBOARD_URL: https://dashboard.staging.snapcraft.io + STORE_API_URL: https://api.staging.snapcraft.io + STORE_UPLOAD_URL: https://storage.staging.snapcraftcontent.com + UBUNTU_ONE_SSO_URL: https://login.staging.ubuntu.com + path: /snapcraft/ include: - tests/ diff --git a/tests/spread/general/store/task.yaml b/tests/spread/store/basic-workflow/task.yaml similarity index 91% rename from tests/spread/general/store/task.yaml rename to tests/spread/store/basic-workflow/task.yaml index 2a41cf3f47..c6035c2bcf 100644 --- a/tests/spread/general/store/task.yaml +++ b/tests/spread/store/basic-workflow/task.yaml @@ -1,6 +1,4 @@ -summary: Test the store workflow - -manual: true +summary: Test the store workflow to register, upload, and release a snap environment: # use a core22 snap with components but no component hooks @@ -9,10 +7,6 @@ environment: SNAPCRAFT_STORE_CREDENTIALS/ubuntu_one: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING})" SNAPCRAFT_STORE_CREDENTIALS/legacy: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING_LEGACY})" SNAPCRAFT_STORE_CREDENTIALS/candid: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING_CANDID})" - STORE_DASHBOARD_URL: https://dashboard.staging.snapcraft.io - STORE_API_URL: https://api.staging.snapcraft.io - STORE_UPLOAD_URL: https://storage.staging.snapcraftcontent.com - UBUNTU_ONE_SSO_URL: https://login.staging.ubuntu.com prepare: | if [[ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]]; then diff --git a/tests/spread/store/validation-sets/editor.sh b/tests/spread/store/validation-sets/editor.sh new file mode 100755 index 0000000000..a38c96be47 --- /dev/null +++ b/tests/spread/store/validation-sets/editor.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +validation_set_file="$1" + +# flip-flop between two valid revisions of `test-snapcraft-assertions` in the staging store: 1 and 2 +if grep -q "^ revision:.*1" "$validation_set_file"; then + (( revision=2 )) +else + (( revision=1 )) +fi + +sed -i "s/ revision:.*/ revision: $revision/g" "$validation_set_file" diff --git a/tests/spread/store/validation-sets/task.yaml b/tests/spread/store/validation-sets/task.yaml new file mode 100644 index 0000000000..277ee9d8e1 --- /dev/null +++ b/tests/spread/store/validation-sets/task.yaml @@ -0,0 +1,41 @@ +summary: test the validation-set commands + +environment: + SNAPCRAFT_ASSERTION_KEY: "$(HOST: echo ${SNAPCRAFT_ASSERTION_KEY})" + SNAPCRAFT_STORE_CREDENTIALS: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING})" + +prepare: | + if [[ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]]; then + ERROR "No credentials set in env SNAPCRAFT_STORE_CREDENTIALS" + fi + + if [[ -z "$SNAPCRAFT_ASSERTION_KEY" ]]; then + ERROR "No gpg key set in env SNAPCRAFT_ASSERTION_KEY" + fi + + # setup snap gpg dir + mkdir -p "$HOME/.snap/gnupg" + chmod 700 "$HOME/.snap/gnupg" + + # import a registered key + echo "$SNAPCRAFT_ASSERTION_KEY" | base64 --decode > store-key.txt + gpg --homedir "$HOME/.snap/gnupg" --import store-key.txt + rm -f store-key.txt + + snap install yq + +execute: | + # ensure snapcraft is logged in and can access the store + snapcraft whoami + + # snapcraft will use a fake file editor + export EDITOR="$PWD/editor.sh" + + snapcraft edit-validation-sets "$(snapcraft whoami | yq .id)" testset 1 --key-name testspreadkey + + snapcraft list-validation-sets | MATCH testset + +restore: | + rm -rf "$HOME/.snap/gnupg" + + snap remove --purge yq From cae7ffbcfd480057f595c6aa8cd31ab42ff3319b Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 30 Jul 2024 12:36:22 -0500 Subject: [PATCH 012/151] fix: use correct types for validation set models (#4928) - Use the correct data types for API calls - Switch to pydantic models - Catch model errors locally rather than relying on the store to validate the data - Make the boilerplate validation set valid Signed-off-by: Callahan Kovacs --- snapcraft/commands/validation_sets.py | 62 ++-- snapcraft_legacy/cli/assertions.py | 3 +- snapcraft_legacy/storeapi/v2/_api_schema.py | 74 +---- .../storeapi/v2/validation_sets.py | 280 ++++++++---------- tests/legacy/unit/store/test_store_client.py | 4 +- .../unit/store/v2/test_validation_sets.py | 215 +++++++------- tests/unit/commands/test_validation_sets.py | 189 +++++++----- 7 files changed, 404 insertions(+), 423 deletions(-) diff --git a/snapcraft/commands/validation_sets.py b/snapcraft/commands/validation_sets.py index 2f01221dc0..50f1ef97ed 100644 --- a/snapcraft/commands/validation_sets.py +++ b/snapcraft/commands/validation_sets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -24,14 +24,17 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Optional +import craft_application.util import yaml from craft_application.commands import AppCommand +from craft_application.errors import CraftValidationError from craft_cli import emit from overrides import overrides from snapcraft import errors, utils from snapcraft_legacy._store import StoreClientCLI from snapcraft_legacy.storeapi.errors import StoreValidationSetsError +from snapcraft_legacy.storeapi.v2 import validation_sets if TYPE_CHECKING: import argparse @@ -40,7 +43,7 @@ _VALIDATIONS_SETS_SNAPS_TEMPLATE = textwrap.dedent( """\ snaps: - # - name: # The name of the snap. + - name: hello # The name of the snap. # id: # The ID of the snap. Optional, defaults to the current ID for # the provided name. # presence: [required|optional|invalid] # Optional, defaults to required. @@ -102,7 +105,9 @@ def run(self, parsed_args: "argparse.Namespace"): validation_sets_path.write_text(validation_sets_template, encoding="utf-8") edited_validation_sets = edit_validation_sets(validation_sets_path) - if edited_validation_sets == yaml.safe_load(validation_sets_template): + if edited_validation_sets == validation_sets.EditableBuildAssertion( + **yaml.safe_load(validation_sets_template) + ): emit.message("No changes made") return @@ -119,7 +124,7 @@ def run(self, parsed_args: "argparse.Namespace"): "Do you wish to amend the validation set?" ): raise errors.SnapcraftError( - "Operation aborted" + "operation aborted" ) from validation_error edited_validation_sets = edit_validation_sets(validation_sets_path) finally: @@ -127,32 +132,43 @@ def run(self, parsed_args: "argparse.Namespace"): def _submit_validation_set( - edited_validation_sets: Dict[str, Any], + edited_validation_sets: validation_sets.EditableBuildAssertion, key_name: Optional[str], store_client: StoreClientCLI, ) -> None: + emit.debug(f"Posting assertion to build: {edited_validation_sets.json()}") build_assertion = store_client.post_validation_sets_build_assertion( - validation_sets=edited_validation_sets + validation_sets=edited_validation_sets.marshal() ) - signed_validation_sets = _sign_assertion( - build_assertion.marshal(), key_name=key_name + build_assertion_dict = build_assertion.marshal_scalars_as_strings() + + signed_validation_sets = _sign_assertion(build_assertion_dict, key_name=key_name) + + emit.debug("Posting signed validation sets.") + response = store_client.post_validation_sets( + signed_validation_sets=signed_validation_sets ) - store_client.post_validation_sets(signed_validation_sets=signed_validation_sets) + emit.debug(f"Response: {response.json()}") def _generate_template( - asserted_validation_sets, *, account_id: str, set_name: str, sequence: str + asserted_validation_sets: validation_sets.ValidationSets, + *, + account_id: str, + set_name: str, + sequence: str, ) -> str: """Generate a template to edit asserted_validation_sets.""" try: # assertions should only have one item since a specific # sequence was requested. - revision = asserted_validation_sets.assertions[0].revision + revision = asserted_validation_sets.assertions[0].headers.revision snaps = yaml.dump( { "snaps": [ - s.marshal() for s in asserted_validation_sets.assertions[0].snaps + s.marshal() + for s in asserted_validation_sets.assertions[0].headers.snaps ] }, default_flow_style=False, @@ -174,7 +190,9 @@ def _generate_template( return unverified_validation_sets -def edit_validation_sets(validation_sets_path: Path) -> Dict[str, Any]: +def edit_validation_sets( + validation_sets_path: Path, +) -> validation_sets.EditableBuildAssertion: """Spawn an editor to modify the validation-sets.""" editor_cmd = os.getenv("EDITOR", "vi") @@ -182,23 +200,29 @@ def edit_validation_sets(validation_sets_path: Path) -> Dict[str, Any]: with emit.pause(): subprocess.run([editor_cmd, validation_sets_path], check=True) try: - edited_validation_sets = yaml.safe_load( - validation_sets_path.read_text(encoding="utf-8") + with validation_sets_path.open() as file: + data = craft_application.util.safe_yaml_load(file) + edited_validation_sets = validation_sets.EditableBuildAssertion.from_yaml_data( + data=data, + # filepath is only shown for pydantic errors and snapcraft should + # not expose the temp file name + filepath=Path("validation-sets"), ) return edited_validation_sets - except yaml.YAMLError as yaml_error: - emit.message(f"A YAML parsing error occurred {yaml_error!s}") + except (yaml.YAMLError, CraftValidationError) as err: + emit.message(f"{err!s}") if not utils.confirm_with_user("Do you wish to amend the validation set?"): - raise errors.SnapcraftError("Operation aborted") from yaml_error + raise errors.SnapcraftError("operation aborted") from err def _sign_assertion(assertion: Dict[str, Any], *, key_name: Optional[str]) -> bytes: + emit.debug("Signing assertion.") cmdline = ["snap", "sign"] if key_name: cmdline += ["-k", key_name] snap_sign = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE) signed_assertion, _ = snap_sign.communicate(input=json.dumps(assertion).encode()) if snap_sign.returncode != 0: - raise errors.SnapcraftError("Failed to sign assertion") + raise errors.SnapcraftError("failed to sign assertion") return signed_assertion diff --git a/snapcraft_legacy/cli/assertions.py b/snapcraft_legacy/cli/assertions.py index c4b73937cb..8177264ed8 100644 --- a/snapcraft_legacy/cli/assertions.py +++ b/snapcraft_legacy/cli/assertions.py @@ -135,7 +135,8 @@ def list_validation_sets(name, sequence): else: headers = ["Account-ID", "Name", "Sequence", "Revision", "When"] assertions = list() - for assertion in asserted_validation_sets.assertions: + for assertion_header in asserted_validation_sets.assertions: + assertion = assertion_header.headers assertions.append( [ assertion.account_id, diff --git a/snapcraft_legacy/storeapi/v2/_api_schema.py b/snapcraft_legacy/storeapi/v2/_api_schema.py index 8e56fca0e4..1bde67d5e9 100644 --- a/snapcraft_legacy/storeapi/v2/_api_schema.py +++ b/snapcraft_legacy/storeapi/v2/_api_schema.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2020 Canonical Ltd +# Copyright 2020,2024 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 @@ -275,75 +275,3 @@ "required": ["account", "channels", "packages", "permissions"], "type": "object", } - -# https://dashboard.snapcraft.io/docs/v2/en/validation-sets.html -BUILD_ASSERTION_JSONSCHEMA: Dict[str, Any] = { - "properties": { - "account-id": { - "description": 'The "account-id" assertion header', - "type": "string", - }, - "authority-id": { - "description": 'The "authority-id" assertion header', - "type": "string", - }, - "name": {"description": 'The "name" assertion header', "type": "string"}, - "revision": { - "description": 'The "revision" assertion header', - "type": "string", - }, - "sequence": { - "description": 'The "sequence" assertion header', - "type": "string", - }, - "series": {"description": 'The "series" assertion header', "type": "string"}, - "snaps": { - "items": { - "description": "List of snaps in a Validation Set assertion", - "properties": { - "id": { - "description": "Snap ID", - "maxLength": 100, - "type": "string", - }, - "name": { - "description": "Snap name", - "maxLength": 100, - "type": "string", - }, - "presence": { - "description": "Snap presence", - "enum": ["required", "optional", "invalid"], - "type": "string", - }, - "revision": {"description": "Snap revision", "type": "string"}, - }, - "required": ["name"], - "type": "object", - }, - "minItems": 1, - "type": "array", - }, - "timestamp": { - "description": 'The "timestamp" assertion header', - "type": "string", - }, - "type": { - "const": "validation-set", - "description": 'The "type" assertion header', - "type": "string", - }, - }, - "required": [ - "type", - "authority-id", - "series", - "account-id", - "name", - "sequence", - # "revision", - "timestamp", - "snaps", - ], - "type": "object", -} diff --git a/snapcraft_legacy/storeapi/v2/validation_sets.py b/snapcraft_legacy/storeapi/v2/validation_sets.py index ad4c5e0ca8..3dbe84f6b9 100644 --- a/snapcraft_legacy/storeapi/v2/validation_sets.py +++ b/snapcraft_legacy/storeapi/v2/validation_sets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright (C) 2021 Canonical Ltd +# Copyright (C) 2021,2024 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 @@ -14,161 +14,135 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from typing import Any, Dict, List, Optional +from typing import Any, Annotated, Dict, Literal, TYPE_CHECKING +import numbers +import pydantic -import jsonschema +from craft_application import models -from ._api_schema import BUILD_ASSERTION_JSONSCHEMA +if TYPE_CHECKING: + SnapName = str + SnapId = str +else: + SnapName = pydantic.constr(max_length=40) + SnapId = pydantic.constr(max_length=40) -class Snap: + +def cast_dict_scalars_to_strings(data: dict) -> dict[str, Any]: + """Cast all scalars in a dictionary to strings. + + Supported scalar types are str, bool, and numbers. + """ + return {_to_string(key): _to_string(value) for key, value in data.items()} + + +def _to_string( + data: dict | list | str | int | float | str | bool | None +) -> dict[str, Any] | list | str | None: + """Recurse through nested dicts and lists and cast scalar values to strings. + + Supported scalar types are str, bool, and numbers. + """ + # start with a string as it is the most common scenario + if isinstance(data, str): + return data + + if isinstance(data, dict): + return {_to_string(key): _to_string(value) for key, value in data.items()} + + if isinstance(data, list): + return [_to_string(i) for i in data] + + if isinstance(data, (numbers.Number, bool)): + return str(data) + + return data + + +class Snap(models.CraftBaseModel): """Represent a Snap in a Validation Set.""" - @classmethod - def unmarshal(cls, payload: Dict[str, Any]) -> "Snap": - jsonschema.validate( - payload, BUILD_ASSERTION_JSONSCHEMA["properties"]["snaps"]["items"] - ) - - return cls( - name=payload["name"], - snap_id=payload.get("id"), - presence=payload.get("presence"), - revision=payload.get("revision"), - ) - - def marshal(self) -> Dict[str, Any]: - payload = {"name": self.name} - - if self.snap_id is not None: - payload["id"] = self.snap_id - - if self.presence is not None: - payload["presence"] = self.presence - - if self.revision is not None: - payload["revision"] = self.revision - - return payload - - def __eq__(self, other) -> bool: - if not isinstance(other, Snap): - return False - - return ( - self.name == other.name - and self.snap_id == other.snap_id - and self.presence == other.presence - and self.revision == other.revision - ) - - def __repr__(self) -> str: - return f"<{self.__class__.__name__}: {self.name!r}>" - - def __init__( - self, - name: str, - snap_id: Optional[str] = None, - presence: Optional[str] = None, - revision: Optional[str] = None, - ): - self.name = name - self.snap_id = snap_id - self.presence = presence - self.revision = revision - - -class BuildAssertion: - """Represent Validation Set assertion headers ready local signing.""" - - @classmethod - def unmarshal(cls, payload: Dict[str, Any]) -> "BuildAssertion": - jsonschema.validate(payload, BUILD_ASSERTION_JSONSCHEMA) - - return cls( - account_id=payload["account-id"], - authority_id=payload["authority-id"], - name=payload["name"], - revision=payload.get("revision", "0"), - sequence=payload["sequence"], - series=payload["series"], - snaps=[Snap.unmarshal(s) for s in payload["snaps"]], - timestamp=payload["timestamp"], - assertion_type=payload["type"], - ) - - def marshal(self) -> Dict[str, Any]: - return { - "account-id": self.account_id, - "authority-id": self.authority_id, - "name": self.name, - "revision": self.revision, - "sequence": self.sequence, - "snaps": [s.marshal() for s in self.snaps], - "timestamp": self.timestamp, - "type": self.assertion_type, - "series": self.series, - } - - def __eq__(self, other) -> bool: - if not isinstance(other, BuildAssertion): - return False - - return ( - self.name == other.name - and self.account_id == other.account_id - and self.authority_id == other.authority_id - and self.revision == other.revision - and self.sequence == other.sequence - and self.snaps == other.snaps - and self.timestamp == other.timestamp - and self.assertion_type == other.assertion_type - and self.series == other.series - ) - - def __repr__(self) -> str: - return f"<{self.__class__.__name__}: {self.name!r}>" - - def __init__( - self, - *, - account_id: str, - authority_id: str, - name: str, - revision: str, - sequence: str, - snaps: List[Snap], - timestamp: str, - assertion_type: str, - series: str = "16", - ): - self.account_id = account_id - self.authority_id = authority_id - self.name = name - self.revision = revision - self.sequence = sequence - self.snaps = snaps - self.series = series - self.timestamp = timestamp - self.assertion_type = assertion_type - - -class ValidationSets: - @classmethod - def unmarshal(cls, payload: Dict[str, Any]) -> "ValidationSets": - return cls( - assertions=[ - BuildAssertion.unmarshal(a["headers"]) - for a in payload.get("assertions", list()) - ] - ) - - def marshal(self) -> Dict[str, Any]: - return {"assertions": [{"headers": a.marshal()} for a in self.assertions]} - - def __repr__(self) -> str: - assertion_count = len(self.assertions) - return f"<{self.__class__.__name__}: assertions {assertion_count!r}>" - - def __init__(self, assertions: List[BuildAssertion]) -> None: - self.assertions = assertions + name: SnapName + """Snap name""" + + id: SnapId | None + """Snap ID""" + + presence: Literal["required", "optional", "invalid"] | None + """Snap presence""" + + revision: int | None + """Snap revision""" + + +class EditableBuildAssertion(models.CraftBaseModel): + """Subset of a build assertion that can be edited by the user. + + https://dashboard.snapcraft.io/docs/reference/v2/en/validation-sets.html#request-json-schema + """ + account_id: str + """The "account-id" assertion header""" + + name: str + """The "name" assertion header""" + + revision: str | None + """The "revision" assertion header""" + + sequence: int + """The "sequence" assertion header""" + + snaps: Annotated[list[Snap], pydantic.Field(min_items=1)] + """List of snaps in a Validation Set assertion""" + + def marshal_scalars_as_strings(self) -> Dict[str, Any]: + """Marshal the object where all scalars are represented as strings.""" + data = self.marshal() + return cast_dict_scalars_to_strings(data) + + +class BuildAssertion(EditableBuildAssertion): + """Full build assertion header for a Validation Set. + + https://dashboard.snapcraft.io/docs/reference/v2/en/validation-sets.html#response-json-schema + """ + authority_id: str + """The "authority-id" assertion header""" + + series: str + """The "series" assertion header""" + + sign_key_sha3_384: str | None + """Signing key ID.""" + + timestamp: str + """The "timestamp" assertion header""" + + type: Literal["validation-set"] + """The "type" assertion header""" + + @pydantic.root_validator(pre=True) + def remove_sign_key(cls, values): + """Accept but always ignore the sign key. + + The API can return and accept the sign key but the previous implementation + ignored it. + """ + values.pop("sign-key-sha3-384", None) + return values + + +class Headers(models.CraftBaseModel): + """Assertion headers for a validation set.""" + headers: BuildAssertion + """Assertion headers""" + + +class ValidationSets(models.CraftBaseModel): + """Validation sets. + + https://dashboard.snapcraft.io/docs/reference/v2/en/validation-sets.html#id4 + """ + assertions: list[Headers] + """List of validation-set assertions""" diff --git a/tests/legacy/unit/store/test_store_client.py b/tests/legacy/unit/store/test_store_client.py index ef89aa0eb1..3309bf13bf 100644 --- a/tests/legacy/unit/store/test_store_client.py +++ b/tests/legacy/unit/store/test_store_client.py @@ -438,7 +438,7 @@ def test_post_valid_assertion(self): self.assertThat(vs, IsInstance(validation_sets.ValidationSets)) self.assertThat(vs.assertions, HasLength(1)) - self.assertThat(vs.assertions[0], Equals(build_assertion)) + self.assertThat(vs.assertions[0].headers, Equals(build_assertion)) def test_post_invalid_assertion(self): build_assertion = self.client.post_validation_sets_build_assertion( @@ -516,7 +516,7 @@ def test_get_validation_sets_by_name(self): self.assertThat(vs, IsInstance(validation_sets.ValidationSets)) self.expectThat(vs.assertions, HasLength(1)) - self.expectThat(vs.assertions[0].name, Equals("acme-cert-2020-10")) + self.expectThat(vs.assertions[0].headers.name, Equals("acme-cert-2020-10")) def test_get_invalid_sequence(self): raised = self.assertRaises( diff --git a/tests/legacy/unit/store/v2/test_validation_sets.py b/tests/legacy/unit/store/v2/test_validation_sets.py index e63db9188f..2820efab48 100644 --- a/tests/legacy/unit/store/v2/test_validation_sets.py +++ b/tests/legacy/unit/store/v2/test_validation_sets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright (C) 2021 Canonical Ltd +# Copyright (C) 2021,2024 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 @@ -19,127 +19,136 @@ from snapcraft_legacy.storeapi.v2 import validation_sets -@pytest.mark.parametrize("snap_id", (None, "snap_id")) -@pytest.mark.parametrize("presence", (None, "required", "optional", "invalid")) -@pytest.mark.parametrize("revision", ("42", None)) -def test_snap(snap_id, presence, revision): - payload = { - "name": "set-1", +@pytest.fixture() +def fake_snap_data(): + return { + "name": "snap-name", + "id": "snap-id", + "presence": "required", + "revision": 42, } - if presence is not None: - payload["presence"] = presence - if snap_id is not None: - payload["id"] = snap_id +@pytest.fixture() +def fake_snap(fake_snap_data): + return validation_sets.Snap.unmarshal(fake_snap_data) - if revision is not None: - payload["revision"] = revision - s = validation_sets.Snap.unmarshal(payload) +@pytest.fixture() +def fake_editable_build_assertion_data(fake_snap_data): + return { + "account-id": "account-id-1", + "name": "validation-set-1", + "revision": "revision-1", + "sequence": 1, + "snaps": [fake_snap_data], + } + - assert repr(s) == "" - assert s.name == payload["name"] - assert s.snap_id == payload.get("id") - assert s.presence == payload.get("presence") - assert s.revision == payload.get("revision") - assert s.marshal() == payload +@pytest.fixture() +def fake_editable_build_assertion(fake_editable_build_assertion_data): + return validation_sets.EditableBuildAssertion.unmarshal( + fake_editable_build_assertion_data + ) -@pytest.mark.parametrize("revision", ("42", None)) -def test_build_assertion(revision): - payload = { +@pytest.fixture() +def fake_build_assertion_data(fake_snap_data): + return { "account-id": "account-id-1", - "authority-id": "authority-id-1", "name": "validation-set-1", - "sequence": "1", + "revision": "revision-1", + "sequence": 1, + "snaps": [fake_snap_data], + "authority-id": "authority-id-1", "series": "16", - "snaps": [validation_sets.Snap(name="snap-name").marshal()], + "sign-key-sha3-384": "sign-key-1", "timestamp": "2020-10-29T16:36:56Z", "type": "validation-set", } - if revision is not None: - payload["revision"] = revision - s = validation_sets.BuildAssertion.unmarshal(payload) - - assert repr(s) == "" - assert s.account_id == payload["account-id"] - assert s.authority_id == payload["authority-id"] - assert s.name == payload["name"] - assert s.sequence == payload.get("sequence") - assert s.series == payload.get("series") - assert s.snaps == [validation_sets.Snap(name="snap-name")] - assert s.timestamp == "2020-10-29T16:36:56Z" - - if revision is None: - payload["revision"] = "0" - - assert s.marshal() == payload - - -def test_validation_sets(): - payload = { - "assertions": [ - { - "headers": { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "1", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-1").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - } - }, +@pytest.fixture() +def fake_build_assertion(fake_build_assertion_data): + return validation_sets.BuildAssertion.unmarshal(fake_build_assertion_data) + + +@pytest.mark.parametrize( + ("input_dict", "expected"), + [ + pytest.param({}, {}, id="empty"), + pytest.param( + {False: False, True: True}, + {"False": "False", "True": "True"}, + id="boolean values", + ), + pytest.param( + {0: 0, None: None, "dict": {}, "list": [], "str": ""}, + ({"0": "0", None: None, "dict": {}, "list": [], "str": ""}), + id="none-like values", + ), + pytest.param( + {10: 10, 20.0: 20.0, "30": "30", True: True}, + {"10": "10", "20.0": "20.0", "30": "30", "True": "True"}, + id="scalar values", + ), + pytest.param( + {"foo": {"bar": [1, 2.0], "baz": {"qux": True}}}, + {"foo": {"bar": ["1", "2.0"], "baz": {"qux": "True"}}}, + id="nested data structures", + ), + ], +) +def test_cast_dict_scalars_to_strings(input_dict, expected): + actual = validation_sets.cast_dict_scalars_to_strings(input_dict) + + assert actual == expected + + +def test_ignore_sign_key(fake_build_assertion): + """Ignore the sign key when unmarshalling.""" + assert not fake_build_assertion.sign_key_sha3_384 + + +def test_editable_build_assertion_marshal_as_str(fake_editable_build_assertion): + """Cast all scalars to string when marshalling.""" + data = fake_editable_build_assertion.marshal_scalars_as_strings() + + assert data == { + "account-id": "account-id-1", + "name": "validation-set-1", + "revision": "revision-1", + "sequence": "1", + "snaps": [ { - "headers": { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "3", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-2").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - }, + "id": "snap-id", + "name": "snap-name", + "presence": "required", + "revision": "42", }, - ] + ], } - s = validation_sets.ValidationSets.unmarshal(payload) - - assert repr(s) == "" - assert s.assertions[0] == validation_sets.BuildAssertion.unmarshal( - { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "1", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-1").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - }, - ) - assert s.assertions[1] == validation_sets.BuildAssertion.unmarshal( - { - "account-id": "account-id-1", - "authority-id": "authority-id-1", - "name": "validation-set-1", - "revision": "3", - "sequence": "1", - "series": "16", - "snaps": [validation_sets.Snap(name="snap-name-2").marshal()], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - } - ) +def test_build_assertion_marshal_as_str(fake_build_assertion): + """Cast all scalars to string when marshalling.""" + data = fake_build_assertion.marshal_scalars_as_strings() - assert s.marshal() == payload + assert data == { + "account-id": "account-id-1", + "authority-id": "authority-id-1", + "name": "validation-set-1", + "revision": "revision-1", + "sequence": "1", + "series": "16", + "snaps": [ + { + "id": "snap-id", + "name": "snap-name", + "presence": "required", + "revision": "42", + }, + ], + "timestamp": "2020-10-29T16:36:56Z", + "type": "validation-set", + } diff --git a/tests/unit/commands/test_validation_sets.py b/tests/unit/commands/test_validation_sets.py index f840a27c9f..1e69a278d1 100644 --- a/tests/unit/commands/test_validation_sets.py +++ b/tests/unit/commands/test_validation_sets.py @@ -32,63 +32,71 @@ # Fixtures # ############ +VALIDATION_SET_DATA = { + "assertions": [ + { + "headers": { + "account-id": "AccountIDXXXOfTheRequestingUserX", + "authority-id": "AccountIDXXXOfTheRequestingUserX", + "name": "certification-x1", + "revision": "222", + "sequence": "9", + "series": "16", + "sign-key-sha3-384": "XSignXKeyXHashXXXXXXXXXXX", + "snaps": [ + { + "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", + "name": "snap-name-1", + "presence": "optional", + "revision": "10", + }, + { + "id": "XXSnapIDForXSnapName2XXXXXXXXXXX", + "name": "snap-name-2", + }, + ], + "timestamp": "2020-10-29T16:36:56Z", + "type": "validation-set", + } + }, + ] +} + @pytest.fixture -def validation_sets_payload(): - return validation_sets.ValidationSets.unmarshal( - { - "assertions": [ - { - "headers": { - "account-id": "AccountIDXXXOfTheRequestingUserX", - "authority-id": "AccountIDXXXOfTheRequestingUserX", - "name": "certification-x1", - "revision": "222", - "sequence": "9", - "series": "16", - "sign-key-sha3-384": "XSignXKeyXHashXXXXXXXXXXX", - "snaps": [ - { - "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", - "name": "snap-name-1", - "presence": "optional", - }, - { - "id": "XXSnapIDForXSnapName2XXXXXXXXXXX", - "name": "snap-name-2", - }, - ], - "timestamp": "2020-10-29T16:36:56Z", - "type": "validation-set", - } - }, - ] - } +def fake_validation_sets(): + return validation_sets.ValidationSets.unmarshal(VALIDATION_SET_DATA) + + +@pytest.fixture() +def fake_build_assertion(): + return validation_sets.BuildAssertion.unmarshal( + VALIDATION_SET_DATA["assertions"][0]["headers"] ) @pytest.fixture -def fake_dashboard_post_validation_sets(validation_sets_payload, mocker): +def fake_dashboard_post_validation_sets(fake_validation_sets, mocker): return mocker.patch.object( - StoreClientCLI, "post_validation_sets", return_value=validation_sets_payload + StoreClientCLI, "post_validation_sets", return_value=fake_validation_sets ) @pytest.fixture -def fake_dashboard_post_validation_sets_build_assertion( - validation_sets_payload, mocker -): +def fake_dashboard_post_validation_sets_build_assertion(fake_validation_sets, mocker): return mocker.patch.object( StoreClientCLI, "post_validation_sets_build_assertion", - return_value=validation_sets_payload.assertions[0], + return_value=validation_sets.BuildAssertion.unmarshal( + VALIDATION_SET_DATA["assertions"][0]["headers"] + ), ) @pytest.fixture -def fake_dashboard_get_validation_sets(validation_sets_payload, mocker): +def fake_dashboard_get_validation_sets(fake_validation_sets, mocker): return mocker.patch.object( - StoreClientCLI, "get_validation_sets", return_value=validation_sets_payload + StoreClientCLI, "get_validation_sets", return_value=fake_validation_sets ) @@ -104,19 +112,22 @@ def sign(assertion: Dict[str, Any], *, key_name: str) -> bytes: @pytest.fixture def edit_return_value(): - return { - "account-id": "AccountIDXXXOfTheRequestingUserX", - "name": "certification-x1", - "sequence": "9", - "snaps": [ - { - "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", - "name": "snap-name-1", - "presence": "required", - }, - {"id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2"}, - ], - } + return validation_sets.EditableBuildAssertion.unmarshal( + { + "account-id": "AccountIDXXXOfTheRequestingUserX", + "name": "certification-x1", + "sequence": "9", + "snaps": [ + { + "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", + "name": "snap-name-1", + "presence": "required", + "revision": "10", + }, + {"id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2"}, + ], + } + ) @pytest.fixture @@ -137,12 +148,13 @@ def fake_edit_validation_sets(mocker, edit_return_value): validation_sets={ "account-id": "AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", - "sequence": "9", + "sequence": 9, "snaps": [ { "name": "snap-name-1", "id": "XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "required", + "revision": 10, }, {"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}, ], @@ -150,13 +162,12 @@ def fake_edit_validation_sets(mocker, edit_return_value): ) EXPECTED_SIGNED_VALIDATION_SETS = ( - '{{"account-id": "AccountIDXXXOfTheRequestingUserX", "authority-id": ' - '"AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", ' - '"revision": "222", "sequence": "9", "snaps": [{{"name": "snap-name-1", ' - '"id": "XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "optional"}}, ' + '{{"account-id": "AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", ' + '"revision": "222", "sequence": "9", "snaps": [{{"name": "snap-name-1", "id": ' + '"XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "optional", "revision": "10"}}, ' '{{"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}}], ' - '"timestamp": "2020-10-29T16:36:56Z", "type": "validation-set", ' - '"series": "16"}}\n\nSIGNED{key_name}' + '"authority-id": "AccountIDXXXOfTheRequestingUserX", "series": "16", "timestamp": ' + '"2020-10-29T16:36:56Z", "type": "validation-set"}}\n\nSIGNED{key_name}' ) @@ -176,8 +187,8 @@ def test_edit_validation_sets_with_no_changes_to_existing_set( emitter, ): # Make it look like there were no edits. - edit_return_value["sequence"] = 9 - edit_return_value["snaps"][0]["presence"] = "optional" + edit_return_value.sequence = 9 + edit_return_value.snaps[0].presence = "optional" fake_edit_validation_sets.return_value = edit_return_value cmd = commands.StoreEditValidationSetsCommand(None) @@ -203,7 +214,8 @@ def test_edit_validation_sets_with_no_changes_to_existing_set( @pytest.mark.usefixtures("memory_keyring", "fake_edit_validation_sets") @pytest.mark.parametrize("key_name", [None, "general", "main"]) def test_edit_validation_sets_with_changes_to_existing_set( - validation_sets_payload, + fake_validation_sets, + fake_build_assertion, fake_dashboard_get_validation_sets, fake_dashboard_post_validation_sets_build_assertion, fake_dashboard_post_validation_sets, @@ -232,11 +244,11 @@ def test_edit_validation_sets_with_changes_to_existing_set( signed_validation_sets=EXPECTED_SIGNED_VALIDATION_SETS.format( key_name=key_name ).encode() - ) + ), ] assert fake_snap_sign.mock_calls == [ call( - validation_sets_payload.assertions[0].marshal(), + fake_build_assertion.marshal_scalars_as_strings(), key_name=key_name, ) ] @@ -244,7 +256,8 @@ def test_edit_validation_sets_with_changes_to_existing_set( @pytest.mark.usefixtures("memory_keyring", "fake_edit_validation_sets") def test_edit_validation_sets_with_errors_to_amend( - validation_sets_payload, + fake_validation_sets, + fake_build_assertion, fake_dashboard_get_validation_sets, fake_dashboard_post_validation_sets_build_assertion, fake_dashboard_post_validation_sets, @@ -260,7 +273,7 @@ def test_edit_validation_sets_with_errors_to_amend( ).encode(), ) ), - validation_sets_payload.assertions[0], + fake_build_assertion, ] confirm_mock = mocker.patch("snapcraft.utils.confirm_with_user", return_value=True) @@ -291,7 +304,7 @@ def test_edit_validation_sets_with_errors_to_amend( ] assert fake_snap_sign.mock_calls == [ call( - validation_sets_payload.assertions[0].marshal(), + fake_build_assertion.marshal_scalars_as_strings(), key_name=None, ) ] @@ -300,7 +313,7 @@ def test_edit_validation_sets_with_errors_to_amend( @pytest.mark.usefixtures("memory_keyring", "fake_edit_validation_sets") def test_edit_validation_sets_with_errors_not_amended( - validation_sets_payload, + fake_validation_sets, fake_dashboard_get_validation_sets, fake_dashboard_post_validation_sets_build_assertion, fake_dashboard_post_validation_sets, @@ -344,20 +357,52 @@ def test_edit_validation_sets_with_errors_not_amended( def test_edit_yaml_error_retry(mocker, tmp_path, monkeypatch): monkeypatch.setenv("EDITOR", "faux-vi") + tmp_file = tmp_path / "validation_sets_template" + confirm_mock = mocker.patch("snapcraft.utils.confirm_with_user", return_value=True) + expected_edited_assertion = validation_sets.EditableBuildAssertion.unmarshal( + { + "account-id": "test", + "name": "test", + "sequence": 1, + "snaps": [{"name": "core22"}], + } + ) + good_yaml = "{'account-id': 'test', 'name': 'test', 'sequence': 1, 'snaps': [{'name': 'core22'}]}" + bad_yaml = f"badyaml}} {good_yaml}" + data_write = [good_yaml, bad_yaml] + + def side_effect(*args, **kwargs): + tmp_file.write_text(data_write.pop(), encoding="utf-8") + subprocess_mock = mocker.patch("subprocess.run", side_effect=side_effect) + + assert edit_validation_sets(tmp_file) == expected_edited_assertion + assert confirm_mock.mock_calls == [call("Do you wish to amend the validation set?")] + assert subprocess_mock.mock_calls == [call(["faux-vi", tmp_file], check=True)] * 2 + + +def test_edit_pydantic_error_retry(mocker, tmp_path, monkeypatch): + monkeypatch.setenv("EDITOR", "faux-vi") tmp_file = tmp_path / "validation_sets_template" confirm_mock = mocker.patch("snapcraft.utils.confirm_with_user", return_value=True) - data_write = [ - "{good: yaml}", - "{{bad yaml {{", - ] + expected_edited_assertion = validation_sets.EditableBuildAssertion.unmarshal( + { + "account-id": "test", + "name": "test", + "sequence": 1, + "snaps": [{"name": "core22"}], + } + ) + good_yaml = "{'account-id': 'test', 'name': 'test', 'sequence': 1, 'snaps': [{'name': 'core22'}]}" + bad_yaml = "{'invalid-field': 'test'}" + data_write = [good_yaml, bad_yaml] def side_effect(*args, **kwargs): tmp_file.write_text(data_write.pop(), encoding="utf-8") subprocess_mock = mocker.patch("subprocess.run", side_effect=side_effect) - assert edit_validation_sets(tmp_file) == {"good": "yaml"} + assert edit_validation_sets(tmp_file) == expected_edited_assertion assert confirm_mock.mock_calls == [call("Do you wish to amend the validation set?")] assert subprocess_mock.mock_calls == [call(["faux-vi", tmp_file], check=True)] * 2 From 6425f1c7a3faaf5d7156f22bed2ba5d332d45ff6 Mon Sep 17 00:00:00 2001 From: Aristo Chen Date: Thu, 1 Aug 2024 00:22:12 +0800 Subject: [PATCH 013/151] fix(kernel_plugin): ignoring missing files when removing symlinks (#4915) Signed-off-by: Aristo Chen --- snapcraft_legacy/plugins/v2/_kernel_build.py | 2 +- tests/legacy/unit/plugins/v2/test_kernel.py | 2 +- tests/unit/parts/plugins/test_kernel.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/snapcraft_legacy/plugins/v2/_kernel_build.py b/snapcraft_legacy/plugins/v2/_kernel_build.py index e1fd3d80c9..48ec7243b2 100644 --- a/snapcraft_legacy/plugins/v2/_kernel_build.py +++ b/snapcraft_legacy/plugins/v2/_kernel_build.py @@ -1111,7 +1111,7 @@ def _arrange_install_dir_cmd(install_dir: str) -> List[str]: # but snapd expects modules/ and firmware/ mv {install_dir}/lib/modules {install_dir}/ # remove symlinks modules/*/build and modules/*/source - rm {install_dir}/modules/*/build {install_dir}/modules/*/source + rm -f {install_dir}/modules/*/build {install_dir}/modules/*/source # if there is firmware dir, move it to snap root # this could have been from stage packages or from kernel build [ -d {install_dir}/lib/firmware ] && mv {install_dir}/lib/firmware {install_dir} diff --git a/tests/legacy/unit/plugins/v2/test_kernel.py b/tests/legacy/unit/plugins/v2/test_kernel.py index 939316c14b..0b7729ad8c 100644 --- a/tests/legacy/unit/plugins/v2/test_kernel.py +++ b/tests/legacy/unit/plugins/v2/test_kernel.py @@ -1768,7 +1768,7 @@ def _is_sub_array(array, sub_array): # but snapd expects modules/ and firmware/ mv ${SNAPCRAFT_PART_INSTALL}/lib/modules ${SNAPCRAFT_PART_INSTALL}/ # remove symlinks modules/*/build and modules/*/source - rm ${SNAPCRAFT_PART_INSTALL}/modules/*/build ${SNAPCRAFT_PART_INSTALL}/modules/*/source + rm -f ${SNAPCRAFT_PART_INSTALL}/modules/*/build ${SNAPCRAFT_PART_INSTALL}/modules/*/source # if there is firmware dir, move it to snap root # this could have been from stage packages or from kernel build [ -d ${SNAPCRAFT_PART_INSTALL}/lib/firmware ] && mv ${SNAPCRAFT_PART_INSTALL}/lib/firmware ${SNAPCRAFT_PART_INSTALL} diff --git a/tests/unit/parts/plugins/test_kernel.py b/tests/unit/parts/plugins/test_kernel.py index 0bc4a68166..09db04137b 100644 --- a/tests/unit/parts/plugins/test_kernel.py +++ b/tests/unit/parts/plugins/test_kernel.py @@ -1919,7 +1919,7 @@ def _is_sub_array(array, sub_array): # but snapd expects modules/ and firmware/ mv ${CRAFT_PART_INSTALL}/lib/modules ${CRAFT_PART_INSTALL}/ # remove symlinks modules/*/build and modules/*/source - rm ${CRAFT_PART_INSTALL}/modules/*/build ${CRAFT_PART_INSTALL}/modules/*/source + rm -f ${CRAFT_PART_INSTALL}/modules/*/build ${CRAFT_PART_INSTALL}/modules/*/source # if there is firmware dir, move it to snap root # this could have been from stage packages or from kernel build [ -d ${CRAFT_PART_INSTALL}/lib/firmware ] && mv ${CRAFT_PART_INSTALL}/lib/firmware ${CRAFT_PART_INSTALL} From 952a8e04da7fabb378b22bf8018e99af3614849b Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Thu, 1 Aug 2024 13:20:14 -0300 Subject: [PATCH 014/151] fix: fix Python venv in core24 classic snaps (#4946) --- snapcraft/parts/plugins/python_plugin.py | 35 +++++++++++++++- snapcraft/services/lifecycle.py | 12 ++++++ .../environment/test-variables/task.yaml | 2 +- .../python-hello/classic/snap/snapcraft.yaml | 3 ++ .../core24/python-hello/src/hello/__init__.py | 5 ++- .../python-hello/strict/snap/snapcraft.yaml | 2 + tests/spread/core24/python-hello/task.yaml | 7 +++- .../unit/parts/plugins/test_python_plugin.py | 40 ++++++++++++++++++- tests/unit/services/test_lifecycle.py | 10 ++++- 9 files changed, 109 insertions(+), 7 deletions(-) diff --git a/snapcraft/parts/plugins/python_plugin.py b/snapcraft/parts/plugins/python_plugin.py index fb2159a9ca..df20f19871 100644 --- a/snapcraft/parts/plugins/python_plugin.py +++ b/snapcraft/parts/plugins/python_plugin.py @@ -17,9 +17,10 @@ """The Snapcraft Python plugin.""" import logging +from pathlib import Path from typing import Optional -from craft_parts import errors +from craft_parts import StepInfo, errors from craft_parts.plugins import python_plugin from overrides import override @@ -64,3 +65,35 @@ def _get_system_python_interpreter(self) -> Optional[str]: confinement, ) return interpreter + + @classmethod + def post_prime(cls, step_info: StepInfo) -> None: + """Perform Python-specific actions right before packing.""" + base = step_info.project_base + + if base in ("core20", "core22"): + # Only fix pyvenv.cfg on core24+ snaps + return + + root_path: Path = step_info.prime_dir + + pyvenv = root_path / "pyvenv.cfg" + if not pyvenv.is_file(): + return + + snap_path = Path(f"/snap/{step_info.project_name}/current") + new_home = f"home = {snap_path}" + + candidates = ( + step_info.part_install_dir, + step_info.stage_dir, + ) + + old_contents = contents = pyvenv.read_text() + for candidate in candidates: + old_home = f"home = {candidate}" + contents = contents.replace(old_home, new_home) + + if old_contents != contents: + logger.debug("Updating pyvenv.cfg to:\n%s", contents) + pyvenv.write_text(contents) diff --git a/snapcraft/services/lifecycle.py b/snapcraft/services/lifecycle.py index 6bfae8d160..61f8f95472 100644 --- a/snapcraft/services/lifecycle.py +++ b/snapcraft/services/lifecycle.py @@ -77,6 +77,7 @@ def setup(self) -> None: extra_build_snaps=project.get_extra_build_snaps(), confinement=project.confinement, project_base=project.base or "", + project_name=project.name, ) callbacks.register_prologue(parts.set_global_environment) callbacks.register_pre_step(parts.set_step_environment) @@ -85,8 +86,19 @@ def setup(self) -> None: @overrides def post_prime(self, step_info: StepInfo) -> bool: """Run post-prime parts steps for Snapcraft.""" + from snapcraft.parts import plugins + project = cast(models.Project, self._project) + part_name = step_info.part_name + plugin_name = project.parts[part_name]["plugin"] + + # Handle plugin-specific prime fixes + if plugin_name == "python": + plugins.PythonPlugin.post_prime(step_info) + + # Handle patch-elf + # do not use system libraries in classic confinement use_system_libs = not bool(project.confinement == "classic") diff --git a/tests/spread/core24-suites/environment/test-variables/task.yaml b/tests/spread/core24-suites/environment/test-variables/task.yaml index 15c6fbae11..f9fb09d0a2 100644 --- a/tests/spread/core24-suites/environment/test-variables/task.yaml +++ b/tests/spread/core24-suites/environment/test-variables/task.yaml @@ -33,7 +33,7 @@ execute: | cat "$file" for exp in \ "^SNAPCRAFT_PROJECT_GRADE=devel$" \ - "^SNAPCRAFT_PROJECT_NAME=None$" \ + "^SNAPCRAFT_PROJECT_NAME=variables$" \ "^SNAPCRAFT_PROJECT_VERSION=1$" \ "^SNAPCRAFT_PARALLEL_BUILD_COUNT=[0-9]\+$" \ "^SNAPCRAFT_PROJECT_DIR=${root}$" \ diff --git a/tests/spread/core24/python-hello/classic/snap/snapcraft.yaml b/tests/spread/core24/python-hello/classic/snap/snapcraft.yaml index bfac77d1a7..d9d88dff4d 100644 --- a/tests/spread/core24/python-hello/classic/snap/snapcraft.yaml +++ b/tests/spread/core24/python-hello/classic/snap/snapcraft.yaml @@ -16,6 +16,8 @@ parts: hello: plugin: python source: src + python-packages: + - black build-attributes: - enable-patchelf stage-packages: @@ -23,3 +25,4 @@ parts: - libpython3.12-stdlib - python3.12-minimal - python3.12-venv + - python3-minimal # (for the "python3" symlink) diff --git a/tests/spread/core24/python-hello/src/hello/__init__.py b/tests/spread/core24/python-hello/src/hello/__init__.py index e3095b2229..de8c28b98f 100644 --- a/tests/spread/core24/python-hello/src/hello/__init__.py +++ b/tests/spread/core24/python-hello/src/hello/__init__.py @@ -1,2 +1,5 @@ +import black + + def main(): - print("hello world") + print(f"hello world! black version: {black.__version__}") diff --git a/tests/spread/core24/python-hello/strict/snap/snapcraft.yaml b/tests/spread/core24/python-hello/strict/snap/snapcraft.yaml index 2192e0e234..f52a1ca966 100644 --- a/tests/spread/core24/python-hello/strict/snap/snapcraft.yaml +++ b/tests/spread/core24/python-hello/strict/snap/snapcraft.yaml @@ -12,3 +12,5 @@ parts: hello: plugin: python source: src + python-packages: + - black diff --git a/tests/spread/core24/python-hello/task.yaml b/tests/spread/core24/python-hello/task.yaml index 11c3f18fd1..2db4043b9e 100644 --- a/tests/spread/core24/python-hello/task.yaml +++ b/tests/spread/core24/python-hello/task.yaml @@ -1,5 +1,10 @@ summary: Build and run Python-based snaps in core24 +systems: + # Must *not* run this on 24.04, which can give false-positives due to the + # presence of the system Python 3.12. + - ubuntu-22.04* + environment: PARAM/strict: "" PARAM/classic: "--classic" @@ -17,4 +22,4 @@ execute: | # shellcheck disable=SC2086 snap install python-hello-"${SPREAD_VARIANT}"_1.0_*.snap --dangerous ${PARAM} - python-hello-"${SPREAD_VARIANT}" | MATCH "hello world" + python-hello-"${SPREAD_VARIANT}" | MATCH "hello world! black version" diff --git a/tests/unit/parts/plugins/test_python_plugin.py b/tests/unit/parts/plugins/test_python_plugin.py index 36487cfcaa..c4cc654dca 100644 --- a/tests/unit/parts/plugins/test_python_plugin.py +++ b/tests/unit/parts/plugins/test_python_plugin.py @@ -17,7 +17,7 @@ from textwrap import dedent import pytest -from craft_parts import Part, PartInfo, ProjectInfo, errors +from craft_parts import Part, PartInfo, ProjectInfo, Step, StepInfo, errors from snapcraft.parts.plugins import PythonPlugin @@ -190,3 +190,41 @@ def test_get_system_python_interpreter_unknown_base(confinement, new_dir): expected_error = "Don't know which interpreter to use for base core10" with pytest.raises(errors.PartsError, match=expected_error): plugin._get_system_python_interpreter() + + +@pytest.mark.parametrize("home_attr", ["part_install_dir", "stage_dir"]) +def test_fix_pyvenv(new_dir, home_attr): + part_info = PartInfo( + project_info=ProjectInfo( + application_name="test", + project_name="test-snap", + base="core24", + confinement="classic", + project_base="core24", + cache_dir=new_dir, + ), + part=Part("my-part", {"plugin": "python"}), + ) + + prime_dir = part_info.prime_dir + prime_dir.mkdir() + + pyvenv = prime_dir / "pyvenv.cfg" + pyvenv.write_text( + dedent( + f"""\ + home = {getattr(part_info, home_attr)}/usr/bin + include-system-site-packages = false + version = 3.12.3 + executable = /root/parts/my-part/install/usr/bin/python3.12 + command = /root/parts/my-part/install/usr/bin/python3 -m venv /root/parts/my-part/install + """ + ) + ) + + step_info = StepInfo(part_info, Step.PRIME) + + PythonPlugin.post_prime(step_info) + + new_contents = pyvenv.read_text() + assert "home = /snap/test-snap/current/usr/bin" in new_contents diff --git a/tests/unit/services/test_lifecycle.py b/tests/unit/services/test_lifecycle.py index 6eb47794af..3c7d63de10 100644 --- a/tests/unit/services/test_lifecycle.py +++ b/tests/unit/services/test_lifecycle.py @@ -43,7 +43,10 @@ def test_lifecycle_installs_base(lifecycle_service, mocker): ) -def test_post_prime_no_patchelf(fp, tmp_path, lifecycle_service): +def test_post_prime_no_patchelf(fp, tmp_path, lifecycle_service, default_project): + new_attrs = {"parts": {"my-part": {"plugin": "nil"}}} + default_project.__dict__.update(**new_attrs) + mock_step_info = mock.Mock() mock_step_info.configure_mock( **{ @@ -51,6 +54,7 @@ def test_post_prime_no_patchelf(fp, tmp_path, lifecycle_service): "build_attributes": [], "state.files": ["usr/bin/ls"], "prime_dir": tmp_path / "prime", + "part_name": "my-part", } ) @@ -82,7 +86,7 @@ def test_post_prime_patchelf( use_system_libs, ): patchelf_spy = mocker.spy(snapcraft.parts, "patch_elf") - new_attrs = {"confinement": confinement} + new_attrs = {"confinement": confinement, "parts": {"my-part": {"plugin": "nil"}}} default_project.__dict__.update(**new_attrs) mock_step_info = mock.Mock() @@ -92,6 +96,7 @@ def test_post_prime_patchelf( "build_attributes": ["enable-patchelf"], "state.files": ["usr/bin/ls"], "prime_dir": tmp_path / "prime", + "part_name": "my-part", } ) @@ -207,6 +212,7 @@ def test_lifecycle_custom_arguments( assert info.project_base == expected_base assert info.confinement == expected_confinement + assert info.project_name == default_project.name == "default" @pytest.mark.usefixtures("default_project") From f57f7e0c05f0f91703a6e825a7b94a6b16bb577c Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 31 Jul 2024 13:21:06 -0500 Subject: [PATCH 015/151] docs: exclude overlay step Signed-off-by: Callahan Kovacs --- docs/conf.py | 2 ++ docs/explanation/index.rst | 3 +-- docs/explanation/parts.rst | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 docs/explanation/parts.rst diff --git a/docs/conf.py b/docs/conf.py index 9e456ae914..264a6c5c60 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,6 +66,8 @@ # errors). "common/craft-parts/explanation/overlay_parameters.rst", "common/craft-parts/explanation/overlays.rst", + "common/craft-parts/explanation/how_parts_are_built.rst", + "common/craft-parts/explanation/parts.rst", "common/craft-parts/how-to/craftctl.rst", "common/craft-parts/reference/parts_steps.rst", "common/craft-parts/reference/step_execution_environment.rst", diff --git a/docs/explanation/index.rst b/docs/explanation/index.rst index 3c68fa674e..1ee150cbec 100644 --- a/docs/explanation/index.rst +++ b/docs/explanation/index.rst @@ -11,7 +11,6 @@ Explanation components remote-build /common/craft-parts/explanation/filesets - /common/craft-parts/explanation/parts - /common/craft-parts/explanation/how_parts_are_built + parts /common/craft-parts/explanation/lifecycle /common/craft-parts/explanation/dump_plugin diff --git a/docs/explanation/parts.rst b/docs/explanation/parts.rst new file mode 100644 index 0000000000..dc37b78f81 --- /dev/null +++ b/docs/explanation/parts.rst @@ -0,0 +1,10 @@ +.. include:: /common/craft-parts/explanation/parts.rst + :end-before: _include-how-parts-are-built: +.. + exclude the overlay step explanation from craft-parts + +.. include:: /common/craft-parts/explanation/how_parts_are_built.rst + :end-before: _overlay-step-begin: + +.. include:: /common/craft-parts/explanation/how_parts_are_built.rst + :start-after: _overlay-step-end: From de13316cb155c91dfd53b43d25ecbd4af96299ad Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 1 Aug 2024 14:47:47 -0500 Subject: [PATCH 016/151] build(deps): bump craft-parts to 1.34.0 Signed-off-by: Callahan Kovacs --- docs/requirements.txt | 2 +- requirements-devel.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index e9980e7bb8..c7df884ad8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -17,7 +17,7 @@ craft-application==3.1.0 craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 -craft-parts==1.33.0 +craft-parts==1.34.0 craft-providers==1.23.1 craft-store==2.6.2 cryptography==42.0.5 diff --git a/requirements-devel.txt b/requirements-devel.txt index 1b99bdf94d..06da4c67d5 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -21,7 +21,7 @@ craft-application==3.1.0 craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 -craft-parts==1.33.0 +craft-parts==1.34.0 craft-providers==1.23.1 craft-store==2.6.2 cryptography==42.0.5 diff --git a/requirements.txt b/requirements.txt index e9979ff7e4..ab46ea4a8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ craft-application==3.1.0 craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 -craft-parts==1.33.0 +craft-parts==1.34.0 craft-providers==1.23.1 craft-store==2.6.2 cryptography==42.0.5 diff --git a/setup.py b/setup.py index f9bcbc9ccf..49b46b6cd6 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def recursive_data_files(directory, install_directory): "craft-archives", "craft-cli>=2.6.0", "craft-grammar", - "craft-parts>=1.33.0", + "craft-parts>=1.34.0", "craft-providers", "craft-store", "docutils<0.20", # Frozen until we can update sphinx dependencies. From f8c1ab46d00ea4a34eb2ac9ef8589a67eeba98ba Mon Sep 17 00:00:00 2001 From: Jeremie Deray Date: Fri, 2 Aug 2024 20:20:31 +0200 Subject: [PATCH 017/151] tests: test ROS 2 Jazzy content sharing on arm64 (#4940) Enable ROS 2 Jazzy (core24) content-sharing spread tests for arm64 (now that the content sharing snaps are available for that arch). Signed-off-by: artivis --- tests/spread/extensions/ros2-jazzy-meta-hello/task.yaml | 2 +- .../spread/extensions/ros2-jazzy-meta-talker-listener/task.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/spread/extensions/ros2-jazzy-meta-hello/task.yaml b/tests/spread/extensions/ros2-jazzy-meta-hello/task.yaml index 5a5569da6b..9aaa7599d0 100644 --- a/tests/spread/extensions/ros2-jazzy-meta-hello/task.yaml +++ b/tests/spread/extensions/ros2-jazzy-meta-hello/task.yaml @@ -28,7 +28,7 @@ systems: - ubuntu-24.04 - ubuntu-24.04-64 - ubuntu-24.04-amd64 - # - ubuntu-24.04-arm64 + - ubuntu-24.04-arm64 prepare: | #shellcheck source=tests/spread/tools/snapcraft-yaml.sh diff --git a/tests/spread/extensions/ros2-jazzy-meta-talker-listener/task.yaml b/tests/spread/extensions/ros2-jazzy-meta-talker-listener/task.yaml index 92926510a5..6ea9ba98dd 100644 --- a/tests/spread/extensions/ros2-jazzy-meta-talker-listener/task.yaml +++ b/tests/spread/extensions/ros2-jazzy-meta-talker-listener/task.yaml @@ -27,7 +27,7 @@ systems: - ubuntu-24.04 - ubuntu-24.04-64 - ubuntu-24.04-amd64 - # - ubuntu-24.04-arm64 + - ubuntu-24.04-arm64 prepare: | #shellcheck source=tests/spread/tools/snapcraft-yaml.sh From c12aaaf74443c1b5ed60cd1646c14e4561634746 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 5 Aug 2024 11:46:53 -0500 Subject: [PATCH 018/151] docs(changelog): add 8.3.2 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index c64516bea5..c473928b31 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -77,6 +77,40 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. + +8.3.2 (2024-Aug-05) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a bug where classic snaps with a Python virtual environment would attempt + to use the system's Python intepretter (`#4942`_). + +Plugins +####### + +Kernel +"""""" + +* Fix a bug where removing a missing symlink would cause the kernel plugin + to fail. + +Store +===== + +* Fix a bug where ``edit-validation-sets`` would fail when editing a validation + sets with snap revisions (`#4909`_). + +For a complete list of commits, check out the `8.3.2`_ release on GitHub. + + 8.3.1 (2024-Jul-08) ------------------- @@ -964,6 +998,8 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4886: https://github.com/canonical/snapcraft/issues/4886 .. _#4889: https://github.com/canonical/snapcraft/issues/4889 .. _#4890: https://github.com/canonical/snapcraft/issues/4890 +.. _#4909: https://github.com/canonical/snapcraft/issues/4909 +.. _#4942: https://github.com/canonical/snapcraft/issues/4942 .. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 .. _8.0.1: https://github.com/canonical/snapcraft/releases/tag/8.0.1 @@ -987,3 +1023,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.2.12: https://github.com/canonical/snapcraft/releases/tag/8.2.12 .. _8.3.0: https://github.com/canonical/snapcraft/releases/tag/8.3.0 .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 +.. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 From 8ab7fd0c8a1d3f13045bec41a6e0158c063faa9b Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 6 Aug 2024 10:40:09 -0500 Subject: [PATCH 019/151] feat: allow numbers in component names (#4945) - Allow for digits in component names. - Refactor to use same validation for snap and component names. - Begin snap and component names errors with a lowercase character. --------- Signed-off-by: Callahan Kovacs --- snapcraft/models/project.py | 63 ++++++++++++++++-------------- tests/unit/models/test_projects.py | 51 ++++++++++++------------ 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 258b5aba23..5db1c0c66e 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -238,26 +238,44 @@ def _validate_version_name(version: str, model_name: str) -> None: ) -def _validate_component_name(name: str) -> None: - """Validate a component name.""" - if not re.fullmatch(r"[a-z-]*[a-z][a-z-]*", name): - raise ValueError( - "Component names can only use ASCII lowercase letters and hyphens" - ) +def _validate_name(*, name: str, field_name: str) -> str: + """Validate a name. - if name.startswith("snap-"): + :param name: The name to validate. + :param field_name: The name of the field being validated. + + :returns: The validated name. + """ + if not re.match(r"^[a-z0-9-]*[a-z][a-z0-9-]*$", name): raise ValueError( - "Component names cannot start with the reserved namespace 'snap-'" + f"{field_name} names can only use lowercase alphanumeric " + "and hyphens and must have at least one letter" ) if name.startswith("-"): - raise ValueError("Component names cannot start with a hyphen") + raise ValueError(f"{field_name} names cannot start with a hyphen") if name.endswith("-"): - raise ValueError("Component names cannot end with a hyphen") + raise ValueError(f"{field_name} names cannot end with a hyphen") if "--" in name: - raise ValueError("Component names cannot have two hyphens in a row") + raise ValueError(f"{field_name} names cannot have two hyphens in a row") + + return name + + +def _validate_component(name: str) -> str: + """Validate a component name. + + :param name: The component name to validate. + + :returns: The validated component name. + """ + if name.startswith("snap-"): + raise ValueError( + "component names cannot start with the reserved prefix 'snap-'" + ) + return _validate_name(name=name, field_name="component") def _get_partitions_from_components( @@ -731,23 +749,8 @@ def _validate_mandatory_base(cls, values): @pydantic.validator("name") @classmethod - def _validate_name(cls, name): - if not re.match(r"^[a-z0-9-]*[a-z][a-z0-9-]*$", name): - raise ValueError( - "Snap names can only use ASCII lowercase letters, numbers, and hyphens, " - "and must have at least one letter" - ) - - if name.startswith("-"): - raise ValueError("Snap names cannot start with a hyphen") - - if name.endswith("-"): - raise ValueError("Snap names cannot end with a hyphen") - - if "--" in name: - raise ValueError("Snap names cannot have two hyphens in a row") - - return name + def _validate_snap_name(cls, name): + return _validate_name(name=name, field_name="snap") @pydantic.validator("version") @classmethod @@ -764,7 +767,7 @@ def _validate_version(cls, version, values): def _validate_components(cls, components): """Validate component names.""" for component_name in components.keys(): - _validate_component_name(component_name) + _validate_component(name=component_name) return components @@ -1077,7 +1080,7 @@ class ComponentProject(models.CraftBaseModel, extra=pydantic.Extra.ignore): def _validate_components(cls, components): """Validate component names.""" for component_name in components.keys(): - _validate_component_name(component_name) + _validate_component(name=component_name) return components diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index a8d1305021..136b4bd137 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -235,13 +235,13 @@ def test_project_name_valid(self, name, project_yaml_data): @pytest.mark.parametrize( "name,error", [ - ("name_with_underscores", "Snap names can only use"), - ("name-with-UPPERCASE", "Snap names can only use"), - ("name with spaces", "Snap names can only use"), - ("-name-starts-with-hyphen", "Snap names cannot start with a hyphen"), - ("name-ends-with-hyphen-", "Snap names cannot end with a hyphen"), - ("name-has--two-hyphens", "Snap names cannot have two hyphens in a row"), - ("123456", "Snap names can only use"), + ("name_with_underscores", "snap names can only use"), + ("name-with-UPPERCASE", "snap names can only use"), + ("name with spaces", "snap names can only use"), + ("-name-starts-with-hyphen", "snap names cannot start with a hyphen"), + ("name-ends-with-hyphen-", "snap names cannot end with a hyphen"), + ("name-has--two-hyphens", "snap names cannot have two hyphens in a row"), + ("123456", "snap names can only use"), ( "a2345678901234567890123456789012345678901", "ensure this value has at most 40 characters", @@ -2490,7 +2490,15 @@ def test_component_type_invalid( project.unmarshal(project_yaml_data(components=component)) @pytest.mark.parametrize( - "name", ["name", "name-with-dashes", "x" * 40, "foo-snap-bar"] + "name", + [ + "name", + "name-with-dashes", + "name-with-numbers-0123", + "0123-name-with-numbers", + "x" * 40, + "foo-snap-bar", + ], ) def test_component_name_valid( self, project, name, project_yaml_data, stub_component_data @@ -2505,26 +2513,21 @@ def test_component_name_valid( @pytest.mark.parametrize( "name,error", [ - ( - "snap-", - "Component names cannot start with the reserved namespace 'snap-'", - ), - ( + pytest.param( "snap-foo", - "Component names cannot start with the reserved namespace 'snap-'", + "component names cannot start with the reserved prefix 'snap-'", + id="reserved prefix", ), - ("123456", "Component names can only use"), - ("name-ends-with-digits-0123", "Component names can only use"), - ("456-name-starts-with-digits", "Component names can only use"), - ("name-789-contains-digits", "Component names can only use"), - ("name_with_underscores", "Component names can only use"), - ("name-with-UPPERCASE", "Component names can only use"), - ("name with spaces", "Component names can only use"), - ("-name-starts-with-hyphen", "Component names cannot start with a hyphen"), - ("name-ends-with-hyphen-", "Component names cannot end with a hyphen"), + pytest.param("123456", "component names can only use", id="no letters"), + ("name_with_underscores", "component names can only use"), + ("name-with-UPPERCASE", "component names can only use"), + ("name with spaces", "component names can only use"), + ("name-with-$symbols", "component names can only use"), + ("-name-starts-with-hyphen", "component names cannot start with a hyphen"), + ("name-ends-with-hyphen-", "component names cannot end with a hyphen"), ( "name-has--two-hyphens", - "Component names cannot have two hyphens in a row", + "component names cannot have two hyphens in a row", ), ("x" * 41, "ensure this value has at most 40 characters"), ], From e6ddd784c777c335c64aa3f98179b1949ca9ec7e Mon Sep 17 00:00:00 2001 From: ohemorange Date: Mon, 12 Aug 2024 12:51:14 -0700 Subject: [PATCH 020/151] feat(remotebuild): add "pending" status (#4956) --- snapcraft/commands/remote.py | 5 +++++ tests/unit/commands/test_remote.py | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index 4ee6b70261..b8892917a0 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -257,6 +257,7 @@ def _monitor_and_complete( # noqa: PLR0912 (too many branches) building: set[str] = set() succeeded: set[str] = set() uploading: set[str] = set() + pending: set[str] = set() not_building: set[str] = set() for arch, build_state in states.items(): if build_state.is_running: @@ -265,6 +266,8 @@ def _monitor_and_complete( # noqa: PLR0912 (too many branches) succeeded.add(arch) elif build_state == BuildState.UPLOADING: uploading.add(arch) + elif build_state == BuildState.PENDING: + pending.add(arch) else: not_building.add(arch) progress_parts: list[str] = [] @@ -276,6 +279,8 @@ def _monitor_and_complete( # noqa: PLR0912 (too many branches) progress_parts.append("Uploading: " + ",".join(sorted(uploading))) if succeeded: progress_parts.append("Succeeded: " + ", ".join(sorted(succeeded))) + if pending: + progress_parts.append("Pending: " + ", ".join(sorted(pending))) emit.progress("; ".join(progress_parts)) except TimeoutError: if build_id: diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index 389ff2dad2..fe2551affc 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -743,6 +743,7 @@ def test_monitor_build(mocker, emitter, snapcraft_yaml, base, fake_services): {"amd64": launchpad.models.BuildState.PENDING}, {"amd64": launchpad.models.BuildState.BUILDING}, {"amd64": launchpad.models.BuildState.UPLOADING}, + {"amd64": launchpad.models.BuildState.SUPERSEDED}, {"amd64": launchpad.models.BuildState.SUCCESS}, ] ], @@ -774,9 +775,10 @@ def test_monitor_build(mocker, emitter, snapcraft_yaml, base, fake_services): mock_fetch_artifacts.assert_called_once() mock_cleanup.assert_called_once() - emitter.assert_progress("Stopped: amd64") + emitter.assert_progress("Pending: amd64") emitter.assert_progress("Building: amd64") emitter.assert_progress("Uploading: amd64") + emitter.assert_progress("Stopped: amd64") emitter.assert_progress("Succeeded: amd64") From 9501e45adef9f325bfe01eba93eb93b05d45baae Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Tue, 13 Aug 2024 13:42:40 -0300 Subject: [PATCH 021/151] fix: use proper environment for tics Signed-off-by: Sergio Schvezov --- .github/workflows/tics.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tics.yaml b/.github/workflows/tics.yaml index 273c40ed1e..b3bd736492 100644 --- a/.github/workflows/tics.yaml +++ b/.github/workflows/tics.yaml @@ -29,13 +29,15 @@ jobs: echo "::endgroup::" - name: Setup Tox environment - run: tox run-parallel --parallel auto --parallel-no-spinner --parallel-live --colored yes -e test-all-py310 --notest + run: tox --workdir /tmp/tox run-parallel --parallel auto --parallel-no-spinner --parallel-live --colored yes -e test-all-py310 --notest - name: Test with tox - run: tox run --skip-pkg-install --result-json results/tox-py310.json --colored yes -e test-all-py310 + run: tox --workdir /tmp/tox run --skip-pkg-install --result-json results/tox-py310.json --colored yes -e test-all-py310 - name: Run TICS analysis uses: tiobe/tics-github-action@v3 + env: + PATH: "/tmp/tox/test-all-py310/bin:/snap/bin:/home/runner/.local/bin:/home/runner/.cargo/bin:/bin:/usr/bin:/usr/local/bin:" with: mode: qserver project: snapcraft From 2ab389abbc1e23e4e693be1e0b55276b9ab43a88 Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Tue, 13 Aug 2024 15:32:06 -0300 Subject: [PATCH 022/151] ci: add pylint for Tiobe TiCS Signed-off-by: Sergio Schvezov --- requirements-devel.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 06da4c67d5..4450106012 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -4,7 +4,7 @@ PyNaCl==1.5.0 PyYAML==6.0.1 SecretStorage==3.3.3 WebOb==1.8.7 -astroid==2.15.8 +astroid==3.2.4 attrs==23.2.0 black==24.3.0 cachetools==5.3.3 @@ -26,6 +26,7 @@ craft-providers==1.23.1 craft-store==2.6.2 cryptography==42.0.5 dill==0.3.8 +dill==0.3.8 distlib==0.3.8 distro==1.9.0 docutils==0.19 @@ -38,6 +39,7 @@ hupper==1.12.1 idna==3.7 importlib-metadata==7.0.1 iniconfig==2.0.0 +isort==5.13.2 jaraco.classes==3.3.1 jeepney==0.8.0 jsonschema==2.5.1 @@ -60,7 +62,7 @@ pbr==6.0.0 pexpect==4.9.0 plaster-pastedeploy==1.0.1 plaster==1.1.2 -platformdirs==4.1.0 +platformdirs==4.2.2 pluggy==1.3.0 progressbar==2.5 protobuf==3.20.3 @@ -77,6 +79,7 @@ pyflakes==3.2.0 pyftpdlib==1.5.9 pygit2==1.13.3 pyinstaller==5.13.2; sys.platform == "win32" +pylint==3.2.6 pylxd==2.3.2 pymacaroons==0.13.0 pyparsing==3.1.2 @@ -105,7 +108,8 @@ testscenarios==0.5.0 testtools==2.7.1 tinydb==4.8.0 toml==0.10.2 -tomlkit==0.12.4 +tomli==2.0.1 +tomlkit==0.13.0 tox==4.11.4 translationstring==1.4 types-Deprecated==1.2.9.20240311 @@ -116,7 +120,7 @@ types-simplejson==3.19.0.20240310 types-tabulate==0.9.0.20240106 types-toml==0.10.8.20240310 types-urllib3==1.26.25.14 -typing_extensions==4.9.0 +typing-extensions==4.12.2 urllib3==1.26.19 validators==0.28.3 venusian==3.1.0 From be6da044be29c97157107adbbaebddeccdd3ffa5 Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Tue, 13 Aug 2024 15:33:06 -0300 Subject: [PATCH 023/151] chore: add flake8 and pylint to devel requirements Signed-off-by: Sergio Schvezov --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 49b46b6cd6..beb6536363 100755 --- a/setup.py +++ b/setup.py @@ -64,6 +64,8 @@ def recursive_data_files(directory, install_directory): "coverage[toml]", "pyflakes", "fixtures", + # For Tiobe TiCS + "flake8", "mccabe", "mypy", "testscenarios", @@ -73,6 +75,8 @@ def recursive_data_files(directory, install_directory): "pydocstyle", "pyftpdlib", "pyinstaller; sys_platform == 'win32'", + # For Tiobe TiCS + "pylint", "pyramid", "pytest", "pytest-cov", From 0fc6b5d9d40023125d9c3bf692874c1b717c873f Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 4 Apr 2024 13:02:12 -0400 Subject: [PATCH 024/151] ci: adjust tags for spread runs This ensures we can use any machine with the spread-installed tag. Necessary for upcoming changes to Canonical's self-hosted runners --- .github/workflows/spread-scheduled.yaml | 4 ++-- .github/workflows/spread.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/spread-scheduled.yaml b/.github/workflows/spread-scheduled.yaml index 35abbfc3fe..1f0c7305ec 100644 --- a/.github/workflows/spread-scheduled.yaml +++ b/.github/workflows/spread-scheduled.yaml @@ -24,7 +24,7 @@ jobs: path: ${{ steps.snapcraft.outputs.snap }} kernel-plugins: - runs-on: self-hosted + runs-on: [spread-installed] needs: [snap-build] strategy: fail-fast: false @@ -47,4 +47,4 @@ jobs: name: snap - name: Kernel plugin test run: | - spread google:ubuntu-22.04-64:tests/spread/plugin/${{ matrix.type }}/kernel + spread google:ubuntu-22.04-64:tests/spread/plugins/${{ matrix.type }}/kernel diff --git a/.github/workflows/spread.yml b/.github/workflows/spread.yml index 2e77a9bb53..332b30f59d 100644 --- a/.github/workflows/spread.yml +++ b/.github/workflows/spread.yml @@ -27,7 +27,7 @@ jobs: sudo snap install --dangerous --classic ${{ steps.build-snapcraft.outputs.snap }} integration-spread-tests: - runs-on: self-hosted + runs-on: [spread-installed] needs: build strategy: # FIXME: enable fail-fast mode once spread can cancel an executing job. @@ -70,7 +70,7 @@ jobs: done integration-spread-tests-store: - runs-on: self-hosted + runs-on: [spread-installed] needs: build strategy: # FIXME: enable fail-fast mode once spread can cancel an executing job. From e3c8e878eb0b321cfba174c0e0699eac647b200c Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 26 Sep 2023 18:56:01 -0400 Subject: [PATCH 025/151] chore(tox): exclude submodules from spread-shellcheck (#4240) --- tox.ini | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 613d7c5e93..2094a941e6 100644 --- a/tox.ini +++ b/tox.ini @@ -81,8 +81,9 @@ env_dir = {work_dir}/linting runner = ignore_env_name_mismatch [shellcheck] -find = git ls-files -filter = file --mime-type -Nnf- | grep shellscript | cut -f1 -d: +find = git ls-files --empty-directory --deduplicate +filter = file --mime-type -Nnf- | grep shellscript$ | cut -f1 -d: +spread_filter = grep -E "^tests/spread/.*(spread|task)\.yaml$" [testenv:lint-{black,ruff,docstyle,isort,shellcheck,codespell,yaml}] description = Lint the source code @@ -92,6 +93,7 @@ allowlist_externals = shellcheck: bash, xargs commands_pre = shellcheck: bash -c '{[shellcheck]find} | {[shellcheck]filter} > {env_tmp_dir}/shellcheck_files' + shellcheck: bash -c '{[shellcheck]find} | {[shellcheck]spread_filter} > {env_tmp_dir}/spread_shellcheck_files' commands = black: black --check --diff {tty:--color} {posargs} . ruff: ruff --diff --respect-gitignore setup.py snapcraft tests tools @@ -99,7 +101,7 @@ commands = isort: isort --check snapcraft snapcraft_legacy tests setup.py tools docstyle: pydocstyle snapcraft shellcheck: xargs -ra {env_tmp_dir}/shellcheck_files shellcheck - shellcheck: python3 tools/spread-shellcheck.py spread.yaml tests/spread/ + shellcheck: xargs -ra {env_tmp_dir}/spread_shellcheck_files python3 tools/spread-shellcheck.py spread.yaml codespell: codespell --toml {tox_root}/pyproject.toml {posargs} yaml: yamllint {posargs} . From 8aa87427a16344fc81d61d2136b7aa8025556775 Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 16 Jul 2024 13:56:22 -0500 Subject: [PATCH 026/151] tests: disable `build-base: devel` spread tests (#4922) Disable `build-base: devel` spread tests until we have the resources to debug the underlying issue with 24.10 buildd images and `systemd-resolved` (#4921). We should be able to address this issue in 2024-Nov. Fixes #4910 (CRAFT-3105) Signed-off-by: Callahan Kovacs --- spread.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spread.yaml b/spread.yaml index 7ac0f6d7b6..5e13814f27 100644 --- a/spread.yaml +++ b/spread.yaml @@ -339,10 +339,11 @@ suites: tests/spread/general/hooks/: summary: tests of snapcraft hook functionality - tests/spread/core-devel/: - summary: tests of devel base snaps - environment: - SNAPCRAFT_BUILD_ENVIRONMENT: "" +# 'build-base: devel' fails due to an issue with the 24.10 buildd image (#4921) +# tests/spread/core-devel/: +# summary: tests of devel base snaps +# environment: +# SNAPCRAFT_BUILD_ENVIRONMENT: "" # General, core suite tests/spread/cross-compile/: From 97c189b6c4abd2d43920b09b6b791f77dad52cf7 Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 1 May 2024 07:38:22 -0500 Subject: [PATCH 027/151] tests(spread): drop check for specific files in ppa tests (#4772) The source package has changed and the test isn't particularly important. Signed-off-by: Callahan Kovacs --- .../package-repositories/test-multi-keys/snap/snapcraft.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/spread/core22/package-repositories/test-multi-keys/snap/snapcraft.yaml b/tests/spread/core22/package-repositories/test-multi-keys/snap/snapcraft.yaml index 19fe27377d..b5035779e3 100644 --- a/tests/spread/core22/package-repositories/test-multi-keys/snap/snapcraft.yaml +++ b/tests/spread/core22/package-repositories/test-multi-keys/snap/snapcraft.yaml @@ -14,10 +14,6 @@ parts: stage-packages: - test-ppa - puppet-tools-release # Comes from the Puppet repo - override-build: | - craftctl default - # This file comes from the puppet-tools-release package. - test -f ${CRAFT_PART_INSTALL}/usr/share/doc/puppet-tools-release/bill-of-materials apps: test-ppa: From 532178172e45e11dc180f08606e42ce6b49bd2c4 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 16 May 2024 12:26:33 +0200 Subject: [PATCH 028/151] tests: fix flutter spread test (#4808) Signed-off-by: Callahan Kovacs --- .../craft-parts/build-and-run-hello/flutter-hello/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/flutter-hello/lib/main.dart b/tests/spread/plugins/craft-parts/build-and-run-hello/flutter-hello/lib/main.dart index e2ca568b72..410f446451 100644 --- a/tests/spread/plugins/craft-parts/build-and-run-hello/flutter-hello/lib/main.dart +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/flutter-hello/lib/main.dart @@ -100,7 +100,7 @@ class _MyHomePageState extends State { ), Text( '$_counter', - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineSmall, ), ], ), From 23faa0f6323c16e40b3f425d0e48909b59324cf6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Jan 2024 11:25:36 -0600 Subject: [PATCH 029/151] build(deps): update github actions (major) (#4500) Signed-off-by: Callahan Kovacs Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Callahan Kovacs --- .github/workflows/cla-check.yaml | 2 +- .github/workflows/publish.yaml | 2 +- .github/workflows/spread-scheduled.yaml | 8 ++++---- .github/workflows/spread.yml | 12 ++++++------ .github/workflows/tox.yaml | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/cla-check.yaml b/.github/workflows/cla-check.yaml index 612d89a475..a5466fc3e6 100644 --- a/.github/workflows/cla-check.yaml +++ b/.github/workflows/cla-check.yaml @@ -3,7 +3,7 @@ on: [pull_request] jobs: cla-check: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Check if CLA signed uses: canonical/has-signed-canonical-cla@v1 diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 75d70c7f32..0f49e95406 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -20,7 +20,7 @@ jobs: - if: steps.decisions.outputs.PUBLISH == 'true' name: Checkout Snapcraft - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Fetch all of history so Snapcraft can determine its own version from git. fetch-depth: 0 diff --git a/.github/workflows/spread-scheduled.yaml b/.github/workflows/spread-scheduled.yaml index 1f0c7305ec..f7b95039e3 100644 --- a/.github/workflows/spread-scheduled.yaml +++ b/.github/workflows/spread-scheduled.yaml @@ -11,14 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Build snap uses: snapcore/action-build@v1 id: snapcraft - name: Upload snap artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: snap path: ${{ steps.snapcraft.outputs.snap }} @@ -37,12 +37,12 @@ jobs: rm -rf "${{ github.workspace }}" mkdir "${{ github.workspace }}" - name: Checkout snapcraft - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true - name: Download snap artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: snap - name: Kernel plugin test diff --git a/.github/workflows/spread.yml b/.github/workflows/spread.yml index 332b30f59d..bcabc746d6 100644 --- a/.github/workflows/spread.yml +++ b/.github/workflows/spread.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Checkout snapcraft - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -17,7 +17,7 @@ jobs: uses: snapcore/action-build@v1 - name: Upload snapcraft snap - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: snap path: ${{ steps.build-snapcraft.outputs.snap }} @@ -44,13 +44,13 @@ jobs: steps: - name: Checkout snapcraft - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true - name: Download snapcraft snap - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: snap path: tests @@ -94,14 +94,14 @@ jobs: - if: steps.decisions.outputs.RUN == 'true' name: Checkout snapcraft - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true - if: steps.decisions.outputs.RUN == 'true' name: Download snapcraft snap - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: snap path: tests diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 11394bb2a0..85d94c4a6c 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -13,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install dependencies @@ -57,11 +57,11 @@ jobs: tox_python: py310 runs-on: ${{ matrix.platform }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python version ${{ matrix.python_version }} on ${{ matrix.platform }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} - name: Install dependencies @@ -87,7 +87,7 @@ jobs: files: coverage*.xml - name: Upload test results if: success() || failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.platform }} path: results/ From 78b7227b2c4e2f34b7068d2e89cb198c96d2fa3d Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 14 Aug 2024 08:26:37 -0500 Subject: [PATCH 030/151] ci: fix actions/upload-artifact Signed-off-by: Callahan Kovacs --- .github/workflows/tox.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 85d94c4a6c..5e603f47a3 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -79,7 +79,7 @@ jobs: - name: Setup Tox environments run: tox run-parallel --parallel auto --parallel-no-spinner --parallel-live -e test-${{ matrix.tox_python }},test-legacy-${{ matrix.tox_python }} --notest - name: Test with tox - run: tox run --skip-pkg-install --result-json results/tox-${{ matrix.platform }}.json -e test-${{ matrix.tox_python }},test-legacy-${{ matrix.tox_python }} + run: tox run --skip-pkg-install --result-json results/tox-${{ matrix.platform }}-${{ matrix.python_version }}.json -e test-${{ matrix.tox_python }},test-legacy-${{ matrix.tox_python }} - name: Upload code coverage uses: codecov/codecov-action@v3 with: @@ -89,5 +89,5 @@ jobs: if: success() || failure() uses: actions/upload-artifact@v4 with: - name: test-results-${{ matrix.platform }} + name: test-results-${{ matrix.platform }}-${{ matrix.python_version }} path: results/ From a41ff9a0d3909a8d6de647b2c3ecbe52df351c2f Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 13 Aug 2024 10:32:53 -0500 Subject: [PATCH 031/151] tests(spread): fix core18 flutter test Signed-off-by: Callahan Kovacs --- tests/spread/plugins/v1/flutter/run/task.yaml | 3 ++- .../plugins/v1/flutter/snaps/flutter_hello/lib/main.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/spread/plugins/v1/flutter/run/task.yaml b/tests/spread/plugins/v1/flutter/run/task.yaml index 54d62bf5f1..64ee7789fc 100644 --- a/tests/spread/plugins/v1/flutter/run/task.yaml +++ b/tests/spread/plugins/v1/flutter/run/task.yaml @@ -3,7 +3,8 @@ summary: Build and run a basic snap using the flutter plugin and extension kill-timeout: 30m environment: - FLUTTER/dev: dev + # fails due to upstream bug: https://github.com/flutter/flutter/issues/133881 + # FLUTTER/dev: dev FLUTTER/master: master FLUTTER/beta: beta FLUTTER/stable: stable diff --git a/tests/spread/plugins/v1/flutter/snaps/flutter_hello/lib/main.dart b/tests/spread/plugins/v1/flutter/snaps/flutter_hello/lib/main.dart index e0160294b9..483cc7a70b 100644 --- a/tests/spread/plugins/v1/flutter/snaps/flutter_hello/lib/main.dart +++ b/tests/spread/plugins/v1/flutter/snaps/flutter_hello/lib/main.dart @@ -100,7 +100,7 @@ class _MyHomePageState extends State { ), Text( '$_counter', - style: Theme.of(context).textTheme.headline4, + style: Theme.of(context).textTheme.headlineSmall, ), ], ), From 217bbcd96c6c1f86efae764a5e30c336775230a1 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 13 Aug 2024 09:23:41 -0500 Subject: [PATCH 032/151] tests(spread): drop core24 spread test for snapcraft 7.x Signed-off-by: Callahan Kovacs --- spread.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spread.yaml b/spread.yaml index 5e13814f27..bc3fd17d8d 100644 --- a/spread.yaml +++ b/spread.yaml @@ -313,11 +313,6 @@ suites: systems: - ubuntu-22.04* - tests/spread/core24/: - summary: core24 tests - systems: - - ubuntu-22.04* - # General, core suite tests/spread/general/: summary: tests of snapcraft core functionality From 0795a741de59b427f6b4b1055f9d5206d0d43301 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 13 Aug 2024 10:32:14 -0500 Subject: [PATCH 033/151] tests(spread): set base for electron builder Signed-off-by: Callahan Kovacs --- .../electron-builder-hello-world/electron-builder.yml | 1 + tests/spread/electron-builder/no-template/task.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/spread/electron-builder/no-template/electron-builder-hello-world/electron-builder.yml b/tests/spread/electron-builder/no-template/electron-builder-hello-world/electron-builder.yml index 419624e0db..8b61508ecf 100644 --- a/tests/spread/electron-builder/no-template/electron-builder-hello-world/electron-builder.yml +++ b/tests/spread/electron-builder/no-template/electron-builder-hello-world/electron-builder.yml @@ -1,4 +1,5 @@ snap: + base: core18 confinement: strict buildPackages: - jq diff --git a/tests/spread/electron-builder/no-template/task.yaml b/tests/spread/electron-builder/no-template/task.yaml index 25a0220bb8..a6f9d2aa5c 100644 --- a/tests/spread/electron-builder/no-template/task.yaml +++ b/tests/spread/electron-builder/no-template/task.yaml @@ -12,7 +12,7 @@ environment: SNAPCRAFT_BUILD_INFO: "1" prepare: | - snap install node --classic + snap install node --classic --channel "19/stable" restore: | snap remove node From 92184dab97cdb71c9c07b43017545144b3c7c2f9 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 13 Aug 2024 13:33:07 -0500 Subject: [PATCH 034/151] tests(spread): fix ruby tests Signed-off-by: Callahan Kovacs --- .../v1/ruby/snaps/ruby-bundle-install/snap/snapcraft.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/spread/plugins/v1/ruby/snaps/ruby-bundle-install/snap/snapcraft.yaml b/tests/spread/plugins/v1/ruby/snaps/ruby-bundle-install/snap/snapcraft.yaml index 32e759cb36..a86c554962 100644 --- a/tests/spread/plugins/v1/ruby/snaps/ruby-bundle-install/snap/snapcraft.yaml +++ b/tests/spread/plugins/v1/ruby/snaps/ruby-bundle-install/snap/snapcraft.yaml @@ -16,6 +16,8 @@ parts: plugin: ruby source: . use-bundler: true - ruby-version: "2.6.10" # bundler requires ruby >= 2.6.0 + ruby-version: "3.3.4" # bundler requires ruby >= 3.0.0 build-environment: - BUNDLE_GEMFILE: "$SNAPCRAFT_PART_SRC/Gemfile" + build-packages: + - libyaml-dev From 9ba22855566970720d6fa498155d3e16bdf1f51a Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 14 Aug 2024 14:23:10 -0500 Subject: [PATCH 035/151] ci: use `ubuntu-latest` for cla check Signed-off-by: Callahan Kovacs --- .github/workflows/cla-check.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla-check.yaml b/.github/workflows/cla-check.yaml index a5466fc3e6..cdb271af63 100644 --- a/.github/workflows/cla-check.yaml +++ b/.github/workflows/cla-check.yaml @@ -3,7 +3,7 @@ on: [pull_request] jobs: cla-check: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Check if CLA signed uses: canonical/has-signed-canonical-cla@v1 From c9d9cc2275f231893592cd8b0ae0234db5104f50 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 15 Aug 2024 07:56:26 -0500 Subject: [PATCH 036/151] fix: configure icon for snaps without apps (#4950) Always configure and install icons for snaps even if they do not have any `apps` defined in their `snapcraft.yaml` file. Signed-off-by: Callahan Kovacs --- snapcraft/parts/setup_assets.py | 28 +++++++++++++-------------- tests/unit/parts/test_setup_assets.py | 19 +++++++++--------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/snapcraft/parts/setup_assets.py b/snapcraft/parts/setup_assets.py index cfacf41c08..a9cf57c7dd 100644 --- a/snapcraft/parts/setup_assets.py +++ b/snapcraft/parts/setup_assets.py @@ -72,9 +72,6 @@ def setup_assets( if kernel_yaml.exists(): _copy_file(kernel_yaml, meta_dir / "kernel.yaml") - if not project.apps: - return - icon_path = _finalize_icon( project.icon, assets_dir=assets_dir, gui_dir=gui_dir, prime_dir=prime_dir ) @@ -87,19 +84,20 @@ def setup_assets( emit.debug(f"relative icon path: {relative_icon_path!r}") - for app_name, app in project.apps.items(): - _validate_command_chain( - app.command_chain, name=f"app {app_name!r}", prime_dir=prime_dir - ) - - if app.desktop: - desktop_file = DesktopFile( - snap_name=project.name, - app_name=app_name, - filename=app.desktop, - prime_dir=prime_dir, + if project.apps: + for app_name, app in project.apps.items(): + _validate_command_chain( + app.command_chain, name=f"app {app_name!r}", prime_dir=prime_dir ) - desktop_file.write(gui_dir=gui_dir, icon_path=relative_icon_path) + + if app.desktop: + desktop_file = DesktopFile( + snap_name=project.name, + app_name=app_name, + filename=app.desktop, + prime_dir=prime_dir, + ) + desktop_file.write(gui_dir=gui_dir, icon_path=relative_icon_path) def _finalize_icon( diff --git a/tests/unit/parts/test_setup_assets.py b/tests/unit/parts/test_setup_assets.py index eaf75825b1..d6a578caf1 100644 --- a/tests/unit/parts/test_setup_assets.py +++ b/tests/unit/parts/test_setup_assets.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2017-2022 Canonical Ltd. +# Copyright 2017-2022,2024 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 @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os import textwrap from pathlib import Path from typing import Any, Dict @@ -310,18 +309,19 @@ def test_setup_assets_icon_in_assets_dir(self, desktop_file, yaml_data, new_dir) ) # icon file exists - Path("prime/snap/gui/icon.svg").is_file() + assert Path("prime/snap/gui/icon.svg").is_file() - def test_setup_assets_no_apps(self, desktop_file, yaml_data, new_dir): + def test_setup_assets_configure_icon_no_apps( + self, desktop_file, yaml_data, new_dir + ): + """Configure icon when no `apps` are defined.""" desktop_file("prime/test.desktop") - Path("prime/usr/share/icons").mkdir(parents=True) - Path("prime/usr/share/icons/icon.svg").touch() - Path("snap/gui").mkdir() + Path("snap/gui").mkdir(parents=True) + Path("snap/gui/icon.svg").touch() # define project project = Project.unmarshal(yaml_data({"adopt-info": "part"})) - # setting up assets does not crash setup_assets( project, assets_dir=Path("snap"), @@ -329,7 +329,8 @@ def test_setup_assets_no_apps(self, desktop_file, yaml_data, new_dir): prime_dir=Path("prime"), ) - assert os.listdir("prime/meta/gui") == [] + # icon file exists + assert Path("prime/snap/gui/icon.svg").is_file() def test_setup_assets_remote_icon(self, desktop_file, yaml_data, new_dir): # create primed tree (no icon) From 1fc8fe6ab7035d13280a8ea376883973d2bc7c45 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 5 Aug 2024 11:22:29 -0500 Subject: [PATCH 037/151] build(deps)!: use pydantic 2, freeze requirements Signed-off-by: Callahan Kovacs --- docs/requirements.txt | 22 +++--- requirements-devel.txt | 138 +++++++++++++++++++------------------ requirements.txt | 75 ++++++++++---------- setup.py | 18 ++--- snapcraft/elf/_elf_file.py | 6 +- 5 files changed, 134 insertions(+), 125 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c7df884ad8..96a36bf294 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,18 +8,19 @@ canonical-sphinx==0.1.0 canonical-sphinx-extensions==0.0.21 catkin-pkg==1.0.0 certifi==2024.7.4 -cffi==1.16.0 +cffi==1.17.0 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==3.1.0 -craft-archives==1.1.3 +craft-application==4.0.0 +craft-archives==2.0.0 craft-cli==2.6.0 -craft-grammar==1.2.0 -craft-parts==1.34.0 -craft-providers==1.23.1 -craft-store==2.6.2 +craft-grammar==2.0.0 +craft-parts==2.0.0 +craft-platforms==0.1.1 +craft-providers==2.0.0 +craft-store==3.0.0 cryptography==42.0.5 Deprecated==1.2.14 distro==1.9.0 @@ -62,8 +63,7 @@ progressbar==2.5 protobuf==5.26.1 psutil==5.9.8 pycparser==2.22 -pydantic==1.10.15 -pydantic-yaml==0.11.2 +pydantic==2.8.2 pyelftools==0.31 pygit2==1.13.3 Pygments==2.17.2 @@ -78,7 +78,7 @@ python-dateutil==2.9.0.post0 python-debian==0.1.49 pytz==2024.1 pyxdg==0.28 -PyYAML==6.0.1 +PyYAML==6.0.2 raven==6.10.0 regex==2024.4.28 requests==2.31.0 @@ -116,7 +116,7 @@ tinydb==4.8.0 toml==0.10.2 tomli==2.0.1 types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240311 +types-PyYAML==6.0.12.20240808 typing_extensions==4.11.0 uc-micro-py==1.0.3 urllib3==1.26.19 diff --git a/requirements-devel.txt b/requirements-devel.txt index 4450106012..29dededf51 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,134 +1,136 @@ -Deprecated==1.2.14 -PasteDeploy==3.1.0 -PyNaCl==1.5.0 -PyYAML==6.0.1 -SecretStorage==3.3.3 -WebOb==1.8.7 +annotated-types==0.7.0 astroid==3.2.4 -attrs==23.2.0 -black==24.3.0 -cachetools==5.3.3 +attrs==24.2.0 +black==24.8.0 +boolean.py==4.0 +cachetools==5.4.0 catkin-pkg==1.0.0 certifi==2024.7.4 -cffi==1.16.0 +cffi==1.17.0 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -codespell==2.2.6 +codespell==2.3.0 colorama==0.4.6 -coverage==7.4.4 -craft-application==3.1.0 -craft-archives==1.1.3 +coverage==7.6.1 +craft-application==4.0.0 +craft-archives==2.0.0 craft-cli==2.6.0 -craft-grammar==1.2.0 -craft-parts==1.34.0 -craft-providers==1.23.1 -craft-store==2.6.2 -cryptography==42.0.5 -dill==0.3.8 +craft-grammar==2.0.0 +craft-parts==2.0.0 +craft-platforms==0.1.1 +craft-providers==2.0.0 +craft-store==3.0.0 +cryptography==43.0.0 dill==0.3.8 distlib==0.3.8 distro==1.9.0 docutils==0.19 -filelock==3.13.3 +filelock==3.15.4 fixtures==4.1.0 -flake8==7.1.0 +flake8==7.1.1 gnupg==2.3.1 httplib2==0.22.0 hupper==1.12.1 idna==3.7 -importlib-metadata==7.0.1 +importlib_metadata==8.2.0 iniconfig==2.0.0 isort==5.13.2 -jaraco.classes==3.3.1 +jaraco.classes==3.4.0 jeepney==0.8.0 jsonschema==2.5.1 keyring==24.3.1 -launchpadlib==1.11.0 +launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 -lazy-object-proxy==1.10.0 -lxml==5.0.0 +license-expression==30.3.1 +lxml==5.3.0 macaroonbakery==1.3.4 mccabe==0.7.0 -more-itertools==10.1.0 +more-itertools==10.4.0 +mypy==1.11.1 mypy-extensions==1.0.0 -mypy==1.8.0 oauthlib==3.2.2 -overrides==7.4.0 -packaging==23.2 +overrides==7.7.0 +packaging==24.1 +PasteDeploy==3.1.0 pathspec==0.12.1 pbr==6.0.0 pexpect==4.9.0 -plaster-pastedeploy==1.0.1 plaster==1.1.2 +plaster-pastedeploy==1.0.1 platformdirs==4.2.2 -pluggy==1.3.0 +pluggy==1.5.0 progressbar==2.5 -protobuf==3.20.3 -psutil==5.9.8 +protobuf==5.27.3 +psutil==6.0.0 ptyprocess==0.7.0 -pyRFC3339==1.1 -pycodestyle==2.12.0 -pycparser==2.21 -pydantic-yaml==0.11.2 -pydantic==1.10.14 +pycodestyle==2.12.1 +pycparser==2.22 +pydantic==2.8.2 +pydantic_core==2.20.1 +pydantic_yaml==1.3.0 pydocstyle==6.3.0 -pyelftools==0.30 +pyelftools==0.31 pyflakes==3.2.0 -pyftpdlib==1.5.9 +pyftpdlib==1.5.10 pygit2==1.13.3 -pyinstaller==5.13.2; sys.platform == "win32" pylint==3.2.6 -pylxd==2.3.2 +pylxd==2.3.4 pymacaroons==0.13.0 +PyNaCl==1.5.0 pyparsing==3.1.2 -pyproject-api==1.6.1 +pyproject-api==1.7.1 pyramid==2.0.2 -pyright==1.1.356 +pyRFC3339==1.1 +pytest==8.3.2 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-subprocess==1.5.0 -pytest==7.4.4 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" -python-dateutil==2.8.2 +pytest-subprocess==1.5.2 +python-dateutil==2.9.0.post0 python-debian==0.1.49 -pytz==2023.4 +pytz==2024.1 pyxdg==0.28 +PyYAML==6.0.2 raven==6.10.0 +requests==2.31.0 requests-toolbelt==1.0.0 requests-unixsocket==0.3.0 -requests==2.31.0 -simplejson==3.19.2 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.8 +SecretStorage==3.3.3 +setuptools==72.2.0 +simplejson==3.19.3 six==1.16.0 snap-helpers==0.4.2 snowballstemmer==2.2.0 tabulate==0.9.0 testscenarios==0.5.0 -testtools==2.7.1 +testtools==2.7.2 tinydb==4.8.0 toml==0.10.2 -tomli==2.0.1 -tomlkit==0.13.0 -tox==4.11.4 +tomlkit==0.13.2 +tox==4.18.0 translationstring==1.4 -types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240311 +types-PyYAML==6.0.12.20240808 types-requests==2.31.0.6 -types-setuptools==69.2.0.20240317 -types-simplejson==3.19.0.20240310 +types-setuptools==71.1.0.20240813 +types-simplejson==3.19.0.20240801 types-tabulate==0.9.0.20240106 types-toml==0.10.8.20240310 types-urllib3==1.26.25.14 -typing-extensions==4.12.2 +typing_extensions==4.12.2 urllib3==1.26.19 -validators==0.28.3 +validators==0.33.0 venusian==3.1.0 -virtualenv==20.25.1 +virtualenv==20.26.3 wadllib==1.3.6 -wrapt==1.16.0 +WebOb==1.8.8 +wheel==0.44.0 ws4py==0.5.1 -yamllint==1.33.0 -zipp==3.19.1 +yamllint==1.35.1 +zipp==3.20.0 zope.deprecation==5.0 -zope.interface==6.1 +zope.interface==7.0.1 +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" +pyinstaller==5.13.1; sys.platform == "win32" diff --git a/requirements.txt b/requirements.txt index ab46ea4a8c..81c18c3e43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,76 +1,81 @@ -attrs==23.2.0 +annotated-types==0.7.0 +attrs==24.2.0 +boolean.py==4.0 catkin-pkg==1.0.0 certifi==2024.7.4 -cffi==1.16.0 +cffi==1.17.0 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==3.1.0 -craft-archives==1.1.3 +craft-application==4.0.0 +craft-archives==2.0.0 craft-cli==2.6.0 -craft-grammar==1.2.0 -craft-parts==1.34.0 -craft-providers==1.23.1 -craft-store==2.6.2 -cryptography==42.0.5 -Deprecated==1.2.14 +craft-grammar==2.0.0 +craft-parts==2.0.0 +craft-platforms==0.1.1 +craft-providers==2.0.0 +craft-store==3.0.0 +cryptography==43.0.0 distro==1.9.0 docutils==0.19 gnupg==2.3.1 httplib2==0.22.0 idna==3.7 -importlib-metadata==7.0.1 -jaraco.classes==3.3.1 +importlib_metadata==8.2.0 +jaraco.classes==3.4.0 jeepney==0.8.0 jsonschema==2.5.1 keyring==24.3.1 -launchpadlib==1.11.0 +launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 -lxml==5.0.0 +license-expression==30.3.1 +lxml==5.3.0 macaroonbakery==1.3.4 -more-itertools==10.1.0 +more-itertools==10.4.0 mypy-extensions==1.0.0 oauthlib==3.2.2 -overrides==7.4.0 -packaging==23.2 -platformdirs==4.1.0 +overrides==7.7.0 +packaging==24.1 +platformdirs==4.2.2 progressbar==2.5 -protobuf==3.20.3 -psutil==5.9.8 -pycparser==2.21 -pydantic==1.10.14 -pydantic-yaml==0.11.2 -pyelftools==0.30 +protobuf==5.27.3 +psutil==6.0.0 +pycparser==2.22 +pydantic==2.8.2 +pydantic_core==2.20.1 +pydantic_yaml==1.3.0 +pyelftools==0.31 pygit2==1.13.3 -pylxd==2.3.2 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.2 pyRFC3339==1.1 -python-dateutil==2.8.2 +python-dateutil==2.9.0.post0 python-debian==0.1.49 -pytz==2023.4 +pytz==2024.1 pyxdg==0.28 -PyYAML==6.0.1 +PyYAML==6.0.2 raven==6.10.0 requests==2.31.0 requests-toolbelt==1.0.0 requests-unixsocket==0.3.0 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.8 SecretStorage==3.3.3 -simplejson==3.19.2 +setuptools==72.2.0 +simplejson==3.19.3 six==1.16.0 snap-helpers==0.4.2 tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 -types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240311 -typing_extensions==4.9.0 +typing_extensions==4.12.2 urllib3==1.26.19 -validators==0.28.3 +validators==0.33.0 wadllib==1.3.6 -wrapt==1.16.0 +wheel==0.44.0 ws4py==0.5.1 -zipp==3.19.1 +zipp==3.20.0 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" diff --git a/setup.py b/setup.py index beb6536363..db48ebe180 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2015-2022 Canonical Ltd. +# Copyright 2015-2022,2024 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 @@ -97,13 +97,14 @@ def recursive_data_files(directory, install_directory): "attrs", "catkin-pkg; sys_platform == 'linux'", "click", - "craft-application>=3.1.0", - "craft-archives", - "craft-cli>=2.6.0", - "craft-grammar", - "craft-parts>=1.34.0", - "craft-providers", - "craft-store", + "craft-application~=4.0", + "craft-archives~=2.0", + "craft-cli~=2.6", + "craft-grammar~=2.0", + "craft-parts~=2.0", + "craft-platforms~=0.1", + "craft-providers~=2.0", + "craft-store~=3.0", "docutils<0.20", # Frozen until we can update sphinx dependencies. "gnupg", "jsonschema==2.5.1", @@ -115,6 +116,7 @@ def recursive_data_files(directory, install_directory): "overrides", "packaging", "progressbar", + "pydantic~=2.8", "pyelftools", # Pygit2 and libgit2 need to match versions. # Further info: https://www.pygit2.org/install.html#version-numbers diff --git a/snapcraft/elf/_elf_file.py b/snapcraft/elf/_elf_file.py index 17008cb694..55fc44a3c3 100644 --- a/snapcraft/elf/_elf_file.py +++ b/snapcraft/elf/_elf_file.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2016-2023 Canonical Ltd. +# Copyright 2016-2024 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 @@ -261,12 +261,12 @@ def _extract_attributes( # noqa: PLR0912 (too-many-branches) for tag in section.iter_tags(): if tag.entry.d_tag == "DT_NEEDED": needed = ( - tag.needed # pyright: ignore[reportAttributeAccessIssue] + tag.needed # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] ) self.needed[needed] = _NeededLibrary(name=needed) elif tag.entry.d_tag == "DT_SONAME": self.soname = ( - tag.soname # pyright: ignore[reportAttributeAccessIssue] + tag.soname # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] ) for segment in elf_file.iter_segments(): From 50bbb247f6be4a898abf8c7c00f70d4fddb50252 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 14 Aug 2024 14:15:46 -0500 Subject: [PATCH 038/151] chore!: update to craft-parts 2.0.0 craft-parts 2.0.0 uses debian architectures Signed-off-by: Callahan Kovacs --- snapcraft/parts/lifecycle.py | 8 +- snapcraft/parts/parts.py | 6 +- snapcraft/parts/plugins/colcon_plugin.py | 52 ++++------- snapcraft/parts/plugins/conda_plugin.py | 36 +++----- snapcraft/parts/plugins/flutter_plugin.py | 40 +++----- snapcraft/parts/plugins/kernel_plugin.py | 92 ++++++++----------- snapcraft/parts/plugins/matter_sdk_plugin.py | 36 ++------ tests/unit/parts/plugins/test_conda_plugin.py | 2 +- tests/unit/parts/plugins/test_kernel.py | 46 +++++----- tests/unit/parts/test_lifecycle.py | 15 ++- tests/unit/parts/test_parts.py | 9 +- 11 files changed, 126 insertions(+), 216 deletions(-) diff --git a/snapcraft/parts/lifecycle.py b/snapcraft/parts/lifecycle.py index b885971bc5..dc23026715 100644 --- a/snapcraft/parts/lifecycle.py +++ b/snapcraft/parts/lifecycle.py @@ -34,11 +34,7 @@ from snapcraft.elf import errors as elf_errors from snapcraft.linters import LinterStatus from snapcraft.meta import component_yaml, manifest, snap_yaml -from snapcraft.utils import ( - convert_architecture_deb_to_platform, - get_host_architecture, - process_version, -) +from snapcraft.utils import get_host_architecture, process_version from . import yaml_utils from .parts import PartsLifecycle, launch_shell @@ -753,7 +749,7 @@ def _expand_environment( application_name="snapcraft", # not used in environment expansion base=yaml_utils.get_base_from_yaml(snapcraft_yaml) or "", cache_dir=Path(), # not used in environment expansion - arch=convert_architecture_deb_to_platform(target_arch), + arch=target_arch, parallel_build_count=parallel_build_count, project_name=snapcraft_yaml.get("name", ""), project_dirs=dirs, diff --git a/snapcraft/parts/parts.py b/snapcraft/parts/parts.py index f27e2a44f9..58a8b56d19 100644 --- a/snapcraft/parts/parts.py +++ b/snapcraft/parts/parts.py @@ -32,7 +32,7 @@ from snapcraft.meta import ExtractedMetadata from snapcraft.parts.extract_metadata import extract_lifecycle_metadata from snapcraft.services.lifecycle import get_prime_dirs_from_project -from snapcraft.utils import convert_architecture_deb_to_platform, get_host_architecture +from snapcraft.utils import get_host_architecture _LIFECYCLE_STEPS = { "pull": Step.PULL, @@ -94,15 +94,13 @@ def __init__( # noqa PLR0913 if target_arch == "all": target_arch = get_host_architecture() - platform_arch = convert_architecture_deb_to_platform(target_arch) - try: self._lcm = craft_parts.LifecycleManager( {"parts": all_parts}, application_name="snapcraft", work_dir=work_dir, cache_dir=cache_dir, - arch=platform_arch, + arch=target_arch, base=base, ignore_local_sources=["*.snap"], extra_build_snaps=extra_build_snaps, diff --git a/snapcraft/parts/plugins/colcon_plugin.py b/snapcraft/parts/plugins/colcon_plugin.py index 74c8c26ba4..8eea647beb 100644 --- a/snapcraft/parts/plugins/colcon_plugin.py +++ b/snapcraft/parts/plugins/colcon_plugin.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -55,7 +55,7 @@ - ROS_DISTRO: "humble" """ -from typing import Any, Dict, List, Set, cast +from typing import Literal, cast from craft_parts import plugins from craft_parts.packages.snaps import _get_parsed_snap @@ -64,38 +64,20 @@ from . import _ros -class ColconPluginProperties(plugins.PluginProperties, plugins.PluginModel): +class ColconPluginProperties(plugins.PluginProperties, frozen=True): """The part properties used by the Colcon plugin.""" - colcon_ament_cmake_args: List[str] = [] - colcon_catkin_cmake_args: List[str] = [] - colcon_cmake_args: List[str] = [] - colcon_packages: List[str] = [] - colcon_packages_ignore: List[str] = [] - colcon_ros_build_snaps: List[str] = [] + plugin: Literal["colcon"] = "colcon" - # part properties required by the plugin - source: str - - @classmethod - @overrides - def unmarshal(cls, data: Dict[str, Any]) -> "ColconPluginProperties": - """Populate make properties from the part specification. - - :param data: A dictionary containing part properties. - - :return: The populated plugin properties data object. + colcon_ament_cmake_args: list[str] = [] + colcon_catkin_cmake_args: list[str] = [] + colcon_cmake_args: list[str] = [] + colcon_packages: list[str] = [] + colcon_packages_ignore: list[str] = [] + colcon_ros_build_snaps: list[str] = [] - :raise pydantic.ValidationError: If validation fails. - """ - - # plugin specific parameters have to be prefixed with the plugin name. - # However we'd like to avoid that for 'ros-build-snaps'. - # Marking it required allows us to circumvent the prefix requirement. - plugin_data = plugins.extract_plugin_properties( - data, plugin_name="colcon", required=["source"] - ) - return cls(**plugin_data) + # part properties required by the plugin + source: str # type: ignore[reportGeneralTypeIssues] class ColconPlugin(_ros.RosPlugin): @@ -104,7 +86,7 @@ class ColconPlugin(_ros.RosPlugin): properties_class = ColconPluginProperties @overrides - def get_build_packages(self) -> Set[str]: + def get_build_packages(self) -> set[str]: base = self._part_info.base build_packages = {"python3-colcon-common-extensions"} if base == "core22": @@ -112,7 +94,7 @@ def get_build_packages(self) -> Set[str]: return super().get_build_packages() | build_packages @overrides - def get_build_environment(self) -> Dict[str, str]: + def get_build_environment(self) -> dict[str, str]: env = super().get_build_environment() env.update( { @@ -123,7 +105,7 @@ def get_build_environment(self) -> Dict[str, str]: return env - def _get_source_command(self, path: str) -> List[str]: + def _get_source_command(self, path: str) -> list[str]: return [ f'if [ -f "{path}/opt/ros/${{ROS_DISTRO}}/local_setup.sh" ]; then', 'AMENT_CURRENT_PREFIX="{wspath}" . "{wspath}/local_setup.sh"'.format( @@ -138,7 +120,7 @@ def _get_source_command(self, path: str) -> List[str]: ] @overrides - def _get_workspace_activation_commands(self) -> List[str]: + def _get_workspace_activation_commands(self) -> list[str]: """Return a list of commands source a ROS 2 workspace. The commands returned will be run before doing anything else. @@ -176,7 +158,7 @@ def _get_workspace_activation_commands(self) -> List[str]: return activation_commands @overrides - def _get_build_commands(self) -> List[str]: + def _get_build_commands(self) -> list[str]: options = cast(ColconPluginProperties, self._options) build_command = [ diff --git a/snapcraft/parts/plugins/conda_plugin.py b/snapcraft/parts/plugins/conda_plugin.py index 47cb0f1c12..a61d8f97d3 100644 --- a/snapcraft/parts/plugins/conda_plugin.py +++ b/snapcraft/parts/plugins/conda_plugin.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,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 @@ -19,7 +19,7 @@ import os import platform import textwrap -from typing import Any, Dict, List, Optional, Set, cast +from typing import Literal, cast from craft_parts import plugins from overrides import overrides @@ -61,30 +61,16 @@ def _get_miniconda_source(version: str) -> str: return source -class CondaPluginProperties(plugins.PluginProperties, plugins.PluginModel): +class CondaPluginProperties(plugins.PluginProperties, frozen=True): """The part properties used by the conda plugin.""" + plugin: Literal["conda"] = "conda" + # part properties required by the plugin - conda_packages: Optional[List[str]] = None - conda_python_version: Optional[str] = None + conda_packages: list[str] | None = None + conda_python_version: str | None = None conda_miniconda_version: str = "latest" - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "CondaPluginProperties": - """Populate class attributes from the part specification. - - :param data: A dictionary containing part properties. - - :return: The populated plugin properties data object. - - :raise pydantic.ValidationError: If validation fails. - """ - plugin_data = plugins.extract_plugin_properties( - data, - plugin_name="conda", - ) - return cls(**plugin_data) - class CondaPlugin(plugins.Plugin): """A plugin for conda projects. @@ -108,15 +94,15 @@ class CondaPlugin(plugins.Plugin): properties_class = CondaPluginProperties @overrides - def get_build_snaps(self) -> Set[str]: + def get_build_snaps(self) -> set[str]: return set() @overrides - def get_build_packages(self) -> Set[str]: + def get_build_packages(self) -> set[str]: return set() @overrides - def get_build_environment(self) -> Dict[str, str]: + def get_build_environment(self) -> dict[str, str]: return {"PATH": "${HOME}/miniconda/bin:${PATH}"} @staticmethod @@ -149,7 +135,7 @@ def _get_deploy_command(self, options) -> str: return " ".join(deploy_cmd) @overrides - def get_build_commands(self) -> List[str]: + def get_build_commands(self) -> list[str]: options = cast(CondaPluginProperties, self._options) url = _get_miniconda_source(options.conda_miniconda_version) return [ diff --git a/snapcraft/parts/plugins/flutter_plugin.py b/snapcraft/parts/plugins/flutter_plugin.py index bccd48c4de..40f6dd5b5a 100644 --- a/snapcraft/parts/plugins/flutter_plugin.py +++ b/snapcraft/parts/plugins/flutter_plugin.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2023 Canonical Ltd. +# Copyright 2023-2024 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 @@ -15,7 +15,7 @@ # along with this program. If not, see . """The flutter plugin.""" -from typing import Any, Dict, List, Literal, Set, cast +from typing import Literal, cast from craft_parts import infos, plugins from overrides import overrides @@ -24,31 +24,15 @@ """The repository where the flutter SDK resides.""" -class FlutterPluginProperties(plugins.PluginProperties, plugins.PluginModel): +class FlutterPluginProperties(plugins.PluginProperties, frozen=True): """The part properties used by the flutter plugin.""" - source: str + plugin: Literal["flutter"] = "flutter" + + source: str # type: ignore[reportGeneralTypeIssues] flutter_channel: Literal["stable", "master", "beta"] = "stable" flutter_target: str = "lib/main.dart" - @classmethod - @overrides - def unmarshal(cls, data: Dict[str, Any]) -> "FlutterPluginProperties": - """Populate class attributes from the part specification. - - :param data: A dictionary containing part properties. - - :return: The populated plugin properties data object. - - :raise pydantic.ValidationError: If validation fails. - """ - plugin_data = plugins.extract_plugin_properties( - data, - plugin_name="flutter", - required=["source"], - ) - return cls(**plugin_data) - class FlutterPlugin(plugins.Plugin): """A plugin for flutter projects. @@ -76,11 +60,11 @@ def __init__( self.flutter_dir = part_info.part_build_dir / "flutter-distro" @overrides - def get_build_snaps(self) -> Set[str]: + def get_build_snaps(self) -> set[str]: return set() @overrides - def get_build_packages(self) -> Set[str]: + def get_build_packages(self) -> set[str]: return { "clang", "curl", @@ -91,12 +75,12 @@ def get_build_packages(self) -> Set[str]: } @overrides - def get_build_environment(self) -> Dict[str, str]: + def get_build_environment(self) -> dict[str, str]: return { "PATH": f"{self.flutter_dir / 'bin'}:${{PATH}}", } - def _get_setup_flutter(self, options) -> List[str]: + def _get_setup_flutter(self, options) -> list[str]: # TODO move to pull return [ # TODO detect changes to plugin properties @@ -106,10 +90,10 @@ def _get_setup_flutter(self, options) -> List[str]: ] @overrides - def get_build_commands(self) -> List[str]: + def get_build_commands(self) -> list[str]: options = cast(FlutterPluginProperties, self._options) - flutter_install_cmd: List[str] = [] + flutter_install_cmd: list[str] = [] if not self.flutter_dir.exists(): flutter_install_cmd = self._get_setup_flutter(options) diff --git a/snapcraft/parts/plugins/kernel_plugin.py b/snapcraft/parts/plugins/kernel_plugin.py index 3ff7a3a4ba..8c05dc5c41 100644 --- a/snapcraft/parts/plugins/kernel_plugin.py +++ b/snapcraft/parts/plugins/kernel_plugin.py @@ -1,7 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- - # -# Copyright 2020-2022 Canonical Ltd. +# Copyright 2020-2022,2024 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 @@ -168,11 +167,12 @@ import logging import os import re -from typing import Any, Dict, List, Optional, Set, Union, cast +from typing import Any, Literal, cast +import pydantic from craft_parts import infos, plugins from overrides import overrides -from pydantic import root_validator +from typing_extensions import Self from snapcraft_legacy.plugins.v2 import _kernel_build @@ -193,67 +193,47 @@ } -class KernelPluginProperties(plugins.PluginProperties, plugins.PluginModel): +class KernelPluginProperties(plugins.PluginProperties, frozen=True): """The part properties used by the Kernel plugin.""" - kernel_kdefconfig: List[str] = ["defconfig"] - kernel_kconfigfile: Optional[str] - kernel_kconfigflavour: Optional[str] - kernel_kconfigs: Optional[List[str]] - kernel_image_target: Any + plugin: Literal["kernel"] = "kernel" + + kernel_kdefconfig: list[str] = ["defconfig"] + kernel_kconfigfile: str | None = None + kernel_kconfigflavour: str | None = None + kernel_kconfigs: list[str] | None = None + kernel_image_target: str | dict[str, Any] | None = None kernel_with_firmware: bool = True - kernel_device_trees: Optional[List[str]] + kernel_device_trees: list[str] | None = None kernel_build_efi_image: bool = False - kernel_compiler: Optional[str] - kernel_compiler_paths: Optional[List[str]] - kernel_compiler_parameters: Optional[List[str]] - kernel_initrd_modules: Optional[List[str]] - kernel_initrd_configured_modules: Optional[List[str]] + kernel_compiler: str | None = None + kernel_compiler_paths: list[str] | None = None + kernel_compiler_parameters: list[str] | None = None + kernel_initrd_modules: list[str] | None = None + kernel_initrd_configured_modules: list[str] | None = None kernel_initrd_stage_firmware: bool = False - kernel_initrd_firmware: Optional[List[str]] - kernel_initrd_compression: Optional[str] - kernel_initrd_compression_options: Optional[List[str]] - kernel_initrd_overlay: Optional[str] - kernel_initrd_addons: Optional[List[str]] + kernel_initrd_firmware: list[str] | None = None + kernel_initrd_compression: str | None = None + kernel_initrd_compression_options: list[str] | None = None + kernel_initrd_overlay: str | None = None + kernel_initrd_addons: list[str] | None = None kernel_enable_zfs_support: bool = False kernel_enable_perf: bool = False kernel_add_ppa: bool = True - kernel_use_llvm: Union[bool, str] = False + kernel_use_llvm: bool | str = False # part properties required by the plugin - @root_validator - @classmethod - def validate_pluging_options(cls, values): + @pydantic.model_validator(mode="after") + def validate_plugin_options(self) -> Self: """If kernel-image-target is defined, it has to be string or dictionary.""" - if values.get("kernel_image_target"): - if not isinstance(values.get("kernel_image_target"), str): - if not isinstance(values.get("kernel_image_target"), dict): - raise ValueError( - f'kernel-image-target is in invalid format(type{type(values.get("kernel_image_target"))}). It should be either string or dictionary.' - ) - - if values.get("kernel_initrd_compression_options") and not values.get( - "kernel_initrd_compression" + if ( + self.kernel_initrd_compression_options + and not self.kernel_initrd_compression ): raise ValueError( "kernel-initrd-compression-options requires also kernel-initrd-compression to be defined." ) - return values - - @classmethod - def unmarshal(cls, data: Dict[str, Any]): - """Populate class attributes from the part specification. - - :param data: A dictionary containing part properties. - - :return: The populated plugin properties data object. - - :raise pydantic.ValidationError: If validation fails. - """ - plugin_data = plugins.extract_plugin_properties( - data, plugin_name="kernel", required=[] - ) - return cls(**plugin_data) + return self class KernelPlugin(plugins.Plugin): @@ -282,7 +262,7 @@ def __init__( self._llvm_version = self._determine_llvm_version() self._target_arch = self._part_info.target_arch - def _determine_llvm_version(self) -> Optional[str]: + def _determine_llvm_version(self) -> str | None: if ( isinstance(self.options.kernel_use_llvm, bool) and self.options.kernel_use_llvm @@ -354,7 +334,7 @@ def _set_llvm(self) -> None: if self._llvm_version is not None: self._make_cmd.append(f'LLVM="{self._llvm_version}"') - def _get_fw_install_targets(self) -> List[str]: + def _get_fw_install_targets(self) -> list[str]: if not self.options.kernel_with_firmware: return [] @@ -377,11 +357,11 @@ def _configure_compiler(self) -> None: self._make_cmd.append(str(opt)) @overrides - def get_build_snaps(self) -> Set[str]: + def get_build_snaps(self) -> set[str]: return set() @overrides - def get_build_packages(self) -> Set[str]: + def get_build_packages(self) -> set[str]: build_packages = { "bc", "binutils", @@ -439,7 +419,7 @@ def get_build_packages(self) -> Set[str]: return build_packages @overrides - def get_build_environment(self) -> Dict[str, str]: + def get_build_environment(self) -> dict[str, str]: logger.info("Getting build env...") self._init_build_env() @@ -466,7 +446,7 @@ def get_build_environment(self) -> Dict[str, str]: return env @overrides - def get_build_commands(self) -> List[str]: + def get_build_commands(self) -> list[str]: logger.info("Getting build commands...") self._configure_compiler() return _kernel_build.get_build_commands( diff --git a/snapcraft/parts/plugins/matter_sdk_plugin.py b/snapcraft/parts/plugins/matter_sdk_plugin.py index 4c50f12996..de62a6ed87 100644 --- a/snapcraft/parts/plugins/matter_sdk_plugin.py +++ b/snapcraft/parts/plugins/matter_sdk_plugin.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2023 Canonical Ltd. +# Copyright 2023-2024 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 @@ -16,7 +16,7 @@ """The matter SDK plugin.""" import os -from typing import Any, Dict, List, Set, cast +from typing import Literal, cast from craft_parts import infos, plugins from overrides import overrides @@ -25,28 +25,12 @@ MATTER_SDK_REPO = "https://github.com/project-chip/connectedhomeip" -class MatterSdkPluginProperties(plugins.PluginProperties, plugins.PluginModel): +class MatterSdkPluginProperties(plugins.PluginProperties, frozen=True): """The part properties used by the matter SDK plugin.""" - matter_sdk_version: str - - @classmethod - @overrides - def unmarshal(cls, data: Dict[str, Any]) -> "MatterSdkPluginProperties": - """Populate class attributes from the part specification. - - :param data: A dictionary containing part properties. + plugin: Literal["matter-sdk"] = "matter-sdk" - :return: The populated plugin properties data object. - - :raise pydantic.ValidationError: If validation fails. - """ - plugin_data = plugins.extract_plugin_properties( - data, - plugin_name="matter-sdk", - required=["matter_sdk_version"], - ) - return cls(**plugin_data) + matter_sdk_version: str class MatterSdkPlugin(plugins.Plugin): @@ -75,7 +59,7 @@ def __init__( self.snap_arch = os.getenv("SNAP_ARCH") @overrides - def get_pull_commands(self) -> List[str]: + def get_pull_commands(self) -> list[str]: options = cast(MatterSdkPluginProperties, self._options) commands = [] @@ -95,7 +79,7 @@ def get_pull_commands(self) -> List[str]: return commands @overrides - def get_build_packages(self) -> Set[str]: + def get_build_packages(self) -> set[str]: return { "clang", "cmake", @@ -118,15 +102,15 @@ def get_build_packages(self) -> Set[str]: } @overrides - def get_build_environment(self) -> Dict[str, str]: + def get_build_environment(self) -> dict[str, str]: return {} @overrides - def get_build_snaps(self) -> Set[str]: + def get_build_snaps(self) -> set[str]: return set() @overrides - def get_build_commands(self) -> List[str]: + def get_build_commands(self) -> list[str]: commands = [] # The project writes its data to /tmp which isn't persisted. diff --git a/tests/unit/parts/plugins/test_conda_plugin.py b/tests/unit/parts/plugins/test_conda_plugin.py index ace3ca9f65..8020713ea6 100644 --- a/tests/unit/parts/plugins/test_conda_plugin.py +++ b/tests/unit/parts/plugins/test_conda_plugin.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,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 diff --git a/tests/unit/parts/plugins/test_kernel.py b/tests/unit/parts/plugins/test_kernel.py index 09db04137b..cf9578d5d2 100644 --- a/tests/unit/parts/plugins/test_kernel.py +++ b/tests/unit/parts/plugins/test_kernel.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022-2023 Canonical Ltd. +# Copyright 2022-2024 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 @@ -25,6 +25,8 @@ import pytest from craft_parts import Part, PartInfo, ProjectInfo +from craft_parts.errors import InvalidArchitecture +from craft_platforms import DebianArchitecture from pydantic import ValidationError from snapcraft.parts.plugins import KernelPlugin @@ -160,7 +162,7 @@ def test_get_base_build_packages_zfs_cross(self, setup_method_fixture, new_dir): properties={ "kernel-enable-zfs-support": True, }, - arch="armv7l", + arch="armhf", ) assert plugin.get_build_packages() == { "bc", @@ -355,9 +357,11 @@ def test_check_configuration_image_wrong_image_target( ) except ValidationError as err: error_list = err.errors() - assert len(error_list) == 1 - error = error_list[0] - assert "kernel-image-target is in invalid format" in error.get("msg") + assert len(error_list) == 2 + assert error_list[0].get("loc") == ("kernel-image-target", "str") + assert error_list[0].get("msg") == "Input should be a valid string" + assert error_list[1].get("loc") == ("kernel-image-target", "dict[str,any]") + assert error_list[1].get("msg") == "Input should be a valid dictionary" def test_check_configuration_image_missing_initrd_compression( self, setup_method_fixture, new_dir @@ -401,7 +405,7 @@ def test_check_get_build_environment_compiler_paths_cross( "kernel-compiler-paths": ["gcc-11/bin"], "kernel-image-target": {"arm64": "Image", "armhf": "Image.gz"}, }, - arch="armv7l", + arch="armhf", ) assert plugin.get_build_environment() == { @@ -682,7 +686,7 @@ def test_check_get_build_command_cross(self, setup_method_fixture, new_dir): "kernel-initrd-configured-modules": ["libarc4"], "kernel-initrd-overlay": "my-overlay", }, - arch="armv7l", + arch="armhf", ) # we need to get build environment @@ -724,23 +728,21 @@ def test_check_get_build_command_cross(self, setup_method_fixture, new_dir): assert _is_sub_array(build_commands, _build_perf_armhf_cmd) assert _is_sub_array(build_commands, _finalize_install_cmd) - @pytest.mark.parametrize("arch", ["aarch64", "armv7l", "riscv64", "x86_64"]) + @pytest.mark.parametrize("arch", ["arm64", "armhf", "riscv64", "amd64"]) def test_check_arch(self, arch, setup_method_fixture, new_dir): - cross_building = platform.machine() != arch + cross_building = DebianArchitecture.from_host() != arch plugin = setup_method_fixture(new_dir, arch=arch) - assert plugin._kernel_arch == _KERNEL_ARCH_TRANSLATIONS[arch] - assert plugin._deb_arch == _DEB_ARCH_TRANSLATIONS[arch] - assert plugin._target_arch == _DEB_ARCH_TRANSLATIONS[arch] + assert plugin._kernel_arch == _DEB_TO_KERNEL_ARCH_TRANSLATIONS[arch] + assert plugin._deb_arch == arch + assert plugin._target_arch == arch assert plugin._cross_building == cross_building def test_check_arch_i686(self, setup_method_fixture, new_dir): - # we do not support i686 so use this arch to test unknnown arch by plugin - arch = "i686" - with pytest.raises(ValueError) as error: - setup_method_fixture(new_dir, arch=arch) - - assert str(error.value) == "unknown deb architecture" + # we do not support i686 so use this arch to test unknown arch by plugin + error = "Architecture 'i686' is not supported" + with pytest.raises(InvalidArchitecture, match=error): + setup_method_fixture(new_dir, arch="i686") def test_check_new_config_good(self, setup_method_fixture, new_dir, caplog): # create test config @@ -1209,11 +1211,11 @@ def _is_sub_array(array, sub_array): "x86_64": "amd64", } -_KERNEL_ARCH_TRANSLATIONS = { - "aarch64": "arm64", - "armv7l": "arm", +_DEB_TO_KERNEL_ARCH_TRANSLATIONS = { + "arm64": "arm64", + "armhf": "arm", "riscv64": "riscv", - "x86_64": "x86", + "amd64": "x86", } _SNAPPY_DEV_KEY_FINGERPRINT = "F1831DDAFC42E99D" diff --git a/tests/unit/parts/test_lifecycle.py b/tests/unit/parts/test_lifecycle.py index 3859cb6fab..0747c0070c 100644 --- a/tests/unit/parts/test_lifecycle.py +++ b/tests/unit/parts/test_lifecycle.py @@ -21,6 +21,7 @@ from pathlib import Path from unittest.mock import ANY, Mock, PropertyMock, call +import pydantic import pytest from craft_cli import EmitterMode, emit from craft_parts import Action, Features, ProjectInfo, Step, callbacks @@ -804,7 +805,7 @@ def test_lifecycle_pack_metadata_error(cmd, snapcraft_yaml, new_dir, mocker): ) assert str(raised.value) == ( - "error setting grade: unexpected value; permitted: 'stable', 'devel'" + "error setting grade: Input should be 'stable' or 'devel'" ) assert run_mock.mock_calls == [ call("prime", shell=False, shell_after=False, rerun_step=False) @@ -1197,13 +1198,11 @@ def test_check_experimental_plugins_enabled(snapcraft_yaml, mocker): def test_get_snap_project_no_base(snapcraft_yaml, new_dir): - with pytest.raises(errors.ProjectValidationError) as raised: - Project.unmarshal(snapcraft_yaml(base=None)) - - assert str(raised.value) == ( - "Bad snapcraft.yaml content:\n" - "- Snap base must be declared when type is not base, kernel or snapd" + error = ( + "Value error, Snap base must be declared when type is not base, kernel or snapd" ) + with pytest.raises(pydantic.ValidationError, match=error): + Project.unmarshal(snapcraft_yaml(base=None)) @pytest.mark.parametrize("base", ["core22", "core24"]) @@ -1234,7 +1233,7 @@ def test_set_global_environment(base, mocker, new_dir): "version": "test-version", "grade": "test-grade", }, - arch="aarch64", + arch="arm64", cache_dir=new_dir, ) set_global_environment(info) diff --git a/tests/unit/parts/test_parts.py b/tests/unit/parts/test_parts.py index bf4155f3fc..6710840de9 100644 --- a/tests/unit/parts/test_parts.py +++ b/tests/unit/parts/test_parts.py @@ -64,7 +64,7 @@ def test_parts_lifecycle_run(mocker, parts_data, step_name, new_dir, emitter): application_name="snapcraft", work_dir=ANY, cache_dir=ANY, - arch="x86_64", + arch="amd64", base="core22", ignore_local_sources=["*.snap"], extra_build_snaps=["core22"], @@ -411,7 +411,8 @@ def test_parts_lifecycle_initialize_with_package_repositories_deps_installed( def test_parts_lifecycle_bad_architecture(parts_data, new_dir): - with pytest.raises(errors.InvalidArchitecture) as raised: + error = "Architecture 'bad-arch' is not supported" + with pytest.raises(errors.PartsLifecycleError, match=error): PartsLifecycle( parts_data, work_dir=new_dir, @@ -431,8 +432,6 @@ def test_parts_lifecycle_bad_architecture(parts_data, new_dir): partitions=None, ) - assert str(raised.value) == "Architecture 'bad-arch' is not supported." - def test_parts_lifecycle_run_with_all_architecture(mocker, parts_data, new_dir): """`target_arch=all` should use the host architecture.""" @@ -465,7 +464,7 @@ def test_parts_lifecycle_run_with_all_architecture(mocker, parts_data, new_dir): application_name="snapcraft", work_dir=ANY, cache_dir=ANY, - arch="x86_64", + arch="amd64", base="core22", ignore_local_sources=["*.snap"], extra_build_snaps=None, From 495dc20d53b721318d26376df560a4d0c7b1f864 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 14 Aug 2024 14:17:45 -0500 Subject: [PATCH 039/151] chore: update to craft-providers 2.0.0 craft-providers 2.0.0 removes the deprecated `build-base` parameter when launching instances. Signed-off-by: Callahan Kovacs --- snapcraft/commands/lint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/snapcraft/commands/lint.py b/snapcraft/commands/lint.py index eb3dc81f2c..368ebf9d9c 100644 --- a/snapcraft/commands/lint.py +++ b/snapcraft/commands/lint.py @@ -176,7 +176,6 @@ def _prepare_instance( project_name="snapcraft-linter", project_path=Path().absolute(), base_configuration=base_configuration, - build_base=build_base.value, instance_name=instance_name, allow_unstable=False, ) as instance: From d0dec23f71ab670a6ae27d88d60d6b7a60bcd45e Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 14 Aug 2024 14:19:18 -0500 Subject: [PATCH 040/151] chore!: update to pydantic 2.8.2 Signed-off-by: Callahan Kovacs --- snapcraft/commands/extensions.py | 5 +- snapcraft/linters/base.py | 31 +- snapcraft/linters/library_linter.py | 6 +- snapcraft/meta/component_yaml.py | 4 +- snapcraft/meta/manifest.py | 46 +- snapcraft/meta/snap_yaml.py | 259 ++++----- snapcraft/models/project.py | 550 +++++++----------- snapcraft/parts/yaml_utils.py | 6 +- snapcraft/snap_config.py | 44 +- snapcraft/ua_manager.py | 22 +- .../storeapi/v2/validation_sets.py | 28 +- tests/spread/general/snap-config/task.yaml | 2 +- tests/unit/commands/test_expand_extensions.py | 207 ++++++- tests/unit/commands/test_remote.py | 2 +- tests/unit/conftest.py | 24 +- tests/unit/meta/test_snap_yaml.py | 33 +- tests/unit/models/test_projects.py | 460 ++++++++------- .../parts/test_setup_assets_components.py | 6 +- tests/unit/parts/test_yaml_utils.py | 3 +- .../services/test_lifecycle_components.py | 9 +- tests/unit/services/test_package.py | 57 +- .../unit/services/test_package_components.py | 10 +- tests/unit/test_application.py | 12 +- tests/unit/test_providers.py | 16 +- tests/unit/test_snap_config.py | 26 +- tests/unit/test_ua_manager.py | 45 +- 26 files changed, 925 insertions(+), 988 deletions(-) diff --git a/snapcraft/commands/extensions.py b/snapcraft/commands/extensions.py index 65e13280c4..65aa16ab43 100644 --- a/snapcraft/commands/extensions.py +++ b/snapcraft/commands/extensions.py @@ -20,7 +20,6 @@ from typing import Dict, List import tabulate -import yaml from craft_application.commands import AppCommand from craft_cli import emit from overrides import overrides @@ -135,5 +134,5 @@ def run(self, parsed_args): # not part of the Project model extract_parse_info(yaml_data_for_arch) - models.Project.unmarshal(yaml_data_for_arch) - emit.message(yaml.safe_dump(yaml_data_for_arch, indent=4, sort_keys=False)) + project_data = models.Project.unmarshal(yaml_data_for_arch) + emit.message(project_data.to_yaml_string()) diff --git a/snapcraft/linters/base.py b/snapcraft/linters/base.py index e27c8eef5b..3eb8c8a620 100644 --- a/snapcraft/linters/base.py +++ b/snapcraft/linters/base.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -20,10 +20,12 @@ import enum import fnmatch from pathlib import Path -from typing import TYPE_CHECKING, List, Literal, Optional, Union +from typing import TYPE_CHECKING, Literal import pydantic +from craft_application.models import base from craft_cli import emit +from pydantic import ConfigDict from snapcraft import elf, models @@ -52,10 +54,10 @@ class LinterIssue(pydantic.BaseModel): type: Literal["lint"] name: str result: LinterResult - filename: Optional[str] + filename: str | None = None text: str - url: Optional[str] - suggested_changes: Optional[str] # XXX: pending definition + url: str | None = None + suggested_changes: str | None = None # XXX: pending definition def __init__(self, **kwargs): super().__init__(type="lint", **kwargs) @@ -72,12 +74,11 @@ def __str__(self): return msg - class Config: - """Pydantic model configuration.""" - - validate_assignment = True - extra = "forbid" - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + model_config = ConfigDict( + validate_assignment=True, + extra="forbid", + alias_generator=base.alias_generator, + ) class Linter(abc.ABC): @@ -90,21 +91,21 @@ def __init__( self, name: str, snap_metadata: "SnapMetadata", - lint: Optional[models.Lint], + lint: models.Lint | None, ): self._name = name self._snap_metadata = snap_metadata self._lint = lint or models.Lint(ignore=[]) @abc.abstractmethod - def run(self) -> List[LinterIssue]: + def run(self) -> list[LinterIssue]: """Execute linting. :return: A list of linter issues flagged by this linter. """ def _is_file_ignored( - self, filepath: Union[elf.ElfFile, Path], category: str = "" + self, filepath: elf.ElfFile | Path, category: str = "" ) -> bool: """Check if the file name matches an ignored file pattern. @@ -136,7 +137,7 @@ def _is_file_ignored( return False @staticmethod - def get_categories() -> List[str]: + def get_categories() -> list[str]: """Get a list of specific subcategories that can be filtered against. For Linter subclasses that perform multiple "kinds" of linting, this diff --git a/snapcraft/linters/library_linter.py b/snapcraft/linters/library_linter.py index be226fb358..e3c2c9d82b 100644 --- a/snapcraft/linters/library_linter.py +++ b/snapcraft/linters/library_linter.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022-2023 Canonical Ltd. +# Copyright 2022-2024 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 @@ -26,7 +26,7 @@ from snapcraft.elf import ElfFile, SonameCache, elf_utils from snapcraft.elf import errors as elf_errors -from .base import Linter, LinterIssue, LinterResult, Optional +from .base import Linter, LinterIssue, LinterResult class LibraryLinter(Linter): @@ -130,7 +130,7 @@ def _generate_ld_config_cache(self) -> None: if match: self._ld_config_cache[match.group(1)] = Path(match.group(2)) - def _find_deb_package(self, library_name: str) -> Optional[str]: + def _find_deb_package(self, library_name: str) -> str | None: """Find the deb package that provides a library. :param library_name: The filename of the library to find. diff --git a/snapcraft/meta/component_yaml.py b/snapcraft/meta/component_yaml.py index 2dd6056fca..1e278294bf 100644 --- a/snapcraft/meta/component_yaml.py +++ b/snapcraft/meta/component_yaml.py @@ -33,10 +33,10 @@ class ComponentMetadata(SnapcraftMetadata): component: str type: str - version: str | None + version: str | None = None summary: str description: str - provenance: str | None + provenance: str | None = None def write( diff --git a/snapcraft/meta/manifest.py b/snapcraft/meta/manifest.py index eed2c4b8a7..f31d6ffc84 100644 --- a/snapcraft/meta/manifest.py +++ b/snapcraft/meta/manifest.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -19,14 +19,14 @@ import json from datetime import datetime from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any -from pydantic_yaml import YamlModel +import craft_application.models from snapcraft import __version__, errors, models, os_release, utils -class Manifest(YamlModel): +class Manifest(craft_application.models.CraftBaseModel): """Manifest file for snaps.""" # Snapcraft annotations @@ -40,30 +40,24 @@ class Manifest(YamlModel): version: str summary: str description: str - base: Optional[str] + base: str | None = None grade: str confinement: str - apps: Optional[Dict[str, Any]] - parts: Dict[str, Any] + apps: dict[str, Any] | None = None + parts: dict[str, Any] # TODO: add assumes, environment, hooks, slots # Architecture - architectures: List[str] + architectures: list[str] # Image info - image_info: Dict[str, Any] + image_info: dict[str, Any] # Build environment - build_packages: List[str] - build_snaps: List[str] - primed_stage_packages: List - - class Config: - """Pydantic model configuration.""" - - allow_population_by_field_name = True - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + build_packages: list[str] + build_snaps: list[str] + primed_stage_packages: list def write( # noqa PLR0913 @@ -71,10 +65,10 @@ def write( # noqa PLR0913 prime_dir: Path, *, arch: str, - parts: Dict[str, Any], + parts: dict[str, Any], image_information: str, start_time: datetime, - primed_stage_packages: List[str], + primed_stage_packages: list[str], ): """Create a manifest.yaml file.""" snap_dir = prime_dir / "snap" @@ -117,14 +111,4 @@ def write( # noqa PLR0913 primed_stage_packages=primed_stage_packages, ) - yaml_data = manifest.yaml( - by_alias=True, - exclude_none=True, - exclude_unset=True, - allow_unicode=True, - sort_keys=False, - width=1000, - ) - - manifest_yaml = snap_dir / "manifest.yaml" - manifest_yaml.write_text(yaml_data) + manifest.to_yaml_file(snap_dir / "manifest.yaml") diff --git a/snapcraft/meta/snap_yaml.py b/snapcraft/meta/snap_yaml.py index 7e45c39538..1033c0bb2b 100644 --- a/snapcraft/meta/snap_yaml.py +++ b/snapcraft/meta/snap_yaml.py @@ -19,14 +19,13 @@ import re from pathlib import Path -from typing import Any, Dict, List, Literal, Optional, Set, Union, cast +from typing import Any, Literal, cast import pydantic import yaml -from craft_application.models import BaseMetadata, SummaryStr, constraints +from craft_application.models import BaseMetadata, SummaryStr, base, constraints +from craft_application.models.constraints import SingleEntryDict from craft_cli import emit -from pydantic import ValidationError, validator -from typing_extensions import override from snapcraft import errors, models from snapcraft.elf.elf_utils import get_arch_triplet @@ -40,18 +39,27 @@ class SnapcraftMetadata(BaseMetadata): """Snapcraft-specific metadata base model.""" + model_config = pydantic.ConfigDict( + validate_assignment=True, + extra="allow", + populate_by_name=True, + alias_generator=base.alias_generator, + # snap metadata may contain versions as ints or floats + coerce_numbers_to_str=True, + ) + def marshal(self) -> dict[str, str | list[str] | dict[str, Any]]: - """Convert to a dictionary.""" - return self.dict( - by_alias=True, exclude_unset=True, exclude_none=True, exclude_defaults=True + """Convert to a dictionary and exclude defaults.""" + return self.model_dump( + mode="json", by_alias=True, exclude_unset=True, exclude_defaults=True ) class Socket(SnapcraftMetadata): """snap.yaml app socket entry.""" - listen_stream: Union[int, str] - socket_mode: Optional[int] + listen_stream: int | str + socket_mode: int | None = None class SnapApp(SnapcraftMetadata): @@ -65,70 +73,59 @@ class SnapApp(SnapcraftMetadata): """ command: str - autostart: Optional[str] - common_id: Optional[str] - bus_name: Optional[str] - completer: Optional[str] - stop_command: Optional[str] - post_stop_command: Optional[str] - start_timeout: Optional[str] - stop_timeout: Optional[str] - watchdog_timeout: Optional[str] - reload_command: Optional[str] - restart_delay: Optional[str] - timer: Optional[str] - daemon: Optional[str] - after: Optional[List[str]] - before: Optional[List[str]] - refresh_mode: Optional[str] - stop_mode: Optional[str] - restart_condition: Optional[str] - install_mode: Optional[str] - plugs: Optional[List[str]] - slots: Optional[List[str]] - aliases: Optional[List[str]] - environment: Optional[Dict[str, Any]] - command_chain: Optional[List[str]] - sockets: Optional[Dict[str, Socket]] - daemon_scope: Optional[str] - activates_on: Optional[List[str]] + autostart: str | None = None + common_id: str | None = None + bus_name: str | None = None + completer: str | None = None + stop_command: str | None = None + post_stop_command: str | None = None + start_timeout: str | None = None + stop_timeout: str | None = None + watchdog_timeout: str | None = None + reload_command: str | None = None + restart_delay: str | None = None + timer: str | None = None + daemon: str | None = None + after: list[str] | None = None + before: list[str] | None = None + refresh_mode: str | None = None + stop_mode: str | None = None + restart_condition: str | None = None + install_mode: str | None = None + plugs: list[str] | None = None + slots: list[str] | None = None + aliases: list[str] | None = None + environment: dict[str, Any] | None = None + command_chain: list[str] | None = None + sockets: dict[str, Socket] | None = None + daemon_scope: str | None = None + activates_on: list[str] | None = None class ContentPlug(SnapcraftMetadata): # type: ignore # (pydantic plugin is crashing) """Content plug definition in the snap metadata.""" - @override - class Config(BaseMetadata.Config): - """Allow extra parameters in content plugs.""" - - extra = pydantic.Extra.ignore + model_config = pydantic.ConfigDict( + validate_assignment=True, + # allow extra parameters in content plugs + extra="ignore", + populate_by_name=True, + alias_generator=base.alias_generator, + ) interface: Literal["content"] target: str - content: Optional[str] - default_provider: Optional[str] - - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "ContentPlug": - """Create and populate a new ``ContentPlug`` object from dictionary data. + content: str | None = None + default_provider: str | None = None - :param data: The dictionary data to unmarshal. - :return: The newly created object. - :raise TypeError: If data is not a dictionary. - """ - if not isinstance(data, dict): - raise TypeError("data is not a dictionary") - - return cls(**data) - - @validator("target") + @pydantic.field_validator("target") @classmethod def _validate_target_not_empty(cls, val): if val == "": raise ValueError("value cannot be empty") return val - @validator("default_provider") + @pydantic.field_validator("default_provider") @classmethod def _validate_default_provider(cls, default_provider): if default_provider and "/" in default_provider: @@ -139,7 +136,7 @@ def _validate_default_provider(cls, default_provider): return default_provider @property - def provider(self) -> Optional[str]: + def provider(self) -> str | None: """Return the default content provider name.""" if self.default_provider is None: return None @@ -155,26 +152,13 @@ class ContentSlot(SnapcraftMetadata): """Content slot definition in the snap metadata.""" interface: Literal["content"] - content: Optional[str] - read: List[str] = [] - write: List[str] = [] - - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "ContentSlot": - """Create and populate a new ``ContentSlot`` object from dictionary data. - - :param data: The dictionary data to unmarshal. - :return: The newly created object. - :raise TypeError: If data is not a dictionary. - """ - if not isinstance(data, dict): - raise TypeError("data is not a dictionary") - - return cls(**data) + content: str | None = None + read: list[str] = [] + write: list[str] = [] - def get_content_dirs(self, installed_path: Path) -> Set[Path]: + def get_content_dirs(self, installed_path: Path) -> set[Path]: """Obtain the slot's content directories.""" - content_dirs: Set[Path] = set() + content_dirs: set[Path] = set() for path_ in self.read + self.write: # Strip leading "$SNAP" and "/". @@ -189,11 +173,11 @@ def get_content_dirs(self, installed_path: Path) -> Set[Path]: class Links(SnapcraftMetadata): """Metadata links used in snaps.""" - contact: Optional[constraints.UniqueStrList] - donation: Optional[constraints.UniqueStrList] - issues: Optional[constraints.UniqueStrList] - source_code: Optional[constraints.UniqueStrList] - website: Optional[constraints.UniqueStrList] + contact: constraints.UniqueStrList | None = None + donation: constraints.UniqueStrList | None = None + issues: constraints.UniqueStrList | None = None + source_code: constraints.UniqueStrList | None = None + website: constraints.UniqueStrList | None = None @staticmethod def _normalize_value( @@ -235,13 +219,15 @@ class ComponentMetadata(SnapcraftMetadata): # type: ignore # (pydantic plugin i summary: SummaryStr description: str type: str - hooks: dict[str, models.Hook] | None - - @override - class Config(BaseMetadata.Config): - """Ignore extra parameters in component metadata.""" - - extra = pydantic.Extra.ignore + hooks: dict[str, models.Hook] | None = None + + model_config = pydantic.ConfigDict( + validate_assignment=True, + # ignore extra parameters in component metadata + extra="ignore", + populate_by_name=True, + alias_generator=base.alias_generator, + ) @classmethod def from_component(cls, component: models.Component) -> "ComponentMetadata": @@ -259,52 +245,35 @@ class SnapMetadata(SnapcraftMetadata): """ name: str - title: Optional[str] + title: str | None = None version: str summary: SummaryStr description: str - license: Optional[str] - type: Optional[str] - architectures: List[str] - base: Optional[str] - assumes: Optional[List[str]] - epoch: Optional[str] - apps: Optional[Dict[str, SnapApp]] + license: str | None = None + type: str | None = None + architectures: list[str] + base: str | None = None + assumes: list[str] | None = None + epoch: str | None = None + apps: dict[str, SnapApp] | None = None confinement: str grade: str - environment: Optional[Dict[str, Any]] - plugs: Optional[Dict[str, Any]] - slots: Optional[Dict[str, Any]] - hooks: Optional[Dict[str, Any]] - layout: Optional[ - Dict[str, Dict[Literal["symlink", "bind", "bind-file", "type"], str]] - ] - system_usernames: Optional[Dict[str, Any]] - provenance: Optional[str] - links: Optional[Links] - components: Optional[Dict[str, ComponentMetadata]] - - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "SnapMetadata": - """Create and populate a new ``SnapMetadata`` object from dictionary data. - - The unmarshal method validates entries in the input dictionary, populating - the corresponding fields in the data object. - - :param data: The dictionary data to unmarshal. - - :return: The newly created object. - - :raise TypeError: If data is not a dictionary. - """ - if not isinstance(data, dict): - raise TypeError("data is not a dictionary") - - return cls(**data) - - def get_provider_content_directories(self) -> List[Path]: + environment: dict[str, Any] | None = None + plugs: dict[str, Any] | None = None + slots: dict[str, Any] | None = None + hooks: dict[str, Any] | None = None + layout: ( + dict[str, SingleEntryDict[Literal["symlink", "bind", "bind-file", "type"], str]] + | None + ) = None + system_usernames: dict[str, Any] | None = None + provenance: str | None = None + links: Links | None = None + components: dict[str, ComponentMetadata] | None = None + + def get_provider_content_directories(self) -> list[Path]: """Get provider content directories from installed snaps.""" - provider_dirs: Set[Path] = set() + provider_dirs: set[Path] = set() for plug in self.get_content_plugs(): # Get matching slot provider for plug @@ -326,35 +295,35 @@ def get_provider_content_directories(self) -> List[Path]: return sorted(provider_dirs) - def get_content_plugs(self) -> List[ContentPlug]: + def get_content_plugs(self) -> list[ContentPlug]: """Return a list of content plugs from the snap metadata plugs.""" if not self.plugs: return [] - content_plugs: List[ContentPlug] = [] + content_plugs: list[ContentPlug] = [] for name, data in self.plugs.items(): try: plug = ContentPlug.unmarshal(data) if not plug.content: plug.content = name content_plugs.append(plug) - except (TypeError, ValidationError): + except (TypeError, pydantic.ValidationError): continue return content_plugs - def get_content_slots(self) -> List[ContentSlot]: + def get_content_slots(self) -> list[ContentSlot]: """Return a list of content slots from the snap metadata slots.""" if not self.slots: return [] - content_slots: List[ContentSlot] = [] + content_slots: list[ContentSlot] = [] for name, slot_data in self.slots.items(): try: slot = ContentSlot.unmarshal(slot_data) if not slot.content: slot.content = name content_slots.append(slot) - except (TypeError, ValidationError): + except (TypeError, pydantic.ValidationError): continue return content_slots @@ -375,8 +344,8 @@ def read(prime_dir: Path) -> SnapMetadata: return SnapMetadata.unmarshal(data) -def _create_snap_app(app: models.App, assumes: Set[str]) -> SnapApp: - app_sockets: Dict[str, Socket] = {} +def _create_snap_app(app: models.App, assumes: set[str]) -> SnapApp: + app_sockets: dict[str, Socket] = {} if app.sockets: for socket_name, socket in app.sockets.items(): app_sockets[socket_name] = Socket( @@ -425,7 +394,7 @@ def _create_snap_app(app: models.App, assumes: Set[str]) -> SnapApp: return snap_app -def _get_grade(grade: Optional[str], build_base: Optional[str]) -> str: +def _get_grade(grade: str | None = None, build_base: str | None = None) -> str: """Get the grade for a project. If the build_base is `devel`, then the grade should be `devel`. @@ -456,9 +425,9 @@ def get_metadata_from_project( :param prime_dir: The directory containing the content to be snapped. :param arch: Target architecture the snap project is built to. """ - assumes: Set[str] = set() + assumes: set[str] = set() - snap_apps: Dict[str, SnapApp] = {} + snap_apps: dict[str, SnapApp] = {} if project.apps: for name, app in project.apps.items(): snap_apps[name] = _create_snap_app(app, assumes) @@ -542,11 +511,11 @@ def _repr_str(dumper, data): def _populate_environment( - environment: Optional[Dict[str, Optional[str]]], + environment: dict[str, str | None] | None, prime_dir: Path, - arch_triplet: Optional[str], + arch_triplet: str | None, confinement: str, -) -> Optional[Dict[str, Optional[str]]]: +) -> dict[str, str | None] | None: """Populate default app environment variables, LD_LIBRARY_PATH and PATH. Three cases for environment variables: @@ -589,8 +558,8 @@ def _populate_environment( def _process_components( - components: Dict[str, models.Component] | None -) -> Dict[str, ComponentMetadata] | None: + components: dict[str, models.Component] | None +) -> dict[str, ComponentMetadata] | None: """Convert Components from a project to ComponentMetadata for a snap.yaml. :param components: Component data from a project model. diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 5db1c0c66e..b13c88584b 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -19,30 +19,22 @@ import copy import re -from typing import ( - TYPE_CHECKING, - Annotated, - Any, - Dict, - List, - Literal, - Mapping, - Optional, - Sequence, - Tuple, - Union, - cast, -) +from typing import Any, Literal, Mapping, Tuple, cast import pydantic from craft_application import models from craft_application.errors import CraftValidationError -from craft_application.models import BuildInfo, SummaryStr, UniqueStrList, VersionStr +from craft_application.models import BuildInfo, SummaryStr, VersionStr +from craft_application.models.constraints import ( + SingleEntryDict, + SingleEntryList, + UniqueList, +) from craft_cli import emit -from craft_grammar.models import GrammarSingleEntryDictList, GrammarStr, GrammarStrList +from craft_grammar.models import Grammar # type: ignore[import-untyped] from craft_providers import bases -from pydantic import PrivateAttr, constr -from typing_extensions import Self, override +from pydantic import ConfigDict, PrivateAttr, StringConstraints +from typing_extensions import Annotated, Self, override from snapcraft import utils from snapcraft.const import SUPPORTED_ARCHS, SnapArch @@ -57,17 +49,10 @@ is_architecture_supported, ) -# A workaround for mypy false positives -# see https://github.com/samuelcolvin/pydantic/issues/975#issuecomment-551147305 -# fmt: off -if TYPE_CHECKING: - ProjectName = str -else: - ProjectName = constr(max_length=40) -# fmt: on +ProjectName = Annotated[str, StringConstraints(max_length=40)] -def _validate_command_chain(command_chains: Optional[List[str]]) -> Optional[List[str]]: +def _validate_command_chain(command_chains: list[str] | None) -> list[str] | None: """Validate command_chain.""" if command_chains is not None: for command_chain in command_chains: @@ -151,8 +136,8 @@ def _expand_architectures(architectures): # convert strings into Architecture objects if isinstance(architecture, str): architectures[index] = Architecture( - build_on=cast(UniqueStrList, [architecture]), - build_for=cast(UniqueStrList, [architecture]), + build_on=cast(UniqueList[str], [architecture]), + build_for=cast(UniqueList[str], [architecture]), ) elif isinstance(architecture, Architecture): # convert strings to lists @@ -279,8 +264,8 @@ def _validate_component(name: str) -> str: def _get_partitions_from_components( - components_data: Optional[Dict[str, Any]] -) -> Optional[List[str]]: + components_data: dict[str, Any] | None +) -> list[str] | None: """Get a list of partitions based on the project's components. :returns: A list of partitions formatted as ['default', 'component/', ...] @@ -295,10 +280,10 @@ def _get_partitions_from_components( class Socket(models.CraftBaseModel): """Snapcraft app socket definition.""" - listen_stream: Union[int, str] - socket_mode: Optional[int] + listen_stream: int | str + socket_mode: int | None = None - @pydantic.validator("listen_stream") + @pydantic.field_validator("listen_stream") @classmethod def _validate_list_stream(cls, listen_stream): if isinstance(listen_stream, int): @@ -330,10 +315,14 @@ class Lint(models.CraftBaseModel): The "known" linter names are the keys in :ref:`LINTERS` """ - ignore: List[Union[str, Dict[str, List[str]]]] + ignore: list[str | dict[str, list[str]]] # A private field to simplify lookup. - _lint_ignores: Dict[str, List[str]] = PrivateAttr(default_factory=dict) + _lint_ignores: dict[str, list[str]] = PrivateAttr(default_factory=dict) + + def __eq__(self, other): + """Compare two Lint objects and ignore private attributes.""" + return self.ignore == other.ignore def __init__(self, **kwargs): super().__init__(**kwargs) @@ -353,7 +342,7 @@ def all_ignored(self, linter_name: str) -> bool: and len(self._lint_ignores[linter_name]) == 0 ) - def ignored_files(self, linter_name: str) -> List[str]: + def ignored_files(self, linter_name: str) -> list[str]: """Get a list of filenames/patterns to ignore for `lint_name`. Since the main usecase for this method is a for-loop with `fnmatch()`, it will @@ -373,24 +362,24 @@ class App(models.CraftBaseModel): """Snapcraft project app definition.""" command: str - autostart: Optional[str] - common_id: Optional[str] - bus_name: Optional[str] - desktop: Optional[str] - completer: Optional[str] - stop_command: Optional[str] - post_stop_command: Optional[str] - start_timeout: Optional[str] - stop_timeout: Optional[str] - watchdog_timeout: Optional[str] - reload_command: Optional[str] - restart_delay: Optional[str] - timer: Optional[str] - daemon: Optional[Literal["simple", "forking", "oneshot", "notify", "dbus"]] - after: UniqueStrList = cast(UniqueStrList, []) - before: UniqueStrList = cast(UniqueStrList, []) - refresh_mode: Optional[Literal["endure", "restart", "ignore-running"]] - stop_mode: Optional[ + autostart: str | None = None + common_id: str | None = None + bus_name: str | None = None + desktop: str | None = None + completer: str | None = None + stop_command: str | None = None + post_stop_command: str | None = None + start_timeout: str | None = None + stop_timeout: str | None = None + watchdog_timeout: str | None = None + reload_command: str | None = None + restart_delay: str | None = None + timer: str | None = None + daemon: Literal["simple", "forking", "oneshot", "notify", "dbus"] | None = None + after: UniqueList[str] = pydantic.Field(default_factory=list) + before: UniqueList[str] = pydantic.Field(default_factory=list) + refresh_mode: Literal["endure", "restart", "ignore-running"] | None = None + stop_mode: ( Literal[ "sigterm", "sigterm-all", @@ -403,8 +392,9 @@ class App(models.CraftBaseModel): "sigint", "sigint-all", ] - ] - restart_condition: Optional[ + | None + ) = None + restart_condition: ( Literal[ "on-success", "on-failure", @@ -414,19 +404,20 @@ class App(models.CraftBaseModel): "always", "never", ] - ] - install_mode: Optional[Literal["enable", "disable"]] - slots: Optional[UniqueStrList] - plugs: Optional[UniqueStrList] - aliases: Optional[UniqueStrList] - environment: Optional[Dict[str, str]] - command_chain: List[str] = [] - sockets: Optional[Dict[str, Socket]] - daemon_scope: Optional[Literal["system", "user"]] - activates_on: Optional[UniqueStrList] - passthrough: Optional[Dict[str, Any]] - - @pydantic.validator("autostart") + | None + ) = None + install_mode: Literal["enable", "disable"] | None = None + slots: UniqueList[str] | None = None + plugs: UniqueList[str] | None = None + aliases: UniqueList[str] | None = None + environment: dict[str, str] | None = None + command_chain: list[str] = [] + sockets: dict[str, Socket] | None = None + daemon_scope: Literal["system", "user"] | None = None + activates_on: UniqueList[str] | None = None + passthrough: dict[str, Any] | None = None + + @pydantic.field_validator("autostart") @classmethod def _validate_autostart_name(cls, name): if not re.match(r"^[A-Za-z0-9. _#:$-]+\.desktop$", name): @@ -436,7 +427,7 @@ def _validate_autostart_name(cls, name): return name - @pydantic.validator("bus_name") + @pydantic.field_validator("bus_name") @classmethod def _validate_bus_name(cls, name): if not re.match(r"^[A-Za-z0-9/. _#:$-]*$", name): @@ -444,7 +435,7 @@ def _validate_bus_name(cls, name): return name - @pydantic.validator( + @pydantic.field_validator( "start_timeout", "stop_timeout", "watchdog_timeout", "restart_delay" ) @classmethod @@ -454,12 +445,12 @@ def _validate_time(cls, timeval): return timeval - @pydantic.validator("command_chain") + @pydantic.field_validator("command_chain") @classmethod def _validate_command_chain(cls, command_chains): return _validate_command_chain(command_chains) - @pydantic.validator("aliases") + @pydantic.field_validator("aliases") @classmethod def _validate_aliases(cls, aliases): for alias in aliases: @@ -476,17 +467,17 @@ def _validate_aliases(cls, aliases): class Hook(models.CraftBaseModel): """Snapcraft project hook definition.""" - command_chain: Optional[List[str]] - environment: Optional[Dict[str, str]] - plugs: Optional[UniqueStrList] - passthrough: Optional[Dict[str, Any]] + command_chain: list[str] | None = None + environment: dict[str, str] | None = None + plugs: UniqueList[str] | None = None + passthrough: dict[str, Any] | None = None - @pydantic.validator("command_chain") + @pydantic.field_validator("command_chain") @classmethod def _validate_command_chain(cls, command_chains): return _validate_command_chain(command_chains) - @pydantic.validator("plugs") + @pydantic.field_validator("plugs") @classmethod def _validate_plugs(cls, plugs): if not plugs: @@ -494,22 +485,22 @@ def _validate_plugs(cls, plugs): return plugs -class Architecture(models.CraftBaseModel, extra=pydantic.Extra.forbid): +class Architecture(models.CraftBaseModel, extra="forbid"): """Snapcraft project architecture definition.""" - build_on: Union[str, UniqueStrList] - build_for: Optional[Union[str, UniqueStrList]] + build_on: str | UniqueList[str] + build_for: str | UniqueList[str] | None = None class ContentPlug(models.CraftBaseModel): """Snapcraft project content plug definition.""" - content: Optional[str] + content: str | None = None interface: str target: str - default_provider: Optional[str] + default_provider: str | None = None - @pydantic.validator("default_provider") + @pydantic.field_validator("default_provider") @classmethod def _validate_default_provider(cls, default_provider): if default_provider and "/" in default_provider: @@ -523,24 +514,18 @@ def _validate_default_provider(cls, default_provider): class Platform(models.Platform): """Snapcraft project platform definition.""" - build_on: Annotated[ # type: ignore[assignment,reportIncompatibleVariableOverride] - list[SnapArch] | None, - pydantic.Field(min_items=1, unique_items=True), - ] - build_for: Annotated[ # type: ignore[assignment,reportIncompatibleVariableOverride] - list[SnapArch | Literal["all"]] | None, - pydantic.Field(min_items=1, max_items=1, unique_items=True), - ] + build_on: UniqueList[str] | None = pydantic.Field(min_length=1) + build_for: SingleEntryList | None = None - @pydantic.validator("build_on", "build_for", pre=True) + @pydantic.field_validator("build_on", "build_for", mode="before") @classmethod - def _vectorise_build_for(cls, val: str | list[str]) -> list[str]: - """Vectorise target architectures if needed.""" + def _vectorise_build_on_build_for(cls, val: str | list[str]) -> list[str]: + """Vectorise architectures if needed.""" if isinstance(val, str): val = [val] return val - @pydantic.root_validator(skip_on_failure=True) + @pydantic.model_validator(mode="before") @classmethod def _validate_platform_set(cls, values: Mapping[str, Any]) -> Mapping[str, Any]: """If build_for is provided, then build_on must also be. @@ -563,17 +548,19 @@ def from_architectures( platforms: dict[str, Self] = {} for architecture in architectures: if isinstance(architecture, str): - build_on = build_for = cast(UniqueStrList, [architecture]) + build_on = build_for = cast(UniqueList[str], [architecture]) else: if isinstance(architecture.build_on, str): - build_on = build_for = cast(UniqueStrList, [architecture.build_on]) + build_on = build_for = cast( + UniqueList[str], [architecture.build_on] + ) else: - build_on = build_for = cast(UniqueStrList, architecture.build_on) + build_on = build_for = cast(UniqueList[str], architecture.build_on) if architecture.build_for: if isinstance(architecture.build_for, str): - build_for = cast(UniqueStrList, [architecture.build_for]) + build_for = cast(UniqueList[str], [architecture.build_for]) else: - build_for = cast(UniqueStrList, architecture.build_for) + build_for = cast(UniqueList[str], architecture.build_for) platforms[build_for[0]] = cls(build_for=build_for, build_on=build_on) @@ -586,19 +573,8 @@ class Component(models.CraftBaseModel): summary: SummaryStr description: str type: Literal["test"] - version: Optional[VersionStr] # type: ignore[assignment] - hooks: dict[str, Hook] | None - - @pydantic.validator("version") - @classmethod - def _validate_version(cls, version): - if version == "": - raise ValueError("Component version cannot be an empty string.") - - if version: - _validate_version_name(version, "Component") - - return version + version: VersionStr | None = None + hooks: dict[str, Hook] | None = None MANDATORY_ADOPTABLE_FIELDS = ("version", "summary", "description") @@ -615,41 +591,41 @@ class Project(models.Project): # snapcraft's `name` is more general than craft-application name: ProjectName # type: ignore[assignment] - build_base: Optional[str] + build_base: str | None = pydantic.Field(validate_default=True, default=None) compression: Literal["lzo", "xz"] = "xz" - version: Optional[VersionStr] # type: ignore[assignment] - donation: Optional[UniqueStrList] + version: VersionStr | None = None + donation: UniqueList[str] | None = None # snapcraft's `source_code` is more general than craft-application - source_code: Optional[UniqueStrList] # type: ignore[assignment] - contact: Optional[UniqueStrList] # type: ignore[assignment] - issues: Optional[UniqueStrList] # type: ignore[assignment] - website: Optional[UniqueStrList] - type: Optional[Literal["app", "base", "gadget", "kernel", "snapd"]] - icon: Optional[str] + source_code: UniqueList[str] | None = None # type: ignore[assignment] + contact: UniqueList[str] | None = None # type: ignore[assignment] + issues: UniqueList[str] | None = None # type: ignore[assignment] + website: UniqueList[str] | None = None + type: Literal["app", "base", "gadget", "kernel", "snapd"] | None = None + icon: str | None = None confinement: Literal["classic", "devmode", "strict"] - layout: Optional[ - Dict[str, Dict[Literal["symlink", "bind", "bind-file", "type"], str]] - ] - grade: Optional[Literal["stable", "devel"]] - architectures: List[Union[str, Architecture]] | None = None + layout: ( + dict[str, SingleEntryDict[Literal["symlink", "bind", "bind-file", "type"], str]] + | None + ) = None + grade: Literal["stable", "devel"] | None = None + architectures: list[str | Architecture] | None = None platforms: dict[str, Platform] | None = None # type: ignore[assignment,reportIncompatibleVariableOverride] - assumes: UniqueStrList = cast(UniqueStrList, []) - package_repositories: Optional[List[Dict[str, Any]]] - hooks: Optional[Dict[str, Hook]] - passthrough: Optional[Dict[str, Any]] - apps: Optional[Dict[str, App]] - plugs: Optional[Dict[str, Union[ContentPlug, Any]]] - slots: Optional[Dict[str, Any]] - lint: Optional[Lint] - epoch: Optional[str] - adopt_info: Optional[str] - system_usernames: Optional[Dict[str, Any]] - environment: Optional[Dict[str, Optional[str]]] - build_packages: Optional[GrammarStrList] - build_snaps: Optional[GrammarStrList] - ua_services: Optional[UniqueStrList] - provenance: Optional[str] - components: Optional[Dict[ProjectName, Component]] + assumes: UniqueList[str] = pydantic.Field(default_factory=list) + hooks: dict[str, Hook] | None = None + passthrough: dict[str, Any] | None = None + apps: dict[str, App] | None = None + plugs: dict[str, ContentPlug | Any] | None = None + slots: dict[str, Any] | None = None + lint: Lint | None = None + epoch: str | None = None + adopt_info: str | None = None + system_usernames: dict[str, Any] | None = None + environment: dict[str, str | None] | None = None + build_packages: Grammar[list[str]] | None = None + build_snaps: Grammar[list[str]] | None = None + ua_services: set[str] | None = None + provenance: str | None = None + components: dict[ProjectName, Component] | None = None @override @classmethod @@ -676,7 +652,7 @@ def _providers_base(cls, base: str) -> bases.BaseAlias | None: except KeyError as err: raise CraftValidationError(f"Unknown base {base!r}") from err - @pydantic.validator("plugs") + @pydantic.field_validator("plugs") @classmethod def _validate_plugs(cls, plugs): empty_plugs = [] @@ -711,7 +687,7 @@ def _validate_plugs(cls, plugs): return plugs - @pydantic.validator("slots") + @pydantic.field_validator("slots") @classmethod def _validate_slots(cls, slots): empty_slots = [] @@ -726,43 +702,31 @@ def _validate_slots(cls, slots): return slots - @pydantic.root_validator(pre=True) - @classmethod - def _validate_adoptable_fields(cls, values): + @pydantic.model_validator(mode="after") + def _validate_adoptable_fields(self) -> Self: for field in MANDATORY_ADOPTABLE_FIELDS: - if field not in values and "adopt-info" not in values: + if getattr(self, field) is None and self.adopt_info is None: raise ValueError( f"Required field '{field}' is not set and 'adopt-info' not used." ) - return values + return self - @pydantic.root_validator(pre=True) - @classmethod - def _validate_mandatory_base(cls, values): - snap_type = values.get("type") - base = values.get("base") + @pydantic.model_validator(mode="after") + def _validate_mandatory_base(self): + snap_type = self.type + base = self.base if (base is not None) ^ (snap_type not in ["base", "kernel", "snapd"]): raise ValueError( "Snap base must be declared when type is not base, kernel or snapd" ) - return values + return self - @pydantic.validator("name") + @pydantic.field_validator("name") @classmethod def _validate_snap_name(cls, name): return _validate_name(name=name, field_name="snap") - @pydantic.validator("version") - @classmethod - def _validate_version(cls, version, values): - if not version and "adopt_info" not in values: - raise ValueError("Version must be declared if not adopting metadata") - - _validate_version_name(version, "Snap") - - return version - - @pydantic.validator("components") + @pydantic.field_validator("components") @classmethod def _validate_components(cls, components): """Validate component names.""" @@ -771,18 +735,8 @@ def _validate_components(cls, components): return components - @pydantic.validator("grade", "summary", "description") - @classmethod - def _validate_adoptable_field(cls, field_value, values, field): - if not field_value and "adopt_info" not in values: - raise ValueError( - f"{field.name.capitalize()} must be declared if not adopting metadata" - ) - return field_value - - @pydantic.root_validator(pre=False) - @classmethod - def _validate_platforms_and_architectures(cls, values): + @pydantic.model_validator(mode="after") + def _validate_platforms_and_architectures(self) -> Self: """Validate usage of platforms and architectures. core22 base: @@ -794,51 +748,50 @@ def _validate_platforms_and_architectures(cls, values): - can optionally define platforms """ base = get_effective_base( - base=values.get("base"), - build_base=values.get("build_base"), - project_type=values.get("type"), - name=values.get("name"), + base=self.base, + build_base=self.build_base, + project_type=self.type, + name=self.name, ) if base == "core22": - if values.get("platforms"): + if self.platforms: raise ValueError( f"'platforms' keyword is not supported for base {base!r}. " "Use 'architectures' keyword instead." ) # set default value - if not values.get("architectures"): - values["architectures"] = [ + if not self.architectures: + self.architectures = [ Architecture( - build_on=cast(UniqueStrList, [get_host_architecture()]), - build_for=cast(UniqueStrList, [get_host_architecture()]), + build_on=[get_host_architecture()], + build_for=[get_host_architecture()], ) ] - elif values.get("architectures"): + elif self.architectures: raise ValueError( f"'architectures' keyword is not supported for base {base!r}. " "Use 'platforms' keyword instead." ) - return values + return self - @pydantic.root_validator() - @classmethod - def _validate_grade_and_build_base(cls, values): + @pydantic.model_validator(mode="after") + def _validate_grade_and_build_base(self) -> Self: """If build_base is devel, then grade must be devel.""" - if values.get("build_base") == "devel" and values.get("grade") == "stable": + if self.build_base == "devel" and self.grade == "stable": raise ValueError("grade must be 'devel' when build-base is 'devel'") - return values + return self - @pydantic.validator("build_base", always=True) + @pydantic.field_validator("build_base") @classmethod - def _validate_build_base(cls, build_base, values): + def _validate_build_base( + cls, value: str | None, info: pydantic.ValidationInfo + ) -> str | None: """Build-base defaults to the base value if not specified.""" - if not build_base: - build_base = values.get("base") - return build_base + return value or info.data.get("base") - @pydantic.validator("epoch") + @pydantic.field_validator("epoch") @classmethod def _validate_epoch(cls, epoch): """Verify epoch format.""" @@ -849,13 +802,13 @@ def _validate_epoch(cls, epoch): return epoch - @pydantic.validator("architectures", always=True) + @pydantic.field_validator("architectures") @classmethod def _validate_architecture_data(cls, architectures): """Validate architecture data.""" return validate_architectures(architectures) - @pydantic.validator("provenance") + @pydantic.field_validator("provenance") @classmethod def _validate_provenance(cls, provenance): if provenance and not re.match(r"^[a-zA-Z0-9-]+$", provenance): @@ -865,39 +818,16 @@ def _validate_provenance(cls, provenance): return provenance - @pydantic.validator( - "contact", "donation", "issues", "source_code", "website", pre=True + @pydantic.field_validator( + "contact", "donation", "issues", "source_code", "website", mode="before" ) @classmethod def _validate_urls(cls, field_value): if isinstance(field_value, str): - field_value = cast(UniqueStrList, [field_value]) + field_value = cast(UniqueList[str], [field_value]) return field_value - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "Project": - """Create and populate a new ``Project`` object from dictionary data. - - The unmarshal method validates entries in the input dictionary, populating - the corresponding fields in the data object. - - :param data: The dictionary data to unmarshal. - - :return: The newly created object. - - :raise TypeError: If data is not a dictionary. - """ - if not isinstance(data, dict): - raise TypeError("Project data is not a dictionary") - - try: - project = Project(**data) - except pydantic.ValidationError as err: - raise ProjectValidationError(_format_pydantic_errors(err.errors())) from err - - return project - - def _get_content_plugs(self) -> List[ContentPlug]: + def _get_content_plugs(self) -> list[ContentPlug]: """Get list of content plugs.""" if self.plugs is not None: return [ @@ -905,7 +835,7 @@ def _get_content_plugs(self) -> List[ContentPlug]: ] return [] - def get_content_snaps(self) -> List[str]: + def get_content_snaps(self) -> list[str]: """Get list of snaps from ContentPlug `default-provider` fields.""" return [ x.default_provider @@ -913,10 +843,10 @@ def get_content_snaps(self) -> List[str]: if x.default_provider is not None ] - def get_extra_build_snaps(self) -> List[str]: + def get_extra_build_snaps(self) -> list[str]: """Get list of extra snaps required to build.""" # Build snaps defined by the user with channel stripped - build_snaps: List[str] = [] + build_snaps: list[str] = [] for part in self.parts.values(): build_snaps.extend(part.get("build-snaps", [])) part_build_snaps = {p.split("/")[0] for p in build_snaps} @@ -954,7 +884,7 @@ def get_build_on(self) -> str: if ( self.architectures and isinstance(self.architectures[0], Architecture) - and isinstance(self.architectures[0].build_on, List) + and isinstance(self.architectures[0].build_on, list) ): return self.architectures[0].build_on[0] @@ -966,14 +896,14 @@ def get_build_for(self) -> str: if ( self.architectures and isinstance(self.architectures[0], Architecture) - and isinstance(self.architectures[0].build_for, List) + and isinstance(self.architectures[0].build_for, list) ): return self.architectures[0].build_for[0] # will not happen after schema validation raise RuntimeError("cannot determine build-for architecture") - def get_build_for_arch_triplet(self) -> Optional[str]: + def get_build_for_arch_triplet(self) -> str | None: """Get the architecture triplet for the first build-for architecture for core22. :returns: The build-for arch triplet. If build-for is "all", then return None. @@ -985,14 +915,14 @@ def get_build_for_arch_triplet(self) -> Optional[str]: return None - def get_component_names(self) -> List[str]: + def get_component_names(self) -> list[str]: """Get a list of component names. :returns: A list of component names. """ return list(self.components.keys()) if self.components else [] - def get_partitions(self) -> Optional[List[str]]: + def get_partitions(self) -> list[str] | None: """Get a list of partitions based on the project's components. :returns: A list of partitions formatted as ['default', 'component/', ...] @@ -1002,32 +932,31 @@ def get_partitions(self) -> Optional[List[str]]: class _GrammarAwareModel(pydantic.BaseModel): - class Config: - """Default configuration for grammar-aware models.""" - - validate_assignment = True - extra = "allow" # this is required to verify only grammar-aware parts - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 - allow_population_by_field_name = True + model_config = ConfigDict( + validate_assignment=True, + extra="allow", + alias_generator=lambda s: s.replace("_", "-"), + populate_by_name=True, + ) class _GrammarAwarePart(_GrammarAwareModel): - source: Optional[GrammarStr] - build_environment: Optional[GrammarSingleEntryDictList] - build_packages: Optional[GrammarStrList] - stage_packages: Optional[GrammarStrList] - build_snaps: Optional[GrammarStrList] - stage_snaps: Optional[GrammarStrList] - parse_info: Optional[List[str]] + source: Grammar[str] | None = None + build_environment: Grammar[list[SingleEntryDict[str, str]]] | None = None + build_packages: Grammar[list[str]] | None = None + stage_packages: Grammar[list[str]] | None = None + build_snaps: Grammar[list[str]] | None = None + stage_snaps: Grammar[list[str]] | None = None + parse_info: list[str] | None = None class GrammarAwareProject(_GrammarAwareModel): """Project definition containing grammar-aware components.""" - parts: Dict[str, _GrammarAwarePart] + parts: dict[str, _GrammarAwarePart] @classmethod - def validate_grammar(cls, data: Dict[str, Any]) -> None: + def validate_grammar(cls, data: dict[str, Any]) -> None: """Ensure grammar-enabled entries are syntactically valid.""" try: cls(**data) @@ -1035,47 +964,27 @@ def validate_grammar(cls, data: Dict[str, Any]) -> None: raise ProjectValidationError(_format_pydantic_errors(err.errors())) from err -class ArchitectureProject(models.CraftBaseModel, extra=pydantic.Extra.ignore): +class ArchitectureProject(models.CraftBaseModel, extra="ignore"): """Project definition containing only architecture data.""" - architectures: List[Union[str, Architecture]] = [get_host_architecture()] + architectures: list[str | Architecture] = pydantic.Field( + default=[get_host_architecture()], + validate_default=True, + ) - @pydantic.validator("architectures", always=True) + @pydantic.field_validator("architectures") @classmethod def _validate_architecture_data(cls, architectures): """Validate architecture data.""" return validate_architectures(architectures) - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "ArchitectureProject": - """Create and populate a new ``ArchitectureProject`` object from dictionary data. - - The unmarshal method validates entries in the input dictionary, populating - the corresponding fields in the data object. - - :param data: The dictionary data to unmarshal. - - :return: The newly created object. - - :raise TypeError: If data is not a dictionary. - """ - if not isinstance(data, dict): - raise TypeError("Project data is not a dictionary") - - try: - architectures = ArchitectureProject(**data) - except pydantic.ValidationError as err: - raise ProjectValidationError(_format_pydantic_errors(err.errors())) from err - - return architectures - -class ComponentProject(models.CraftBaseModel, extra=pydantic.Extra.ignore): +class ComponentProject(models.CraftBaseModel, extra="ignore"): """Project definition containing only component data.""" - components: Optional[Dict[ProjectName, Component]] + components: dict[ProjectName, Component] | None = None - @pydantic.validator("components") + @pydantic.field_validator("components") @classmethod def _validate_components(cls, components): """Validate component names.""" @@ -1084,37 +993,14 @@ def _validate_components(cls, components): return components - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "ComponentProject": - """Create and populate a new ``ComponentProject`` object from dictionary data. - - The unmarshal method validates entries in the input dictionary, populating - the corresponding fields in the data object. - - :param data: The dictionary data to unmarshal. - - :return: The newly created object. - - :raise TypeError: If data is not a dictionary. - """ - if not isinstance(data, dict): - raise TypeError("Project data is not a dictionary") - - try: - components = ComponentProject(**data) - except pydantic.ValidationError as err: - raise ProjectValidationError(_format_pydantic_errors(err.errors())) from err - - return components - - def get_component_names(self) -> List[str]: + def get_component_names(self) -> list[str]: """Get a list of component names. :returns: A list of component names. """ return list(self.components.keys()) if self.components else [] - def get_partitions(self) -> Optional[List[str]]: + def get_partitions(self) -> list[str] | None: """Get a list of partitions based on the project's components. :returns: A list of partitions formatted as ['default', 'component/', ...] @@ -1217,7 +1103,7 @@ def _printable_field_location_split(location: str) -> Tuple[str, str]: return field_name, "top-level" -def _format_global_keyword_warning(keyword: str, empty_entries: List[str]) -> str: +def _format_global_keyword_warning(keyword: str, empty_entries: list[str]) -> str: """Create a warning message about global assignment in the ``keyword`` field. :param keyword: @@ -1242,38 +1128,24 @@ def _format_global_keyword_warning(keyword: str, empty_entries: List[str]) -> st class SnapcraftBuildPlanner(models.BuildPlanner): """A project model that creates build plans.""" - base: str | None + base: str | None = None build_base: str | None = None name: str platforms: dict[str, Platform] | None = None # type: ignore[assignment] - architectures: List[Union[str, Architecture]] | None = None + architectures: list[str | Architecture] | None = None project_type: str | None = pydantic.Field(default=None, alias="type") - @pydantic.validator("platforms") + @pydantic.field_validator("platforms") @classmethod - def _validate_all_platforms(cls, platforms: dict[str, Any]) -> dict[str, Any]: + def _validate_all_platforms( + cls, platforms: dict[str, Platform] + ) -> dict[str, Platform]: """Validate and convert platform data to a dict of Platforms.""" - for platform_label in platforms: - platform_data: Platform | dict[str, Any] = ( - platforms[platform_label] if platforms[platform_label] else {} - ) + for platform_label, platform in platforms.items(): error_prefix = f"Error for platform entry '{platform_label}'" - - # Make sure the provided platform_set is valid - if isinstance(platform_data, Platform): - platform = platform_data - else: - try: - platform = Platform(**platform_data) - except CraftValidationError as err: - raise ValueError(f"{error_prefix}: {str(err)}") from None - # build_on and build_for are validated # let's also validate the platform label - if platform.build_on: - build_on_one_of: Sequence[SnapArch | str] = platform.build_on - else: - build_on_one_of = [platform_label] + build_on_one_of = platform.build_on or [platform_label] # If the label maps to a valid architecture and # `build-for` is present, then both need to have the same value, @@ -1311,7 +1183,7 @@ def _validate_all_platforms(cls, platforms: dict[str, Any]) -> dict[str, Any]: return platforms - def get_build_plan(self) -> List[BuildInfo]: + def get_build_plan(self) -> list[BuildInfo]: """Get the build plan for this project.""" build_infos: list[BuildInfo] = [] effective_base = SNAPCRAFT_BASE_TO_PROVIDER_BASE[ @@ -1332,8 +1204,8 @@ def get_build_plan(self) -> List[BuildInfo]: if self.platforms is None: self.platforms = { get_host_architecture(): Platform( - build_on=[SnapArch(get_host_architecture())], - build_for=[SnapArch(get_host_architecture())], + build_on=[SnapArch(get_host_architecture()).value], + build_for=[SnapArch(get_host_architecture()).value], ) } # For backwards compatibility with core22, convert the platforms. diff --git a/snapcraft/parts/yaml_utils.py b/snapcraft/parts/yaml_utils.py index 42c0a88a5e..0480a916f8 100644 --- a/snapcraft/parts/yaml_utils.py +++ b/snapcraft/parts/yaml_utils.py @@ -25,7 +25,7 @@ from snapcraft import const, errors, utils from snapcraft.extensions import apply_extensions -from snapcraft.models import Architecture, GrammarAwareProject +from snapcraft.models import GrammarAwareProject from . import grammar @@ -208,9 +208,7 @@ def apply_yaml( ) ): # replace all architectures with the architectures in the current build plan - yaml_data["architectures"] = [ - Architecture(build_on=build_on, build_for=build_for) - ] + yaml_data["architectures"] = [{"build-on": build_on, "build-for": build_for}] else: # replace all platforms with the platform in the current build plan yaml_data["platforms"] = { diff --git a/snapcraft/snap_config.py b/snapcraft/snap_config.py index e78a6c4282..9f5d1620ea 100644 --- a/snapcraft/snap_config.py +++ b/snapcraft/snap_config.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -15,53 +15,27 @@ # along with this program. If not, see . """Snap config file definitions and helpers.""" -from typing import Any, Dict, Literal, Optional +from typing import Annotated, Literal, Optional +import craft_application.models import pydantic from craft_cli import emit from snaphelpers import SnapConfigOptions, SnapCtlError from snapcraft.utils import is_snapcraft_running_from_snap +ProviderName = Annotated[ + Literal["lxd", "multipass"], pydantic.BeforeValidator(lambda name: name.lower()) +] -class SnapConfig(pydantic.BaseModel, extra=pydantic.Extra.forbid): + +class SnapConfig(craft_application.models.CraftBaseModel): """Data stored in a snap config. :param provider: provider to use. Valid values are 'lxd' and 'multipass'. """ - provider: Optional[Literal["lxd", "multipass"]] = None - - @pydantic.validator("provider", pre=True) - @classmethod - def convert_to_lower(cls, provider): - """Convert provider value to lowercase.""" - return provider.lower() - - @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "SnapConfig": - """Create and populate a new ``SnapConfig`` object from dictionary data. - - The unmarshal method validates entries in the input dictionary, populating - the corresponding fields in the data object. - - :param data: The dictionary data to unmarshal. - - :return: The newly created object. - - :raise TypeError: If data is not a dictionary. - :raise ValueError: If data is invalid. - """ - if not isinstance(data, dict): - raise TypeError("snap config data is not a dictionary") - - try: - snap_config = cls(**data) - except pydantic.ValidationError as error: - # TODO: use `_format_pydantic_errors()` from project.py - raise ValueError(f"error parsing snap config: {error}") from error - - return snap_config + provider: ProviderName | None = None def get_snap_config() -> Optional[SnapConfig]: diff --git a/snapcraft/ua_manager.py b/snapcraft/ua_manager.py index a6f58e9556..4956132e23 100644 --- a/snapcraft/ua_manager.py +++ b/snapcraft/ua_manager.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2021-2022 Canonical Ltd. +# Copyright 2021-2022,2024 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 @@ -73,7 +73,7 @@ def _attach(ua_token: str) -> None: raise UATokenAttachError from error -def _enable_services(services: List[str]) -> None: +def _enable_services(services: set[str]) -> None: """Enable the specified UA services. If a service is already enabled, it will not be re-enabled because UA will raise @@ -81,16 +81,16 @@ def _enable_services(services: List[str]) -> None: """ with emit.open_stream("Enable UA services") as stream: service_data = _status().get("services") - enabled_services = [] - disabled_services = [] + enabled_services = set() + disabled_services = set() if service_data: # sort services into enabled and disabled for service in services: if _is_service_enabled(service, service_data): - enabled_services.append(service) + enabled_services.add(service) else: - disabled_services.append(service) + disabled_services.add(service) else: # assume all services are disabled if they are not in the status data disabled_services = services @@ -105,7 +105,13 @@ def _enable_services(services: List[str]) -> None: emit.debug(f"Enabling services {disabled_services}.") try: subprocess.check_call( - ["ua", "enable", *disabled_services, "--beta", "--assume-yes"], + [ + "ua", + "enable", + *sorted(disabled_services), + "--beta", + "--assume-yes", + ], stdout=stream, stderr=stream, ) @@ -153,7 +159,7 @@ def _status() -> Dict[str, Any]: @contextlib.contextmanager def ua_manager( - ua_token: Optional[str], *, services: Optional[List[str]] + ua_token: Optional[str], *, services: Optional[set[str]] ) -> Iterator[None]: """Attach and detach UA token as required. diff --git a/snapcraft_legacy/storeapi/v2/validation_sets.py b/snapcraft_legacy/storeapi/v2/validation_sets.py index 3dbe84f6b9..3dc7b68c45 100644 --- a/snapcraft_legacy/storeapi/v2/validation_sets.py +++ b/snapcraft_legacy/storeapi/v2/validation_sets.py @@ -14,19 +14,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from typing import Any, Annotated, Dict, Literal, TYPE_CHECKING +from typing import Any, Literal import numbers import pydantic from craft_application import models +from typing_extensions import Annotated -if TYPE_CHECKING: - SnapName = str - SnapId = str -else: - SnapName = pydantic.constr(max_length=40) - SnapId = pydantic.constr(max_length=40) +SnapName = Annotated[str, pydantic.StringConstraints(max_length=40)] +SnapId = Annotated[str, pydantic.StringConstraints(max_length=40)] def cast_dict_scalars_to_strings(data: dict) -> dict[str, Any]: @@ -66,13 +63,13 @@ class Snap(models.CraftBaseModel): name: SnapName """Snap name""" - id: SnapId | None + id: SnapId | None = None """Snap ID""" - presence: Literal["required", "optional", "invalid"] | None + presence: Literal["required", "optional", "invalid"] | None = None """Snap presence""" - revision: int | None + revision: int | None = None """Snap revision""" @@ -87,16 +84,16 @@ class EditableBuildAssertion(models.CraftBaseModel): name: str """The "name" assertion header""" - revision: str | None + revision: str | None = None """The "revision" assertion header""" sequence: int """The "sequence" assertion header""" - snaps: Annotated[list[Snap], pydantic.Field(min_items=1)] + snaps: Annotated[list[Snap], pydantic.Field(min_length=1)] """List of snaps in a Validation Set assertion""" - def marshal_scalars_as_strings(self) -> Dict[str, Any]: + def marshal_scalars_as_strings(self) -> dict[str, Any]: """Marshal the object where all scalars are represented as strings.""" data = self.marshal() return cast_dict_scalars_to_strings(data) @@ -113,7 +110,7 @@ class BuildAssertion(EditableBuildAssertion): series: str """The "series" assertion header""" - sign_key_sha3_384: str | None + sign_key_sha3_384: None = None """Signing key ID.""" timestamp: str @@ -122,7 +119,8 @@ class BuildAssertion(EditableBuildAssertion): type: Literal["validation-set"] """The "type" assertion header""" - @pydantic.root_validator(pre=True) + @pydantic.model_validator(mode="before") + @classmethod def remove_sign_key(cls, values): """Accept but always ignore the sign key. diff --git a/tests/spread/general/snap-config/task.yaml b/tests/spread/general/snap-config/task.yaml index dfcf34834d..2d241217aa 100644 --- a/tests/spread/general/snap-config/task.yaml +++ b/tests/spread/general/snap-config/task.yaml @@ -42,7 +42,7 @@ execute: | # finally, set an invalid value and verify the configure hook exits with an error output="$(sudo snap set snapcraft provider=invalid-value 2>&1 || true)" - if ! [[ $output =~ "unexpected value; permitted: 'lxd', 'multipass'" ]]; then + if ! [[ $output =~ "Input should be 'lxd' or 'multipass'" ]]; then echo "configure hook did not exit with the expected error message" exit 1 fi diff --git a/tests/unit/commands/test_expand_extensions.py b/tests/unit/commands/test_expand_extensions.py index 7cfca2725e..7eda7af40d 100644 --- a/tests/unit/commands/test_expand_extensions.py +++ b/tests/unit/commands/test_expand_extensions.py @@ -20,6 +20,7 @@ from textwrap import dedent import pytest +from craft_platforms import DebianArchitecture from snapcraft import commands, const @@ -33,7 +34,7 @@ class CoreData: grade: str -@pytest.fixture(params=const.CURRENT_BASES) +@pytest.fixture(params=const.CURRENT_BASES - {"core22"}) def valid_core_data(request) -> CoreData: """Fixture that provides valid base, build-base and grade values for each base.""" # special handling for devel base @@ -43,6 +44,71 @@ def valid_core_data(request) -> CoreData: return CoreData(base=request.param, build_base=request.param, grade="stable") +@pytest.mark.usefixtures("fake_extension") +def test_expand_extensions_simple_core22(new_dir, emitter): + """Expand an extension for a simple snapcraft.yaml file.""" + with Path("snapcraft.yaml").open("w") as yaml_file: + print( + dedent( + """\ + name: test-name + version: "0.1" + summary: testing extensions + description: expand a fake extension + base: core22 + confinement: strict + grade: stable + + apps: + app1: + command: app1 + command-chain: [fake-command] + extensions: [fake-extension] + + parts: + part1: + plugin: nil + """ + ), + file=yaml_file, + ) + + cmd = commands.ExpandExtensionsCommand(None) + cmd.run(Namespace()) + emitter.assert_message( + dedent( + f"""\ + name: test-name + version: '0.1' + summary: testing extensions + description: expand a fake extension + base: core22 + parts: + part1: + plugin: nil + after: + - fake-extension/fake-part + fake-extension/fake-part: + plugin: nil + confinement: strict + grade: stable + architectures: + - build-on: + - {DebianArchitecture.from_host()} + build-for: + - {DebianArchitecture.from_host()} + apps: + app1: + command: app1 + plugs: + - fake-plug + command-chain: + - fake-command + """ + ) + ) + + @pytest.mark.usefixtures("fake_extension") def test_expand_extensions_simple(new_dir, emitter, valid_core_data): """Expand an extension for a simple snapcraft.yaml file.""" @@ -84,22 +150,107 @@ def test_expand_extensions_simple(new_dir, emitter, valid_core_data): description: expand a fake extension base: {valid_core_data.base} build-base: {valid_core_data.build_base} + parts: + part1: + plugin: nil + after: + - fake-extension/fake-part + fake-extension/fake-part: + plugin: nil confinement: strict grade: {valid_core_data.grade} apps: - app1: - command: app1 - command-chain: - - fake-command - plugs: - - fake-plug - parts: - part1: + app1: + command: app1 + plugs: + - fake-plug + command-chain: + - fake-command + """ + ) + ) + + +@pytest.mark.usefixtures("fake_extension") +def test_expand_extensions_complex_core22(new_dir, emitter, mocker): + """Expand an extension for a complex snapcraft.yaml file. + + This includes parse-info, architectures, and advanced grammar. + """ + # mock for advanced grammar parsing (i.e. `on amd64:`) + mocker.patch( + "snapcraft.commands.extensions.get_host_architecture", + return_value="amd64", + ) + with Path("snapcraft.yaml").open("w") as yaml_file: + print( + dedent( + """\ + name: test-name + version: "0.1" + summary: testing extensions + description: expand a fake extension + base: core22 + confinement: strict + grade: stable + architectures: [amd64, arm64, armhf] + + apps: + app1: + command: app1 + command-chain: [fake-command] + extensions: [fake-extension] + + parts: + nil: + plugin: nil + parse-info: + - usr/share/metainfo/app1.appdata.xml + stage-packages: + - mesa-opencl-icd + - ocl-icd-libopencl1 + - on amd64: + - intel-opencl-icd + """ + ), + file=yaml_file, + ) + + cmd = commands.ExpandExtensionsCommand(None) + cmd.run(Namespace()) + emitter.assert_message( + dedent( + f"""\ + name: test-name + version: '0.1' + summary: testing extensions + description: expand a fake extension + base: core22 + parts: + nil: plugin: nil + stage-packages: + - mesa-opencl-icd + - ocl-icd-libopencl1 + - intel-opencl-icd after: - fake-extension/fake-part - fake-extension/fake-part: + fake-extension/fake-part: plugin: nil + confinement: strict + grade: stable + architectures: + - build-on: + - {DebianArchitecture.from_host()} + build-for: + - {DebianArchitecture.from_host()} + apps: + app1: + command: app1 + plugs: + - fake-plug + command-chain: + - fake-command """ ) ) @@ -128,7 +279,7 @@ def test_expand_extensions_complex(new_dir, emitter, mocker, valid_core_data): build-base: {valid_core_data.build_base} confinement: strict grade: {valid_core_data.grade} - architectures: [amd64, arm64, armhf] + platforms: [amd64, arm64, armhf] apps: app1: @@ -162,26 +313,26 @@ def test_expand_extensions_complex(new_dir, emitter, mocker, valid_core_data): description: expand a fake extension base: {valid_core_data.base} build-base: {valid_core_data.build_base} + parts: + nil: + plugin: nil + stage-packages: + - mesa-opencl-icd + - ocl-icd-libopencl1 + - intel-opencl-icd + after: + - fake-extension/fake-part + fake-extension/fake-part: + plugin: nil confinement: strict grade: {valid_core_data.grade} apps: - app1: - command: app1 - command-chain: - - fake-command - plugs: - - fake-plug - parts: - nil: - plugin: nil - stage-packages: - - mesa-opencl-icd - - ocl-icd-libopencl1 - - intel-opencl-icd - after: - - fake-extension/fake-part - fake-extension/fake-part: - plugin: nil + app1: + command: app1 + plugs: + - fake-plug + command-chain: + - fake-command """ ) ) diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index fe2551affc..f64e521a61 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -27,8 +27,8 @@ import pytest from craft_application import launchpad from craft_application.errors import RemoteBuildError +from craft_application.git import GitRepo from craft_application.launchpad.models import BuildState -from craft_application.remote.git import GitRepo from craft_application.remote.utils import get_build_id from snapcraft import application, const diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 3708e5aa3c..c3bcc5f309 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -393,22 +393,22 @@ def extra_project_params(): @pytest.fixture() def default_project(extra_project_params): - from craft_application.models import SummaryStr, VersionStr - from snapcraft.models.project import Project parts = extra_project_params.pop("parts", {}) - return Project( - name="default", - version=VersionStr("1.0"), - summary=SummaryStr("default project"), - description="default project", - base="core24", - grade="devel", - parts=parts, - license="MIT", - **extra_project_params, + return Project.unmarshal( + { + "name": "default", + "version": "1.0", + "summary": "default project", + "description": "default project", + "base": "core24", + "grade": "devel", + "parts": parts, + "license": "MIT", + **extra_project_params, + } ) diff --git a/tests/unit/meta/test_snap_yaml.py b/tests/unit/meta/test_snap_yaml.py index a44878f6dd..4adbe3f562 100644 --- a/tests/unit/meta/test_snap_yaml.py +++ b/tests/unit/meta/test_snap_yaml.py @@ -16,12 +16,10 @@ import textwrap from pathlib import Path -from typing import cast import pydantic import pytest import yaml -from craft_application.models import SummaryStr, UniqueStrList, VersionStr from snapcraft import const, models from snapcraft.meta import snap_yaml @@ -1018,7 +1016,7 @@ def test_content_plug_invalid_target(): assert len(err) == 1 assert err[0]["loc"] == ("target",) assert err[0]["type"] == "value_error" - assert err[0]["msg"] == "value cannot be empty" + assert err[0]["msg"] == "Value error, value cannot be empty" def test_content_plug_provider(): @@ -1366,18 +1364,23 @@ def test_no_links(simple_project): def test_component_metadata_from_component(): """Create a ComponentMetadata from a Component.""" - component = models.Component( - summary=SummaryStr("test"), - description="test", - type="test", - version=VersionStr("1.0"), - hooks={ - "install": models.Hook( - plugs=cast(UniqueStrList, ["home", "network"]), - command_chain=["test"], - environment={"test-variable-1": "test", "test-variable-2": "test"}, - passthrough={"somefield": ["some", "value"]}, - ) + component = models.Component.unmarshal( + { + "summary": "test", + "description": "test", + "type": "test", + "version": "1.0", + "hooks": { + "install": { + "plugs": ["home", "network"], + "command_chain": ["test"], + "environment": { + "test-variable-1": "test", + "test-variable-2": "test", + }, + "passthrough": {"somefield": ["some", "value"]}, + }, + }, }, ) diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index 136b4bd137..32e2e946d9 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -20,7 +20,7 @@ import pydantic import pytest from craft_application.errors import CraftValidationError -from craft_application.models import BuildInfo, UniqueStrList +from craft_application.models import BuildInfo, UniqueStrList, VersionStr from craft_providers.bases import BaseName import snapcraft.models @@ -158,12 +158,34 @@ def test_app_defaults(self, project_yaml_data): class TestProjectValidation: """Validate top-level project items.""" + def test_build_base_validation_reentrant(self, project_yaml_data): + """Validators should be reentrant. + + Changing a field causes all validators to re-run, so validators should not + fail when validating an existing model. + + This is a regression test for `base: core22` and `build-base: bare`, where + the validators receive "build-base" when creating the model and "build_base" + when re-validating. + """ + data = project_yaml_data( + base="bare", + # build-base has to be parsed for the validator to allow 'architectures' + build_base="core22", + architectures=["amd64"], + ) + + project = Project.unmarshal(data) + + # changing any value will re-run the validators, which should not raise an error + project.version = cast(VersionStr, "1.2.3") + @pytest.mark.parametrize("field", ["name", "confinement", "parts"]) def test_mandatory_fields(self, field, project_yaml_data): data = project_yaml_data() data.pop(field) - error = f"field {field!r} required in top-level configuration" - with pytest.raises(errors.ProjectValidationError, match=error): + error = f"{field}\n Field required" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -182,7 +204,7 @@ def test_mandatory_base(self, snap_type, requires_base, project_yaml_data): if requires_base: error = "Snap base must be declared when type is not" - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) else: project = Project.unmarshal(data) @@ -200,7 +222,7 @@ def test_adoptable_fields(self, field, project_yaml_data): data = project_yaml_data() data.pop(field) error = f"Required field '{field}' is not set and 'adopt-info' not used." - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize("field", MANDATORY_ADOPTABLE_FIELDS) @@ -244,12 +266,12 @@ def test_project_name_valid(self, name, project_yaml_data): ("123456", "snap names can only use"), ( "a2345678901234567890123456789012345678901", - "ensure this value has at most 40 characters", + "String should have at most 40 characters", ), ], ) def test_project_name_invalid(self, name, error, project_yaml_data): - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(name=name)) @pytest.mark.parametrize( @@ -269,18 +291,12 @@ def test_project_version_valid(self, version, project_yaml_data): assert project.version == version def test_project_version_invalid(self, project_yaml_data): - # We only test one invalid version as this model is inherited - # from Craft Application. - with pytest.raises(errors.ProjectValidationError) as raised: + """Test one invalid version as this is inherited from Craft Application.""" + error = "invalid version: Valid versions consist of" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(version="1=1")) - assert str(raised.value) == ( - "Bad snapcraft.yaml content:\n- string does not match regex " - '"^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$" ' - "(in field 'version')" - ) - @pytest.mark.parametrize( "snap_type", ["app", "gadget", "kernel", "snapd", "base", "_invalid"], @@ -294,8 +310,8 @@ def test_project_type(self, snap_type, project_yaml_data): project = Project.unmarshal(data) assert project.type == snap_type else: - error = ".*unexpected value; permitted: 'app', 'base', 'gadget', 'kernel', 'snapd'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be 'app', 'base', 'gadget', 'kernel' or 'snapd'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -308,8 +324,8 @@ def test_project_confinement(self, confinement, project_yaml_data): project = Project.unmarshal(data) assert project.confinement == confinement else: - error = ".*unexpected value; permitted: 'classic', 'devmode', 'strict'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be 'classic', 'devmode' or 'strict'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize("grade", ["devel", "stable", "_invalid"]) @@ -320,8 +336,8 @@ def test_project_grade(self, grade, project_yaml_data): project = Project.unmarshal(data) assert project.grade == grade else: - error = ".*unexpected value; permitted: 'stable', 'devel'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be 'stable' or 'devel'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize("grade", ["devel", "stable", "_invalid"]) @@ -332,7 +348,7 @@ def test_project_grade_assignment(self, grade, project_yaml_data): if grade != "_invalid": project.grade = grade else: - error = ".*unexpected value; permitted: 'stable', 'devel'" + error = "Input should be 'stable' or 'devel'" with pytest.raises(pydantic.ValidationError, match=error): project.grade = grade # type: ignore @@ -343,8 +359,8 @@ def test_project_summary_valid(self, project_yaml_data): def test_project_summary_invalid(self, project_yaml_data): summary = "x" * 79 - error = "ensure this value has at most 78 characters" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "String should have at most 78 characters" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(summary=summary)) @pytest.mark.parametrize( @@ -375,7 +391,7 @@ def test_project_epoch_valid(self, epoch, project_yaml_data): ) def test_project_epoch_invalid(self, epoch, project_yaml_data): error = "Epoch is a positive integer followed by an optional asterisk" - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(epoch=epoch)) def test_project_package_repository(self, project_yaml_data): @@ -399,8 +415,8 @@ def test_project_package_repository_missing_fields(self, project_yaml_data): "type": "apt", }, ] - error = r".*- field 'url' required .*\n- field 'key-id' required" - with pytest.raises(errors.ProjectValidationError, match=error): + error = r"url\n Field required.*\n.*\n.*key-id\n Field required" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(package_repositories=repos)) def test_project_package_repository_extra_fields(self, project_yaml_data): @@ -410,8 +426,8 @@ def test_project_package_repository_extra_fields(self, project_yaml_data): "extra": "something", }, ] - error = r".*- extra field 'extra' not permitted" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Extra inputs are not permitted" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(package_repositories=repos)) @pytest.mark.parametrize( @@ -435,8 +451,8 @@ def test_project_environment_valid(self, environment, project_yaml_data): ], ) def test_project_environment_invalid(self, environment, project_yaml_data): - error = ".*value is not a valid dict" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be a valid dictionary" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(environment=environment)) @pytest.mark.parametrize( @@ -460,8 +476,8 @@ def test_project_plugs_valid(self, plugs, project_yaml_data): ], ) def test_project_plugs_invalid(self, plugs, project_yaml_data): - error = ".*value is not a valid dict" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be a valid dictionary" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(plugs=plugs)) def test_project_content_plugs_valid(self, project_yaml_data): @@ -489,7 +505,7 @@ def test_project_content_plugs_missing_target(self, project_yaml_data): } error = ".*'content-interface' must have a 'target' parameter" - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(plugs=content_plug)) def test_project_get_content_snaps(self, project_yaml_data): @@ -520,7 +536,7 @@ def test_project_default_provider_with_channel(self, project_yaml_data): "test-provider/edge" ) - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(plugs=content_plug_data)) @pytest.mark.parametrize("decl_type", ["symlink", "bind", "bind-file", "type"]) @@ -532,11 +548,8 @@ def test_project_layout(self, decl_type, project_yaml_data): assert project.layout["foo"][decl_type] == "bar" def test_project_layout_invalid(self, project_yaml_data): - error = ( - "Bad snapcraft.yaml content:\n" - "- unexpected value; permitted: 'symlink', 'bind', 'bind-file', 'type'" - ) - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be 'symlink', 'bind', 'bind-file' or 'type'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(layout={"foo": {"invalid": "bar"}})) @pytest.mark.parametrize( @@ -577,12 +590,9 @@ def test_project_grade_not_defined(self, build_base, project_yaml_data): def test_project_build_base_devel_grade_stable_error(self, project_yaml_data): """Raise an error if build_base is `devel` and grade is `stable`.""" - error = ( - "Bad snapcraft.yaml content:\n" - "- grade must be 'devel' when build-base is 'devel'" - ) + error = "grade must be 'devel' when build-base is 'devel'" - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(build_base="devel", grade="stable")) @pytest.mark.parametrize( @@ -722,7 +732,7 @@ def test_project_hooks_command_chain_invalid(self, project_yaml_data): hook = {"configure": {"command-chain": ["_invalid!"]}} error = "'_invalid!' is not a valid command chain" - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(hooks=hook)) @pytest.mark.parametrize( @@ -736,15 +746,15 @@ def test_project_hooks_command_chain_invalid(self, project_yaml_data): def test_project_hooks_environment_invalid(self, environment, project_yaml_data): hooks = {"configure": {"environment": environment}} - error = ".*value is not a valid dict" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be a valid dictionary" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(hooks=hooks)) def test_project_hooks_plugs_empty(self, project_yaml_data): hook = {"configure": {"plugs": []}} - error = ".*'plugs' field cannot be empty" + error = "'plugs' field cannot be empty" - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(hooks=hook)) @@ -778,19 +788,17 @@ def test_platform_validation_strings(self, build_on, build_for, project_yaml_dat def test_platform_build_for_requires_build_on(self, project_yaml_data): """Raise an error if build-for is provided by build-on is not.""" - with pytest.raises(CraftValidationError) as raised: + error = r"build-on\n Field required" + with pytest.raises(pydantic.ValidationError, match=error): Platform(**{"build-for": [const.SnapArch.amd64]}) # type: ignore[reportArgumentType] - assert "'build_for' expects 'build_on' to also be provided" in str(raised.value) - def test_platforms_not_allowed_core22(self, project_yaml_data): - with pytest.raises(errors.ProjectValidationError) as raised: - Project.unmarshal(project_yaml_data(platforms={"amd64": None})) - - assert ( + error = ( "'platforms' keyword is not supported for base 'core22'. " - "Use 'architectures' keyword instead." in str(raised.value) + "Use 'architectures' keyword instead." ) + with pytest.raises(pydantic.ValidationError, match=error): + Project.unmarshal(project_yaml_data(platforms={"amd64": None})) @pytest.mark.parametrize( ("architectures", "expected"), @@ -888,8 +896,11 @@ def test_app_autostart(self, autostart, app_yaml_data): assert project.apps is not None assert project.apps["app1"].autostart == autostart else: - error = ".*'_invalid' is not a valid desktop file name" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + "apps.app1.autostart\n Value error, '_invalid' is not a valid " + "desktop file name" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_common_id(self, app_yaml_data): @@ -910,8 +921,10 @@ def test_app_bus_name(self, bus_name, app_yaml_data): assert project.apps is not None assert project.apps["app1"].bus_name == bus_name else: - error = ".*'_invalid!' is not a valid bus name" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + "apps.app1.bus_name\n Value error, '_invalid!' is not a valid bus name" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_completer(self, app_yaml_data): @@ -948,8 +961,8 @@ def test_app_start_timeout_valid(self, start_timeout, app_yaml_data): def test_app_start_timeout_invalid(self, start_timeout, app_yaml_data): data = app_yaml_data(start_timeout=start_timeout) - error = f".*'{start_timeout}' is not a valid time value" - with pytest.raises(errors.ProjectValidationError, match=error): + error = f"'{start_timeout}' is not a valid time value" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -968,8 +981,8 @@ def test_app_stop_timeout_valid(self, stop_timeout, app_yaml_data): def test_app_stop_timeout_invalid(self, stop_timeout, app_yaml_data): data = app_yaml_data(stop_timeout=stop_timeout) - error = f".*'{stop_timeout}' is not a valid time value" - with pytest.raises(errors.ProjectValidationError, match=error): + error = f"'{stop_timeout}' is not a valid time value" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -988,8 +1001,8 @@ def test_app_watchdog_timeout_valid(self, watchdog_timeout, app_yaml_data): def test_app_watchdog_timeout_invalid(self, watchdog_timeout, app_yaml_data): data = app_yaml_data(watchdog_timeout=watchdog_timeout) - error = f".*'{watchdog_timeout}' is not a valid time value" - with pytest.raises(errors.ProjectValidationError, match=error): + error = f"'{watchdog_timeout}' is not a valid time value" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_reload_command(self, app_yaml_data): @@ -1014,8 +1027,11 @@ def test_app_restart_delay_valid(self, restart_delay, app_yaml_data): def test_app_restart_delay_invalid(self, restart_delay, app_yaml_data): data = app_yaml_data(restart_delay=restart_delay) - error = f".*'{restart_delay}' is not a valid time value" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + f"apps.app1.restart_delay\n Value error, '{restart_delay}' is not a " + "valid time value" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_timer(self, app_yaml_data): @@ -1036,8 +1052,8 @@ def test_app_daemon(self, daemon, app_yaml_data): assert project.apps is not None assert project.apps["app1"].daemon == daemon else: - error = ".*unexpected value; permitted: 'simple', 'forking', 'oneshot'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.daemon\n Input should be 'simple', 'forking', 'oneshot', 'notify' or 'dbus'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1051,8 +1067,8 @@ def test_app_after(self, after, app_yaml_data): data = app_yaml_data(after=after) if after == "i am a string": - error = ".*value is not a valid list" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.after\n Input should be a valid list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) else: project = Project.unmarshal(data) @@ -1062,8 +1078,8 @@ def test_app_after(self, after, app_yaml_data): def test_app_duplicate_after(self, app_yaml_data): data = app_yaml_data(after=["duplicate", "duplicate"]) - error = ".*duplicate entries in 'after' not permitted" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.after\n Value error, duplicate values in list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1077,8 +1093,8 @@ def test_app_before(self, before, app_yaml_data): data = app_yaml_data(before=before) if before == "i am a string": - error = ".*value is not a valid list" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.before\n Input should be a valid list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) else: project = Project.unmarshal(data) @@ -1088,8 +1104,8 @@ def test_app_before(self, before, app_yaml_data): def test_app_duplicate_before(self, app_yaml_data): data = app_yaml_data(before=["duplicate", "duplicate"]) - error = ".*duplicate entries in 'before' not permitted" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.before\n Value error, duplicate values in list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1103,8 +1119,11 @@ def test_app_refresh_mode(self, refresh_mode, app_yaml_data): assert project.apps is not None assert project.apps["app1"].refresh_mode == refresh_mode else: - error = ".*unexpected value; permitted: 'endure', 'restart'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + "apps.app1.refresh_mode\n Input should be 'endure', 'restart' " + "or 'ignore-running'" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1131,8 +1150,12 @@ def test_app_stop_mode(self, stop_mode, app_yaml_data): assert project.apps is not None assert project.apps["app1"].stop_mode == stop_mode else: - error = ".*unexpected value; permitted: 'sigterm', 'sigterm-all', 'sighup'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + "apps.app1.stop_mode\n Input should be 'sigterm', 'sigterm-all', " + "'sighup', 'sighup-all', 'sigusr1', 'sigusr1-all', " + "'sigusr2', 'sigusr2-all', 'sigint' or 'sigint-all'" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1156,8 +1179,12 @@ def test_app_restart_condition(self, restart_condition, app_yaml_data): assert project.apps is not None assert project.apps["app1"].restart_condition == restart_condition else: - error = ".*unexpected value; permitted: 'on-success', 'on-failure', 'on-abnormal'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + "apps.app1.restart_condition\n Input should be 'on-success', " + "'on-failure', 'on-abnormal', 'on-abort', 'on-watchdog', " + "'always' or 'never'" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize("install_mode", ["enable", "disable", "_invalid"]) @@ -1169,8 +1196,8 @@ def test_app_install_mode(self, install_mode, app_yaml_data): assert project.apps is not None assert project.apps["app1"].install_mode == install_mode else: - error = ".*unexpected value; permitted: 'enable', 'disable'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.install_mode\n Input should be 'enable' or 'disable'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_valid_aliases(self, app_yaml_data): @@ -1191,19 +1218,21 @@ def test_app_invalid_aliases(self, aliases, app_yaml_data): data = app_yaml_data(aliases=aliases) if isinstance(aliases, list): - error = f".*'{aliases[0]}' is not a valid alias" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + f"apps.app1.aliases\n Value error, '{aliases[0]}' is not a valid alias" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) else: - error = ".*value is not a valid list" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.aliases\n Input should be a valid list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_duplicate_aliases(self, app_yaml_data): data = app_yaml_data(aliases=["duplicate", "duplicate"]) - error = ".*duplicate entries in 'aliases' not permitted" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.aliases\n Value error, duplicate values in list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1231,8 +1260,8 @@ def test_app_environment_valid(self, environment, app_yaml_data): def test_app_environment_invalid(self, environment, app_yaml_data): data = app_yaml_data(environment=environment) - error = ".*value is not a valid dict" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.environment\n Input should be a valid dictionary" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1248,12 +1277,15 @@ def test_app_command_chain(self, command_chain, app_yaml_data): data = app_yaml_data(command_chain=command_chain) if command_chain == "i am a string": - error = ".*value is not a valid list" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.command_chain\n Input should be a valid list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) elif command_chain == ["_invalid!"]: - error = f".*'{command_chain[0]}' is not a valid command chain" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + f"apps.app1.command_chain\n Value error, '{command_chain[0]}' is not a " + "valid command chain" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) else: project = Project.unmarshal(data) @@ -1277,8 +1309,11 @@ def test_app_sockets_invalid_int_listen_stream( ): data = socket_yaml_data(listen_stream=listen_stream) - error = f".*{listen_stream} is not an integer between 1 and 65535" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + f"apps.app1.sockets.socket1.listen_stream\n Value error, {listen_stream} is not an " + "integer between 1 and 65535" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize("listen_stream", ["@foo"]) @@ -1287,15 +1322,18 @@ def test_app_sockets_invalid_socket_listen_stream( ): data = socket_yaml_data(listen_stream=listen_stream) - error = f".*{listen_stream!r} is not a valid socket path.*" - with pytest.raises(errors.ProjectValidationError, match=error): + error = ( + f"apps.app1.sockets.socket1.listen_stream\n Value error, {listen_stream!r} is not a " + "valid socket path" + ) + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) def test_app_sockets_missing_listen_stream(self, socket_yaml_data): data = socket_yaml_data() - error = ".*field 'listen-stream' required" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.sockets.socket1.listen-stream\n Field required" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize("socket_mode", [1, "_invalid"]) @@ -1308,8 +1346,8 @@ def test_app_sockets_valid_socket_mode(self, socket_mode, socket_yaml_data): assert project.apps["app1"].sockets is not None assert project.apps["app1"].sockets["socket1"].socket_mode == socket_mode else: - error = ".*value is not a valid integer" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "apps.app1.sockets.socket1.socket_mode\n Input should be a valid integer" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) @pytest.mark.parametrize( @@ -1337,8 +1375,8 @@ def test_project_system_usernames_valid(self, system_username, project_yaml_data ], ) def test_project_system_usernames_invalid(self, system_username, project_yaml_data): - error = "- value is not a valid dict" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be a valid dictionary" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(system_usernames=system_username)) def test_project_provenance(self, project_yaml_data): @@ -1350,7 +1388,7 @@ def test_project_provenance(self, project_yaml_data): def test_project_provenance_invalid(self, provenance, project_yaml_data): """Verify invalid provenance values raises an error.""" error = "provenance must consist of alphanumeric characters and/or hyphens." - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(provenance=provenance)) @@ -1462,7 +1500,7 @@ def test_grammar_try(self, project_yaml_data): } ) - error = r".*- 'try' was removed from grammar, use 'on ' instead" + error = "'try' was removed from grammar, use 'on ' instead" with pytest.raises(errors.ProjectValidationError, match=error): GrammarAwareProject.validate_grammar(data) @@ -1478,7 +1516,7 @@ def test_grammar_type_error(self, project_yaml_data): } ) - error = r".*- value must be a string: \[25\]" + error = r"value must be a str: \[25\]" with pytest.raises(errors.ProjectValidationError, match=error): GrammarAwareProject.validate_grammar(data) @@ -1494,7 +1532,7 @@ def test_grammar_syntax_error(self, project_yaml_data): } ) - error = r".*- syntax error in 'on' selector" + error = "syntax error in 'on' selector" with pytest.raises(errors.ProjectValidationError, match=error): GrammarAwareProject.validate_grammar(data) @@ -1634,11 +1672,10 @@ def test_architecture_invalid_string(self, project_yaml_data): """A single string is not valid.""" data = project_yaml_data(architectures="amd64") - with pytest.raises(errors.ProjectValidationError) as error: + error = "Input should be a valid list" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "value is not a valid list" in str(error.value) - def test_architecture_multiple_build_on(self, project_yaml_data): """Multiple architectures can be defined in a single `build-on`.""" data = project_yaml_data( @@ -1679,20 +1716,18 @@ def test_architecture_unknown_property(self, project_yaml_data): ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = r"bad-property\n Extra inputs are not permitted" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "extra field 'bad-property' not permitted" in str(error.value) - def test_architecture_missing_build_on(self, project_yaml_data): """`build-on` is a required field.""" data = project_yaml_data(architectures=[{"build-for": ["amd64"]}]) - with pytest.raises(errors.ProjectValidationError) as error: + error = r"build-on\n Field required" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "field 'build-on' required" in str(error.value) - def test_architecture_build_on_all_and_others(self, project_yaml_data): """ `all` cannot be used in the `build-on` field if another @@ -1702,35 +1737,31 @@ def test_architecture_build_on_all_and_others(self, project_yaml_data): architectures=[{"build-on": ["all", "amd64"], "build-for": ["amd64"]}] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "'all' cannot be used for 'build-on'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "'all' cannot be used for 'build-on'" in str(error.value) - def test_architecture_invalid_multiple_build_for(self, project_yaml_data): """Only a single item can be defined for `build-for`.""" data = project_yaml_data( architectures=[{"build-on": ["amd64"], "build-for": ["all", "amd64"]}] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "only one architecture can be defined for 'build-for'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "only one architecture can be defined for 'build-for'" in str( - error.value - ) - def test_architecture_invalid_multiple_implicit_build_for(self, project_yaml_data): - """Only a single item can be defined for `build-for`.""" + """Only a single item can be defined for `build-for`. + + This is true even when 'build-for' is implicitly inferred from 'build-on'. + """ data = project_yaml_data(architectures=[{"build-on": ["amd64", "armhf"]}]) - with pytest.raises(errors.ProjectValidationError) as error: + error = "only one architecture can be defined for 'build-for'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "only one architecture can be defined for 'build-for'" in str( - error.value - ) - def test_architecture_invalid_build_on_all_build_for_all(self, project_yaml_data): """`build-on: all` and `build-for: all` is invalid.""" data = project_yaml_data( @@ -1739,11 +1770,10 @@ def test_architecture_invalid_build_on_all_build_for_all(self, project_yaml_data ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "'all' cannot be used for 'build-on'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "'all' cannot be used for 'build-on'" in str(error.value) - def test_architecture_invalid_build_on_all_implicit(self, project_yaml_data): """`build-on: all` is invalid, even when build-for is missing.""" data = project_yaml_data( @@ -1752,11 +1782,10 @@ def test_architecture_invalid_build_on_all_implicit(self, project_yaml_data): ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "'all' cannot be used for 'build-on'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "'all' cannot be used for 'build-on'" in str(error.value) - def test_architecture_invalid_build_on_all_build_for_architecture( self, project_yaml_data ): @@ -1767,11 +1796,10 @@ def test_architecture_invalid_build_on_all_build_for_architecture( ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "'all' cannot be used for 'build-on'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "'all' cannot be used for 'build-on'" in str(error.value) - def test_architecture_build_on_architecture_build_for_all(self, project_yaml_data): """`build-on: arch` and `build-for: all` is valid.""" data = project_yaml_data( @@ -1795,11 +1823,10 @@ def test_architecture_build_on_all_and_other_architectures(self, project_yaml_da ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "'all' cannot be used for 'build-on'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "'all' cannot be used for 'build-on'" in str(error.value) - def test_architecture_build_for_all_and_other_architectures( self, project_yaml_data ): @@ -1811,14 +1838,13 @@ def test_architecture_build_for_all_and_other_architectures( ] ) - with pytest.raises(errors.ProjectValidationError) as error: - Project.unmarshal(data) - - assert ( + error = ( "one of the items has 'all' in 'build-for', but there are" " 2 items: upon release they will conflict." - "'all' should only be used if there is a single item" in str(error.value) + "'all' should only be used if there is a single item" ) + with pytest.raises(pydantic.ValidationError, match=error): + Project.unmarshal(data) def test_architecture_multiple_build_on_all(self, project_yaml_data): """`all` cannot be used for multiple `build-on` fields.""" @@ -1829,11 +1855,10 @@ def test_architecture_multiple_build_on_all(self, project_yaml_data): ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "'all' cannot be used for 'build-on'" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "'all' cannot be used for 'build-on'" in str(error.value) - def test_architecture_multiple_build_for_all(self, project_yaml_data): """`all` cannot be used for multiple `build-for` fields.""" data = project_yaml_data( @@ -1843,14 +1868,13 @@ def test_architecture_multiple_build_for_all(self, project_yaml_data): ] ) - with pytest.raises(errors.ProjectValidationError) as error: - Project.unmarshal(data) - - assert ( + error = ( "one of the items has 'all' in 'build-for', but there are" " 2 items: upon release they will conflict." - "'all' should only be used if there is a single item" in str(error.value) + "'all' should only be used if there is a single item" ) + with pytest.raises(pydantic.ValidationError, match=error): + Project.unmarshal(data) def test_architecture_multiple_build_on_same_architecture(self, project_yaml_data): """The same architecture can be defined in multiple `build-on` fields.""" @@ -1880,13 +1904,10 @@ def test_architecture_multiple_build_for_same_architecture(self, project_yaml_da ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "multiple items will build snaps that claim to run on amd64" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "multiple items will build snaps that claim to run on amd64" in str( - error.value - ) - def test_architecture_multiple_build_for_same_architecture_implicit( self, project_yaml_data ): @@ -1901,13 +1922,10 @@ def test_architecture_multiple_build_for_same_architecture_implicit( ] ) - with pytest.raises(errors.ProjectValidationError) as error: + error = "multiple items will build snaps that claim to run on amd64" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "multiple items will build snaps that claim to run on amd64" in str( - error.value - ) - @pytest.mark.parametrize( "architectures", [ @@ -1921,11 +1939,10 @@ def test_architecture_unsupported(self, architectures, project_yaml_data): """Raise an error for unsupported architectures.""" data = project_yaml_data(architectures=[architectures]) - with pytest.raises(errors.ProjectValidationError) as error: + error = "Architecture 'unknown' is not supported" + with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(data) - assert "Architecture 'unknown' is not supported." in str(error.value) - def test_project_get_build_on(self, project_yaml_data): """Test `get_build_on()` returns the build-on string.""" data = project_yaml_data( @@ -1974,13 +1991,13 @@ def test_project_get_build_for_arch_triplet_all(self, project_yaml_data): def test_architectures_not_allowed(self, project_yaml_data): """'architectures' keyword is not allowed if base is not core22.""" - with pytest.raises(errors.ProjectValidationError) as raised: - Project.unmarshal(project_yaml_data(**CORE24_DATA, architectures=["amd64"])) - - assert ( + error = ( "'architectures' keyword is not supported for base 'core24'. " "Use 'platforms' keyword instead." - ) in str(raised.value) + ) + + with pytest.raises(pydantic.ValidationError, match=error): + Project.unmarshal(project_yaml_data(**CORE24_DATA, architectures=["amd64"])) class TestApplyRootPackages: @@ -2094,7 +2111,7 @@ def test_root_packages_transform_no_affect(self, project_yaml_data): ) def test_build_planner_get_build_plan(platforms, expected_build_infos): """Test `get_build_plan()` function with different platforms.""" - planner = snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + planner = snapcraft.models.project.SnapcraftBuildPlanner.model_validate( {"name": "test-snap", "base": "core24", "platforms": platforms} ) @@ -2225,7 +2242,7 @@ def test_build_planner_get_build_plan(platforms, expected_build_infos): ) def test_build_planner_get_build_plan_core22(architectures, expected_build_infos): """Test `get_build_plan()` function with different platforms.""" - planner = snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + planner = snapcraft.models.project.SnapcraftBuildPlanner.model_validate( {"name": "test-snap", "base": "core22", "architectures": architectures} ) @@ -2243,10 +2260,10 @@ def test_build_planner_get_build_plan_core22(architectures, expected_build_infos "platform entry label must correspond to a valid architecture if 'build-for' is not provided", id="no-build-for", ), - # this will always be invalid + # this is invalid because the platform 'all' will be used for 'build-on' pytest.param( {"all": None}, - "value is not a valid enumeration member", + "'all' cannot be used for 'build-on'", id="no-build-for-no-build-on", ), ], @@ -2258,11 +2275,9 @@ def test_build_planner_all_as_platform_invalid(platforms, message): "base": "core24", "platforms": platforms, } - with pytest.raises(pydantic.ValidationError) as raised: + with pytest.raises(pydantic.ValidationError, match=message): snapcraft.models.project.SnapcraftBuildPlanner(**build_plan_data) - assert message in str(raised.value) - def test_build_planner_all_with_other_builds(): """'build-for: all' cannot be combined with other builds.""" @@ -2319,7 +2334,7 @@ def test_build_planner_all_with_other_builds_core22(): def test_get_build_plan_devel(): """Test that "devel" build-bases are correctly reflected on the build plan""" - planner = snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + planner = snapcraft.models.project.SnapcraftBuildPlanner.model_validate( { "name": "test-snap", "base": "core24", @@ -2341,7 +2356,7 @@ def test_get_build_plan_devel(): def test_platform_default(): """Default value for platforms is the host architecture.""" - planner = snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + planner = snapcraft.models.project.SnapcraftBuildPlanner.model_validate( {"name": "test-snap", "base": "core24"} ) @@ -2362,7 +2377,7 @@ def test_build_planner_get_build_plan_base(mocker): mock_get_effective_base = mocker.patch( "snapcraft.models.project.get_effective_base", return_value="core24" ) - planner = snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + planner = snapcraft.models.project.SnapcraftBuildPlanner.model_validate( { "name": "test-snap", "base": "test-base", @@ -2393,8 +2408,9 @@ def test_build_planner_get_build_plan_base(mocker): def test_project_platform_error_has_context(): """Platform validation errors include which platform entry is invalid.""" - with pytest.raises(CraftValidationError) as raised: - snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + error = r"build-on\n Field required" + with pytest.raises(pydantic.ValidationError, match=error): + snapcraft.models.project.SnapcraftBuildPlanner.model_validate( { "name": "test-snap", "base": "test-base", @@ -2404,13 +2420,11 @@ def test_project_platform_error_has_context(): } ) - assert "'build_for' expects 'build_on' to also be provided." in str(raised.value) - def test_project_platform_mismatch(): """Raise an error if platform name and build-for are valid but different archs.""" with pytest.raises(pydantic.ValidationError) as raised: - snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + snapcraft.models.project.SnapcraftBuildPlanner.model_validate( { "name": "test-snap", "base": "test-base", @@ -2430,7 +2444,7 @@ def test_project_platform_mismatch(): def test_project_platform_unknown_name(): """Raise an error if an empty platform is not a valid architecture.""" with pytest.raises(CraftValidationError) as raised: - snapcraft.models.project.SnapcraftBuildPlanner.parse_obj( + snapcraft.models.project.SnapcraftBuildPlanner.model_validate( { "name": "test-snap", "base": "test-base", @@ -2452,7 +2466,7 @@ class TestComponents: @pytest.fixture def stub_component_data(self): - data: dict[str, Any] = { + data = { "type": "test", "summary": "test summary", "description": "test description", @@ -2462,9 +2476,13 @@ def stub_component_data(self): return data def test_components_valid(self, project, project_yaml_data, stub_component_data): - components = {"foo": stub_component_data, "bar": stub_component_data} + component_data = {"foo": stub_component_data, "bar": stub_component_data} + components = { + "foo": snapcraft.models.Component.unmarshal(stub_component_data), + "bar": snapcraft.models.Component.unmarshal(stub_component_data), + } - test_project = project.unmarshal(project_yaml_data(components=components)) + test_project = project.unmarshal(project_yaml_data(components=component_data)) assert test_project.components == components @@ -2484,9 +2502,9 @@ def test_component_type_invalid( ): component = {"foo": stub_component_data} component["foo"]["type"] = "invalid" - error = ".*unexpected value; permitted: 'test'" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "Input should be 'test'" + with pytest.raises(pydantic.ValidationError, match=error): project.unmarshal(project_yaml_data(components=component)) @pytest.mark.parametrize( @@ -2529,7 +2547,7 @@ def test_component_name_valid( "name-has--two-hyphens", "component names cannot have two hyphens in a row", ), - ("x" * 41, "ensure this value has at most 40 characters"), + ("x" * 41, "String should have at most 40 characters"), ], ) def test_component_name_invalid( @@ -2537,7 +2555,7 @@ def test_component_name_invalid( ): component = {name: stub_component_data} - with pytest.raises(errors.ProjectValidationError, match=error): + with pytest.raises(pydantic.ValidationError, match=error): project.unmarshal(project_yaml_data(components=component)) def test_component_summary_valid( @@ -2558,8 +2576,8 @@ def test_component_summary_invalid( component = {"foo": stub_component_data} component["foo"]["summary"] = "x" * 79 - error = "ensure this value has at most 78 characters" - with pytest.raises(errors.ProjectValidationError, match=error): + error = "String should have at most 78 characters" + with pytest.raises(pydantic.ValidationError, match=error): project.unmarshal(project_yaml_data(components=component)) @pytest.mark.parametrize( @@ -2590,62 +2608,63 @@ def test_component_version_valid( [ pytest.param( "1_0", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="'_' in version", ), pytest.param( "1=1", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="'=' in version", ), pytest.param( ".1", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot start with '.'", ), pytest.param( ":1", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot start with ':'", ), pytest.param( "+1", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot start with '+'", ), pytest.param( "~1", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot start with '~'", ), pytest.param( "-1", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot start with '-'", ), pytest.param( "1.", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot end with '.'", ), pytest.param( "1:", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot end with ':'", ), pytest.param( "1-", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="cannot end with '-'", ), pytest.param( "x" * 33, - "ensure this value has at most 32 characters", + # TODO: can we fix this wording for strings? + "Value should have at most 32 items after validation, not 33", id="too large", ), pytest.param( "", - 'string does not match regex "^[a-zA-Z0-9](?:[a-zA-Z0-9:.+~-]*[a-zA-Z0-9+~])?$"', + "invalid version: Valid versions consist of upper- and lower-case", id="empty string", ), ], @@ -2656,12 +2675,9 @@ def test_component_version_invalid( component = {"foo": stub_component_data} component["foo"]["version"] = version - with pytest.raises(errors.ProjectValidationError) as raised: + with pytest.raises(pydantic.ValidationError, match=error): project.unmarshal(project_yaml_data(components=component)) - assert error in str(raised.value) - assert str(raised.value).endswith("(in field 'components.foo.version')") - def test_get_component_names(self, project, project_yaml_data, stub_component_data): components = {"foo": stub_component_data, "bar-baz": stub_component_data} test_project = project.unmarshal(project_yaml_data(components=components)) diff --git a/tests/unit/parts/test_setup_assets_components.py b/tests/unit/parts/test_setup_assets_components.py index 3024d34efc..720557d3db 100644 --- a/tests/unit/parts/test_setup_assets_components.py +++ b/tests/unit/parts/test_setup_assets_components.py @@ -27,14 +27,12 @@ @pytest.fixture def extra_project_params(extra_project_params): - from craft_application.models import SummaryStr, VersionStr - extra_project_params["components"] = { "firstcomponent": { "type": "test", - "summary": SummaryStr("first component"), + "summary": "first component", "description": "lorem ipsum", - "version": VersionStr("1.0"), + "version": "1.0", "hooks": { "configure": { "environment": { diff --git a/tests/unit/parts/test_yaml_utils.py b/tests/unit/parts/test_yaml_utils.py index 29d0a2b707..fea87d3bce 100644 --- a/tests/unit/parts/test_yaml_utils.py +++ b/tests/unit/parts/test_yaml_utils.py @@ -21,7 +21,6 @@ import pytest from snapcraft import errors -from snapcraft.models import Architecture from snapcraft.parts import yaml_utils @@ -196,7 +195,7 @@ def test_apply_yaml_defines_root_packages(minimal_yaml_data, key, value): "version": "1.0", "summary": "summary", "description": "description", - "architectures": [Architecture(build_on="amd64", build_for="amd64")], + "architectures": [{"build-on": "amd64", "build-for": "amd64"}], "parts": {"nil": {}, "snapcraft/core": {"plugin": "nil", key: ["foo"]}}, } diff --git a/tests/unit/services/test_lifecycle_components.py b/tests/unit/services/test_lifecycle_components.py index 8df0327ae1..314373a9d0 100644 --- a/tests/unit/services/test_lifecycle_components.py +++ b/tests/unit/services/test_lifecycle_components.py @@ -22,20 +22,19 @@ @pytest.fixture def extra_project_params(extra_project_params): - from craft_application.models import SummaryStr, VersionStr extra_project_params["components"] = { "firstcomponent": { "type": "test", - "summary": SummaryStr("first component"), + "summary": "first component", "description": "lorem ipsum", - "version": VersionStr("1.0"), + "version": "1.0", }, "secondcomponent": { "type": "test", - "summary": SummaryStr("second component"), + "summary": "second component", "description": "lorem ipsum", - "version": VersionStr("1.0"), + "version": "1.0", }, } diff --git a/tests/unit/services/test_package.py b/tests/unit/services/test_package.py index 5d5303f621..57949bd4f8 100644 --- a/tests/unit/services/test_package.py +++ b/tests/unit/services/test_package.py @@ -22,7 +22,6 @@ import pytest import yaml -from craft_application.models import SummaryStr, VersionStr from snapcraft import __version__, linters, meta, models, pack, services from snapcraft.application import APP_METADATA @@ -85,33 +84,35 @@ def test_metadata( build_plan=default_build_plan, ) - assert package_service.metadata == meta.SnapMetadata( - name="default", - title=None, - version=VersionStr("1.0"), - summary=SummaryStr("default project"), - description="default project", - license="MIT", - type=None, - architectures=["amd64"], - base="core24", - assumes=None, - epoch=None, - apps=None, - confinement="devmode", - grade="devel", - 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", - }, - plugs=None, - slots=None, - hooks=None, - layout=None, - system_usernames=None, - provenance=None, - links=None, - components=None, + assert package_service.metadata == meta.SnapMetadata.unmarshal( + { + "name": "default", + "title": None, + "version": "1.0", + "summary": "default project", + "description": "default project", + "license": "MIT", + "type": None, + "architectures": ["amd64"], + "base": "core24", + "assumes": None, + "epoch": None, + "apps": None, + "confinement": "devmode", + "grade": "devel", + "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", + }, + "plugs": None, + "slots": None, + "hooks": None, + "layout": None, + "system_usernames": None, + "provenance": None, + "links": None, + "components": None, + } ) diff --git a/tests/unit/services/test_package_components.py b/tests/unit/services/test_package_components.py index 90b0daee89..a4b93b3120 100644 --- a/tests/unit/services/test_package_components.py +++ b/tests/unit/services/test_package_components.py @@ -27,14 +27,12 @@ @pytest.fixture def extra_project_params(extra_project_params): - from craft_application.models import SummaryStr, VersionStr - extra_project_params["components"] = { "firstcomponent": { "type": "test", - "summary": SummaryStr("first component"), + "summary": "first component", "description": "lorem ipsum", - "version": VersionStr("1.0"), + "version": "1.0", "hooks": { "install": { "command-chain": ["test-command-chain"], @@ -50,9 +48,9 @@ def extra_project_params(extra_project_params): }, "secondcomponent": { "type": "test", - "summary": SummaryStr("second component"), + "summary": "second component", "description": "lorem ipsum", - "version": VersionStr("1.0"), + "version": "1.0", }, } diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 3a640c6a5a..b245a427d3 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -148,15 +148,15 @@ def test_application_expand_extensions(emitter, monkeypatch, extension_source, n base: core24 license: MIT parts: - fake-extension/fake-part: - plugin: nil + fake-extension/fake-part: + plugin: nil confinement: strict grade: devel apps: - app1: - command: app1 - plugs: - - fake-plug + app1: + command: app1 + plugs: + - fake-plug """ ) ) diff --git a/tests/unit/test_providers.py b/tests/unit/test_providers.py index 5619100021..925144855b 100644 --- a/tests/unit/test_providers.py +++ b/tests/unit/test_providers.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -548,25 +548,13 @@ def test_get_provider_snap_config(mocker, provider, expected_provider): # set snap config mocker.patch( "snapcraft.providers.get_snap_config", - return_value=SnapConfig(provider=provider), + return_value=SnapConfig.unmarshal({"provider": provider}), ) actual_provider = providers.get_provider() assert isinstance(actual_provider, expected_provider) -def test_get_provider_snap_config_invalid(mocker): - """Verify an invalid environmental variable raises an error.""" - snap_config = SnapConfig() - snap_config.provider = "invalid-provider" # type: ignore - mocker.patch("snapcraft.providers.get_snap_config", return_value=snap_config) - - with pytest.raises(ValueError) as raised: - providers.get_provider() - - assert str(raised.value) == "unsupported provider specified: 'invalid-provider'" - - def test_get_provider_snap_config_priority(mocker): """Verify provider defined by SNAPCRAFT_BUILD_ENVIRONMENT has the correct priority. diff --git a/tests/unit/test_snap_config.py b/tests/unit/test_snap_config.py index 4dd9c54970..7cc56b770a 100644 --- a/tests/unit/test_snap_config.py +++ b/tests/unit/test_snap_config.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2022 Canonical Ltd. +# Copyright 2022,2024 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 @@ -47,36 +47,24 @@ def test_unmarshal(): def test_unmarshal_not_a_dictionary(): """Verify unmarshalling with data that is not a dictionary raises an error.""" - with pytest.raises(TypeError) as raised: + error = "Project data is not a dictionary" + with pytest.raises(TypeError, match=error): SnapConfig.unmarshal("provider=lxd") # type: ignore - assert str(raised.value) == "snap config data is not a dictionary" - def test_unmarshal_invalid_provider_error(): """Verify unmarshalling with an invalid provider raises an error.""" - with pytest.raises(ValueError) as raised: + error = "provider\n Input should be 'lxd' or 'multipass'" + with pytest.raises(ValueError, match=error): SnapConfig.unmarshal({"provider": "invalid-value"}) - assert str(raised.value) == ( - "error parsing snap config: 1 validation error for SnapConfig\n" - "provider\n" - " unexpected value; permitted: 'lxd', 'multipass' " - "(type=value_error.const; given=invalid-value; permitted=('lxd', 'multipass'))" - ) - def test_unmarshal_extra_data_error(): """Verify unmarshalling with extra data raises an error.""" - with pytest.raises(ValueError) as raised: + error = "test\n Extra inputs are not permitted" + with pytest.raises(ValueError, match=error): SnapConfig.unmarshal({"provider": "lxd", "test": "test"}) - assert str(raised.value) == ( - "error parsing snap config: 1 validation error for SnapConfig\n" - "test\n" - " extra fields not permitted (type=value_error.extra)" - ) - @pytest.mark.parametrize("provider", ["lxd", "multipass"]) def test_get_snap_config(mock_config, mock_is_running_from_snap, provider): diff --git a/tests/unit/test_ua_manager.py b/tests/unit/test_ua_manager.py index 29ed670de6..b110193f63 100644 --- a/tests/unit/test_ua_manager.py +++ b/tests/unit/test_ua_manager.py @@ -1,6 +1,6 @@ # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- # -# Copyright 2021-2022 Canonical Ltd. +# Copyright 2021-2022,2024 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 @@ -129,6 +129,11 @@ def test_ua_manager_enable_services(fake_process, mock_status_data): - Services not in the status data are enabled. - Enabled services are not re-enabled. """ + services = { + "disabled-service-1", + "disabled-service-2", + "service-not-in-status-data", + } fake_process.register_subprocess( ["ua", "status", "--all", "--format", "json"], stdout=mock_status_data ) @@ -140,23 +145,14 @@ def test_ua_manager_enable_services(fake_process, mock_status_data): [ "ua", "enable", - "disabled-service-1", - "disabled-service-2", - "service-not-in-status-data", + *sorted(services), "--beta", "--assume-yes", ] ) fake_process.register_subprocess(["ua", "detach", "--assume-yes"]) - with ua_manager.ua_manager( - "test-ua-token", - services=[ - "disabled-service-1", - "disabled-service-2", - "service-not-in-status-data", - ], - ): + with ua_manager.ua_manager("test-ua-token", services=services): pass assert list(fake_process.calls) == [ @@ -166,9 +162,7 @@ def test_ua_manager_enable_services(fake_process, mock_status_data): [ "ua", "enable", - "disabled-service-1", - "disabled-service-2", - "service-not-in-status-data", + *sorted(services), "--beta", "--assume-yes", ], @@ -178,6 +172,10 @@ def test_ua_manager_enable_services(fake_process, mock_status_data): def test_ua_manager_enable_services_no_services(fake_process): """Assume services are disabled if the services node is not in the status data.""" + services = { + "disabled-service-1", + "disabled-service-2", + } fake_process.register_subprocess( ["ua", "status", "--all", "--format", "json"], stdout='{"attached": false, "_doc": "Content..."}', @@ -191,17 +189,14 @@ def test_ua_manager_enable_services_no_services(fake_process): [ "ua", "enable", - "disabled-service-1", - "disabled-service-2", + *sorted(services), "--beta", "--assume-yes", ] ) fake_process.register_subprocess(["ua", "detach", "--assume-yes"]) - with ua_manager.ua_manager( - "test-ua-token", services=["disabled-service-1", "disabled-service-2"] - ): + with ua_manager.ua_manager("test-ua-token", services=services): pass assert list(fake_process.calls) == [ @@ -211,8 +206,7 @@ def test_ua_manager_enable_services_no_services(fake_process): [ "ua", "enable", - "disabled-service-1", - "disabled-service-2", + *sorted(services), "--beta", "--assume-yes", ], @@ -222,6 +216,7 @@ def test_ua_manager_enable_services_no_services(fake_process): def test_ua_manager_enable_services_error(fake_process): """Raise a UAEnableServicesError when the `ua enable` command fails.""" + services = {"svc1", "svc2"} fake_process.register_subprocess( ["ua", "status", "--all", "--format", "json"], stdout='{"attached": false, "_doc": "Content..."}', @@ -232,19 +227,19 @@ def test_ua_manager_enable_services_error(fake_process): stdout='{"attached": false, "_doc": "Content..."}', ) fake_process.register_subprocess( - ["ua", "enable", "svc1", "svc2", "--beta", "--assume-yes"], returncode=1 + ["ua", "enable", *sorted(services), "--beta", "--assume-yes"], returncode=1 ) fake_process.register_subprocess(["ua", "detach", "--assume-yes"]) with pytest.raises(ua_manager.UAEnableServicesError) as raised: - with ua_manager.ua_manager("test-ua-token", services=["svc1", "svc2"]): + with ua_manager.ua_manager("test-ua-token", services={"svc1", "svc2"}): pass assert list(fake_process.calls) == [ ["ua", "status", "--all", "--format", "json"], ["ua", "attach", "test-ua-token"], ["ua", "status", "--all", "--format", "json"], - ["ua", "enable", "svc1", "svc2", "--beta", "--assume-yes"], + ["ua", "enable", *sorted(services), "--beta", "--assume-yes"], ["ua", "detach", "--assume-yes"], ] assert str(raised.value) == "Error enabling UA services." From 655e543ac853b4de74d8986325d38eba9f482a9e Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 15 Aug 2024 10:44:44 -0500 Subject: [PATCH 041/151] ci: update renovate config and workflow (#4962) Sync renovate config and workflow to match starbase. Signed-off-by: Callahan Kovacs --- .github/renovate.json5 | 112 +++++++++++++++++++------- .github/workflows/check-renovate.yaml | 40 +++++++++ 2 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/check-renovate.yaml diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8eaf7a91ee..8342105f37 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,16 +1,38 @@ { // Configuration file for RenovateBot: https://docs.renovatebot.com/configuration-options - extends: ["config:base"], + extends: ["config:recommended", ":semanticCommitTypeAll(build)"], labels: ["dependencies"], // For convenient searching in GitHub + baseBranches: ["$default", "/^hotfix\\/.*/"], pip_requirements: { - fileMatch: ["^tox.ini$", "(^|/)requirements([\\w-]*)\\.txt$"] + fileMatch: ["^tox.ini$", "(^|/)requirements([\\w-]*)\\.txt$", "^.pre-commit-config.yaml$"] }, packageRules: [ + { + // Internal package minor patch updates get top priority, with auto-merging + groupName: "internal package minor releases", + matchPackagePatterns: ["^craft-.*"], + matchUpdateTypes: ["minor", "patch", "pin", "digest"], + prPriority: 10, + automerge: true, + minimumReleaseAge: "0 seconds", + schedule: ["at any time"], + matchBaseBranches: ["$default"], // Only do minor releases on main + }, + { + // Same as above, but for hotfix branches, only for patch, and without auto-merging. + groupName: "internal package patch releases (hotfix)", + matchPackagePatterns: ["^craft-.*"], + matchUpdateTypes: ["patch", "pin", "digest"], + prPriority: 10, + minimumReleaseAge: "0 seconds", + schedule: ["at any time"], + matchBaseBranches: ["/^hotfix\\/.*/"], // All hotfix branches + }, { // Automerge patches, pin changes and digest changes. // Also groups these changes together. groupName: "bugfixes", - excludePackagePrefixes: ["dev", "lint", "types"], + excludeDepPatterns: ["lint/.*", "types/.*"], matchUpdateTypes: ["patch", "pin", "digest"], prPriority: 3, // Patches should go first! automerge: true @@ -18,28 +40,55 @@ { // Update all internal packages in one higher-priority PR groupName: "internal packages", - matchPackagePrefixes: ["craft-", "snap-"], - matchLanguages: ["python"], - prPriority: 2 + matchDepPatterns: ["craft-.*", "snap-.*"], + matchCategories: ["python"], + prPriority: 2, + matchBaseBranches: ["$default"], // Not for hotfix branches }, { - // GitHub Actions are higher priority to update than most dependencies. + // GitHub Actions are higher priority to update than most dependencies since they don't tend to break things. groupName: "GitHub Actions", matchManagers: ["github-actions"], prPriority: 1, automerge: true, }, // Everything not in one of these rules gets priority 0 and falls here. + { + //Do all pydantic-related updates together + groupName: "pydantic etc.", + matchPackagePatterns: ["^pydantic"], + }, { // Minor changes can be grouped and automerged for dev dependencies, but are also deprioritised. groupName: "development dependencies (non-major)", groupSlug: "dev-dependencies", - matchPackagePrefixes: [ - "dev", - "lint", - "types" + matchDepPatterns: [ + "dev/.*", + "lint/.*", + "types/.*" + ], + matchPackagePatterns: [ + // Brought from charmcraft. May not be complete. + // This helps group dependencies in requirements-dev.txt files. + "^(.*/)?autoflake$", + "^(.*/)?black$", + "^(.*/)?codespell$", + "^(.*/)?coverage$", + "^(.*/)?flake8$", + "^(.*/)?hypothesis$", + "^(.*/)?mypy$", + "^(.*/)?pycodestyle$", + "^(.*/)?docstyle$", + "^(.*/)?pyfakefs$", + "^(.*/)?pyflakes$", + "^(.*/)?pylint$", + "^(.*/)?pytest", + "^(.*/)?responses$", + "^(.*/)?ruff$", + "^(.*/)?twine$", + "^(.*/)?tox$", + "^(.*/)?types-", ], - excludePackagePatterns: ["ruff"], matchUpdateTypes: ["minor", "patch", "pin", "digest"], prPriority: -1, automerge: true @@ -48,14 +97,16 @@ // Documentation related updates groupName: "documentation dependencies", groupSlug: "doc-dependencies", - matchPackageNames: ["Sphinx"], - matchPackagePatterns: ["^[Ss]phinx.*$", "^furo$"], - matchPackagePrefixes: ["docs"], + matchPackageNames: ["Sphinx", "furo"], + matchPackagePatterns: ["[Ss]phinx.*$"], + matchDepPatterns: ["docs/.*"], + matchBaseBranches: ["$default"], // Not for hotfix branches }, { // Other major dependencies get deprioritised below minor dev dependencies. matchUpdateTypes: ["major"], - prPriority: -2 + prPriority: -2, + matchBaseBranches: ["$default"], // Not for hotfix branches }, { // Major dev dependencies are stone last, but grouped. @@ -63,19 +114,22 @@ groupSlug: "dev-dependencies", matchDepTypes: ["devDependencies"], matchUpdateTypes: ["major"], - prPriority: -3 + prPriority: -3, + matchBaseBranches: ["$default"], // Not for hotfix branches }, { - // Ruff is still unstable, so update it separately. - groupName: "ruff", - matchPackagePatterns: ["^(lint/)?ruff$"], - prPriority: -3 + // Pyright makes regular breaking changes in patch releases, so we separate these + // and do them independently. + matchPackageNames: ["pyright", "types/pyright"], + prPriority: -4, + matchBaseBranches: ["$default"], // Not for hotfix branches } ], - regexManagers: [ + customManagers: [ { // tox.ini can get updates too if we specify for each package. fileMatch: ["tox.ini"], + customType: "regex", depTypeTemplate: "devDependencies", matchStrings: [ "# renovate: datasource=(?\\S+)\n\\s+(?.*?)(\\[[\\w]*\\])*[=><]=?(?.*?)\n" @@ -84,18 +138,22 @@ { // .pre-commit-config.yaml version updates fileMatch: [".pre-commit-config.yaml"], - depTypeTemplate: "devDependencies", + customType: "regex", + datasourceTemplate: "pypi", + depTypeTemplate: "lint", matchStrings: [ - "# renovate: datasource=(?\\S+);\\s*depName=(?.*?)\n\s+rev: \"v?(?.*?)\"" + "- repo: .*/<(?\\S+)\\s*\\n\\s*rev:\s+\"?v?(?\\S*)\"?", ] } ], timezone: "Etc/UTC", - automergeSchedule: ["every weekend"], schedule: ["every weekend"], prConcurrentLimit: 2, // No more than 2 open PRs at a time. + branchConcurrentLimit: 20, // No more than 20 open branches at a time. prCreation: "not-pending", // Wait until status checks have completed before raising the PR prNotPendingHours: 4, // ...unless the status checks have been running for 4+ hours. prHourlyLimit: 1, // No more than 1 PR per hour. - stabilityDays: 2 // Wait 2 days from release before updating. -} \ No newline at end of file + minimumReleaseAge: "2 days", + automergeStrategy: "squash", // Squash & rebase when auto-merging. + semanticCommitType: "build" // use `build` as commit header type (i.e. `build(deps): `) +} diff --git a/.github/workflows/check-renovate.yaml b/.github/workflows/check-renovate.yaml new file mode 100644 index 0000000000..7d13cfbf21 --- /dev/null +++ b/.github/workflows/check-renovate.yaml @@ -0,0 +1,40 @@ +name: Renovate check +on: + pull_request: + paths: + - ".github/workflows/check-renovate.yaml" + - ".github/renovate.json5" + + # Allows triggering the workflow manually from the Actions tab + workflow_dispatch: + inputs: + enable_ssh_access: + type: boolean + description: 'Enable ssh access' + required: false + default: false + +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Install renovate + run: npm install --global renovate + - name: Enable ssh access + uses: mxschmitt/action-tmate@v3 + if: ${{ inputs.enable_ssh_access }} + with: + limit-access-to-actor: true + - name: Check renovate config + run: renovate-config-validator .github/renovate.json5 + - name: Renovate dry-run + run: renovate --dry-run --autodiscover + env: + RENOVATE_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RENOVATE_USE_BASE_BRANCH_CONFIG: ${{ github.ref }} From 654871d45c19fb792e9de30fdabaf007b72bee49 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:45:03 -0500 Subject: [PATCH 042/151] build(deps): update bugfixes (hotfix/7.5) (#4974) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/.sphinx/pinned-requirements.txt | 18 +++++------ requirements-devel.txt | 48 ++++++++++++++-------------- requirements.txt | 24 +++++++------- tox.ini | 2 +- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index e718a50ccb..d35ef8783b 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -1,4 +1,4 @@ -alabaster==0.7.12 +alabaster==0.7.16 Babel==2.11.0 beautifulsoup4==4.11.1 certifi==2022.12.7 @@ -8,9 +8,9 @@ docutils==0.19 furo==2022.9.29 idna==3.4 imagesize==1.4.1 -Jinja2==3.1.2 +Jinja2==3.1.4 livereload==2.6.3 -MarkupSafe==2.1.1 +MarkupSafe==2.1.5 packaging==21.3 pyenchant==3.2.2 Pygments==2.13.0 @@ -22,13 +22,13 @@ snowballstemmer==2.2.0 soupsieve==2.3.2.post1 Sphinx==5.3.0 sphinx-autobuild==2021.3.14 -sphinx-basic-ng==1.0.0b1 +sphinx-basic-ng==1.0.0b2 sphinx_design==0.3.0 -sphinxcontrib-applehelp==1.0.2 -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-applehelp==1.0.8 +sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-qthelp==1.0.8 +sphinxcontrib-serializinghtml==1.1.10 tornado==6.2 -urllib3==1.26.13 +urllib3==1.26.19 diff --git a/requirements-devel.txt b/requirements-devel.txt index 06ea0457d0..2163371a67 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,4 +1,4 @@ -astroid==2.15.4 +astroid==2.15.8 attrs==23.1.0 black==23.3.0 cachetools==5.3.0 @@ -7,7 +7,7 @@ certifi==2023.5.7 cffi==1.15.1 chardet==5.1.0 charset-normalizer==3.1.0 -click==8.1.3 +click==8.1.7 codespell==2.2.4 colorama==0.4.6 coverage==7.2.5 @@ -18,9 +18,9 @@ craft-parts==1.19.7 craft-providers==1.20.2 craft-store==2.5.0 cryptography==40.0.2 -Deprecated==1.2.13 -dill==0.3.6 -distlib==0.3.6 +Deprecated==1.2.14 +dill==0.3.8 +distlib==0.3.8 distro==1.8.0 docutils==0.19 exceptiongroup==1.1.1 @@ -28,7 +28,7 @@ filelock==3.12.0 fixtures==4.0.1 gnupg==2.3.1 httplib2==0.22.0 -hupper==1.12 +hupper==1.12.1 idna==3.4 importlib-metadata==6.6.0 importlib-resources==5.12.0 @@ -39,11 +39,11 @@ jeepney==0.8.0 jsonschema==2.5.1 keyring==23.13.1 launchpadlib==1.11.0 -lazr.restfulclient==0.14.5 +lazr.restfulclient==0.14.6 lazr.uri==1.0.6 lazy-object-proxy==1.9.0 -lxml==4.9.2 -macaroonbakery==1.3.1 +lxml==4.9.4 +macaroonbakery==1.3.4 mccabe==0.7.0 more-itertools==9.1.0 mypy==1.3.0 @@ -61,7 +61,7 @@ platformdirs==3.5.0 pluggy==1.0.0 progressbar==2.5 protobuf==3.20.3 -psutil==5.9.5 +psutil==5.9.8 ptyprocess==0.7.0 pycodestyle==2.10.0 pycparser==2.21 @@ -70,33 +70,33 @@ pydantic-yaml==0.11.2 pydocstyle==6.3.0 pyelftools==0.29 pyflakes==3.0.1 -pyftpdlib==1.5.7 -pylint==2.17.4 -pylint-fixme-info==1.0.3 -pylint-pytest==1.1.2 -pylxd==2.3.1 +pyftpdlib==1.5.10 +pylint==2.17.7 +pylint-fixme-info==1.0.4 +pylint-pytest==1.1.8 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.0.9 pyproject_api==1.5.1 -pyramid==2.0.1 +pyramid==2.0.2 pyRFC3339==1.1 pytest==7.3.1 pytest-cov==4.0.0 pytest-mock==3.10.0 -pytest-subprocess==1.5.0 +pytest-subprocess==1.5.2 python-dateutil==2.8.2 python-debian==0.1.49 pytz==2023.3 pyxdg==0.28 -PyYAML==6.0 +PyYAML==6.0.2 raven==6.10.0 requests==2.30.0 requests-toolbelt==1.0.0 requests-unixsocket==0.3.0 ruff==0.0.220 SecretStorage==3.3.3 -simplejson==3.19.1 +simplejson==3.19.2 six==1.16.0 snap-helpers==0.3.2 snowballstemmer==2.2.0 @@ -109,14 +109,14 @@ tomli==2.0.1 tomlkit==0.11.8 tox==4.5.1 translationstring==1.4 -types-Deprecated==1.2.9.2 -types-PyYAML==6.0.12.9 +types-Deprecated==1.2.9.20240311 +types-PyYAML==6.0.12.20240808 types-requests==2.30.0.0 types-setuptools==67.7.0.2 -types-tabulate==0.9.0.2 -types-urllib3==1.26.25.13 +types-tabulate==0.9.0.20240106 +types-urllib3==1.26.25.14 typing_extensions==4.5.0 -urllib3==1.26.15 +urllib3==1.26.19 venusian==3.0.0 virtualenv==20.23.0 wadllib==1.3.6 diff --git a/requirements.txt b/requirements.txt index 6328a931ef..82f60f1f0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ certifi==2023.5.7 cffi==1.15.1 chardet==5.1.0 charset-normalizer==3.1.0 -click==8.1.3 +click==8.1.7 craft-archives==1.1.3 craft-cli==1.2.0 craft-grammar==1.1.1 @@ -12,7 +12,7 @@ craft-parts==1.19.7 craft-providers==1.20.2 craft-store==2.5.0 cryptography==40.0.2 -Deprecated==1.2.13 +Deprecated==1.2.14 distro==1.8.0 docutils==0.19 gnupg==2.3.1 @@ -25,10 +25,10 @@ jeepney==0.8.0 jsonschema==2.5.1 keyring==23.13.1 launchpadlib==1.11.0 -lazr.restfulclient==0.14.5 +lazr.restfulclient==0.14.6 lazr.uri==1.0.6 -lxml==4.9.2 -macaroonbakery==1.3.1 +lxml==4.9.4 +macaroonbakery==1.3.4 more-itertools==9.1.0 mypy-extensions==1.0.0 oauthlib==3.2.2 @@ -37,12 +37,12 @@ packaging==23.1 platformdirs==3.5.0 progressbar==2.5 protobuf==3.20.3 -psutil==5.9.5 +psutil==5.9.8 pycparser==2.21 pydantic==1.10.7 pydantic-yaml==0.11.2 pyelftools==0.29 -pylxd==2.3.1 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.0.9 @@ -51,22 +51,22 @@ python-dateutil==2.8.2 python-debian==0.1.49 pytz==2023.3 pyxdg==0.28 -PyYAML==6.0.1 +PyYAML==6.0.2 raven==6.10.0 requests==2.30.0 requests-toolbelt==1.0.0 requests-unixsocket==0.3.0 SecretStorage==3.3.3 -simplejson==3.19.1 +simplejson==3.19.2 six==1.16.0 snap-helpers==0.3.2 tabulate==0.9.0 tinydb==4.7.1 toml==0.10.2 -types-Deprecated==1.2.9.2 -types-PyYAML==6.0.12.9 +types-Deprecated==1.2.9.20240311 +types-PyYAML==6.0.12.20240808 typing_extensions==4.5.0 -urllib3==1.26.15 +urllib3==1.26.19 wadllib==1.3.6 wrapt==1.15.0 ws4py==0.5.1 diff --git a/tox.ini b/tox.ini index 2094a941e6..c027d620be 100644 --- a/tox.ini +++ b/tox.ini @@ -161,7 +161,7 @@ deps = # renovate: datasource=pypi sphinx_design==0.4.1 # renovate: datasource=pypi - sphinx-copybutton==0.5.1 + sphinx-copybutton==0.5.2 # renovate: datasource=pypi sphinx-pydantic==0.1.1 # renovate: datasource=pypi From 7bee67cd70ef5a32c06f0529aa91fdd92d5e843a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:45:41 -0500 Subject: [PATCH 043/151] build(deps): update bugfixes (hotfix/8.3) (#4977) Signed-off-by: Callahan Kovacs Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Callahan Kovacs --- docs/.sphinx/pinned-requirements.txt | 2 +- docs/requirements.txt | 26 +++++++++++++------------- requirements-devel.txt | 18 +++++++++--------- requirements.txt | 8 ++++---- snapcraft/models/project.py | 2 +- tox.ini | 2 +- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index fb783efad0..ce55a379a0 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -28,7 +28,7 @@ sphinxcontrib-applehelp==1.0.8 sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.5 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-qthelp==1.0.8 sphinxcontrib-serializinghtml==1.1.10 tornado==6.4.1 urllib3==1.26.19 diff --git a/docs/requirements.txt b/docs/requirements.txt index f226f05c8e..bbf7ab866d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -5,7 +5,7 @@ Babel==2.14.0 beautifulsoup4==4.12.3 bracex==2.4 canonical-sphinx==0.1.0 -canonical-sphinx-extensions==0.0.21 +canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 certifi==2024.2.2 cffi==1.16.0 @@ -20,11 +20,11 @@ craft-grammar==1.2.0 craft-parts==1.33.0 craft-providers==1.23.1 craft-store==2.6.2 -cryptography==42.0.5 +cryptography==42.0.8 Deprecated==1.2.14 distro==1.9.0 docutils==0.19 -exceptiongroup==1.2.1 +exceptiongroup==1.2.2 furo==2024.5.6 gnupg==2.3.1 h11==0.14.0 @@ -47,7 +47,7 @@ macaroonbakery==1.3.4 Markdown==3.6 markdown-it-py==3.0.0 MarkupSafe==2.1.5 -mdit-py-plugins==0.4.0 +mdit-py-plugins==0.4.1 mdurl==0.1.2 more-itertools==10.2.0 mypy-extensions==1.0.0 @@ -55,8 +55,8 @@ myst-parser==3.0.1 oauthlib==3.2.2 overrides==7.7.0 packaging==24.0 -pip==23.3 -platformdirs==4.2.1 +pip==23.3.2 +platformdirs==4.2.2 polib==1.2.0 progressbar==2.5 protobuf==5.26.1 @@ -67,7 +67,7 @@ pydantic-yaml==0.11.2 pyelftools==0.31 pygit2==1.13.3 Pygments==2.17.2 -pylxd==2.3.2 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.2 @@ -78,7 +78,7 @@ python-dateutil==2.9.0.post0 python-debian==0.1.49 pytz==2024.1 pyxdg==0.28 -PyYAML==6.0.1 +PyYAML==6.0.2 raven==6.10.0 regex==2024.4.28 requests==2.31.0 @@ -98,8 +98,8 @@ sphinx-basic-ng==1.0.0b2 sphinx-copybutton==0.5.2 sphinx_design==0.5.0 sphinx-lint==0.9.1 -sphinx-notfound-page==1.0.0 -sphinx-reredirects==0.1.3 +sphinx-notfound-page==1.0.4 +sphinx-reredirects==0.1.5 sphinx-tabs==3.4.5 sphinxcontrib-applehelp==1.0.8 sphinxcontrib-details-directive==0.1.0 @@ -107,7 +107,7 @@ sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.5 sphinxcontrib-jquery==4.1 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-qthelp==1.0.8 sphinxcontrib-serializinghtml==1.1.10 sphinxext-opengraph==0.9.1 starlette==0.37.2 @@ -116,7 +116,7 @@ tinydb==4.8.0 toml==0.10.2 tomli==2.0.1 types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240311 +types-PyYAML==6.0.12.20240808 typing_extensions==4.11.0 uc-micro-py==1.0.3 urllib3==1.26.19 @@ -124,7 +124,7 @@ uvicorn==0.29.0 validators==0.28.3 wadllib==1.3.6 watchfiles==0.21.0 -wcmatch==8.5.1 +wcmatch==8.5.2 webencodings==0.5.1 websockets==12.0 wrapt==1.16.0 diff --git a/requirements-devel.txt b/requirements-devel.txt index 7c389dd760..c6ca7fc209 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -18,7 +18,7 @@ craft-grammar==1.2.0 craft-parts==1.33.0 craft-providers==1.23.1 craft-store==2.6.2 -cryptography==42.0.5 +cryptography==42.0.8 Deprecated==1.2.14 dill==0.3.8 distlib==0.3.8 @@ -68,25 +68,25 @@ pydantic-yaml==0.11.2 pydocstyle==6.3.0 pyelftools==0.30 pyflakes==3.1.0 -pyftpdlib==1.5.9 +pyftpdlib==1.5.10 pygit2==1.13.3 -pylxd==2.3.2 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.2 pyproject-api==1.6.1 pyramid==2.0.2 pyRFC3339==1.1 -pyright==1.1.356 +pyright==1.1.375 pytest==7.4.4 pytest-cov==5.0.0 pytest-mock==3.14.0 -pytest-subprocess==1.5.0 +pytest-subprocess==1.5.2 python-dateutil==2.8.2 python-debian==0.1.49 pytz==2023.4 pyxdg==0.28 -PyYAML==6.0.1 +PyYAML==6.0.2 raven==6.10.0 requests==2.31.0 requests-toolbelt==1.0.0 @@ -98,17 +98,17 @@ snap-helpers==0.4.2 snowballstemmer==2.2.0 tabulate==0.9.0 testscenarios==0.5.0 -testtools==2.7.1 +testtools==2.7.2 tinydb==4.8.0 toml==0.10.2 tomlkit==0.12.4 tox==4.11.4 translationstring==1.4 types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240311 +types-PyYAML==6.0.12.20240808 types-requests==2.31.0.6 types-setuptools==69.2.0.20240317 -types-simplejson==3.19.0.20240310 +types-simplejson==3.19.0.20240801 types-tabulate==0.9.0.20240106 types-toml==0.10.8.20240310 types-urllib3==1.26.25.14 diff --git a/requirements.txt b/requirements.txt index b1cf9056d5..daca506562 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ craft-grammar==1.2.0 craft-parts==1.33.0 craft-providers==1.23.1 craft-store==2.6.2 -cryptography==42.0.5 +cryptography==42.0.8 Deprecated==1.2.14 distro==1.9.0 docutils==0.19 @@ -43,7 +43,7 @@ pydantic==1.10.14 pydantic-yaml==0.11.2 pyelftools==0.30 pygit2==1.13.3 -pylxd==2.3.2 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.2 @@ -52,7 +52,7 @@ python-dateutil==2.8.2 python-debian==0.1.49 pytz==2023.4 pyxdg==0.28 -PyYAML==6.0.1 +PyYAML==6.0.2 raven==6.10.0 requests==2.31.0 requests-toolbelt==1.0.0 @@ -65,7 +65,7 @@ tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240311 +types-PyYAML==6.0.12.20240808 typing_extensions==4.9.0 urllib3==1.26.19 validators==0.28.3 diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 258b5aba23..adb4a9a782 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -597,7 +597,7 @@ class Project(models.Project): # snapcraft's `name` is more general than craft-application name: ProjectName # type: ignore[assignment] - build_base: Optional[str] + build_base: Optional[str] # type: ignore[assignment] compression: Literal["lzo", "xz"] = "xz" version: Optional[VersionStr] # type: ignore[assignment] donation: Optional[UniqueStrList] diff --git a/tox.ini b/tox.ini index a18d9fe950..45fa74c780 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ requires = # renovate: datasource=pypi tox-ignore-env-name-mismatch==0.2.0.post2 # renovate: datasource=pypi - tox-gh==1.3.1 + tox-gh==1.3.2 # Allow tox to access the user's $TMPDIR environment variable if set. # This workaround is required to avoid circular dependencies for TMPDIR, # since tox will otherwise attempt to use the environment's TMPDIR variable. From 07795c0d7ec346cb2a13cc9b0ffe65dae0488ee4 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 15 Aug 2024 09:14:38 -0500 Subject: [PATCH 044/151] build(deps): bump craft-application to 4.1.0 Signed-off-by: Callahan Kovacs --- docs/requirements.txt | 2 +- requirements-devel.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 96a36bf294..706fececc8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -13,7 +13,7 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.0.0 +craft-application==4.1.0 craft-archives==2.0.0 craft-cli==2.6.0 craft-grammar==2.0.0 diff --git a/requirements-devel.txt b/requirements-devel.txt index 29dededf51..b4f63e5297 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -13,7 +13,7 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.0.0 +craft-application==4.1.0 craft-archives==2.0.0 craft-cli==2.6.0 craft-grammar==2.0.0 diff --git a/requirements.txt b/requirements.txt index 81c18c3e43..f6d5f584fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ cffi==1.17.0 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.0.0 +craft-application==4.1.0 craft-archives==2.0.0 craft-cli==2.6.0 craft-grammar==2.0.0 diff --git a/setup.py b/setup.py index db48ebe180..bd113976b6 100755 --- a/setup.py +++ b/setup.py @@ -97,7 +97,7 @@ def recursive_data_files(directory, install_directory): "attrs", "catkin-pkg; sys_platform == 'linux'", "click", - "craft-application~=4.0", + "craft-application~=4.1", "craft-archives~=2.0", "craft-cli~=2.6", "craft-grammar~=2.0", From 28da1251534bbce4563c5b0a4284bddd49a0fe3b Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 14 Aug 2024 16:21:44 -0500 Subject: [PATCH 045/151] fix(provider): forward proxy variables to core24 build environments Forward `http_proxy`, `https_proxy`, and `no_proxy` to core24 build environments. Signed-off-by: Callahan Kovacs --- snapcraft/const.py | 10 ++++++++++ snapcraft/services/provider.py | 17 +++++++---------- tests/unit/services/test_provider.py | 22 +++++++++++++--------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/snapcraft/const.py b/snapcraft/const.py index fcf090978a..6543ea5519 100644 --- a/snapcraft/const.py +++ b/snapcraft/const.py @@ -47,3 +47,13 @@ def __str__(self) -> str: CURRENT_BASES = frozenset(BASES - ESM_BASES - LEGACY_BASES) """Bases handled by the current snapcraft codebase.""" + + +SNAPCRAFT_ENVIRONMENT_VARIABLES = frozenset( + { + "SNAPCRAFT_BUILD_INFO", + "SNAPCRAFT_IMAGE_INFO", + "SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", + } +) +"""Snapcraft-specific environment variables.""" diff --git a/snapcraft/services/provider.py b/snapcraft/services/provider.py index 2ed2b59083..def7267d69 100644 --- a/snapcraft/services/provider.py +++ b/snapcraft/services/provider.py @@ -20,19 +20,16 @@ from craft_application import ProviderService from overrides import overrides +from snapcraft.const import SNAPCRAFT_ENVIRONMENT_VARIABLES + class Provider(ProviderService): """Snapcraft specialization of the Lifecycle Service.""" @overrides def setup(self) -> None: - if build_info := os.getenv("SNAPCRAFT_BUILD_INFO"): - self.environment["SNAPCRAFT_BUILD_INFO"] = build_info - if image_info := os.getenv("SNAPCRAFT_IMAGE_INFO"): - self.environment["SNAPCRAFT_IMAGE_INFO"] = image_info - if experimental_extensions := os.getenv( - "SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS" - ): - self.environment["SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS"] = ( - experimental_extensions - ) + """Add snapcraft environment variables to the build environment.""" + super().setup() + for variable in SNAPCRAFT_ENVIRONMENT_VARIABLES: + if variable in os.environ: + self.environment[variable] = os.environ[variable] diff --git a/tests/unit/services/test_provider.py b/tests/unit/services/test_provider.py index 8735e4d763..68c008ecc3 100644 --- a/tests/unit/services/test_provider.py +++ b/tests/unit/services/test_provider.py @@ -16,15 +16,19 @@ """Tests for the Snapcraft provider service.""" +from craft_application.services.provider import DEFAULT_FORWARD_ENVIRONMENT_VARIABLES + +from snapcraft.const import SNAPCRAFT_ENVIRONMENT_VARIABLES + def test_provider(provider_service, monkeypatch): - monkeypatch.setenv("SNAPCRAFT_BUILD_INFO", "foo") - monkeypatch.setenv("SNAPCRAFT_IMAGE_INFO", "bar") - monkeypatch.setenv("SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS", "baz") - provider_service.setup() - assert provider_service.environment["SNAPCRAFT_BUILD_INFO"] == "foo" - assert provider_service.environment["SNAPCRAFT_IMAGE_INFO"] == "bar" - assert ( - provider_service.environment["SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS"] - == "baz" + variables = SNAPCRAFT_ENVIRONMENT_VARIABLES | set( + DEFAULT_FORWARD_ENVIRONMENT_VARIABLES ) + for variable in variables: + monkeypatch.setenv(variable, f"{variable}-test-value") + + provider_service.setup() + + for variable in variables: + assert provider_service.environment[variable] == f"{variable}-test-value" From b6e647c01ab05bee061273b6c65b599861873d37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:03:45 +0000 Subject: [PATCH 046/151] build(deps): update bugfixes (main) (#4976) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/.sphinx/pinned-requirements.txt | 2 +- docs/requirements.txt | 22 +++++++++++----------- requirements-devel.txt | 2 +- tox.ini | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index 203a675ccc..dc494d914b 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -28,7 +28,7 @@ sphinxcontrib-applehelp==1.0.8 sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.5 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-qthelp==1.0.8 sphinxcontrib-serializinghtml==1.1.10 tornado==6.4.1 urllib3==1.26.19 diff --git a/docs/requirements.txt b/docs/requirements.txt index 706fececc8..14b3993e5b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -5,7 +5,7 @@ Babel==2.14.0 beautifulsoup4==4.12.3 bracex==2.4 canonical-sphinx==0.1.0 -canonical-sphinx-extensions==0.0.21 +canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 certifi==2024.7.4 cffi==1.17.0 @@ -21,11 +21,11 @@ craft-parts==2.0.0 craft-platforms==0.1.1 craft-providers==2.0.0 craft-store==3.0.0 -cryptography==42.0.5 +cryptography==42.0.8 Deprecated==1.2.14 distro==1.9.0 docutils==0.19 -exceptiongroup==1.2.1 +exceptiongroup==1.2.2 furo==2024.5.6 gnupg==2.3.1 h11==0.14.0 @@ -48,7 +48,7 @@ macaroonbakery==1.3.4 Markdown==3.6 markdown-it-py==3.0.0 MarkupSafe==2.1.5 -mdit-py-plugins==0.4.0 +mdit-py-plugins==0.4.1 mdurl==0.1.2 more-itertools==10.2.0 mypy-extensions==1.0.0 @@ -56,8 +56,8 @@ myst-parser==3.0.1 oauthlib==3.2.2 overrides==7.7.0 packaging==24.0 -pip==23.3 -platformdirs==4.2.1 +pip==23.3.2 +platformdirs==4.2.2 polib==1.2.0 progressbar==2.5 protobuf==5.26.1 @@ -67,7 +67,7 @@ pydantic==2.8.2 pyelftools==0.31 pygit2==1.13.3 Pygments==2.17.2 -pylxd==2.3.2 +pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.2 @@ -98,8 +98,8 @@ sphinx-basic-ng==1.0.0b2 sphinx-copybutton==0.5.2 sphinx_design==0.5.0 sphinx-lint==0.9.1 -sphinx-notfound-page==1.0.0 -sphinx-reredirects==0.1.3 +sphinx-notfound-page==1.0.4 +sphinx-reredirects==0.1.5 sphinx-tabs==3.4.5 sphinxcontrib-applehelp==1.0.8 sphinxcontrib-details-directive==0.1.0 @@ -107,7 +107,7 @@ sphinxcontrib-devhelp==1.0.6 sphinxcontrib-htmlhelp==2.0.5 sphinxcontrib-jquery==4.1 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.7 +sphinxcontrib-qthelp==1.0.8 sphinxcontrib-serializinghtml==1.1.10 sphinxext-opengraph==0.9.1 starlette==0.37.2 @@ -124,7 +124,7 @@ uvicorn==0.29.0 validators==0.28.3 wadllib==1.3.6 watchfiles==0.21.0 -wcmatch==8.5.1 +wcmatch==8.5.2 webencodings==0.5.1 websockets==12.0 wrapt==1.16.0 diff --git a/requirements-devel.txt b/requirements-devel.txt index b4f63e5297..9c9107e6dc 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -133,4 +133,4 @@ zipp==3.20.0 zope.deprecation==5.0 zope.interface==7.0.1 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" -pyinstaller==5.13.1; sys.platform == "win32" +pyinstaller==5.13.2; sys.platform == "win32" diff --git a/tox.ini b/tox.ini index bba9fe6575..785793b14b 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ requires = # renovate: datasource=pypi tox-ignore-env-name-mismatch==0.2.0.post2 # renovate: datasource=pypi - tox-gh==1.3.1 + tox-gh==1.3.2 # Allow tox to access the user's $TMPDIR environment variable if set. # This workaround is required to avoid circular dependencies for TMPDIR, # since tox will otherwise attempt to use the environment's TMPDIR variable. From 037c230170cfa33760c27cbefd4388a78b148294 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 15 Aug 2024 19:58:16 -0500 Subject: [PATCH 047/151] docs(changelog): add 7.5.6 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index c473928b31..20572bda28 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -78,6 +78,24 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +7.5.6 (2024-Aug-15) +------------------- + +Core +==== + +Bases +##### + +core22 +"""""" + +* Fix a regression where icons would not be configured and installed for snaps + with no ``apps`` defined in their ``snapcraft.yaml``. + +For a complete list of commits, check out the `7.5.6`_ release on GitHub. + + 8.3.2 (2024-Aug-05) ------------------- @@ -1001,6 +1019,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4909: https://github.com/canonical/snapcraft/issues/4909 .. _#4942: https://github.com/canonical/snapcraft/issues/4942 +.. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 .. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 .. _8.0.1: https://github.com/canonical/snapcraft/releases/tag/8.0.1 .. _8.0.2: https://github.com/canonical/snapcraft/releases/tag/8.0.2 From 3a1b8a9491bd7927a73556215b4aa754b1c8745f Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Tue, 20 Aug 2024 08:28:36 -0400 Subject: [PATCH 048/151] style(lint): fix ruff 0.6 linting errors (#4981) --- snapcraft/commands/remote.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index b8892917a0..1987bfbfd2 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -246,7 +246,8 @@ def _run( # noqa: PLR0915 [too-many-statements] builder.cleanup() return returncode - def _monitor_and_complete( # noqa: PLR0912 (too many branches) + # noqa for too-many-branches and too-many-statements + def _monitor_and_complete( # noqa: PLR0912, PLR0915 self, build_id: str | None, builds: Collection[Build] ) -> int: builder = self._services.remote_build From 35c7d97a4cb95e05417a5d6e7ef7abb08a7c0d99 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:51:33 -0400 Subject: [PATCH 049/151] build(deps): update github actions (main) (major) (#4988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [codecov/codecov-action](https://togithub.com/codecov/codecov-action) | action | major | `v3` -> `v4` | | [release-drafter/release-drafter](https://togithub.com/release-drafter/release-drafter) | action | major | `v5.7.0` -> `v6.0.0` | --- ### Release Notes
codecov/codecov-action (codecov/codecov-action) ### [`v4`](https://togithub.com/codecov/codecov-action/compare/v3...v4) [Compare Source](https://togithub.com/codecov/codecov-action/compare/v3...v4)
release-drafter/release-drafter (release-drafter/release-drafter) ### [`v6.0.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v6.0.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.25.0...v6.0.0) ### What's Changed - Update Node.js to 20 ([#​1379](https://togithub.com/release-drafter/release-drafter/issues/1379)) [@​massongit](https://togithub.com/massongit) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.25.0...v6.0.0 ### [`v5.25.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.25.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.24.0...v5.25.0) ### What's Changed #### New - add prerelease increment behavior ([#​1303](https://togithub.com/release-drafter/release-drafter/issues/1303)) [@​neilime](https://togithub.com/neilime) - add latest input ([#​1348](https://togithub.com/release-drafter/release-drafter/issues/1348)) [@​o-mago](https://togithub.com/o-mago) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.24.0...v5.25.0 ### [`v5.24.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.24.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.23.0...v5.24.0) ##### What's Changed ##### New - Add release version to github action output ([#​1300](https://togithub.com/release-drafter/release-drafter/issues/1300)) [@​mehdihadeli](https://togithub.com/mehdihadeli) ##### Bug Fixes - fix(release): strip prefix before comparing version ([#​1255](https://togithub.com/release-drafter/release-drafter/issues/1255)) [@​neilime](https://togithub.com/neilime) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.23.0...v5.24.0 ### [`v5.23.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.23.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.22.0...v5.23.0) ##### What's Changed ##### New - Add `include-pre-releases` configuration option ([#​1302](https://togithub.com/release-drafter/release-drafter/issues/1302)) [@​robbinjanssen](https://togithub.com/robbinjanssen) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.22.0...v5.23.0 ### [`v5.22.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.22.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.21.1...v5.22.0) ##### What's Changed ##### New - Only use last full release when drafting ([#​1240](https://togithub.com/release-drafter/release-drafter/issues/1240)) [@​ssbarnea](https://togithub.com/ssbarnea) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.21.1...v5.22.0 ### [`v5.21.1`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.21.1) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.21.0...v5.21.1) ### What's Changed #### Dependency Updates - Address `set-output` deprecation ([#​1247](https://togithub.com/release-drafter/release-drafter/issues/1247)) [@​NotMyFault](https://togithub.com/NotMyFault) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.21.0...v5.21.1 ### [`v5.21.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.21.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.20.1...v5.21.0) ### What's Changed #### New - fetch 100 labels for pull requests instead of 10 ([#​1220](https://togithub.com/release-drafter/release-drafter/issues/1220)) [@​matoubidou](https://togithub.com/matoubidou) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.20.1...v5.21.0 ### [`v5.20.1`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.20.1) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.20.0...v5.20.1) ### What's Changed #### Bug Fixes - Add missing inputs to action config ([#​1202](https://togithub.com/release-drafter/release-drafter/issues/1202)) [@​gilbertsoft](https://togithub.com/gilbertsoft) #### Documentation - Add more comments about pull requests permission ([#​1187](https://togithub.com/release-drafter/release-drafter/issues/1187)) [@​Kirade](https://togithub.com/Kirade) - Fix Vercel link ([#​1188](https://togithub.com/release-drafter/release-drafter/issues/1188)) [@​shinshin86](https://togithub.com/shinshin86) - Add permissions to README ([#​1132](https://togithub.com/release-drafter/release-drafter/issues/1132)) [@​danyeaw](https://togithub.com/danyeaw) #### Dependency Updates
20 changes - Bump eslint-plugin-unicorn from 42.0.0 to 43.0.2 ([#​1192](https://togithub.com/release-drafter/release-drafter/issues/1192)) [@​dependabot](https://togithub.com/dependabot) - Bump node from `af50279` to `4c8f734` ([#​1191](https://togithub.com/release-drafter/release-drafter/issues/1191)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.9.0-alpine to 18.7.0-alpine ([#​1190](https://togithub.com/release-drafter/release-drafter/issues/1190)) [@​dependabot](https://togithub.com/dependabot) - Bump jest from 28.1.0 to 28.1.3 ([#​1182](https://togithub.com/release-drafter/release-drafter/issues/1182)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.16.0 to 8.20.0 ([#​1185](https://togithub.com/release-drafter/release-drafter/issues/1185)) [@​dependabot](https://togithub.com/dependabot) - Bump nock from 13.2.4 to 13.2.9 ([#​1186](https://togithub.com/release-drafter/release-drafter/issues/1186)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 12.2.4 to 12.2.5 ([#​1178](https://togithub.com/release-drafter/release-drafter/issues/1178)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-plugin-prettier from 4.0.0 to 4.2.1 ([#​1176](https://togithub.com/release-drafter/release-drafter/issues/1176)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 13.0.0 to 13.0.3 ([#​1172](https://togithub.com/release-drafter/release-drafter/issues/1172)) [@​dependabot](https://togithub.com/dependabot) - Bump prettier from 2.6.2 to 2.7.1 ([#​1166](https://togithub.com/release-drafter/release-drafter/issues/1166)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​actions/core](https://togithub.com/actions/core) from 1.8.2 to 1.9.0 ([#​1164](https://togithub.com/release-drafter/release-drafter/issues/1164)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.4.3 to 13.0.0 ([#​1156](https://togithub.com/release-drafter/release-drafter/issues/1156)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 12.2.3 to 12.2.4 ([#​1155](https://togithub.com/release-drafter/release-drafter/issues/1155)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​vercel/ncc](https://togithub.com/vercel/ncc) from 0.33.4 to 0.34.0 ([#​1151](https://togithub.com/release-drafter/release-drafter/issues/1151)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.4.2 to 12.4.3 ([#​1153](https://togithub.com/release-drafter/release-drafter/issues/1153)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.4.1 to 12.4.2 ([#​1150](https://togithub.com/release-drafter/release-drafter/issues/1150)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.15.0 to 8.16.0 ([#​1149](https://togithub.com/release-drafter/release-drafter/issues/1149)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 12.2.2 to 12.2.3 ([#​1152](https://togithub.com/release-drafter/release-drafter/issues/1152)) [@​dependabot](https://togithub.com/dependabot) - Bump husky from 8.0.0 to 8.0.1 ([#​1141](https://togithub.com/release-drafter/release-drafter/issues/1141)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​actions/core](https://togithub.com/actions/core) from 1.8.0 to 1.8.2 ([#​1144](https://togithub.com/release-drafter/release-drafter/issues/1144)) [@​dependabot](https://togithub.com/dependabot)
**Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.20.0...v5.20.1 ### [`v5.20.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.20.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.19.0...v5.20.0) ### What's Changed #### New - allow header and footer to be passed as input ([#​1142](https://togithub.com/release-drafter/release-drafter/issues/1142)) [@​jetersen](https://togithub.com/jetersen) #### Dependency Updates
26 changes - Bump jest from 27.5.1 to 28.1.0 ([#​1140](https://togithub.com/release-drafter/release-drafter/issues/1140)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.14.0 to 8.15.0 ([#​1137](https://togithub.com/release-drafter/release-drafter/issues/1137)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​actions/core](https://togithub.com/actions/core) from 1.6.0 to 1.8.0 ([#​1139](https://togithub.com/release-drafter/release-drafter/issues/1139)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.4.0 to 12.4.1 ([#​1131](https://togithub.com/release-drafter/release-drafter/issues/1131)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​actions/core](https://togithub.com/actions/core) from 1.6.0 to 1.7.0 ([#​1129](https://togithub.com/release-drafter/release-drafter/issues/1129)) [@​dependabot](https://togithub.com/dependabot) - Bump husky from 7.0.4 to 8.0.0 ([#​1138](https://togithub.com/release-drafter/release-drafter/issues/1138)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.13.0 to 8.14.0 ([#​1127](https://togithub.com/release-drafter/release-drafter/issues/1127)) [@​dependabot](https://togithub.com/dependabot) - Bump cli-table3 from 0.6.1 to 0.6.2 ([#​1122](https://togithub.com/release-drafter/release-drafter/issues/1122)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.8.0-alpine to 17.9.0-alpine ([#​1121](https://togithub.com/release-drafter/release-drafter/issues/1121)) [@​dependabot](https://togithub.com/dependabot) - Bump semver from 7.3.6 to 7.3.7 ([#​1120](https://togithub.com/release-drafter/release-drafter/issues/1120)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​vercel/ncc](https://togithub.com/vercel/ncc) from 0.33.3 to 0.33.4 ([#​1118](https://togithub.com/release-drafter/release-drafter/issues/1118)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.3.7 to 12.4.0 ([#​1124](https://togithub.com/release-drafter/release-drafter/issues/1124)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.11.0 to 8.13.0 ([#​1117](https://togithub.com/release-drafter/release-drafter/issues/1117)) [@​dependabot](https://togithub.com/dependabot) - Bump semver from 7.3.5 to 7.3.6 ([#​1115](https://togithub.com/release-drafter/release-drafter/issues/1115)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-plugin-unicorn from 41.0.1 to 42.0.0 ([#​1113](https://togithub.com/release-drafter/release-drafter/issues/1113)) [@​dependabot](https://togithub.com/dependabot) - Bump prettier from 2.6.0 to 2.6.2 ([#​1112](https://togithub.com/release-drafter/release-drafter/issues/1112)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.7.1-alpine to 17.8.0-alpine ([#​1108](https://togithub.com/release-drafter/release-drafter/issues/1108)) [@​dependabot](https://togithub.com/dependabot) - Bump minimist from 1.2.5 to 1.2.6 ([#​1116](https://togithub.com/release-drafter/release-drafter/issues/1116)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.3.6 to 12.3.7 ([#​1104](https://togithub.com/release-drafter/release-drafter/issues/1104)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-plugin-unicorn from 41.0.0 to 41.0.1 ([#​1105](https://togithub.com/release-drafter/release-drafter/issues/1105)) [@​dependabot](https://togithub.com/dependabot) - Bump node from `8c62619` to `d1d5dc5` ([#​1106](https://togithub.com/release-drafter/release-drafter/issues/1106)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 12.2.1 to 12.2.2 ([#​1097](https://togithub.com/release-drafter/release-drafter/issues/1097)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.6.0-alpine to 17.7.1-alpine ([#​1100](https://togithub.com/release-drafter/release-drafter/issues/1100)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.10.0 to 8.11.0 ([#​1099](https://togithub.com/release-drafter/release-drafter/issues/1099)) [@​dependabot](https://togithub.com/dependabot) - Bump prettier from 2.5.1 to 2.6.0 ([#​1102](https://togithub.com/release-drafter/release-drafter/issues/1102)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.3.5 to 12.3.6 ([#​1103](https://togithub.com/release-drafter/release-drafter/issues/1103)) [@​dependabot](https://togithub.com/dependabot)
**Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.19.0...v5.20.0 ### [`v5.19.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.19.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.18.1...v5.19.0) ### What's Changed #### New - Add a `collapse-after` option for categories ([#​1095](https://togithub.com/release-drafter/release-drafter/issues/1095)) [@​robbinjanssen](https://togithub.com/robbinjanssen) - Tag prefix ([#​1092](https://togithub.com/release-drafter/release-drafter/issues/1092)) [@​blast-hardcheese](https://togithub.com/blast-hardcheese) - include-paths: Permit filtering only PRs that modify path prefixes ([#​1084](https://togithub.com/release-drafter/release-drafter/issues/1084)) [@​blast-hardcheese](https://togithub.com/blast-hardcheese) - Unify commit search and release targeting around `commitish`/`ref` ([#​1065](https://togithub.com/release-drafter/release-drafter/issues/1065)) [@​mikeroll](https://togithub.com/mikeroll) #### Bug Fixes - Regenerate schema after [#​1084](https://togithub.com/release-drafter/release-drafter/issues/1084) ([#​1094](https://togithub.com/release-drafter/release-drafter/issues/1094)) [@​blast-hardcheese](https://togithub.com/blast-hardcheese) - Filter by `commitish` regardless of the `refs/heads` prefix ([#​1089](https://togithub.com/release-drafter/release-drafter/issues/1089)) [@​mikeroll](https://togithub.com/mikeroll) - Cap evaluated release limit at 1000 ([#​1085](https://togithub.com/release-drafter/release-drafter/issues/1085)) [@​eddmann](https://togithub.com/eddmann) - Provide sane defaults if we have no prior releases ([#​1067](https://togithub.com/release-drafter/release-drafter/issues/1067)) [@​jetersen](https://togithub.com/jetersen) #### Maintenance - Sync docker/action.yml with action.yml ([#​1062](https://togithub.com/release-drafter/release-drafter/issues/1062)) [@​mkurz](https://togithub.com/mkurz) #### Documentation - Improve contribution docs and remove unused package script ([#​1066](https://togithub.com/release-drafter/release-drafter/issues/1066)) [@​masashi-sutou](https://togithub.com/masashi-sutou) #### Dependency Updates
14 changes - Bump lint-staged from 12.3.4 to 12.3.5 ([#​1096](https://togithub.com/release-drafter/release-drafter/issues/1096)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.9.0 to 8.10.0 ([#​1086](https://togithub.com/release-drafter/release-drafter/issues/1086)) [@​dependabot](https://togithub.com/dependabot) - Bump actions/setup-node from 2 to 3 ([#​1087](https://togithub.com/release-drafter/release-drafter/issues/1087)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.5.0-alpine to 17.6.0-alpine ([#​1080](https://togithub.com/release-drafter/release-drafter/issues/1080)) [@​dependabot](https://togithub.com/dependabot) - Bump actions/checkout from 2 to 3 ([#​1090](https://togithub.com/release-drafter/release-drafter/issues/1090)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-config-prettier from 8.3.0 to 8.5.0 ([#​1091](https://togithub.com/release-drafter/release-drafter/issues/1091)) [@​dependabot](https://togithub.com/dependabot) - Bump url-parse from 1.5.7 to 1.5.10 ([#​1088](https://togithub.com/release-drafter/release-drafter/issues/1088)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-plugin-unicorn from 40.1.0 to 41.0.0 ([#​1074](https://togithub.com/release-drafter/release-drafter/issues/1074)) [@​dependabot](https://togithub.com/dependabot) - Bump url-parse from 1.5.4 to 1.5.7 ([#​1075](https://togithub.com/release-drafter/release-drafter/issues/1075)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.8.0 to 8.9.0 ([#​1071](https://togithub.com/release-drafter/release-drafter/issues/1071)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.3.3 to 12.3.4 ([#​1070](https://togithub.com/release-drafter/release-drafter/issues/1070)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​vercel/ncc](https://togithub.com/vercel/ncc) from 0.33.1 to 0.33.3 ([#​1069](https://togithub.com/release-drafter/release-drafter/issues/1069)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.4.0-alpine to 17.5.0-alpine ([#​1072](https://togithub.com/release-drafter/release-drafter/issues/1072)) [@​dependabot](https://togithub.com/dependabot) - Bump jest from 27.5.0 to 27.5.1 ([#​1063](https://togithub.com/release-drafter/release-drafter/issues/1063)) [@​dependabot](https://togithub.com/dependabot)
**Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.18.1...v5.19.0 ### [`v5.18.1`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.18.1) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.18.0...v5.18.1) ### What's Changed #### Dependency Updates - Bump nock from 13.2.2 to 13.2.4 ([#​1056](https://togithub.com/release-drafter/release-drafter/issues/1056)) [@​dependabot](https://togithub.com/dependabot) - Bump jest from 27.4.7 to 27.5.0 ([#​1055](https://togithub.com/release-drafter/release-drafter/issues/1055)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​probot/octokit-plugin-config](https://togithub.com/probot/octokit-plugin-config) from 1.1.4 to 1.1.5 ([#​1057](https://togithub.com/release-drafter/release-drafter/issues/1057)) [@​jetersen](https://togithub.com/jetersen) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.18.0...v5.18.1 ### [`v5.18.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.18.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.6...v5.18.0) ### What's Changed #### New - Add header and footer ([#​1050](https://togithub.com/release-drafter/release-drafter/issues/1050)) [@​mkurz](https://togithub.com/mkurz) #### Bug Fixes - Update `target_commitish` when updating a release ([#​1052](https://togithub.com/release-drafter/release-drafter/issues/1052)) [@​mikeroll](https://togithub.com/mikeroll) - Expand vars in tag and name inputs ([#​1049](https://togithub.com/release-drafter/release-drafter/issues/1049)) [@​mkurz](https://togithub.com/mkurz) #### Dependency Updates - Bump lint-staged from 12.3.2 to 12.3.3 ([#​1051](https://togithub.com/release-drafter/release-drafter/issues/1051)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.7.0 to 8.8.0 ([#​1046](https://togithub.com/release-drafter/release-drafter/issues/1046)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.2.2 to 12.3.2 ([#​1044](https://togithub.com/release-drafter/release-drafter/issues/1044)) [@​dependabot](https://togithub.com/dependabot) - Bump joi from 17.5.0 to 17.6.0 ([#​1045](https://togithub.com/release-drafter/release-drafter/issues/1045)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 17.3.1-alpine to 17.4.0-alpine ([#​1038](https://togithub.com/release-drafter/release-drafter/issues/1038)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.2.1 to 12.2.2 ([#​1037](https://togithub.com/release-drafter/release-drafter/issues/1037)) [@​dependabot](https://togithub.com/dependabot) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.17.6...v5.18.0 ### [`v5.17.6`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.17.6) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.5...v5.17.6) ### What's Changed #### Bug Fixes - fix default for version-resolver in schema ([#​1026](https://togithub.com/release-drafter/release-drafter/issues/1026)) [@​jetersen](https://togithub.com/jetersen) #### Maintenance - allow conditional pre commit ([#​1033](https://togithub.com/release-drafter/release-drafter/issues/1033)) [@​jetersen](https://togithub.com/jetersen) - add action-build for dependabot updates ([#​1036](https://togithub.com/release-drafter/release-drafter/issues/1036)) [@​jetersen](https://togithub.com/jetersen) - move lint staged config to JavaScript file ([#​1027](https://togithub.com/release-drafter/release-drafter/issues/1027)) [@​jetersen](https://togithub.com/jetersen) #### Dependency Updates - Bump [@​probot/adapter-github-actions](https://togithub.com/probot/adapter-github-actions) from 3.1.0 to 3.1.1 ([#​1034](https://togithub.com/release-drafter/release-drafter/issues/1034)) [@​dependabot](https://togithub.com/dependabot) - remove nodemon as unused dev dependency ([#​1035](https://togithub.com/release-drafter/release-drafter/issues/1035)) [@​jetersen](https://togithub.com/jetersen) - Bump probot from 12.2.0 to 12.2.1 ([#​1031](https://togithub.com/release-drafter/release-drafter/issues/1031)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 12.2.0 to 12.2.1 ([#​1032](https://togithub.com/release-drafter/release-drafter/issues/1032)) [@​dependabot](https://togithub.com/dependabot) - update joi dependency ([#​1028](https://togithub.com/release-drafter/release-drafter/issues/1028)) [@​jetersen](https://togithub.com/jetersen) - Bump lint-staged from 12.1.7 to 12.2.0 ([#​1025](https://togithub.com/release-drafter/release-drafter/issues/1025)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-plugin-unicorn from 40.0.0 to 40.1.0 ([#​1021](https://togithub.com/release-drafter/release-drafter/issues/1021)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 8.6.0 to 8.7.0 ([#​1019](https://togithub.com/release-drafter/release-drafter/issues/1019)) [@​dependabot](https://togithub.com/dependabot) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.17.5...v5.17.6 ### [`v5.17.5`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.17.5) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.4...v5.17.5) ### What's Changed #### New - add owner and repository context to template variables ([#​1017](https://togithub.com/release-drafter/release-drafter/issues/1017)) [@​jetersen](https://togithub.com/jetersen) - add support for moving uncategorized to a category with no labels ([#​1013](https://togithub.com/release-drafter/release-drafter/issues/1013)) [@​jetersen](https://togithub.com/jetersen) #### Bug Fixes - ensure PRs are merged ([#​1015](https://togithub.com/release-drafter/release-drafter/issues/1015)) [@​jetersen](https://togithub.com/jetersen) #### Maintenance - add automatic release of release-drafter ([#​1016](https://togithub.com/release-drafter/release-drafter/issues/1016)) [@​jetersen](https://togithub.com/jetersen) **Full Changelog**: https://github.com/release-drafter/release-drafter/compare/v5.16.2...v5.17.5 ### [`v5.17.4`](https://togithub.com/release-drafter/release-drafter/compare/v5.17.3...v5.17.4) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.3...v5.17.4) ### [`v5.17.3`](https://togithub.com/release-drafter/release-drafter/compare/v5.17.2...v5.17.3) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.2...v5.17.3) ### [`v5.17.2`](https://togithub.com/release-drafter/release-drafter/compare/v5.17.1...v5.17.2) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.1...v5.17.2) ### [`v5.17.1`](https://togithub.com/release-drafter/release-drafter/compare/v5.17.0...v5.17.1) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.17.0...v5.17.1) ### [`v5.17.0`](https://togithub.com/release-drafter/release-drafter/compare/v5.16.2...v5.17.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.16.2...v5.17.0) ### [`v5.16.2`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.16.2) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.16.1...v5.16.2) ### What's Changed #### Bug Fixes - ensure deep merge of config ([#​1012](https://togithub.com/release-drafter/release-drafter/issues/1012)) [@​jetersen](https://togithub.com/jetersen) #### Dependency Updates - Bump husky from 4.3.8 to 7.0.4 ([#​962](https://togithub.com/release-drafter/release-drafter/issues/962)) [@​dependabot](https://togithub.com/dependabot) ### [`v5.16.1`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.16.1) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.16.0...v5.16.1) ### What's Changed #### Bug Fixes - fix server ([#​1010](https://togithub.com/release-drafter/release-drafter/issues/1010)) [@​jetersen](https://togithub.com/jetersen) ### [`v5.16.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.16.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.15.0...v5.16.0) ### What's Changed #### New - Support `pull_request_target` events ([#​991](https://togithub.com/release-drafter/release-drafter/issues/991)) [@​fjchen7](https://togithub.com/fjchen7) - Improve error message for when config is not found ([#​973](https://togithub.com/release-drafter/release-drafter/issues/973)) [@​nowsprinting](https://togithub.com/nowsprinting) - Added exclude-contributors and no-contributors-template configuration options ([#​898](https://togithub.com/release-drafter/release-drafter/issues/898)) [@​ThomasKasene](https://togithub.com/ThomasKasene) - add BASE_REF_NAME and HEAD_REF_NAME template variables ([#​816](https://togithub.com/release-drafter/release-drafter/issues/816)) [@​Eun](https://togithub.com/Eun) - Add $NEXT\_{PATCH,MINOR,MAJOR}*VERSION*{PATCH,MINOR,MAJOR} variables ([#​823](https://togithub.com/release-drafter/release-drafter/issues/823)) [@​mkurz](https://togithub.com/mkurz) #### Bug Fixes - fix server ([#​1010](https://togithub.com/release-drafter/release-drafter/issues/1010)) [@​jetersen](https://togithub.com/jetersen) - Check if PR body is null ([#​969](https://togithub.com/release-drafter/release-drafter/issues/969)) [@​frundh](https://togithub.com/frundh) - Fix prerelease string getting removed and add $COMPLETE version variable ([#​918](https://togithub.com/release-drafter/release-drafter/issues/918)) [@​tjenkinson](https://togithub.com/tjenkinson) - Use commitish from action inputs. Fixes [#​824](https://togithub.com/release-drafter/release-drafter/issues/824). ([#​865](https://togithub.com/release-drafter/release-drafter/issues/865)) [@​danielcweber](https://togithub.com/danielcweber) - Include `edited` event for autolabeler ([#​931](https://togithub.com/release-drafter/release-drafter/issues/931)) [@​juanecabellob](https://togithub.com/juanecabellob) #### Maintenance - update dependencies ([#​1003](https://togithub.com/release-drafter/release-drafter/issues/1003)) [@​jetersen](https://togithub.com/jetersen) - add dev container ([#​1002](https://togithub.com/release-drafter/release-drafter/issues/1002)) [@​jetersen](https://togithub.com/jetersen) #### Documentation - Add commitish configuration option to schema.json ([#​989](https://togithub.com/release-drafter/release-drafter/issues/989)) [@​menno-ll](https://togithub.com/menno-ll) - Added a gray stroke around the letters of the logo ([#​940](https://togithub.com/release-drafter/release-drafter/issues/940)) [@​ThomasKasene](https://togithub.com/ThomasKasene) - Provide autolabeler configuration for forks ([#​948](https://togithub.com/release-drafter/release-drafter/issues/948)) [@​gilbertsoft](https://togithub.com/gilbertsoft) - Docs: Clarify config must be on default branch ([#​836](https://togithub.com/release-drafter/release-drafter/issues/836)) [@​JustinGrote](https://togithub.com/JustinGrote) - Ultra tiny typo fix ([#​881](https://togithub.com/release-drafter/release-drafter/issues/881)) [@​jensenbox](https://togithub.com/jensenbox) #### Dependency Updates - Bump node from 15.11.0-alpine to 17.3.1-alpine ([#​1007](https://togithub.com/release-drafter/release-drafter/issues/1007)) [@​dependabot](https://togithub.com/dependabot) - Bump path-parse from 1.0.6 to 1.0.7 ([#​919](https://togithub.com/release-drafter/release-drafter/issues/919)) [@​dependabot](https://togithub.com/dependabot) - Bump url-parse from 1.4.4 to 1.5.4 ([#​1009](https://togithub.com/release-drafter/release-drafter/issues/1009)) [@​dependabot](https://togithub.com/dependabot) - Bump semver-regex from 3.1.2 to 3.1.3 ([#​945](https://togithub.com/release-drafter/release-drafter/issues/945)) [@​dependabot](https://togithub.com/dependabot) - Bump normalize-url from 4.5.0 to 4.5.1 ([#​890](https://togithub.com/release-drafter/release-drafter/issues/890)) [@​dependabot](https://togithub.com/dependabot) - Bump tmpl from 1.0.4 to 1.0.5 ([#​946](https://togithub.com/release-drafter/release-drafter/issues/946)) [@​dependabot](https://togithub.com/dependabot) - Bump cirrus-actions/rebase from 1.4 to 1.5 ([#​867](https://togithub.com/release-drafter/release-drafter/issues/867)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​vercel/ncc](https://togithub.com/vercel/ncc) from 0.27.0 to 0.28.4 ([#​855](https://togithub.com/release-drafter/release-drafter/issues/855)) [@​dependabot](https://togithub.com/dependabot) - Bump y18n from 4.0.0 to 4.0.1 ([#​828](https://togithub.com/release-drafter/release-drafter/issues/828)) [@​dependabot](https://togithub.com/dependabot) ### [`v5.15.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.15.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.14.0...v5.15.0) ### What's Changed #### New - Configurable functionality ([#​789](https://togithub.com/release-drafter/release-drafter/issues/789)) [@​rofafor](https://togithub.com/rofafor) #### Documentation - Clarify how autolabeler matcher configurations work ([#​806](https://togithub.com/release-drafter/release-drafter/issues/806)) [@​oleg-nenashev](https://togithub.com/oleg-nenashev) #### Dependency Updates - Bump node from `c01b572` to `1aa4d55` ([#​815](https://togithub.com/release-drafter/release-drafter/issues/815)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 7.21.0 to 7.22.0 ([#​814](https://togithub.com/release-drafter/release-drafter/issues/814)) [@​dependabot](https://togithub.com/dependabot) - Bump lodash from 4.17.20 to 4.17.21 ([#​791](https://togithub.com/release-drafter/release-drafter/issues/791)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 11.0.6 to 11.1.0 ([#​801](https://togithub.com/release-drafter/release-drafter/issues/801)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-config-prettier from 7.2.0 to 8.1.0 ([#​797](https://togithub.com/release-drafter/release-drafter/issues/797)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 7.20.0 to 7.21.0 ([#​799](https://togithub.com/release-drafter/release-drafter/issues/799)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 15.8.0-alpine to 15.11.0-alpine ([#​805](https://togithub.com/release-drafter/release-drafter/issues/805)) [@​dependabot](https://togithub.com/dependabot) - Bump nock from 13.0.7 to 13.0.11 ([#​809](https://togithub.com/release-drafter/release-drafter/issues/809)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 7.19.0 to 7.20.0 ([#​787](https://togithub.com/release-drafter/release-drafter/issues/787)) [@​dependabot](https://togithub.com/dependabot) ### [`v5.14.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.14.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.13.0...v5.14.0) ### What's Changed #### New - Add autolabeler ([#​774](https://togithub.com/release-drafter/release-drafter/issues/774)) [@​rofafor](https://togithub.com/rofafor) - Add commitish to config ([#​771](https://togithub.com/release-drafter/release-drafter/issues/771)) [@​subsetpark](https://togithub.com/subsetpark) - Container compatibility ([#​770](https://togithub.com/release-drafter/release-drafter/issues/770)) [@​rofafor](https://togithub.com/rofafor) - Category templating ([#​767](https://togithub.com/release-drafter/release-drafter/issues/767)) [@​rofafor](https://togithub.com/rofafor) #### Maintenance - use probot github adapter ([#​782](https://togithub.com/release-drafter/release-drafter/issues/782)) [@​jetersen](https://togithub.com/jetersen) #### Documentation - Fix syntax on example snippet ([#​779](https://togithub.com/release-drafter/release-drafter/issues/779)) [@​ignasi35](https://togithub.com/ignasi35) #### Dependency Updates - Bump node from `5755177` to `db3f9c8` ([#​778](https://togithub.com/release-drafter/release-drafter/issues/778)) [@​dependabot](https://togithub.com/dependabot) - Bump lint-staged from 10.5.3 to 10.5.4 ([#​777](https://togithub.com/release-drafter/release-drafter/issues/777)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 11.0.4 to 11.0.6 ([#​776](https://togithub.com/release-drafter/release-drafter/issues/776)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 15.5.1-alpine to 15.8.0-alpine ([#​775](https://togithub.com/release-drafter/release-drafter/issues/775)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 7.18.0 to 7.19.0 ([#​773](https://togithub.com/release-drafter/release-drafter/issues/773)) [@​dependabot](https://togithub.com/dependabot) - Bump nock from 13.0.5 to 13.0.7 ([#​772](https://togithub.com/release-drafter/release-drafter/issues/772)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-config-prettier from 7.1.0 to 7.2.0 ([#​762](https://togithub.com/release-drafter/release-drafter/issues/762)) [@​dependabot](https://togithub.com/dependabot) - Bump husky from 4.3.7 to 4.3.8 ([#​761](https://togithub.com/release-drafter/release-drafter/issues/761)) [@​dependabot](https://togithub.com/dependabot) - Bump probot from 11.0.1 to 11.0.4 ([#​760](https://togithub.com/release-drafter/release-drafter/issues/760)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 7.17.0 to 7.18.0 ([#​759](https://togithub.com/release-drafter/release-drafter/issues/759)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​vercel/ncc](https://togithub.com/vercel/ncc) from 0.26.2 to 0.27.0 ([#​755](https://togithub.com/release-drafter/release-drafter/issues/755)) [@​dependabot](https://togithub.com/dependabot) - Bump nodemon from 2.0.6 to 2.0.7 ([#​752](https://togithub.com/release-drafter/release-drafter/issues/752)) [@​dependabot](https://togithub.com/dependabot) - Bump husky from 4.3.6 to 4.3.7 ([#​753](https://togithub.com/release-drafter/release-drafter/issues/753)) [@​dependabot](https://togithub.com/dependabot) - Bump [@​vercel/ncc](https://togithub.com/vercel/ncc) from 0.26.1 to 0.26.2 ([#​754](https://togithub.com/release-drafter/release-drafter/issues/754)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 15.5.0-alpine to 15.5.1-alpine ([#​751](https://togithub.com/release-drafter/release-drafter/issues/751)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint-plugin-prettier from 3.3.0 to 3.3.1 ([#​750](https://togithub.com/release-drafter/release-drafter/issues/750)) [@​dependabot](https://togithub.com/dependabot) - Bump eslint from 7.16.0 to 7.17.0 ([#​749](https://togithub.com/release-drafter/release-drafter/issues/749)) [@​dependabot](https://togithub.com/dependabot) - Create Dependabot config file ([#​748](https://togithub.com/release-drafter/release-drafter/issues/748)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump husky from 4.2.5 to 4.3.6 ([#​747](https://togithub.com/release-drafter/release-drafter/issues/747)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint-config-prettier from 6.11.0 to 7.1.0 ([#​746](https://togithub.com/release-drafter/release-drafter/issues/746)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint-plugin-prettier from 3.1.4 to 3.3.0 ([#​745](https://togithub.com/release-drafter/release-drafter/issues/745)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump semver from 7.3.2 to 7.3.4 ([#​744](https://togithub.com/release-drafter/release-drafter/issues/744)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump minimist from 1.2.0 to 1.2.5 ([#​743](https://togithub.com/release-drafter/release-drafter/issues/743)) [@​dependabot-preview](https://togithub.com/dependabot-preview) ### [`v5.13.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.13.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.12.1...v5.13.0) ### What's Changed #### New - improve GitHub Action build times ([#​742](https://togithub.com/release-drafter/release-drafter/issues/742)) [@​jetersen](https://togithub.com/jetersen) - Allow more than just a push event to run the action ([#​632](https://togithub.com/release-drafter/release-drafter/issues/632)) [@​MindaugasLaganeckas](https://togithub.com/MindaugasLaganeckas) - Filter releases to consider only the target branch ([#​683](https://togithub.com/release-drafter/release-drafter/issues/683)) [@​fujifish](https://togithub.com/fujifish) #### Documentation - fix(readme): replace version resolution with version resolver ([#​701](https://togithub.com/release-drafter/release-drafter/issues/701)) [@​abarghoud](https://togithub.com/abarghoud) #### Dependency Updates - Bump probot from 9.11.5 to 11.0.1 ([#​735](https://togithub.com/release-drafter/release-drafter/issues/735)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump node-notifier from 8.0.0 to 8.0.1 ([#​739](https://togithub.com/release-drafter/release-drafter/issues/739)) [@​dependabot](https://togithub.com/dependabot) - Bump node from 14.14.0-alpine to 15.5.0-alpine ([#​738](https://togithub.com/release-drafter/release-drafter/issues/738)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint from 7.2.0 to 7.16.0 ([#​736](https://togithub.com/release-drafter/release-drafter/issues/736)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump ini from 1.3.5 to 1.3.8 ([#​731](https://togithub.com/release-drafter/release-drafter/issues/731)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.11 to 10.5.3 ([#​726](https://togithub.com/release-drafter/release-drafter/issues/726)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump prettier from 2.0.5 to 2.2.1 ([#​722](https://togithub.com/release-drafter/release-drafter/issues/722)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump nock from 12.0.3 to 13.0.5 ([#​703](https://togithub.com/release-drafter/release-drafter/issues/703)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump jest from 26.0.1 to 26.6.3 ([#​699](https://togithub.com/release-drafter/release-drafter/issues/699)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump nodemon from 2.0.4 to 2.0.6 ([#​685](https://togithub.com/release-drafter/release-drafter/issues/685)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lodash from 4.17.19 to 4.17.20 ([#​621](https://togithub.com/release-drafter/release-drafter/issues/621)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump node-fetch from 2.6.0 to 2.6.1 ([#​644](https://togithub.com/release-drafter/release-drafter/issues/644)) [@​dependabot](https://togithub.com/dependabot) - Bump smee-client from 1.1.0 to 1.2.2 ([#​623](https://togithub.com/release-drafter/release-drafter/issues/623)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump regex-parser from 2.2.10 to 2.2.11 ([#​616](https://togithub.com/release-drafter/release-drafter/issues/616)) [@​dependabot-preview](https://togithub.com/dependabot-preview) ### [`v5.12.1`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.12.1) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.12.0...v5.12.1) ### What's Changed #### Bug Fixes - Revert "filter releases to consider only the target branch " ([#​682](https://togithub.com/release-drafter/release-drafter/issues/682)) [@​jetersen](https://togithub.com/jetersen) ### [`v5.12.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.12.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.11.0...v5.12.0) ### What's Changed #### New - filter releases to consider only the target branch ([#​657](https://togithub.com/release-drafter/release-drafter/issues/657)) [@​fujifish](https://togithub.com/fujifish) - Allow providing a list of characters to escape in PR titles ([#​611](https://togithub.com/release-drafter/release-drafter/issues/611)) [@​Happypig375](https://togithub.com/Happypig375) - Allow adding the URL of the PR to the release notes ([#​582](https://togithub.com/release-drafter/release-drafter/issues/582)) [@​b00men](https://togithub.com/b00men) #### Bug Fixes - Annotated tags support when publishing ([#​676](https://togithub.com/release-drafter/release-drafter/issues/676)) [@​timja](https://togithub.com/timja) - Make the generated schema compliant with draft04 ([#​600](https://togithub.com/release-drafter/release-drafter/issues/600)) [@​PierreBtz](https://togithub.com/PierreBtz) #### Documentation - Update example in README to use version resolution ([#​571](https://togithub.com/release-drafter/release-drafter/issues/571)) [@​bpg](https://togithub.com/bpg) #### Dependency Updates - Bump node from 14.4.0-alpine to 14.14.0-alpine ([#​677](https://togithub.com/release-drafter/release-drafter/issues/677)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump [@​actions/core](https://togithub.com/actions/core) from 1.2.4 to 1.2.6 ([#​653](https://togithub.com/release-drafter/release-drafter/issues/653)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump lodash from 4.17.15 to 4.17.19 ([#​589](https://togithub.com/release-drafter/release-drafter/issues/589)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.9 to 10.2.11 ([#​570](https://togithub.com/release-drafter/release-drafter/issues/570)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint-plugin-prettier from 3.1.3 to 3.1.4 ([#​567](https://togithub.com/release-drafter/release-drafter/issues/567)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint from 7.1.0 to 7.2.0 ([#​562](https://togithub.com/release-drafter/release-drafter/issues/562)) [@​dependabot-preview](https://togithub.com/dependabot-preview) ### [`v5.11.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.11.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.9.0...v5.11.0) ### What's Changed #### New - Support triggable references (branches, tags). Ignore triggable references on GitHub action ([#​546](https://togithub.com/release-drafter/release-drafter/issues/546)) [@​jetersen](https://togithub.com/jetersen) - prerelease config override by input ([#​472](https://togithub.com/release-drafter/release-drafter/issues/472)) [@​sergioflores-j](https://togithub.com/sergioflores-j) - Use created_at tag date instead of published_at ([#​541](https://togithub.com/release-drafter/release-drafter/issues/541)) [@​darkdreamingdan](https://togithub.com/darkdreamingdan) #### Bug Fixes - fix outputs config in action.yml ([#​560](https://togithub.com/release-drafter/release-drafter/issues/560)) [@​jetersen](https://togithub.com/jetersen) - fix actions not recognizing input ([#​543](https://togithub.com/release-drafter/release-drafter/issues/543)) [@​jetersen](https://togithub.com/jetersen) #### Maintenance - use `toMatchInlineSnapshot` ([#​547](https://togithub.com/release-drafter/release-drafter/issues/547)) [@​jetersen](https://togithub.com/jetersen) - dog food release-drafter ([#​544](https://togithub.com/release-drafter/release-drafter/issues/544)) [@​jetersen](https://togithub.com/jetersen) #### Dependency Updates - Bump node from 14.3.0-alpine to 14.4.0-alpine ([#​553](https://togithub.com/release-drafter/release-drafter/issues/553)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.8 to 10.2.9 ([#​557](https://togithub.com/release-drafter/release-drafter/issues/557)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump probot from 9.11.4 to 9.11.5 ([#​556](https://togithub.com/release-drafter/release-drafter/issues/556)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.7 to 10.2.8 ([#​555](https://togithub.com/release-drafter/release-drafter/issues/555)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.6 to 10.2.7 ([#​539](https://togithub.com/release-drafter/release-drafter/issues/539)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump probot from 9.11.3 to 9.11.4 ([#​540](https://togithub.com/release-drafter/release-drafter/issues/540)) [@​dependabot-preview](https://togithub.com/dependabot-preview) ### [`v5.9.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.9.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.8.0...v5.9.0) ### What's Changed #### New - Add generated release body on output ([#​462](https://togithub.com/release-drafter/release-drafter/issues/462)) [@​sergioflores-j](https://togithub.com/sergioflores-j) - Set the release name and tag_name as outputs of the action ([#​538](https://togithub.com/release-drafter/release-drafter/issues/538)) [@​alonsohki](https://togithub.com/alonsohki) - Add support for automatic version resolution ([#​513](https://togithub.com/release-drafter/release-drafter/issues/513)) [@​darkdreamingdan](https://togithub.com/darkdreamingdan) - Allow adding the body of the PR to the release ([#​531](https://togithub.com/release-drafter/release-drafter/issues/531)) [@​gavinlove](https://togithub.com/gavinlove) - Add include-labels option ([#​500](https://togithub.com/release-drafter/release-drafter/issues/500)) [@​kreyusb](https://togithub.com/kreyusb) #### Bug Fixes - Filter PRs from forks ([#​251](https://togithub.com/release-drafter/release-drafter/issues/251)) [@​TimonVS](https://togithub.com/TimonVS) #### Maintenance - add lint ci ([#​535](https://togithub.com/release-drafter/release-drafter/issues/535)) [@​jetersen](https://togithub.com/jetersen) - fix eslint/prettier styling ([#​532](https://togithub.com/release-drafter/release-drafter/issues/532)) [@​jetersen](https://togithub.com/jetersen) #### Documentation - Document GitHub action inputs ([#​536](https://togithub.com/release-drafter/release-drafter/issues/536)) [@​dfreeman](https://togithub.com/dfreeman) #### Dependency Updates - Bump eslint from 7.0.0 to 7.1.0 ([#​534](https://togithub.com/release-drafter/release-drafter/issues/534)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.4 to 10.2.6 ([#​530](https://togithub.com/release-drafter/release-drafter/issues/530)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump node from 13.10.1-alpine to 14.3.0-alpine ([#​529](https://togithub.com/release-drafter/release-drafter/issues/529)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.2.2 to 10.2.4 ([#​528](https://togithub.com/release-drafter/release-drafter/issues/528)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint from 6.8.0 to 7.0.0 ([#​525](https://togithub.com/release-drafter/release-drafter/issues/525)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump nodemon from 2.0.3 to 2.0.4 ([#​526](https://togithub.com/release-drafter/release-drafter/issues/526)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump [@​actions/core](https://togithub.com/actions/core) from 1.2.3 to 1.2.4 ([#​523](https://togithub.com/release-drafter/release-drafter/issues/523)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump acorn from 7.1.0 to 7.2.0 ([#​522](https://togithub.com/release-drafter/release-drafter/issues/522)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint-plugin-prettier from 3.1.2 to 3.1.3 ([#​521](https://togithub.com/release-drafter/release-drafter/issues/521)) [@​dependabot-preview](https://togithub.com/dependabot-preview) ### [`v5.8.0`](https://togithub.com/release-drafter/release-drafter/releases/tag/v5.8.0) [Compare Source](https://togithub.com/release-drafter/release-drafter/compare/v5.7.0...v5.8.0) ### What's Changed #### New - Allow creating published releases ([#​424](https://togithub.com/release-drafter/release-drafter/issues/424)) [@​dfreeman](https://togithub.com/dfreeman) #### Bug Fixes - fix setOutput ([#​445](https://togithub.com/release-drafter/release-drafter/issues/445)) [@​jetersen](https://togithub.com/jetersen) #### Maintenance - fix line endings to lf ([#​476](https://togithub.com/release-drafter/release-drafter/issues/476)) [@​jetersen](https://togithub.com/jetersen) #### Documentation - Fixing test description typo ([#​439](https://togithub.com/release-drafter/release-drafter/issues/439)) [@​ianwalter](https://togithub.com/ianwalter) #### Dependency Updates - Bump jest from 25.1.0 to 26.0.1 ([#​512](https://togithub.com/release-drafter/release-drafter/issues/512)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump husky from 4.2.3 to 4.2.5 ([#​482](https://togithub.com/release-drafter/release-drafter/issues/482)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump nodemon from 2.0.2 to 2.0.3 ([#​480](https://togithub.com/release-drafter/release-drafter/issues/480)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump escape-string-regexp from 2.0.0 to 4.0.0 ([#​502](https://togithub.com/release-drafter/release-drafter/issues/502)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 10.0.8 to 10.2.2 ([#​505](https://togithub.com/release-drafter/release-drafter/issues/505)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump cli-table3 from 0.5.1 to 0.6.0 ([#​467](https://togithub.com/release-drafter/release-drafter/issues/467)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump prettier from 1.19.1 to 2.0.5 ([#​497](https://togithub.com/release-drafter/release-drafter/issues/497)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint-config-prettier from 6.10.0 to 6.11.0 ([#​496](https://togithub.com/release-drafter/release-drafter/issues/496)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump semver from 7.1.3 to 7.3.2 ([#​487](https://togithub.com/release-drafter/release-drafter/issues/487)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump probot from 9.10.2 to 9.11.3 ([#​484](https://togithub.com/release-drafter/release-drafter/issues/484)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump nock from 12.0.2 to 12.0.3 ([#​448](https://togithub.com/release-drafter/release-drafter/issues/448)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump probot from 9.10.1 to 9.10.2 ([#​447](https://togithub.com/release-drafter/release-drafter/issues/447)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump acorn from 6.1.1 to 6.4.1 ([#​446](https://togithub.com/release-drafter/release-drafter/issues/446)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump jest from 24.9.0 to 25.1.0 ([#​392](https://togithub.com/release-drafter/release-drafter/issues/392)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - \[Security] Bump acorn from 5.7.3 to 5.7.4 ([#​444](https://togithub.com/release-drafter/release-drafter/issues/444)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump lint-staged from 9.5.0 to 10.0.8 ([#​431](https://togithub.com/release-drafter/release-drafter/issues/431)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump request from 2.88.0 to 2.88.2 ([#​419](https://togithub.com/release-drafter/release-drafter/issues/419)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump compare-versions from 3.5.1 to 3.6.0 ([#​425](https://togithub.com/release-drafter/release-drafter/issues/425)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump eslint-config-prettier from 6.9.0 to 6.10.0 ([#​402](https://togithub.com/release-drafter/release-drafter/issues/402)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump [@​actions/core](https://togithub.com/actions/core) from 1.2.0 to 1.2.3 ([#​434](https://togithub.com/release-drafter/release-drafter/issues/434)) [@​dependabot-preview](https://togithub.com/dependabot-preview) - Bump nock from 11.7.2 to 12.0.2 ([#​433](https://togithub.com/release-drafter/release-drafter/issues/433)) [@​dependabot-preview](https://togithub.com/dependabot-previ
--- ### Configuration 📅 **Schedule**: Branch creation - "every weekend" in timezone Etc/UTC, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View the [repository job log](https://developer.mend.io/github/canonical/snapcraft). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release-drafter.yml | 2 +- .github/workflows/tox.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index d2e8e70ae0..ce63191800 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -11,6 +11,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Release Drafter - uses: release-drafter/release-drafter@v5.7.0 + uses: release-drafter/release-drafter@v6.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 017367d8fc..abfdf47baa 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -66,7 +66,7 @@ jobs: - name: Test with tox run: tox run --skip-pkg-install --result-json results/tox-ubuntu-22.04.json --colored yes -e test-py310,test-legacy-py310 - name: Upload code coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: directory: ./results/ files: coverage*.xml From 5cd480725954baa26194728d51c9210e64c4c917 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 21 Aug 2024 12:33:41 -0500 Subject: [PATCH 050/151] fix(remotebuild): parse '--build-for' and '--platform' * '--build-for' and '--platform' can only be used when 'platforms' and 'architectures' are not defined in the project metadata * '--platform' cannot be used for core22 snaps Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 1 + snapcraft/commands/remote.py | 113 +++++--- snapcraft/models/project.py | 11 +- .../snaps/platforms/snapcraft.yaml | 12 +- tests/spread/core24/remote-build/task.yaml | 5 +- tests/unit/commands/test_remote.py | 268 ++++++++++-------- tests/unit/models/test_projects.py | 24 ++ tests/unit/parts/test_lifecycle.py | 15 +- 8 files changed, 293 insertions(+), 156 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index 29a9f1683d..4e13807354 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -54,6 +54,7 @@ source_ignore_patterns=["*.snap"], project_variables=["version", "grade"], mandatory_adoptable_fields=["version", "summary", "description"], + docs_url="https://canonical-snapcraft.readthedocs-hosted.com/en/{version}", ) diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index 1987bfbfd2..6ff2101163 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -26,11 +26,11 @@ import lazr.restfulclient.errors from craft_application import errors -from craft_application.application import filter_plan from craft_application.commands import ExtensibleCommand from craft_application.errors import RemoteBuildError from craft_application.launchpad.models import Build, BuildState from craft_application.remote.utils import get_build_id +from craft_application.util import humanize_list from craft_cli import emit from overrides import overrides @@ -106,6 +106,9 @@ def _fill_parser(self, parser: argparse.ArgumentParser) -> None: metavar="name", default=os.getenv("CRAFT_PLATFORM"), help="Set platform to build for", + # '--platform' needs to be handled differently since remote-build can + # build for an architecture that is not in the project metadata + dest="remote_build_platform", ) group.add_argument( "--build-for", @@ -113,6 +116,9 @@ def _fill_parser(self, parser: argparse.ArgumentParser) -> None: metavar="arch", default=os.getenv("CRAFT_BUILD_FOR"), help="Set architecture to build for", + # '--build-for' needs to be handled differently since remote-build can + # build for architecture that is not in the project metadata + dest="remote_build_build_for", ) parser.add_argument( "--project", help="upload to the specified Launchpad project" @@ -143,8 +149,56 @@ def _validate(self, parsed_args: argparse.Namespace) -> None: "In non-interactive runs, please use the option " "`--launchpad-accept-public-upload`." ), + doc_slug="/explanation/remote-build.html", reportable=False, - retcode=77, + retcode=os.EX_NOPERM, + ) + + build_for = parsed_args.remote_build_build_for or None + platform = parsed_args.remote_build_platform or None + parameter = "--build-for" if build_for else "--platform" if platform else None + keyword = ( + "architectures" + if self.project._architectures_in_yaml + else "platforms" if self.project.platforms else None + ) + + if keyword and parameter: + raise errors.RemoteBuildError( + f"{parameter!r} cannot be used when {keyword!r} is in the snapcraft.yaml.", + resolution=f"Remove {parameter!r} from the command line or remove {keyword!r} in the snapcraft.yaml.", + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, + ) + + if platform: + if self.project.get_effective_base() == "core22": + raise errors.RemoteBuildError( + "'--platform' cannot be used for core22 snaps.", + resolution="Use '--build-for' instead.", + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, + ) + if platform not in SUPPORTED_ARCHS: + raise errors.RemoteBuildError( + f"Unsupported platform {parsed_args.remote_build_platform!r}.", + resolution=( + "Use a supported debian architecture. Supported " + f"architectures are: {humanize_list(SUPPORTED_ARCHS, 'and')}" + ), + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, + ) + + if build_for and build_for not in SUPPORTED_ARCHS: + raise errors.RemoteBuildError( + f"Unsupported build-for architecture {parsed_args.remote_build_build_for!r}.", + resolution=( + "Use a supported debian architecture. Supported " + f"architectures are: {humanize_list(SUPPORTED_ARCHS, 'and')}" + ), + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, ) def _run( # noqa: PLR0915 [too-many-statements] @@ -159,14 +213,13 @@ def _run( # noqa: PLR0915 [too-many-statements] if parsed_args.project: self._services.remote_build.set_project(parsed_args.project) - self._validate(parsed_args) emit.progress( "remote-build is experimental and is subject to change. Use with caution.", permanent=True, ) builder = self._services.remote_build - project = cast(models.Project, self._services.project) + self.project = cast(models.Project, self._services.project) config = cast(dict[str, Any], self.config) project_dir = ( Path(config.get("global_args", {}).get("project_dir") or ".") @@ -175,41 +228,22 @@ def _run( # noqa: PLR0915 [too-many-statements] ) emit.trace(f"Project directory: {project_dir}") + self._validate(parsed_args) - possible_build_plan = filter_plan( - self._app.BuildPlannerClass.unmarshal(project.marshal()).get_build_plan(), - platform=parsed_args.platform, - build_for=parsed_args.build_for, - host_arch=None, - ) - - architectures: list[str] | None = list( - {build_info.build_for for build_info in possible_build_plan} - ) - - if parsed_args.platform and not architectures: - emit.progress( - f"platform '{parsed_args.platform}' is not present in the build plan.", - permanent=True, - ) - return 78 # Configuration error - - if parsed_args.build_for and not architectures: - if parsed_args.build_for not in SUPPORTED_ARCHS: - raise errors.RemoteBuildError( - f"build-for '{parsed_args.build_for}' is not supported.", retcode=78 - ) - # Allow the user to build for a single architecture if snapcraft.yaml - # doesn't define architectures. - architectures = [parsed_args.build_for] + if parsed_args.remote_build_build_for: + architectures = [parsed_args.remote_build_build_for] + elif parsed_args.remote_build_platform: + architectures = [parsed_args.remote_build_platform] + else: + architectures = self._get_project_build_fors() - emit.debug(f"Architectures to build: {architectures}") + emit.debug(f"Architectures to build for: {architectures}") if parsed_args.launchpad_timeout: emit.debug(f"Setting timeout to {parsed_args.launchpad_timeout} seconds") builder.set_timeout(parsed_args.launchpad_timeout) - build_id = get_build_id(self._app.name, project.name, project_dir) + build_id = get_build_id(self._app.name, self.project.name, project_dir) if parsed_args.recover: emit.progress(f"Recovering build {build_id}") builds = builder.resume_builds(build_id) @@ -325,3 +359,18 @@ def _monitor_and_complete( # noqa: PLR0912, PLR0915 f"Artifacts: {', '.join(artifact_names)}" ) return return_code + + def _get_project_build_fors(self) -> list[str]: + """Get a unique list of build-for architectures from the project. + + :returns: A list of architectures. + """ + build_plan = self._app.BuildPlannerClass.unmarshal( + self.project.marshal() + ).get_build_plan() + emit.debug(f"Build plan: {build_plan}") + + build_fors = set({build_info.build_for for build_info in build_plan}) + emit.debug(f"Parsed build-for architectures from build plan: {build_fors}") + + return list(build_fors) diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index b13c88584b..3642a2f795 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -609,6 +609,7 @@ class Project(models.Project): ) = None grade: Literal["stable", "devel"] | None = None architectures: list[str | Architecture] | None = None + _architectures_in_yaml: bool | None = None platforms: dict[str, Platform] | None = None # type: ignore[assignment,reportIncompatibleVariableOverride] assumes: UniqueList[str] = pydantic.Field(default_factory=list) hooks: dict[str, Hook] | None = None @@ -759,8 +760,14 @@ def _validate_platforms_and_architectures(self) -> Self: f"'platforms' keyword is not supported for base {base!r}. " "Use 'architectures' keyword instead." ) - # set default value - if not self.architectures: + + # this is a one-shot - the value should not change when re-validating + if self.architectures and self._architectures_in_yaml is None: + # record if architectures are defined in the yaml for remote-build (#4881) + self._architectures_in_yaml = True + elif not self.architectures: + self._architectures_in_yaml = False + # set default value self.architectures = [ Architecture( build_on=[get_host_architecture()], diff --git a/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml b/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml index e32f72cb87..14d3541c22 100644 --- a/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml +++ b/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml @@ -7,16 +7,20 @@ description: Test snap for remote build grade: stable confinement: strict +# This does not test: +# - multiple artefacts on the same build-on (#4995) +# - cross-compiling (#4996) + platforms: # implicit build-on and build-for amd64: # implicit build-for - armhf: + arm64: build-on: [arm64] # fully defined - arm64: - build-on: [amd64, arm64] - build-for: [arm64] + armhf: + build-on: [armhf] + build-for: [armhf] parts: my-part: diff --git a/tests/spread/core24/remote-build/task.yaml b/tests/spread/core24/remote-build/task.yaml index 4ab305f8c4..14f7d0ca54 100644 --- a/tests/spread/core24/remote-build/task.yaml +++ b/tests/spread/core24/remote-build/task.yaml @@ -8,9 +8,8 @@ systems: environment: LAUNCHPAD_TOKEN: "$(HOST: echo ${LAUNCHPAD_TOKEN})" SNAP: no-platforms - # launchpad can't parse `platforms` (https://github.com/canonical/snapcraft/issues/4858) - #SNAP/all: all - #SNAP/platforms: platforms + SNAP/all: all + SNAP/platforms: platforms SNAP/no_platforms: no-platforms CREDENTIALS_FILE: "$HOME/.local/share/snapcraft/launchpad-credentials" CREDENTIALS_FILE/new_credentials: "$HOME/.local/share/snapcraft/launchpad-credentials" diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index f64e521a61..714eaa80c7 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -410,8 +410,8 @@ def test_run_in_shallow_repo_unsupported( ###################### -@pytest.mark.parametrize("base", const.CURRENT_BASES) -def test_no_platform_defined_no_platform_or_build_for( +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"devel"}) +def test_default_architecture( mocker, snapcraft_yaml, base, @@ -419,18 +419,9 @@ def test_no_platform_defined_no_platform_or_build_for( mock_confirm, mock_remote_builder_fake_build_process, ): - """Test that the remote-build command uses the host architecture if no platform is defined.""" - snapcraft_yaml_dict = { - "base": base, - "build-base": "devel", - "grade": "devel", - } - snapcraft_yaml(**snapcraft_yaml_dict) - mocker.patch.object( - sys, - "argv", - ["snapcraft", "remote-build"], - ) + """Default to the host architecture if not defined elsewhere.""" + snapcraft_yaml(base=base) + mocker.patch.object(sys, "argv", ["snapcraft", "remote-build"]) mock_start_builds = mocker.patch( "craft_application.services.remotebuild.RemoteBuildService.start_builds" ) @@ -442,7 +433,7 @@ def test_no_platform_defined_no_platform_or_build_for( ) -@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) def test_platform_build_for_all( mocker, snapcraft_yaml, @@ -451,10 +442,9 @@ def test_platform_build_for_all( mock_confirm, mock_remote_builder_fake_build_process, ): + """Use 'build-for: all' from the project metadata with the platforms keyword.""" snapcraft_yaml_dict = { "base": base, - "build-base": "devel", - "grade": "devel", "platforms": { "test-platform": {"build-on": "arm64", "build-for": "all"}, }, @@ -478,10 +468,9 @@ def test_platform_build_for_all_core22( mock_confirm, mock_remote_builder_fake_build_process, ): - """remote-build command uses all platforms if no platform or build-for is given.""" + """Use 'build-for: all' from the project metadata with the architectures keyword.""" snapcraft_yaml_dict = { "base": "core22", - "grade": "devel", "architectures": [ {"build-on": ["arm64"], "build-for": ["all"]}, ], @@ -498,8 +487,8 @@ def test_platform_build_for_all_core22( assert mock_start_builds.call_args[1]["architectures"] == ["all"] -@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) -def test_platform_defined_no_platform_or_build_for( +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) +def test_platform_in_project_metadata( mocker, snapcraft_yaml, base, @@ -507,14 +496,13 @@ def test_platform_defined_no_platform_or_build_for( mock_confirm, mock_remote_builder_fake_build_process, ): - """remote-build command uses all platforms if no platform or build-for is given.""" + """Use the platform's build-for architectures from the project metadata.""" snapcraft_yaml_dict = { "base": base, - "build-base": "devel", - "grade": "devel", "platforms": { "rpi4": {"build-on": "arm64", "build-for": "arm64"}, "x86-64": {"build-on": "amd64", "build-for": "amd64"}, + "same-build-for-x86-64": {"build-on": "riscv64", "build-for": "amd64"}, }, } snapcraft_yaml(**snapcraft_yaml_dict) @@ -534,36 +522,26 @@ def test_platform_defined_no_platform_or_build_for( assert const.SnapArch.amd64 in mock_start_builds.call_args[1]["architectures"] -@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) -@pytest.mark.parametrize( - ["platform", "arch"], - [("rpi4", const.SnapArch.arm64), ("x86-64", const.SnapArch.amd64)], -) -def test_platform( +def test_architecture_in_project_metadata( mocker, snapcraft_yaml, - base, fake_services, mock_confirm, mock_remote_builder_fake_build_process, - platform, - arch, ): - """Test that the remote-build command uses the platform if --platform is given.""" + """Use the build-for architectures from the project metadata.""" snapcraft_yaml_dict = { - "base": base, - "build-base": "devel", - "grade": "devel", - "platforms": { - "rpi4": {"build-on": "arm64", "build-for": "arm64"}, - "x86-64": {"build-on": "amd64", "build-for": "amd64"}, - }, + "base": "core22", + "architectures": [ + {"build-on": ["arm64"], "build-for": ["arm64"]}, + {"build-on": ["riscv64"], "build-for": ["riscv64"]}, + ], } snapcraft_yaml(**snapcraft_yaml_dict) mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--platform", platform], + ["snapcraft", "remote-build"], ) mock_start_builds = mocker.patch( "craft_application.services.remotebuild.RemoteBuildService.start_builds" @@ -571,51 +549,42 @@ def test_platform( app = application.create_app() app.run() - mock_start_builds.assert_called_once_with(ANY, architectures=[arch]) + mock_start_builds.assert_called_once() + assert sorted(mock_start_builds.call_args[1]["architectures"]) == sorted( + ["arm64", "riscv64"] + ) -@pytest.mark.xfail( - reason="craft-application catches before it gets to the remote-build command" -) -@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) -def test_platform_error( - emitter, +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) +@pytest.mark.parametrize("platform", const.SnapArch) +def test_platform_argument( mocker, snapcraft_yaml, base, fake_services, mock_confirm, + mock_remote_builder_fake_build_process, + platform, ): - """Test that the remote-build command errors if the given platform is not found.""" - snapcraft_yaml_dict = { - "base": base, - "build-base": "devel", - "grade": "devel", - "platforms": { - "rpi4": {"build-on": "arm64", "build-for": "arm64"}, - "x86-64": {"build-on": "amd64", "build-for": "amd64"}, - }, - } - snapcraft_yaml(**snapcraft_yaml_dict) + """Use architectures provided by the `--platform` argument.""" + snapcraft_yaml(base=base) mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--platform", "nonexistent"], + ["snapcraft", "remote-build", "--platform", platform], ) - - app = application.create_app() - assert app.run() == 1 - emitter.assert_progress( - "Platform 'nonexistent' not found in the project definition.", permanent=True + mock_start_builds = mocker.patch( + "craft_application.services.remotebuild.RemoteBuildService.start_builds" ) + app = application.create_app() + app.run() + mock_start_builds.assert_called_once_with(ANY, architectures=[platform]) -@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) -@pytest.mark.parametrize( - ["build_for", "arch"], - [("amd64", const.SnapArch.amd64), ("arm64", const.SnapArch.arm64)], -) -def test_build_for( + +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"devel"}) +@pytest.mark.parametrize("build_for", const.SnapArch) +def test_build_for_argument( mocker, snapcraft_yaml, base, @@ -623,19 +592,9 @@ def test_build_for( mock_confirm, mock_remote_builder_fake_build_process, build_for, - arch, ): - """Test that the remote-build command uses the build-for if --build-for is given.""" - snapcraft_yaml_dict = { - "base": base, - "build-base": "devel", - "grade": "devel", - "platforms": { - "rpi4": {"build-on": "arm64", "build-for": "arm64"}, - "x86-64": {"build-on": "amd64", "build-for": "amd64"}, - }, - } - snapcraft_yaml(**snapcraft_yaml_dict) + """Use architectures provided by the `--build-for` argument.""" + snapcraft_yaml(base=base) mocker.patch.object( sys, "argv", @@ -647,66 +606,122 @@ def test_build_for( app = application.create_app() app.run() - mock_start_builds.assert_called_once_with(ANY, architectures=[arch]) + mock_start_builds.assert_called_once_with(ANY, architectures=[build_for]) -@pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.parametrize( - ["build_for", "arch"], - [("amd64", const.SnapArch.amd64), ("arm64", const.SnapArch.arm64)], -) -def test_build_for_no_platforms( +def test_architecture_defined_twice_error( + capsys, mocker, snapcraft_yaml, + fake_services, + mock_confirm, +): + """Error if architectures are in the project metadata and as a build argument.""" + snapcraft_yaml_dict = { + "base": "core22", + "architectures": [{"build-on": ["riscv64"], "build-for": ["riscv64"]}], + } + snapcraft_yaml(**snapcraft_yaml_dict) + mocker.patch.object( + sys, + "argv", + ["snapcraft", "remote-build", "--build-for", "riscv64"], + ) + app = application.create_app() + app.run() + + _, err = capsys.readouterr() + + assert ( + "'--build-for' cannot be used when 'architectures' is in the snapcraft.yaml." + ) in err + assert ( + "Remove '--build-for' from the command line or remove 'architectures' in the snapcraft.yaml." + ) in err + + +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) +@pytest.mark.parametrize("argument", ["--build-for", "--platform"]) +def test_platform_defined_twice_error( base, + argument, + capsys, + mocker, + snapcraft_yaml, fake_services, mock_confirm, - mock_remote_builder_fake_build_process, - build_for, - arch, ): - """remote-build command uses the build-for if --build-for is given without platforms.""" + """Error if platforms are in the project metadata and as a build argument.""" snapcraft_yaml_dict = { "base": base, - "build-base": "devel", - "grade": "devel", + "platforms": { + "riscv64": {"build-on": "riscv64", "build-for": "riscv64"}, + }, } snapcraft_yaml(**snapcraft_yaml_dict) mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--build-for", build_for], - ) - mock_start_builds = mocker.patch( - "craft_application.services.remotebuild.RemoteBuildService.start_builds" + ["snapcraft", "remote-build", argument, "riscv64"], ) app = application.create_app() app.run() - mock_start_builds.assert_called_once_with(ANY, architectures=[arch]) + _, err = capsys.readouterr() + + assert ( + f"{argument!r} cannot be used when 'platforms' is in the snapcraft.yaml." + ) in err + assert ( + f"Remove {argument!r} from the command line or remove 'platforms' in the snapcraft.yaml." + ) in err @pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) -def test_build_for_error( +def test_unknown_platform_error( capsys, mocker, snapcraft_yaml, base, fake_services, mock_confirm, - mock_remote_builder_fake_build_process, ): - """Test that the remote-build command errors if the given build-for is not found.""" + """Error if `--platform` is not a valid debian architecture.""" snapcraft_yaml_dict = { "base": base, "build-base": "devel", "grade": "devel", - "platforms": { - "rpi4": {"build-on": "arm64", "build-for": "arm64"}, - "x86-64": {"build-on": "amd64", "build-for": "amd64"}, - }, } snapcraft_yaml(**snapcraft_yaml_dict) + mocker.patch.object( + sys, + "argv", + ["snapcraft", "remote-build", "--platform", "nonexistent"], + ) + + app = application.create_app() + assert app.run() == os.EX_CONFIG + + _, err = capsys.readouterr() + assert "Unsupported platform 'nonexistent'" in err + assert ( + "Recommended resolution: Use a supported debian architecture. " + "Supported architectures are:" + ) in err + + +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) +def test_unknown_build_for_error( + capsys, + mocker, + snapcraft_yaml, + base, + fake_services, + mock_confirm, + mock_remote_builder_fake_build_process, +): + """Error if `--build-for` is not a valid debian architecture.""" + snapcraft_yaml(base=base) mocker.patch.object( sys, "argv", @@ -716,11 +731,42 @@ def test_build_for_error( "craft_application.services.remotebuild.RemoteBuildService.start_builds" ) app = application.create_app() - assert app.run() == 1 + assert app.run() == os.EX_CONFIG + + _, err = capsys.readouterr() + + assert "Unsupported build-for architecture 'nonexistent'" in err + assert ( + "Recommended resolution: Use a supported debian architecture. " + "Supported architectures are:" + ) in err + + +def test_platform_core22_error( + capsys, + mocker, + snapcraft_yaml, + fake_services, + mock_confirm, + mock_remote_builder_fake_build_process, +): + """Error on `--platform` for core22 snaps.""" + snapcraft_yaml(base="core22") + mocker.patch.object( + sys, + "argv", + ["snapcraft", "remote-build", "--platform", "amd64"], + ) + mocker.patch( + "craft_application.services.remotebuild.RemoteBuildService.start_builds" + ) + app = application.create_app() + assert app.run() == os.EX_CONFIG _, err = capsys.readouterr() - assert "Architecture 'nonexistent' is not supported." in err + assert "--platform' cannot be used for core22 snaps" in err + assert "Use '--build-for' instead." in err ######################## diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index 32e2e946d9..1cb147d676 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -1999,6 +1999,30 @@ def test_architectures_not_allowed(self, project_yaml_data): with pytest.raises(pydantic.ValidationError, match=error): Project.unmarshal(project_yaml_data(**CORE24_DATA, architectures=["amd64"])) + @pytest.mark.parametrize( + ("data", "expected"), + [ + ({"base": "core22", "architectures": ["amd64"]}, True), + ({"base": "core22"}, False), + # core24 and newer do not set this field + ({"base": "core24"}, None), + ], + ) + def test_architectures_in_yaml(self, project_yaml_data, data, expected): + """Check if architectures were present in the yaml before unmarshalling.""" + project_yaml = project_yaml_data(**data) + + project = Project.unmarshal(project_yaml) + + assert project._architectures_in_yaml is expected + + # adding architectures after unmarshalling does not change the field + if project.base == "core22": + project.architectures = [ + Architecture(build_on=["amd64"], build_for=["amd64"]) + ] + assert project._architectures_in_yaml is expected + class TestApplyRootPackages: """Test Transform the Project.""" diff --git a/tests/unit/parts/test_lifecycle.py b/tests/unit/parts/test_lifecycle.py index 0747c0070c..6f5b598b02 100644 --- a/tests/unit/parts/test_lifecycle.py +++ b/tests/unit/parts/test_lifecycle.py @@ -25,13 +25,14 @@ import pytest from craft_cli import EmitterMode, emit from craft_parts import Action, Features, ProjectInfo, Step, callbacks +from craft_platforms import DebianArchitecture from craft_providers.bases.ubuntu import BuilddBaseAlias from snapcraft import errors from snapcraft.elf import ElfFile from snapcraft.models import MANDATORY_ADOPTABLE_FIELDS, Project from snapcraft.parts import lifecycle as parts_lifecycle -from snapcraft.parts import set_global_environment +from snapcraft.parts import set_global_environment, yaml_utils from snapcraft.parts.plugins import KernelPlugin, MatterSdkPlugin from snapcraft.parts.update_metadata import update_project_metadata from snapcraft.utils import get_host_architecture @@ -146,7 +147,9 @@ def test_snapcraft_yaml_load(new_dir, snapcraft_yaml, filename, mocker): ), ) - project = Project.unmarshal(yaml_data) + arch = DebianArchitecture.from_host().value + applied_yaml = yaml_utils.apply_yaml(yaml_data, arch, arch) + project = Project.unmarshal(applied_yaml) if filename == "build-aux/snap/snapcraft.yaml": assets_dir = Path("build-aux/snap") @@ -198,7 +201,9 @@ def test_lifecycle_run_with_components( ), ) - project = Project.unmarshal(yaml_data) + arch = DebianArchitecture.from_host().value + applied_yaml = yaml_utils.apply_yaml(yaml_data, arch, arch) + project = Project.unmarshal(applied_yaml) assert mock_run_command.mock_calls == [ call( @@ -247,7 +252,9 @@ def test_lifecycle_run_no_components(new_dir, snapcraft_yaml, mocker): ), ) - project = Project.unmarshal(yaml_data) + arch = DebianArchitecture.from_host().value + applied_yaml = yaml_utils.apply_yaml(yaml_data, arch, arch) + project = Project.unmarshal(applied_yaml) assert mock_run_command.mock_calls == [ call( From 7ea9136c8c6f42f70ad1d7ebe25304199db5cddd Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 21 Aug 2024 12:36:42 -0500 Subject: [PATCH 051/151] docs: update remote builder explanation Signed-off-by: Callahan Kovacs --- docs/explanation/remote-build.rst | 48 +++++++++++++++++++------------ docs/reference/commands.rst | 2 ++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/docs/explanation/remote-build.rst b/docs/explanation/remote-build.rst index 360db6d4d9..8d287f9524 100644 --- a/docs/explanation/remote-build.rst +++ b/docs/explanation/remote-build.rst @@ -56,7 +56,8 @@ snaps. The legacy remote builder was deprecated because of its design. It retrieves and tarballs remote sources and modifies the project's ``snapcraft.yaml`` -file to point to the local tarballs. +file to point to the local tarballs. This caused many unexpected failures that +could not be reproduced locally. Choosing a remote-builder ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -84,37 +85,44 @@ Current ``--platform`` and ``--build-for`` ********************************** -If ``--platform`` or ``--build-for`` are provided, Snapcraft will: +.. note:: + ``--platform`` and ``--build-for`` behave differently than they do for + :ref:`lifecycle commands`. -#. parse the project metadata for ``platforms`` or ``architectures`` keywords -#. create a build plan -#. filter the build plan with whichever of ``--build-for`` or ``--platform`` - was specified +``--platform`` or ``--build-for`` can only be provided when the ``platforms`` +and ``architectures`` keywords are not defined in the project metadata +(`[12]`_). -``--build-for`` and ``--platform`` are mutually exclusive keywords. +These keywords are mutually exclusive and must be a single debian architecture. +A list of comma-separated architectures is not supported (`[11]`_). -If a ``--build-for`` or ``--platform`` is provided that does not exist in the -project metadata, Snapcraft will fail (`[1]`_). - -The ``--platform`` keyword applies to ``core22`` projects because Snapcraft -will convert ``architectures`` entries to a ``platforms`` object by using the -``build-for`` entry of the ``architectures`` entry as the platform name. +``core22`` snaps can only use ``--build-for``. ``core24`` and newer snaps +can use ``--platform`` or ``--build-for``. Project platforms and architectures *********************************** The ``snapcraft.yaml`` file is always parsed by the new remote builder. +If the project metadata contains a ``platforms`` or ``architectures`` entry, +Snapcraft will request a build for each unique ``build-for`` architecture. + +.. note:: + + Launchpad does not support cross-compiling (`[13]`_). + +.. note:: + + Launchpad does not support building multiple snaps on the same + ``build-on`` architecture (`[14]`_). + If the project metadata does not contain a ``platforms`` or ``architectures`` entry and no ``--build-for`` or ``--platform`` are passed, Snapcraft will -request a build for the host's architecture. +request a build on, and for, the host's architecture. The remote builder does not work for ``core20`` snaps because it cannot parse the ``run-on`` keyword in a ``core20`` architecture entry (`[2]`_). -The remote builder does not work as expected for most ``platform`` definitions -because Launchpad does not properly parse the entry (`[3]`_). - Legacy ^^^^^^ @@ -164,9 +172,7 @@ Launchpad is not able to parse this notation (`[9]`_). .. _`Launchpad account`: https://launchpad.net/+login .. _`Launchpad`: https://launchpad.net/ .. _`build farm`: https://launchpad.net/builders -.. _`[1]`: https://github.com/canonical/snapcraft/issues/4881 .. _`[2]`: https://github.com/canonical/snapcraft/issues/4842 -.. _`[3]`: https://github.com/canonical/snapcraft/issues/4858 .. _`[4]`: https://github.com/canonical/snapcraft/issues/4341 .. _`[5]`: https://bugs.launchpad.net/snapcraft/+bug/1885150 .. _`[6]`: https://github.com/canonical/snapcraft/issues/4144 @@ -174,3 +180,7 @@ Launchpad is not able to parse this notation (`[9]`_). .. _`[8]`: https://bugs.launchpad.net/snapcraft/+bug/2007789 .. _`[9]`: https://bugs.launchpad.net/snapcraft/+bug/2042167 .. _`[10]`: https://github.com/canonical/snapcraft/issues/4885 +.. _`[11]`: https://github.com/canonical/snapcraft/issues/4990 +.. _`[12]`: https://github.com/canonical/snapcraft/issues/4992 +.. _`[13]`: https://github.com/canonical/snapcraft/issues/4996 +.. _`[14]`: https://github.com/canonical/snapcraft/issues/4995 diff --git a/docs/reference/commands.rst b/docs/reference/commands.rst index 2faa5e9106..a5b6603291 100644 --- a/docs/reference/commands.rst +++ b/docs/reference/commands.rst @@ -5,6 +5,8 @@ Snapcraft commands .. include:: commands/toc.rst +.. _reference-lifecycle-commands: + Lifecycle --------- Lifecycle commands can take an optional parameter ````. When a part From aa54d3cb611263c099992b1bb7fb0527b796e7b9 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 19 Aug 2024 08:24:37 -0500 Subject: [PATCH 052/151] fix: freeze requirements for docs Signed-off-by: Callahan Kovacs --- tools/freeze-requirements.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/freeze-requirements.sh b/tools/freeze-requirements.sh index c33eea2153..22723c420a 100755 --- a/tools/freeze-requirements.sh +++ b/tools/freeze-requirements.sh @@ -40,6 +40,10 @@ pip install -e . pip freeze --exclude-editable > requirements.txt requirements_fixups "requirements.txt" +pip install -e .[docs] +pip freeze --exclude-editable > requirements-docs.txt +requirements_fixups "requirements-docs.txt" + # Set the configured python-apt and python-distutils-extra packages. pip install -e .[dev] pip freeze --exclude-editable > requirements-devel.txt From 37d2803aab1167d14147e90625ba9a144e709d37 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 19 Aug 2024 08:25:31 -0500 Subject: [PATCH 053/151] build(deps): freeze requirements for docs Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 72 ++++++++++++- .../requirements.txt => requirements-docs.txt | 102 ++++++++++-------- setup.py | 6 +- 3 files changed, 133 insertions(+), 47 deletions(-) rename docs/requirements.txt => requirements-docs.txt (61%) diff --git a/requirements-devel.txt b/requirements-devel.txt index 9c9107e6dc..3205215889 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,9 +1,20 @@ +alabaster==0.7.16 annotated-types==0.7.0 +anyio==4.4.0 +apeye==1.4.1 +apeye-core==1.1.5 astroid==3.2.4 attrs==24.2.0 +autodocsumm==0.2.13 +babel==2.16.0 +beautifulsoup4==4.12.3 black==24.8.0 boolean.py==4.0 -cachetools==5.4.0 +bracex==2.5 +CacheControl==0.14.0 +cachetools==5.5.0 +canonical-sphinx==0.1.0 +canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 certifi==2024.7.4 cffi==1.17.0 @@ -22,34 +33,53 @@ craft-platforms==0.1.1 craft-providers==2.0.0 craft-store==3.0.0 cryptography==43.0.0 +cssutils==2.11.1 +dict2css==0.3.0.post1 dill==0.3.8 distlib==0.3.8 distro==1.9.0 docutils==0.19 +domdf-python-tools==3.9.0 filelock==3.15.4 fixtures==4.1.0 flake8==7.1.1 +furo==2024.8.6 +gitdb==4.0.11 +GitPython==3.1.43 gnupg==2.3.1 +h11==0.14.0 +html5lib==1.1 httplib2==0.22.0 hupper==1.12.1 idna==3.7 +imagesize==1.4.1 importlib_metadata==8.2.0 iniconfig==2.0.0 isort==5.13.2 jaraco.classes==3.4.0 jeepney==0.8.0 +Jinja2==3.1.4 jsonschema==2.5.1 keyring==24.3.1 launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 license-expression==30.3.1 +linkify-it-py==2.0.3 lxml==5.3.0 macaroonbakery==1.3.4 +Markdown==3.7 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 mccabe==0.7.0 +mdit-py-plugins==0.4.1 +mdurl==0.1.2 more-itertools==10.4.0 +msgpack==1.0.8 mypy==1.11.1 mypy-extensions==1.0.0 +myst-parser==4.0.0 +natsort==8.4.0 oauthlib==3.2.2 overrides==7.7.0 packaging==24.1 @@ -61,6 +91,7 @@ plaster==1.1.2 plaster-pastedeploy==1.0.1 platformdirs==4.2.2 pluggy==1.5.0 +polib==1.2.0 progressbar==2.5 protobuf==5.27.3 psutil==6.0.0 @@ -75,6 +106,7 @@ pyelftools==0.31 pyflakes==3.2.0 pyftpdlib==1.5.10 pygit2==1.13.3 +Pygments==2.18.0 pylint==3.2.6 pylxd==2.3.4 pymacaroons==0.13.0 @@ -83,6 +115,7 @@ pyparsing==3.1.2 pyproject-api==1.7.1 pyramid==2.0.2 pyRFC3339==1.1 +pyspelling==2.10 pytest==8.3.2 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -93,6 +126,7 @@ pytz==2024.1 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 +regex==2024.7.24 requests==2.31.0 requests-toolbelt==1.0.0 requests-unixsocket==0.3.0 @@ -102,8 +136,34 @@ SecretStorage==3.3.3 setuptools==72.2.0 simplejson==3.19.3 six==1.16.0 +smmap==5.0.1 snap-helpers==0.4.2 +sniffio==1.3.1 snowballstemmer==2.2.0 +soupsieve==2.6 +Sphinx==7.3.7 +sphinx-autobuild==2024.4.16 +sphinx-autodoc-typehints==2.2.3 +sphinx-basic-ng==1.0.0b2 +sphinx-copybutton==0.5.2 +sphinx-jinja2-compat==0.3.0 +sphinx-lint==0.9.1 +sphinx-notfound-page==1.0.4 +sphinx-prompt==1.8.0 +sphinx-tabs==3.4.5 +sphinx-toolbox==3.8.0 +sphinx_design==0.6.1 +sphinx_reredirects==0.1.5 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-details-directive==0.1.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jquery==4.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +sphinxext-opengraph==0.9.1 +starlette==0.38.2 tabulate==0.9.0 testscenarios==0.5.0 testtools==2.7.2 @@ -114,18 +174,24 @@ tox==4.18.0 translationstring==1.4 types-PyYAML==6.0.12.20240808 types-requests==2.31.0.6 -types-setuptools==71.1.0.20240813 +types-setuptools==71.1.0.20240818 types-simplejson==3.19.0.20240801 types-tabulate==0.9.0.20240106 types-toml==0.10.8.20240310 types-urllib3==1.26.25.14 typing_extensions==4.12.2 +uc-micro-py==1.0.3 urllib3==1.26.19 +uvicorn==0.30.6 validators==0.33.0 venusian==3.1.0 virtualenv==20.26.3 wadllib==1.3.6 +watchfiles==0.23.0 +wcmatch==9.0 +webencodings==0.5.1 WebOb==1.8.8 +websockets==12.0 wheel==0.44.0 ws4py==0.5.1 yamllint==1.35.1 @@ -133,4 +199,4 @@ zipp==3.20.0 zope.deprecation==5.0 zope.interface==7.0.1 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" -pyinstaller==5.13.2; sys.platform == "win32" +pyinstaller==5.13.1; sys.platform == "win32" diff --git a/docs/requirements.txt b/requirements-docs.txt similarity index 61% rename from docs/requirements.txt rename to requirements-docs.txt index 14b3993e5b..12d804442e 100644 --- a/docs/requirements.txt +++ b/requirements-docs.txt @@ -1,9 +1,15 @@ alabaster==0.7.16 -anyio==4.3.0 -attrs==23.2.0 -Babel==2.14.0 +annotated-types==0.7.0 +anyio==4.4.0 +apeye==1.4.1 +apeye-core==1.1.5 +attrs==24.2.0 +autodocsumm==0.2.13 +babel==2.16.0 beautifulsoup4==4.12.3 -bracex==2.4 +boolean.py==4.0 +bracex==2.5 +CacheControl==0.14.0 canonical-sphinx==0.1.0 canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 @@ -21,112 +27,124 @@ craft-parts==2.0.0 craft-platforms==0.1.1 craft-providers==2.0.0 craft-store==3.0.0 -cryptography==42.0.8 -Deprecated==1.2.14 +cryptography==43.0.0 +cssutils==2.11.1 +dict2css==0.3.0.post1 distro==1.9.0 docutils==0.19 -exceptiongroup==1.2.2 -furo==2024.5.6 +domdf-python-tools==3.9.0 +filelock==3.15.4 +furo==2024.8.6 +gitdb==4.0.11 +GitPython==3.1.43 gnupg==2.3.1 h11==0.14.0 html5lib==1.1 httplib2==0.22.0 idna==3.7 imagesize==1.4.1 -importlib_metadata==7.1.0 +importlib_metadata==8.2.0 jaraco.classes==3.4.0 jeepney==0.8.0 Jinja2==3.1.4 jsonschema==2.5.1 keyring==24.3.1 -launchpadlib==1.11.0 +launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 +license-expression==30.3.1 linkify-it-py==2.0.3 -lxml==5.2.1 +lxml==5.3.0 macaroonbakery==1.3.4 -Markdown==3.6 +Markdown==3.7 markdown-it-py==3.0.0 MarkupSafe==2.1.5 mdit-py-plugins==0.4.1 mdurl==0.1.2 -more-itertools==10.2.0 +more-itertools==10.4.0 +msgpack==1.0.8 mypy-extensions==1.0.0 -myst-parser==3.0.1 +myst-parser==4.0.0 +natsort==8.4.0 oauthlib==3.2.2 overrides==7.7.0 -packaging==24.0 -pip==23.3.2 +packaging==24.1 platformdirs==4.2.2 polib==1.2.0 progressbar==2.5 -protobuf==5.26.1 -psutil==5.9.8 +protobuf==5.27.3 +psutil==6.0.0 pycparser==2.22 pydantic==2.8.2 +pydantic_core==2.20.1 +pydantic_yaml==1.3.0 pyelftools==0.31 pygit2==1.13.3 -Pygments==2.17.2 +Pygments==2.18.0 pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.2 pyRFC3339==1.1 pyspelling==2.10 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" python-dateutil==2.9.0.post0 python-debian==0.1.49 pytz==2024.1 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -regex==2024.4.28 +regex==2024.7.24 requests==2.31.0 requests-toolbelt==1.0.0 requests-unixsocket==0.3.0 +ruamel.yaml==0.18.6 +ruamel.yaml.clib==0.2.8 SecretStorage==3.3.3 -setuptools==65.5.1 -simplejson==3.19.2 +setuptools==72.2.0 +simplejson==3.19.3 six==1.16.0 +smmap==5.0.1 snap-helpers==0.4.2 sniffio==1.3.1 snowballstemmer==2.2.0 -soupsieve==2.5 +soupsieve==2.6 Sphinx==7.3.7 sphinx-autobuild==2024.4.16 +sphinx-autodoc-typehints==2.2.3 sphinx-basic-ng==1.0.0b2 sphinx-copybutton==0.5.2 -sphinx_design==0.5.0 +sphinx-jinja2-compat==0.3.0 sphinx-lint==0.9.1 sphinx-notfound-page==1.0.4 -sphinx-reredirects==0.1.5 +sphinx-prompt==1.8.0 sphinx-tabs==3.4.5 -sphinxcontrib-applehelp==1.0.8 +sphinx-toolbox==3.8.0 +sphinx_design==0.6.1 +sphinx_reredirects==0.1.5 +sphinxcontrib-applehelp==2.0.0 sphinxcontrib-details-directive==0.1.0 -sphinxcontrib-devhelp==1.0.6 -sphinxcontrib-htmlhelp==2.0.5 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jquery==4.1 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.8 -sphinxcontrib-serializinghtml==1.1.10 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.37.2 +starlette==0.38.2 tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 -tomli==2.0.1 -types-Deprecated==1.2.9.20240311 -types-PyYAML==6.0.12.20240808 -typing_extensions==4.11.0 +typing_extensions==4.12.2 uc-micro-py==1.0.3 urllib3==1.26.19 -uvicorn==0.29.0 -validators==0.28.3 +uvicorn==0.30.6 +validators==0.33.0 wadllib==1.3.6 -watchfiles==0.21.0 -wcmatch==8.5.2 +watchfiles==0.23.0 +wcmatch==9.0 webencodings==0.5.1 websockets==12.0 -wrapt==1.16.0 +wheel==0.44.0 ws4py==0.5.1 -zipp==3.19.1 +zipp==3.20.0 +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" diff --git a/setup.py b/setup.py index bd113976b6..2988795a93 100755 --- a/setup.py +++ b/setup.py @@ -143,10 +143,12 @@ def recursive_data_files(directory, install_directory): docs_requires = { "canonical-sphinx", - "pyspelling", - "sphinxcontrib-details-directive", "sphinx-autobuild", + "sphinx-autodoc-typehints", + "sphinxcontrib-details-directive", "sphinx-lint", + "sphinx-toolbox", + "pyspelling", } extras_requires = {"dev": dev_requires, "docs": docs_requires} From 9458688c456682239d02f8362405613a013225a4 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 19 Aug 2024 08:25:45 -0500 Subject: [PATCH 054/151] docs: build docs from requirements-docs.txt Signed-off-by: Callahan Kovacs --- .readthedocs.yaml | 7 +++---- tox.ini | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 5ee3294372..fa08f743a2 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -16,19 +16,18 @@ build: - libapt-pkg-dev jobs: post_checkout: + - git fetch --unshallow || true # See https://docs.readthedocs.io/en/latest/build-customization.html#unshallow-git-clone - git fetch --tags --depth 1 # Also fetch tags - git describe # Useful for debugging # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py - builder: html + builder: dirhtml fail_on_warning: true python: install: - - requirements: docs/requirements.txt + - requirements: requirements-docs.txt - method: pip path: . - extra_requirements: - - docs diff --git a/tox.ini b/tox.ini index 785793b14b..0d9b1d7f1d 100644 --- a/tox.ini +++ b/tox.ini @@ -154,7 +154,7 @@ no_package = true env_dir = {work_dir}/docs runner = ignore_env_name_mismatch deps = - -r{tox_root}/docs/requirements.txt + -r{tox_root}/requirements-docs.txt [testenv:build-docs] description = Build sphinx documentation From 4bbfde2ab462676faad6778d24258e438b7e7d93 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 28 Aug 2024 08:37:55 -0400 Subject: [PATCH 055/151] ci: make snapcraft.yaml render as YAML in bug reports (#5004) --- .github/ISSUE_TEMPLATE/bug.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 26bcf446cb..82f8d0f086 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -45,7 +45,7 @@ body: If possible, please paste your snapcraft.yaml contents. This will be automatically formatted into code, so no need for backticks. - render: shell + render: yaml validations: required: true - type: textarea From 96140ad2ae9cd700f4cd975cd07faa1133249768 Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 28 Aug 2024 10:47:20 -0500 Subject: [PATCH 056/151] build(deps): bump craft-providers to 1.24.2 (#5006) Signed-off-by: Callahan Kovacs --- docs/requirements.txt | 2 +- requirements-devel.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index bbf7ab866d..8c13c5e998 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -18,7 +18,7 @@ craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 craft-parts==1.33.0 -craft-providers==1.23.1 +craft-providers==1.24.2 craft-store==2.6.2 cryptography==42.0.8 Deprecated==1.2.14 diff --git a/requirements-devel.txt b/requirements-devel.txt index c6ca7fc209..b8045f6cb3 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -16,7 +16,7 @@ craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 craft-parts==1.33.0 -craft-providers==1.23.1 +craft-providers==1.24.2 craft-store==2.6.2 cryptography==42.0.8 Deprecated==1.2.14 diff --git a/requirements.txt b/requirements.txt index daca506562..f4cc6581aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 craft-parts==1.33.0 -craft-providers==1.23.1 +craft-providers==1.24.2 craft-store==2.6.2 cryptography==42.0.8 Deprecated==1.2.14 diff --git a/setup.py b/setup.py index f9bcbc9ccf..cbd6c1de8b 100755 --- a/setup.py +++ b/setup.py @@ -98,7 +98,7 @@ def recursive_data_files(directory, install_directory): "craft-cli>=2.6.0", "craft-grammar", "craft-parts>=1.33.0", - "craft-providers", + "craft-providers>=1.24.2", "craft-store", "docutils<0.20", # Frozen until we can update sphinx dependencies. "gnupg", From ad3b54bbd93aa5a1e44091475443176676ef5808 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 29 Aug 2024 10:25:25 -0500 Subject: [PATCH 057/151] fix(remotebuild): error early for multiple artifacts per build-on (#5005) Launchpad cannot build multiple artifacts on the same build-on architecture and will fail with an esoteric error. Snapcraft now checks for the scenario and fails with an informative error before the build begins. Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 1 + setup.py | 1 + snapcraft/commands/remote.py | 48 ++++++- .../snaps/platforms/snapcraft.yaml | 1 - tests/unit/commands/test_remote.py | 119 ++++++++++++++++++ 5 files changed, 163 insertions(+), 7 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 3205215889..1d109ae49a 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -117,6 +117,7 @@ pyramid==2.0.2 pyRFC3339==1.1 pyspelling==2.10 pytest==8.3.2 +pytest-check==2.4.1 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-subprocess==1.5.2 diff --git a/setup.py b/setup.py index 2988795a93..5eeeb70cb1 100755 --- a/setup.py +++ b/setup.py @@ -80,6 +80,7 @@ def recursive_data_files(directory, install_directory): "pyramid", "pytest", "pytest-cov", + "pytest-check", "pytest-mock", "pytest-subprocess", "tox>=4.5", diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index 6ff2101163..fd815321d1 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -201,6 +201,41 @@ def _validate(self, parsed_args: argparse.Namespace) -> None: retcode=os.EX_CONFIG, ) + self._validate_single_artifact_per_build_on() + + def _validate_single_artifact_per_build_on(self) -> None: + """Validate that only one artifact will be created for each build-on. + + :raise RemoteBuildError: If multiple artifacts will be created for the same build-on. + """ + # mapping of `build-on` to `build-for` architectures + build_map: dict[str, list[str]] = {} + for build_info in self.build_plan: + build_map.setdefault(build_info.build_on, []).append(build_info.build_for) + + # assemble a list so all errors are shown at once + build_on_errors = list() + for build_on, build_fors in build_map.items(): + if len(build_fors) > 1: + build_on_errors.append( + f"\n - Building on {build_on!r} will create snaps for " + f"{humanize_list(build_fors, 'and')}." + ) + + if build_on_errors: + raise errors.RemoteBuildError( + message=( + "Remote build does not support building multiple snaps on the " + f"same architecture:{''.join(build_on_errors)}" + ), + resolution=( + "Ensure that only one snap will be created for each build-on " + "architecture." + ), + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, + ) + def _run( # noqa: PLR0915 [too-many-statements] self, parsed_args: argparse.Namespace, **kwargs: Any ) -> int | None: @@ -220,6 +255,12 @@ def _run( # noqa: PLR0915 [too-many-statements] builder = self._services.remote_build self.project = cast(models.Project, self._services.project) + + self.build_plan = self._app.BuildPlannerClass.unmarshal( + self.project.marshal() + ).get_build_plan() + emit.debug(f"Build plan: {self.build_plan}") + config = cast(dict[str, Any], self.config) project_dir = ( Path(config.get("global_args", {}).get("project_dir") or ".") @@ -365,12 +406,7 @@ def _get_project_build_fors(self) -> list[str]: :returns: A list of architectures. """ - build_plan = self._app.BuildPlannerClass.unmarshal( - self.project.marshal() - ).get_build_plan() - emit.debug(f"Build plan: {build_plan}") - - build_fors = set({build_info.build_for for build_info in build_plan}) + build_fors = set({build_info.build_for for build_info in self.build_plan}) emit.debug(f"Parsed build-for architectures from build plan: {build_fors}") return list(build_fors) diff --git a/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml b/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml index 14d3541c22..2be885a16d 100644 --- a/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml +++ b/tests/spread/core24/remote-build/snaps/platforms/snapcraft.yaml @@ -8,7 +8,6 @@ grade: stable confinement: strict # This does not test: -# - multiple artefacts on the same build-on (#4995) # - cross-compiling (#4996) platforms: diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index 714eaa80c7..cd9a862910 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -769,6 +769,125 @@ def test_platform_core22_error( assert "Use '--build-for' instead." in err +@pytest.mark.parametrize( + ("base", "build_info", "error_messages"), + [ + pytest.param( + "core22", + { + "architectures": [ + {"build-on": "amd64", "build-for": "amd64"}, + {"build-on": ["amd64", "riscv64"], "build-for": "riscv64"}, + ], + }, + ["Building on 'amd64' will create snaps for 'amd64' and 'riscv64'."], + id="core22-simple", + ), + pytest.param( + "core22", + { + "architectures": [ + {"build-on": "amd64", "build-for": "amd64"}, + {"build-on": ["amd64", "riscv64"], "build-for": "riscv64"}, + {"build-on": "s390x", "build-for": "s390x"}, + {"build-on": "s390x", "build-for": "ppc64el"}, + ], + }, + [ + "Building on 'amd64' will create snaps for 'amd64' and 'riscv64'.", + "Building on 's390x' will create snaps for 'ppc64el' and 's390x'.", + ], + id="core22-complex", + ), + pytest.param( + "core24", + { + "platforms": { + "platform1": {"build-on": "amd64", "build-for": "amd64"}, + "platform2": { + "build-on": ["amd64", "riscv64"], + "build-for": "riscv64", + }, + }, + }, + ["Building on 'amd64' will create snaps for 'amd64' and 'riscv64'."], + id="core24-simple", + ), + pytest.param( + "core24", + { + "platforms": { + "platform1": {"build-on": "amd64", "build-for": "riscv64"}, + "identical-platform": {"build-on": "amd64", "build-for": "riscv64"}, + }, + }, + # this will show 'riscv64' twice because the platform name is different + ["Building on 'amd64' will create snaps for 'riscv64' and 'riscv64'."], + id="core24-same-architectures-different-platform-name", + ), + pytest.param( + "core24", + { + "platforms": { + "amd64": None, + "riscv64": {"build-on": "amd64", "build-for": "riscv64"}, + }, + }, + ["Building on 'amd64' will create snaps for 'amd64' and 'riscv64'."], + id="core24-shorthand", + ), + pytest.param( + "core24", + { + "platforms": { + "platform1": {"build-on": "amd64", "build-for": "amd64"}, + "platform2": { + "build-on": ["amd64", "s390x"], + "build-for": "riscv64", + }, + "s390x": None, + "platform4": {"build-on": "s390x", "build-for": "riscv64"}, + }, + }, + [ + "Building on 'amd64' will create snaps for 'amd64' and 'riscv64'.", + "Building on 's390x' will create snaps for 'riscv64', 'riscv64', and 's390x'.", + ], + id="core24-complex", + ), + ], +) +def test_multiple_artifacts_per_build_on( + check, + base, + build_info, + error_messages, + capsys, + mocker, + snapcraft_yaml, + fake_services, + mock_confirm, + mock_remote_builder_fake_build_process, +): + """Error when multiple artifacts will be produced on one build-on architecture.""" + snapcraft_yaml(**{"base": base, **build_info}) + mocker.patch.object(sys, "argv", ["snapcraft", "remote-build"]) + mocker.patch( + "craft_application.services.remotebuild.RemoteBuildService.start_builds" + ) + app = application.create_app() + assert app.run() == os.EX_CONFIG + + _, err = capsys.readouterr() + + check.is_in( + "Remote build does not support building multiple snaps on the same architecture", + err, + ) + for message in error_messages: + check.is_in(message, err) + + ######################## # Remote builder tests # ######################## From 31bec52003ce81c3e3e24f777c182d813ca488e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:33:20 -0400 Subject: [PATCH 058/151] build(deps): update dependency cryptography to v43.0.1 [security] (main) (#5010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [cryptography](https://redirect.github.com/pyca/cryptography) ([changelog](https://cryptography.io/en/latest/changelog/)) | `==43.0.0` -> `==43.0.1` | [![age](https://developer.mend.io/api/mc/badges/age/pypi/cryptography/43.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/cryptography/43.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/cryptography/43.0.0/43.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/cryptography/43.0.0/43.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | ### GitHub Vulnerability Alerts #### [GHSA-h4gh-qq45-vh27](https://redirect.github.com/pyca/cryptography/security/advisories/GHSA-h4gh-qq45-vh27) pyca/cryptography's wheels include a statically linked copy of OpenSSL. The versions of OpenSSL included in cryptography 37.0.0-43.0.0 are vulnerable to a security issue. More details about the vulnerability itself can be found in https://openssl-library.org/news/secadv/20240903.txt. If you are building cryptography source ("sdist") then you are responsible for upgrading your copy of OpenSSL. Only users installing from wheels built by the cryptography project (i.e., those distributed on PyPI) need to update their cryptography versions. --- ### Release Notes
pyca/cryptography (cryptography) ### [`v43.0.1`](https://redirect.github.com/pyca/cryptography/compare/43.0.0...43.0.1) [Compare Source](https://redirect.github.com/pyca/cryptography/compare/43.0.0...43.0.1)
--- ### Configuration 📅 **Schedule**: Branch creation - "" in timezone Etc/UTC, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/canonical/snapcraft). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 1d109ae49a..c38715751f 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -32,7 +32,7 @@ craft-parts==2.0.0 craft-platforms==0.1.1 craft-providers==2.0.0 craft-store==3.0.0 -cryptography==43.0.0 +cryptography==43.0.1 cssutils==2.11.1 dict2css==0.3.0.post1 dill==0.3.8 diff --git a/requirements-docs.txt b/requirements-docs.txt index 12d804442e..6b481ca54f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -27,7 +27,7 @@ craft-parts==2.0.0 craft-platforms==0.1.1 craft-providers==2.0.0 craft-store==3.0.0 -cryptography==43.0.0 +cryptography==43.0.1 cssutils==2.11.1 dict2css==0.3.0.post1 distro==1.9.0 diff --git a/requirements.txt b/requirements.txt index f6d5f584fd..0d9656807a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ craft-parts==2.0.0 craft-platforms==0.1.1 craft-providers==2.0.0 craft-store==3.0.0 -cryptography==43.0.0 +cryptography==43.0.1 distro==1.9.0 docutils==0.19 gnupg==2.3.1 From e21b65be7adde8503ee6ce431e2c7c1a8971b64f Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 28 Aug 2024 08:29:25 -0500 Subject: [PATCH 059/151] refactor: use craft-cli command groups CommandGroups can be used now that craft-application will accept a sequence of commands. See https://github.com/canonical/craft-application/pull/359 Signed-off-by: Callahan Kovacs --- snapcraft/cli.py | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/snapcraft/cli.py b/snapcraft/cli.py index aa0e3f778d..6b2ca2de78 100644 --- a/snapcraft/cli.py +++ b/snapcraft/cli.py @@ -20,7 +20,6 @@ import contextlib import os import sys -from dataclasses import dataclass from typing import Any, Dict import craft_application.commands @@ -37,16 +36,7 @@ from . import commands from .legacy_cli import run_legacy - -@dataclass -class CommandGroup: - """Dataclass to hold a command group.""" - - name: str - commands: list - - -CORE22_LIFECYCLE_COMMAND_GROUP = CommandGroup( +CORE22_LIFECYCLE_COMMAND_GROUP = craft_cli.CommandGroup( "Lifecycle", [ commands.core22.CleanCommand, @@ -60,7 +50,7 @@ class CommandGroup: ], ) -CORE24_LIFECYCLE_COMMAND_GROUP = CommandGroup( +CORE24_LIFECYCLE_COMMAND_GROUP = craft_cli.CommandGroup( "Lifecycle", [ craft_application.commands.lifecycle.CleanCommand, @@ -76,14 +66,14 @@ class CommandGroup: ) COMMAND_GROUPS = [ - CommandGroup( + craft_cli.CommandGroup( "Plugins", [ commands.PluginsCommand, commands.ListPluginsCommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Extensions", [ commands.ListExtensionsCommand, @@ -91,7 +81,7 @@ class CommandGroup: commands.ExpandExtensionsCommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Store Account", [ commands.StoreLoginCommand, @@ -100,7 +90,7 @@ class CommandGroup: commands.StoreWhoAmICommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Store Snap Names", [ commands.StoreRegisterCommand, @@ -111,7 +101,7 @@ class CommandGroup: commands.StoreLegacyUploadMetadataCommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Store Snap Release Management", [ commands.StoreReleaseCommand, @@ -124,7 +114,7 @@ class CommandGroup: commands.StoreRevisionsCommand, # hidden (alias to list-revisions) ], ), - CommandGroup( + craft_cli.CommandGroup( "Store Snap Tracks", [ commands.StoreListTracksCommand, @@ -132,7 +122,7 @@ class CommandGroup: commands.StoreLegacySetDefaultTrackCommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Store Key Management", [ commands.StoreLegacyCreateKeyCommand, @@ -141,7 +131,7 @@ class CommandGroup: commands.StoreLegacyListKeysCommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Store Validation Sets", [ commands.StoreEditValidationSetsCommand, @@ -150,7 +140,7 @@ class CommandGroup: commands.StoreLegacyGatedCommand, ], ), - CommandGroup( + craft_cli.CommandGroup( "Other", [ *craft_application.commands.get_other_command_group().commands, @@ -206,10 +196,7 @@ def get_verbosity() -> EmitterMode: def get_dispatcher() -> craft_cli.Dispatcher: """Return an instance of Dispatcher.""" - craft_cli_command_groups = [ - craft_cli.CommandGroup(group.name, group.commands) - for group in COMMAND_GROUPS + [CORE22_LIFECYCLE_COMMAND_GROUP] - ] + craft_cli_command_groups = [*COMMAND_GROUPS, CORE22_LIFECYCLE_COMMAND_GROUP] return craft_cli.Dispatcher( "snapcraft", From 098a47be372f149fb3ff8b1fa78ba92e8b8f4a1a Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 28 Aug 2024 08:44:42 -0500 Subject: [PATCH 060/151] style: reformat dispatcher error messages --- snapcraft/application.py | 33 ++++++++++++++++++++---------- tests/unit/test_application.py | 37 +++++++++++++++------------------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index 4e13807354..d19a6be4a4 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -66,7 +66,7 @@ def _get_esm_error_for_base(base: str) -> None: """Raise an error appropriate for the base under ESM.""" - channel: Optional[str] = None + channel: str | None = None match base: case "core": channel = "4.x" @@ -77,9 +77,12 @@ def _get_esm_error_for_base(base: str) -> None: case _: return - raise RuntimeError( - f"ERROR: base {base!r} was last supported on Snapcraft {version} available " - f"on the {channel!r} channel." + raise errors.SnapcraftError( + message=f"Base {base!r} is not supported by this version of Snapcraft.", + resolution=( + f"Use Snapcraft {version} from the {channel!r} channel of snapcraft where " + f"{base!r} was last supported." + ), ) @@ -271,19 +274,27 @@ def _get_dispatcher(self) -> craft_cli.Dispatcher: None, ): raise errors.SnapcraftError( - f"Unknown value {build_strategy!r} in environment variable " - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY'. " - "Valid values are 'disable-fallback' and 'force-fallback'." + message=( + f"Unknown value {build_strategy!r} in environment variable " + "'SNAPCRAFT_REMOTE_BUILD_STRATEGY'. " + ), + resolution=( + "Valid values are 'disable-fallback' and 'force-fallback'." + ), ) # core20 must use the legacy remote builder because the Project model # cannot parse core20 snapcraft.yaml schemas (#4885) if "core20" in (base, build_base): if build_strategy == "disable-fallback": - raise RuntimeError( - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot " - "be used for core20 snaps. Unset the environment variable " - "or use 'force-fallback'." + raise errors.SnapcraftError( + message=( + "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' " + "cannot be used for core20 snaps." + ), + resolution=( + "Unset the environment variable or use 'force-fallback'." + ), ) raise errors.ClassicFallback() diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index b245a427d3..b48df3600c 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -377,17 +377,16 @@ def test_default_command_integrated(monkeypatch, mocker, new_dir): @pytest.mark.parametrize("base", const.ESM_BASES) -def test_esm_error(snapcraft_yaml, base): +def test_esm_error(snapcraft_yaml, base, monkeypatch, capsys): """Test that an error is raised when using an ESM base.""" snapcraft_yaml_dict = {"base": base} snapcraft_yaml(**snapcraft_yaml_dict) + monkeypatch.setattr("sys.argv", ["snapcraft"]) - app = application.create_app() + application.main() - with pytest.raises( - RuntimeError, match=f"ERROR: base {base!r} was last supported on Snapcraft" - ): - app.run() + _, err = capsys.readouterr() + assert f"Base {base!r} is not supported by this version of Snapcraft" in err @pytest.mark.parametrize("base", const.CURRENT_BASES) @@ -440,19 +439,17 @@ def test_run_envvar( @pytest.mark.parametrize("base", const.LEGACY_BASES) @pytest.mark.usefixtures("mock_confirm", "mock_remote_build_argv") -def test_run_envvar_disable_fallback_core20(snapcraft_yaml, base, monkeypatch): +def test_run_envvar_disable_fallback_core20(snapcraft_yaml, base, monkeypatch, capsys): """core20 bases cannot use the new remote-build.""" monkeypatch.setenv("SNAPCRAFT_REMOTE_BUILD_STRATEGY", "disable-fallback") + monkeypatch.setattr("sys.argv", ["snapcraft", "remote-build"]) snapcraft_yaml_dict = {"base": base} snapcraft_yaml(**snapcraft_yaml_dict) - with pytest.raises(RuntimeError) as raised: - application.main() + application.main() - assert str(raised.value) == ( - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot be used for core20 " - "snaps. Unset the environment variable or use 'force-fallback'." - ) + _, err = capsys.readouterr() + assert f"'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot be used for {base} snaps" in err @pytest.mark.parametrize("base", const.LEGACY_BASES | {"core22"}) @@ -505,20 +502,18 @@ def test_run_envvar_force_fallback_empty_core22( @pytest.mark.parametrize("base", const.LEGACY_BASES | {"core22"}) @pytest.mark.usefixtures("mock_confirm", "mock_remote_build_argv") -def test_run_envvar_invalid(snapcraft_yaml, base, monkeypatch): +def test_run_envvar_invalid(snapcraft_yaml, base, monkeypatch, capsys): """core20 and core22 bases raise an error if the envvar is invalid.""" monkeypatch.setenv("SNAPCRAFT_REMOTE_BUILD_STRATEGY", "badvalue") + monkeypatch.setattr("sys.argv", ["snapcraft", "remote-build"]) snapcraft_yaml_dict = {"base": base} snapcraft_yaml(**snapcraft_yaml_dict) - with pytest.raises(SnapcraftError) as err: - application.main() - assert err.match( - "Unknown value 'badvalue' in environment variable " - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY'. Valid values are 'disable-fallback' and " - "'force-fallback'" - ) + application.main() + + _, err = capsys.readouterr() + assert "Unknown value 'badvalue' in environment variable 'SNAPCRAFT_REMOTE_BUILD_STRATEGY'" in err @pytest.mark.parametrize( From 0fa19dafaaabcf4d8f38bfe06ee4b9bf6986f61f Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 28 Aug 2024 18:21:49 -0500 Subject: [PATCH 061/151] refactor(application): redesign pre-run validation Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 277 +++++++++++++++++++++-------- snapcraft/cli.py | 1 - snapcraft/commands/plugins.py | 2 +- tests/unit/commands/test_remote.py | 10 -- tests/unit/test_application.py | 193 ++++++++++++++++++-- 5 files changed, 385 insertions(+), 98 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index d19a6be4a4..e842fecb74 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -22,24 +22,24 @@ import os import pathlib import sys -from typing import Any, Optional +from typing import Any -import craft_application.commands as craft_app_commands import craft_cli import craft_parts import craft_store from craft_application import Application, AppMetadata, util +from craft_application.commands import get_other_command_group from craft_cli import emit from craft_parts.plugins.plugins import PluginType from overrides import override import snapcraft import snapcraft_legacy -from snapcraft import cli, errors, models, services, store +from snapcraft import cli, commands, errors, models, services, store from snapcraft.extensions import apply_extensions from snapcraft.models.project import SnapcraftBuildPlanner, apply_root_packages from snapcraft.parts import set_global_environment -from snapcraft.utils import get_host_architecture +from snapcraft.utils import get_effective_base, get_host_architecture from snapcraft_legacy.cli import legacy from .legacy_cli import _LIB_NAMES, _ORIGINAL_LIB_NAME_LOG_LEVEL @@ -66,7 +66,6 @@ def _get_esm_error_for_base(base: str) -> None: """Raise an error appropriate for the base under ESM.""" - channel: str | None = None match base: case "core": channel = "4.x" @@ -150,11 +149,15 @@ def _configure_services(self, provider_name: str | None) -> None: super()._configure_services(provider_name) - # TODO: remove this override (#4911) @property def command_groups(self): - """Short-circuit the standard command groups for now.""" - return self._command_groups + """Replace craft-application's LifecycleCommand group.""" + _command_groups = super().command_groups + for index, command_group in enumerate(_command_groups): + if command_group.name == "Lifecycle": + _command_groups[index] = cli.CORE24_LIFECYCLE_COMMAND_GROUP + + return _command_groups @override def _resolve_project_path(self, project_dir: pathlib.Path | None) -> pathlib.Path: @@ -177,6 +180,31 @@ def app_config(self) -> dict[str, Any]: config["core24"] = self._known_core24 return config + @override + def _pre_run(self, dispatcher: craft_cli.Dispatcher) -> None: + """Do any final setup before running the command. + + :raises SnapcraftError: If the wrong codebase is chosen for the project. + :raises SnapcraftError: If the project uses a base that is not supported by the + current version of Snapcraft. + """ + if self._snapcraft_yaml_data: + # if the project metadata is incomplete, assume core24 so craft application + # can present user-friendly errors when unmarshalling the model + effective_base = ( + get_effective_base( + base=self._snapcraft_yaml_data.get("base"), + build_base=self._snapcraft_yaml_data.get("build-base"), + project_type=self._snapcraft_yaml_data.get("type"), + name=self._snapcraft_yaml_data.get("name"), + ) + or "core24" + ) + _get_esm_error_for_base(effective_base) + self._ensure_remote_build_supported(effective_base) + + super()._pre_run(dispatcher) + @override def run(self) -> int: try: @@ -216,6 +244,7 @@ def _setup_partitions(self, yaml_data: dict[str, Any]) -> list[str] | None: # This is why we have the enablement after the check, if not # we could have directly returned with .get_partitions() which # handles the empty case. + craft_parts.Features.reset() craft_parts.Features(enable_partitions=True) return components.get_partitions() @@ -232,82 +261,179 @@ def _extra_yaml_transform( @staticmethod def _get_argv_command() -> str | None: - """Return the first non-option argument.""" - for arg in sys.argv[1:]: - if arg.startswith("-"): - continue - return arg - - return None - - @override - def _get_dispatcher(self) -> craft_cli.Dispatcher: - """Handle multiplexing of Snapcraft "codebases" depending on the project's base. - - The ClassicFallback-based flow is used in any of the following scenarios: - - there is no project to load - - for core20 remote builds if SNAPCRAFT_REMOTE_BUILD_STRATEGY is not "disable-fallback" - - for core22 remote builds if SNAPCRAFT_REMOTE_BUILD_STRATEGY is "force-fallback" - - The craft-application-based flow is used in any of the following scenarios: - - the project base is core24 or newer - - for the "version" command + """Return the first command name used as an argument.""" + command_names = { + command.name + for group in [ + *cli.COMMAND_GROUPS, + cli.CORE22_LIFECYCLE_COMMAND_GROUP, + cli.CORE24_LIFECYCLE_COMMAND_GROUP, + get_other_command_group(), + ] + for command in group.commands + } + + return next((arg for arg in sys.argv[1:] if arg in command_names), None) + + def _check_for_classic_fallback(self) -> None: + """Check for and raise a ClassicFallback if an older codebase should be used. + + The project should use the classic fallback path for any of the following conditions. + + core20: + 1. Running a lifecycle command for a core20 snap + 2. Expanding extensions for a core20 snap + 3. Listing plugins for a core20 snap via the project metadata + 4. Remote builds for a core20 snap + 5. Listing plugins for a core20 snap via `snapcraft list-plugins --base core20` + + core22: + 6. Running a lifecycle command for a core22 snap + 7. Remote builds for a core22 snap with the `force-fallback` strategy + + Exception: If `--version` or `-V` is passed, do not use the classic fallback. + + If none of the above conditions are met, then the default craft-application + code path should be used. + + :raises ClassicFallback: If the project should use the classic fallback code path. """ argv_command = self._get_argv_command() - if argv_command == "lint": - # We don't need to check for core24 if we're just linting - return super()._get_dispatcher() + build_strategy = os.environ.get("SNAPCRAFT_REMOTE_BUILD_STRATEGY", None) + + # Exception: If `--version` or `-V` is passed, do not use the classic fallback. + if {"--version", "-V"}.intersection(sys.argv): + return if self._snapcraft_yaml_data: - base = self._snapcraft_yaml_data.get("base") - build_base = self._snapcraft_yaml_data.get("build-base") - _get_esm_error_for_base(base) - - if argv_command == "remote-build" and any( - b in ("core20", "core22") for b in (base, build_base) - ): - build_strategy = os.environ.get("SNAPCRAFT_REMOTE_BUILD_STRATEGY", None) - if build_strategy not in ( - "force-fallback", - "disable-fallback", - "", - None, + # if the project metadata is incomplete, assume core24 so craft application + # can present user-friendly errors when unmarshalling the model + effective_base = ( + get_effective_base( + base=self._snapcraft_yaml_data.get("base"), + build_base=self._snapcraft_yaml_data.get("build-base"), + project_type=self._snapcraft_yaml_data.get("type"), + name=self._snapcraft_yaml_data.get("name"), + ) + or "core24" + ) + + classic_lifecycle_commands = [ + command.name for command in cli.CORE22_LIFECYCLE_COMMAND_GROUP.commands + ] + + if effective_base == "core20": + # 1. Running a lifecycle command for a core20 snap + # 2. Expanding extensions for a core20 snap + # 3. Listing plugins for a core20 snap via the project metadata + if argv_command is None or argv_command in [ + *classic_lifecycle_commands, + "expand-extensions", + "list-plugins", + "plugins", + ]: + raise errors.ClassicFallback() + + # 4. Remote builds for a core20 snap + # Note that a `core20` snap with 'disable-fallback' set will follow the + # craft-application codepath and an error will be raised after the + # dispatcher is created. + if ( + argv_command == "remote-build" + and not build_strategy + or build_strategy == "force-fallback" ): - raise errors.SnapcraftError( - message=( - f"Unknown value {build_strategy!r} in environment variable " - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY'. " - ), - resolution=( - "Valid values are 'disable-fallback' and 'force-fallback'." - ), - ) - - # core20 must use the legacy remote builder because the Project model - # cannot parse core20 snapcraft.yaml schemas (#4885) - if "core20" in (base, build_base): - if build_strategy == "disable-fallback": - raise errors.SnapcraftError( - message=( - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' " - "cannot be used for core20 snaps." - ), - resolution=( - "Unset the environment variable or use 'force-fallback'." - ), - ) raise errors.ClassicFallback() - # Use craft-application unless explicitly forced to use legacy snapcraft + if effective_base == "core22": + # 6. Running a lifecycle command for a core22 snap + if argv_command is None or argv_command in classic_lifecycle_commands: + raise errors.ClassicFallback() + + # 7. Remote builds for a core22 snap with the `force-fallback` strategy if ( - "core22" in (base, build_base) + argv_command == "remote-build" and build_strategy == "force-fallback" ): raise errors.ClassicFallback() - elif not self._known_core24 and not ( - argv_command == "version" or "--version" in sys.argv or "-V" in sys.argv - ): - raise errors.ClassicFallback() + + # 5. Listing plugins for a core20 snap via `snapcraft list-plugins --base core20` + if argv_command in ["list-plugins", "plugins"] and { + "--base=core20", + "core20", + }.intersection(sys.argv): + raise errors.ClassicFallback() + + @staticmethod + def _ensure_remote_build_supported(base: str) -> None: + """Ensure the version of remote build is supported for the project. + + 1. SNAPCRAFT_REMOTE_BUILD_STRATEGY must be unset, 'disable-fallback', or + 'force-fallback' + 2. core20 projects must use the legacy remote builder + 3. core24 and newer projects must use the craft-application remote builder + + :raises SnapcraftError: If the environment variable `SNAPCRAFT_REMOTE_BUILD_STRATEGY` + is invalid. + :raises SnapcraftError: If the remote build version cannot be used for the project. + """ + build_strategy = os.environ.get("SNAPCRAFT_REMOTE_BUILD_STRATEGY", None) + + # 1. SNAPCRAFT_REMOTE_BUILD_STRATEGY must be unset, 'disable-fallback', or 'force-fallback' + if build_strategy and build_strategy not in ( + "force-fallback", + "disable-fallback", + ): + raise errors.SnapcraftError( + message=( + f"Unknown value {build_strategy!r} in environment variable " + "'SNAPCRAFT_REMOTE_BUILD_STRATEGY'. " + ), + resolution=( + "Valid values are 'disable-fallback' and 'force-fallback'." + ), + ) + + # 2. core20 projects must use the legacy remote builder (#4885) + if base == "core20" and build_strategy == "disable-fallback": + raise errors.SnapcraftError( + message=( + "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' " + "cannot be used for core20 snaps." + ), + resolution=( + "Unset the environment variable or set it to 'force-fallback'." + ), + ) + + # 3. core24 and newer projects must use the craft-application remote builder + elif base not in ["core20", "core22"] and build_strategy == "force-fallback": + raise errors.SnapcraftError( + message=( + "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=force-fallback' cannot " + "be used for core24 and newer snaps." + ), + resolution=( + "Unset the environment variable or set it to 'disable-fallback'." + ), + ) + + @override + def _get_dispatcher(self) -> craft_cli.Dispatcher: + """Handle multiplexing of Snapcraft "codebases" depending on the project's base. + + ClassicFallback errors must be raised before creating the Dispatcher. + + - The codebase for core24 and newer commands uses craft-application to + create and manage the Dispatcher. + - The codebase for core22 commands creates its own Dispatcher and handles + errors and exit codes. + - The codebase for core20 commands uses the legacy snapcraft codebase which + handles logging, errors, and exit codes internally. + + :raises ClassicFallback: If the core20 or core22 codebases should be used. + """ + self._check_for_classic_fallback() return super()._get_dispatcher() @override @@ -318,7 +444,7 @@ def _create_dispatcher(self) -> craft_cli.Dispatcher: self.command_groups, summary=str(self.app.summary), extra_global_args=self._global_arguments, - default_command=craft_app_commands.lifecycle.PackCommand, + default_command=commands.PackCommand, ) @override @@ -335,10 +461,9 @@ def create_app() -> Snapcraft: app = Snapcraft( app=APP_METADATA, services=snapcraft_services, - extra_loggers={"snapcraft.remote"}, ) - for group in [cli.CORE24_LIFECYCLE_COMMAND_GROUP, *cli.COMMAND_GROUPS]: + for group in cli.COMMAND_GROUPS: app.add_command_group(group.name, group.commands) return app diff --git a/snapcraft/cli.py b/snapcraft/cli.py index 6b2ca2de78..c916b9ff5e 100644 --- a/snapcraft/cli.py +++ b/snapcraft/cli.py @@ -143,7 +143,6 @@ craft_cli.CommandGroup( "Other", [ - *craft_application.commands.get_other_command_group().commands, commands.LintCommand, commands.InitCommand, ], diff --git a/snapcraft/commands/plugins.py b/snapcraft/commands/plugins.py index 7b16979cdf..5a81448418 100644 --- a/snapcraft/commands/plugins.py +++ b/snapcraft/commands/plugins.py @@ -60,7 +60,7 @@ def fill_parser(self, parser: "argparse.ArgumentParser") -> None: @overrides def run(self, parsed_args): - if parsed_args.base in ("core18", "core20"): + if parsed_args.base == "core20": raise errors.LegacyFallback() base = parsed_args.base diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index cd9a862910..0ffb10f9bb 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -32,7 +32,6 @@ from craft_application.remote.utils import get_build_id from snapcraft import application, const -from snapcraft.errors import ClassicFallback from snapcraft.utils import get_host_architecture # remote-build control logic may check if the working dir is a git repo, @@ -240,15 +239,6 @@ def test_remote_build_sudo_warns(emitter, snapcraft_yaml, base, mock_run_remote_ mock_run_remote_build.assert_called_once() -@pytest.mark.usefixtures("mock_argv", "mock_confirm", "fake_services") -def test_cannot_load_snapcraft_yaml(capsys, mocker): - """Raise an error if the snapcraft.yaml does not exist.""" - app = application.create_app() - - with pytest.raises(ClassicFallback): - app.run() - - @pytest.mark.parametrize("base", const.CURRENT_BASES) @pytest.mark.usefixtures("mock_argv", "mock_confirm", "fake_services") def test_launchpad_timeout_default(mocker, snapcraft_yaml, base, fake_services): diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index b48df3600c..17287a5d0d 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -26,12 +26,13 @@ import pytest import yaml from craft_application import util +from craft_application.commands import get_other_command_group from craft_parts.packages import snaps from craft_providers import bases -from snapcraft import application, const, services +from snapcraft import application, cli, const, services from snapcraft.commands import PackCommand -from snapcraft.errors import ClassicFallback, SnapcraftError +from snapcraft.errors import ClassicFallback from snapcraft.models.project import Architecture @@ -409,12 +410,10 @@ def test_esm_pass(mocker, snapcraft_yaml, base): mock_dispatch.assert_called_once() -@pytest.mark.parametrize( - "envvar", ["force-fallback", "disable-fallback", "badvalue", None] -) +@pytest.mark.parametrize("envvar", ["disable-fallback", None]) @pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) @pytest.mark.usefixtures("mock_confirm", "mock_remote_build_argv") -def test_run_envvar( +def test_run_remote_build_core24( monkeypatch, snapcraft_yaml, base, @@ -422,7 +421,7 @@ def test_run_envvar( mock_remote_build_run, mock_run_legacy, ): - """Bases core24 and later run new remote-build regardless of envvar.""" + """Bases core24 and later use the new remote-build.""" snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} snapcraft_yaml(**snapcraft_yaml_dict) @@ -437,6 +436,24 @@ def test_run_envvar( mock_run_legacy.assert_not_called() +@pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) +@pytest.mark.usefixtures("mock_confirm", "mock_remote_build_argv") +def test_run_remote_build_core24_error(monkeypatch, snapcraft_yaml, base, capsys): + """Error if using force-fallback for core24 or newer.""" + snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} + snapcraft_yaml(**snapcraft_yaml_dict) + monkeypatch.setenv("SNAPCRAFT_REMOTE_BUILD_STRATEGY", "force-fallback") + monkeypatch.setattr("sys.argv", ["snapcraft", "remote-build"]) + + application.main() + + _, err = capsys.readouterr() + assert ( + "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=force-fallback' cannot be used " + "for core24 and newer snaps" + ) in err + + @pytest.mark.parametrize("base", const.LEGACY_BASES) @pytest.mark.usefixtures("mock_confirm", "mock_remote_build_argv") def test_run_envvar_disable_fallback_core20(snapcraft_yaml, base, monkeypatch, capsys): @@ -449,7 +466,10 @@ def test_run_envvar_disable_fallback_core20(snapcraft_yaml, base, monkeypatch, c application.main() _, err = capsys.readouterr() - assert f"'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot be used for {base} snaps" in err + assert ( + f"'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot be used for {base} snaps" + in err + ) @pytest.mark.parametrize("base", const.LEGACY_BASES | {"core22"}) @@ -500,7 +520,7 @@ def test_run_envvar_force_fallback_empty_core22( mock_run_legacy.assert_not_called() -@pytest.mark.parametrize("base", const.LEGACY_BASES | {"core22"}) +@pytest.mark.parametrize("base", const.LEGACY_BASES | const.CURRENT_BASES) @pytest.mark.usefixtures("mock_confirm", "mock_remote_build_argv") def test_run_envvar_invalid(snapcraft_yaml, base, monkeypatch, capsys): """core20 and core22 bases raise an error if the envvar is invalid.""" @@ -513,7 +533,89 @@ def test_run_envvar_invalid(snapcraft_yaml, base, monkeypatch, capsys): application.main() _, err = capsys.readouterr() - assert "Unknown value 'badvalue' in environment variable 'SNAPCRAFT_REMOTE_BUILD_STRATEGY'" in err + assert ( + "Unknown value 'badvalue' in environment variable 'SNAPCRAFT_REMOTE_BUILD_STRATEGY'" + in err + ) + + +@pytest.mark.parametrize("command", ["plugins", "list-plugins"]) +@pytest.mark.parametrize( + "args", + [ + [], + ["--base", "core22"], + ["--base=core22"], + ["--base", "core24"], + ["--base=core24"], + ], +) +@pytest.mark.parametrize("base", const.CURRENT_BASES | {None}) +def test_run_list_plugins(command, args, base, mocker, monkeypatch, snapcraft_yaml): + """Do not trigger a classic fallback for `list-plugins` for core22 or newer.""" + monkeypatch.setattr("sys.argv", ["snapcraft", command, *args]) + if base: + snapcraft_yaml(base=base) + mock_dispatch = mocker.patch( + "craft_application.application.Application._get_dispatcher" + ) + + app = application.create_app() + app.run() + + mock_dispatch.assert_called_once() + + +@pytest.mark.parametrize("command", ["plugins", "list-plugins"]) +@pytest.mark.parametrize("args", [["--base", "core20"], ["--base=core20"]]) +@pytest.mark.parametrize("base", const.LEGACY_BASES | const.CURRENT_BASES | {None}) +def test_run_list_plugins_classic( + command, args, base, mocker, monkeypatch, snapcraft_yaml +): + """`list-plugins` triggers a fallback only with `--base=core20`.""" + monkeypatch.setattr("sys.argv", ["snapcraft", command, *args]) + if base: + snapcraft_yaml(base=base) + mock_dispatch = mocker.patch( + "craft_application.application.Application._get_dispatcher" + ) + + app = application.create_app() + with pytest.raises(ClassicFallback): + app.run() + + mock_dispatch.assert_not_called() + + +def test_run_expand_extensions_classic(mocker, monkeypatch, snapcraft_yaml): + """`expand-extensions` triggers a fallback for core20 snaps.""" + monkeypatch.setattr("sys.argv", ["snapcraft", "expand-extensions"]) + snapcraft_yaml(base="core20") + mock_dispatch = mocker.patch( + "craft_application.application.Application._get_dispatcher" + ) + + app = application.create_app() + with pytest.raises(ClassicFallback): + app.run() + + mock_dispatch.assert_not_called() + + +@pytest.mark.parametrize("base", const.CURRENT_BASES | {None}) +def test_run_version(base, mocker, monkeypatch, snapcraft_yaml): + """Do not trigger a classic fallback for `version`.""" + monkeypatch.setattr("sys.argv", ["snapcraft", "version"]) + if base: + snapcraft_yaml(base=base) + mock_dispatch = mocker.patch( + "craft_application.application.Application._get_dispatcher" + ) + + app = application.create_app() + app.run() + + mock_dispatch.assert_called_once() @pytest.mark.parametrize( @@ -586,3 +688,74 @@ def test_store_key_error(mocker, capsys): # pylint: enable=[line-too-long] ) ) + + +@pytest.mark.parametrize( + "command", + { + command.name + for group in [ + *cli.COMMAND_GROUPS, + cli.CORE22_LIFECYCLE_COMMAND_GROUP, + cli.CORE24_LIFECYCLE_COMMAND_GROUP, + get_other_command_group(), + ] + for command in group.commands + }, +) +def test_get_argv_command(command, monkeypatch): + """Get the command.""" + monkeypatch.setattr( + "sys.argv", + [ + "snapcraft", + "--verbosity" "trace", + "--build-for=armhf", + "--shell-after", + command, + ], + ) + + app = application.create_app() + actual_command = app._get_argv_command() + + assert actual_command == command + + +@pytest.mark.parametrize( + ["args", "expected_command"], + [ + # no default command + ([], None), + (["--verbosity=trace"], None), + (["--verbosity", "trace"], None), + (["--shell-after"], None), + # no options + (["pack"], "pack"), + # with an option + (["pack", "--verbosity=trace"], "pack"), + (["pack", "--verbosity", "trace"], "pack"), + (["--verbosity=trace", "pack"], "pack"), + (["--verbosity", "trace", "pack"], "pack"), + # with a flag + (["pack", "--shell-after"], "pack"), + (["--shell-after", "pack"], "pack"), + # with an option and a flag + (["pack", "--verbosity=trace", "--shell-after"], "pack"), + (["--shell-after", "pack", "--verbosity=trace", "--shell-after"], "pack"), + (["pack", "--verbosity", "trace", "--shell-after"], "pack"), + (["--shell-after", "pack", "--verbosity", "trace"], "pack"), + (["--verbosity=trace", "pack", "--shell-after"], "pack"), + (["--shell-after", "--verbosity=trace", "pack"], "pack"), + (["--verbosity", "trace", "pack"], "pack"), + (["--shell-after", "--verbosity", "trace", "pack"], "pack"), + ], +) +def test_get_argv_command_with_options(args, expected_command, monkeypatch): + """Get the command no with a variety of options.""" + monkeypatch.setattr("sys.argv", ["snapcraft", *args]) + + app = application.create_app() + command = app._get_argv_command() + + assert command == expected_command From 3508cf393435eb1cb148248098a68f20d81bf037 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 3 Sep 2024 09:11:15 -0500 Subject: [PATCH 062/151] refactor: enable craft-parts features in the correct location Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index e842fecb74..b26749d264 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -233,20 +233,18 @@ def run(self) -> int: return return_code + @override + def _enable_craft_parts_features(self) -> None: + """Enable partitions if components are defined.""" + if self._snapcraft_yaml_data and self._snapcraft_yaml_data.get("components"): + craft_parts.Features(enable_partitions=True) + @override def _setup_partitions(self, yaml_data: dict[str, Any]) -> list[str] | None: components = models.ComponentProject.unmarshal(yaml_data) if components.components is None: return None - # Users of partitions need to manually enable them, in Snapcraft - # this is done dynamically depending on the existence of components. - # This is why we have the enablement after the check, if not - # we could have directly returned with .get_partitions() which - # handles the empty case. - craft_parts.Features.reset() - craft_parts.Features(enable_partitions=True) - return components.get_partitions() @override From ec1cc0a5bdd538cb3bc98b028b8178872a2c2fa7 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 5 Sep 2024 19:29:15 -0500 Subject: [PATCH 063/151] chore(revert): add content interfaces for qt-common-themes (#5013) This is a temporary revert of #4884 for `hotfix/8.4` to allow releasing snapcraft 8.4. See the discussion in #4884 and [this forum post](https://forum.snapcraft.io/t/kde-global-auto-connect-qt-common-themes/40878) for context. --- snapcraft/extensions/kde_neon_6.py | 37 ------------- tests/unit/extensions/test_kde_neon_6.py | 70 ------------------------ 2 files changed, 107 deletions(-) diff --git a/snapcraft/extensions/kde_neon_6.py b/snapcraft/extensions/kde_neon_6.py index 20b85ae77e..2c39530c5c 100644 --- a/snapcraft/extensions/kde_neon_6.py +++ b/snapcraft/extensions/kde_neon_6.py @@ -51,8 +51,6 @@ class KDENeon6(Extension): It configures each application with the following plugs: \b - - Common GTK themes. - - Common Qt themes. - Common Icon Themes. - Common Sound Themes. - The Qt6 and KDE Frameworks 6 runtime libraries and utilities. @@ -153,51 +151,16 @@ def get_root_snippet(self) -> Dict[str, Any]: "compression": "lzo", "plugs": { "desktop": {"mount-host-font-cache": False}, - "gtk-2-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "gtk-common-themes", - }, - "kde-gtk2-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "qt-common-themes", - }, - "kde-gtk3-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "qt-common-themes", - }, - "gtk-3-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "gtk-common-themes", - }, - "qt-icon-themes": { - "interface": "content", - "target": "$SNAP/data-dir/icons", - "default-provider": "qt-common-themes", - }, "icon-themes": { "interface": "content", "target": "$SNAP/data-dir/icons", "default-provider": "gtk-common-themes", }, - "qt-sound-themes": { - "interface": "content", - "target": "$SNAP/data-dir/sounds", - "default-provider": "qt-common-themes", - }, "sound-themes": { "interface": "content", "target": "$SNAP/data-dir/sounds", "default-provider": "gtk-common-themes", }, - "qt-6-themes": { - "interface": "content", - "target": "$SNAP/kf6", - "default-provider": "qt-common-themes", - }, platform_kf6_snap: { "content": content_kf6_snap, "interface": "content", diff --git a/tests/unit/extensions/test_kde_neon_6.py b/tests/unit/extensions/test_kde_neon_6.py index 564e432a7d..47340f290f 100644 --- a/tests/unit/extensions/test_kde_neon_6.py +++ b/tests/unit/extensions/test_kde_neon_6.py @@ -121,51 +121,16 @@ def test_get_root_snippet(kde_neon_6_extension): }, "plugs": { "desktop": {"mount-host-font-cache": False}, - "gtk-2-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "gtk-common-themes", - }, - "kde-gtk2-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "qt-common-themes", - }, - "kde-gtk3-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "qt-common-themes", - }, - "gtk-3-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "gtk-common-themes", - }, - "qt-icon-themes": { - "interface": "content", - "target": "$SNAP/data-dir/icons", - "default-provider": "qt-common-themes", - }, "icon-themes": { "interface": "content", "target": "$SNAP/data-dir/icons", "default-provider": "gtk-common-themes", }, - "qt-sound-themes": { - "interface": "content", - "target": "$SNAP/data-dir/sounds", - "default-provider": "qt-common-themes", - }, "sound-themes": { "interface": "content", "target": "$SNAP/data-dir/sounds", "default-provider": "gtk-common-themes", }, - "qt-6-themes": { - "interface": "content", - "target": "$SNAP/kf6", - "default-provider": "qt-common-themes", - }, "kf6-core22": { "content": "kf6-core22-all", "interface": "content", @@ -193,51 +158,16 @@ def test_get_root_snippet_with_external_sdk(kde_neon_6_extension_with_build_snap }, "plugs": { "desktop": {"mount-host-font-cache": False}, - "gtk-2-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "gtk-common-themes", - }, - "kde-gtk2-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "qt-common-themes", - }, - "kde-gtk3-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "qt-common-themes", - }, - "gtk-3-themes": { - "interface": "content", - "target": "$SNAP/data-dir/themes", - "default-provider": "gtk-common-themes", - }, - "qt-icon-themes": { - "interface": "content", - "target": "$SNAP/data-dir/icons", - "default-provider": "qt-common-themes", - }, "icon-themes": { "interface": "content", "target": "$SNAP/data-dir/icons", "default-provider": "gtk-common-themes", }, - "qt-sound-themes": { - "interface": "content", - "target": "$SNAP/data-dir/sounds", - "default-provider": "qt-common-themes", - }, "sound-themes": { "interface": "content", "target": "$SNAP/data-dir/sounds", "default-provider": "gtk-common-themes", }, - "qt-6-themes": { - "interface": "content", - "target": "$SNAP/kf6", - "default-provider": "qt-common-themes", - }, "kf6-core22": { "content": "kf6-core22-all", "interface": "content", From 49be97f168448adb22403ce8e4363b7fe2debf58 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:51:17 -0400 Subject: [PATCH 064/151] build(deps): update dependency craft-cli to v2.7.0 (hotfix/8.4) (#5023) --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index c38715751f..7129d8c2c3 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -26,7 +26,7 @@ colorama==0.4.6 coverage==7.6.1 craft-application==4.1.0 craft-archives==2.0.0 -craft-cli==2.6.0 +craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 6b481ca54f..dad9d275fe 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -21,7 +21,7 @@ click==8.1.7 colorama==0.4.6 craft-application==4.1.0 craft-archives==2.0.0 -craft-cli==2.6.0 +craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 diff --git a/requirements.txt b/requirements.txt index 0d9656807a..e83674d721 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ charset-normalizer==3.3.2 click==8.1.7 craft-application==4.1.0 craft-archives==2.0.0 -craft-cli==2.6.0 +craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 From 0d06f8006827c7e82345ad4ac13214c58671a19a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:51:32 -0400 Subject: [PATCH 065/151] build(deps): update dependency craft-cli to v2.7.0 (main) (#5021) --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index c38715751f..7129d8c2c3 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -26,7 +26,7 @@ colorama==0.4.6 coverage==7.6.1 craft-application==4.1.0 craft-archives==2.0.0 -craft-cli==2.6.0 +craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 6b481ca54f..dad9d275fe 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -21,7 +21,7 @@ click==8.1.7 colorama==0.4.6 craft-application==4.1.0 craft-archives==2.0.0 -craft-cli==2.6.0 +craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 diff --git a/requirements.txt b/requirements.txt index 0d9656807a..e83674d721 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ charset-normalizer==3.3.2 click==8.1.7 craft-application==4.1.0 craft-archives==2.0.0 -craft-cli==2.6.0 +craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 From 536541c5debd8c2e392434997ee7a1285e00a772 Mon Sep 17 00:00:00 2001 From: Callahan Date: Mon, 9 Sep 2024 12:30:52 -0500 Subject: [PATCH 066/151] feat(registries): add `list-registries` command (#5014) Add support for the `list-registries` command. Signed-off-by: Callahan Kovacs --- docs/reference/commands.rst | 5 + snapcraft/cli.py | 6 ++ snapcraft/commands/__init__.py | 2 + snapcraft/commands/registries.py | 71 +++++++++++++++ snapcraft/const.py | 16 ++++ snapcraft/models/__init__.py | 3 + snapcraft/models/assertions.py | 42 +++++++++ snapcraft/services/__init__.py | 4 + snapcraft/services/assertions.py | 105 +++++++++++++++++++++ snapcraft/services/registries.py | 70 ++++++++++++++ snapcraft/services/service_factory.py | 8 ++ snapcraft/store/client.py | 33 ++++++- tests/unit/commands/test_registries.py | 60 ++++++++++++ tests/unit/conftest.py | 42 +++++++++ tests/unit/models/test_assertions.py | 27 ++++++ tests/unit/services/test_assertions.py | 121 +++++++++++++++++++++++++ tests/unit/services/test_registries.py | 63 +++++++++++++ tests/unit/store/test_client.py | 90 +++++++++++++++++- 18 files changed, 766 insertions(+), 2 deletions(-) create mode 100644 snapcraft/commands/registries.py create mode 100644 snapcraft/models/assertions.py create mode 100644 snapcraft/services/assertions.py create mode 100644 snapcraft/services/registries.py create mode 100644 tests/unit/commands/test_registries.py create mode 100644 tests/unit/models/test_assertions.py create mode 100644 tests/unit/services/test_assertions.py create mode 100644 tests/unit/services/test_registries.py diff --git a/docs/reference/commands.rst b/docs/reference/commands.rst index a5b6603291..af0421bc2c 100644 --- a/docs/reference/commands.rst +++ b/docs/reference/commands.rst @@ -59,3 +59,8 @@ Store validation sets --------------------- .. include:: commands/store-validation-sets-commands.rst + +Store registries +---------------- + +.. include:: commands/store-registries-commands.rst diff --git a/snapcraft/cli.py b/snapcraft/cli.py index c916b9ff5e..7da38b0eab 100644 --- a/snapcraft/cli.py +++ b/snapcraft/cli.py @@ -140,6 +140,12 @@ commands.StoreLegacyGatedCommand, ], ), + craft_cli.CommandGroup( + "Store Registries", + [ + commands.StoreListRegistriesCommand, + ], + ), craft_cli.CommandGroup( "Other", [ diff --git a/snapcraft/commands/__init__.py b/snapcraft/commands/__init__.py index efc727d3b9..bd145d721c 100644 --- a/snapcraft/commands/__init__.py +++ b/snapcraft/commands/__init__.py @@ -52,6 +52,7 @@ StoreRegisterCommand, ) from .plugins import ListPluginsCommand, PluginsCommand +from .registries import StoreListRegistriesCommand from .remote import RemoteBuildCommand from .status import ( StoreListRevisionsCommand, @@ -92,6 +93,7 @@ "StoreLegacyUploadMetadataCommand", "StoreLegacyValidateCommand", "StoreListRevisionsCommand", + "StoreListRegistriesCommand", "StoreListTracksCommand", "StoreLoginCommand", "StoreLogoutCommand", diff --git a/snapcraft/commands/registries.py b/snapcraft/commands/registries.py new file mode 100644 index 0000000000..1c70a959b4 --- /dev/null +++ b/snapcraft/commands/registries.py @@ -0,0 +1,71 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Snapcraft Store Registry Sets commands.""" + +import argparse +import textwrap + +import craft_application.commands +from typing_extensions import override + +from snapcraft import const, services + + +class StoreListRegistriesCommand(craft_application.commands.AppCommand): + """List registries.""" + + name = "list-registries" + help_msg = "List registries" + overview = textwrap.dedent( + """ + List all registries for the authenticated account. + + Shows the account ID, name, revision, and last modified date of each registry. + + If a name is provided, only the registry with that name will be listed. + + Use the ``edit-registries`` command create and edit registries. + """ + ) + _services: services.SnapcraftServiceFactory # type: ignore[reportIncompatibleVariableOverride] + + @override + def fill_parser(self, parser: "argparse.ArgumentParser") -> None: + parser.add_argument( + "--name", + metavar="name", + required=False, + type=str, + help="Name of the registry to list", + ) + parser.add_argument( + "--format", + type=str, + choices=[ + const.OutputFormat.table.value, + const.OutputFormat.json.value, + ], + help="The output format (table | json)", + default=const.OutputFormat.table, + ) + + @override + def run(self, parsed_args: "argparse.Namespace"): + self._services.registries.list_assertions( + name=parsed_args.name, + output_format=parsed_args.format, + ) diff --git a/snapcraft/const.py b/snapcraft/const.py index 6543ea5519..546be083cf 100644 --- a/snapcraft/const.py +++ b/snapcraft/const.py @@ -36,6 +36,22 @@ def __str__(self) -> str: SUPPORTED_ARCHS = frozenset(arch.value for arch in SnapArch) + +class OutputFormat(str, enum.Enum): + """Output formats for snapcraft commands.""" + + json = "json" + table = "table" + + def __str__(self) -> str: + """Stringify the value.""" + return str(self.value) + + +OUTPUT_FORMATS = frozenset(output_format.value for output_format in OutputFormat) +"""Supported output formats for commands.""" + + BASES = frozenset({"core", "core18", "core20", "core22", "core24", "devel"}) """All bases recognized by snapcraft.""" diff --git a/snapcraft/models/__init__.py b/snapcraft/models/__init__.py index b0e3343a17..b61840fa6d 100644 --- a/snapcraft/models/__init__.py +++ b/snapcraft/models/__init__.py @@ -15,6 +15,7 @@ # along with this program. If not, see . """Data models for snapcraft.""" +from .assertions import Assertion, RegistryAssertion from .manifest import Manifest from .project import ( MANDATORY_ADOPTABLE_FIELDS, @@ -35,6 +36,7 @@ __all__ = [ "MANDATORY_ADOPTABLE_FIELDS", + "Assertion", "App", "Architecture", "ArchitectureProject", @@ -47,6 +49,7 @@ "Manifest", "Platform", "Project", + "RegistryAssertion", "SnapcraftBuildPlanner", "Socket", ] diff --git a/snapcraft/models/assertions.py b/snapcraft/models/assertions.py new file mode 100644 index 0000000000..8f1364173c --- /dev/null +++ b/snapcraft/models/assertions.py @@ -0,0 +1,42 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# 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 . + +"""Models for assertion sets.""" + +from typing import Any, Literal + +from craft_application import models + + +class RegistryAssertion(models.CraftBaseModel): + """Data model for a registry assertion.""" + + account_id: str + authority_id: str + body: dict[str, Any] | str | None = None + body_length: str | None = None + name: str + revision: int = 0 + sign_key_sha3_384: str | None = None + summary: str | None = None + timestamp: str + type: Literal["registry"] + views: dict[str, Any] + + +# this will be a union for validation sets and registries once +# validation sets are migrated from the legacy codebase +Assertion = RegistryAssertion diff --git a/snapcraft/services/__init__.py b/snapcraft/services/__init__.py index f0d0045b71..18994abf21 100644 --- a/snapcraft/services/__init__.py +++ b/snapcraft/services/__init__.py @@ -16,16 +16,20 @@ """Snapcraft services.""" +from snapcraft.services.assertions import AssertionService from snapcraft.services.lifecycle import Lifecycle from snapcraft.services.package import Package from snapcraft.services.provider import Provider +from snapcraft.services.registries import RegistriesService from snapcraft.services.remotebuild import RemoteBuild from snapcraft.services.service_factory import SnapcraftServiceFactory __all__ = [ + "AssertionService", "Lifecycle", "Package", "Provider", + "RegistriesService", "RemoteBuild", "SnapcraftServiceFactory", ] diff --git a/snapcraft/services/assertions.py b/snapcraft/services/assertions.py new file mode 100644 index 0000000000..7cd833eb8f --- /dev/null +++ b/snapcraft/services/assertions.py @@ -0,0 +1,105 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# 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 . + +"""Abstract service class for assertions.""" + +from __future__ import annotations + +import abc +import json +from typing import Any + +import craft_cli +import tabulate +from craft_application.services import base +from typing_extensions import override + +from snapcraft import const, errors, models, store + + +class AssertionService(base.AppService): + """Abstract service for interacting with assertions.""" + + @override + def setup(self) -> None: + """Application-specific service setup.""" + self._store_client = store.StoreClientCLI() + super().setup() + + @property + @abc.abstractmethod + def _assertion_type(self) -> str: + """The pluralized name of the assertion type.""" + + @abc.abstractmethod + def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: + """Get assertions from the store. + + :param name: The name of the assertion to retrieve. If not provided, all + assertions are retrieved. + + :returns: A list of assertions. + """ + + @abc.abstractmethod + def _normalize_assertions( + self, assertions: list[models.Assertion] + ) -> tuple[list[str], list[list[Any]]]: + """Convert a list of assertion models to a tuple of headers and data. + + :param assertions: A list of assertions to normalize. + + :returns: A tuple containing the headers and normalized assertions. + """ + + def list_assertions(self, *, output_format: str, name: str | None = None) -> None: + """List assertions from the store. + + :param output_format: The output format to render. + :param name: The name of the assertion to list. If not provided, all assertions + are listed. + + :raises FeatureNotImplemented: If the output format is not supported. + """ + assertions = self._get_assertions(name) + + if assertions: + headers, normalized_assertions = self._normalize_assertions(assertions) + match output_format: + case const.OutputFormat.json: + json_assertions = { + self._assertion_type.lower(): [ + { + header.lower(): value + for header, value in zip(headers, assertion) + } + for assertion in normalized_assertions + ] + } + craft_cli.emit.message(json.dumps(json_assertions, indent=4)) + case const.OutputFormat.table: + tabulated_sets = tabulate.tabulate( + normalized_assertions, + headers=headers, + tablefmt="plain", + ) + craft_cli.emit.message(tabulated_sets) + case _: + raise errors.FeatureNotImplemented( + msg=f"'--format {output_format}'", + ) + else: + craft_cli.emit.message(f"No {self._assertion_type} found.") diff --git a/snapcraft/services/registries.py b/snapcraft/services/registries.py new file mode 100644 index 0000000000..32aae29935 --- /dev/null +++ b/snapcraft/services/registries.py @@ -0,0 +1,70 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Abstract service class for assertions.""" + +from __future__ import annotations + +from typing import Any + +from typing_extensions import override + +from snapcraft import models +from snapcraft.services import AssertionService + + +class RegistriesService(AssertionService): + """Service for interacting with registries.""" + + @property + @override + def _assertion_type(self) -> str: + """The pluralized name of the assertion type.""" + return "registries" + + @override + def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: + """Get assertions from the store. + + :param name: The name of the assertion to retrieve. If not provided, all + assertions are retrieved. + + :returns: A list of assertions. + """ + return self._store_client.list_registries(name=name) + + @override + def _normalize_assertions( + self, assertions: list[models.Assertion] + ) -> tuple[list[str], list[list[Any]]]: + """Convert a list of assertion models to a tuple of headers and data. + + :param assertions: A list of assertions to normalize. + + :returns: A tuple containing the headers and normalized assertions. + """ + headers = ["Account ID", "Name", "Revision", "When"] + registries = [ + [ + assertion.account_id, + assertion.name, + assertion.revision, + assertion.timestamp[:10], + ] + for assertion in assertions + ] + + return headers, registries diff --git a/snapcraft/services/service_factory.py b/snapcraft/services/service_factory.py index c1c781425f..fdafbc530b 100644 --- a/snapcraft/services/service_factory.py +++ b/snapcraft/services/service_factory.py @@ -19,6 +19,7 @@ from __future__ import annotations from dataclasses import dataclass +from typing import TYPE_CHECKING from craft_application import ServiceFactory @@ -44,3 +45,10 @@ class SnapcraftServiceFactory(ServiceFactory): RemoteBuildClass: type[ # type: ignore[reportIncompatibleVariableOverride] services.RemoteBuild ] = services.RemoteBuild + RegistriesClass: type[ # type: ignore[reportIncompatibleVariableOverride] + services.RegistriesService + ] = services.RegistriesService + + if TYPE_CHECKING: + # Allow static type check to report correct types for Snapcraft services + registries: services.RegistriesService = None # type: ignore[assignment] diff --git a/snapcraft/store/client.py b/snapcraft/store/client.py index 9774ce7941..79e4d2d6d6 100644 --- a/snapcraft/store/client.py +++ b/snapcraft/store/client.py @@ -27,7 +27,7 @@ from craft_cli import emit from overrides import overrides -from snapcraft import __version__, errors, utils +from snapcraft import __version__, errors, models, utils from snapcraft_legacy.storeapi.v2.releases import Releases as Revisions from . import channel_map, constants @@ -503,6 +503,37 @@ def list_revisions(self, snap_name: str) -> Revisions: return Revisions.unmarshal(response.json()) + def list_registries( + self, *, name: str | None = None + ) -> list[models.RegistryAssertion]: + """Return a list of registries. + + :param name: If specified, only list the registry set with that name. + """ + endpoint = f"{self._base_url}/api/v2/registries" + if name: + endpoint += f"/{name}" + + response = self.request( + "GET", + endpoint, + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + ) + + registry_assertions = [] + if assertions := response.json().get("assertions"): + for assertion_data in assertions: + emit.debug(f"Parsing assertion: {assertion_data}") + assertion = models.RegistryAssertion.unmarshal( + assertion_data["headers"] + ) + registry_assertions.append(assertion) + + return registry_assertions + class OnPremStoreClientCLI(LegacyStoreClientCLI): """On Premises Store Client command line interface.""" diff --git a/tests/unit/commands/test_registries.py b/tests/unit/commands/test_registries.py new file mode 100644 index 0000000000..683f1b72f9 --- /dev/null +++ b/tests/unit/commands/test_registries.py @@ -0,0 +1,60 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys + +import pytest + +from snapcraft import application, const + + +@pytest.fixture +def mock_list_assertions(mocker): + return mocker.patch( + "snapcraft.services.registries.RegistriesService.list_assertions" + ) + + +@pytest.mark.usefixtures("memory_keyring") +@pytest.mark.parametrize("output_format", const.OUTPUT_FORMATS) +@pytest.mark.parametrize("name", [None, "test"]) +def test_list_registries(mocker, mock_list_assertions, output_format, name): + """Test `snapcraft list-registries`.""" + cmd = ["snapcraft", "list-registries", "--format", output_format] + if name: + cmd.extend(["--name", name]) + mocker.patch.object(sys, "argv", cmd) + + app = application.create_app() + app.run() + + mock_list_assertions.assert_called_once_with(name=name, output_format=output_format) + + +@pytest.mark.usefixtures("memory_keyring") +@pytest.mark.parametrize("name", [None, "test"]) +def test_list_registries_default_format(mocker, mock_list_assertions, name): + """Default format is 'table'.""" + """Test `snapcraft list-registries`.""" + cmd = ["snapcraft", "list-registries"] + if name: + cmd.extend(["--name", name]) + mocker.patch.object(sys, "argv", cmd) + + app = application.create_app() + app.run() + + mock_list_assertions.assert_called_once_with(name=name, output_format="table") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c3bcc5f309..04e061dad8 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -530,6 +530,48 @@ class FakeRemoteBuildService(RemoteBuild): return service +@pytest.fixture() +def registries_service(default_factory, mocker): + from snapcraft.application import APP_METADATA + from snapcraft.services import RegistriesService + + service = RegistriesService(app=APP_METADATA, services=default_factory) + service._store_client = mocker.patch( + "snapcraft.store.StoreClientCLI", autospec=True + ) + + return service + + +@pytest.fixture() +def fake_registry_assertion_data(): + """Returns a dictionary of assertion data with required fields.""" + from snapcraft.models import RegistryAssertion + + def _assertion_data(**kwargs) -> dict[str, Any]: + return { + "account_id": "test-account-id", + "authority_id": "test-authority-id", + "name": "test-registry", + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "access": "read-write", + "request": "ssids", + "storage": "wifi.ssids", + } + ] + } + }, + **kwargs, + } + + return RegistryAssertion.unmarshal(_assertion_data()) + + @pytest.fixture() def fake_services( default_factory, lifecycle_service, package_service, remote_build_service diff --git a/tests/unit/models/test_assertions.py b/tests/unit/models/test_assertions.py new file mode 100644 index 0000000000..d2653dcd71 --- /dev/null +++ b/tests/unit/models/test_assertions.py @@ -0,0 +1,27 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +"""Tests for Assertion models.""" + + +def test_assertion_defaults(fake_registry_assertion_data, check): + """Test default values of the RegistryAssertion model.""" + check.equal(fake_registry_assertion_data.body, None) + check.equal(fake_registry_assertion_data.body_length, None) + check.equal(fake_registry_assertion_data.sign_key_sha3_384, None) + check.equal(fake_registry_assertion_data.summary, None) + check.equal(fake_registry_assertion_data.revision, 0) diff --git a/tests/unit/services/test_assertions.py b/tests/unit/services/test_assertions.py new file mode 100644 index 0000000000..1d191f8277 --- /dev/null +++ b/tests/unit/services/test_assertions.py @@ -0,0 +1,121 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# 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 . + +"""Tests for the abstract assertions service.""" + +import textwrap +from typing import Any + +import pytest +from craft_application.models import CraftBaseModel +from typing_extensions import override + +from snapcraft import const, errors + + +class FakeAssertion(CraftBaseModel): + """Fake assertion model.""" + + test_field_1: str + test_field_2: int + + +@pytest.fixture +def mock_assertion_service(default_factory): + from snapcraft.application import APP_METADATA + from snapcraft.services import AssertionService + + class FakeAssertionService(AssertionService): + @property + @override + def _assertion_type(self) -> str: + return "fake-assertions" + + @override + def _get_assertions( # type: ignore[override] + self, name: str | None = None + ) -> list[FakeAssertion]: + return [ + FakeAssertion(test_field_1="test-value-1", test_field_2=0), + FakeAssertion(test_field_1="test-value-2", test_field_2=100), + ] + + @override + def _normalize_assertions( # type: ignore[override] + self, assertions: list[FakeAssertion] + ) -> tuple[list[str], list[list[Any]]]: + headers = ["test-field-1", "test-field-2"] + assertion_data = [ + [ + assertion.test_field_1, + assertion.test_field_2, + ] + for assertion in assertions + ] + return headers, assertion_data + + return FakeAssertionService(app=APP_METADATA, services=default_factory) + + +def test_list_assertions_table(mock_assertion_service, emitter): + """List assertions as a table.""" + mock_assertion_service.list_assertions( + output_format=const.OutputFormat.table, name="test-registry" + ) + + emitter.assert_message( + textwrap.dedent( + """\ + test-field-1 test-field-2 + test-value-1 0 + test-value-2 100""" + ) + ) + + +def test_list_assertions_json(mock_assertion_service, emitter): + """List assertions as json.""" + mock_assertion_service.list_assertions( + output_format=const.OutputFormat.json, name="test-registry" + ) + + emitter.assert_message( + textwrap.dedent( + """\ + { + "fake-assertions": [ + { + "test-field-1": "test-value-1", + "test-field-2": 0 + }, + { + "test-field-1": "test-value-2", + "test-field-2": 100 + } + ] + }""" + ) + ) + + +def test_list_assertions_unknown_format(mock_assertion_service): + """Error for unknown formats.""" + expected = "Command or feature not implemented: '--format unknown'" + + with pytest.raises(errors.FeatureNotImplemented, match=expected): + mock_assertion_service.list_assertions( + output_format="unknown", name="test-registry" + ) diff --git a/tests/unit/services/test_registries.py b/tests/unit/services/test_registries.py new file mode 100644 index 0000000000..69a9a52e76 --- /dev/null +++ b/tests/unit/services/test_registries.py @@ -0,0 +1,63 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# 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 . + +"""Tests for the registries service.""" + + +def test_registries_service_type(registries_service): + assert registries_service._assertion_type == "registries" + + +def test_get_assertions(registries_service): + registries_service._get_assertions("test-registry") + + registries_service._store_client.list_registries.assert_called_once_with( + name="test-registry" + ) + + +def test_normalize_assertions_empty(registries_service, check): + headers, registries = registries_service._normalize_assertions([]) + + check.equal(headers, ["Account ID", "Name", "Revision", "When"]) + check.equal(registries, []) + + +def test_normalize_assertions(fake_registry_assertion_data, registries_service, check): + registries = [ + fake_registry_assertion_data, + fake_registry_assertion_data.copy( + update={ + "account_id": "test-account-id-2", + "name": "test-registry-2", + "revision": 100, + "timestamp": "2024-12-31", + } + ), + ] + + headers, normalized_registries = registries_service._normalize_assertions( + registries + ) + + check.equal(headers, ["Account ID", "Name", "Revision", "When"]) + check.equal( + normalized_registries, + [ + ["test-account-id", "test-registry", 0, "2024-01-01"], + ["test-account-id-2", "test-registry-2", 100, "2024-12-31"], + ], + ) diff --git a/tests/unit/store/test_client.py b/tests/unit/store/test_client.py index 359067bf9b..160132cbdc 100644 --- a/tests/unit/store/test_client.py +++ b/tests/unit/store/test_client.py @@ -25,7 +25,7 @@ from craft_store import endpoints from craft_store.models import RevisionsResponseModel -from snapcraft import errors +from snapcraft import errors, models from snapcraft.store import LegacyUbuntuOne, client, constants from snapcraft.store.channel_map import ChannelMap from snapcraft.utils import OSPlatform @@ -171,6 +171,38 @@ def list_revisions_payload(): } +@pytest.fixture +def list_registries_payload(): + return { + "assertions": [ + { + "headers": { + "account-id": "test-account-id", + "authority-id": "test-authority-id", + "body-length": "92", + "name": "test-registry", + "revision": "9", + "sign-key-sha3-384": "test-sign-key", + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "access": "read-write", + "request": "ssids", + "storage": "wifi.ssids", + } + ] + } + }, + }, + "body": '{\n "storage": {\n "schema": {\n "wifi": {\n "values": "any"\n }\n }\n }\n}', + }, + ], + } + + #################### # User Agent Tests # #################### @@ -1052,6 +1084,62 @@ def test_list_revisions(fake_client, list_revisions_payload): ] +################### +# List Registries # +################### + + +@pytest.mark.parametrize("name", [None, "test-registry"]) +def test_list_registries(name, fake_client, list_registries_payload, check): + """Test the registries endpoint.""" + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(list_registries_payload).encode() + ) + + registries = client.StoreClientCLI().list_registries(name=name) + + check.is_instance(registries, list) + for registry in registries: + check.is_instance(registry, models.RegistryAssertion) + check.equal( + fake_client.request.mock_calls, + [ + call( + "GET", + f"https://dashboard.snapcraft.io/api/v2/registries{f'/{name}' if name else ''}", + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + ) + ], + ) + + +def test_list_registries_empty(fake_client, check): + """Test the registries endpoint with no registries returned.""" + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps({"assertions": []}).encode() + ) + + registries = client.StoreClientCLI().list_registries() + + check.equal(registries, []) + check.equal( + fake_client.request.mock_calls, + [ + call( + "GET", + "https://dashboard.snapcraft.io/api/v2/registries", + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + ) + ], + ) + + ######################## # OnPremStoreClientCLI # ######################## From bfe53b5a7c40f21f693d68a2a4f79688eecb1087 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:17:01 +0000 Subject: [PATCH 067/151] build(deps): update bugfixes (hotfix/8.4) (#5024) --- docs/.sphinx/pinned-requirements.txt | 2 +- requirements-devel.txt | 22 +++++++++---------- requirements-docs.txt | 14 ++++++------ requirements.txt | 12 +++++----- .../build-for-no-match/expected-failure.txt | 2 +- .../build-on-no-match/expected-failure.txt | 2 +- .../env-var-no-match/expected-failure.txt | 2 +- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index dc494d914b..9a3bb3b63e 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -31,4 +31,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.8 sphinxcontrib-serializinghtml==1.1.10 tornado==6.4.1 -urllib3==1.26.19 +urllib3==1.26.20 diff --git a/requirements-devel.txt b/requirements-devel.txt index 7129d8c2c3..db2aba6fe6 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -17,20 +17,20 @@ canonical-sphinx==0.1.0 canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 certifi==2024.7.4 -cffi==1.17.0 +cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.1.0 +craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 -craft-providers==2.0.0 +craft-providers==2.0.1 craft-store==3.0.0 cryptography==43.0.1 cssutils==2.11.1 @@ -76,7 +76,7 @@ mdit-py-plugins==0.4.1 mdurl==0.1.2 more-itertools==10.4.0 msgpack==1.0.8 -mypy==1.11.1 +mypy==1.11.2 mypy-extensions==1.0.0 myst-parser==4.0.0 natsort==8.4.0 @@ -107,11 +107,11 @@ pyflakes==3.2.0 pyftpdlib==1.5.10 pygit2==1.13.3 Pygments==2.18.0 -pylint==3.2.6 +pylint==3.2.7 pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 -pyparsing==3.1.2 +pyparsing==3.1.4 pyproject-api==1.7.1 pyramid==2.0.2 pyRFC3339==1.1 @@ -164,7 +164,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.2 +starlette==0.38.4 tabulate==0.9.0 testscenarios==0.5.0 testtools==2.7.2 @@ -182,7 +182,7 @@ types-toml==0.10.8.20240310 types-urllib3==1.26.25.14 typing_extensions==4.12.2 uc-micro-py==1.0.3 -urllib3==1.26.19 +urllib3==1.26.20 uvicorn==0.30.6 validators==0.33.0 venusian==3.1.0 @@ -196,8 +196,8 @@ websockets==12.0 wheel==0.44.0 ws4py==0.5.1 yamllint==1.35.1 -zipp==3.20.0 +zipp==3.20.1 zope.deprecation==5.0 -zope.interface==7.0.1 +zope.interface==7.0.3 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" -pyinstaller==5.13.1; sys.platform == "win32" +pyinstaller==5.13.2; sys.platform == "win32" diff --git a/requirements-docs.txt b/requirements-docs.txt index dad9d275fe..9de9e51bab 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -14,18 +14,18 @@ canonical-sphinx==0.1.0 canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 certifi==2024.7.4 -cffi==1.17.0 +cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.1.0 +craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 -craft-providers==2.0.0 +craft-providers==2.0.1 craft-store==3.0.0 cryptography==43.0.1 cssutils==2.11.1 @@ -84,7 +84,7 @@ Pygments==2.18.0 pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 -pyparsing==3.1.2 +pyparsing==3.1.4 pyRFC3339==1.1 pyspelling==2.10 python-dateutil==2.9.0.post0 @@ -130,13 +130,13 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.2 +starlette==0.38.4 tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 typing_extensions==4.12.2 uc-micro-py==1.0.3 -urllib3==1.26.19 +urllib3==1.26.20 uvicorn==0.30.6 validators==0.33.0 wadllib==1.3.6 @@ -146,5 +146,5 @@ webencodings==0.5.1 websockets==12.0 wheel==0.44.0 ws4py==0.5.1 -zipp==3.20.0 +zipp==3.20.1 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" diff --git a/requirements.txt b/requirements.txt index e83674d721..06a5668896 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,17 +3,17 @@ attrs==24.2.0 boolean.py==4.0 catkin-pkg==1.0.0 certifi==2024.7.4 -cffi==1.17.0 +cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.1.0 +craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 craft-parts==2.0.0 craft-platforms==0.1.1 -craft-providers==2.0.0 +craft-providers==2.0.1 craft-store==3.0.0 cryptography==43.0.1 distro==1.9.0 @@ -50,7 +50,7 @@ pygit2==1.13.3 pylxd==2.3.4 pymacaroons==0.13.0 PyNaCl==1.5.0 -pyparsing==3.1.2 +pyparsing==3.1.4 pyRFC3339==1.1 python-dateutil==2.9.0.post0 python-debian==0.1.49 @@ -72,10 +72,10 @@ tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 typing_extensions==4.12.2 -urllib3==1.26.19 +urllib3==1.26.20 validators==0.33.0 wadllib==1.3.6 wheel==0.44.0 ws4py==0.5.1 -zipp==3.20.0 +zipp==3.20.1 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" diff --git a/tests/spread/core24/platforms/snaps/build-for-no-match/expected-failure.txt b/tests/spread/core24/platforms/snaps/build-for-no-match/expected-failure.txt index 1670a251ed..0342785b42 100644 --- a/tests/spread/core24/platforms/snaps/build-for-no-match/expected-failure.txt +++ b/tests/spread/core24/platforms/snaps/build-for-no-match/expected-failure.txt @@ -1 +1 @@ -No build matches the current platform. +No build matches the current execution environment. diff --git a/tests/spread/core24/platforms/snaps/build-on-no-match/expected-failure.txt b/tests/spread/core24/platforms/snaps/build-on-no-match/expected-failure.txt index 1670a251ed..0342785b42 100644 --- a/tests/spread/core24/platforms/snaps/build-on-no-match/expected-failure.txt +++ b/tests/spread/core24/platforms/snaps/build-on-no-match/expected-failure.txt @@ -1 +1 @@ -No build matches the current platform. +No build matches the current execution environment. diff --git a/tests/spread/core24/platforms/snaps/env-var-no-match/expected-failure.txt b/tests/spread/core24/platforms/snaps/env-var-no-match/expected-failure.txt index 1670a251ed..0342785b42 100644 --- a/tests/spread/core24/platforms/snaps/env-var-no-match/expected-failure.txt +++ b/tests/spread/core24/platforms/snaps/env-var-no-match/expected-failure.txt @@ -1 +1 @@ -No build matches the current platform. +No build matches the current execution environment. From 0caaaaf45e5b56f6e4e8531ec533d93746ee7fa1 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 9 Sep 2024 15:06:30 -0500 Subject: [PATCH 068/151] docs(changelog): add 8.3.3 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index 20572bda28..7ab21d7954 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -78,6 +78,27 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.3.3 (2024-Aug-28) +------------------- + +Core +==== + +* Improve detection and error messages when LXD is not installed or not + properly enabled. + +Bases +##### + +core24 +"""""" + +* Require Multipass >= ``1.14.1`` when using Multipass to build ``core24`` + snaps. + +For a complete list of commits, check out the `8.3.3`_ release on GitHub. + + 7.5.6 (2024-Aug-15) ------------------- @@ -1043,3 +1064,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.0: https://github.com/canonical/snapcraft/releases/tag/8.3.0 .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 .. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 +.. _8.3.3: https://github.com/canonical/snapcraft/releases/tag/8.3.3 From 1b44db714885bc61acf17ab18754b41701fc23f9 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 10 Sep 2024 08:15:10 -0500 Subject: [PATCH 069/151] build(deps): bump craft-parts to 2.1.0 craft-parts 2.1.0 adds a Poetry plugin. This plugin disabled until the integration work in #5025 is completed. Signed-off-by: Callahan Kovacs --- docs/conf.py | 1 + requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- snapcraft/application.py | 3 +++ tests/unit/parts/plugins/test_python_plugin.py | 2 +- tests/unit/test_application.py | 11 +++++++++++ 8 files changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 264a6c5c60..e2ba3c7475 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -72,6 +72,7 @@ "common/craft-parts/reference/parts_steps.rst", "common/craft-parts/reference/step_execution_environment.rst", "common/craft-parts/reference/step_output_directories.rst", + "common/craft-parts/reference/plugins/poetry_plugin.rst", "common/craft-parts/reference/plugins/python_plugin.rst", "common/craft-parts/reference/plugins/maven_plugin.rst", # Extra non-craft-parts exclusions can be added after this comment diff --git a/requirements-devel.txt b/requirements-devel.txt index db2aba6fe6..714e376a69 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -28,7 +28,7 @@ craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 -craft-parts==2.0.0 +craft-parts==2.1.0 craft-platforms==0.1.1 craft-providers==2.0.1 craft-store==3.0.0 diff --git a/requirements-docs.txt b/requirements-docs.txt index 9de9e51bab..11f007664e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -23,7 +23,7 @@ craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 -craft-parts==2.0.0 +craft-parts==2.1.0 craft-platforms==0.1.1 craft-providers==2.0.1 craft-store==3.0.0 diff --git a/requirements.txt b/requirements.txt index 06a5668896..cd628a7231 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 -craft-parts==2.0.0 +craft-parts==2.1.0 craft-platforms==0.1.1 craft-providers==2.0.1 craft-store==3.0.0 diff --git a/setup.py b/setup.py index 5eeeb70cb1..7b2a06c73e 100755 --- a/setup.py +++ b/setup.py @@ -102,7 +102,7 @@ def recursive_data_files(directory, install_directory): "craft-archives~=2.0", "craft-cli~=2.6", "craft-grammar~=2.0", - "craft-parts~=2.0", + "craft-parts~=2.1", "craft-platforms~=0.1", "craft-providers~=2.0", "craft-store~=3.0", diff --git a/snapcraft/application.py b/snapcraft/application.py index b26749d264..706dd87b21 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -134,6 +134,9 @@ def _register_default_plugins(self) -> None: """Register per application plugins when initializing.""" super()._register_default_plugins() + # poetry plugin needs integration work, see #5025 + craft_parts.plugins.unregister("poetry") + if self._known_core24: # dotnet is disabled for core24 and newer because it is pending a rewrite craft_parts.plugins.unregister("dotnet") diff --git a/tests/unit/parts/plugins/test_python_plugin.py b/tests/unit/parts/plugins/test_python_plugin.py index c4cc654dca..d41f6d295a 100644 --- a/tests/unit/parts/plugins/test_python_plugin.py +++ b/tests/unit/parts/plugins/test_python_plugin.py @@ -107,7 +107,7 @@ def test_get_build_commands(plugin, new_dir): eval "${{opts_state}}" """ ), - 'ln -sf "${symlink_target}" "${PARTS_PYTHON_VENV_INTERP_PATH}"\n', + 'ln -sf "${symlink_target}" "${PARTS_PYTHON_VENV_INTERP_PATH}"', ] diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 17287a5d0d..3f507b8663 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -353,6 +353,17 @@ def test_application_dotnet_not_registered(base, build_base, snapcraft_yaml): assert "dotnet" not in craft_parts.plugins.get_registered_plugins() +@pytest.mark.parametrize("base", const.CURRENT_BASES) +def test_application_poetry_not_registered(base, snapcraft_yaml): + """poetry plugin is disabled for all bases.""" + snapcraft_yaml(base=base) + app = application.create_app() + + app._register_default_plugins() + + assert "poetry" not in craft_parts.plugins.get_registered_plugins() + + def test_default_command_integrated(monkeypatch, mocker, new_dir): """Test that for core24 projects we accept "pack" as the default command.""" From b5a564b50e4d80148f3ea7c8f4a236c3e6b83e75 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 9 Sep 2024 16:22:09 -0500 Subject: [PATCH 070/151] docs(changelog): add 8.4.0 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 125 ++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index 20572bda28..803e213ca9 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -25,21 +25,12 @@ Changelog """""""" - List plugins - """""""""""" - Extensions ########## """"""""""" - Expand extensions - """"""""""""""""" - - List extensions - """"""""""""""" - Metadata ######## @@ -78,6 +69,109 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.4.0 (2024-Sep-10) +------------------- + +.. note:: + + 8.4.0 includes changes from the :ref:`7.5.6<7.5.6_changelog>` release. + +Core +==== + +* Fix a bug where Snapcraft would fail to inject itself into the build + environment when not running as a snap (`canonical/charmcraft#406`_). If an + app isn't running from snap, the installed app will now install the snap in + the build environment using the channel in the ``CRAFT_SNAP_CHANNEL`` + environment variable, defaulting to ``latest/stable`` if none is set. + +* Fix a regression where icons wouldn't be configured and installed for snaps + with no ``apps`` defined in their ``snapcraft.yaml``. + +Bases +##### + +core24 +"""""" + +* Raise an error if the build plan is empty and no snaps will be built + (`canonical/craft-application#225`_). + +* Fix a regression where ``https_proxy``, ``https_proxy``, and ``no_proxy`` + were not forwarded into the build environment. + +Plugins +####### + +* Fix a bug where ``snapcraft list-plugins --base core20`` would fail in a + ``core24`` project directory (`#5008`_). + +Components +########## + +* Allow numbers and hyphens in component names (`LP#2069783`_). + +* Fix a bug where ``stage-packages`` can't be used when components are defined + (`canonical/craft-parts#804`_). + +Command line +============ + +* Improve error messages when parsing a ``snapcraft.yaml`` file (`#4941`_). + +* Improve error messages when using an `ESM base`_. + +* Improve error messages for missing files (`canonical/craft-parts#802`_). + +* Improve error messages when a build fails because it matches multiple + platforms (`canonical/craft-application#382`_). + +* Fix a bug where multi-line error messages would overwrite the previous line + (`canonical/craft-cli#270`_). + +Remote build +============ + +* Add "Pending" status for queued remote builds. + +* Add documentation links to remote-build errors. + +* Improve error messages when multiple snaps can be built on a single + ``build-on`` architecture (`#4995`_). + +* Improve error messages when using the wrong remote builder. + +* Fix a regression where ``--platform`` or ``--build-for`` could be used when + ``platforms`` or ``architectures`` were defined in the ``snapcraft.yaml`` + file (`#4881`_). + +* Fix a regression where ``--platform`` could be used for ``core22`` snaps + (`#4881`_). + +* Fix a bug where ``SNAPCRAFT_REMOTE_BUILD_STRATEGY`` would be validated when + running commands other than ``remote-build``. + +* Fix a bug where ``SNAPCRAFT_REMOTE_BUILD_STRATEGY`` was ignored for + ``core24`` snaps. + +Documentation +============= + +* Add changelog notes for all Snapcraft 8.x releases + +* Add :doc:`reference`, + :doc:`explanation`, and + :doc:`how-to` for components. + +* Add :doc:`reference`, + :doc:`explanation`, and + :doc:`how-to` for bases. + +For a complete list of commits, check out the `8.4.0`_ release on GitHub. + + +.. _7.5.6_changelog: + 7.5.6 (2024-Aug-15) ------------------- @@ -960,6 +1054,7 @@ https://snapcraft.io/docs/snapcraft-authentication. For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _advanced grammar: https://snapcraft.io/docs/snapcraft-advanced-grammar +.. _ESM base: https://snapcraft.io/docs/snapcraft-esm .. _canonical-sphinx: https://github.com/canonical/canonical-sphinx .. _core24 migration guide: https://snapcraft.io/docs/migrate-core24 .. _Craft Application: https://github.com/canonical/craft-application @@ -971,11 +1066,18 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _PEP 518: https://peps.python.org/pep-0518/ .. _ROS architectures with snaps: https://ubuntu.com/robotics/docs/ros-architectures-with-snaps. +.. _canonical/charmcraft#406: https://github.com/canonical/charmcraft/issues/406 +.. _canonical/craft-application#225: https://github.com/canonical/craft-application/pull/225 .. _canonical/craft-application#355: https://github.com/canonical/craft-application/pull/355 +.. _canonical/craft-application#382: https://github.com/canonical/craft-application/pull/382 +.. _canonical/craft-cli#270: https://github.com/canonical/craft-parts/issues/270 .. _canonical/craft-parts#717: https://github.com/canonical/craft-parts/issues/717 +.. _canonical/craft-parts#802: https://github.com/canonical/craft-parts/issues/802 +.. _canonical/craft-parts#804: https://github.com/canonical/craft-parts/issues/804 .. _LP#2061603: https://bugs.launchpad.net/snapcraft/+bug/2061603 .. _LP#2064639: https://bugs.launchpad.net/snapcraft/+bug/2064639 +.. _LP#2069783: https://bugs.launchpad.net/snapcraft/+bug/2069783 .. _#4142: https://github.com/canonical/snapcraft/issues/4142 .. _#4356: https://github.com/canonical/snapcraft/issues/4356 @@ -1013,11 +1115,15 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4842: https://github.com/canonical/snapcraft/issues/4842 .. _#4854: https://github.com/canonical/snapcraft/issues/4854 .. _#4865: https://github.com/canonical/snapcraft/issues/4865 +.. _#4881: https://github.com/canonical/snapcraft/issues/4881 .. _#4886: https://github.com/canonical/snapcraft/issues/4886 .. _#4889: https://github.com/canonical/snapcraft/issues/4889 .. _#4890: https://github.com/canonical/snapcraft/issues/4890 .. _#4909: https://github.com/canonical/snapcraft/issues/4909 +.. _#4941: https://github.com/canonical/snapcraft/issues/4941 .. _#4942: https://github.com/canonical/snapcraft/issues/4942 +.. _#4995: https://github.com/canonical/snapcraft/issues/4995 +.. _#5008: https://github.com/canonical/snapcraft/issues/5008 .. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 .. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 @@ -1043,3 +1149,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.0: https://github.com/canonical/snapcraft/releases/tag/8.3.0 .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 .. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 +.. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 From 189edf429448e73bd6f0a1d2bc46e96b47f0cc8c Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 11 Sep 2024 09:23:44 -0500 Subject: [PATCH 071/151] fix(remotebuild): parse comma-separated architectures Signed-off-by: Callahan Kovacs --- snapcraft/commands/remote.py | 85 ++++++++++--------- .../snaps/build-for-arg/arguments.txt | 1 + .../snaps/build-for-arg/expected-snaps.txt | 2 + .../snaps/build-for-arg/snapcraft.yaml | 12 +++ tests/spread/core22/remote-build/task.yaml | 9 +- .../snaps/build-for-arg/arguments.txt | 1 + .../snaps/build-for-arg/expected-snaps.txt | 2 + .../snaps/build-for-arg/snapcraft.yaml | 12 +++ .../snaps/platform-arg/arguments.txt | 1 + .../snaps/platform-arg/expected-snaps.txt | 2 + .../snaps/platform-arg/snapcraft.yaml | 12 +++ tests/spread/core24/remote-build/task.yaml | 10 ++- tests/unit/commands/test_remote.py | 68 ++++++++++++--- 13 files changed, 164 insertions(+), 53 deletions(-) create mode 100644 tests/spread/core22/remote-build/snaps/build-for-arg/arguments.txt create mode 100644 tests/spread/core22/remote-build/snaps/build-for-arg/expected-snaps.txt create mode 100644 tests/spread/core22/remote-build/snaps/build-for-arg/snapcraft.yaml create mode 100644 tests/spread/core24/remote-build/snaps/build-for-arg/arguments.txt create mode 100644 tests/spread/core24/remote-build/snaps/build-for-arg/expected-snaps.txt create mode 100644 tests/spread/core24/remote-build/snaps/build-for-arg/snapcraft.yaml create mode 100644 tests/spread/core24/remote-build/snaps/platform-arg/arguments.txt create mode 100644 tests/spread/core24/remote-build/snaps/platform-arg/expected-snaps.txt create mode 100644 tests/spread/core24/remote-build/snaps/platform-arg/snapcraft.yaml diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index fd815321d1..2ac7b0e2b3 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -77,51 +77,51 @@ class RemoteBuildCommand(ExtensibleCommand): @overrides def _fill_parser(self, parser: argparse.ArgumentParser) -> None: parser.add_argument( - "--recover", action="store_true", help="recover an interrupted build" + "--recover", action="store_true", help="Recover an interrupted build" ) parser.add_argument( "--launchpad-accept-public-upload", action="store_true", - help="acknowledge that uploaded code will be publicly available.", + help="Acknowledge that uploaded code will be publicly available", ) parser.add_argument( "--launchpad-timeout", type=int, default=0, metavar="", - help="Time in seconds to wait for launchpad to build.", + help="Time in seconds to wait for launchpad to build", ) parser.add_argument( - "--status", action="store_true", help="display remote build status" + "--status", action="store_true", help="Display remote build status" ) parser.add_argument( - "--build-id", metavar="build-id", help="specific build id to retrieve" + "--build-id", metavar="build-id", help="Specific build ID to retrieve" ) group = parser.add_mutually_exclusive_group() group.add_argument( "--platform", - type=str, + type=lambda arg: [arch.strip() for arch in arg.split(",")], metavar="name", default=os.getenv("CRAFT_PLATFORM"), - help="Set platform to build for", + help="Comma-separated list of platforms to build for", # '--platform' needs to be handled differently since remote-build can # build for an architecture that is not in the project metadata - dest="remote_build_platform", + dest="remote_build_platforms", ) group.add_argument( "--build-for", - type=str, + type=lambda arg: [arch.strip() for arch in arg.split(",")], metavar="arch", default=os.getenv("CRAFT_BUILD_FOR"), - help="Set architecture to build for", + help="Comma-separated list of architectures to build for", # '--build-for' needs to be handled differently since remote-build can # build for architecture that is not in the project metadata - dest="remote_build_build_for", + dest="remote_build_build_fors", ) parser.add_argument( - "--project", help="upload to the specified Launchpad project" + "--project", help="Upload to the specified Launchpad project" ) def _validate(self, parsed_args: argparse.Namespace) -> None: @@ -154,9 +154,9 @@ def _validate(self, parsed_args: argparse.Namespace) -> None: retcode=os.EX_NOPERM, ) - build_for = parsed_args.remote_build_build_for or None - platform = parsed_args.remote_build_platform or None - parameter = "--build-for" if build_for else "--platform" if platform else None + build_fors = parsed_args.remote_build_build_fors or None + platforms = parsed_args.remote_build_platforms or None + parameter = "--build-for" if build_fors else "--platform" if platforms else None keyword = ( "architectures" if self.project._architectures_in_yaml @@ -171,7 +171,7 @@ def _validate(self, parsed_args: argparse.Namespace) -> None: retcode=os.EX_CONFIG, ) - if platform: + if platforms: if self.project.get_effective_base() == "core22": raise errors.RemoteBuildError( "'--platform' cannot be used for core22 snaps.", @@ -179,27 +179,30 @@ def _validate(self, parsed_args: argparse.Namespace) -> None: doc_slug="/explanation/remote-build.html", retcode=os.EX_CONFIG, ) - if platform not in SUPPORTED_ARCHS: - raise errors.RemoteBuildError( - f"Unsupported platform {parsed_args.remote_build_platform!r}.", - resolution=( - "Use a supported debian architecture. Supported " - f"architectures are: {humanize_list(SUPPORTED_ARCHS, 'and')}" - ), - doc_slug="/explanation/remote-build.html", - retcode=os.EX_CONFIG, - ) - - if build_for and build_for not in SUPPORTED_ARCHS: - raise errors.RemoteBuildError( - f"Unsupported build-for architecture {parsed_args.remote_build_build_for!r}.", - resolution=( - "Use a supported debian architecture. Supported " - f"architectures are: {humanize_list(SUPPORTED_ARCHS, 'and')}" - ), - doc_slug="/explanation/remote-build.html", - retcode=os.EX_CONFIG, - ) + for platform in platforms: + if platform not in SUPPORTED_ARCHS: + raise errors.RemoteBuildError( + f"Unsupported platform {platform!r}.", + resolution=( + "Use a supported debian architecture. Supported " + f"architectures are: {humanize_list(SUPPORTED_ARCHS, 'and')}" + ), + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, + ) + + if build_fors: + for build_for in build_fors: + if build_for not in SUPPORTED_ARCHS: + raise errors.RemoteBuildError( + f"Unsupported build-for architecture {build_for!r}.", + resolution=( + "Use a supported debian architecture. Supported " + f"architectures are: {humanize_list(SUPPORTED_ARCHS, 'and')}" + ), + doc_slug="/explanation/remote-build.html", + retcode=os.EX_CONFIG, + ) self._validate_single_artifact_per_build_on() @@ -271,10 +274,10 @@ def _run( # noqa: PLR0915 [too-many-statements] emit.trace(f"Project directory: {project_dir}") self._validate(parsed_args) - if parsed_args.remote_build_build_for: - architectures = [parsed_args.remote_build_build_for] - elif parsed_args.remote_build_platform: - architectures = [parsed_args.remote_build_platform] + if parsed_args.remote_build_build_fors: + architectures = parsed_args.remote_build_build_fors + elif parsed_args.remote_build_platforms: + architectures = parsed_args.remote_build_platforms else: architectures = self._get_project_build_fors() diff --git a/tests/spread/core22/remote-build/snaps/build-for-arg/arguments.txt b/tests/spread/core22/remote-build/snaps/build-for-arg/arguments.txt new file mode 100644 index 0000000000..c9eaf14f09 --- /dev/null +++ b/tests/spread/core22/remote-build/snaps/build-for-arg/arguments.txt @@ -0,0 +1 @@ +--build-for amd64,s390x diff --git a/tests/spread/core22/remote-build/snaps/build-for-arg/expected-snaps.txt b/tests/spread/core22/remote-build/snaps/build-for-arg/expected-snaps.txt new file mode 100644 index 0000000000..5b9051821a --- /dev/null +++ b/tests/spread/core22/remote-build/snaps/build-for-arg/expected-snaps.txt @@ -0,0 +1,2 @@ +test-snap-build-for-arg-core22_1.0_amd64.snap +test-snap-build-for-arg-core22_1.0_s390x.snap diff --git a/tests/spread/core22/remote-build/snaps/build-for-arg/snapcraft.yaml b/tests/spread/core22/remote-build/snaps/build-for-arg/snapcraft.yaml new file mode 100644 index 0000000000..4dede42d0d --- /dev/null +++ b/tests/spread/core22/remote-build/snaps/build-for-arg/snapcraft.yaml @@ -0,0 +1,12 @@ +name: test-snap-build-for-arg-core22 +base: core22 +version: "1.0" +summary: Test snap for remote build +description: Test snap for remote build + +grade: stable +confinement: strict + +parts: + my-part: + plugin: nil diff --git a/tests/spread/core22/remote-build/task.yaml b/tests/spread/core22/remote-build/task.yaml index 06faeb0703..feee247997 100644 --- a/tests/spread/core22/remote-build/task.yaml +++ b/tests/spread/core22/remote-build/task.yaml @@ -8,6 +8,7 @@ environment: SNAP/all: all SNAP/no_architectures: no-architectures SNAP/architectures: architectures + SNAP/build_for_arg: build-for-arg SNAPCRAFT_REMOTE_BUILD_STRATEGY: disable-fallback CREDENTIALS_FILE: "$HOME/.local/share/snapcraft/launchpad-credentials" CREDENTIALS_FILE/new_credentials: "$HOME/.local/share/snapcraft/launchpad-credentials" @@ -39,7 +40,13 @@ restore: | execute: | cd "./snaps/$SNAP" - snapcraft remote-build --launchpad-accept-public-upload + call_args="" + if [[ -e "arguments.txt" ]]; then + call_args=$(cat "arguments.txt") + fi + + # shellcheck disable=SC2086 + snapcraft remote-build --launchpad-accept-public-upload $call_args find . -maxdepth 1 -name "*.snap" | MATCH ".snap" diff --git a/tests/spread/core24/remote-build/snaps/build-for-arg/arguments.txt b/tests/spread/core24/remote-build/snaps/build-for-arg/arguments.txt new file mode 100644 index 0000000000..c9eaf14f09 --- /dev/null +++ b/tests/spread/core24/remote-build/snaps/build-for-arg/arguments.txt @@ -0,0 +1 @@ +--build-for amd64,s390x diff --git a/tests/spread/core24/remote-build/snaps/build-for-arg/expected-snaps.txt b/tests/spread/core24/remote-build/snaps/build-for-arg/expected-snaps.txt new file mode 100644 index 0000000000..ecd71f79c8 --- /dev/null +++ b/tests/spread/core24/remote-build/snaps/build-for-arg/expected-snaps.txt @@ -0,0 +1,2 @@ +test-snap-build-for-arg-core24_1.0_amd64.snap +test-snap-build-for-arg-core24_1.0_s390x.snap diff --git a/tests/spread/core24/remote-build/snaps/build-for-arg/snapcraft.yaml b/tests/spread/core24/remote-build/snaps/build-for-arg/snapcraft.yaml new file mode 100644 index 0000000000..1bafa9fbc4 --- /dev/null +++ b/tests/spread/core24/remote-build/snaps/build-for-arg/snapcraft.yaml @@ -0,0 +1,12 @@ +name: test-snap-build-for-arg-core24 +base: core24 +version: "1.0" +summary: Test snap for remote build +description: Test snap for remote build + +grade: stable +confinement: strict + +parts: + my-part: + plugin: nil diff --git a/tests/spread/core24/remote-build/snaps/platform-arg/arguments.txt b/tests/spread/core24/remote-build/snaps/platform-arg/arguments.txt new file mode 100644 index 0000000000..dddd614311 --- /dev/null +++ b/tests/spread/core24/remote-build/snaps/platform-arg/arguments.txt @@ -0,0 +1 @@ +--platform amd64,s390x diff --git a/tests/spread/core24/remote-build/snaps/platform-arg/expected-snaps.txt b/tests/spread/core24/remote-build/snaps/platform-arg/expected-snaps.txt new file mode 100644 index 0000000000..1185b31b0c --- /dev/null +++ b/tests/spread/core24/remote-build/snaps/platform-arg/expected-snaps.txt @@ -0,0 +1,2 @@ +test-snap-platform-arg-core24_1.0_amd64.snap +test-snap-platform-arg-core24_1.0_s390x.snap diff --git a/tests/spread/core24/remote-build/snaps/platform-arg/snapcraft.yaml b/tests/spread/core24/remote-build/snaps/platform-arg/snapcraft.yaml new file mode 100644 index 0000000000..7f793ff460 --- /dev/null +++ b/tests/spread/core24/remote-build/snaps/platform-arg/snapcraft.yaml @@ -0,0 +1,12 @@ +name: test-snap-platform-arg-core24 +base: core24 +version: "1.0" +summary: Test snap for remote build +description: Test snap for remote build + +grade: stable +confinement: strict + +parts: + my-part: + plugin: nil diff --git a/tests/spread/core24/remote-build/task.yaml b/tests/spread/core24/remote-build/task.yaml index 14f7d0ca54..152d3abec4 100644 --- a/tests/spread/core24/remote-build/task.yaml +++ b/tests/spread/core24/remote-build/task.yaml @@ -11,6 +11,8 @@ environment: SNAP/all: all SNAP/platforms: platforms SNAP/no_platforms: no-platforms + SNAP/platform_arg: platform-arg + SNAP/build_for_arg: build-for-arg CREDENTIALS_FILE: "$HOME/.local/share/snapcraft/launchpad-credentials" CREDENTIALS_FILE/new_credentials: "$HOME/.local/share/snapcraft/launchpad-credentials" CREDENTIALS_FILE/old_credentials: "$HOME/.local/share/snapcraft/provider/launchpad/credentials" @@ -42,7 +44,13 @@ restore: | execute: | cd "./snaps/$SNAP" - snapcraft remote-build --launchpad-accept-public-upload + call_args="" + if [[ -e "arguments.txt" ]]; then + call_args=$(cat "arguments.txt") + fi + + # shellcheck disable=SC2086 + snapcraft remote-build --launchpad-accept-public-upload $call_args find . -maxdepth 1 -name "*.snap" | MATCH ".snap" diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index 0ffb10f9bb..c8e109ef91 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -546,7 +546,20 @@ def test_architecture_in_project_metadata( @pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) -@pytest.mark.parametrize("platform", const.SnapArch) +@pytest.mark.parametrize( + ("platforms", "expected_platforms"), + [ + *zip(const.SnapArch, [[arch] for arch in const.SnapArch]), + ("amd64,riscv64", ["amd64", "riscv64"]), + ("amd64,riscv64,s390x", ["amd64", "riscv64", "s390x"]), + pytest.param(" amd64 , riscv64 ", ["amd64", "riscv64"], id="with-whitespace"), + pytest.param( + "amd64,amd64,riscv64", + ["amd64", "amd64", "riscv64"], + id="launchpad-handles-duplicates", + ), + ], +) def test_platform_argument( mocker, snapcraft_yaml, @@ -554,14 +567,15 @@ def test_platform_argument( fake_services, mock_confirm, mock_remote_builder_fake_build_process, - platform, + platforms, + expected_platforms, ): """Use architectures provided by the `--platform` argument.""" snapcraft_yaml(base=base) mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--platform", platform], + ["snapcraft", "remote-build", "--platform", platforms], ) mock_start_builds = mocker.patch( "craft_application.services.remotebuild.RemoteBuildService.start_builds" @@ -569,11 +583,24 @@ def test_platform_argument( app = application.create_app() app.run() - mock_start_builds.assert_called_once_with(ANY, architectures=[platform]) + mock_start_builds.assert_called_once_with(ANY, architectures=expected_platforms) @pytest.mark.parametrize("base", const.CURRENT_BASES - {"devel"}) -@pytest.mark.parametrize("build_for", const.SnapArch) +@pytest.mark.parametrize( + ("build_fors", "expected_build_fors"), + [ + *zip(const.SnapArch, [[arch] for arch in const.SnapArch]), + ("amd64,riscv64", ["amd64", "riscv64"]), + ("amd64,riscv64,s390x", ["amd64", "riscv64", "s390x"]), + pytest.param(" amd64 , riscv64 ", ["amd64", "riscv64"], id="with-whitespace"), + pytest.param( + "amd64,amd64,riscv64", + ["amd64", "amd64", "riscv64"], + id="duplicates-passthrough-to-launchpad", + ), + ], +) def test_build_for_argument( mocker, snapcraft_yaml, @@ -581,14 +608,15 @@ def test_build_for_argument( fake_services, mock_confirm, mock_remote_builder_fake_build_process, - build_for, + build_fors, + expected_build_fors, ): """Use architectures provided by the `--build-for` argument.""" snapcraft_yaml(base=base) mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--build-for", build_for], + ["snapcraft", "remote-build", "--build-for", build_fors], ) mock_start_builds = mocker.patch( "craft_application.services.remotebuild.RemoteBuildService.start_builds" @@ -596,7 +624,7 @@ def test_build_for_argument( app = application.create_app() app.run() - mock_start_builds.assert_called_once_with(ANY, architectures=[build_for]) + mock_start_builds.assert_called_once_with(ANY, architectures=expected_build_fors) def test_architecture_defined_twice_error( @@ -667,11 +695,21 @@ def test_platform_defined_twice_error( ) in err +@pytest.mark.parametrize( + "platforms", + [ + "nonexistent", + "nonexistent,riscv64", + "riscv64,nonexistent", + "riscv64,nonexistent,amd64", + ], +) @pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22"}) def test_unknown_platform_error( capsys, mocker, snapcraft_yaml, + platforms, base, fake_services, mock_confirm, @@ -686,7 +724,7 @@ def test_unknown_platform_error( mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--platform", "nonexistent"], + ["snapcraft", "remote-build", "--platform", platforms], ) app = application.create_app() @@ -700,11 +738,21 @@ def test_unknown_platform_error( ) in err +@pytest.mark.parametrize( + "build_fors", + [ + "nonexistent", + "nonexistent,riscv64", + "riscv64,nonexistent", + "riscv64,nonexistent,amd64", + ], +) @pytest.mark.parametrize("base", const.CURRENT_BASES - {"core22", "devel"}) def test_unknown_build_for_error( capsys, mocker, snapcraft_yaml, + build_fors, base, fake_services, mock_confirm, @@ -715,7 +763,7 @@ def test_unknown_build_for_error( mocker.patch.object( sys, "argv", - ["snapcraft", "remote-build", "--build-for", "nonexistent"], + ["snapcraft", "remote-build", "--build-for", build_fors], ) mocker.patch( "craft_application.services.remotebuild.RemoteBuildService.start_builds" From 12b70e7085b43602102521ae91cb8a534d3871d0 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 11 Sep 2024 14:42:17 -0500 Subject: [PATCH 072/151] docs(remotebuild): update usage of `--build-for` and `--platform` Signed-off-by: Callahan Kovacs --- docs/explanation/remote-build.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/explanation/remote-build.rst b/docs/explanation/remote-build.rst index 8d287f9524..9e95faad52 100644 --- a/docs/explanation/remote-build.rst +++ b/docs/explanation/remote-build.rst @@ -90,11 +90,11 @@ Current :ref:`lifecycle commands`. ``--platform`` or ``--build-for`` can only be provided when the ``platforms`` -and ``architectures`` keywords are not defined in the project metadata +or ``architectures`` keywords are not defined in the project metadata (`[12]`_). -These keywords are mutually exclusive and must be a single debian architecture. -A list of comma-separated architectures is not supported (`[11]`_). +These keywords are mutually exclusive and must be a comma-separated list of +debian architectures. ``core22`` snaps can only use ``--build-for``. ``core24`` and newer snaps can use ``--platform`` or ``--build-for``. @@ -180,7 +180,6 @@ Launchpad is not able to parse this notation (`[9]`_). .. _`[8]`: https://bugs.launchpad.net/snapcraft/+bug/2007789 .. _`[9]`: https://bugs.launchpad.net/snapcraft/+bug/2042167 .. _`[10]`: https://github.com/canonical/snapcraft/issues/4885 -.. _`[11]`: https://github.com/canonical/snapcraft/issues/4990 .. _`[12]`: https://github.com/canonical/snapcraft/issues/4992 .. _`[13]`: https://github.com/canonical/snapcraft/issues/4996 .. _`[14]`: https://github.com/canonical/snapcraft/issues/4995 From 8faa6d54eef1f4437c7e10337ed8fdcc8dc51cbc Mon Sep 17 00:00:00 2001 From: Michael DuBelko Date: Fri, 13 Sep 2024 09:19:17 -0700 Subject: [PATCH 073/151] docs: document tox builds (#5033) - Remove snapcraft_legacy as an autobuild target, since it was blocking. - Add temporary documentation to HACKING.md, since it likely won't remain after we impose README standards. Co-authored-by: Alex Lowe --- HACKING.md | 42 ++++++++++++++++++++++++++++++++++++++++++ tox.ini | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/HACKING.md b/HACKING.md index 44622ea88d..31d93e2f35 100644 --- a/HACKING.md +++ b/HACKING.md @@ -54,6 +54,48 @@ See the [Testing guide](TESTING.md). Given that the `--debug` option in snapcraft is reserved for project specific debugging, enabling for the `logger.debug` calls is achieved by setting the "SNAPCRAFT_ENABLE_DEVELOPER_DEBUG" environment variable to a truthful value. Snapcraft's internal tools, e.g.; `snapcraftctl` should pick up this environment variable as well. + +## Documentation + + +### Build + +To render the documentation as HTML in `docs/_build`, run: + +```shell +tox run -e build-docs +``` + +> **Important** +> +> Interactive builds are currently defective and cause an infinite loop. [This GitHub issue](https://github.com/sphinx-doc/sphinx/issues/11556#issuecomment-1667451983) posits that this is caused by by pages referencing each other. + +If you prefer to compose pages interactively, you can host the documentation on a local server: + +```shell +tox run -e autobuild-docs +``` + +You can reach the interactive site at http://127.0.0.1:8080 in a web browser. + + +### Test + +The documentation Makefile provided by the [Sphinx Starter Pack](https://github.com/canonical/sphinx-docs-starter-pack) provides a number of natural language checks such as style guide adherence, inclusive words, and product terminology, however they currently aren't configured correctly for Snapcraft. Instead, you can validate for basic language and syntax using two of the development tests. + +To check for syntax errors in documentation, run: + +```shell +tox run -e lint-docs +``` + +For a rudimentary spell check, you can use codespell: + +```shell +tox run -e lint-codespell +``` + + ## Evaluating pull requests Oftentimes all you want to do is see if a given pull request solves the issue you were having. To make this easier, the Travis CI setup for snapcraft _publishes_ the resulting snap that was built for x86-64 using `transfer.sh`. diff --git a/tox.ini b/tox.ini index 0d9b1d7f1d..0f1e5187b0 100644 --- a/tox.ini +++ b/tox.ini @@ -169,7 +169,7 @@ commands = sphinx-build {posargs:-b html} -W {tox_root}/docs {tox_root}/docs/_bu [testenv:autobuild-docs] description = Build documentation with an autoupdating server base = docs -commands = sphinx-autobuild {posargs:-b html --open-browser --port 8080} -W --watch {tox_root}/snapcraft {tox_root}/snapcraft_legacy {tox_root}/docs {tox_root}/docs/_build +commands = sphinx-autobuild {posargs:-b html --open-browser --port 8080} {tox_root}/docs {tox_root}/docs/_build [testenv:lint-docs] description = Lint the documentation with sphinx-lint From a24dc3e497aeed82db840921c879fc5967e86ff0 Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Fri, 13 Sep 2024 14:07:48 -0300 Subject: [PATCH 074/151] build(deps): bump craft-parts (#5031) --- docs/requirements.txt | 2 +- requirements-devel.txt | 2 +- requirements.txt | 2 +- tests/spread/core24/npm-reentrant/hello.js | 3 +++ .../spread/core24/npm-reentrant/package-lock.json | 5 +++++ tests/spread/core24/npm-reentrant/package.json | 13 +++++++++++++ .../core24/npm-reentrant/snap/snapcraft.yaml | 15 +++++++++++++++ tests/spread/core24/npm-reentrant/task.yaml | 11 +++++++++++ 8 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 tests/spread/core24/npm-reentrant/hello.js create mode 100644 tests/spread/core24/npm-reentrant/package-lock.json create mode 100644 tests/spread/core24/npm-reentrant/package.json create mode 100644 tests/spread/core24/npm-reentrant/snap/snapcraft.yaml create mode 100644 tests/spread/core24/npm-reentrant/task.yaml diff --git a/docs/requirements.txt b/docs/requirements.txt index 8c13c5e998..13b624d522 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -17,7 +17,7 @@ craft-application==3.1.0 craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 -craft-parts==1.33.0 +craft-parts==1.33.1 craft-providers==1.24.2 craft-store==2.6.2 cryptography==42.0.8 diff --git a/requirements-devel.txt b/requirements-devel.txt index b8045f6cb3..62325a653b 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -15,7 +15,7 @@ craft-application==3.1.0 craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 -craft-parts==1.33.0 +craft-parts==1.33.1 craft-providers==1.24.2 craft-store==2.6.2 cryptography==42.0.8 diff --git a/requirements.txt b/requirements.txt index f4cc6581aa..77e189bb9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ craft-application==3.1.0 craft-archives==1.1.3 craft-cli==2.6.0 craft-grammar==1.2.0 -craft-parts==1.33.0 +craft-parts==1.33.1 craft-providers==1.24.2 craft-store==2.6.2 cryptography==42.0.8 diff --git a/tests/spread/core24/npm-reentrant/hello.js b/tests/spread/core24/npm-reentrant/hello.js new file mode 100644 index 0000000000..8001048aa4 --- /dev/null +++ b/tests/spread/core24/npm-reentrant/hello.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +console.log('hello world'); diff --git a/tests/spread/core24/npm-reentrant/package-lock.json b/tests/spread/core24/npm-reentrant/package-lock.json new file mode 100644 index 0000000000..cf8ee3b99e --- /dev/null +++ b/tests/spread/core24/npm-reentrant/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "npm-hello", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/tests/spread/core24/npm-reentrant/package.json b/tests/spread/core24/npm-reentrant/package.json new file mode 100644 index 0000000000..c45d277657 --- /dev/null +++ b/tests/spread/core24/npm-reentrant/package.json @@ -0,0 +1,13 @@ +{ + "name": "npm-hello", + "version": "1.0.0", + "description": "Testing grounds for snapcraft integration tests", + "bin": { + "npm-hello": "hello.js" + }, + "scripts": { + "npm-hello": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "GPL-3.0" +} diff --git a/tests/spread/core24/npm-reentrant/snap/snapcraft.yaml b/tests/spread/core24/npm-reentrant/snap/snapcraft.yaml new file mode 100644 index 0000000000..7a75149e72 --- /dev/null +++ b/tests/spread/core24/npm-reentrant/snap/snapcraft.yaml @@ -0,0 +1,15 @@ +name: npm-reentrant +version: "1.0" +summary: test the npm plugin +description: Check that the npm plugin works across snapcraft calls + +confinement: strict +grade: devel +base: core24 + +parts: + hello: + source: . + plugin: npm + npm-include-node: true + npm-node-version: 22.1.0 diff --git a/tests/spread/core24/npm-reentrant/task.yaml b/tests/spread/core24/npm-reentrant/task.yaml new file mode 100644 index 0000000000..9ad7c48d5d --- /dev/null +++ b/tests/spread/core24/npm-reentrant/task.yaml @@ -0,0 +1,11 @@ +summary: Pull, then Build, a Node snap + +execute: | + snapcraft pull + snapcraft build + snapcraft pack + test -f npm-reentrant*.snap + +restore: | + snapcraft clean + rm -f ./*.snap From 4f94112162561b9692250865a11b13d2c61a2d09 Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Sat, 14 Sep 2024 09:16:49 -0300 Subject: [PATCH 075/151] build(deps): bump craft-parts for hotfix/8.4 (#5039) - [ ] Have you followed the [guidelines for contributing](https://github.com/snapcore/snapcraft/blob/master/CONTRIBUTING.md)? - [ ] Have you signed the [CLA](http://www.ubuntu.com/legal/contributors/)? - [ ] Have you successfully run `tox run -m lint`? - [ ] Have you successfully run `tox run -e test-py310`? (supported versions: `py39`, `py310`, `py311`, `py312`) ----- --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- tests/spread/core24/npm-reentrant/hello.js | 3 +++ .../spread/core24/npm-reentrant/package-lock.json | 5 +++++ tests/spread/core24/npm-reentrant/package.json | 13 +++++++++++++ .../core24/npm-reentrant/snap/snapcraft.yaml | 15 +++++++++++++++ tests/spread/core24/npm-reentrant/task.yaml | 11 +++++++++++ 8 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 tests/spread/core24/npm-reentrant/hello.js create mode 100644 tests/spread/core24/npm-reentrant/package-lock.json create mode 100644 tests/spread/core24/npm-reentrant/package.json create mode 100644 tests/spread/core24/npm-reentrant/snap/snapcraft.yaml create mode 100644 tests/spread/core24/npm-reentrant/task.yaml diff --git a/requirements-devel.txt b/requirements-devel.txt index 714e376a69..cdd42303c3 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -28,7 +28,7 @@ craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 -craft-parts==2.1.0 +craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 craft-store==3.0.0 diff --git a/requirements-docs.txt b/requirements-docs.txt index 11f007664e..61102c9523 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -23,7 +23,7 @@ craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 -craft-parts==2.1.0 +craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 craft-store==3.0.0 diff --git a/requirements.txt b/requirements.txt index cd628a7231..75f6107ff1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ craft-application==4.1.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 -craft-parts==2.1.0 +craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 craft-store==3.0.0 diff --git a/tests/spread/core24/npm-reentrant/hello.js b/tests/spread/core24/npm-reentrant/hello.js new file mode 100644 index 0000000000..8001048aa4 --- /dev/null +++ b/tests/spread/core24/npm-reentrant/hello.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +console.log('hello world'); diff --git a/tests/spread/core24/npm-reentrant/package-lock.json b/tests/spread/core24/npm-reentrant/package-lock.json new file mode 100644 index 0000000000..cf8ee3b99e --- /dev/null +++ b/tests/spread/core24/npm-reentrant/package-lock.json @@ -0,0 +1,5 @@ +{ + "name": "npm-hello", + "version": "1.0.0", + "lockfileVersion": 1 +} diff --git a/tests/spread/core24/npm-reentrant/package.json b/tests/spread/core24/npm-reentrant/package.json new file mode 100644 index 0000000000..c45d277657 --- /dev/null +++ b/tests/spread/core24/npm-reentrant/package.json @@ -0,0 +1,13 @@ +{ + "name": "npm-hello", + "version": "1.0.0", + "description": "Testing grounds for snapcraft integration tests", + "bin": { + "npm-hello": "hello.js" + }, + "scripts": { + "npm-hello": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "GPL-3.0" +} diff --git a/tests/spread/core24/npm-reentrant/snap/snapcraft.yaml b/tests/spread/core24/npm-reentrant/snap/snapcraft.yaml new file mode 100644 index 0000000000..7a75149e72 --- /dev/null +++ b/tests/spread/core24/npm-reentrant/snap/snapcraft.yaml @@ -0,0 +1,15 @@ +name: npm-reentrant +version: "1.0" +summary: test the npm plugin +description: Check that the npm plugin works across snapcraft calls + +confinement: strict +grade: devel +base: core24 + +parts: + hello: + source: . + plugin: npm + npm-include-node: true + npm-node-version: 22.1.0 diff --git a/tests/spread/core24/npm-reentrant/task.yaml b/tests/spread/core24/npm-reentrant/task.yaml new file mode 100644 index 0000000000..9ad7c48d5d --- /dev/null +++ b/tests/spread/core24/npm-reentrant/task.yaml @@ -0,0 +1,11 @@ +summary: Pull, then Build, a Node snap + +execute: | + snapcraft pull + snapcraft build + snapcraft pack + test -f npm-reentrant*.snap + +restore: | + snapcraft clean + rm -f ./*.snap From 19699c1b88bc3140c522e23d10d105300b32dcba Mon Sep 17 00:00:00 2001 From: Callahan Date: Mon, 16 Sep 2024 15:29:22 -0500 Subject: [PATCH 076/151] fix: use correct type for `build-on` and `build-for` in build plans (#5043) Signed-off-by: Callahan Kovacs --- snapcraft/models/project.py | 4 +-- .../implicit-build-for/expected-snaps.txt | 1 + .../implicit-build-for/snap/snapcraft.yaml | 15 +++++++++ tests/spread/core24/platforms/task.yaml | 1 + tests/unit/models/test_projects.py | 31 +++++++++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/spread/core24/platforms/snaps/implicit-build-for/expected-snaps.txt create mode 100644 tests/spread/core24/platforms/snaps/implicit-build-for/snap/snapcraft.yaml diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 3642a2f795..507a1161cd 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -1222,8 +1222,8 @@ def get_build_plan(self) -> list[BuildInfo]: ) for platform_entry, platform in self.platforms.items(): - for build_for in platform.build_for or [SnapArch(platform_entry)]: - for build_on in platform.build_on or [SnapArch(platform_entry)]: + for build_for in platform.build_for or [SnapArch(platform_entry).value]: + for build_on in platform.build_on or [SnapArch(platform_entry).value]: build_infos.append( BuildInfo( platform=platform_entry, diff --git a/tests/spread/core24/platforms/snaps/implicit-build-for/expected-snaps.txt b/tests/spread/core24/platforms/snaps/implicit-build-for/expected-snaps.txt new file mode 100644 index 0000000000..faafb3a420 --- /dev/null +++ b/tests/spread/core24/platforms/snaps/implicit-build-for/expected-snaps.txt @@ -0,0 +1 @@ +implicit-build-for_1.0_riscv64.snap diff --git a/tests/spread/core24/platforms/snaps/implicit-build-for/snap/snapcraft.yaml b/tests/spread/core24/platforms/snaps/implicit-build-for/snap/snapcraft.yaml new file mode 100644 index 0000000000..fd28c2073a --- /dev/null +++ b/tests/spread/core24/platforms/snaps/implicit-build-for/snap/snapcraft.yaml @@ -0,0 +1,15 @@ +name: implicit-build-for +version: "1.0" +summary: test +description: | + Use the platform name implicitly for the `build-for`. +confinement: strict +base: core24 + +platforms: + riscv64: + build-on: [amd64] + +parts: + my-part: + plugin: nil diff --git a/tests/spread/core24/platforms/task.yaml b/tests/spread/core24/platforms/task.yaml index 28c89d7ba5..a8a237553d 100644 --- a/tests/spread/core24/platforms/task.yaml +++ b/tests/spread/core24/platforms/task.yaml @@ -9,6 +9,7 @@ environment: SNAP/env_var_all: env-var-all SNAP/env_var_match: env-var-match SNAP/env_var_no_match: env-var-no-match + SNAP/implicit_build_for: implicit-build-for SNAP/multiple_build_for: multiple-build-for SNAP/platform_all: platform-all SNAP/platform_match: platform-match diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index 1cb147d676..50b5b07adb 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -2068,6 +2068,37 @@ def test_root_packages_transform_no_affect(self, project_yaml_data): ], id="single_platform_as_arch", ), + pytest.param( + { + "s390x": { + "build-on": "s390x", + }, + "riscv64": { + "build-on": ["amd64", "riscv64"], + }, + }, + [ + BuildInfo( + build_on="s390x", + build_for="s390x", + base=BaseName(name="ubuntu", version="24.04"), + platform="s390x", + ), + BuildInfo( + build_on="amd64", + build_for="riscv64", + base=BaseName(name="ubuntu", version="24.04"), + platform="riscv64", + ), + BuildInfo( + build_on="riscv64", + build_for="riscv64", + base=BaseName(name="ubuntu", version="24.04"), + platform="riscv64", + ), + ], + id="implicit_build_for", + ), pytest.param( { "arm64": { From 055fb3639fa7b6651233ee8c538366d48366f327 Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 17 Sep 2024 09:02:58 -0500 Subject: [PATCH 077/151] docs: add linter reference page (#5032) Signed-off-by: Callahan Kovacs Co-authored-by: Michael DuBelko Co-authored-by: Alex Lowe --- docs/reference/index.rst | 1 + docs/reference/linters.rst | 61 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 docs/reference/linters.rst diff --git a/docs/reference/index.rst b/docs/reference/index.rst index c765436735..4c6c69a88c 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -11,6 +11,7 @@ Reference changelog commands components + linters plugins /common/craft-parts/reference/part_properties parts_steps diff --git a/docs/reference/linters.rst b/docs/reference/linters.rst new file mode 100644 index 0000000000..e9eb5cd1fb --- /dev/null +++ b/docs/reference/linters.rst @@ -0,0 +1,61 @@ +Linters +======= + +A *linter* is an analysis tool that checks for common errors or compatibility +issues, usually automatically, or as part of some other process. + +Snapcraft 7.2 and higher provides built-in linter functionality when the snap +uses core22 or higher as its :doc:`base `. + +By default, these built-in linters run automatically when a snap is built. If +they're unneeded, you can disable them in the snap's ``snapcraft.yaml``. + +Built-in linters +----------------- + +Snapcraft runs the following linters: + +- `classic`_. Verifies binary file parameters for snaps using + `classic confinement`_. + +- `library`_. Verifies that no ELF file dependencies, such as libraries, are + missing, and that no extra libraries are included in the snap package. + +Disable a linter +---------------- + +You can disable a linter by adding it to the ``lint.ignore`` key in +``snapcraft.yaml``. For example: + +.. code-block:: yaml + + lint: + ignore: + - classic + - library + + +Ignore specific files +~~~~~~~~~~~~~~~~~~~~~ + +To disable a linter for a specific file, you can list it under a linter's entry +in the ``lint.ignore`` key. The path is relative to the snap directory tree, +and supports wildcard characters (**\***). + +In the following example, the ``classic`` linter is disabled entirely, and the +``library`` linter won't run for the files in ``usr/lib`` that match the +specified pattern: + +.. code-block:: yaml + + lint: + ignore: + - classic + - library: + - usr/lib/**/libfoo.so* + + + +.. _classic: https://snapcraft.io/docs/linters-classic +.. _classic confinement: https://snapcraft.io/docs/snap-confinement +.. _library: https://snapcraft.io/docs/linters-library From 464bd08e89c60ca0f3bceda74cea33ae02a46b61 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 13 Sep 2024 12:53:01 -0500 Subject: [PATCH 078/151] docs(changelog): add 8.3.4 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index 571faec3e1..8559a3c2ff 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -68,6 +68,22 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.3.4 (2024-Sep-13) +------------------- + +Core +==== + +Plugins +####### + +NPM +""" + +* Fix a bug where NPM parts fail to build if the ``pull`` and ``build`` steps + did not occur in the same execution of Snapcraft. + +For a complete list of commits, check out the `8.3.4`_ release on GitHub. 8.4.0 (2024-Sep-10) ------------------- @@ -1171,4 +1187,5 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 .. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 .. _8.3.3: https://github.com/canonical/snapcraft/releases/tag/8.3.3 +.. _8.3.4: https://github.com/canonical/snapcraft/releases/tag/8.3.4 .. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 From bb018262c7ef3e534a11a2a6b6482626eb2ff2e4 Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Wed, 18 Sep 2024 09:31:41 -0300 Subject: [PATCH 079/151] chore: update craft-application to 4.2.2 - Setup the emitter fixture where required - Make more use of pytest.mark.usefixtures for unused fixtures - Remove some of the unused fixtures Signed-off-by: Sergio Schvezov --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- tests/unit/commands/test_lifecycle.py | 3 +- tests/unit/commands/test_remote.py | 45 +++++++++++---------------- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index cdd42303c3..a160875797 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -24,7 +24,7 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.1.2 +craft-application==4.2.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 diff --git a/requirements-docs.txt b/requirements-docs.txt index 61102c9523..a23b44e6db 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -19,7 +19,7 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.1.2 +craft-application==4.2.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 diff --git a/requirements.txt b/requirements.txt index 75f6107ff1..d7265c9402 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.1.2 +craft-application==4.2.2 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 diff --git a/tests/unit/commands/test_lifecycle.py b/tests/unit/commands/test_lifecycle.py index ee8ce3b23d..b4e5ab47a1 100644 --- a/tests/unit/commands/test_lifecycle.py +++ b/tests/unit/commands/test_lifecycle.py @@ -107,7 +107,8 @@ def test_snap_command_fallback(tmp_path, emitter, mocker, fake_services): ) -def test_core24_try_command(tmp_path, mocker, fake_services): +@pytest.mark.usefixtures("emitter") +def test_core24_try_command(tmp_path, fake_services): parsed_args = argparse.Namespace(parts=[], output=tmp_path) cmd = lifecycle.TryCommand({"app": APP_METADATA, "services": fake_services}) diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index c8e109ef91..6ebdc50633 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -36,7 +36,8 @@ # remote-build control logic may check if the working dir is a git repo, # so execute all tests inside a test directory -pytestmark = pytest.mark.usefixtures("new_dir") +# The service also emits +pytestmark = pytest.mark.usefixtures("new_dir", "emitter") @pytest.fixture() @@ -163,7 +164,7 @@ def test_no_confirmation_for_private_project( @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv") +@pytest.mark.usefixtures("mock_argv", "emitter") def test_command_user_confirms_upload( snapcraft_yaml, base, mock_confirm, fake_services ): @@ -183,7 +184,7 @@ def test_command_user_confirms_upload( @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv", "fake_services") +@pytest.mark.usefixtures("mock_argv", "emitter", "fake_services") def test_command_user_denies_upload( capsys, snapcraft_yaml, @@ -206,7 +207,7 @@ def test_command_user_denies_upload( @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv", "fake_services") +@pytest.mark.usefixtures("mock_argv", "emitter", "fake_services") def test_command_accept_upload( mocker, snapcraft_yaml, base, mock_confirm, mock_run_remote_build ): @@ -224,7 +225,9 @@ def test_command_accept_upload( @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv", "mock_confirm", "fake_services", "fake_sudo") +@pytest.mark.usefixtures( + "mock_argv", "mock_confirm", "emitter", "fake_services", "fake_sudo" +) def test_remote_build_sudo_warns(emitter, snapcraft_yaml, base, mock_run_remote_build): "Check if a warning is shown when snapcraft is run with sudo." snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} @@ -240,8 +243,8 @@ def test_remote_build_sudo_warns(emitter, snapcraft_yaml, base, mock_run_remote_ @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv", "mock_confirm", "fake_services") -def test_launchpad_timeout_default(mocker, snapcraft_yaml, base, fake_services): +@pytest.mark.usefixtures("mock_argv", "mock_confirm", "emitter", "fake_services") +def test_launchpad_timeout_default(mocker, snapcraft_yaml, base): """Check if no timeout is set by default.""" snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} snapcraft_yaml(**snapcraft_yaml_dict) @@ -256,8 +259,8 @@ def test_launchpad_timeout_default(mocker, snapcraft_yaml, base, fake_services): @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv", "mock_confirm", "fake_services") -def test_launchpad_timeout(mocker, snapcraft_yaml, base, fake_services): +@pytest.mark.usefixtures("mock_argv", "mock_confirm", "emitter", "fake_services") +def test_launchpad_timeout(mocker, snapcraft_yaml, base): """Set the timeout for the remote builder.""" mocker.patch.object( sys, "argv", ["snapcraft", "remote-build", "--launchpad-timeout", "100"] @@ -281,7 +284,7 @@ def test_launchpad_timeout(mocker, snapcraft_yaml, base, fake_services): @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_argv", "mock_confirm", "fake_services") +@pytest.mark.usefixtures("mock_argv", "mock_confirm", "emitter", "fake_services") def test_run_core22_and_later(snapcraft_yaml, base, mock_remote_build_run): """Bases that are core22 and later will use craft-application remote-build.""" snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} @@ -309,15 +312,8 @@ def test_run_core20( @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_confirm", "mock_argv") -def test_run_in_repo_newer_than_core22( - emitter, - mocker, - snapcraft_yaml, - base, - new_dir, - fake_services, -): +@pytest.mark.usefixtures("mock_confirm", "mock_argv", "emitter", "fake_services") +def test_run_in_repo_newer_than_core22(mocker, snapcraft_yaml, base, new_dir): """Bases newer than core22 run craft-application remote-build regardless of being in a repo.""" # initialize a git repo GitRepo(new_dir) @@ -337,15 +333,10 @@ def test_run_in_repo_newer_than_core22( @pytest.mark.usefixtures( "mock_confirm", "mock_argv", + "mock_remote_builder_start_builds", + "fake_services", ) -def test_run_in_shallow_repo_unsupported( - capsys, - new_dir, - snapcraft_yaml, - base, - mock_remote_builder_start_builds, - fake_services, -): +def test_run_in_shallow_repo_unsupported(capsys, new_dir, snapcraft_yaml, base): """devel / core24 and newer bases run new remote-build in a shallow git repo.""" root_path = Path(new_dir) snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} From 9e02eb000a8f72fcbfe6ef7d232f8552e129489e Mon Sep 17 00:00:00 2001 From: Sergio Schvezov Date: Fri, 13 Sep 2024 10:23:45 -0300 Subject: [PATCH 080/151] fix: override _run_inner to catch exceptions Remove the heading from the original error to not give the impression it is a craft application error to the user, but instead an error generated store side. Signed-off-by: Sergio Schvezov --- snapcraft/application.py | 10 ++++------ tests/unit/test_application.py | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index 706dd87b21..ee7229fa87 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -209,13 +209,13 @@ def _pre_run(self, dispatcher: craft_cli.Dispatcher) -> None: super()._pre_run(dispatcher) @override - def run(self) -> int: + def _run_inner(self) -> int: try: - return_code = super().run() + return_code = super()._run_inner() except craft_store.errors.NoKeyringError as err: self._emit_error( craft_cli.errors.CraftError( - f"craft-store error: {err}", + f"{err}", resolution=( "Ensure the keyring is working or " f"{store.constants.ENVIRONMENT_STORE_CREDENTIALS} " @@ -227,9 +227,7 @@ def run(self) -> int: return_code = 1 except craft_store.errors.CraftStoreError as err: self._emit_error( - craft_cli.errors.CraftError( - f"craft-store error: {err}", resolution=err.resolution - ), + craft_cli.errors.CraftError(f"{err}", resolution=err.resolution), cause=err, ) return_code = 1 diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 3f507b8663..e4ee1d5998 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -665,7 +665,7 @@ def test_known_core24(snapcraft_yaml, base, build_base, is_known_core24): ) def test_store_error(mocker, capsys, message, resolution, expected_message): mocker.patch( - "snapcraft.application.Application.run", + "snapcraft.application.Application._run_inner", side_effect=craft_store.errors.CraftStoreError(message, resolution=resolution), ) @@ -673,12 +673,12 @@ def test_store_error(mocker, capsys, message, resolution, expected_message): assert return_code == 1 _, err = capsys.readouterr() - assert f"craft-store error: {expected_message}" in err + assert expected_message in err def test_store_key_error(mocker, capsys): mocker.patch( - "snapcraft.application.Application.run", + "snapcraft.application.Application._run_inner", side_effect=craft_store.errors.NoKeyringError(), ) @@ -692,7 +692,7 @@ def test_store_key_error(mocker, capsys): # pylint: disable=[line-too-long] dedent( """\ - craft-store error: No keyring found to store or retrieve credentials from. + No keyring found to store or retrieve credentials from. Recommended resolution: Ensure the keyring is working or SNAPCRAFT_STORE_CREDENTIALS is correctly exported into the environment For more information, check out: https://snapcraft.io/docs/snapcraft-authentication """ From e25a78b293143be9dad62b8b7f720c16177f4e39 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 18 Sep 2024 15:21:07 -0400 Subject: [PATCH 081/151] build(deps): update craft-application --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index a160875797..3db5bdf4ef 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -24,7 +24,7 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.2.2 +craft-application==4.2.3 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 diff --git a/requirements-docs.txt b/requirements-docs.txt index a23b44e6db..ae23b0c27f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -19,7 +19,7 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.2.2 +craft-application==4.2.3 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 diff --git a/requirements.txt b/requirements.txt index d7265c9402..fab86ab254 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.2.2 +craft-application==4.2.3 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.0 From 0e2cfbf56a4823ab7e2f8168354ce8b8e44979f5 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 20 Sep 2024 09:09:45 -0500 Subject: [PATCH 082/151] build(deps): bump craft-grammar to 2.0.1 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 3db5bdf4ef..0b3343d956 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -27,7 +27,7 @@ coverage==7.6.1 craft-application==4.2.3 craft-archives==2.0.0 craft-cli==2.7.0 -craft-grammar==2.0.0 +craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index ae23b0c27f..53eab9ebe1 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -22,7 +22,7 @@ colorama==0.4.6 craft-application==4.2.3 craft-archives==2.0.0 craft-cli==2.7.0 -craft-grammar==2.0.0 +craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 diff --git a/requirements.txt b/requirements.txt index fab86ab254..66cb6dcf78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ click==8.1.7 craft-application==4.2.3 craft-archives==2.0.0 craft-cli==2.7.0 -craft-grammar==2.0.0 +craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 diff --git a/setup.py b/setup.py index 7b2a06c73e..d8d2e4bd86 100755 --- a/setup.py +++ b/setup.py @@ -101,7 +101,7 @@ def recursive_data_files(directory, install_directory): "craft-application~=4.1", "craft-archives~=2.0", "craft-cli~=2.6", - "craft-grammar~=2.0", + "craft-grammar>=2.0.1,<3.0.0", "craft-parts~=2.1", "craft-platforms~=0.1", "craft-providers~=2.0", From 488700a7922618e343dd33953eac58d90153bc31 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 20 Sep 2024 10:14:30 -0500 Subject: [PATCH 083/151] tests: add tests for coercing numbers Signed-off-by: Callahan Kovacs --- tests/unit/models/test_projects.py | 61 +++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index 50b5b07adb..99ccc4c4ee 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -82,6 +82,44 @@ def _socket_yaml_data(**kwargs) -> Dict[str, Any]: yield _socket_yaml_data +@pytest.fixture +def fake_project_with_numbers(project_yaml_data): + """Returns a fake project with numbers in string fields. + + This includes numbers in fields that are validated by snapcraft and fields + validated by craft-parts. + """ + return project_yaml_data( + # string + version=1.0, + # string + icon=2, + # list[str] + website=[3.0, 4], + # dict[str, str] + environment={ + "float": 5.0, + "int": 6, + }, + parts={ + "p1": { + "plugin": "nil", + # string + "source-type": 7, + # string + "source-commit": 8.0, + # list[str] + "build-snaps": [9, 10.0], + # dict[str, str] + "build-environment": [ + {"float": 11.0}, + {"int": 12}, + ], + } + }, + ) + + class TestProjectDefaults: """Ensure unspecified items have the correct default value.""" @@ -701,6 +739,23 @@ def test_links_list(self, project_yaml_data): "https://github.com/NickvisionApps/Denaro", ] + def test_coerce_numbers(self, fake_project_with_numbers): + """Coerce numbers into strings.""" + project = Project.unmarshal(fake_project_with_numbers) + + assert project.version == "1.0" + assert project.icon == "2" + assert project.website == ["3.0", "4"] + assert project.environment == {"float": "5.0", "int": "6"} + # parts remain a dictionary with original types + assert project.parts["p1"]["source-type"] == 7 + assert project.parts["p1"]["source-commit"] == 8.0 + assert project.parts["p1"]["build-snaps"] == [9, 10.0] + assert project.parts["p1"]["build-environment"] == [ + {"float": 11.0}, + {"int": 12}, + ] + class TestHookValidation: """Validate hooks.""" @@ -1504,6 +1559,10 @@ def test_grammar_try(self, project_yaml_data): with pytest.raises(errors.ProjectValidationError, match=error): GrammarAwareProject.validate_grammar(data) + def test_grammar_number_coercion(self, fake_project_with_numbers): + """Ensure that grammar validation does not fail when coercing numbers into strings.""" + GrammarAwareProject.validate_grammar(fake_project_with_numbers) + def test_grammar_type_error(self, project_yaml_data): data = project_yaml_data( parts={ @@ -1516,7 +1575,7 @@ def test_grammar_type_error(self, project_yaml_data): } ) - error = r"value must be a str: \[25\]" + error = r"Input should be a valid string \(in field 'parts\.p1\.source\[0\]'\)" with pytest.raises(errors.ProjectValidationError, match=error): GrammarAwareProject.validate_grammar(data) From 758a9ae5c15f4ca79a07caf2976dfefb8e01fc15 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 20 Sep 2024 11:27:31 -0500 Subject: [PATCH 084/151] build(deps): bump craft-store to 3.0.1 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 0b3343d956..01712df5c4 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -31,7 +31,7 @@ craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 -craft-store==3.0.0 +craft-store==3.0.1 cryptography==43.0.1 cssutils==2.11.1 dict2css==0.3.0.post1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 53eab9ebe1..7e58aec50a 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -26,7 +26,7 @@ craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 -craft-store==3.0.0 +craft-store==3.0.1 cryptography==43.0.1 cssutils==2.11.1 dict2css==0.3.0.post1 diff --git a/requirements.txt b/requirements.txt index 66cb6dcf78..33eb42e01b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 -craft-store==3.0.0 +craft-store==3.0.1 cryptography==43.0.1 distro==1.9.0 docutils==0.19 diff --git a/setup.py b/setup.py index d8d2e4bd86..c1ed03edc2 100755 --- a/setup.py +++ b/setup.py @@ -105,7 +105,7 @@ def recursive_data_files(directory, install_directory): "craft-parts~=2.1", "craft-platforms~=0.1", "craft-providers~=2.0", - "craft-store~=3.0", + "craft-store>=3.0.1,<4.0.0", "docutils<0.20", # Frozen until we can update sphinx dependencies. "gnupg", "jsonschema==2.5.1", From ad3483f712c5de4a20ce0f1560672da749493f72 Mon Sep 17 00:00:00 2001 From: Callahan Date: Fri, 20 Sep 2024 14:24:22 -0500 Subject: [PATCH 085/151] feat(models): add registries models (#5049) Adds pydantic models for registries. Signed-off-by: Callahan Kovacs --- snapcraft/models/__init__.py | 11 +++- snapcraft/models/assertions.py | 75 ++++++++++++++++++---- tests/unit/models/test_assertions.py | 93 ++++++++++++++++++++++++++-- 3 files changed, 161 insertions(+), 18 deletions(-) diff --git a/snapcraft/models/__init__.py b/snapcraft/models/__init__.py index b61840fa6d..c12016e948 100644 --- a/snapcraft/models/__init__.py +++ b/snapcraft/models/__init__.py @@ -15,7 +15,13 @@ # along with this program. If not, see . """Data models for snapcraft.""" -from .assertions import Assertion, RegistryAssertion +from .assertions import ( + Assertion, + EditableAssertion, + EditableRegistryAssertion, + Registry, + RegistryAssertion, +) from .manifest import Manifest from .project import ( MANDATORY_ADOPTABLE_FIELDS, @@ -43,12 +49,15 @@ "Component", "ComponentProject", "ContentPlug", + "EditableAssertion", + "EditableRegistryAssertion", "GrammarAwareProject", "Hook", "Lint", "Manifest", "Platform", "Project", + "Registry", "RegistryAssertion", "SnapcraftBuildPlanner", "Socket", diff --git a/snapcraft/models/assertions.py b/snapcraft/models/assertions.py index 8f1364173c..d557ff9092 100644 --- a/snapcraft/models/assertions.py +++ b/snapcraft/models/assertions.py @@ -14,29 +14,82 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . -"""Models for assertion sets.""" +"""Assertion models.""" -from typing import Any, Literal +from typing import Literal +import pydantic from craft_application import models +from typing_extensions import Self -class RegistryAssertion(models.CraftBaseModel): - """Data model for a registry assertion.""" +class Registry(models.CraftBaseModel): + """Access and data definitions for a specific facet of a snap or system.""" + + request: str | None = None + """Optional dot-separated path to access the field.""" + + storage: str + """Dot-separated storage path.""" + + access: Literal["read", "write", "read-write"] | None = None + """Access permissions for the field.""" + + content: list[Self] | None = None + """Optional nested rules.""" + + +class Rules(models.CraftBaseModel): + """A list of registries for a particular view.""" + + rules: list[Registry] + + +class EditableRegistryAssertion(models.CraftBaseModel): + """Subset of a registries assertion that can be edited by the user.""" account_id: str - authority_id: str - body: dict[str, Any] | str | None = None - body_length: str | None = None + """Issuer of the registry assertion and owner of the signing key.""" + name: str - revision: int = 0 - sign_key_sha3_384: str | None = None summary: str | None = None - timestamp: str + revision: int | None = 0 + + views: dict[str, Rules] + """A map of logical views of how the storage is accessed.""" + + body: str | None = None + """A JSON schema that defines the storage structure.""" + + +class RegistryAssertion(EditableRegistryAssertion): + """A full registries assertion containing editable and non-editable fields.""" + type: Literal["registry"] - views: dict[str, Any] + + authority_id: str + """Issuer of the registry assertion and owner of the signing key.""" + + timestamp: str + """Timestamp of when the assertion was issued.""" + + body_length: str | None = None + """Length of the body field.""" + + sign_key_sha3_384: str | None = None + """Signing key ID.""" + + +class RegistriesList(models.CraftBaseModel): + """A list of registry assertions.""" + + registry_list: list[RegistryAssertion] = pydantic.Field(default_factory=list) # this will be a union for validation sets and registries once # validation sets are migrated from the legacy codebase Assertion = RegistryAssertion + +# this will be a union for editable validation sets and editable registries once +# validation sets are migrated from the legacy codebase +EditableAssertion = EditableRegistryAssertion diff --git a/tests/unit/models/test_assertions.py b/tests/unit/models/test_assertions.py index d2653dcd71..a6fed23d95 100644 --- a/tests/unit/models/test_assertions.py +++ b/tests/unit/models/test_assertions.py @@ -17,11 +17,92 @@ """Tests for Assertion models.""" +from snapcraft.models import EditableRegistryAssertion, Registry, RegistryAssertion -def test_assertion_defaults(fake_registry_assertion_data, check): + +def test_registry_defaults(check): + """Test default values of the Registry model.""" + registry = Registry.unmarshal({"storage": "test-storage"}) + + check.is_none(registry.request) + check.is_none(registry.access) + check.is_none(registry.content) + + +def test_registry_nested(check): + """Test that nested registries are supported.""" + registry = Registry.unmarshal( + { + "request": "test-request", + "storage": "test-storage", + "access": "read", + "content": [ + { + "request": "nested-request", + "storage": "nested-storage", + "access": "write", + } + ], + } + ) + + check.equal(registry.request, "test-request") + check.equal(registry.storage, "test-storage") + check.equal(registry.access, "read") + check.equal( + registry.content, + [Registry(request="nested-request", storage="nested-storage", access="write")], + ) + + +def test_editable_registry_assertion_defaults(check): + """Test default values of the EditableRegistryAssertion model.""" + assertion = EditableRegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "name": "test-registry", + "views": { + "wifi-setup": { + "rules": [ + { + "storage": "wifi.ssids", + } + ] + } + }, + } + ) + + check.is_none(assertion.summary) + check.equal(assertion.revision, 0) + check.is_none(assertion.body) + + +def test_registry_assertion_defaults(check): """Test default values of the RegistryAssertion model.""" - check.equal(fake_registry_assertion_data.body, None) - check.equal(fake_registry_assertion_data.body_length, None) - check.equal(fake_registry_assertion_data.sign_key_sha3_384, None) - check.equal(fake_registry_assertion_data.summary, None) - check.equal(fake_registry_assertion_data.revision, 0) + assertion = RegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "authority_id": "test-authority-id", + "name": "test-registry", + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "access": "read-write", + "request": "ssids", + "storage": "wifi.ssids", + } + ] + } + }, + } + ) + + check.is_none(assertion.body) + check.is_none(assertion.body_length) + check.is_none(assertion.sign_key_sha3_384) + check.is_none(assertion.summary) + check.equal(assertion.revision, 0) From aed30068ddb05cd3dd0fabcaa19b70e0a16d349c Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 19 Sep 2024 14:42:17 -0500 Subject: [PATCH 086/151] build(deps): bump craft-application to 4.2.4 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 01712df5c4..515ced5b6b 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -24,7 +24,7 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.2.3 +craft-application==4.2.4 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 7e58aec50a..665fa0369f 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -19,7 +19,7 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.2.3 +craft-application==4.2.4 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 diff --git a/requirements.txt b/requirements.txt index 33eb42e01b..45c38f3fc6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.2.3 +craft-application==4.2.4 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 diff --git a/setup.py b/setup.py index c1ed03edc2..78ee0a0b6a 100755 --- a/setup.py +++ b/setup.py @@ -98,7 +98,7 @@ def recursive_data_files(directory, install_directory): "attrs", "catkin-pkg; sys_platform == 'linux'", "click", - "craft-application~=4.1", + "craft-application>=4.2.4,<5.0.0", "craft-archives~=2.0", "craft-cli~=2.6", "craft-grammar>=2.0.1,<3.0.0", From 741e36eb9a2dd18b17cdad7a099406726a87c1a2 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 19 Sep 2024 14:37:33 -0500 Subject: [PATCH 087/151] fix: catch remote build errors Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 6 +++++- tests/unit/test_application.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index ee7229fa87..ee2fd30a28 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -27,7 +27,7 @@ import craft_cli import craft_parts import craft_store -from craft_application import Application, AppMetadata, util +from craft_application import Application, AppMetadata, remote, util from craft_application.commands import get_other_command_group from craft_cli import emit from craft_parts.plugins.plugins import PluginType @@ -231,6 +231,10 @@ def _run_inner(self) -> int: cause=err, ) return_code = 1 + except remote.RemoteBuildError as err: + err.doc_slug = "/explanation/remote-build" + self._emit_error(err) + return_code = err.retcode return return_code diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index e4ee1d5998..9100a24601 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -17,9 +17,11 @@ import json import os +import re import sys from textwrap import dedent +import craft_application.remote import craft_cli import craft_parts.plugins import craft_store @@ -701,6 +703,25 @@ def test_store_key_error(mocker, capsys): ) +def test_remote_build_error(mocker, capsys): + """Catch remote build errors and include a documentation link.""" + mocker.patch( + "snapcraft.application.Application._run_inner", + side_effect=craft_application.remote.RemoteBuildError(message="test-error"), + ) + + return_code = application.main() + + assert return_code == 1 + _, err = capsys.readouterr() + + assert re.match( + r"^test-error\n" + r"For more information, check out: .*/explanation/remote-build\n", + err, + ) + + @pytest.mark.parametrize( "command", { From 711ac75428938c78039db60974246a2857219bae Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 20 Sep 2024 11:29:29 -0500 Subject: [PATCH 088/151] fix: add documentation links to remote build and esm base errors Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 4 ++++ tests/unit/test_application.py | 34 +++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index ee2fd30a28..df84454fa8 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -82,6 +82,7 @@ def _get_esm_error_for_base(base: str) -> None: f"Use Snapcraft {version} from the {channel!r} channel of snapcraft where " f"{base!r} was last supported." ), + doc_slug="/reference/bases", ) @@ -395,6 +396,7 @@ def _ensure_remote_build_supported(base: str) -> None: resolution=( "Valid values are 'disable-fallback' and 'force-fallback'." ), + doc_slug="/explanation/remote-build", ) # 2. core20 projects must use the legacy remote builder (#4885) @@ -407,6 +409,7 @@ def _ensure_remote_build_supported(base: str) -> None: resolution=( "Unset the environment variable or set it to 'force-fallback'." ), + doc_slug="/explanation/remote-build", ) # 3. core24 and newer projects must use the craft-application remote builder @@ -419,6 +422,7 @@ def _ensure_remote_build_supported(base: str) -> None: resolution=( "Unset the environment variable or set it to 'disable-fallback'." ), + doc_slug="/explanation/remote-build", ) @override diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index 9100a24601..ad1dab8a0b 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -400,7 +400,13 @@ def test_esm_error(snapcraft_yaml, base, monkeypatch, capsys): application.main() _, err = capsys.readouterr() - assert f"Base {base!r} is not supported by this version of Snapcraft" in err + + assert re.match( + rf"^Base {base!r} is not supported by this version of Snapcraft.\n" + rf"Recommended resolution: Use Snapcraft .* from the '.*' channel of snapcraft where {base!r} was last supported.\n" + r"For more information, check out: .*/reference/bases\n", + err, + ) @pytest.mark.parametrize("base", const.CURRENT_BASES) @@ -461,10 +467,12 @@ def test_run_remote_build_core24_error(monkeypatch, snapcraft_yaml, base, capsys application.main() _, err = capsys.readouterr() - assert ( - "'SNAPCRAFT_REMOTE_BUILD_STRATEGY=force-fallback' cannot be used " - "for core24 and newer snaps" - ) in err + assert re.match( + r"^'SNAPCRAFT_REMOTE_BUILD_STRATEGY=force-fallback' cannot be used for core24 and newer snaps\.\n" + r"Recommended resolution: Unset the environment variable or set it to 'disable-fallback'\.\n" + r"For more information, check out: .*/explanation/remote-build", + err, + ) @pytest.mark.parametrize("base", const.LEGACY_BASES) @@ -479,9 +487,11 @@ def test_run_envvar_disable_fallback_core20(snapcraft_yaml, base, monkeypatch, c application.main() _, err = capsys.readouterr() - assert ( - f"'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot be used for {base} snaps" - in err + assert re.match( + r"'SNAPCRAFT_REMOTE_BUILD_STRATEGY=disable-fallback' cannot be used for core20 snaps\.\n" + r"Recommended resolution: Unset the environment variable or set it to 'force-fallback'\.\n" + r"For more information, check out: .*/explanation/remote-build", + err, ) @@ -546,9 +556,11 @@ def test_run_envvar_invalid(snapcraft_yaml, base, monkeypatch, capsys): application.main() _, err = capsys.readouterr() - assert ( - "Unknown value 'badvalue' in environment variable 'SNAPCRAFT_REMOTE_BUILD_STRATEGY'" - in err + assert re.match( + r"Unknown value 'badvalue' in environment variable 'SNAPCRAFT_REMOTE_BUILD_STRATEGY'\.\n" + r"Recommended resolution: Valid values are 'disable-fallback' and 'force-fallback'\.\n" + r"For more information, check out: .*/explanation/remote-build", + err, ) From 3f8039517d7939719f22ccb68ee6972289097e10 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 18 Sep 2024 15:58:28 -0500 Subject: [PATCH 089/151] refactor: clean up assertion services Signed-off-by: Callahan Kovacs --- snapcraft/services/__init__.py | 8 ++-- snapcraft/services/assertions.py | 10 ++--- snapcraft/services/registries.py | 24 +++--------- snapcraft/services/service_factory.py | 6 +-- tests/unit/commands/test_registries.py | 4 +- tests/unit/conftest.py | 52 +++++++++++++------------- tests/unit/services/test_assertions.py | 24 ++++++------ tests/unit/services/test_registries.py | 18 ++++----- 8 files changed, 65 insertions(+), 81 deletions(-) diff --git a/snapcraft/services/__init__.py b/snapcraft/services/__init__.py index 18994abf21..746d7dbafb 100644 --- a/snapcraft/services/__init__.py +++ b/snapcraft/services/__init__.py @@ -16,20 +16,20 @@ """Snapcraft services.""" -from snapcraft.services.assertions import AssertionService +from snapcraft.services.assertions import Assertion from snapcraft.services.lifecycle import Lifecycle from snapcraft.services.package import Package from snapcraft.services.provider import Provider -from snapcraft.services.registries import RegistriesService +from snapcraft.services.registries import Registries from snapcraft.services.remotebuild import RemoteBuild from snapcraft.services.service_factory import SnapcraftServiceFactory __all__ = [ - "AssertionService", + "Assertion", "Lifecycle", "Package", "Provider", - "RegistriesService", + "Registries", "RemoteBuild", "SnapcraftServiceFactory", ] diff --git a/snapcraft/services/assertions.py b/snapcraft/services/assertions.py index 7cd833eb8f..0e6abd72a0 100644 --- a/snapcraft/services/assertions.py +++ b/snapcraft/services/assertions.py @@ -30,7 +30,7 @@ from snapcraft import const, errors, models, store -class AssertionService(base.AppService): +class Assertion(base.AppService): """Abstract service for interacting with assertions.""" @override @@ -41,8 +41,8 @@ def setup(self) -> None: @property @abc.abstractmethod - def _assertion_type(self) -> str: - """The pluralized name of the assertion type.""" + def _assertion_name(self) -> str: + """The lowercase name of the assertion type.""" @abc.abstractmethod def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: @@ -81,7 +81,7 @@ def list_assertions(self, *, output_format: str, name: str | None = None) -> Non match output_format: case const.OutputFormat.json: json_assertions = { - self._assertion_type.lower(): [ + f"{self._assertion_name}s": [ { header.lower(): value for header, value in zip(headers, assertion) @@ -102,4 +102,4 @@ def list_assertions(self, *, output_format: str, name: str | None = None) -> Non msg=f"'--format {output_format}'", ) else: - craft_cli.emit.message(f"No {self._assertion_type} found.") + craft_cli.emit.message(f"No {self._assertion_name}s found.") diff --git a/snapcraft/services/registries.py b/snapcraft/services/registries.py index 32aae29935..98c5edba0b 100644 --- a/snapcraft/services/registries.py +++ b/snapcraft/services/registries.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -"""Abstract service class for assertions.""" +"""Service class for registries.""" from __future__ import annotations @@ -23,39 +23,25 @@ from typing_extensions import override from snapcraft import models -from snapcraft.services import AssertionService +from snapcraft.services import Assertion -class RegistriesService(AssertionService): +class Registries(Assertion): """Service for interacting with registries.""" @property @override - def _assertion_type(self) -> str: - """The pluralized name of the assertion type.""" - return "registries" + def _assertion_name(self) -> str: + return "registries set" @override def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: - """Get assertions from the store. - - :param name: The name of the assertion to retrieve. If not provided, all - assertions are retrieved. - - :returns: A list of assertions. - """ return self._store_client.list_registries(name=name) @override def _normalize_assertions( self, assertions: list[models.Assertion] ) -> tuple[list[str], list[list[Any]]]: - """Convert a list of assertion models to a tuple of headers and data. - - :param assertions: A list of assertions to normalize. - - :returns: A tuple containing the headers and normalized assertions. - """ headers = ["Account ID", "Name", "Revision", "When"] registries = [ [ diff --git a/snapcraft/services/service_factory.py b/snapcraft/services/service_factory.py index fdafbc530b..bbb9f0630f 100644 --- a/snapcraft/services/service_factory.py +++ b/snapcraft/services/service_factory.py @@ -46,9 +46,9 @@ class SnapcraftServiceFactory(ServiceFactory): services.RemoteBuild ] = services.RemoteBuild RegistriesClass: type[ # type: ignore[reportIncompatibleVariableOverride] - services.RegistriesService - ] = services.RegistriesService + services.Registries + ] = services.Registries if TYPE_CHECKING: # Allow static type check to report correct types for Snapcraft services - registries: services.RegistriesService = None # type: ignore[assignment] + registries: services.Registries = None # type: ignore[assignment] diff --git a/tests/unit/commands/test_registries.py b/tests/unit/commands/test_registries.py index 683f1b72f9..22a75a6e31 100644 --- a/tests/unit/commands/test_registries.py +++ b/tests/unit/commands/test_registries.py @@ -23,9 +23,7 @@ @pytest.fixture def mock_list_assertions(mocker): - return mocker.patch( - "snapcraft.services.registries.RegistriesService.list_assertions" - ) + return mocker.patch("snapcraft.services.registries.Registries.list_assertions") @pytest.mark.usefixtures("memory_keyring") diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 04e061dad8..285cddb152 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -533,9 +533,9 @@ class FakeRemoteBuildService(RemoteBuild): @pytest.fixture() def registries_service(default_factory, mocker): from snapcraft.application import APP_METADATA - from snapcraft.services import RegistriesService + from snapcraft.services import Registries - service = RegistriesService(app=APP_METADATA, services=default_factory) + service = Registries(app=APP_METADATA, services=default_factory) service._store_client = mocker.patch( "snapcraft.store.StoreClientCLI", autospec=True ) @@ -544,32 +544,34 @@ def registries_service(default_factory, mocker): @pytest.fixture() -def fake_registry_assertion_data(): - """Returns a dictionary of assertion data with required fields.""" +def fake_registry_assertion(): + """Returns a fake registry assertion with required fields.""" from snapcraft.models import RegistryAssertion - def _assertion_data(**kwargs) -> dict[str, Any]: - return { - "account_id": "test-account-id", - "authority_id": "test-authority-id", - "name": "test-registry", - "timestamp": "2024-01-01T10:20:30Z", - "type": "registry", - "views": { - "wifi-setup": { - "rules": [ - { - "access": "read-write", - "request": "ssids", - "storage": "wifi.ssids", - } - ] - } - }, - **kwargs, - } + def _fake_registry_assertion(**kwargs) -> RegistryAssertion: + return RegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "authority_id": "test-authority-id", + "name": "test-registry", + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "access": "read-write", + "request": "ssids", + "storage": "wifi.ssids", + } + ] + } + }, + **kwargs, + } + ) - return RegistryAssertion.unmarshal(_assertion_data()) + return _fake_registry_assertion @pytest.fixture() diff --git a/tests/unit/services/test_assertions.py b/tests/unit/services/test_assertions.py index 1d191f8277..490cd06bf6 100644 --- a/tests/unit/services/test_assertions.py +++ b/tests/unit/services/test_assertions.py @@ -34,15 +34,15 @@ class FakeAssertion(CraftBaseModel): @pytest.fixture -def mock_assertion_service(default_factory): +def fake_assertion_service(default_factory): from snapcraft.application import APP_METADATA - from snapcraft.services import AssertionService + from snapcraft.services import Assertion - class FakeAssertionService(AssertionService): + class FakeAssertionService(Assertion): @property @override - def _assertion_type(self) -> str: - return "fake-assertions" + def _assertion_name(self) -> str: + return "fake assertion" @override def _get_assertions( # type: ignore[override] @@ -70,9 +70,9 @@ def _normalize_assertions( # type: ignore[override] return FakeAssertionService(app=APP_METADATA, services=default_factory) -def test_list_assertions_table(mock_assertion_service, emitter): +def test_list_assertions_table(fake_assertion_service, emitter): """List assertions as a table.""" - mock_assertion_service.list_assertions( + fake_assertion_service.list_assertions( output_format=const.OutputFormat.table, name="test-registry" ) @@ -86,9 +86,9 @@ def test_list_assertions_table(mock_assertion_service, emitter): ) -def test_list_assertions_json(mock_assertion_service, emitter): +def test_list_assertions_json(fake_assertion_service, emitter): """List assertions as json.""" - mock_assertion_service.list_assertions( + fake_assertion_service.list_assertions( output_format=const.OutputFormat.json, name="test-registry" ) @@ -96,7 +96,7 @@ def test_list_assertions_json(mock_assertion_service, emitter): textwrap.dedent( """\ { - "fake-assertions": [ + "fake assertions": [ { "test-field-1": "test-value-1", "test-field-2": 0 @@ -111,11 +111,11 @@ def test_list_assertions_json(mock_assertion_service, emitter): ) -def test_list_assertions_unknown_format(mock_assertion_service): +def test_list_assertions_unknown_format(fake_assertion_service): """Error for unknown formats.""" expected = "Command or feature not implemented: '--format unknown'" with pytest.raises(errors.FeatureNotImplemented, match=expected): - mock_assertion_service.list_assertions( + fake_assertion_service.list_assertions( output_format="unknown", name="test-registry" ) diff --git a/tests/unit/services/test_registries.py b/tests/unit/services/test_registries.py index 69a9a52e76..077c9fc2f1 100644 --- a/tests/unit/services/test_registries.py +++ b/tests/unit/services/test_registries.py @@ -18,7 +18,7 @@ def test_registries_service_type(registries_service): - assert registries_service._assertion_type == "registries" + assert registries_service._assertion_name == "registries set" def test_get_assertions(registries_service): @@ -36,16 +36,14 @@ def test_normalize_assertions_empty(registries_service, check): check.equal(registries, []) -def test_normalize_assertions(fake_registry_assertion_data, registries_service, check): +def test_normalize_assertions(fake_registry_assertion, registries_service, check): registries = [ - fake_registry_assertion_data, - fake_registry_assertion_data.copy( - update={ - "account_id": "test-account-id-2", - "name": "test-registry-2", - "revision": 100, - "timestamp": "2024-12-31", - } + fake_registry_assertion(), + fake_registry_assertion( + account_id="test-account-id-2", + name="test-registry-2", + revision=100, + timestamp="2024-12-31", ), ] From b813ceee876d407c8707ffcc9fff62d12fc3a232 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 18 Sep 2024 15:58:59 -0500 Subject: [PATCH 090/151] feat: add `edit-registries` command Signed-off-by: Callahan Kovacs --- snapcraft/cli.py | 1 + snapcraft/commands/__init__.py | 3 +- snapcraft/commands/registries.py | 40 +++- snapcraft/services/assertions.py | 130 +++++++++++- snapcraft/services/registries.py | 71 +++++++ snapcraft/store/client.py | 2 + tests/unit/commands/test_registries.py | 21 ++ tests/unit/services/test_assertions.py | 262 +++++++++++++++++++++++++ tests/unit/services/test_registries.py | 70 +++++++ tests/unit/store/test_client.py | 5 + 10 files changed, 602 insertions(+), 3 deletions(-) diff --git a/snapcraft/cli.py b/snapcraft/cli.py index 7da38b0eab..98d890ceeb 100644 --- a/snapcraft/cli.py +++ b/snapcraft/cli.py @@ -143,6 +143,7 @@ craft_cli.CommandGroup( "Store Registries", [ + commands.StoreEditRegistriesCommand, commands.StoreListRegistriesCommand, ], ), diff --git a/snapcraft/commands/__init__.py b/snapcraft/commands/__init__.py index bd145d721c..b1a8cdc5eb 100644 --- a/snapcraft/commands/__init__.py +++ b/snapcraft/commands/__init__.py @@ -52,7 +52,7 @@ StoreRegisterCommand, ) from .plugins import ListPluginsCommand, PluginsCommand -from .registries import StoreListRegistriesCommand +from .registries import StoreEditRegistriesCommand, StoreListRegistriesCommand from .remote import RemoteBuildCommand from .status import ( StoreListRevisionsCommand, @@ -77,6 +77,7 @@ "SnapCommand", "StoreCloseCommand", "StoreEditValidationSetsCommand", + "StoreEditRegistriesCommand", "StoreExportLoginCommand", "StoreLegacyCreateKeyCommand", "StoreLegacyGatedCommand", diff --git a/snapcraft/commands/registries.py b/snapcraft/commands/registries.py index 1c70a959b4..95fe349df2 100644 --- a/snapcraft/commands/registries.py +++ b/snapcraft/commands/registries.py @@ -29,7 +29,7 @@ class StoreListRegistriesCommand(craft_application.commands.AppCommand): """List registries.""" name = "list-registries" - help_msg = "List registries" + help_msg = "List registries sets" overview = textwrap.dedent( """ List all registries for the authenticated account. @@ -69,3 +69,41 @@ def run(self, parsed_args: "argparse.Namespace"): name=parsed_args.name, output_format=parsed_args.format, ) + + +class StoreEditRegistriesCommand(craft_application.commands.AppCommand): + """Edit a registries set.""" + + name = "edit-registries" + help_msg = "Edit or create a registries set" + overview = textwrap.dedent( + """ + Edit a registries set. + + If the registries set does not exist, then a new registries set will be created. + + The account ID of the authenticated account can be determined with the + ``snapcraft whoami`` command. + + Use the ``list-registries`` command to view existing registries. + """ + ) + _services: services.SnapcraftServiceFactory # type: ignore[reportIncompatibleVariableOverride] + + @override + def fill_parser(self, parser: "argparse.ArgumentParser") -> None: + parser.add_argument( + "account_id", + metavar="account-id", + help="The account ID of the registries set to edit", + ) + parser.add_argument( + "name", metavar="name", help="Name of the registries set to edit" + ) + + @override + def run(self, parsed_args: "argparse.Namespace"): + self._services.registries.edit_assertion( + name=parsed_args.name, + account_id=parsed_args.account_id, + ) diff --git a/snapcraft/services/assertions.py b/snapcraft/services/assertions.py index 0e6abd72a0..b3650b7883 100644 --- a/snapcraft/services/assertions.py +++ b/snapcraft/services/assertions.py @@ -19,15 +19,23 @@ from __future__ import annotations import abc +import io import json +import os +import pathlib +import subprocess +import tempfile from typing import Any import craft_cli import tabulate +import yaml +from craft_application.errors import CraftValidationError from craft_application.services import base +from craft_application.util import safe_yaml_load from typing_extensions import override -from snapcraft import const, errors, models, store +from snapcraft import const, errors, models, store, utils class Assertion(base.AppService): @@ -37,6 +45,7 @@ class Assertion(base.AppService): def setup(self) -> None: """Application-specific service setup.""" self._store_client = store.StoreClientCLI() + self._editor_cmd = os.getenv("EDITOR", "vi") super().setup() @property @@ -44,6 +53,11 @@ def setup(self) -> None: def _assertion_name(self) -> str: """The lowercase name of the assertion type.""" + @property + @abc.abstractmethod + def _editable_assertion_class(self) -> type[models.EditableAssertion]: + """The type of the editable assertion.""" + @abc.abstractmethod def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: """Get assertions from the store. @@ -65,6 +79,29 @@ def _normalize_assertions( :returns: A tuple containing the headers and normalized assertions. """ + @abc.abstractmethod + def _generate_yaml_from_model(self, assertion: models.Assertion) -> str: + """Generate a multi-line yaml string from an existing assertion. + + This string should contain only user-editable data. + + :param assertion: The assertion to generate a yaml string from. + + :returns: A multi-line yaml string. + """ + + @abc.abstractmethod + def _generate_yaml_from_template(self, name: str, account_id: str) -> str: + """Generate a multi-line yaml string of a default assertion. + + This string should contain only user-editable data. + + :param name: The name of the assertion. + :param account_id: The account ID of the authenticated user. + + :returns: A multi-line yaml string. + """ + def list_assertions(self, *, output_format: str, name: str | None = None) -> None: """List assertions from the store. @@ -103,3 +140,94 @@ def list_assertions(self, *, output_format: str, name: str | None = None) -> Non ) else: craft_cli.emit.message(f"No {self._assertion_name}s found.") + + def _edit_yaml_file(self, filepath: pathlib.Path) -> models.EditableAssertion: + """Edit a yaml file and unmarshal it to an editable assertion. + + If the file is not valid, the user is prompted to amend it. + + :param filepath: The path to the yaml file to edit. + + :returns: The edited assertion. + """ + while True: + craft_cli.emit.debug(f"Using {self._editor_cmd} to edit file.") + with craft_cli.emit.pause(): + subprocess.run([self._editor_cmd, filepath], check=True) + try: + with filepath.open() as file: + data = safe_yaml_load(file) + edited_assertion = self._editable_assertion_class.from_yaml_data( + data=data, + # filepath is only shown for pydantic errors and snapcraft should + # not expose the temp file name + filepath=pathlib.Path(self._assertion_name.replace(" ", "-")), + ) + return edited_assertion + except (yaml.YAMLError, CraftValidationError) as err: + craft_cli.emit.message(f"{err!s}") + if not utils.confirm_with_user( + f"Do you wish to amend the {self._assertion_name}?" + ): + raise errors.SnapcraftError("operation aborted") from err + + def _get_yaml_data(self, name: str, account_id: str) -> str: + craft_cli.emit.progress( + f"Requesting {self._assertion_name} '{name}' from the store." + ) + + if assertions := self._get_assertions(name=name): + yaml_data = self._generate_yaml_from_model(assertions[0]) + else: + craft_cli.emit.progress( + f"Creating a new {self._assertion_name} because no existing " + f"{self._assertion_name} named '{name}' was found for the " + "authenticated account.", + permanent=True, + ) + yaml_data = self._generate_yaml_from_template( + name=name, account_id=account_id + ) + + return yaml_data + + @staticmethod + def _write_to_file(yaml_data: str) -> pathlib.Path: + with tempfile.NamedTemporaryFile() as temp_file: + filepath = pathlib.Path(temp_file.name) + craft_cli.emit.trace(f"Writing yaml data to temporary file '{filepath}'.") + filepath.write_text(yaml_data, encoding="utf-8") + return filepath + + @staticmethod + def _remove_temp_file(filepath: pathlib.Path) -> None: + craft_cli.emit.trace(f"Removing temporary file '{filepath}'.") + filepath.unlink() + + def edit_assertion(self, *, name: str, account_id: str) -> None: + """Edit, sign and upload an assertion. + + If the assertion does not exist, a new assertion is created from a template. + + :param name: The name of the assertion to edit. + :param account_id: The account ID associated with the registries set. + """ + yaml_data = self._get_yaml_data(name=name, account_id=account_id) + yaml_file = self._write_to_file(yaml_data) + original_assertion = self._editable_assertion_class.unmarshal( + safe_yaml_load(io.StringIO(yaml_data)) + ) + edited_assertion = self._edit_yaml_file(yaml_file) + + if edited_assertion == original_assertion: + craft_cli.emit.message("No changes made.") + self._remove_temp_file(yaml_file) + return + + # TODO: build, sign, and push assertion (#5018) + + self._remove_temp_file(yaml_file) + craft_cli.emit.message(f"Successfully edited {self._assertion_name} {name!r}.") + raise errors.FeatureNotImplemented( + f"Building, signing and uploading {self._assertion_name} is not implemented.", + ) diff --git a/snapcraft/services/registries.py b/snapcraft/services/registries.py index 98c5edba0b..e6cd785c16 100644 --- a/snapcraft/services/registries.py +++ b/snapcraft/services/registries.py @@ -18,13 +18,55 @@ from __future__ import annotations +import textwrap from typing import Any +from craft_application.util import dump_yaml from typing_extensions import override from snapcraft import models from snapcraft.services import Assertion +_REGISTRY_SETS_TEMPLATE = textwrap.dedent( + """\ + account-id: {account_id} + name: {set_name} + # summary: {summary} + # The revision for this registries set + # revision: {revision} + {views} + {body} + """ +) + + +_REGISTRY_SETS_VIEWS_TEMPLATE = textwrap.dedent( + """\ + views: + wifi-setup: + rules: + - request: ssids + storage: wifi.ssids + access: read + """ +) + + +_REGISTRY_SETS_BODY_TEMPLATE = textwrap.dedent( + """\ + body: |- + { + "storage": { + "schema": { + "wifi": { + "values": "any" + } + } + } + } + """ +) + class Registries(Assertion): """Service for interacting with registries.""" @@ -34,6 +76,11 @@ class Registries(Assertion): def _assertion_name(self) -> str: return "registries set" + @property + @override + def _editable_assertion_class(self) -> type[models.EditableAssertion]: + return models.EditableRegistryAssertion + @override def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: return self._store_client.list_registries(name=name) @@ -54,3 +101,27 @@ def _normalize_assertions( ] return headers, registries + + @override + def _generate_yaml_from_model(self, assertion: models.Assertion) -> str: + return _REGISTRY_SETS_TEMPLATE.format( + account_id=assertion.account_id, + views=dump_yaml( + {"views": assertion.marshal().get("views")}, default_flow_style=False + ), + body=dump_yaml({"body": assertion.body}, default_flow_style=False), + summary=assertion.summary, + set_name=assertion.name, + revision=assertion.revision, + ) + + @override + def _generate_yaml_from_template(self, name: str, account_id: str) -> str: + return _REGISTRY_SETS_TEMPLATE.format( + account_id=account_id, + views=_REGISTRY_SETS_VIEWS_TEMPLATE, + body=_REGISTRY_SETS_BODY_TEMPLATE, + summary="A brief summary of the registries set", + set_name=name, + revision=1, + ) diff --git a/snapcraft/store/client.py b/snapcraft/store/client.py index 79e4d2d6d6..bbb58aed43 100644 --- a/snapcraft/store/client.py +++ b/snapcraft/store/client.py @@ -527,6 +527,8 @@ def list_registries( if assertions := response.json().get("assertions"): for assertion_data in assertions: emit.debug(f"Parsing assertion: {assertion_data}") + # move body into model + assertion_data["headers"]["body"] = assertion_data["body"] assertion = models.RegistryAssertion.unmarshal( assertion_data["headers"] ) diff --git a/tests/unit/commands/test_registries.py b/tests/unit/commands/test_registries.py index 22a75a6e31..c4f34dac92 100644 --- a/tests/unit/commands/test_registries.py +++ b/tests/unit/commands/test_registries.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +"""Unit tests for registries commands.""" + import sys import pytest @@ -26,6 +28,11 @@ def mock_list_assertions(mocker): return mocker.patch("snapcraft.services.registries.Registries.list_assertions") +@pytest.fixture +def mock_edit_assertion(mocker): + return mocker.patch("snapcraft.services.registries.Registries.edit_assertion") + + @pytest.mark.usefixtures("memory_keyring") @pytest.mark.parametrize("output_format", const.OUTPUT_FORMATS) @pytest.mark.parametrize("name", [None, "test"]) @@ -56,3 +63,17 @@ def test_list_registries_default_format(mocker, mock_list_assertions, name): app.run() mock_list_assertions.assert_called_once_with(name=name, output_format="table") + + +@pytest.mark.usefixtures("memory_keyring") +def test_edit_registries(mocker, mock_edit_assertion): + """Test `snapcraft edit-registries`.""" + cmd = ["snapcraft", "edit-registries", "test-account-id", "test-name"] + mocker.patch.object(sys, "argv", cmd) + + app = application.create_app() + app.run() + + mock_edit_assertion.assert_called_once_with( + name="test-name", account_id="test-account-id" + ) diff --git a/tests/unit/services/test_assertions.py b/tests/unit/services/test_assertions.py index 490cd06bf6..73b15bc92e 100644 --- a/tests/unit/services/test_assertions.py +++ b/tests/unit/services/test_assertions.py @@ -18,6 +18,7 @@ import textwrap from typing import Any +from unittest import mock import pytest from craft_application.models import CraftBaseModel @@ -26,6 +27,44 @@ from snapcraft import const, errors +@pytest.fixture(autouse=True) +def mock_store_client(mocker): + """Mock the store client before it is initialized in the service's setup.""" + return mocker.patch( + "snapcraft.store.StoreClientCLI", + ) + + +@pytest.fixture(autouse=True) +def fake_editor(monkeypatch): + """Set a fake editor.""" + return monkeypatch.setenv("EDITOR", "faux-vi") + + +@pytest.fixture +def mock_confirm_with_user(mocker, request): + """Mock the confirm_with_user function.""" + return mocker.patch("snapcraft.utils.confirm_with_user", return_value=request.param) + + +@pytest.fixture +def mock_subprocess_run(mocker, tmp_path, request): + """Mock the subprocess.run function to write data to a file. + + :param request: A list of strings to write to a file. Each time the subprocess.run + function is called, the last string in the list will be written to the file + and removed from the list. + """ + data_write = request.param.copy() + tmp_file = tmp_path / "assertion-file" + + def side_effect(*args, **kwargs): + tmp_file.write_text(data_write.pop(), encoding="utf-8") + + subprocess_mock = mocker.patch("subprocess.run", side_effect=side_effect) + return subprocess_mock + + class FakeAssertion(CraftBaseModel): """Fake assertion model.""" @@ -44,6 +83,13 @@ class FakeAssertionService(Assertion): def _assertion_name(self) -> str: return "fake assertion" + @property + @override + def _editable_assertion_class( # type: ignore[override] + self, + ) -> type[FakeAssertion]: + return FakeAssertion + @override def _get_assertions( # type: ignore[override] self, name: str | None = None @@ -67,9 +113,41 @@ def _normalize_assertions( # type: ignore[override] ] return headers, assertion_data + @override + def _generate_yaml_from_model( # type: ignore[override] + self, assertion: FakeAssertion + ) -> str: + return textwrap.dedent( + """\ + test-field-1: test-value-1 + test-field-2: 0 + """ + ) + + @override + def _generate_yaml_from_template(self, name: str, account_id: str) -> str: + return textwrap.dedent( + """\ + test-field-1: default-value-1 + test-field-2: 0 + """ + ) + return FakeAssertionService(app=APP_METADATA, services=default_factory) +@pytest.fixture +def fake_edit_yaml_file(mocker, fake_assertion_service): + """Apply a fake edit to a yaml file.""" + return mocker.patch.object( + fake_assertion_service, + "_edit_yaml_file", + return_value=FakeAssertion( + test_field_1="test-value-1-UPDATED", test_field_2=999 + ), + ) + + def test_list_assertions_table(fake_assertion_service, emitter): """List assertions as a table.""" fake_assertion_service.list_assertions( @@ -119,3 +197,187 @@ def test_list_assertions_unknown_format(fake_assertion_service): fake_assertion_service.list_assertions( output_format="unknown", name="test-registry" ) + + +def test_edit_assertions_changes_made( + fake_edit_yaml_file, fake_assertion_service, emitter +): + """Edit an assertion and make a valid change.""" + expected = "Building, signing and uploading fake assertion is not implemented" + fake_assertion_service.setup() + + with pytest.raises(errors.FeatureNotImplemented, match=expected): + fake_assertion_service.edit_assertion( + name="test-registry", account_id="test-account-id" + ) + + emitter.assert_message("Successfully edited fake assertion 'test-registry'.") + + +def test_edit_assertions_no_changes_made( + fake_edit_yaml_file, fake_assertion_service, emitter, mocker +): + """Edit an assertion but make no changes to the data.""" + mocker.patch.object( + fake_assertion_service, + "_edit_yaml_file", + # make no changes to the fake assertion + return_value=FakeAssertion(test_field_1="test-value-1", test_field_2=0), + ) + fake_assertion_service.setup() + + fake_assertion_service.edit_assertion( + name="test-registry", account_id="test-account-id" + ) + + emitter.assert_message("No changes made.") + + +@pytest.mark.parametrize("editor", [None, "faux-vi"]) +@pytest.mark.parametrize( + "mock_subprocess_run", + [ + [ + textwrap.dedent( + """\ + test-field-1: test-value-1-UPDATED + test-field-2: 999 + """ + ), + ], + ], + indirect=True, +) +@pytest.mark.parametrize("mock_confirm_with_user", [True], indirect=True) +def test_edit_yaml_file( + editor, + fake_assertion_service, + tmp_path, + mock_confirm_with_user, + mock_subprocess_run, + monkeypatch, +): + """Successfully edit a yaml file with the correct editor.""" + if editor: + monkeypatch.setenv("EDITOR", editor) + expected_editor = editor + else: + monkeypatch.delenv("EDITOR", raising=False) + # default is vi + expected_editor = "vi" + tmp_file = tmp_path / "assertion-file" + fake_assertion_service.setup() + + edited_assertion = fake_assertion_service._edit_yaml_file(tmp_file) + + assert edited_assertion == FakeAssertion( + test_field_1="test-value-1-UPDATED", test_field_2=999 + ) + mock_confirm_with_user.assert_not_called() + assert mock_subprocess_run.mock_calls == [ + mock.call([expected_editor, tmp_file], check=True) + ] + + +@pytest.mark.parametrize( + "mock_subprocess_run", + [ + pytest.param( + [ + textwrap.dedent( + """\ + test-field-1: test-value-1-UPDATED + test-field-2: 999 + """ + ), + textwrap.dedent( + """\ + bad yaml {{ + test-field-1: test-value-1 + test-field-2: 0 + """ + ), + ], + id="invalid yaml syntax", + ), + pytest.param( + [ + textwrap.dedent( + """\ + test-field-1: test-value-1-UPDATED + test-field-2: 999 + """ + ), + textwrap.dedent( + """\ + extra-field: not-allowed + test-field-1: [wrong data type] + test-field-2: 0 + """ + ), + ], + id="invalid pydantic data", + ), + ], + indirect=True, +) +@pytest.mark.parametrize("mock_confirm_with_user", [True], indirect=True) +def test_edit_yaml_file_error_retry( + fake_assertion_service, + tmp_path, + mock_confirm_with_user, + mock_subprocess_run, +): + """Edit a yaml file but encounter an error and retry.""" + tmp_file = tmp_path / "assertion-file" + fake_assertion_service.setup() + + edited_assertion = fake_assertion_service._edit_yaml_file(tmp_file) + + assert edited_assertion == FakeAssertion( + test_field_1="test-value-1-UPDATED", test_field_2=999 + ) + assert mock_confirm_with_user.mock_calls == [ + mock.call("Do you wish to amend the fake assertion?") + ] + assert ( + mock_subprocess_run.mock_calls + == [mock.call(["faux-vi", tmp_file], check=True)] * 2 + ) + + +@pytest.mark.parametrize( + "mock_subprocess_run", + [ + [ + textwrap.dedent( + """\ + bad yaml {{ + test-field-1: test-value-1 + test-field-2: 0 + """ + ), + ], + ], + indirect=True, +) +@pytest.mark.parametrize("mock_confirm_with_user", [False], indirect=True) +def test_edit_error_no_retry( + fake_assertion_service, + tmp_path, + mock_confirm_with_user, + mock_subprocess_run, +): + """Edit a yaml file and encounter an error but do not retry.""" + tmp_file = tmp_path / "assertion-file" + fake_assertion_service.setup() + + with pytest.raises(errors.SnapcraftError, match="operation aborted"): + fake_assertion_service._edit_yaml_file(tmp_file) + + assert mock_confirm_with_user.mock_calls == [ + mock.call("Do you wish to amend the fake assertion?") + ] + assert mock_subprocess_run.mock_calls == [ + mock.call(["faux-vi", tmp_file], check=True) + ] diff --git a/tests/unit/services/test_registries.py b/tests/unit/services/test_registries.py index 077c9fc2f1..f791cceb9a 100644 --- a/tests/unit/services/test_registries.py +++ b/tests/unit/services/test_registries.py @@ -16,11 +16,19 @@ """Tests for the registries service.""" +import textwrap + +from snapcraft.models import EditableRegistryAssertion + def test_registries_service_type(registries_service): assert registries_service._assertion_name == "registries set" +def test_editable_assertion_class(registries_service): + assert registries_service._editable_assertion_class == EditableRegistryAssertion + + def test_get_assertions(registries_service): registries_service._get_assertions("test-registry") @@ -59,3 +67,65 @@ def test_normalize_assertions(fake_registry_assertion, registries_service, check ["test-account-id-2", "test-registry-2", 100, "2024-12-31"], ], ) + + +def test_generate_yaml_from_model(fake_registry_assertion, registries_service): + assertion = fake_registry_assertion( + summary="test-summary", + revision="10", + views={ + "wifi-setup": { + "rules": [ + { + "request": "test-request", + "storage": "test-storage", + "access": "read", + "content": [ + { + "request": "nested-request", + "storage": "nested-storage", + "access": "write", + } + ], + } + ] + } + }, + body=( + "{\n 'storage': {\n 'schema': {\n 'wifi': {\n " + "'values': 'any'\n }\n }\n }\n}" + ), + ) + yaml_data = registries_service._generate_yaml_from_model(assertion) + + assert yaml_data == textwrap.dedent( + """\ + account-id: test-account-id + name: test-registry + # summary: test-summary + # The revision for this registries set + # revision: 10 + views: + wifi-setup: + rules: + - request: test-request + storage: test-storage + access: read + content: + - request: nested-request + storage: nested-storage + access: write + + body: |- + { + 'storage': { + 'schema': { + 'wifi': { + 'values': 'any' + } + } + } + } + + """ + ) diff --git a/tests/unit/store/test_client.py b/tests/unit/store/test_client.py index 160132cbdc..8a41d82fce 100644 --- a/tests/unit/store/test_client.py +++ b/tests/unit/store/test_client.py @@ -1101,6 +1101,11 @@ def test_list_registries(name, fake_client, list_registries_payload, check): check.is_instance(registries, list) for registry in registries: check.is_instance(registry, models.RegistryAssertion) + check.equal( + registry.body, + '{\n "storage": {\n "schema": {\n "wifi": {\n ' + '"values": "any"\n }\n }\n }\n}', + ) check.equal( fake_client.request.mock_calls, [ From 145a790fbb21d30486e17c01cfec43f328b8bb22 Mon Sep 17 00:00:00 2001 From: Callahan Date: Fri, 20 Sep 2024 17:41:47 -0500 Subject: [PATCH 091/151] docs(changelog): add 8.4.1 release notes (#5055) Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index 803e213ca9..ff8ef38273 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -68,6 +68,66 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.4.1 (2024-Sep-20) +------------------- + +Core +==== + +* Fix a regression where numeric entries in ``snapcraft.yaml`` couldn't be + parsed. + +Bases +##### + +core24 +"""""" + +* Fix a regression where ``build-for`` couldn't be omitted in a ``platforms`` + entry in a ``snapcraft.yaml`` file. + +* Fix a regression where ``--shell`` and ``--shell-after`` weren't supported + for the ``pack`` command (`#4963`_). + +* Fix a regression where ``--debug`` wouldn't open a shell into the build + environment if the packing step fails (`#4959`_). + +Plugins +####### + +NPM +""" + +* Fix a bug where NPM parts fail to build if the ``pull`` and ``build`` steps + didn't occur in the same instance of Snapcraft. + +Command line +============ + +* Fix a regression where store errors would be raised as an internal error + (`#4930`_). + +* Add documentation links for error messages about using an `ESM base`_. + +Remote build +============ + +* Fix a regression where ``--build-for`` and ``--platform`` couldn't accept + comma-separated values (`#4990`_). + +* Fix a regression where remote build errors would be raised as an internal + error (`#4908`_). + +* Add documentation links and recommended resolutions to remote-build errors. + +Store +===== + +* Fix a regression where Ubuntu One macaroons couldn't be refreshed + (`#5048`_). + +For a complete list of changes, check out the `8.4.1`_ release on GitHub. + 8.4.0 (2024-Sep-10) ------------------- @@ -1119,11 +1179,17 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4886: https://github.com/canonical/snapcraft/issues/4886 .. _#4889: https://github.com/canonical/snapcraft/issues/4889 .. _#4890: https://github.com/canonical/snapcraft/issues/4890 +.. _#4908: https://github.com/canonical/snapcraft/issues/4908 .. _#4909: https://github.com/canonical/snapcraft/issues/4909 +.. _#4930: https://github.com/canonical/snapcraft/issues/4930 .. _#4941: https://github.com/canonical/snapcraft/issues/4941 .. _#4942: https://github.com/canonical/snapcraft/issues/4942 +.. _#4959: https://github.com/canonical/snapcraft/issues/4959 +.. _#4963: https://github.com/canonical/snapcraft/issues/4963 +.. _#4990: https://github.com/canonical/snapcraft/issues/4990 .. _#4995: https://github.com/canonical/snapcraft/issues/4995 .. _#5008: https://github.com/canonical/snapcraft/issues/5008 +.. _#5048: https://github.com/canonical/snapcraft/issues/5048 .. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 .. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 @@ -1150,3 +1216,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 .. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 .. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 +.. _8.4.1: https://github.com/canonical/snapcraft/releases/tag/8.4.1 From 6c2e2a81c55c0e5608316a56381c6e68e738d182 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 07:54:37 -0500 Subject: [PATCH 092/151] build(deps): update bugfixes (main) (#5058) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements-devel.txt | 12 ++++++------ requirements-docs.txt | 6 +++--- requirements.txt | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 515ced5b6b..2660b4f900 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -72,7 +72,7 @@ Markdown==3.7 markdown-it-py==3.0.0 MarkupSafe==2.1.5 mccabe==0.7.0 -mdit-py-plugins==0.4.1 +mdit-py-plugins==0.4.2 mdurl==0.1.2 more-itertools==10.4.0 msgpack==1.0.8 @@ -116,7 +116,7 @@ pyproject-api==1.7.1 pyramid==2.0.2 pyRFC3339==1.1 pyspelling==2.10 -pytest==8.3.2 +pytest==8.3.3 pytest-check==2.4.1 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -164,7 +164,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.4 +starlette==0.38.5 tabulate==0.9.0 testscenarios==0.5.0 testtools==2.7.2 @@ -173,7 +173,7 @@ toml==0.10.2 tomlkit==0.13.2 tox==4.18.0 translationstring==1.4 -types-PyYAML==6.0.12.20240808 +types-PyYAML==6.0.12.20240917 types-requests==2.31.0.6 types-setuptools==71.1.0.20240818 types-simplejson==3.19.0.20240801 @@ -186,7 +186,7 @@ urllib3==1.26.20 uvicorn==0.30.6 validators==0.33.0 venusian==3.1.0 -virtualenv==20.26.3 +virtualenv==20.26.5 wadllib==1.3.6 watchfiles==0.23.0 wcmatch==9.0 @@ -196,7 +196,7 @@ websockets==12.0 wheel==0.44.0 ws4py==0.5.1 yamllint==1.35.1 -zipp==3.20.1 +zipp==3.20.2 zope.deprecation==5.0 zope.interface==7.0.3 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" diff --git a/requirements-docs.txt b/requirements-docs.txt index 665fa0369f..787942bceb 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -59,7 +59,7 @@ macaroonbakery==1.3.4 Markdown==3.7 markdown-it-py==3.0.0 MarkupSafe==2.1.5 -mdit-py-plugins==0.4.1 +mdit-py-plugins==0.4.2 mdurl==0.1.2 more-itertools==10.4.0 msgpack==1.0.8 @@ -130,7 +130,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.4 +starlette==0.38.5 tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 @@ -146,5 +146,5 @@ webencodings==0.5.1 websockets==12.0 wheel==0.44.0 ws4py==0.5.1 -zipp==3.20.1 +zipp==3.20.2 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" diff --git a/requirements.txt b/requirements.txt index 45c38f3fc6..638d3ddb58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -77,5 +77,5 @@ validators==0.33.0 wadllib==1.3.6 wheel==0.44.0 ws4py==0.5.1 -zipp==3.20.1 +zipp==3.20.2 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" From 5ea2c393c31279a48937096720962caf03b3bce2 Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 25 Sep 2024 10:40:41 -0500 Subject: [PATCH 093/151] chore: update references from snapcore to canonical (#5062) All repositories in the snapcore project have been migrated to canonical. Any code related to Travis CI is outdated and can be removed, but that should be a separate PR. Signed-off-by: Callahan Kovacs --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/publish.yaml | 4 ++-- .github/workflows/spread-scheduled.yaml | 2 +- .github/workflows/spread.yml | 2 +- .gitmodules | 2 +- .travis.yml | 18 +++++++++--------- HACKING.md | 4 ++-- README.md | 10 +++++----- TESTING.md | 2 +- debian/control | 6 +++--- debian/copyright | 2 +- docs/explanation/architectures.rst | 2 +- extensions/desktop/common/desktop-exports | 2 +- extensions/desktop/common/fonts | 2 +- extensions/desktop/common/init | 2 +- extensions/desktop/common/mark-and-exec | 2 +- extensions/desktop/gnome/launcher-specific | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 4 ++-- snapcraft/extensions/kde_neon.py | 2 +- snapcraft/extensions/kde_neon_6.py | 2 +- snapcraft_legacy/internal/errors.py | 2 +- snapcraft_legacy/internal/meta/slots.py | 2 +- tools/brew_install_from_source.py | 4 ++-- tools/retry_autopkgtest.sh | 2 +- 25 files changed, 43 insertions(+), 43 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9fab5c6106..9f0d42ea2d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -- [ ] Have you followed the [guidelines for contributing](https://github.com/snapcore/snapcraft/blob/master/CONTRIBUTING.md)? +- [ ] Have you followed the [guidelines for contributing](https://github.com/canonical/snapcraft/blob/main/CONTRIBUTING.md)? - [ ] Have you signed the [CLA](http://www.ubuntu.com/legal/contributors/)? - [ ] Have you successfully run `tox run -m lint`? - [ ] Have you successfully run `tox run -e test-py310`? (supported versions: `py39`, `py310`, `py311`, `py312`) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index dbc67234b5..256bc582db 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 - if: steps.decisions.outputs.PUBLISH == 'true' - uses: snapcore/action-build@v1 + uses: canonical/action-build@v1 name: Build Snapcraft Snap id: build with: @@ -54,7 +54,7 @@ jobs: - name: Publish feature branch to edge/${{ steps.vars.outputs.branch }} if: steps.decisions.outputs.PUBLISH == 'true' - uses: snapcore/action-publish@v1 + uses: canonical/action-publish@v1 env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} with: diff --git a/.github/workflows/spread-scheduled.yaml b/.github/workflows/spread-scheduled.yaml index 25484b0aca..ce99ecf9b5 100644 --- a/.github/workflows/spread-scheduled.yaml +++ b/.github/workflows/spread-scheduled.yaml @@ -14,7 +14,7 @@ jobs: with: fetch-depth: 0 - name: Build snap - uses: snapcore/action-build@v1 + uses: canonical/action-build@v1 id: snapcraft - name: Upload snap artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/spread.yml b/.github/workflows/spread.yml index b6ae331d1b..9f55db8b71 100644 --- a/.github/workflows/spread.yml +++ b/.github/workflows/spread.yml @@ -14,7 +14,7 @@ jobs: - name: Build snapcraft snap id: build-snapcraft - uses: snapcore/action-build@v1 + uses: canonical/action-build@v1 - name: Upload snapcraft snap uses: actions/upload-artifact@v4 diff --git a/.gitmodules b/.gitmodules index 74422edfac..4ca275d2e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "tests/spread/tools/snapd-testing-tools"] path = tests/spread/tools/snapd-testing-tools - url = https://github.com/snapcore/snapd-testing-tools.git + url = https://github.com/canonical/snapd-testing-tools.git [submodule "docs/sphinx-resources"] path = docs/sphinx-resources url = https://github.com/canonical/sphinx-docs-starter-pack.git diff --git a/.travis.yml b/.travis.yml index 76985ff6fe..a5c3f506a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,7 +47,7 @@ jobs: name: store workspaces: use: snaps - if: head_repo = "snapcore/snapcraft" + if: head_repo = "canonical/snapcraft" script: - ./runtests.sh spread "google:ubuntu-18.04-64:tests/spread/general/store" @@ -56,23 +56,23 @@ jobs: RISK: "stable" script: - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag snapcore/snapcraft:${RISK} . - - docker run snapcore/snapcraft:${RISK} snapcraft --version + - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . + - docker run canonical/snapcraft:${RISK} snapcraft --version - env: RISK: "edge" script: - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag snapcore/snapcraft:${RISK} . - - docker run snapcore/snapcraft:${RISK} snapcraft --version + - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . + - docker run canonical/snapcraft:${RISK} snapcraft --version - env: RISK: "beta" script: - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag snapcore/snapcraft:${RISK} . - - docker run snapcore/snapcraft:${RISK} snapcraft --version + - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . + - docker run canonical/snapcraft:${RISK} snapcraft --version - env: RISK: "candidate" script: - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag snapcore/snapcraft:${RISK} . - - docker run snapcore/snapcraft:${RISK} snapcraft --version + - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . + - docker run canonical/snapcraft:${RISK} snapcraft --version diff --git a/HACKING.md b/HACKING.md index 31d93e2f35..68d976d7a5 100644 --- a/HACKING.md +++ b/HACKING.md @@ -7,7 +7,7 @@ We want to make sure everyone develops using a consistent base, to ensure that t Clone the snapcraft repository and its submodules and make it your working directory: ```shell -git clone https://github.com/snapcore/snapcraft.git --recurse-submodules +git clone https://github.com/canonical/snapcraft.git --recurse-submodules cd snapcraft ``` @@ -105,7 +105,7 @@ To download the snap, find the relevant CI job run for the PR under review and l We'd love the help! -- Submit pull requests against [snapcraft](https://github.com/snapcore/snapcraft/pulls) +- Submit pull requests against [snapcraft](https://github.com/canonical/snapcraft/pulls) - Make sure to read the [contribution guide](CONTRIBUTING.md) - Find us under the snapcraft category of the forum https://forum.snapcraft.io - Discuss with us using IRC in #snapcraft on Freenode. diff --git a/README.md b/README.md index de5bdc9984..a8b6e875f8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![snapcraft](https://snapcraft.io/snapcraft/badge.svg)](https://snapcraft.io/snapcraft) [![Build Status][travis-image]][travis-url] [![Documentation Status](https://readthedocs.com/projects/canonical-snapcraft/badge/?version=latest)](https://canonical-snapcraft.readthedocs-hosted.com/en/latest/?badge=latest) -[![Scheduled spread tests](https://github.com/snapcore/snapcraft/actions/workflows/spread-scheduled.yaml/badge.svg?branch=main)](https://github.com/snapcore/snapcraft/actions/workflows/spread-scheduled.yaml) +[![Scheduled spread tests](https://github.com/canonical/snapcraft/actions/workflows/spread-scheduled.yaml/badge.svg?branch=main)](https://github.com/canonical/snapcraft/actions/workflows/spread-scheduled.yaml) [![Coverage Status][codecov-image]][codecov-url] [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) @@ -29,8 +29,8 @@ Learn about the latest features by following Snapcraft on We love contributors. Read the [hacking guide](HACKING.md) if you're interested in helping out. -[travis-image]: https://travis-ci.org/snapcore/snapcraft.svg?branch=master -[travis-url]: https://travis-ci.org/snapcore/snapcraft +[travis-image]: https://travis-ci.org/canonical/snapcraft.svg?branch=master +[travis-url]: https://travis-ci.org/canonical/snapcraft -[codecov-image]: https://codecov.io/github/snapcore/snapcraft/coverage.svg?branch=master -[codecov-url]: https://codecov.io/github/snapcore/snapcraft?branch=master +[codecov-image]: https://codecov.io/github/canonical/snapcraft/coverage.svg?branch=master +[codecov-url]: https://codecov.io/github/canonical/snapcraft?branch=master diff --git a/TESTING.md b/TESTING.md index d554b30b44..71a0be2e93 100644 --- a/TESTING.md +++ b/TESTING.md @@ -151,7 +151,7 @@ It's possible to select only one of the suites using `--test-name`, for example: ## Spread tests for the snapcraft snap -[Spread](https://github.com/snapcore/spread) is a system to distribute tests and execute them in different backends, in parallel. We are currently using spread only to run the integration suite using the installed snapcraft snap from the edge channel. +[Spread](https://github.com/canonical/spread) is a system to distribute tests and execute them in different backends, in parallel. We are currently using spread only to run the integration suite using the installed snapcraft snap from the edge channel. To run them, first, download the spread binary: diff --git a/debian/control b/debian/control index 5361e7eba9..57cc3394df 100644 --- a/debian/control +++ b/debian/control @@ -3,9 +3,9 @@ Section: utils Priority: extra Maintainer: Snapcraft Team Build-Depends: debhelper -Homepage: https://github.com/snapcore/snapcraft -Vcs-Git: https://github.com/snapcore/snapcraft.git -Vcs-Browser: https://github.com/snapcore/snapcraft +Homepage: https://github.com/canonical/snapcraft +Vcs-Git: https://github.com/canonical/snapcraft.git +Vcs-Browser: https://github.com/canonical/snapcraft Standards-Version: 4.1.3 Package: snapcraft diff --git a/debian/copyright b/debian/copyright index 4ca533afff..e47c2839d4 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,7 +1,7 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Snapcraft Upstream-Contact: Snapcraft Team -Source: https://github.com/snapcore/snapcraft +Source: https://github.com/canonical/snapcraft Files: * Copyright: 2015-2017 Canonical Ltd diff --git a/docs/explanation/architectures.rst b/docs/explanation/architectures.rst index 9c3c9fc1fd..c2dda58651 100644 --- a/docs/explanation/architectures.rst +++ b/docs/explanation/architectures.rst @@ -239,4 +239,4 @@ architectures supported by Launchpad. The second cause is the same :ref:`as above` - not enclosing a list of multiple architectures with brackets. -.. _`issue 4340`: https://github.com/snapcore/snapcraft/issues/4340 +.. _`issue 4340`: https://github.com/canonical/snapcraft/issues/4340 diff --git a/extensions/desktop/common/desktop-exports b/extensions/desktop/common/desktop-exports index 78802e4805..ac3d1dfc11 100644 --- a/extensions/desktop/common/desktop-exports +++ b/extensions/desktop/common/desktop-exports @@ -3,7 +3,7 @@ # Launcher common exports for any desktop app # This is not used with the gnome extension for # core22 and later, please see -# https://github.com/snapcore/snapcraft-desktop-integration +# https://github.com/canonical/snapcraft-desktop-integration ########################################################### # Note: We avoid using `eval` because we don't want to expand variable names diff --git a/extensions/desktop/common/fonts b/extensions/desktop/common/fonts index fc9c1edb18..7962fbac55 100644 --- a/extensions/desktop/common/fonts +++ b/extensions/desktop/common/fonts @@ -3,7 +3,7 @@ ########################################################### # This is not used with the gnome extension for # core22 and later, please see -# https://github.com/snapcore/snapcraft-desktop-integration +# https://github.com/canonical/snapcraft-desktop-integration ########################################################### set -e diff --git a/extensions/desktop/common/init b/extensions/desktop/common/init index 456555db1e..cada14b14a 100644 --- a/extensions/desktop/common/init +++ b/extensions/desktop/common/init @@ -3,7 +3,7 @@ # Launcher init # This is not used with the gnome extension for # core22 and later, please see -# https://github.com/snapcore/snapcraft-desktop-integration +# https://github.com/canonical/snapcraft-desktop-integration ########################################################### # shellcheck disable=SC2034 diff --git a/extensions/desktop/common/mark-and-exec b/extensions/desktop/common/mark-and-exec index 9258393c4f..b3c49c0199 100644 --- a/extensions/desktop/common/mark-and-exec +++ b/extensions/desktop/common/mark-and-exec @@ -3,7 +3,7 @@ # Mark update and exec binary # This is not used with the gnome extension for # core22 and later, please see -# https://github.com/snapcore/snapcraft-desktop-integration +# https://github.com/canonical/snapcraft-desktop-integration ########################################################### # shellcheck disable=SC2154 diff --git a/extensions/desktop/gnome/launcher-specific b/extensions/desktop/gnome/launcher-specific index 6245db8018..a896eaa4d1 100755 --- a/extensions/desktop/gnome/launcher-specific +++ b/extensions/desktop/gnome/launcher-specific @@ -3,7 +3,7 @@ # GTK launcher specific part # This is not used with the gnome extension for # core22 and later, please see -# https://github.com/snapcore/snapcraft-desktop-integration +# https://github.com/canonical/snapcraft-desktop-integration ########################################################### # shellcheck disable=SC2154 diff --git a/setup.py b/setup.py index 78ee0a0b6a..5fdd2365ab 100755 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def recursive_data_files(directory, install_directory): name = "snapcraft" description = "Publish your app for Linux users for desktop, cloud, and IoT." author_email = "snapcraft@lists.snapcraft.io" -url = "https://github.com/snapcore/snapcraft" +url = "https://github.com/canonical/snapcraft" license_ = "GPL v3" classifiers = [ "Development Status :: 4 - Beta", diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 70754e382e..4cdd587559 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -12,7 +12,7 @@ license: GPL-3.0 assumes: - snapd2.43 -# https://github.com/snapcore/snapcraft/issues/4187 +# https://github.com/canonical/snapcraft/issues/4187 environment: PATH: "$SNAP/libexec/snapcraft:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" LD_LIBRARY_PATH: "$SNAP/none" @@ -56,7 +56,7 @@ parts: patchelf: plugin: autotools - source: https://github.com/snapcore/patchelf + source: https://github.com/canonical/patchelf source-type: git source-branch: '0.9+snapcraft' autotools-configure-parameters: diff --git a/snapcraft/extensions/kde_neon.py b/snapcraft/extensions/kde_neon.py index e8ad16a476..587418b055 100644 --- a/snapcraft/extensions/kde_neon.py +++ b/snapcraft/extensions/kde_neon.py @@ -204,7 +204,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: """ # We can change this to the lightweight command-chain when # the content snap includes the desktop-launch from - # https://github.com/snapcore/snapcraft-desktop-integration + # https://github.com/canonical/snapcraft-desktop-integration source = get_extensions_data_dir() / "desktop" / "kde-neon" if self.kde_snaps.builtin: diff --git a/snapcraft/extensions/kde_neon_6.py b/snapcraft/extensions/kde_neon_6.py index 2c39530c5c..ab4a362774 100644 --- a/snapcraft/extensions/kde_neon_6.py +++ b/snapcraft/extensions/kde_neon_6.py @@ -277,7 +277,7 @@ def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: def get_parts_snippet(self) -> Dict[str, Any]: # We can change this to the lightweight command-chain when # the content snap includes the desktop-launch from - # https://github.com/snapcore/snapcraft-desktop-integration + # https://github.com/canonical/snapcraft-desktop-integration source = get_extensions_data_dir() / "desktop" / "kde-neon-6" if self.kde_snaps.kf6_builtin: diff --git a/snapcraft_legacy/internal/errors.py b/snapcraft_legacy/internal/errors.py index 107caa4c50..c787ea8cdf 100644 --- a/snapcraft_legacy/internal/errors.py +++ b/snapcraft_legacy/internal/errors.py @@ -351,7 +351,7 @@ class MissingGadgetError(SnapcraftError): "file\n" "in the root of your snapcraft project\n\n" "Read more about gadget snaps and the gadget.yaml on:\n" - "https://github.com/snapcore/snapd/wiki/Gadget-snap" + "https://snapcraft.io/docs/the-gadget-snap" ) diff --git a/snapcraft_legacy/internal/meta/slots.py b/snapcraft_legacy/internal/meta/slots.py index 9e3d417840..e87736a83c 100644 --- a/snapcraft_legacy/internal/meta/slots.py +++ b/snapcraft_legacy/internal/meta/slots.py @@ -170,7 +170,7 @@ def from_dict(cls, *, slot_dict: Dict[str, Any], slot_name: str) -> "ContentSlot # Content directories may be nested under "source", # but they cannot be in both places according to snapd: - # https://github.com/snapcore/snapd/blob/master/interfaces/builtin/content.go#L81 + # https://github.com/canonical/snapd/blob/6341e63d82dabb39a0a834fb956fa26dc06a2788/interfaces/builtin/content.go#L81 if "source" in slot_dict: source_data = slot_dict["source"] slot.use_source_key = True diff --git a/tools/brew_install_from_source.py b/tools/brew_install_from_source.py index 95feb6ab4f..b4c3029fca 100755 --- a/tools/brew_install_from_source.py +++ b/tools/brew_install_from_source.py @@ -47,8 +47,8 @@ def main(): def download_snapcraft_source(dest_dir): dest_file = os.path.join(dest_dir, "snapcraft-0.1.tar.gz") branch_source = "https://github.com/{}/archive/{}.tar.gz".format( - os.environ.get("TRAVIS_PULL_REQUEST_SLUG") or "snapcore/snapcraft", - os.environ.get("TRAVIS_PULL_REQUEST_BRANCH") or "master", + os.environ.get("TRAVIS_PULL_REQUEST_SLUG") or "canonical/snapcraft", + os.environ.get("TRAVIS_PULL_REQUEST_BRANCH") or "main", ) print("Downloading branch source from {}".format(branch_source)) urllib.request.urlretrieve(branch_source, dest_file) # noqa S310 diff --git a/tools/retry_autopkgtest.sh b/tools/retry_autopkgtest.sh index b1ca1de118..0d479acd7d 100755 --- a/tools/retry_autopkgtest.sh +++ b/tools/retry_autopkgtest.sh @@ -53,5 +53,5 @@ for testrun in "${tests[@]}"; do [ -z "$architecture" ] && architecture='amd64' [ -n "$testsuite" ] && testname="&testname=${testsuite}" echo "Launching tests for the ${release} release in the ${architecture} architecture..." - "${temp_dir}/retry-github-test" "https://api.github.com/repos/snapcore/snapcraft/pulls/${pr}" "https://autopkgtest.ubuntu.com/request.cgi?release=${release}&arch=${architecture}&package=snapcraft${testname}&ppa=snappy-dev%2Fsnapcraft-daily" "${temp_dir}/sec.txt" + "${temp_dir}/retry-github-test" "https://api.github.com/repos/canonical/snapcraft/pulls/${pr}" "https://autopkgtest.ubuntu.com/request.cgi?release=${release}&arch=${architecture}&package=snapcraft${testname}&ppa=snappy-dev%2Fsnapcraft-daily" "${temp_dir}/sec.txt" done From 040b66c007adc70336a03c9e6204296ffa288ccc Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Mon, 30 Sep 2024 10:57:18 -0400 Subject: [PATCH 094/151] ci: add security scan (#5068) --- .github/workflows/security-scan.yaml | 17 +++++++++++++++++ osv-scanner.toml | 4 ++++ requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- tools/freeze-requirements.sh | 2 +- 6 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/security-scan.yaml create mode 100644 osv-scanner.toml diff --git a/.github/workflows/security-scan.yaml b/.github/workflows/security-scan.yaml new file mode 100644 index 0000000000..20770c8ad9 --- /dev/null +++ b/.github/workflows/security-scan.yaml @@ -0,0 +1,17 @@ +name: Security scan +on: + pull_request: + push: + branches: + - main + - hotfix/* + - work/secscan # For development + +jobs: + python-scans: + name: Scan Python project + uses: canonical/starflow/.github/workflows/scan-python.yaml@main + with: + packages: python-apt-dev + osv-extra-args: '--config=source/osv-scanner.toml' + trivy-extra-args: '--severity HIGH,CRITICAL --ignore-unfixed --skip-dirs "tests/spread/**"' diff --git a/osv-scanner.toml b/osv-scanner.toml new file mode 100644 index 0000000000..17da2fac14 --- /dev/null +++ b/osv-scanner.toml @@ -0,0 +1,4 @@ +[[IgnoredVulns]] +id = "CVE-2024-35195" +ignoreUntil = "2025-01-01T00:00:00Z" +reason = "Needed for requests-unixsocket, which we're replacing with requests-unixsocket2" diff --git a/requirements-devel.txt b/requirements-devel.txt index 2660b4f900..37f48f4751 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -199,5 +199,5 @@ yamllint==1.35.1 zipp==3.20.2 zope.deprecation==5.0 zope.interface==7.0.3 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" pyinstaller==5.13.2; sys.platform == "win32" diff --git a/requirements-docs.txt b/requirements-docs.txt index 787942bceb..1504cc4323 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -147,4 +147,4 @@ websockets==12.0 wheel==0.44.0 ws4py==0.5.1 zipp==3.20.2 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" diff --git a/requirements.txt b/requirements.txt index 638d3ddb58..75e74f446c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -78,4 +78,4 @@ wadllib==1.3.6 wheel==0.44.0 ws4py==0.5.1 zipp==3.20.2 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" diff --git a/tools/freeze-requirements.sh b/tools/freeze-requirements.sh index 22723c420a..5f086892ee 100755 --- a/tools/freeze-requirements.sh +++ b/tools/freeze-requirements.sh @@ -5,7 +5,7 @@ requirements_fixups() { # Python apt library pinned to source. sed -i '/python-apt=*/d' "$req_file" - echo 'python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys.platform == "linux"' >> "$req_file" + echo 'python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux"' >> "$req_file" # https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463 sed -i '/pkg[-_]resources==0.0.0/d' "$req_file" From a22b218ab29dcae389a745c0bf7b877c2cf39f85 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 27 Sep 2024 09:26:57 -0500 Subject: [PATCH 095/151] tests: fix validation sets spread test Signed-off-by: Callahan Kovacs --- tests/spread/store/validation-sets/editor.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/spread/store/validation-sets/editor.sh b/tests/spread/store/validation-sets/editor.sh index a38c96be47..eceab47d09 100755 --- a/tests/spread/store/validation-sets/editor.sh +++ b/tests/spread/store/validation-sets/editor.sh @@ -2,11 +2,11 @@ validation_set_file="$1" -# flip-flop between two valid revisions of `test-snapcraft-assertions` in the staging store: 1 and 2 -if grep -q "^ revision:.*1" "$validation_set_file"; then - (( revision=2 )) +# flip-flop between 'hello-world' being optional or required +if grep -q "^ presence:.*optional" "$validation_set_file"; then + presence="required" else - (( revision=1 )) + presence="optional" fi -sed -i "s/ revision:.*/ revision: $revision/g" "$validation_set_file" +sed -i "s/ presence:.*/ presence: $presence/g" "$validation_set_file" From 22cd1f3f409439da98a2f378ee2aeb3bb76562c2 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 9 Sep 2024 15:06:30 -0500 Subject: [PATCH 096/151] docs(changelog): add 8.3.3 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index ff8ef38273..de12b1aee6 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -230,6 +230,27 @@ Documentation For a complete list of commits, check out the `8.4.0`_ release on GitHub. +8.3.3 (2024-Aug-28) +------------------- + +Core +==== + +* Improve detection and error messages when LXD is not installed or not + properly enabled. + +Bases +##### + +core24 +"""""" + +* Require Multipass >= ``1.14.1`` when using Multipass to build ``core24`` + snaps. + +For a complete list of commits, check out the `8.3.3`_ release on GitHub. + + .. _7.5.6_changelog: 7.5.6 (2024-Aug-15) @@ -1215,5 +1236,6 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.0: https://github.com/canonical/snapcraft/releases/tag/8.3.0 .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 .. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 +.. _8.3.3: https://github.com/canonical/snapcraft/releases/tag/8.3.3 .. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 .. _8.4.1: https://github.com/canonical/snapcraft/releases/tag/8.4.1 From 1df696d689d114567786679c1807debca9aeb1ee Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 13 Sep 2024 12:53:01 -0500 Subject: [PATCH 097/151] docs(changelog): add 8.3.4 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index de12b1aee6..fbc344f8ac 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -129,6 +129,23 @@ Store For a complete list of changes, check out the `8.4.1`_ release on GitHub. +8.3.4 (2024-Sep-13) +------------------- + +Core +==== + +Plugins +####### + +NPM +""" +* Fix a bug where NPM parts fail to build if the ``pull`` and ``build`` steps + did not occur in the same execution of Snapcraft. + +For a complete list of commits, check out the `8.3.4`_ release on GitHub. + + 8.4.0 (2024-Sep-10) ------------------- @@ -1237,5 +1254,6 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.1: https://github.com/canonical/snapcraft/releases/tag/8.3.1 .. _8.3.2: https://github.com/canonical/snapcraft/releases/tag/8.3.2 .. _8.3.3: https://github.com/canonical/snapcraft/releases/tag/8.3.3 +.. _8.3.4: https://github.com/canonical/snapcraft/releases/tag/8.3.4 .. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 .. _8.4.1: https://github.com/canonical/snapcraft/releases/tag/8.4.1 From 5e91afb6adc323edaa40d5829100fda037cd450d Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 1 Oct 2024 07:40:36 -0500 Subject: [PATCH 098/151] chore: remove travis and autopkgtest (#5073) Remove unused code related to travis and autopkgtest. Signed-off-by: Callahan Kovacs --- .travis.yml | 78 --------------- HACKING.md | 5 +- README.md | 4 - TESTING.md | 37 -------- snapcraft/store/client.py | 10 +- snapcraft_legacy/storeapi/agent.py | 14 +-- spread.yaml | 47 --------- tests/legacy/unit/store/test_agent.py | 26 ----- .../general/package-repositories/task.yaml | 7 +- .../plugins/v2/colcon-ros2-daemon/task.yaml | 1 - tests/unit/store/test_client.py | 24 ----- tools/brew_install_from_source.py | 5 +- tools/collect_ppa_autopkgtests_results.py | 95 ------------------- tools/retry_autopkgtest.sh | 57 ----------- tools/run_ppa_autopkgtests.py | 92 ------------------ 15 files changed, 7 insertions(+), 495 deletions(-) delete mode 100644 .travis.yml delete mode 100755 tools/collect_ppa_autopkgtests_results.py delete mode 100755 tools/retry_autopkgtest.sh delete mode 100755 tools/run_ppa_autopkgtests.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a5c3f506a4..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,78 +0,0 @@ -language: bash -dist: bionic - -env: - global: - - LC_ALL: "C.UTF-8" - - LANG: "C.UTF-8" - - PATH: "/snap/bin:$PATH" - -jobs: - include: - - stage: snap - name: snap - workspaces: - create: - name: snaps - paths: - - "snapcraft-pr$TRAVIS_PULL_REQUEST.snap" - addons: - snaps: - - name: snapcraft - channel: stable - classic: true - - name: transfer - - name: http - apt: - packages: - - libapt-pkg-dev - - libffi-dev - - libnacl-dev - - libsodium-dev - - libssl-dev - - libyaml-dev - - python3.6-dev - script: - - snapcraft snap --destructive-mode --output "snapcraft-pr$TRAVIS_PULL_REQUEST.snap" - - sudo snap install "snapcraft-pr$TRAVIS_PULL_REQUEST.snap" --dangerous --classic - - snapcraft clean --destructive-mode - - snapcraft snap --destructive-mode --output "snapcraft-pr$TRAVIS_PULL_REQUEST.snap" - after_success: - - timeout 180 /snap/bin/transfer snapcraft-pr$TRAVIS_PULL_REQUEST.snap - after_failure: - - sudo journalctl -u snapd - - /snap/bin/http https://api.snapcraft.io/v2/snaps/info/core architecture==amd64 Snap-Device-Series:16 - - - stage: integration - name: store - workspaces: - use: snaps - if: head_repo = "canonical/snapcraft" - script: - - ./runtests.sh spread "google:ubuntu-18.04-64:tests/spread/general/store" - - - stage: docker - env: - RISK: "stable" - script: - - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . - - docker run canonical/snapcraft:${RISK} snapcraft --version - - env: - RISK: "edge" - script: - - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . - - docker run canonical/snapcraft:${RISK} snapcraft --version - - env: - RISK: "beta" - script: - - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . - - docker run canonical/snapcraft:${RISK} snapcraft --version - - env: - RISK: "candidate" - script: - - cd docker - - docker build --no-cache -f ${RISK}.Dockerfile --tag canonical/snapcraft:${RISK} . - - docker run canonical/snapcraft:${RISK} snapcraft --version diff --git a/HACKING.md b/HACKING.md index 68d976d7a5..ab505761f3 100644 --- a/HACKING.md +++ b/HACKING.md @@ -98,8 +98,9 @@ tox run -e lint-codespell ## Evaluating pull requests -Oftentimes all you want to do is see if a given pull request solves the issue you were having. To make this easier, the Travis CI setup for snapcraft _publishes_ the resulting snap that was built for x86-64 using `transfer.sh`. -To download the snap, find the relevant CI job run for the PR under review and locate the "snap" stage, the URL to download from will be located at the end of logs for that job. +Oftentimes all you want to do is see if a given pull request solves the issue you were having. To make this easier, a snap is published for `amd64` on a channel named `latest/edge/pr-` where `PR number` is the number of the pull request. + +For feature branches, a snap is published for `amd64` on a channel named `latest/edge/`. For example, a branch named `feature/offline-mode` would be available on the channel `latest/edge/offline-mode`. ## Reaching out diff --git a/README.md b/README.md index a8b6e875f8..e8b6707df8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ [![snapcraft](https://snapcraft.io/snapcraft/badge.svg)](https://snapcraft.io/snapcraft) -[![Build Status][travis-image]][travis-url] [![Documentation Status](https://readthedocs.com/projects/canonical-snapcraft/badge/?version=latest)](https://canonical-snapcraft.readthedocs-hosted.com/en/latest/?badge=latest) [![Scheduled spread tests](https://github.com/canonical/snapcraft/actions/workflows/spread-scheduled.yaml/badge.svg?branch=main)](https://github.com/canonical/snapcraft/actions/workflows/spread-scheduled.yaml) [![Coverage Status][codecov-image]][codecov-url] @@ -29,8 +28,5 @@ Learn about the latest features by following Snapcraft on We love contributors. Read the [hacking guide](HACKING.md) if you're interested in helping out. -[travis-image]: https://travis-ci.org/canonical/snapcraft.svg?branch=master -[travis-url]: https://travis-ci.org/canonical/snapcraft - [codecov-image]: https://codecov.io/github/canonical/snapcraft/coverage.svg?branch=master [codecov-url]: https://codecov.io/github/canonical/snapcraft?branch=master diff --git a/TESTING.md b/TESTING.md index 71a0be2e93..2d4671d6ce 100644 --- a/TESTING.md +++ b/TESTING.md @@ -40,14 +40,6 @@ These tests are in the `tests/integration` directory, with the `snapcraft.yamls` At any time, an integration test may fail and given the use of temporary directories it can be hard to inspect what went on. When working on a specific test case you can set the environment variable `SNAPCRAFT_TEST_KEEP_DATA_PATH` to a directory path for the sepecic test. This mechanism will only work when working with individual tests and will fail to run with a batch of them. -### Slow tests - -Some tests take too long. This affects the pull requests because we have to wait for a long time, and they will make Travis CI timeout because we have only 50 minutes per suite in there. The solution is to tag these tests as slow, and don't run them in all pull requests. These tests will only be run in autopkgtests. - -To mark a test case as slow, set the class attribute `slow_test = True`. - -To run all the tests, including the slow ones, set the environment variable `SNAPCRAFT_SLOW_TESTS=1`. - ### Snaps tests The snaps tests is a suite of high-level tests that try to simulate real-world scenarios of a user interacting with snapcraft. They cover the call to snapcraft to generate a snap file from the source files of a fully functional project, the installation of the resulting snap, and the execution of the binaries and services of this snap. @@ -120,35 +112,6 @@ We can currently run a minimal subset of snapcraft integration tests on macOS. T For manual exploratory testing, the team has one mac machine available. -## Autopkgtests for the snapcraft deb - -Autopkgtests are tests for the project packaged as a deb. The unit tests are run during autopkgtests while the snapcraft deb is being built. Then the resulting deb is installed, and the integration and snaps suites are executed using the installed snapcraft. - - -### How to run on Xenial - -The easiest way is to use a LXC container. From the root of the project, run: - - sudo apt install autopkgtest - adt-run --unbuilt-tree . --apt-upgrade --- lxd ubuntu:xenial - -It's possible to select only one of the suites using `--testname`, for example: - - adt-run --unbuilt-tree . --apt-upgrade --testname=integrationtests --- lxd ubuntu:xenial - - -### How to run on Bionic - -The easiest way is to use a LXC container. From the root of the project, run: - - sudo apt install autopkgtest - autopkgtest . -U -- lxd ubuntu:xenial - -It's possible to select only one of the suites using `--test-name`, for example: - - autopkgtest . -U --test-name=integrationtests-spread -- lxd ubuntu:xenial - - ## Spread tests for the snapcraft snap [Spread](https://github.com/canonical/spread) is a system to distribute tests and execute them in different backends, in parallel. We are currently using spread only to run the integration suite using the installed snapcraft snap from the edge channel. diff --git a/snapcraft/store/client.py b/snapcraft/store/client.py index bbb58aed43..dbde3a4daf 100644 --- a/snapcraft/store/client.py +++ b/snapcraft/store/client.py @@ -34,8 +34,6 @@ from ._legacy_account import LegacyUbuntuOne from .onprem_client import ON_PREM_ENDPOINTS, OnPremClient -_TESTING_ENV_PREFIXES = ["TRAVIS", "AUTOPKGTEST_TMP"] - _POLL_DELAY = 1 _HUMAN_STATUS = { "being_processed": "processing", @@ -51,13 +49,7 @@ def build_user_agent( os_platform: utils.OSPlatform = utils.get_os_platform(), # noqa: B008 ): """Build Snapcraft's user agent.""" - if any( - key.startswith(prefix) for prefix in _TESTING_ENV_PREFIXES for key in os.environ - ): - testing = " (testing) " - else: - testing = " " - return f"snapcraft/{version}{testing}{os_platform!s}" + return f"snapcraft/{version} {os_platform!s}" def use_candid() -> bool: diff --git a/snapcraft_legacy/storeapi/agent.py b/snapcraft_legacy/storeapi/agent.py index 04d516e75b..66c982829a 100644 --- a/snapcraft_legacy/storeapi/agent.py +++ b/snapcraft_legacy/storeapi/agent.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os import sys import snapcraft_legacy @@ -23,16 +22,6 @@ from snapcraft_legacy.internal.errors import OsReleaseNameError, OsReleaseVersionIdError -def _is_ci_env(): - env_prefixes = ["TRAVIS", "AUTOPKGTEST_TMP"] - matches = [] - - for prefix in env_prefixes: - matches += [var for var in os.environ.keys() if var.startswith(prefix)] - - return len(matches) > 0 - - def _get_linux_release(release: os_release.OsRelease) -> str: try: os_name = release.name() @@ -48,11 +37,10 @@ def _get_linux_release(release: os_release.OsRelease) -> str: def get_user_agent(platform: str = sys.platform) -> str: arch = project.Project().deb_arch - testing = "(testing) " if _is_ci_env() else "" if platform == "linux": os_platform = _get_linux_release(os_release.OsRelease()) else: os_platform = platform.title() - return f"snapcraft/{snapcraft_legacy.__version__} {testing}{os_platform} ({arch})" + return f"snapcraft/{snapcraft_legacy.__version__} {os_platform} ({arch})" diff --git a/spread.yaml b/spread.yaml index 039ef727b3..b7ac155b78 100644 --- a/spread.yaml +++ b/spread.yaml @@ -121,51 +121,6 @@ backends: username: root password: ubuntu - autopkgtest: - type: adhoc - allocate: | - echo "Allocating ad-hoc $SPREAD_SYSTEM" - if [ -z "${ADT_ARTIFACTS:-}" ]; then - FATAL "adhoc only works inside autopkgtest" - exit 1 - fi - echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99-spread-users - ADDRESS localhost:22 - discard: echo "Discarding ad-hoc $SPREAD_SYSTEM" - systems: - # Focal - - ubuntu-20.04-amd64: - username: ubuntu - password: ubuntu - - ubuntu-20.04-ppc64el: - username: ubuntu - password: ubuntu - - ubuntu-20.04-armhf: - username: ubuntu - password: ubuntu - - ubuntu-20.04-s390x: - username: ubuntu - password: ubuntu - - ubuntu-20.04-arm64: - username: ubuntu - password: ubuntu - # Jammy - - ubuntu-22.04-amd64: - username: ubuntu - password: ubuntu - - ubuntu-22.04-ppc64el: - username: ubuntu - password: ubuntu - - ubuntu-22.04-armhf: - username: ubuntu - password: ubuntu - - ubuntu-22.04-s390x: - username: ubuntu - password: ubuntu - - ubuntu-22.04-arm64: - username: ubuntu - password: ubuntu - exclude: [snaps-cache/] prepare: | @@ -342,14 +297,12 @@ suites: - ubuntu-20.04-64 - ubuntu-22.04-64 kill-timeout: 180m - warn-timeout: 9m # Keep less than 10 minutes so Travis can't timeout priority: 90 # Run this test relatively early since fetching images can take time tests/spread/providers/legacy/: summary: tests of snapcraft using build providers systems: - ubuntu-20.04-64 kill-timeout: 180m - warn-timeout: 9m # Keep less than 10 minutes so Travis can't timeout priority: 90 # Run this test relatively early since fetching images can take time # Plugin-specific suites diff --git a/tests/legacy/unit/store/test_agent.py b/tests/legacy/unit/store/test_agent.py index 0150c68bc9..acad3a0e13 100644 --- a/tests/legacy/unit/store/test_agent.py +++ b/tests/legacy/unit/store/test_agent.py @@ -14,9 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -import fixtures from testtools.matchers import Equals from snapcraft_legacy import ProjectOptions @@ -54,27 +52,3 @@ def test_user_agent_darwin(self): expected = f"snapcraft/{snapcraft_version} Darwin ({arch})" self.expectThat(agent.get_user_agent(platform="darwin"), Equals(expected)) - - def test_in_travis_ci_env(self): - self.useFixture(fixtures.EnvironmentVariable("TRAVIS_TESTING", "1")) - - self.assertTrue(agent._is_ci_env()) - - def test_in_autopkgtest_ci_env(self): - self.useFixture(fixtures.EnvironmentVariable("AUTOPKGTEST_TMP", "1")) - - self.assertTrue(agent._is_ci_env()) - - def test_not_in_ci_env(self): - # unset any known testing environment vars - testing_vars = ["TRAVIS", "AUTHPKGTEST_TMP"] - vars_to_unset = [] - for env_var in os.environ: - for test_var in testing_vars: - if env_var.startswith(test_var): - vars_to_unset.append(env_var) - - for var in vars_to_unset: - self.useFixture(fixtures.EnvironmentVariable(var, None)) - - self.assertFalse(agent._is_ci_env()) diff --git a/tests/spread/general/package-repositories/task.yaml b/tests/spread/general/package-repositories/task.yaml index 0c62af55b8..c7bfa0cb7f 100644 --- a/tests/spread/general/package-repositories/task.yaml +++ b/tests/spread/general/package-repositories/task.yaml @@ -30,12 +30,7 @@ execute: | cd "$SNAP" # Build what we have. - # We cannot use --use-lxd for autopkgtest so we resort to --destructive-mode there. - if [ "$SPREAD_SYSTEM" = "ubuntu-20.04-64" ]; then - snapcraft --use-lxd - else - snapcraft --destructive-mode - fi + snapcraft --use-lxd # And verify the snap runs as expected. snap install "${SNAP}"_1.0_*.snap --dangerous diff --git a/tests/spread/plugins/v2/colcon-ros2-daemon/task.yaml b/tests/spread/plugins/v2/colcon-ros2-daemon/task.yaml index e35c624bba..bb131dbbc8 100644 --- a/tests/spread/plugins/v2/colcon-ros2-daemon/task.yaml +++ b/tests/spread/plugins/v2/colcon-ros2-daemon/task.yaml @@ -1,5 +1,4 @@ summary: Build and run a basic daemon colcon snap -warn-timeout: 9m # Keep less than 10 minutes so Travis can't timeout priority: 100 # Run this test early so we're not waiting for it environment: diff --git a/tests/unit/store/test_client.py b/tests/unit/store/test_client.py index 8a41d82fce..fd3bf73921 100644 --- a/tests/unit/store/test_client.py +++ b/tests/unit/store/test_client.py @@ -219,30 +219,6 @@ def test_useragent_linux(): ) -@pytest.mark.parametrize("testing_env", ("TRAVIS_TESTING", "AUTOPKGTEST_TMP")) -def test_useragent_linux_with_testing(monkeypatch, testing_env): - """Construct a user-agent as a patched Linux machine""" - monkeypatch.setenv(testing_env, "1") - os_platform = OSPlatform( - system="Arch Linux", release="5.10.10-arch1-1", machine="x86_64" - ) - - assert client.build_user_agent(version="7.1.0", os_platform=os_platform) == ( - "snapcraft/7.1.0 (testing) Arch Linux/5.10.10-arch1-1 (x86_64)" - ) - - -@pytest.mark.parametrize("testing_env", ("TRAVIS_TESTING", "AUTOPKGTEST_TMP")) -def test_useragent_windows_with_testing(monkeypatch, testing_env): - """Construct a user-agent as a patched Windows machine""" - monkeypatch.setenv(testing_env, "1") - os_platform = OSPlatform(system="Windows", release="10", machine="AMD64") - - assert client.build_user_agent(version="7.1.0", os_platform=os_platform) == ( - "snapcraft/7.1.0 (testing) Windows/10 (AMD64)" - ) - - ##################### # Store Environment # ##################### diff --git a/tools/brew_install_from_source.py b/tools/brew_install_from_source.py index b4c3029fca..3c797464ed 100755 --- a/tools/brew_install_from_source.py +++ b/tools/brew_install_from_source.py @@ -46,10 +46,7 @@ def main(): def download_snapcraft_source(dest_dir): dest_file = os.path.join(dest_dir, "snapcraft-0.1.tar.gz") - branch_source = "https://github.com/{}/archive/{}.tar.gz".format( - os.environ.get("TRAVIS_PULL_REQUEST_SLUG") or "canonical/snapcraft", - os.environ.get("TRAVIS_PULL_REQUEST_BRANCH") or "main", - ) + branch_source = "https://github.com/canonical/snapcraft/archive/main.tar.gz" print("Downloading branch source from {}".format(branch_source)) urllib.request.urlretrieve(branch_source, dest_file) # noqa S310 return dest_file diff --git a/tools/collect_ppa_autopkgtests_results.py b/tools/collect_ppa_autopkgtests_results.py deleted file mode 100755 index e6a1cb8019..0000000000 --- a/tools/collect_ppa_autopkgtests_results.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- -# -# Copyright (C) 2017 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 -# 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 warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import argparse -import os -import subprocess -import tempfile - -ACTIVE_DISTROS = ("xenial", "artful", "bionic") - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("day", help="The day of the results, with format yyyymmdd") - args = parser.parse_args() - install_autopkgtest_results_formatter() - with tempfile.TemporaryDirectory(dir=os.environ.get("HOME")) as temp_dir: - clone_results_repo(temp_dir) - format_results(temp_dir, ACTIVE_DISTROS, args.day) - commit_and_push(temp_dir, args.day) - - -def install_autopkgtest_results_formatter(): - subprocess.check_call( - ["sudo", "snap", "install", "autopkgtest-results-formatter", "--edge"] - ) - - -def clone_results_repo(dest_dir): - subprocess.check_call( - ["git", "clone", "https://github.com/elopio/autopkgtest-results.git", dest_dir] - ) - - -def format_results(dest_dir, distros, day): - subprocess.check_call( - [ - "/snap/bin/autopkgtest-results-formatter", - "--destination", - dest_dir, - "--distros", - *distros, - "--day", - day, - ] - ) - - -def commit_and_push(repo_dir, day): - subprocess.check_call( - ["git", "config", "--global", "user.email", "u1test+m-o@canonical.com"] - ) - subprocess.check_call(["git", "config", "--global", "user.name", "snappy-m-o"]) - - subprocess.check_call(["git", "-C", repo_dir, "add", "--all"]) - subprocess.check_call( - [ - "git", - "-C", - repo_dir, - "commit", - "--message", - "Add the results for {}".format(day), - ] - ) - - subprocess.check_call( - [ - "git", - "-C", - repo_dir, - "push", - "https://{GH_TOKEN}@github.com/elopio/autopkgtest-results.git".format( - GH_TOKEN=os.environ.get("GH_TOKEN_PPA_AUTOPKGTEST_RESULTS") - ), - ] - ) - - -if __name__ == "__main__": - main() diff --git a/tools/retry_autopkgtest.sh b/tools/retry_autopkgtest.sh deleted file mode 100755 index 0d479acd7d..0000000000 --- a/tools/retry_autopkgtest.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -# -# Retry the autopkgtest run in a pull request. -# Arguments: -# pr: The identifier of the pull request to test. -# [release[:architecture[:test]] ...]: A list of the names of the Ubuntu releases and -# architectures and tests to run. By default, it will launch all the tests in -# `xenial:amd64`. If only the release name is passed as an argument, `amd64` -# will be used as the architecture. The possible autopkgtests are `integrationtests` -# and `snapstests`. -# -# Environment variables: -# SNAPCRAFT_AUTOPKGTEST_SECRET: The secret to authenticate the test execution. -# -# Examples: -# Run all the tests for pull request #123 in xenial amd64: -# ./tools/retry_autopkgtest.sh 123 -# Run all the tests for pull request #123 in xenial armhf: -# ./tools/retry_autopkgtest.sh 123 xenial:armhf -# Run all the tests for pull request #123 in xenial arm64, yakkety armhf and -# zesty amd64: -# ./tools/retry_autopkgtest.sh 123 xenial:arm64 yakkety:armhf zesty -# Run the integration tests for pull request #123 in xenial amd64: -# ./tools/retry_autopkgtest.sh 123 xenial:amd64:integrationtests - -if [ -z "${SNAPCRAFT_AUTOPKGTEST_SECRET}" ]; then - echo 'Set the secret to the environment variable SNAPCRAFT_AUTOPKGTEST_SECRET.' - exit 1 -fi - -if [ "$#" -lt 1 ]; then - echo "Usage: $0 [release[:architecture[:test]] ...]" - exit 1 -fi - -pr="$1" -shift -tests=("$@") -[ ${#tests[@]} -eq 0 ] && tests=('xenial:amd64') - -temp_dir="$(mktemp -d)" -trap 'rm -rf ${temp_dir}' EXIT - -# Download the retry script. -wget https://git.launchpad.net/autopkgtest-cloud/plain/tools/retry-github-test -O "${temp_dir}/retry-github-test" -chmod +x "${temp_dir}/retry-github-test" - -# Save the secret to a file. -echo "${SNAPCRAFT_AUTOPKGTEST_SECRET}" > "${temp_dir}/sec.txt" - -for testrun in "${tests[@]}"; do - IFS=':' read -r release architecture testsuite <<< "$testrun" - [ -z "$architecture" ] && architecture='amd64' - [ -n "$testsuite" ] && testname="&testname=${testsuite}" - echo "Launching tests for the ${release} release in the ${architecture} architecture..." - "${temp_dir}/retry-github-test" "https://api.github.com/repos/canonical/snapcraft/pulls/${pr}" "https://autopkgtest.ubuntu.com/request.cgi?release=${release}&arch=${architecture}&package=snapcraft${testname}&ppa=snappy-dev%2Fsnapcraft-daily" "${temp_dir}/sec.txt" -done diff --git a/tools/run_ppa_autopkgtests.py b/tools/run_ppa_autopkgtests.py deleted file mode 100755 index 8f5450c4a2..0000000000 --- a/tools/run_ppa_autopkgtests.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- -# -# Copyright (C) 2017 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 -# 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 warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import subprocess -import sys -import tempfile - -from launchpadlib.launchpad import Launchpad - -ACTIVE_DISTROS = ("xenial", "artful", "bionic") -ACTIVE_ARCHITECTURES = ("amd64", "i386", "armhf", "arm64") - - -def main(): - try: - cookie_file_path = save_cookie() - for distro, architecture, version in snapcraft_ppa_packages(): - request_autopkgtest_execution( - cookie_file_path, distro, architecture, version - ) - finally: - os.remove(cookie_file_path) - - -def save_cookie(): - cookie_file = tempfile.NamedTemporaryFile(delete=False) - cookie_file.write( - "autopkgtest.ubuntu.com\tTRUE\t/\tTRUE\t0\tsession\t{}".format( - os.environ.get("SNAPCRAFT_AUTOPKGTEST_COOKIE") - ) - ) - cookie_file.close() - return cookie_file.name - - -def snapcraft_ppa_packages(): - launchpad = Launchpad.login_anonymously("snappy-m-o", "production") - ubuntu = launchpad.distributions["ubuntu"] - snapcraft_daily_ppa = launchpad.people["snappy-dev"].getPPAByName( - name="snapcraft-daily" - ) - - for distro in ACTIVE_DISTROS: - for architecture in ACTIVE_ARCHITECTURES: - distro_arch = ubuntu.getSeries(name_or_version=distro).getDistroArchSeries( - archtag=architecture - ) - for package in snapcraft_daily_ppa.getPublishedBinaries( - status="Published", - binary_name="snapcraft", - exact_match=True, - distro_arch_series=distro_arch, - ): - yield distro, architecture, str(package.binary_package_version) - - -def request_autopkgtest_execution(cookie_path, distro, architecture, version): - output = subprocess.check_output( - [ - "wget", - "-O-", - "--load-cookies", - cookie_path, - "https://autopkgtest.ubuntu.com/request.cgi?release={distro}&" - "arch={architecture}&package=snapcraft&" - "ppa=snappy-dev/snapcraft-daily&" - "trigger=snapcraft/{version}".format( - distro=distro, architecture=architecture, version=version - ), - ] - ) - if "Test request submitted" not in output: - sys.exit("Failed to request the autopkgtest") - - -if __name__ == "__main__": - main() From 1b6b0cb42b2ff5b11bb8ffa83fa51a5be8e1779a Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Tue, 1 Oct 2024 11:10:04 -0300 Subject: [PATCH 099/151] docs: improve sidebar for the changelog page (#5070) --- docs/reference/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index fbc344f8ac..42b485f78d 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -1,3 +1,5 @@ +:tocdepth: 2 + Changelog ********* From 0449661651bf5ce3af265506645739f5ed9daab9 Mon Sep 17 00:00:00 2001 From: Callahan Date: Tue, 1 Oct 2024 11:15:41 -0500 Subject: [PATCH 100/151] ci: allow republishing branches (#5078) (#5082) Signed-off-by: Callahan Kovacs --- .github/workflows/publish.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 256bc582db..0a11f13634 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -5,6 +5,8 @@ on: push: branches: - "feature/**" + # allow manual re-publishing as branches expire after 30 days + workflow_dispatch: jobs: publish: From caa0757c2f9d7e072898ce3b5136149cb395f712 Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 2 Oct 2024 09:36:19 -0500 Subject: [PATCH 101/151] fix(remotebuild): do not auto clean interrupted builds (#5081) Fixes a bug where the remote builder would ignore the user and always clean the launchpad project. Also drops some magic values in favor of constants. Fixes #4929 Signed-off-by: Callahan Kovacs --- snapcraft/commands/remote.py | 9 +++++--- tests/unit/commands/test_remote.py | 33 ++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/snapcraft/commands/remote.py b/snapcraft/commands/remote.py index 2ac7b0e2b3..a38ea34a1f 100644 --- a/snapcraft/commands/remote.py +++ b/snapcraft/commands/remote.py @@ -308,7 +308,7 @@ def _run( # noqa: PLR0915 [too-many-statements] emit.progress("Remote repository already exists.", permanent=True) emit.progress("Cleaning up") builder.cleanup() - return 75 + return os.EX_TEMPFAIL try: returncode = self._monitor_and_complete(build_id, builds) @@ -316,10 +316,13 @@ def _run( # noqa: PLR0915 [too-many-statements] if confirm_with_user("Cancel builds?", default=True): emit.progress("Cancelling builds.") builder.cancel_builds() - returncode = 0 + emit.progress("Cleaning up.") + builder.cleanup() + return os.EX_OK except Exception: # noqa: BLE001 [blind-except] returncode = 1 # General error on any other exception - if returncode != 75: # TimeoutError + + if returncode != os.EX_TEMPFAIL: emit.progress("Cleaning up") builder.cleanup() return returncode diff --git a/tests/unit/commands/test_remote.py b/tests/unit/commands/test_remote.py index 6ebdc50633..c50abc9b21 100644 --- a/tests/unit/commands/test_remote.py +++ b/tests/unit/commands/test_remote.py @@ -22,7 +22,7 @@ import sys import time from pathlib import Path -from unittest.mock import ANY, Mock +from unittest.mock import ANY, Mock, call import pytest from craft_application import launchpad @@ -1019,9 +1019,13 @@ def test_monitor_build_error(mocker, emitter, snapcraft_yaml, base, fake_service @pytest.mark.parametrize("base", const.CURRENT_BASES) -@pytest.mark.usefixtures("mock_confirm") -def test_monitor_build_interrupt(mocker, emitter, snapcraft_yaml, base, fake_services): +@pytest.mark.parametrize("cleanup", [True, False]) +def test_monitor_build_interrupt( + cleanup, mock_confirm, mocker, emitter, snapcraft_yaml, base, fake_services +): """Test the monitor_build cleanup when a keyboard interrupt occurs.""" + # first prompt is for public upload, second is to cancel builds + mock_confirm.side_effect = [True, cleanup] mocker.patch.object(sys, "argv", ["snapcraft", "remote-build"]) snapcraft_yaml_dict = {"base": base, "build-base": "devel", "grade": "devel"} snapcraft_yaml(**snapcraft_yaml_dict) @@ -1043,6 +1047,10 @@ def test_monitor_build_interrupt(mocker, emitter, snapcraft_yaml, base, fake_ser "craft_application.services.remotebuild.RemoteBuildService.cleanup" ) + mock_cancel_builds = mocker.patch( + "craft_application.services.remotebuild.RemoteBuildService.cancel_builds" + ) + app = application.create_app() app.services.remote_build._name = get_build_id( app.services.app.name, app.project.name, app.project_dir @@ -1050,15 +1058,24 @@ def test_monitor_build_interrupt(mocker, emitter, snapcraft_yaml, base, fake_ser app.services.remote_build._is_setup = True app.services.remote_build.request.download_files_with_progress = Mock() - assert app.run() == 0 + assert app.run() == os.EX_OK mock_start_builds.assert_called_once() mock_monitor_builds.assert_called_once() mock_fetch_logs.assert_not_called() - mock_cleanup.assert_called_once() - emitter.assert_progress("Cancelling builds.") - emitter.assert_progress("Cleaning up") + cancel_emitted = call("progress", "Cancelling builds.") in emitter.interactions + clean_emitted = call("progress", "Cleaning up.") in emitter.interactions + if cleanup: + mock_cancel_builds.assert_called_once() + assert cancel_emitted + mock_cleanup.assert_called_once() + assert clean_emitted + else: + mock_cancel_builds.assert_not_called() + assert not cancel_emitted + mock_cleanup.assert_not_called() + assert not clean_emitted @pytest.mark.parametrize("base", const.CURRENT_BASES) @@ -1091,7 +1108,7 @@ def test_monitor_build_timeout(mocker, emitter, snapcraft_yaml, base, fake_servi app.services.app.name, app.project.name, app.project_dir ) - assert app.run() == 75 + assert app.run() == os.EX_TEMPFAIL mock_start_builds.assert_called_once() mock_monitor_builds.assert_called_once() From 932f9e47d0b97d1e70cef61dfec1c73a52cfe4e1 Mon Sep 17 00:00:00 2001 From: Callahan Date: Wed, 2 Oct 2024 11:36:38 -0500 Subject: [PATCH 102/151] feat: build, sign and publish registries (#5072) Finalizes support for the `edit-registries` command by building, signing and publishing registries. Fixes #5018 Signed-off-by: Callahan Kovacs --- snapcraft/commands/registries.py | 6 + snapcraft/errors.py | 7 + snapcraft/models/assertions.py | 38 ++- snapcraft/services/assertions.py | 119 +++++++-- snapcraft/services/registries.py | 15 +- snapcraft/store/client.py | 87 ++++++- tests/unit/commands/test_registries.py | 8 +- tests/unit/models/test_assertions.py | 88 ++++++- tests/unit/services/test_assertions.py | 342 ++++++++++++++++++------- tests/unit/services/test_registries.py | 31 ++- tests/unit/store/test_client.py | 198 +++++++++++++- 11 files changed, 809 insertions(+), 130 deletions(-) diff --git a/snapcraft/commands/registries.py b/snapcraft/commands/registries.py index 95fe349df2..d71d5b7613 100644 --- a/snapcraft/commands/registries.py +++ b/snapcraft/commands/registries.py @@ -82,6 +82,8 @@ class StoreEditRegistriesCommand(craft_application.commands.AppCommand): If the registries set does not exist, then a new registries set will be created. + If a key name is not provided, the default key is used. + The account ID of the authenticated account can be determined with the ``snapcraft whoami`` command. @@ -100,10 +102,14 @@ def fill_parser(self, parser: "argparse.ArgumentParser") -> None: parser.add_argument( "name", metavar="name", help="Name of the registries set to edit" ) + parser.add_argument( + "--key-name", metavar="key-name", help="Key used to sign the registries set" + ) @override def run(self, parsed_args: "argparse.Namespace"): self._services.registries.edit_assertion( name=parsed_args.name, account_id=parsed_args.account_id, + key_name=parsed_args.key_name, ) diff --git a/snapcraft/errors.py b/snapcraft/errors.py index 408c2fcb02..49c88f32ee 100644 --- a/snapcraft/errors.py +++ b/snapcraft/errors.py @@ -173,3 +173,10 @@ def __init__(self, message: str, *, resolution: str) -> None: resolution=resolution, docs_url="https://snapcraft.io/docs/snapcraft-authentication", ) + + +class SnapcraftAssertionError(SnapcraftError): + """Error raised when an assertion (validation or registries set) is invalid. + + Not to be confused with Python's built-in AssertionError. + """ diff --git a/snapcraft/models/assertions.py b/snapcraft/models/assertions.py index d557ff9092..efe6a30a4e 100644 --- a/snapcraft/models/assertions.py +++ b/snapcraft/models/assertions.py @@ -16,13 +16,44 @@ """Assertion models.""" -from typing import Literal +import numbers +from collections import abc +from typing import Any, Literal import pydantic from craft_application import models from typing_extensions import Self +def cast_dict_scalars_to_strings(data: dict) -> dict: + """Cast all scalars in a dictionary to strings. + + Supported scalar types are str, bool, and numbers. + """ + return {_to_string(key): _to_string(value) for key, value in data.items()} + + +def _to_string(data: Any) -> Any: + """Recurse through nested dicts and lists and cast scalar values to strings. + + Supported scalar types are str, bool, and numbers. + """ + # check for a string first, as it is the most common scenario + if isinstance(data, str): + return data + + if isinstance(data, abc.Mapping): + return {_to_string(key): _to_string(value) for key, value in data.items()} + + if isinstance(data, abc.Collection): + return [_to_string(i) for i in data] + + if isinstance(data, (numbers.Number, bool)): + return str(data) + + return data + + class Registry(models.CraftBaseModel): """Access and data definitions for a specific facet of a snap or system.""" @@ -52,7 +83,6 @@ class EditableRegistryAssertion(models.CraftBaseModel): """Issuer of the registry assertion and owner of the signing key.""" name: str - summary: str | None = None revision: int | None = 0 views: dict[str, Rules] @@ -61,6 +91,10 @@ class EditableRegistryAssertion(models.CraftBaseModel): body: str | None = None """A JSON schema that defines the storage structure.""" + def marshal_scalars_as_strings(self) -> dict[str, Any]: + """Marshal the model where all scalars are represented as strings.""" + return cast_dict_scalars_to_strings(self.marshal()) + class RegistryAssertion(EditableRegistryAssertion): """A full registries assertion containing editable and non-editable fields.""" diff --git a/snapcraft/services/assertions.py b/snapcraft/services/assertions.py index b3650b7883..c5aa03e876 100644 --- a/snapcraft/services/assertions.py +++ b/snapcraft/services/assertions.py @@ -33,6 +33,7 @@ from craft_application.errors import CraftValidationError from craft_application.services import base from craft_application.util import safe_yaml_load +from craft_store.errors import StoreServerError from typing_extensions import override from snapcraft import const, errors, models, store, utils @@ -68,6 +69,24 @@ def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: :returns: A list of assertions. """ + @abc.abstractmethod + def _build_assertion(self, assertion: models.EditableAssertion) -> models.Assertion: + """Build an assertion from an editable assertion. + + :param assertion: The editable assertion to build. + + :returns: The built assertion. + """ + + @abc.abstractmethod + def _post_assertion(self, assertion_data: bytes) -> models.Assertion: + """Post an assertion to the store. + + :param assertion_data: A signed assertion represented as bytes. + + :returns: The published assertion. + """ + @abc.abstractmethod def _normalize_assertions( self, assertions: list[models.Assertion] @@ -102,6 +121,15 @@ def _generate_yaml_from_template(self, name: str, account_id: str) -> str: :returns: A multi-line yaml string. """ + @abc.abstractmethod + def _get_success_message(self, assertion: models.Assertion) -> str: + """Create a message after an assertion has been successfully posted. + + :param assertion: The published assertion. + + :returns: The success message to log. + """ + def list_assertions(self, *, output_format: str, name: str | None = None) -> None: """List assertions from the store. @@ -150,6 +178,7 @@ def _edit_yaml_file(self, filepath: pathlib.Path) -> models.EditableAssertion: :returns: The edited assertion. """ + craft_cli.emit.progress(f"Editing {self._assertion_name}.") while True: craft_cli.emit.debug(f"Using {self._editor_cmd} to edit file.") with craft_cli.emit.pause(): @@ -161,8 +190,9 @@ def _edit_yaml_file(self, filepath: pathlib.Path) -> models.EditableAssertion: data=data, # filepath is only shown for pydantic errors and snapcraft should # not expose the temp file name - filepath=pathlib.Path(self._assertion_name.replace(" ", "-")), + filepath=pathlib.Path(self._assertion_name), ) + craft_cli.emit.progress(f"Edited {self._assertion_name}.") return edited_assertion except (yaml.YAMLError, CraftValidationError) as err: craft_cli.emit.message(f"{err!s}") @@ -178,12 +208,12 @@ def _get_yaml_data(self, name: str, account_id: str) -> str: if assertions := self._get_assertions(name=name): yaml_data = self._generate_yaml_from_model(assertions[0]) + craft_cli.emit.progress( + f"Retrieved {self._assertion_name} '{name}' from the store.", + ) else: craft_cli.emit.progress( - f"Creating a new {self._assertion_name} because no existing " - f"{self._assertion_name} named '{name}' was found for the " - "authenticated account.", - permanent=True, + f"Could not find an existing {self._assertion_name} named '{name}'.", ) yaml_data = self._generate_yaml_from_template( name=name, account_id=account_id @@ -204,30 +234,83 @@ def _remove_temp_file(filepath: pathlib.Path) -> None: craft_cli.emit.trace(f"Removing temporary file '{filepath}'.") filepath.unlink() - def edit_assertion(self, *, name: str, account_id: str) -> None: + @staticmethod + def _sign_assertion(assertion: models.Assertion, key_name: str | None) -> bytes: + """Sign an assertion with `snap sign`. + + :param assertion: The assertion to sign. + :param key_name: Name of the key to sign the assertion. + + :returns: A signed assertion represented as bytes. + """ + craft_cli.emit.progress("Signing assertion.") + cmdline = ["snap", "sign"] + if key_name: + cmdline += ["-k", key_name] + + # snapd expects a json string where all scalars are strings + unsigned_assertion = json.dumps(assertion.marshal_scalars_as_strings()) + + try: + # pause the emitter for passphrase prompts + with craft_cli.emit.pause(): + signed_assertion = subprocess.check_output( + cmdline, input=unsigned_assertion.encode() + ) + except subprocess.CalledProcessError as sign_error: + raise errors.SnapcraftAssertionError( + "Failed to sign assertion" + ) from sign_error + + craft_cli.emit.progress("Signed assertion.") + craft_cli.emit.trace(f"Signed assertion: {signed_assertion.decode()}") + return signed_assertion + + def edit_assertion( + self, *, name: str, account_id: str, key_name: str | None = None + ) -> None: """Edit, sign and upload an assertion. If the assertion does not exist, a new assertion is created from a template. :param name: The name of the assertion to edit. :param account_id: The account ID associated with the registries set. + :param key_name: Name of the key to sign the assertion. """ yaml_data = self._get_yaml_data(name=name, account_id=account_id) yaml_file = self._write_to_file(yaml_data) original_assertion = self._editable_assertion_class.unmarshal( safe_yaml_load(io.StringIO(yaml_data)) ) - edited_assertion = self._edit_yaml_file(yaml_file) - if edited_assertion == original_assertion: - craft_cli.emit.message("No changes made.") + try: + while True: + try: + edited_assertion = self._edit_yaml_file(yaml_file) + if edited_assertion == original_assertion: + craft_cli.emit.message("No changes made.") + break + + craft_cli.emit.progress(f"Building {self._assertion_name}.") + built_assertion = self._build_assertion(edited_assertion) + craft_cli.emit.progress(f"Built {self._assertion_name}.") + + signed_assertion = self._sign_assertion(built_assertion, key_name) + published_assertion = self._post_assertion(signed_assertion) + craft_cli.emit.message( + self._get_success_message(published_assertion) + ) + break + except ( + StoreServerError, + errors.SnapcraftAssertionError, + ) as assertion_error: + craft_cli.emit.message(str(assertion_error)) + if not utils.confirm_with_user( + f"Do you wish to amend the {self._assertion_name}?" + ): + raise errors.SnapcraftError( + "operation aborted" + ) from assertion_error + finally: self._remove_temp_file(yaml_file) - return - - # TODO: build, sign, and push assertion (#5018) - - self._remove_temp_file(yaml_file) - craft_cli.emit.message(f"Successfully edited {self._assertion_name} {name!r}.") - raise errors.FeatureNotImplemented( - f"Building, signing and uploading {self._assertion_name} is not implemented.", - ) diff --git a/snapcraft/services/registries.py b/snapcraft/services/registries.py index e6cd785c16..ce4de0c18f 100644 --- a/snapcraft/services/registries.py +++ b/snapcraft/services/registries.py @@ -31,7 +31,6 @@ """\ account-id: {account_id} name: {set_name} - # summary: {summary} # The revision for this registries set # revision: {revision} {views} @@ -85,6 +84,14 @@ def _editable_assertion_class(self) -> type[models.EditableAssertion]: def _get_assertions(self, name: str | None = None) -> list[models.Assertion]: return self._store_client.list_registries(name=name) + @override + def _build_assertion(self, assertion: models.EditableAssertion) -> models.Assertion: + return self._store_client.build_registries(registries=assertion) + + @override + def _post_assertion(self, assertion_data: bytes) -> models.Assertion: + return self._store_client.post_registries(registries_data=assertion_data) + @override def _normalize_assertions( self, assertions: list[models.Assertion] @@ -110,7 +117,6 @@ def _generate_yaml_from_model(self, assertion: models.Assertion) -> str: {"views": assertion.marshal().get("views")}, default_flow_style=False ), body=dump_yaml({"body": assertion.body}, default_flow_style=False), - summary=assertion.summary, set_name=assertion.name, revision=assertion.revision, ) @@ -121,7 +127,10 @@ def _generate_yaml_from_template(self, name: str, account_id: str) -> str: account_id=account_id, views=_REGISTRY_SETS_VIEWS_TEMPLATE, body=_REGISTRY_SETS_BODY_TEMPLATE, - summary="A brief summary of the registries set", set_name=name, revision=1, ) + + @override + def _get_success_message(self, assertion: models.Assertion) -> str: + return f"Successfully created revision {assertion.revision!r} for {assertion.name!r}." diff --git a/snapcraft/store/client.py b/snapcraft/store/client.py index dbde3a4daf..6170c5d8e2 100644 --- a/snapcraft/store/client.py +++ b/snapcraft/store/client.py @@ -23,7 +23,9 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, cast import craft_store +import pydantic import requests +from craft_application.util.error_formatting import format_pydantic_errors from craft_cli import emit from overrides import overrides @@ -495,6 +497,22 @@ def list_revisions(self, snap_name: str) -> Revisions: return Revisions.unmarshal(response.json()) + @staticmethod + def _unmarshal_registries_set(registries_data) -> models.RegistryAssertion: + """Unmarshal a registries set. + + :raises StoreAssertionError: If the registries set cannot be unmarshalled. + """ + try: + return models.RegistryAssertion.unmarshal(registries_data) + except pydantic.ValidationError as err: + raise errors.SnapcraftAssertionError( + message="Received invalid registries set from the store", + # this is an unexpected failure that the user can't fix, so hide + # the response in the details + details=f"{format_pydantic_errors(err.errors(), file_name='registries set')}", + ) from err + def list_registries( self, *, name: str | None = None ) -> list[models.RegistryAssertion]: @@ -518,16 +536,75 @@ def list_registries( registry_assertions = [] if assertions := response.json().get("assertions"): for assertion_data in assertions: - emit.debug(f"Parsing assertion: {assertion_data}") # move body into model - assertion_data["headers"]["body"] = assertion_data["body"] - assertion = models.RegistryAssertion.unmarshal( - assertion_data["headers"] - ) + assertion_data["headers"]["body"] = assertion_data.get("body") + + assertion = self._unmarshal_registries_set(assertion_data["headers"]) registry_assertions.append(assertion) + emit.debug(f"Parsed registries set: {assertion.model_dump_json()}") return registry_assertions + def build_registries( + self, *, registries: models.EditableRegistryAssertion + ) -> models.RegistryAssertion: + """Build a registries set. + + Sends an edited registries set to the store, which validates the data, + populates additional fields, and returns the registries set. + + :param registries: The registries set to build. + + :returns: The built registries set. + """ + response = self.request( + "POST", + f"{self._base_url}/api/v2/registries/build-assertion", + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + json=registries.marshal(), + ) + + assertion = self._unmarshal_registries_set(response.json()) + emit.debug(f"Built registries set: {assertion.model_dump_json()}") + return assertion + + def post_registries(self, *, registries_data: bytes) -> models.RegistryAssertion: + """Send a registries set to be published. + + :param registries_data: A signed registries set represented as bytes. + + :returns: The published assertion. + """ + response = self.request( + "POST", + f"{self._base_url}/api/v2/registries", + headers={ + "Accept": "application/json", + "Content-Type": "application/x.ubuntu.assertion", + }, + data=registries_data, + ) + + assertions = response.json().get("assertions") + + if not assertions or len(assertions) != 1: + raise errors.SnapcraftAssertionError( + message="Received invalid registries set from the store", + # this is an unexpected failure that the user can't fix, so hide + # the response in the details + details=f"Received data: {assertions}", + ) + + # move body into model + assertions[0]["headers"]["body"] = assertions[0]["body"] + + assertion = self._unmarshal_registries_set(assertions[0]["headers"]) + emit.debug(f"Published registries set: {assertion.model_dump_json()}") + return assertion + class OnPremStoreClientCLI(LegacyStoreClientCLI): """On Premises Store Client command line interface.""" diff --git a/tests/unit/commands/test_registries.py b/tests/unit/commands/test_registries.py index c4f34dac92..f206fe3866 100644 --- a/tests/unit/commands/test_registries.py +++ b/tests/unit/commands/test_registries.py @@ -53,7 +53,6 @@ def test_list_registries(mocker, mock_list_assertions, output_format, name): @pytest.mark.parametrize("name", [None, "test"]) def test_list_registries_default_format(mocker, mock_list_assertions, name): """Default format is 'table'.""" - """Test `snapcraft list-registries`.""" cmd = ["snapcraft", "list-registries"] if name: cmd.extend(["--name", name]) @@ -65,15 +64,18 @@ def test_list_registries_default_format(mocker, mock_list_assertions, name): mock_list_assertions.assert_called_once_with(name=name, output_format="table") +@pytest.mark.parametrize("key_name", [None, "test-key"]) @pytest.mark.usefixtures("memory_keyring") -def test_edit_registries(mocker, mock_edit_assertion): +def test_edit_registries(key_name, mocker, mock_edit_assertion): """Test `snapcraft edit-registries`.""" cmd = ["snapcraft", "edit-registries", "test-account-id", "test-name"] + if key_name: + cmd.extend(["--key-name", key_name]) mocker.patch.object(sys, "argv", cmd) app = application.create_app() app.run() mock_edit_assertion.assert_called_once_with( - name="test-name", account_id="test-account-id" + name="test-name", account_id="test-account-id", key_name=key_name ) diff --git a/tests/unit/models/test_assertions.py b/tests/unit/models/test_assertions.py index a6fed23d95..16c6552347 100644 --- a/tests/unit/models/test_assertions.py +++ b/tests/unit/models/test_assertions.py @@ -17,7 +17,42 @@ """Tests for Assertion models.""" +import pytest + from snapcraft.models import EditableRegistryAssertion, Registry, RegistryAssertion +from snapcraft.models.assertions import cast_dict_scalars_to_strings + + +@pytest.mark.parametrize( + ("input_dict", "expected_dict"), + [ + pytest.param({}, {}, id="empty"), + pytest.param( + {False: False, True: True}, + {"False": "False", "True": "True"}, + id="boolean values", + ), + pytest.param( + {0: 0, None: None, "dict": {}, "list": [], "str": ""}, + ({"0": "0", None: None, "dict": {}, "list": [], "str": ""}), + id="none-like values", + ), + pytest.param( + {10: 10, 20.0: 20.0, "30": "30", True: True}, + {"10": "10", "20.0": "20.0", "30": "30", "True": "True"}, + id="scalar values", + ), + pytest.param( + {"foo": {"bar": [1, 2.0], "baz": {"qux": True}}}, + {"foo": {"bar": ["1", "2.0"], "baz": {"qux": "True"}}}, + id="nested data structures", + ), + ], +) +def test_cast_dict_scalars_to_strings(input_dict, expected_dict): + actual = cast_dict_scalars_to_strings(input_dict) + + assert actual == expected_dict def test_registry_defaults(check): @@ -73,11 +108,34 @@ def test_editable_registry_assertion_defaults(check): } ) - check.is_none(assertion.summary) check.equal(assertion.revision, 0) check.is_none(assertion.body) +def test_editable_registry_assertion_marshal_as_str(): + """Cast all scalars to string when marshalling.""" + assertion = EditableRegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "name": "test-registry", + "revision": 10, + "views": { + "wifi-setup": { + "rules": [ + { + "storage": "wifi.ssids", + } + ] + } + }, + } + ) + + assertion_dict = assertion.marshal_scalars_as_strings() + + assert assertion_dict["revision"] == "10" + + def test_registry_assertion_defaults(check): """Test default values of the RegistryAssertion model.""" assertion = RegistryAssertion.unmarshal( @@ -104,5 +162,31 @@ def test_registry_assertion_defaults(check): check.is_none(assertion.body) check.is_none(assertion.body_length) check.is_none(assertion.sign_key_sha3_384) - check.is_none(assertion.summary) check.equal(assertion.revision, 0) + + +def test_registry_assertion_marshal_as_str(): + """Cast all scalars to strings when marshalling.""" + assertion = RegistryAssertion.unmarshal( + { + "account_id": "test-account-id", + "authority_id": "test-authority-id", + "name": "test-registry", + "revision": 10, + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "storage": "wifi.ssids", + } + ] + } + }, + } + ) + + assertion_dict = assertion.marshal_scalars_as_strings() + + assert assertion_dict["revision"] == "10" diff --git a/tests/unit/services/test_assertions.py b/tests/unit/services/test_assertions.py index 73b15bc92e..56da17d207 100644 --- a/tests/unit/services/test_assertions.py +++ b/tests/unit/services/test_assertions.py @@ -16,15 +16,19 @@ """Tests for the abstract assertions service.""" +import json +import tempfile import textwrap from typing import Any from unittest import mock +import craft_store.errors import pytest from craft_application.models import CraftBaseModel from typing_extensions import override from snapcraft import const, errors +from tests.unit.store.utils import FakeResponse @pytest.fixture(autouse=True) @@ -48,8 +52,8 @@ def mock_confirm_with_user(mocker, request): @pytest.fixture -def mock_subprocess_run(mocker, tmp_path, request): - """Mock the subprocess.run function to write data to a file. +def write_text(mocker, tmp_path, request): + """Mock the subprocess.run function to write fake data to a temp assertion file. :param request: A list of strings to write to a file. Each time the subprocess.run function is called, the last string in the list will be written to the file @@ -65,12 +69,49 @@ def side_effect(*args, **kwargs): return subprocess_mock +@pytest.fixture +def fake_sign_assertion(mocker): + def _fake_sign(cmdline, input): # noqa: A002 (builtin-argument-shadowing) + return input + b"-signed" + + mock_subprocess = mocker.patch("subprocess.check_output") + mock_subprocess.side_effect = _fake_sign + return mock_subprocess + + +@pytest.fixture(autouse=True) +def mock_named_temporary_file(mocker, tmp_path): + _mock_tempfile = mocker.patch( + "tempfile.NamedTemporaryFile", spec=tempfile.NamedTemporaryFile + ) + _mock_tempfile.return_value.__enter__.return_value.name = str( + tmp_path / "assertion-file" + ) + yield _mock_tempfile.return_value + + +FAKE_STORE_ERROR = craft_store.errors.StoreServerError( + response=FakeResponse( + content=json.dumps( + {"error_list": [{"code": "bad assertion", "message": "bad assertion"}]} + ), + status_code=400, + ) +) + + class FakeAssertion(CraftBaseModel): """Fake assertion model.""" test_field_1: str test_field_2: int + def marshal_scalars_as_strings(self): + return { + "test_field_1": self.test_field_1, + "test_field_2": str(self.test_field_2), + } + @pytest.fixture def fake_assertion_service(default_factory): @@ -99,6 +140,21 @@ def _get_assertions( # type: ignore[override] FakeAssertion(test_field_1="test-value-2", test_field_2=100), ] + @override + def _build_assertion( # type: ignore[override] + self, assertion: FakeAssertion + ) -> FakeAssertion: + assertion.test_field_1 = assertion.test_field_1 + "-built" + return assertion + + @override + def _post_assertion( # type: ignore[override] + self, assertion_data: bytes + ) -> FakeAssertion: + return FakeAssertion( + test_field_1="test-published-assertion", test_field_2=0 + ) + @override def _normalize_assertions( # type: ignore[override] self, assertions: list[FakeAssertion] @@ -133,19 +189,13 @@ def _generate_yaml_from_template(self, name: str, account_id: str) -> str: """ ) - return FakeAssertionService(app=APP_METADATA, services=default_factory) - + @override + def _get_success_message( # type: ignore[override] + self, assertion: FakeAssertion + ) -> str: + return "Success." -@pytest.fixture -def fake_edit_yaml_file(mocker, fake_assertion_service): - """Apply a fake edit to a yaml file.""" - return mocker.patch.object( - fake_assertion_service, - "_edit_yaml_file", - return_value=FakeAssertion( - test_field_1="test-value-1-UPDATED", test_field_2=999 - ), - ) + return FakeAssertionService(app=APP_METADATA, services=default_factory) def test_list_assertions_table(fake_assertion_service, emitter): @@ -199,62 +249,213 @@ def test_list_assertions_unknown_format(fake_assertion_service): ) +@pytest.mark.parametrize( + "write_text", + [["test-field-1: test-value-1-edited\ntest-field-2: 999"]], + indirect=True, +) +@pytest.mark.usefixtures("fake_sign_assertion") def test_edit_assertions_changes_made( - fake_edit_yaml_file, fake_assertion_service, emitter + fake_assertion_service, + emitter, + mocker, + tmp_path, + write_text, ): """Edit an assertion and make a valid change.""" - expected = "Building, signing and uploading fake assertion is not implemented" - fake_assertion_service.setup() + expected_assertion = ( + b'{"test_field_1": "test-value-1-edited-built", "test_field_2": "999"}-signed' + ) + mock_post_assertion = mocker.spy(fake_assertion_service, "_post_assertion") - with pytest.raises(errors.FeatureNotImplemented, match=expected): - fake_assertion_service.edit_assertion( - name="test-registry", account_id="test-account-id" - ) + fake_assertion_service.setup() + fake_assertion_service.edit_assertion( + name="test-registry", account_id="test-account-id", key_name="test-key" + ) - emitter.assert_message("Successfully edited fake assertion 'test-registry'.") + mock_post_assertion.assert_called_once_with(expected_assertion) + emitter.assert_trace(f"Signed assertion: {expected_assertion.decode()}") + emitter.assert_message("Success.") +@pytest.mark.parametrize( + "write_text", + [["test-field-1: test-value-1\ntest-field-2: 0"]], + indirect=True, +) def test_edit_assertions_no_changes_made( - fake_edit_yaml_file, fake_assertion_service, emitter, mocker + fake_assertion_service, emitter, tmp_path, write_text ): """Edit an assertion but make no changes to the data.""" + fake_assertion_service.setup() + fake_assertion_service.edit_assertion( + name="test-registry", account_id="test-account-id" + ) + + emitter.assert_message("No changes made.") + assert not (tmp_path / "assertion-file").exists() + + +@pytest.mark.parametrize( + "write_text", + [ + [ + "test-field-1: test-value-1-edited-edited\ntest-field-2: 999", + "test-field-1: test-value-1-edited\ntest-field-2: 999", + ], + ], + indirect=True, +) +@pytest.mark.parametrize("mock_confirm_with_user", [True], indirect=True) +@pytest.mark.parametrize( + "error", [FAKE_STORE_ERROR, errors.SnapcraftAssertionError("bad assertion")] +) +@pytest.mark.usefixtures("fake_sign_assertion") +def test_edit_assertions_build_assertion_error( + error, + fake_assertion_service, + emitter, + mock_confirm_with_user, + write_text, + mocker, + tmp_path, +): + """Receive an error while building an assertion, then re-edit and post the assertion.""" + expected_assertion = b'{"test_field_1": "test-value-1-edited-edited-built", "test_field_2": "999"}-signed' + mock_post_assertion = mocker.spy(fake_assertion_service, "_post_assertion") mocker.patch.object( fake_assertion_service, - "_edit_yaml_file", - # make no changes to the fake assertion - return_value=FakeAssertion(test_field_1="test-value-1", test_field_2=0), + "_build_assertion", + side_effect=[ + error, + FakeAssertion( + test_field_1="test-value-1-edited-edited-built", test_field_2=999 + ), + ], ) + fake_assertion_service.setup() + fake_assertion_service.edit_assertion( + name="test-registry", account_id="test-account-id", key_name="test-key" + ) + assert mock_confirm_with_user.mock_calls == [ + mock.call("Do you wish to amend the fake assertion?") + ] + assert mock_post_assertion.mock_calls == [mock.call(expected_assertion)] + emitter.assert_trace(f"Signed assertion: {expected_assertion.decode()}") + emitter.assert_message("Success.") + assert not (tmp_path / "assertion-file").exists() + + +@pytest.mark.parametrize( + "write_text", + [ + [ + "test-field-1: test-value-1-edited-edited\ntest-field-2: 999", + "test-field-1: test-value-1-edited\ntest-field-2: 999", + ], + ], + indirect=True, +) +@pytest.mark.parametrize("mock_confirm_with_user", [True], indirect=True) +@pytest.mark.usefixtures("fake_sign_assertion") +def test_edit_assertions_sign_assertion_error( + fake_assertion_service, + emitter, + mock_confirm_with_user, + write_text, + mocker, + tmp_path, +): + """Receive an error while signing an assertion, then re-edit and post the assertion.""" + expected_assertion = b'{"test_field_1": "test-value-1-edited-edited-built", "test_field_2": "999"}-signed' + mock_post_assertion = mocker.spy(fake_assertion_service, "_post_assertion") + mocker.patch.object( + fake_assertion_service, + "_sign_assertion", + side_effect=[ + errors.SnapcraftAssertionError("bad assertion"), + expected_assertion, + ], + ) + + fake_assertion_service.setup() fake_assertion_service.edit_assertion( - name="test-registry", account_id="test-account-id" + name="test-registry", account_id="test-account-id", key_name="test-key" ) - emitter.assert_message("No changes made.") + assert mock_confirm_with_user.mock_calls == [ + mock.call("Do you wish to amend the fake assertion?") + ] + assert mock_post_assertion.mock_calls == [mock.call(expected_assertion)] + emitter.assert_message("Success.") + assert not (tmp_path / "assertion-file").exists() -@pytest.mark.parametrize("editor", [None, "faux-vi"]) @pytest.mark.parametrize( - "mock_subprocess_run", + "write_text", [ [ - textwrap.dedent( - """\ - test-field-1: test-value-1-UPDATED - test-field-2: 999 - """ - ), + "test-field-1: test-value-1-edited-edited\ntest-field-2: 999", + "test-field-1: test-value-1-edited\ntest-field-2: 999", ], ], indirect=True, ) @pytest.mark.parametrize("mock_confirm_with_user", [True], indirect=True) +@pytest.mark.parametrize( + "error", [FAKE_STORE_ERROR, errors.SnapcraftAssertionError("bad assertion")] +) +@pytest.mark.usefixtures("fake_sign_assertion") +def test_edit_assertions_post_assertion_error( + error, + fake_assertion_service, + emitter, + mock_confirm_with_user, + write_text, + mocker, + tmp_path, +): + """Receive an error while processing an assertion, then re-edit and post the assertion.""" + expected_first_assertion = ( + b'{"test_field_1": "test-value-1-edited-built", "test_field_2": "999"}-signed' + ) + expected_second_assertion = b'{"test_field_1": "test-value-1-edited-edited-built", "test_field_2": "999"}-signed' + mock_post_assertion = mocker.patch.object( + fake_assertion_service, "_post_assertion", side_effect=[error, None] + ) + + fake_assertion_service.setup() + fake_assertion_service.edit_assertion( + name="test-registry", account_id="test-account-id", key_name="test-key" + ) + + assert mock_confirm_with_user.mock_calls == [ + mock.call("Do you wish to amend the fake assertion?") + ] + assert mock_post_assertion.mock_calls == [ + mock.call(expected_first_assertion), + mock.call(expected_second_assertion), + ] + emitter.assert_trace(f"Signed assertion: {expected_second_assertion.decode()}") + emitter.assert_message("Success.") + assert not (tmp_path / "assertion-file").exists() + + +@pytest.mark.parametrize("editor", [None, "faux-vi"]) +@pytest.mark.parametrize( + "write_text", + [["test-field-1: test-value-1-edited\ntest-field-2: 999"]], + indirect=True, +) +@pytest.mark.parametrize("mock_confirm_with_user", [True], indirect=True) def test_edit_yaml_file( editor, fake_assertion_service, tmp_path, mock_confirm_with_user, - mock_subprocess_run, + write_text, monkeypatch, ): """Successfully edit a yaml file with the correct editor.""" @@ -271,50 +472,26 @@ def test_edit_yaml_file( edited_assertion = fake_assertion_service._edit_yaml_file(tmp_file) assert edited_assertion == FakeAssertion( - test_field_1="test-value-1-UPDATED", test_field_2=999 + test_field_1="test-value-1-edited", test_field_2=999 ) mock_confirm_with_user.assert_not_called() - assert mock_subprocess_run.mock_calls == [ - mock.call([expected_editor, tmp_file], check=True) - ] + assert write_text.mock_calls == [mock.call([expected_editor, tmp_file], check=True)] @pytest.mark.parametrize( - "mock_subprocess_run", + "write_text", [ pytest.param( [ - textwrap.dedent( - """\ - test-field-1: test-value-1-UPDATED - test-field-2: 999 - """ - ), - textwrap.dedent( - """\ - bad yaml {{ - test-field-1: test-value-1 - test-field-2: 0 - """ - ), + "test-field-1: test-value-1-edited\ntest-field-2: 999", + "bad yaml {{\ntest-field-1: test-value-1\ntest-field-2: 0", ], id="invalid yaml syntax", ), pytest.param( [ - textwrap.dedent( - """\ - test-field-1: test-value-1-UPDATED - test-field-2: 999 - """ - ), - textwrap.dedent( - """\ - extra-field: not-allowed - test-field-1: [wrong data type] - test-field-2: 0 - """ - ), + "test-field-1: test-value-1-edited\ntest-field-2: 999", + "extra-field: not-allowed\ntest-field-1: [wrong data type]\ntest-field-2: 0", ], id="invalid pydantic data", ), @@ -326,7 +503,7 @@ def test_edit_yaml_file_error_retry( fake_assertion_service, tmp_path, mock_confirm_with_user, - mock_subprocess_run, + write_text, ): """Edit a yaml file but encounter an error and retry.""" tmp_file = tmp_path / "assertion-file" @@ -335,30 +512,17 @@ def test_edit_yaml_file_error_retry( edited_assertion = fake_assertion_service._edit_yaml_file(tmp_file) assert edited_assertion == FakeAssertion( - test_field_1="test-value-1-UPDATED", test_field_2=999 + test_field_1="test-value-1-edited", test_field_2=999 ) assert mock_confirm_with_user.mock_calls == [ mock.call("Do you wish to amend the fake assertion?") ] - assert ( - mock_subprocess_run.mock_calls - == [mock.call(["faux-vi", tmp_file], check=True)] * 2 - ) + assert write_text.mock_calls == [mock.call(["faux-vi", tmp_file], check=True)] * 2 @pytest.mark.parametrize( - "mock_subprocess_run", - [ - [ - textwrap.dedent( - """\ - bad yaml {{ - test-field-1: test-value-1 - test-field-2: 0 - """ - ), - ], - ], + "write_text", + [["bad yaml {{\ntest-field-1: test-value-1\ntest-field-2: 0"]], indirect=True, ) @pytest.mark.parametrize("mock_confirm_with_user", [False], indirect=True) @@ -366,7 +530,7 @@ def test_edit_error_no_retry( fake_assertion_service, tmp_path, mock_confirm_with_user, - mock_subprocess_run, + write_text, ): """Edit a yaml file and encounter an error but do not retry.""" tmp_file = tmp_path / "assertion-file" @@ -378,6 +542,4 @@ def test_edit_error_no_retry( assert mock_confirm_with_user.mock_calls == [ mock.call("Do you wish to amend the fake assertion?") ] - assert mock_subprocess_run.mock_calls == [ - mock.call(["faux-vi", tmp_file], check=True) - ] + assert write_text.mock_calls == [mock.call(["faux-vi", tmp_file], check=True)] diff --git a/tests/unit/services/test_registries.py b/tests/unit/services/test_registries.py index f791cceb9a..2305f13413 100644 --- a/tests/unit/services/test_registries.py +++ b/tests/unit/services/test_registries.py @@ -17,8 +17,9 @@ """Tests for the registries service.""" import textwrap +from unittest import mock -from snapcraft.models import EditableRegistryAssertion +from snapcraft.models import EditableRegistryAssertion, RegistryAssertion def test_registries_service_type(registries_service): @@ -37,6 +38,24 @@ def test_get_assertions(registries_service): ) +def test_build_assertion(registries_service): + mock_assertion = mock.Mock(spec=RegistryAssertion) + + registries_service._build_assertion(mock_assertion) + + registries_service._store_client.build_registries.assert_called_once_with( + registries=mock_assertion + ) + + +def test_post_assertions(registries_service): + registries_service._post_assertion(b"test-assertion-data") + + registries_service._store_client.post_registries.assert_called_once_with( + registries_data=b"test-assertion-data" + ) + + def test_normalize_assertions_empty(registries_service, check): headers, registries = registries_service._normalize_assertions([]) @@ -71,7 +90,6 @@ def test_normalize_assertions(fake_registry_assertion, registries_service, check def test_generate_yaml_from_model(fake_registry_assertion, registries_service): assertion = fake_registry_assertion( - summary="test-summary", revision="10", views={ "wifi-setup": { @@ -102,7 +120,6 @@ def test_generate_yaml_from_model(fake_registry_assertion, registries_service): """\ account-id: test-account-id name: test-registry - # summary: test-summary # The revision for this registries set # revision: 10 views: @@ -129,3 +146,11 @@ def test_generate_yaml_from_model(fake_registry_assertion, registries_service): """ ) + + +def test_get_success_message(fake_registry_assertion, registries_service): + message = registries_service._get_success_message( + fake_registry_assertion(revision=10) + ) + + assert message == "Successfully created revision 10 for 'test-registry'." diff --git a/tests/unit/store/test_client.py b/tests/unit/store/test_client.py index fd3bf73921..6fc14ae9f0 100644 --- a/tests/unit/store/test_client.py +++ b/tests/unit/store/test_client.py @@ -17,7 +17,7 @@ import json import textwrap import time -from unittest.mock import ANY, call +from unittest.mock import ANY, Mock, call import craft_store import pytest @@ -180,7 +180,7 @@ def list_registries_payload(): "account-id": "test-account-id", "authority-id": "test-authority-id", "body-length": "92", - "name": "test-registry", + "name": "test-registries", "revision": "9", "sign-key-sha3-384": "test-sign-key", "timestamp": "2024-01-01T10:20:30Z", @@ -203,6 +203,62 @@ def list_registries_payload(): } +@pytest.fixture +def build_registries_payload(): + return { + "account_id": "test-account-id", + "authority_id": "test-authority-id", + "name": "test-registries", + "revision": "10", + "views": { + "wifi-setup": { + "rules": [ + { + "request": "ssids", + "storage": "wifi.ssids", + "access": "read-write", + } + ] + } + }, + "body": '{\n "storage": {\n "schema": {\n "wifi": {\n "values": "any"\n }\n }\n }\n}', + "type": "registry", + "timestamp": "2024-01-01T10:20:30Z", + } + + +@pytest.fixture +def post_registries_payload(): + return { + "assertions": [ + { + "headers": { + "account-id": "test-account-id", + "authority-id": "test-authority-id", + "body-length": "92", + "name": "test-registries", + "revision": "10", + "sign-key-sha3-384": "test-key", + "timestamp": "2024-01-01T10:20:30Z", + "type": "registry", + "views": { + "wifi-setup": { + "rules": [ + { + "access": "read", + "request": "ssids", + "storage": "wifi.ssids", + } + ] + } + }, + }, + "body": '{\n "storage": {\n "schema": {\n "wifi": {\n "values": "any"\n }\n }\n }\n}', + } + ] + } + + #################### # User Agent Tests # #################### @@ -1067,7 +1123,7 @@ def test_list_revisions(fake_client, list_revisions_payload): @pytest.mark.parametrize("name", [None, "test-registry"]) def test_list_registries(name, fake_client, list_registries_payload, check): - """Test the registries endpoint.""" + """Test the list registries endpoint.""" fake_client.request.return_value = FakeResponse( status_code=200, content=json.dumps(list_registries_payload).encode() ) @@ -1098,7 +1154,7 @@ def test_list_registries(name, fake_client, list_registries_payload, check): def test_list_registries_empty(fake_client, check): - """Test the registries endpoint with no registries returned.""" + """Test the list registries endpoint with no registries returned.""" fake_client.request.return_value = FakeResponse( status_code=200, content=json.dumps({"assertions": []}).encode() ) @@ -1121,6 +1177,140 @@ def test_list_registries_empty(fake_client, check): ) +def test_list_registries_unmarshal_error(fake_client, list_registries_payload): + """Raise an error if the response cannot be unmarshalled.""" + list_registries_payload["assertions"][0]["headers"].pop("name") + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(list_registries_payload).encode() + ) + + with pytest.raises(errors.SnapcraftAssertionError) as raised: + client.StoreClientCLI().list_registries() + + assert str(raised.value) == "Received invalid registries set from the store" + assert raised.value.details == ( + "Bad registries set content:\n" + "- field 'name' required in top-level configuration" + ) + + +#################### +# Build Registries # +#################### + + +def test_build_registries(fake_client, build_registries_payload): + """Test the build registries endpoint.""" + mock_registries = Mock(spec=models.RegistryAssertion) + expected_registries = models.RegistryAssertion(**build_registries_payload) + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(build_registries_payload).encode() + ) + + registries_set = client.StoreClientCLI().build_registries( + registries=mock_registries + ) + + assert registries_set == expected_registries + assert fake_client.request.mock_calls == [ + call( + "POST", + "https://dashboard.snapcraft.io/api/v2/registries/build-assertion", + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + }, + json=mock_registries.marshal(), + ) + ] + + +def test_build_registries_unmarshal_error(fake_client, build_registries_payload): + """Raise an error if the response cannot be unmarshalled.""" + mock_registries = Mock(spec=models.RegistryAssertion) + build_registries_payload.pop("name") + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(build_registries_payload).encode() + ) + + with pytest.raises(errors.SnapcraftAssertionError) as raised: + client.StoreClientCLI().build_registries(registries=mock_registries) + + assert str(raised.value) == "Received invalid registries set from the store" + assert raised.value.details == ( + "Bad registries set content:\n" + "- field 'name' required in top-level configuration" + ) + + +################### +# Post Registries # +################### + + +def test_post_registries(fake_client, post_registries_payload): + """Test the post registries endpoint.""" + expected_registries = models.RegistryAssertion( + **post_registries_payload["assertions"][0]["headers"], + body=post_registries_payload["assertions"][0]["body"], + ) + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(post_registries_payload).encode() + ) + + registries_set = client.StoreClientCLI().post_registries( + registries_data=b"test-data" + ) + + assert registries_set == expected_registries + assert fake_client.request.mock_calls == [ + call( + "POST", + "https://dashboard.snapcraft.io/api/v2/registries", + headers={ + "Accept": "application/json", + "Content-Type": "application/x.ubuntu.assertion", + }, + data=b"test-data", + ) + ] + + +@pytest.mark.parametrize("num_assertions", [0, 2]) +def test_post_registries_wrong_payload_error( + num_assertions, fake_client, post_registries_payload +): + """Error if the wrong number of assertions are returned.""" + post_registries_payload["assertions"] = ( + post_registries_payload["assertions"] * num_assertions + ) + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(post_registries_payload).encode() + ) + + with pytest.raises(errors.SnapcraftAssertionError) as raised: + client.StoreClientCLI().post_registries(registries_data=b"test-data") + + assert str(raised.value) == "Received invalid registries set from the store" + + +def test_post_registries_unmarshal_error(fake_client, post_registries_payload): + """Raise an error if the response cannot be unmarshalled.""" + post_registries_payload["assertions"][0]["headers"].pop("name") + fake_client.request.return_value = FakeResponse( + status_code=200, content=json.dumps(post_registries_payload).encode() + ) + + with pytest.raises(errors.SnapcraftAssertionError) as raised: + client.StoreClientCLI().post_registries(registries_data=b"test-data") + + assert str(raised.value) == "Received invalid registries set from the store" + assert raised.value.details == ( + "Bad registries set content:\n" + "- field 'name' required in top-level configuration" + ) + + ######################## # OnPremStoreClientCLI # ######################## From 1b5789fa81eb7560c3e27c9f932aa4fc436dea09 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Tue, 1 Oct 2024 12:46:59 -0500 Subject: [PATCH 103/151] build(deps): bump craft-store to 3.0.2 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 515ced5b6b..0b60799231 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -31,7 +31,7 @@ craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 -craft-store==3.0.1 +craft-store==3.0.2 cryptography==43.0.1 cssutils==2.11.1 dict2css==0.3.0.post1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 665fa0369f..31ce1e69fe 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -26,7 +26,7 @@ craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 -craft-store==3.0.1 +craft-store==3.0.2 cryptography==43.0.1 cssutils==2.11.1 dict2css==0.3.0.post1 diff --git a/requirements.txt b/requirements.txt index 45c38f3fc6..b0d657e27a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 craft-providers==2.0.1 -craft-store==3.0.1 +craft-store==3.0.2 cryptography==43.0.1 distro==1.9.0 docutils==0.19 diff --git a/setup.py b/setup.py index 78ee0a0b6a..f81a57187d 100755 --- a/setup.py +++ b/setup.py @@ -105,7 +105,7 @@ def recursive_data_files(directory, install_directory): "craft-parts~=2.1", "craft-platforms~=0.1", "craft-providers~=2.0", - "craft-store>=3.0.1,<4.0.0", + "craft-store>=3.0.2,<4.0.0", "docutils<0.20", # Frozen until we can update sphinx dependencies. "gnupg", "jsonschema==2.5.1", From 5693b96eac6d04864d9ba448b33ae5330d62127e Mon Sep 17 00:00:00 2001 From: Dariusz Duda Date: Mon, 23 Sep 2024 17:38:33 -0400 Subject: [PATCH 104/151] fix: correctly set PIP_NO_BINARY Signed-off-by: Dariusz Duda --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 70754e382e..119cbef6e7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -171,7 +171,7 @@ parts: build-environment: # Build PyNaCl from source since the wheel files interact # strangely with classic snaps. Well, build it all from source. - - "PIP_NO_BINARY": ":all" + - "PIP_NO_BINARY": ":all:" # Use base image's libsodium for PyNaCl. - "SODIUM_INSTALL": "system" - "CFLAGS": "$(pkg-config python-3.10 yaml-0.1 --cflags)" From bf90e90abbf733d824a0a54f350112a95236c25f Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 2 Oct 2024 13:46:26 -0500 Subject: [PATCH 105/151] build(deps): bump libgit2 to 1.7.2 Signed-off-by: Callahan Kovacs --- snap/snapcraft.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 119cbef6e7..ebbfea1d71 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -140,11 +140,12 @@ parts: -e 's/^ENABLE_USER_SITE = None$/ENABLE_USER_SITE = False/' libgit2: - source: https://github.com/libgit2/libgit2/archive/refs/tags/v1.7.1.tar.gz - source-checksum: sha256/17d2b292f21be3892b704dddff29327b3564f96099a1c53b00edc23160c71327 + source: https://github.com/libgit2/libgit2/archive/refs/tags/v1.7.2.tar.gz + source-checksum: sha256/de384e29d7efc9330c6cdb126ebf88342b5025d920dcb7c645defad85195ea7f plugin: cmake cmake-parameters: - -DCMAKE_INSTALL_PREFIX=/usr + - -DCMAKE_BUILD_TYPE=RelWithDebInfo build-attributes: - enable-patchelf prime: From b6ce7100b1f440554bae3b20da95f4d58f518400 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:23:19 -0400 Subject: [PATCH 106/151] build(deps): switch to requests-unixsocket2 --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b893e30c37..2d4a2377da 100755 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ def recursive_data_files(directory, install_directory): "pyyaml", "raven", "requests-toolbelt", - "requests-unixsocket", + "requests-unixsocket2", "requests", # pin setuptools<66 (CRAFT-1598) "setuptools<66", @@ -131,7 +131,6 @@ def recursive_data_files(directory, install_directory): "toml", "tinydb", "typing-extensions", - "urllib3<2", # requests-unixsocket does not yet work with urllib3 v2.0+ ] try: From 0769811adc5adac134191a3e77dd56423707047b Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:24:11 -0400 Subject: [PATCH 107/151] build(deps): update dependencies --- requirements-devel.txt | 12 ++++++------ requirements.txt | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 2163371a67..92e9651bd0 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -13,9 +13,9 @@ colorama==0.4.6 coverage==7.2.5 craft-archives==1.1.3 craft-cli==1.2.0 -craft-grammar==1.1.1 -craft-parts==1.19.7 -craft-providers==1.20.2 +craft-grammar==1.1.2 +craft-parts==1.19.8 +craft-providers==1.20.4 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.14 @@ -93,7 +93,7 @@ PyYAML==6.0.2 raven==6.10.0 requests==2.30.0 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 ruff==0.0.220 SecretStorage==3.3.3 simplejson==3.19.2 @@ -116,7 +116,7 @@ types-setuptools==67.7.0.2 types-tabulate==0.9.0.20240106 types-urllib3==1.26.25.14 typing_extensions==4.5.0 -urllib3==1.26.19 +urllib3==2.2.3 venusian==3.0.0 virtualenv==20.23.0 wadllib==1.3.6 @@ -125,6 +125,6 @@ wrapt==1.15.0 ws4py==0.5.1 zope.deprecation==5.0 zope.interface==6.0 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz ; sys.platform == "linux" setuptools<66 pyinstaller==4.10; sys.platform == "win32" diff --git a/requirements.txt b/requirements.txt index 82f60f1f0b..4db1d8a064 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,9 @@ charset-normalizer==3.1.0 click==8.1.7 craft-archives==1.1.3 craft-cli==1.2.0 -craft-grammar==1.1.1 -craft-parts==1.19.7 -craft-providers==1.20.2 +craft-grammar==1.1.2 +craft-parts==1.19.8 +craft-providers==1.20.4 craft-store==2.5.0 cryptography==40.0.2 Deprecated==1.2.14 @@ -55,7 +55,7 @@ PyYAML==6.0.2 raven==6.10.0 requests==2.30.0 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 SecretStorage==3.3.3 simplejson==3.19.2 six==1.16.0 @@ -66,9 +66,9 @@ toml==0.10.2 types-Deprecated==1.2.9.20240311 types-PyYAML==6.0.12.20240808 typing_extensions==4.5.0 -urllib3==1.26.19 +urllib3==2.2.3 wadllib==1.3.6 wrapt==1.15.0 ws4py==0.5.1 zipp==3.15.0 -python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz; sys.platform == "linux" +python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.0.1ubuntu0.20.04.1/python-apt_2.0.1ubuntu0.20.04.1.tar.xz ; sys.platform == "linux" From 8fec6cae182e1a1f78b0e16d902ea62a313237d4 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:27:17 -0400 Subject: [PATCH 108/151] build(deps): update requests for security issue --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 92e9651bd0..5070baab70 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -91,7 +91,7 @@ pytz==2023.3 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -requests==2.30.0 +requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 ruff==0.0.220 diff --git a/requirements.txt b/requirements.txt index 4db1d8a064..f7483945d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -53,7 +53,7 @@ pytz==2023.3 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -requests==2.30.0 +requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 SecretStorage==3.3.3 From e9f488239d6a5bfda5afc102693733b32f81bb57 Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Wed, 2 Oct 2024 17:42:59 -0400 Subject: [PATCH 109/151] build(deps): update pylxd --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 5070baab70..98ba127424 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -74,7 +74,7 @@ pyftpdlib==1.5.10 pylint==2.17.7 pylint-fixme-info==1.0.4 pylint-pytest==1.1.8 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.0.9 diff --git a/requirements.txt b/requirements.txt index f7483945d5..9cc2d2172a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,7 +42,7 @@ pycparser==2.21 pydantic==1.10.7 pydantic-yaml==0.11.2 pyelftools==0.29 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.0.9 From bf9a203df2e186920625a84b858d4364fac7ff67 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 10:37:03 -0500 Subject: [PATCH 110/151] tests: pin deps in electron-builder test Signed-off-by: Callahan Kovacs --- .../electron-builder-hello-world/package.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json b/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json index 83884ea31d..216ae8f342 100644 --- a/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json +++ b/tests/spread/electron-builder/no-template/electron-builder-hello-world/package.json @@ -17,9 +17,12 @@ ], "author": "GitHub", "license": "CC0-1.0", + "resolutions": { + "minimatch": "9.0.5" + }, "devDependencies": { - "electron": "latest", - "electron-builder": "latest", - "electron-installer-snap": "latest" + "electron": "31.3.1", + "electron-builder": "25.0.1", + "electron-installer-snap": "5.2.0" } } From 702ed0b11465b0acd6dd68a9f5ee45fae938046b Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 3 Oct 2024 14:41:51 -0400 Subject: [PATCH 111/151] fix(tests/legacy): don't send bad data --- tests/legacy/fake_servers/snapd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/legacy/fake_servers/snapd.py b/tests/legacy/fake_servers/snapd.py index 4616b28a32..be5c52a01f 100644 --- a/tests/legacy/fake_servers/snapd.py +++ b/tests/legacy/fake_servers/snapd.py @@ -53,7 +53,6 @@ def _handle_snaps(self): def _handle_snap_file(self, parsed_url): self.send_response(200) - self.send_header("Content-Length", len(parsed_url)) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(parsed_url.encode()) From 1a89a70a210acb32e5fad44aabc81eaa5951014e Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 07:55:42 -0500 Subject: [PATCH 112/151] refactor: rename snap_name Rename legacy variables to match snapd's definition: snap_name->snap_instance_name (possibly aliased) snap_store_name->snap_name (unaliased) Signed-off-by: Callahan Kovacs --- .../internal/build_providers/_snap.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/snapcraft_legacy/internal/build_providers/_snap.py b/snapcraft_legacy/internal/build_providers/_snap.py index db4b1cf101..bd85107353 100644 --- a/snapcraft_legacy/internal/build_providers/_snap.py +++ b/snapcraft_legacy/internal/build_providers/_snap.py @@ -58,9 +58,10 @@ def __init__( latest_revision: Optional[str], inject_from_host: bool = True ) -> None: - self.snap_name = snap_name - # the local snap name may have a suffix if it was installed with `--name` - self.snap_store_name = snap_name.split("_")[0] + # name of the snap instance, which may have an alias + self.snap_instance_name = snap_name + # name of the snap (no alias) + self.snap_name = snap_name.split("_")[0] self._remote_snap_dir = remote_snap_dir self._inject_from_host = inject_from_host @@ -74,7 +75,7 @@ def __init__( def _get_snap_repo(self): if self.__repo is None: - self.__repo = repo.snaps.SnapPackage(self.snap_name) + self.__repo = repo.snaps.SnapPackage(self.snap_instance_name) return self.__repo def get_op(self) -> _SnapOp: @@ -125,7 +126,7 @@ def get_op(self) -> _SnapOp: # This is a programmatic error raise RuntimeError( "Unhandled scenario for {!r} (host installed: {}, latest_revision {})".format( - self.snap_name, is_installed, self._latest_revision + self.snap_instance_name, is_installed, self._latest_revision ) ) @@ -136,9 +137,9 @@ def push_host_snap(self, *, file_pusher: Callable[..., None]) -> None: # TODO not being able to lock down on a snap revision can lead to races. host_snap_repo = self._get_snap_repo() with tempfile.TemporaryDirectory() as temp_dir: - snap_file_path = os.path.join(temp_dir, "{}.snap".format(self.snap_name)) + snap_file_path = os.path.join(temp_dir, "{}.snap".format(self.snap_instance_name)) assertion_file_path = os.path.join( - temp_dir, "{}.assert".format(self.snap_name) + temp_dir, "{}.assert".format(self.snap_instance_name) ) host_snap_repo.local_download( snap_path=snap_file_path, assertion_path=assertion_file_path @@ -171,7 +172,7 @@ def _set_data(self) -> None: switch_cmd = [ "snap", "switch", - self.snap_name, + self.snap_instance_name, "--channel", snap_channel, ] @@ -198,9 +199,9 @@ def _set_data(self) -> None: elif op == _SnapOp.INSTALL or op == _SnapOp.REFRESH: install_cmd = ["snap", op.name.lower()] - snap_channel = _get_snap_channel(self.snap_store_name) + snap_channel = _get_snap_channel(self.snap_name) - store_snap_info = storeapi.SnapAPI().get_info(self.snap_store_name) + store_snap_info = storeapi.SnapAPI().get_info(self.snap_name) snap_channel_map = store_snap_info.get_channel_mapping( risk=snap_channel.risk, track=snap_channel.track ) @@ -208,7 +209,7 @@ def _set_data(self) -> None: if snap_channel_map.confinement == "classic": install_cmd.append("--classic") install_cmd.extend(["--channel", snap_channel_map.channel_details.name]) - install_cmd.append(self.snap_store_name) + install_cmd.append(self.snap_name) self.__install_cmd = install_cmd self.__switch_cmd = switch_cmd @@ -224,7 +225,7 @@ def get_revision(self) -> str: # Shouldn't happen. raise RuntimeError( "Unhandled scenario for {!r} (revision {})".format( - self.snap_name, self.__revision + self.snap_instance_name, self.__revision ) ) @@ -238,7 +239,7 @@ def get_snap_install_cmd(self) -> List[str]: if self.__install_cmd is None: raise RuntimeError( "Unhandled scenario for {!r} (install_cmd {})".format( - self.snap_name, self.__install_cmd + self.snap_instance_name, self.__install_cmd ) ) @@ -259,7 +260,7 @@ def get_assertion_ack_cmd(self) -> List[str]: if self.__assertion_ack_cmd is None: raise RuntimeError( "Unhandled scenario for {!r} (assertion_ack_cmd {})".format( - self.snap_name, self.__assertion_ack_cmd + self.snap_instance_name, self.__assertion_ack_cmd ) ) @@ -379,7 +380,7 @@ def apply(self) -> None: return # Allow using snapd from the snapd snap to leverage newer snapd features. - if any(s.snap_name == "snapd" for s in self._snaps): + if any(s.snap_instance_name == "snapd" for s in self._snaps): self._enable_snapd_snap() # Disable refreshes so they do not interfere with installation ops. @@ -396,6 +397,6 @@ def apply(self) -> None: self._runner(snap.get_snap_install_cmd()) if snap.get_channel_switch_cmd() is not None: self._runner(snap.get_channel_switch_cmd()) - self._record_revision(snap.snap_store_name, snap.get_revision()) + self._record_revision(snap.snap_name, snap.get_revision()) _save_registry(self._registry_data, self._registry_filepath) From 119b92a5fd2df06ccd53831aa7965a8ecca3f580 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 08:51:30 -0500 Subject: [PATCH 113/151] fix(snapcraft_legacy): get assertions for aliased snaps Signed-off-by: Callahan Kovacs --- .../internal/build_providers/_snap.py | 6 +- snapcraft_legacy/internal/repo/snaps.py | 8 +- .../legacy/unit/build_providers/test_snap.py | 118 ++++++++++++++++++ tests/legacy/unit/repo/test_snaps.py | 42 +++++++ 4 files changed, 168 insertions(+), 6 deletions(-) diff --git a/snapcraft_legacy/internal/build_providers/_snap.py b/snapcraft_legacy/internal/build_providers/_snap.py index bd85107353..60191cd888 100644 --- a/snapcraft_legacy/internal/build_providers/_snap.py +++ b/snapcraft_legacy/internal/build_providers/_snap.py @@ -170,11 +170,7 @@ def _set_data(self) -> None: if not snap_revision.startswith("x") and snap_channel: switch_cmd = [ - "snap", - "switch", - self.snap_instance_name, - "--channel", - snap_channel, + "snap", "switch", self.snap_name, "--channel", snap_channel ] if snap_revision.startswith("x"): diff --git a/snapcraft_legacy/internal/repo/snaps.py b/snapcraft_legacy/internal/repo/snaps.py index 75e286c5fc..0a779b0550 100644 --- a/snapcraft_legacy/internal/repo/snaps.py +++ b/snapcraft_legacy/internal/repo/snaps.py @@ -179,7 +179,13 @@ def local_download(self, *, snap_path: str, assertion_path: str) -> None: # We write an empty assertions file for dangerous installs to # have a consistent interface. if self.has_assertions(): - assertions.append(["snap-declaration", "snap-name={}".format(self.name)]) + assertions.append( + [ + "snap-declaration", + # use the snap name without any alias + f"snap-name={self.name.partition('_')[0]}" + ] + ) assertions.append( [ "snap-revision", diff --git a/tests/legacy/unit/build_providers/test_snap.py b/tests/legacy/unit/build_providers/test_snap.py index 40bff35b19..762ad15c88 100644 --- a/tests/legacy/unit/build_providers/test_snap.py +++ b/tests/legacy/unit/build_providers/test_snap.py @@ -162,6 +162,124 @@ def test_snapcraft_installed_on_host_from_store(self): ), ) + def test_snapcraft_installed_on_host_aliased_from_store(self): + self.fake_snapd.snaps_result = [ + { + "name": "snapd", + "confinement": "strict", + "id": "2kkitQ", + "channel": "edge", + "revision": "1", + "tracking-channel": "latest/edge", + }, + { + "name": "core18", + "confinement": "strict", + "id": "2kkibb", + "channel": "stable", + "revision": "123", + "tracking-channel": "latest/beta", + }, + { + "name": "snapcraft_alias", + "confinement": "classic", + "id": "3lljuR", + "channel": "edge", + "revision": "345", + "tracking-channel": "latest/candidate", + }, + ] + self.get_assertion_mock.side_effect = [ + b"fake-assertion-account-store", + b"fake-assertion-declaration-snapd", + b"fake-assertion-revision-snapd-1", + b"fake-assertion-account-store", + b"fake-assertion-declaration-core18", + b"fake-assertion-revision-core18-123", + b"fake-assertion-account-store", + b"fake-assertion-declaration-snapcraft", + b"fake-assertion-revision-snapcraft-345", + ] + + snap_injector = SnapInjector( + registry_filepath=self.registry_filepath, + runner=self.provider._run, + file_pusher=self.provider._push_file, + ) + snap_injector.add("snapd") + snap_injector.add("core18") + snap_injector.add("snapcraft_alias") + snap_injector.apply() + + get_assertion_calls = [ + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=snapd"]), + call(["snap-revision", "snap-revision=1", "snap-id=2kkitQ"]), + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=core18"]), + call(["snap-revision", "snap-revision=123", "snap-id=2kkibb"]), + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=snapcraft"]), + call(["snap-revision", "snap-revision=345", "snap-id=3lljuR"]), + ] + self.get_assertion_mock.assert_has_calls(get_assertion_calls) + self.provider.run_mock.assert_has_calls( + [ + call(["snap", "set", "system", "experimental.snapd-snap=true"]), + call(["snap", "set", "system", ANY]), + call(["snap", "watch", "--last=auto-refresh?"]), + call(["snap", "ack", "/var/tmp/snapd.assert"]), + call(["snap", "install", "/var/tmp/snapd.snap"]), + call(["snap", "switch", "snapd", "--channel", "latest/edge"]), + call(["snap", "ack", "/var/tmp/core18.assert"]), + call(["snap", "install", "/var/tmp/core18.snap"]), + call(["snap", "switch", "core18", "--channel", "latest/beta"]), + call(["snap", "ack", "/var/tmp/snapcraft_alias.assert"]), + call(["snap", "install", "--classic", "/var/tmp/snapcraft_alias.snap"]), + call(["snap", "switch", "snapcraft", "--channel", "latest/candidate"]), + ] + ) + self.provider.push_file_mock.assert_has_calls( + [ + call(source=ANY, destination="/var/tmp/snapd.snap"), + call(source=ANY, destination="/var/tmp/snapd.assert"), + call(source=ANY, destination="/var/tmp/core18.snap"), + call(source=ANY, destination="/var/tmp/core18.assert"), + call(source=ANY, destination="/var/tmp/snapcraft_alias.snap"), + call(source=ANY, destination="/var/tmp/snapcraft_alias.assert"), + ] + ) + self.assertThat( + self.registry_filepath, + FileContains( + dedent( + """\ + core18: + - revision: '123' + snapcraft: + - revision: '345' + snapd: + - revision: '1' + """ + ) + ), + ) + def test_snapcraft_installed_on_host_from_store_but_injection_disabled(self): self.useFixture(fixture_setup.FakeStore()) diff --git a/tests/legacy/unit/repo/test_snaps.py b/tests/legacy/unit/repo/test_snaps.py index bf7f8ffcb6..13d813dbc3 100644 --- a/tests/legacy/unit/repo/test_snaps.py +++ b/tests/legacy/unit/repo/test_snaps.py @@ -348,6 +348,48 @@ def test_download_from_host(self): ] ) + def test_download_from_host_alias(self): + """Download an aliased snap from the host.""" + fake_get_assertion = fixtures.MockPatch( + "snapcraft_legacy.internal.repo.snaps.get_assertion", + return_value=b"foo-assert", + ) + self.useFixture(fake_get_assertion) + + self.fake_snapd.snaps_result = [ + { + "id": "fake-snap-id", + "name": "fake-snap_alias", + "channel": "stable", + "revision": "10", + } + ] + + snap_pkg = snaps.SnapPackage("fake-snap_alias/strict/stable") + snap_pkg.local_download( + snap_path="fake-snap.snap", assertion_path="fake-snap.assert" + ) + + self.assertThat("fake-snap.snap", FileExists()) + self.assertThat( + "fake-snap.assert", FileContains("foo-assert\nfoo-assert\nfoo-assert\n") + ) + fake_get_assertion.mock.assert_has_calls( + [ + mock.call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + # uses the non-aliased name + mock.call(["snap-declaration", "snap-name=fake-snap"]), + mock.call( + ["snap-revision", "snap-revision=10", "snap-id=fake-snap-id"] + ), + ] + ) + def test_download_from_host_dangerous(self): fake_get_assertion = fixtures.MockPatch( "snapcraft_legacy.internal.repo.snaps.get_assertion", From 5a42f0aa5196b18d47422f565af47ea3569b3f2d Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Fri, 4 Oct 2024 10:02:56 -0300 Subject: [PATCH 114/151] feat: add Snapcraft-specific Poetry plugin (#5090) The plugin has the same behavior and restrictions as the Python plugin with regards to base-dependent behavior, symlink handling, etc. Therefore, this common behavior is extracted into a new "python_common" module, used by both plugins. This approach is also taken for the reference docs - the requirement of staging a Python interpreter (or not) is the same for both plugins. Enabled for core22 and core24. Fixes #5025 --- docs/reference/plugins.rst | 1 + docs/reference/plugins/_python_common.rst | 17 ++++ docs/reference/plugins/poetry_plugin.rst | 7 ++ docs/reference/plugins/python_plugin.rst | 17 +--- snapcraft/application.py | 3 - snapcraft/parts/plugins/__init__.py | 2 + snapcraft/parts/plugins/poetry_plugin.py | 30 ++++++ snapcraft/parts/plugins/python_common.py | 96 +++++++++++++++++++ snapcraft/parts/plugins/python_plugin.py | 73 +------------- snapcraft/parts/plugins/register.py | 2 + snapcraft/services/lifecycle.py | 6 +- .../python-hello/poetry/snap/snapcraft.yaml | 14 +++ .../python-hello/poetry/src/pyproject.toml | 18 ++++ tests/spread/core24/python-hello/task.yaml | 1 + .../poetry-hello/snap/snapcraft.yaml | 15 +++ .../poetry-hello/src/hello/__init__.py | 2 + .../poetry-hello/src/pyproject.toml | 18 ++++ .../craft-parts/build-and-run-hello/task.yaml | 1 + .../unit/parts/plugins/test_python_plugin.py | 4 +- tests/unit/test_application.py | 11 --- 20 files changed, 233 insertions(+), 105 deletions(-) create mode 100644 docs/reference/plugins/_python_common.rst create mode 100644 docs/reference/plugins/poetry_plugin.rst create mode 100644 snapcraft/parts/plugins/poetry_plugin.py create mode 100644 snapcraft/parts/plugins/python_common.py create mode 100644 tests/spread/core24/python-hello/poetry/snap/snapcraft.yaml create mode 100644 tests/spread/core24/python-hello/poetry/src/pyproject.toml create mode 100644 tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/snap/snapcraft.yaml create mode 100644 tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/hello/__init__.py create mode 100644 tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/pyproject.toml diff --git a/docs/reference/plugins.rst b/docs/reference/plugins.rst index d00ecde67c..9b9e76368f 100644 --- a/docs/reference/plugins.rst +++ b/docs/reference/plugins.rst @@ -21,6 +21,7 @@ Snapcraft. /common/craft-parts/reference/plugins/meson_plugin /common/craft-parts/reference/plugins/nil_plugin /common/craft-parts/reference/plugins/npm_plugin + plugins/poetry_plugin plugins/python_plugin /common/craft-parts/reference/plugins/qmake_plugin /common/craft-parts/reference/plugins/rust_plugin diff --git a/docs/reference/plugins/_python_common.rst b/docs/reference/plugins/_python_common.rst new file mode 100644 index 0000000000..f894647c10 --- /dev/null +++ b/docs/reference/plugins/_python_common.rst @@ -0,0 +1,17 @@ + +Dependencies +------------ + +Whether the Python interpreter needs to be included in the snap depends on its +``confinement``. Specifically: + +- Projects with ``strict`` or ``devmode`` confinement can safely use the base + snap's interpreter, so they typically do **not** need to include Python. +- Projects with ``classic`` confinement **cannot** use the base snap's + interpreter and thus must always bundle it (typically via ``stage-packages``). +- In both cases, a specific/custom Python installation can always be included + in the snap. This can be useful, for example, when using a different Python + version or building an interpreter with custom flags. + +Snapcraft will prefer an included interpreter over the base's, even for projects +with ``strict`` and ``devmode`` confinement. diff --git a/docs/reference/plugins/poetry_plugin.rst b/docs/reference/plugins/poetry_plugin.rst new file mode 100644 index 0000000000..0823c4e9ba --- /dev/null +++ b/docs/reference/plugins/poetry_plugin.rst @@ -0,0 +1,7 @@ +.. include:: /common/craft-parts/reference/plugins/poetry_plugin.rst + :end-before: .. _poetry-details-begin: + +.. include:: _python_common.rst + +.. include:: /common/craft-parts/reference/plugins/poetry_plugin.rst + :start-after: .. _poetry-details-end: diff --git a/docs/reference/plugins/python_plugin.rst b/docs/reference/plugins/python_plugin.rst index f0cf5e76b5..eeeb5ce1ac 100644 --- a/docs/reference/plugins/python_plugin.rst +++ b/docs/reference/plugins/python_plugin.rst @@ -2,22 +2,7 @@ .. include:: /common/craft-parts/reference/plugins/python_plugin.rst :end-before: .. _python-details-begin: -Dependencies ------------- - -Whether the Python interpreter needs to be included in the snap depends on its -``confinement``. Specifically: - -- Projects with ``strict`` or ``devmode`` confinement can safely use the base - snap's interpreter, so they typically do **not** need to include Python. -- Projects with ``classic`` confinement **cannot** use the base snap's - interpreter and thus must always bundle it (typically via ``stage-packages``). -- In both cases, a specific/custom Python installation can always be included - in the snap. This can be useful, for example, when using a different Python - version or building an interpreter with custom flags. - -Snapcraft will prefer an included interpreter over the base's, even for projects -with ``strict`` and ``devmode`` confinement. +.. include:: _python_common.rst .. include:: /common/craft-parts/reference/plugins/python_plugin.rst :start-after: .. _python-details-end: diff --git a/snapcraft/application.py b/snapcraft/application.py index df84454fa8..f8fd99f980 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -135,9 +135,6 @@ def _register_default_plugins(self) -> None: """Register per application plugins when initializing.""" super()._register_default_plugins() - # poetry plugin needs integration work, see #5025 - craft_parts.plugins.unregister("poetry") - if self._known_core24: # dotnet is disabled for core24 and newer because it is pending a rewrite craft_parts.plugins.unregister("dotnet") diff --git a/snapcraft/parts/plugins/__init__.py b/snapcraft/parts/plugins/__init__.py index 6104dfed0c..b641244c53 100644 --- a/snapcraft/parts/plugins/__init__.py +++ b/snapcraft/parts/plugins/__init__.py @@ -22,6 +22,7 @@ from .flutter_plugin import FlutterPlugin from .kernel_plugin import KernelPlugin from .matter_sdk_plugin import MatterSdkPlugin +from .poetry_plugin import PoetryPlugin from .python_plugin import PythonPlugin from .register import get_plugins, register @@ -31,6 +32,7 @@ "FlutterPlugin", "MatterSdkPlugin", "KernelPlugin", + "PoetryPlugin", "PythonPlugin", "get_plugins", "register", diff --git a/snapcraft/parts/plugins/poetry_plugin.py b/snapcraft/parts/plugins/poetry_plugin.py new file mode 100644 index 0000000000..efb3804edf --- /dev/null +++ b/snapcraft/parts/plugins/poetry_plugin.py @@ -0,0 +1,30 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""The Snapcraft Poetry plugin.""" + +from craft_parts.plugins import poetry_plugin +from overrides import override + +from snapcraft.parts.plugins import python_common + + +class PoetryPlugin(poetry_plugin.PoetryPlugin): + """A Poetry plugin for Snapcraft.""" + + @override + def _get_system_python_interpreter(self) -> str | None: + return python_common.get_system_interpreter(self._part_info) diff --git a/snapcraft/parts/plugins/python_common.py b/snapcraft/parts/plugins/python_common.py new file mode 100644 index 0000000000..f9c4a7e525 --- /dev/null +++ b/snapcraft/parts/plugins/python_common.py @@ -0,0 +1,96 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2023-2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Common functionality for Python-based plugins. + +This plugin extends Craft-parts' vanilla Python plugin to properly +set the Python interpreter according to the Snapcraft base and +confinement parameters. +""" + +import logging +from pathlib import Path + +from craft_parts import PartInfo, StepInfo, errors + +logger = logging.getLogger(__name__) + + +_CONFINED_PYTHON_PATH = { + "core22": "/usr/bin/python3.10", + "core24": "/usr/bin/python3.12", +} + + +def get_system_interpreter(part_info: PartInfo) -> str | None: + """Obtain the path to the system-provided python interpreter. + + :param part_info: The info of the part that is being built. + """ + base = part_info.project_base + confinement = part_info.confinement + + if confinement == "classic" or base == "bare": + # classic snaps, and snaps without bases, must always provision Python + interpreter = None + else: + # otherwise, we should always know which Python is present on the + # base. If this fails on a new base, update _CONFINED_PYTHON_PATH + interpreter = _CONFINED_PYTHON_PATH.get(base) + if interpreter is None: + brief = f"Don't know which interpreter to use for base {base}." + resolution = "Please contact the Snapcraft team." + raise errors.PartsError(brief=brief, resolution=resolution) + + logger.debug( + "Using python interpreter '%s' for base '%s', confinement '%s'", + interpreter, + base, + confinement, + ) + return interpreter + + +def post_prime(step_info: StepInfo) -> None: + """Perform Python-specific actions right before packing.""" + base = step_info.project_base + + if base in ("core20", "core22"): + # Only fix pyvenv.cfg on core24+ snaps + return + + root_path: Path = step_info.prime_dir + + pyvenv = root_path / "pyvenv.cfg" + if not pyvenv.is_file(): + return + + snap_path = Path(f"/snap/{step_info.project_name}/current") + new_home = f"home = {snap_path}" + + candidates = ( + step_info.part_install_dir, + step_info.stage_dir, + ) + + old_contents = contents = pyvenv.read_text() + for candidate in candidates: + old_home = f"home = {candidate}" + contents = contents.replace(old_home, new_home) + + if old_contents != contents: + logger.debug("Updating pyvenv.cfg to:\n%s", contents) + pyvenv.write_text(contents) diff --git a/snapcraft/parts/plugins/python_plugin.py b/snapcraft/parts/plugins/python_plugin.py index df20f19871..63a306f85d 100644 --- a/snapcraft/parts/plugins/python_plugin.py +++ b/snapcraft/parts/plugins/python_plugin.py @@ -16,84 +16,17 @@ """The Snapcraft Python plugin.""" -import logging -from pathlib import Path from typing import Optional -from craft_parts import StepInfo, errors from craft_parts.plugins import python_plugin from overrides import override -logger = logging.getLogger(__name__) - - -_CONFINED_PYTHON_PATH = { - "core22": "/usr/bin/python3.10", - "core24": "/usr/bin/python3.12", -} +from snapcraft.parts.plugins import python_common class PythonPlugin(python_plugin.PythonPlugin): - """A Python plugin for Snapcraft. - - This plugin extends Craft-parts' vanilla Python plugin to properly - set the Python interpreter according to the Snapcraft base and - confinement parameters. - """ + """A Python plugin for Snapcraft.""" @override def _get_system_python_interpreter(self) -> Optional[str]: - base = self._part_info.project_base - confinement = self._part_info.confinement - - if confinement == "classic" or base == "bare": - # classic snaps, and snaps without bases, must always provision Python - interpreter = None - else: - # otherwise, we should always know which Python is present on the - # base. If this fails on a new base, update _CONFINED_PYTHON_PATH - interpreter = _CONFINED_PYTHON_PATH.get(base) - if interpreter is None: - brief = f"Don't know which interpreter to use for base {base}." - resolution = "Please contact the Snapcraft team." - raise errors.PartsError(brief=brief, resolution=resolution) - - logger.debug( - "Using python interpreter '%s' for base '%s', confinement '%s'", - interpreter, - base, - confinement, - ) - return interpreter - - @classmethod - def post_prime(cls, step_info: StepInfo) -> None: - """Perform Python-specific actions right before packing.""" - base = step_info.project_base - - if base in ("core20", "core22"): - # Only fix pyvenv.cfg on core24+ snaps - return - - root_path: Path = step_info.prime_dir - - pyvenv = root_path / "pyvenv.cfg" - if not pyvenv.is_file(): - return - - snap_path = Path(f"/snap/{step_info.project_name}/current") - new_home = f"home = {snap_path}" - - candidates = ( - step_info.part_install_dir, - step_info.stage_dir, - ) - - old_contents = contents = pyvenv.read_text() - for candidate in candidates: - old_home = f"home = {candidate}" - contents = contents.replace(old_home, new_home) - - if old_contents != contents: - logger.debug("Updating pyvenv.cfg to:\n%s", contents) - pyvenv.write_text(contents) + return python_common.get_system_interpreter(self._part_info) diff --git a/snapcraft/parts/plugins/register.py b/snapcraft/parts/plugins/register.py index 5d578462a0..7f8b56c6c9 100644 --- a/snapcraft/parts/plugins/register.py +++ b/snapcraft/parts/plugins/register.py @@ -24,6 +24,7 @@ from .flutter_plugin import FlutterPlugin from .kernel_plugin import KernelPlugin from .matter_sdk_plugin import MatterSdkPlugin +from .poetry_plugin import PoetryPlugin from .python_plugin import PythonPlugin @@ -38,6 +39,7 @@ def get_plugins(core22: bool) -> dict[str, PluginType]: "flutter": FlutterPlugin, "python": PythonPlugin, "matter-sdk": MatterSdkPlugin, + "poetry": PoetryPlugin, } if core22: diff --git a/snapcraft/services/lifecycle.py b/snapcraft/services/lifecycle.py index 61f8f95472..f093468280 100644 --- a/snapcraft/services/lifecycle.py +++ b/snapcraft/services/lifecycle.py @@ -86,7 +86,7 @@ def setup(self) -> None: @overrides def post_prime(self, step_info: StepInfo) -> bool: """Run post-prime parts steps for Snapcraft.""" - from snapcraft.parts import plugins + from snapcraft.parts.plugins import python_common project = cast(models.Project, self._project) @@ -94,8 +94,8 @@ def post_prime(self, step_info: StepInfo) -> bool: plugin_name = project.parts[part_name]["plugin"] # Handle plugin-specific prime fixes - if plugin_name == "python": - plugins.PythonPlugin.post_prime(step_info) + if plugin_name in ("python", "poetry"): + python_common.post_prime(step_info) # Handle patch-elf diff --git a/tests/spread/core24/python-hello/poetry/snap/snapcraft.yaml b/tests/spread/core24/python-hello/poetry/snap/snapcraft.yaml new file mode 100644 index 0000000000..95d5a63e74 --- /dev/null +++ b/tests/spread/core24/python-hello/poetry/snap/snapcraft.yaml @@ -0,0 +1,14 @@ +name: python-hello-poetry +version: "1.0" +summary: simple python application +description: build a python application using core24 +base: core24 +confinement: strict + +apps: + python-hello-poetry: + command: bin/hello +parts: + hello: + plugin: poetry + source: src diff --git a/tests/spread/core24/python-hello/poetry/src/pyproject.toml b/tests/spread/core24/python-hello/poetry/src/pyproject.toml new file mode 100644 index 0000000000..eef363c29b --- /dev/null +++ b/tests/spread/core24/python-hello/poetry/src/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "hello" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.10" +black = "^24.8.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +hello = "hello:main" diff --git a/tests/spread/core24/python-hello/task.yaml b/tests/spread/core24/python-hello/task.yaml index 2db4043b9e..47a434462b 100644 --- a/tests/spread/core24/python-hello/task.yaml +++ b/tests/spread/core24/python-hello/task.yaml @@ -8,6 +8,7 @@ systems: environment: PARAM/strict: "" PARAM/classic: "--classic" + PARAM/poetry: "" restore: | cd ./"${SPREAD_VARIANT}" diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/snap/snapcraft.yaml b/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/snap/snapcraft.yaml new file mode 100644 index 0000000000..b1d101bb25 --- /dev/null +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/snap/snapcraft.yaml @@ -0,0 +1,15 @@ +name: poetry-hello +version: "1.0" +summary: simple python application +description: build a python application using core22 +base: core22 +confinement: strict + +apps: + poetry-hello: + command: bin/hello + +parts: + hello: + plugin: poetry + source: src diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/hello/__init__.py b/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/hello/__init__.py new file mode 100644 index 0000000000..e3095b2229 --- /dev/null +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/hello/__init__.py @@ -0,0 +1,2 @@ +def main(): + print("hello world") diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/pyproject.toml b/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/pyproject.toml new file mode 100644 index 0000000000..eef363c29b --- /dev/null +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/poetry-hello/src/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "hello" +version = "0.1.0" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.10" +black = "^24.8.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +hello = "hello:main" diff --git a/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml b/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml index bbb9bbea02..01b9eb9aa2 100644 --- a/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml +++ b/tests/spread/plugins/craft-parts/build-and-run-hello/task.yaml @@ -8,6 +8,7 @@ environment: SNAP/colcon_ros2_wrapper: colcon-ros2-wrapper SNAP/flutter: flutter-hello SNAP/python: python-hello + SNAP/poetry: poetry-hello SNAP/qmake: qmake-hello SNAP/maven: maven-hello SNAP/dotnet: dotnet-hello diff --git a/tests/unit/parts/plugins/test_python_plugin.py b/tests/unit/parts/plugins/test_python_plugin.py index d41f6d295a..ed357cd3fe 100644 --- a/tests/unit/parts/plugins/test_python_plugin.py +++ b/tests/unit/parts/plugins/test_python_plugin.py @@ -19,7 +19,7 @@ import pytest from craft_parts import Part, PartInfo, ProjectInfo, Step, StepInfo, errors -from snapcraft.parts.plugins import PythonPlugin +from snapcraft.parts.plugins import PythonPlugin, python_common @pytest.fixture @@ -224,7 +224,7 @@ def test_fix_pyvenv(new_dir, home_attr): step_info = StepInfo(part_info, Step.PRIME) - PythonPlugin.post_prime(step_info) + python_common.post_prime(step_info) new_contents = pyvenv.read_text() assert "home = /snap/test-snap/current/usr/bin" in new_contents diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py index ad1dab8a0b..ea86a2b518 100644 --- a/tests/unit/test_application.py +++ b/tests/unit/test_application.py @@ -355,17 +355,6 @@ def test_application_dotnet_not_registered(base, build_base, snapcraft_yaml): assert "dotnet" not in craft_parts.plugins.get_registered_plugins() -@pytest.mark.parametrize("base", const.CURRENT_BASES) -def test_application_poetry_not_registered(base, snapcraft_yaml): - """poetry plugin is disabled for all bases.""" - snapcraft_yaml(base=base) - app = application.create_app() - - app._register_default_plugins() - - assert "poetry" not in craft_parts.plugins.get_registered_plugins() - - def test_default_command_integrated(monkeypatch, mocker, new_dir): """Test that for core24 projects we accept "pack" as the default command.""" From 495c41f0441fc375e95b9a8e3a14e5880c5d7caa Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 08:48:19 -0500 Subject: [PATCH 115/151] build(deps): point craft-providers to 2.0.3 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 0b60799231..f9daa0d898 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -30,7 +30,7 @@ craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 -craft-providers==2.0.1 +craft-providers==2.0.3 craft-store==3.0.2 cryptography==43.0.1 cssutils==2.11.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 31ce1e69fe..06ab3d0070 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -25,7 +25,7 @@ craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 -craft-providers==2.0.1 +craft-providers==2.0.3 craft-store==3.0.2 cryptography==43.0.1 cssutils==2.11.1 diff --git a/requirements.txt b/requirements.txt index b0d657e27a..f6eac1072b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.1 craft-platforms==0.1.1 -craft-providers==2.0.1 +craft-providers==2.0.3 craft-store==3.0.2 cryptography==43.0.1 distro==1.9.0 diff --git a/setup.py b/setup.py index f81a57187d..351f5a9379 100755 --- a/setup.py +++ b/setup.py @@ -104,7 +104,7 @@ def recursive_data_files(directory, install_directory): "craft-grammar>=2.0.1,<3.0.0", "craft-parts~=2.1", "craft-platforms~=0.1", - "craft-providers~=2.0", + "craft-providers>=2.0.3,<3.0.0", "craft-store>=3.0.2,<4.0.0", "docutils<0.20", # Frozen until we can update sphinx dependencies. "gnupg", From 78af8ea0b7859e090a886b627de463260f6d9a66 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 07:55:42 -0500 Subject: [PATCH 116/151] refactor: rename snap_name Rename legacy variables to match snapd's definition: snap_name->snap_instance_name (possibly aliased) snap_store_name->snap_name (unaliased) Signed-off-by: Callahan Kovacs --- .../internal/build_providers/_snap.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/snapcraft_legacy/internal/build_providers/_snap.py b/snapcraft_legacy/internal/build_providers/_snap.py index 7bbb8b0063..6c56822670 100644 --- a/snapcraft_legacy/internal/build_providers/_snap.py +++ b/snapcraft_legacy/internal/build_providers/_snap.py @@ -58,9 +58,10 @@ def __init__( latest_revision: Optional[str], inject_from_host: bool = True ) -> None: - self.snap_name = snap_name - # the local snap name may have a suffix if it was installed with `--name` - self.snap_store_name = snap_name.split("_")[0] + # name of the snap instance, which may have an alias + self.snap_instance_name = snap_name + # name of the snap (no alias) + self.snap_name = snap_name.split("_")[0] self._remote_snap_dir = remote_snap_dir self._inject_from_host = inject_from_host @@ -74,7 +75,7 @@ def __init__( def _get_snap_repo(self): if self.__repo is None: - self.__repo = repo.snaps.SnapPackage(self.snap_name) + self.__repo = repo.snaps.SnapPackage(self.snap_instance_name) return self.__repo def get_op(self) -> _SnapOp: @@ -125,7 +126,7 @@ def get_op(self) -> _SnapOp: # This is a programmatic error raise RuntimeError( "Unhandled scenario for {!r} (host installed: {}, latest_revision {})".format( - self.snap_name, is_installed, self._latest_revision + self.snap_instance_name, is_installed, self._latest_revision ) ) @@ -136,9 +137,9 @@ def push_host_snap(self, *, file_pusher: Callable[..., None]) -> None: # TODO not being able to lock down on a snap revision can lead to races. host_snap_repo = self._get_snap_repo() with tempfile.TemporaryDirectory() as temp_dir: - snap_file_path = os.path.join(temp_dir, "{}.snap".format(self.snap_name)) + snap_file_path = os.path.join(temp_dir, "{}.snap".format(self.snap_instance_name)) assertion_file_path = os.path.join( - temp_dir, "{}.assert".format(self.snap_name) + temp_dir, "{}.assert".format(self.snap_instance_name) ) host_snap_repo.local_download( snap_path=snap_file_path, assertion_path=assertion_file_path @@ -171,7 +172,7 @@ def _set_data(self) -> None: switch_cmd = [ "snap", "switch", - self.snap_name, + self.snap_instance_name, "--channel", snap_channel, ] @@ -198,9 +199,9 @@ def _set_data(self) -> None: elif op == _SnapOp.INSTALL or op == _SnapOp.REFRESH: install_cmd = ["snap", op.name.lower()] - snap_channel = _get_snap_channel(self.snap_store_name) + snap_channel = _get_snap_channel(self.snap_name) - store_snap_info = storeapi.SnapAPI().get_info(self.snap_store_name) + store_snap_info = storeapi.SnapAPI().get_info(self.snap_name) snap_channel_map = store_snap_info.get_channel_mapping( risk=snap_channel.risk, track=snap_channel.track ) @@ -208,7 +209,7 @@ def _set_data(self) -> None: if snap_channel_map.confinement == "classic": install_cmd.append("--classic") install_cmd.extend(["--channel", snap_channel_map.channel_details.name]) - install_cmd.append(self.snap_store_name) + install_cmd.append(self.snap_name) self.__install_cmd = install_cmd self.__switch_cmd = switch_cmd @@ -224,7 +225,7 @@ def get_revision(self) -> str: # Shouldn't happen. raise RuntimeError( "Unhandled scenario for {!r} (revision {})".format( - self.snap_name, self.__revision + self.snap_instance_name, self.__revision ) ) @@ -238,7 +239,7 @@ def get_snap_install_cmd(self) -> List[str]: if self.__install_cmd is None: raise RuntimeError( "Unhandled scenario for {!r} (install_cmd {})".format( - self.snap_name, self.__install_cmd + self.snap_instance_name, self.__install_cmd ) ) @@ -259,7 +260,7 @@ def get_assertion_ack_cmd(self) -> List[str]: if self.__assertion_ack_cmd is None: raise RuntimeError( "Unhandled scenario for {!r} (assertion_ack_cmd {})".format( - self.snap_name, self.__assertion_ack_cmd + self.snap_instance_name, self.__assertion_ack_cmd ) ) @@ -379,7 +380,7 @@ def apply(self) -> None: return # Allow using snapd from the snapd snap to leverage newer snapd features. - if any(s.snap_name == "snapd" for s in self._snaps): + if any(s.snap_instance_name == "snapd" for s in self._snaps): self._enable_snapd_snap() # Disable refreshes so they do not interfere with installation ops. @@ -396,6 +397,6 @@ def apply(self) -> None: self._runner(snap.get_snap_install_cmd()) if snap.get_channel_switch_cmd() is not None: self._runner(snap.get_channel_switch_cmd()) - self._record_revision(snap.snap_store_name, snap.get_revision()) + self._record_revision(snap.snap_name, snap.get_revision()) _save_registry(self._registry_data, self._registry_filepath) From 6c18404b365d392e5d72254f1cd3242e5329162b Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 3 Oct 2024 08:51:30 -0500 Subject: [PATCH 117/151] fix(snapcraft_legacy): get assertions for aliased snaps Signed-off-by: Callahan Kovacs --- .../internal/build_providers/_snap.py | 6 +- snapcraft_legacy/internal/repo/snaps.py | 8 +- .../legacy/unit/build_providers/test_snap.py | 118 ++++++++++++++++++ tests/legacy/unit/repo/test_snaps.py | 42 +++++++ 4 files changed, 168 insertions(+), 6 deletions(-) diff --git a/snapcraft_legacy/internal/build_providers/_snap.py b/snapcraft_legacy/internal/build_providers/_snap.py index 6c56822670..28a9d757f1 100644 --- a/snapcraft_legacy/internal/build_providers/_snap.py +++ b/snapcraft_legacy/internal/build_providers/_snap.py @@ -170,11 +170,7 @@ def _set_data(self) -> None: if not snap_revision.startswith("x") and snap_channel: switch_cmd = [ - "snap", - "switch", - self.snap_instance_name, - "--channel", - snap_channel, + "snap", "switch", self.snap_name, "--channel", snap_channel ] if snap_revision.startswith("x"): diff --git a/snapcraft_legacy/internal/repo/snaps.py b/snapcraft_legacy/internal/repo/snaps.py index 75e286c5fc..0a779b0550 100644 --- a/snapcraft_legacy/internal/repo/snaps.py +++ b/snapcraft_legacy/internal/repo/snaps.py @@ -179,7 +179,13 @@ def local_download(self, *, snap_path: str, assertion_path: str) -> None: # We write an empty assertions file for dangerous installs to # have a consistent interface. if self.has_assertions(): - assertions.append(["snap-declaration", "snap-name={}".format(self.name)]) + assertions.append( + [ + "snap-declaration", + # use the snap name without any alias + f"snap-name={self.name.partition('_')[0]}" + ] + ) assertions.append( [ "snap-revision", diff --git a/tests/legacy/unit/build_providers/test_snap.py b/tests/legacy/unit/build_providers/test_snap.py index 40bff35b19..762ad15c88 100644 --- a/tests/legacy/unit/build_providers/test_snap.py +++ b/tests/legacy/unit/build_providers/test_snap.py @@ -162,6 +162,124 @@ def test_snapcraft_installed_on_host_from_store(self): ), ) + def test_snapcraft_installed_on_host_aliased_from_store(self): + self.fake_snapd.snaps_result = [ + { + "name": "snapd", + "confinement": "strict", + "id": "2kkitQ", + "channel": "edge", + "revision": "1", + "tracking-channel": "latest/edge", + }, + { + "name": "core18", + "confinement": "strict", + "id": "2kkibb", + "channel": "stable", + "revision": "123", + "tracking-channel": "latest/beta", + }, + { + "name": "snapcraft_alias", + "confinement": "classic", + "id": "3lljuR", + "channel": "edge", + "revision": "345", + "tracking-channel": "latest/candidate", + }, + ] + self.get_assertion_mock.side_effect = [ + b"fake-assertion-account-store", + b"fake-assertion-declaration-snapd", + b"fake-assertion-revision-snapd-1", + b"fake-assertion-account-store", + b"fake-assertion-declaration-core18", + b"fake-assertion-revision-core18-123", + b"fake-assertion-account-store", + b"fake-assertion-declaration-snapcraft", + b"fake-assertion-revision-snapcraft-345", + ] + + snap_injector = SnapInjector( + registry_filepath=self.registry_filepath, + runner=self.provider._run, + file_pusher=self.provider._push_file, + ) + snap_injector.add("snapd") + snap_injector.add("core18") + snap_injector.add("snapcraft_alias") + snap_injector.apply() + + get_assertion_calls = [ + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=snapd"]), + call(["snap-revision", "snap-revision=1", "snap-id=2kkitQ"]), + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=core18"]), + call(["snap-revision", "snap-revision=123", "snap-id=2kkibb"]), + call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + call(["snap-declaration", "snap-name=snapcraft"]), + call(["snap-revision", "snap-revision=345", "snap-id=3lljuR"]), + ] + self.get_assertion_mock.assert_has_calls(get_assertion_calls) + self.provider.run_mock.assert_has_calls( + [ + call(["snap", "set", "system", "experimental.snapd-snap=true"]), + call(["snap", "set", "system", ANY]), + call(["snap", "watch", "--last=auto-refresh?"]), + call(["snap", "ack", "/var/tmp/snapd.assert"]), + call(["snap", "install", "/var/tmp/snapd.snap"]), + call(["snap", "switch", "snapd", "--channel", "latest/edge"]), + call(["snap", "ack", "/var/tmp/core18.assert"]), + call(["snap", "install", "/var/tmp/core18.snap"]), + call(["snap", "switch", "core18", "--channel", "latest/beta"]), + call(["snap", "ack", "/var/tmp/snapcraft_alias.assert"]), + call(["snap", "install", "--classic", "/var/tmp/snapcraft_alias.snap"]), + call(["snap", "switch", "snapcraft", "--channel", "latest/candidate"]), + ] + ) + self.provider.push_file_mock.assert_has_calls( + [ + call(source=ANY, destination="/var/tmp/snapd.snap"), + call(source=ANY, destination="/var/tmp/snapd.assert"), + call(source=ANY, destination="/var/tmp/core18.snap"), + call(source=ANY, destination="/var/tmp/core18.assert"), + call(source=ANY, destination="/var/tmp/snapcraft_alias.snap"), + call(source=ANY, destination="/var/tmp/snapcraft_alias.assert"), + ] + ) + self.assertThat( + self.registry_filepath, + FileContains( + dedent( + """\ + core18: + - revision: '123' + snapcraft: + - revision: '345' + snapd: + - revision: '1' + """ + ) + ), + ) + def test_snapcraft_installed_on_host_from_store_but_injection_disabled(self): self.useFixture(fixture_setup.FakeStore()) diff --git a/tests/legacy/unit/repo/test_snaps.py b/tests/legacy/unit/repo/test_snaps.py index bf7f8ffcb6..13d813dbc3 100644 --- a/tests/legacy/unit/repo/test_snaps.py +++ b/tests/legacy/unit/repo/test_snaps.py @@ -348,6 +348,48 @@ def test_download_from_host(self): ] ) + def test_download_from_host_alias(self): + """Download an aliased snap from the host.""" + fake_get_assertion = fixtures.MockPatch( + "snapcraft_legacy.internal.repo.snaps.get_assertion", + return_value=b"foo-assert", + ) + self.useFixture(fake_get_assertion) + + self.fake_snapd.snaps_result = [ + { + "id": "fake-snap-id", + "name": "fake-snap_alias", + "channel": "stable", + "revision": "10", + } + ] + + snap_pkg = snaps.SnapPackage("fake-snap_alias/strict/stable") + snap_pkg.local_download( + snap_path="fake-snap.snap", assertion_path="fake-snap.assert" + ) + + self.assertThat("fake-snap.snap", FileExists()) + self.assertThat( + "fake-snap.assert", FileContains("foo-assert\nfoo-assert\nfoo-assert\n") + ) + fake_get_assertion.mock.assert_has_calls( + [ + mock.call( + [ + "account-key", + "public-key-sha3-384=BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3m8ul", + ] + ), + # uses the non-aliased name + mock.call(["snap-declaration", "snap-name=fake-snap"]), + mock.call( + ["snap-revision", "snap-revision=10", "snap-id=fake-snap-id"] + ), + ] + ) + def test_download_from_host_dangerous(self): fake_get_assertion = fixtures.MockPatch( "snapcraft_legacy.internal.repo.snaps.get_assertion", From b746c6c8cda56b4c18b0079210b550949484e99a Mon Sep 17 00:00:00 2001 From: Callahan Date: Fri, 4 Oct 2024 15:31:17 -0500 Subject: [PATCH 118/151] tests: add spread tests for registries (#5091) Add a spread test for `edit-registries` and `list-registries`. Fixes #5054 Signed-off-by: Callahan Kovacs --- tests/spread/store/registries/editor.sh | 12 ++++++ tests/spread/store/registries/task.yaml | 42 ++++++++++++++++++++ tests/spread/store/validation-sets/task.yaml | 4 +- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100755 tests/spread/store/registries/editor.sh create mode 100644 tests/spread/store/registries/task.yaml diff --git a/tests/spread/store/registries/editor.sh b/tests/spread/store/registries/editor.sh new file mode 100755 index 0000000000..46b07515a4 --- /dev/null +++ b/tests/spread/store/registries/editor.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +registries_file="$1" + +# flip-flop between 'access' being read and write +if grep -q "^ *access:.*read" "$registries_file"; then + access="write" +else + access="read" +fi + +sed -i "s/^\([[:space:]]*\)access:.*/\1access: $access/g" "$registries_file" diff --git a/tests/spread/store/registries/task.yaml b/tests/spread/store/registries/task.yaml new file mode 100644 index 0000000000..8441a863c2 --- /dev/null +++ b/tests/spread/store/registries/task.yaml @@ -0,0 +1,42 @@ +summary: test the registries commands + +environment: + SNAPCRAFT_ASSERTION_KEY: "$(HOST: echo ${SNAPCRAFT_ASSERTION_KEY})" + SNAPCRAFT_STORE_CREDENTIALS: "$(HOST: echo ${SNAPCRAFT_STORE_CREDENTIALS_STAGING})" + +prepare: | + if [[ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]]; then + ERROR "No credentials set in env SNAPCRAFT_STORE_CREDENTIALS" + fi + + if [[ -z "$SNAPCRAFT_ASSERTION_KEY" ]]; then + ERROR "No gpg key set in env SNAPCRAFT_ASSERTION_KEY" + fi + + # setup snap gpg dir + mkdir -p "$HOME/.snap/gnupg" + chmod 700 "$HOME/.snap/gnupg" + + # import a registered key + gpg --homedir "$HOME/.snap/gnupg" --import <(echo "$SNAPCRAFT_ASSERTION_KEY" | base64 --decode) + + snap install yq + # registries only available in edge + snap refresh snapd --edge + +execute: | + # ensure snapcraft is logged in and can access the store + snapcraft whoami + + # snapcraft will use a fake file editor + export EDITOR="$PWD/editor.sh" + + snapcraft edit-registries "$(snapcraft whoami | yq .id)" testset --key-name testspreadkey + + snapcraft list-registries | MATCH testset + +restore: | + rm -rf "$HOME/.snap/gnupg" + + snap remove --purge yq + snap refresh snapd --stable diff --git a/tests/spread/store/validation-sets/task.yaml b/tests/spread/store/validation-sets/task.yaml index 277ee9d8e1..599f01aad6 100644 --- a/tests/spread/store/validation-sets/task.yaml +++ b/tests/spread/store/validation-sets/task.yaml @@ -18,9 +18,7 @@ prepare: | chmod 700 "$HOME/.snap/gnupg" # import a registered key - echo "$SNAPCRAFT_ASSERTION_KEY" | base64 --decode > store-key.txt - gpg --homedir "$HOME/.snap/gnupg" --import store-key.txt - rm -f store-key.txt + gpg --homedir "$HOME/.snap/gnupg" --import <(echo "$SNAPCRAFT_ASSERTION_KEY" | base64 --decode) snap install yq From 4df8089130e91be32eb82ade148f563dc0a8092f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:34:32 -0500 Subject: [PATCH 119/151] build(deps): update dependency requests to v2.32.2 [security] (hotfix/7.5) (#5065) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/.sphinx/pinned-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index d35ef8783b..547fdea834 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -16,7 +16,7 @@ pyenchant==3.2.2 Pygments==2.13.0 pyparsing==3.0.9 pytz==2022.6 -requests==2.28.1 +requests==2.32.2 six==1.16.0 snowballstemmer==2.2.0 soupsieve==2.3.2.post1 From 6d1ded7dc448aa0d51420c0c9c5bc2b134130916 Mon Sep 17 00:00:00 2001 From: Callahan Date: Mon, 7 Oct 2024 08:33:08 -0500 Subject: [PATCH 120/151] chore: drop debian directory (#5093) Removes the `debian` directory and moves the bash completion script to `snap/local`. Signed-off-by: Callahan Kovacs --- MANIFEST.in | 1 - debian/changelog | 3681 ------------------- debian/compat | 1 - debian/control | 19 - debian/copyright | 50 - debian/rules | 14 - debian/source/format | 1 - debian/source/options | 2 - debian/tests/control | 9 - debian/tests/get-release-codename | 6 - debian/tests/integrationtests-spread | 52 - {debian => snap/local}/snapcraft-completion | 0 snap/snapcraft.yaml | 2 +- 13 files changed, 1 insertion(+), 3837 deletions(-) delete mode 100644 debian/changelog delete mode 100644 debian/compat delete mode 100644 debian/control delete mode 100644 debian/copyright delete mode 100755 debian/rules delete mode 100644 debian/source/format delete mode 100644 debian/source/options delete mode 100644 debian/tests/control delete mode 100755 debian/tests/get-release-codename delete mode 100755 debian/tests/integrationtests-spread rename {debian => snap/local}/snapcraft-completion (100%) diff --git a/MANIFEST.in b/MANIFEST.in index ab2b4cf212..f24832561c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,2 @@ include schema/* include extensions/* -include debian/changelog diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index 3636153038..0000000000 --- a/debian/changelog +++ /dev/null @@ -1,3681 +0,0 @@ -snapcraft (3.9.1) bionic; urgency=medium - - [ Sergio Schvezov ] - * ci: switch to travis workspaces - * states: add support for required-grade in global state - * lifecycle: store the required grade depending on the base - * meta: move grade setting logic back to snap_packaging - - [ Chris Patterson ] - * yaml_utils: move project_info's _load_yaml to yaml_utils - * yaml_utils: introduce a couple of unit tests for load_yaml_file - * remote-build: rename `--arch` to `--build-on` - * remote-build: prepend 'launchpad' to 'user' and 'accept-public-upload' - - -- Sergio Schvezov Fri, 01 Nov 2019 17:57:43 +0000 - -snapcraft (3.9) bionic; urgency=medium - - [ Sergio Schvezov ] - * tests: print journal logs when spread tests fail - * meta: new application handler - * meta: add desktop file handling to application - * meta: move desktop file cleanup to a separate method - * meta: replace logic in _SnapPackaging with Application - * tests: completely mock bzr tests - * tests: completely mock mercurial tests - * tests: completely mock 7z tests - * snap: migrate to core18 - * ci: move unit tests to spread - * build providers: inject core18 instead of core - * tests: completely mock subversion tests - * docs: add a Code of Conduct (#2724) - * pluginhandler: remove the exception for elf patching go - * project: support for base bare - * tests: update rust-toolchain test so it pulls from beta - * storeapi: use the channels attribute in push - * meta: take no command-chain being prepended into account - * cli: add -s back to clean for legacy (LP: #1834628) - * cli: prompt for login if required - * extensions: new kde-neon extension - * cli: use click utilities for login prompts - * meta: warn about command mangling - * storeapi: add StoreErrorList to handle store errors - * cli: clean up StoreClientCLI - * tests: move cli store push/upload tests to FakeStoreCommands... - * cli: use click utilities for registering on push (LP: #1805211) - * meta: support the case of a plug without a default provider - * remote build: switch from core to core18 - * make plugin: support for core20 - * snaps: invalidate cache on refresh or install - * snaps: allow installation of non stable bases - * meta: force grade devel when using non stable bases - * build providers: inject snapd snap for latest feature availability - * repo: convey proper error message when refreshing to invalid channel - * cli: pass channels None when not doing a push --release - - [ Chris Patterson ] - * tests: change default spread provider to lxd outside of travis - * meta: handle desktop files with multiple sections - * meta: preserve desktop file Exec= arguments - * snaps: if snap is installed, don't check is_valid() - * mypy.ini: set python version to 3.6 - * tests: minor fixups for mypy to run successfully - * runtests: add mypy coverage of unit tests to static target - * errors: add new abstract base class for snapcraft exceptions - * cli: add support for new-style snapcraft exceptions - * tests: fix mypy error with test_errors.py - * meta: introduce snap, hook, plug, and slot types - * application: refactor to work with introduced snap meta objects - * command: refactor to work with Snap meta - * project: instantiate snap meta - * project: introduce _get_content_snaps() and _get_provider_content_dirs() - * project-loader: initialize project._snap_meta when data is updated - * runner: install content snaps when installing build snaps - * meta: remove create_snap_packaging from init to prevent import loop - * snap-packaging: refactor to use Snap - * pluginhandler: refactoring dependency resolution - * elf: consider content directories for determining dependencies - * common: rename get_core_path() to get_installed_snap_path() - * pluginhandler: add some type annotations - * fixtures: mock patch Project._get_provider_content_dirs() - * spread tests: update unicode-metadata expect_snap.yaml's ordering - * snap-packaging: do not write command-chain wrapper if there are no apps - * project options: add compatibility shims for tests - * elf: handle missing dependencies not found on system - * tests: update gnome-3-28 extension spread test to use gtk - * tests: update gnome extension spread task to account for content snaps - * tests: update kde extension spread task to account for content snaps - * remote-build: detect early build errors (#2642) - * fixtures/SnapcraftYaml: rewrite snapcraft.yaml on updates - * remote-build: fully preserve local sources - * remote-build: introduce --package-all-sources flag - * git: add init, add, commit, push, version, check_if_installed functions - * remote-build/launchpad: pivot to git source handler - * remote-build: use project name in build-id for launchpad git repo - * remote-build: error if --user is required - * requirements: add lazr.restfulclient dependency for launchpad - * windows: update snapcraft.spec for new remote-build dependencies - * remote-build: make --user required and drop config file handling - * remote-build: only prepare project if starting build - * project: add `_get_project_directory_hash` method - * remote-build: use project directory hash for id - * remote-build: introduce LaunchpadGitPushError - * tests/remote-build: minor cleanup for mock usage - * remote-build: update launchpad to support git https tokens - * tests/remote-build: cleanup usage of mock_lp - * remote-build: graduate from preview -> experimental - * errors: migrate handful of errors to SnapcraftException - * project: truncate project directory hash (#2766) - * setup.py: convert classifiers from tuple to list - * sources: add some initial support for win32 - * file_utils: fix create_similar_directory on Windows platforms - * file_utils: add cross-platform rmtree (Windows support) - * remote-build: use file_utils.rmtree for Windows support - * remote-build: use posix pathing when creating paths for snapcraft yaml - * remote-build: gunzip downloaded log files - * manifest: sort package and snap lists for consistency - * remote-build: cleanup and fix architecture handling - * remote-build: explicitly default build arch to host arch - * remote-build: remove `all` option for `--arch` - * remote-build: remove old TODO comment - * erorrs: preserve quotes when printing SnapcraftPluginCommandError - * remote-build: improve resiliency for https connection issues - * remote-build: add unit tests for errors - * remote-build: support autorecovery of builds - - [ Claudio Matsuoka ] - * cli: add remote build (#2500) - * remote build: add warning before sending data (#2567) - * remote build: retrieve build log files (#2574) - * remote build: don't send log files back to remote - * remote build: handle git push in detached head state (#2564) - * remote build: add option to skip public upload question (#2590) - - [ Kyle Fazzari ] - * cmake plugin: support disable-parallel option - * project: use os.sched_getaffinity instead of multiprocessing.cpu_count - * project_loader: load build-environment after snapcraft environment - - [ Merlijn Sebrechts ] - * extensions: add gsettings plug to gnome-3-28 extension - * docs: Added 'shellcheck' testing dependency - * extensions: support using gjs from gnome runtime - * appstream: extract title and version - * docs: use real testing examples - * appstream: support legacy ids without desktop suffix (LP: #1778546) - * extensions: kde-neon: add icon and sound themes - - [ NickZ ] - * nodejs plugin: fix errors when building with sudo (#2747) - - [ Anatoli Babenia ] - * docker: use apt-get to avoid warnings (#2672) - - [ Ken VanDine ] - * gnome extension: use the snap name only for the default-provider (#2763) - * kde neon extension: use the snap name only for the default-provider (#2764) - - -- Sergio Schvezov Tue, 29 Oct 2019 01:13:57 +0000 - -snapcraft (3.8) xenial; urgency=medium - - [ Sergio Schvezov ] - * test: autopkgtest beta - * debian: minimal deb package for autopkgtest - * extensions: new gnome extension (#2655) - * deltas: code cleanup - * tests: move meta testing to its own package - * yaml utils: move OctInt from meta - * spread tests: minor performance improvements - * meta: move _errors to errors with related error classes - * meta: decouple DesktopFile logic - * schema: schema: build-base support for the snapd type - * rust plugin: support for s390x - * schema: build-base support for the kernel type - * spread tests: update gnome extension tests - * extensions: rename extension classes to known names - * extensions: create the gnome-platform directory - * extensions: improve docsting (used in the cli) - * spread tests: finer grained arch support for autopkgtests - - [ Chris Patterson ] - * elf: handle invalid elf files - * cli: handle exception when cleaning a part with a fresh project - * spread: fix unbound variable error - * docs: quick init for lxd in HACKING.md - * windows: drop cx_Freeze support in setup.py - * cli: use absolute import paths instead of relative imports - * requirements: update to python 3.7 for PyYaml wheel - * requirements: uprev all OS to pexpect 4.7.0 - * requirements: add pyinstaller 3.5 for win32 - * windows: add snapcraft.ico icon - * windows: add pyinstaller spec file to generate frozen snapcraft.exe - * dirs: find Windows data directory for currently-known scenarios - * lxd: conditionally import pylxd based on OS - * windows: add inno-installer script - * windows: add powershell script to generate self-signed certificate - * tests: fix snapcraft command for win32 virtual env - * appveyor: build Windows inno-installer - * windows: add MSIX/AppX installer - * dirs: raise SnapcraftDataDirectoryMissingError() if paths not set - * multipass: update ProverNotFound url to https://multipass.run - * indicators: windows fix for is_dumb_terminal - * multipass: add installation support for windows - * travis: use apt addon to prevent apt update issues in CLA-check - * multipass: fix setup exception when multipass is not found in PATH - * dirs: check for existence of required data directories - - [ Jeremie Deray ] - * catkin plugin: forward parallel build count (#2669) - * colcon plugin: forward parallel build count (#2670) - - [ Anatoli Babenia ] - * lifecycle: add support for building inside podman containers (#2659) - * docker: remove snapcraft-wrapper - - [ Stefano Rivera ] - * repo: properly handle install query for unknown apt packages (#2692) - - [ Kyle Fazzari ] - * spread tests: install package marker into ament index - * colcon plugin: add ability to ignore packages (#2687) - - -- Sergio Schvezov Thu, 05 Sep 2019 15:48:57 +0000 - -snapcraft (3.7.2) xenial; urgency=medium - - [ Sergio Schvezov ] - * meta: transparently support command-chain - * extensions: structure the base extension class - * extensions: refactor checks into the extension base class - * extensions: support confinement restrictions - * appstream: xslt support for ul nested in p - - [ Colin Watson ] - * store: send snapcraft-started-at in push requests - - [ Chris Patterson ] - * scriplets: run override-pull on update_pull (#2653) - * cli: replace HiddenOption with click 7.0's new flag (#2654) - - -- Sergio Schvezov Wed, 07 Aug 2019 23:54:28 +0000 - -snapcraft (3.7.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * ant plugin: correct default channel and improve help - * cli: improve help for push-metadata - * project, cli: clean up snap asset messages - * file utils: better error for NotADirectory - * cli: only say sorry for in-snapcraft issues - - [ Chris Patterson ] - * deprecations: add deprecation notice for version-script (dn10) - - [ Carlo Lobrano ] - * meta: add InvalidAppCommand errors for non-existent and not-found (#2539) - - -- Sergio Schvezov Tue, 30 Jul 2019 17:59:00 +0000 - -snapcraft (3.7) xenial; urgency=medium - - [ Sergio Schvezov ] - * static: use beta channel for black (#2606) - * catkin spread tests: dump apt-config on failures for legacy (#2610) - * rust plugin: use toml to dump the config (#2611) - * rust plugin: use rust-toolchain by default if present (#2613) - * conda plugin: new plugin (#2608) - * build providers: support injection for LXD (#2621) - * schema: remove support for os when using bases (#2626) - * appstream extractor: skip non icon file paths (#2630) - * spread tests: enable LXD build provider tests (#2631) - * build environment: detect base type and use name as base - * plugins: use get_build_base to determine base support - * project: add support for build-base - * repo: add support for querying file ownership - * pluginhandler: suggest stage-packages for missing DT_NEEDED - * tests: add python3-toml for autopkgtests - * spread tests: limit conda plugin to non autopkgtests x86-64 systems - * spread tests: crystal tests should only run on x86-64 - - [ Chris Patterson ] - * black: minor format changes from updated black (#2603) - * sources: introduce SnapcraftSourceNotFoundError (#2604) - * spread: use more workers to reduce job times - * catkin/legacy-pull: set test to manual - * cli: convert users of click.confirm/prompt to echo.confirm/prompt - * echo: respect SNAPCRAFT_HAS_TTY for is_tty_connected() - * ant plugin: switch to using ant snap for building (by default) - * general spread tests: set base for cwd test (#2618) - * errors: refactor exception/error handling (#2602) - * tests/unit/pluginhandler: introduce tests to repro symlink preservation bug - * file_utils/create_similar_directory: drop follow_symlinks option - * pluginhandler: honour symlink directory paths for filesets (LP: #1833408) - * test_pluginhandler: remove faulty (redundant) tests - * schema: synchronizing snapd supported schema to snapcraft (#2627) - - [ Brian J. Cardiff ] - * crystal plugin: new plugin (#2598) - - [ Mike Miller ] - * build providers: enforce well-known temp dir (#2607) (LP: #1833292) - - [ Pawel Stolowski ] - * schema: allow snapd as snap type (#2609) - - [ Claudio Matsuoka ] - * echo: add wrappers for click.prompt() and click.confirm() - - [ Kyle Fazzari ] - * colcon plugin: add support for dashing (#2593) - - [ anatoly techtonik ] - * cli: add -h short option for help (#2527) (LP: #1807423) - - [ Stefan Bodewig ] - * use the stable risk level now that ant has been released - - [ Chris MacNaughton ] - * rust plugin: add ability to rebuild (#2620) (LP: #1825858) - - [ Carlo Lobrano ] - * tools: let environment-setup.sh skip unnecessary steps (#2625) - - -- Sergio Schvezov Mon, 22 Jul 2019 19:10:34 +0000 - -snapcraft (3.6) xenial; urgency=medium - - [ Sergio Schvezov ] - * docker images: update to be self contained (#2591) - * static: update to newer black (#2599) - * repo: set priority to critical for debs (LP: #1821313) - - [ Kyle Fazzari ] - * docker images: use build stages and generate locale (#2588) - * {catkin,colcon} plugin: remove old ROS key (#2586) - * catkin plugin: check workspace for dependencies (#2585) (LP: #1832044) - - [ Stefan Bodewig ] - * ant plugin: make build file location configurable (#2596) - - [ Chris Patterson ] - * tools: add `black` snap to environment-setup.sh (#2595) - - [ dependabot[bot] ] - * requirements: bump pyxdg from 0.25 to 0.26 (#2587) - - -- Sergio Schvezov Fri, 14 Jun 2019 20:00:41 +0000 - -snapcraft (3.5.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * requirements: update to requests-toolbelt 0.8.0 (#2565) - * kernel plugin: correctly download the os.snap (#2566) (LP: #1828843) - * docs: consolidate on a simple HACKING.md (#2568) - * plainbox spread tests: lock down python packages (#2579) - * cli: refactor re-execution into legacy (#2571) - * plainbox spread tests: use https for the git source entries (#2583) - * catkin spread tests: finer system filter for legacy-pull (#2584) - * unit tests: stricter file checking on store download tests - * LEGACY common: add is_deb method - * LEGACY lifecycle: warn about bases - * LEGACY unit tests: stricter testing for common.is_deb - * LEGACY common: take into account dpkg not being found - * LEGACY common: check if running from snap before deb - * LEGACY common: check for relative argv0 in is_deb - - [ Ian Metcalf ] - * dotnet plugin: fix parsing of newer sdk releases (#2537) - - [ Kyle Fazzari ] - * catkin spread tests: use kinetic instead of indigo (#2575) (LP: #1830769) - * keyrings: update ROS signing key (#2578) - * LEGACY tests: stop testing indigo in catkin spread (#2572) - * LEGACY catkin plugin: remove default rosdistro (#2576) - - [ Adam Collard ] - * cli: add --destructive-mode to clean (#2577) - - [ Michael Vogt ] - * cli: do not prompt when there is no tty available on stdin (#2570) - - [ dalance ] - * rust plugin: fix linker on i386 (#2580) - - -- Sergio Schvezov Thu, 06 Jun 2019 19:24:29 +0000 - -snapcraft (3.5) xenial; urgency=medium - - [ Sergio Schvezov ] - * vcs: ignore .idea (#2558) - * ci: remove dependency on LXD from travis tests (#2557) - * cli: snapcraft promote (#2556) (LP: #1827513) - * manifest: expose snapcraft-started-at (#2559) (LP: #1806658) - * storeapi: allow promotion from branches - * tests: release and promote to grade devel channels - * tests: fix the status test - * tests: add ppc64el to the fake server info results - - [ Claudio Matsuoka ] - * project: read local plugins from build-aux (#2551) (LP: #1827095) - * extensions: block direct use of private extensions (#2555) - - [ Daniel Llewellyn ] - * meta: take ${SNAP} into account .desktop icon checks (#2541) - - -- Sergio Schvezov Sat, 11 May 2019 14:56:00 +0000 - -snapcraft (3.4.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * tests: add a set_confinement helper - * tests: classic confinement spread tests for ant and maven (LP: #1805206) - * build providers: get rid of attribute warnings from pylxd (#2535) - * rust plugin: fix usage of rust-channel (#2538) (LP: #1825062) - * storeapi: move from details (v1) to info (v2) (#2550) - - [ Tony Simpson ] - * project loader: add SNAPCRAFT_PROJECT_DIR environment variable (#2534) - (LP: #1824417) - - [ Kyle Fazzari ] - * tests: allow python2 as well as python for catkin spread tests (#2540) - * integration tests: use correct series in get_package_version (#2548) - * catkin plugin: use build-packages for compilers (#2545) (LP: #1827148) - - [ Claudio Matsuoka ] - * extractors: fix typo in appstream icon unit test (#2543) - * meta: Validate common ID against appstream metadata (#2542) (LP: #1814902) - - -- Sergio Schvezov Fri, 03 May 2019 12:48:58 +0000 - -snapcraft (3.4) xenial; urgency=medium - - [ Sergio Schvezov ] - * build providers: modify the _run signature (#2511) (LP: #1821401) - * build providers: support for provider setup (#2515) (LP: #1821586) - * readme: add snap store badge (#2516) - * build providers: initial support for LXD (#2509) (LP: #1805221) - * cli: cleanup environment detection (#2521) - * build providers: add API for friendly instance type names (#2522) - * snap: set core as a base (#2520) - * ci: improve travis integration conditionals (#2523) - * cli: snapcraft try (#2524) (LP: #1805212) - * build providers: idempotent destroy for LXD (#2529) - * tests: add missing pylxd Build-Depends - * tests: restrict catking stage-snap tests arches - - [ Claudio Matsuoka ] - * repo: handle deb package fetch error (#2513) - * project: ensure yaml load returns a dictionary (#2517) - * many: better handling of appstream icons (#2512) (LP: #1814898) - * go plugin, elf: use patchelf 0.10 and relink dynamic go binaries (#2519) - (LP: #1805205) - * snap: use snapcraft's 0.10 patchelf branch (#2528) - * snap: revert to patchelf 0.9 with local patches (#2531) - - [ adanhawth ] - * schema: add more detail wrt numeric version errors (#2506) - - [ Kyle Fazzari ] - * catkin plugin: check stage-snaps for ROS dependencies (#2525) - - -- Sergio Schvezov Mon, 08 Apr 2019 20:40:27 +0000 - -snapcraft (3.3) xenial; urgency=medium - - [ Sergio Schvezov ] - * many: support for "base: core" in snapcraft.yaml (#2499) (LP: #1819290) - * python plugin: graceful ret when no packages set (#2498) (LP: #1794216) - * many: support the use of build-aux/snap (#2496) (LP: #1805219) - * nodejs pluging: support for type str bin entries (#2501) (LP: #1817553) - * store: support registering to a specific store (#2479) (LP: #1820107) - * meta: fix management of snap/local (#2502) - * tests: improve login pexpect errors - * tests: correctly retry registers - * build providers: enhance provider errors (#2508) (LP: #1821217) - * build providers: improve handling in snap logic (#2507) (LP: #1820864) - * tests: filter per arch and fix snap build deps - - [ Claudio Matsuoka ] - * sources: handle network request errors (#2494) - * store: handle invalid snap file errors (#2492) - * tests: fix multipass error handling spread test (#2491) - * plugins: improve python and go schema validation (#2473) (LP: #1806055) - * cli: disable raven if not running from package (#2503) - - [ Facundo Batista ] - * schema: better 'version' error messages: wrong type and incorrect length (#2497) - (LP: #1815812) - - -- Sergio Schvezov Thu, 21 Mar 2019 20:34:19 +0000 - -snapcraft (3.2) xenial; urgency=medium - - [ Sergio Schvezov ] - * many: support for stage-snaps (#2468) (LP: #1805214) - * project loader: do not leak a part's build-environment (#2472) - (LP: #1815658) - * build providers: remove dead code (#2474) - * tests: disable spread colcon tests (#2476) - * ci: shallow clones for CLA checks on travis (#2477) - * meta: handle symlinked hooks (#2478) - * Update setup.py - * Update PULL_REQUEST_TEMPLATE.md - * project loader: remove special LD_LIBRARY_FLAGS handling for classic (#2485) - (LP: #1817300) - * sources: avoid marking changes to the snap directory as dirty (#2475) - (LP: #1806746, #1816397) - - [ Claudio Matsuoka ] - * cli: clean up snapcraft push output (#2469) (LP: #1804439) - * cli: handle legitimate provider exec errors (#2483) - - [ Daniel Llewellyn ] - * schema: convert yaml jsonschema document to a json equivalent (#2448) - - [ Kyle Fazzari ] - * tests: make before/after items an array in schema test (#2465) - * ruby plugin: support new download URL (#2466) (LP: #1815336) - * colcon plugin: new plugin (#2456) (LP: #1805213) - * colcon plugin: support build-time chaining (#2486) (LP: #1816565) - - -- Sergio Schvezov Tue, 26 Feb 2019 18:25:49 +0000 - -snapcraft (3.1.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * ant, maven and gradle plugins: use correct defaults for jre (#2453) - (LP: #1813637, #1813636) - * rust plugin: new link for rustup (#2438) (LP: #1662960) - * baseplugin: add a proper exception for cross-compilation support (#2454) - (LP: #1808454) - * clean: error out on invalid or missing yaml (#2458) (LP: #1777501) - * lifecyle: avoid installation of snaps in docker (#2457) (LP: #1814148) - * ci: use non virt-enabled gce instances for 16.04 (#2461) - - [ Buo-ren Lin ] - * extractors: fix typo in code comment (#2452) - - [ Claudio Matsuoka ] - * pluginhandler: handle removal of inconsistent files (#2450) (LP: #1813033) - * cli: retrieve error data from provider (#2455) (LP: #1793082) - * build providers: recreate instance if base changed (#2460) (LP: #1794506) - - [ Kyle Fazzari ] - * catkin plugin: describe how to build all packages (#2459) - - -- Sergio Schvezov Wed, 06 Feb 2019 15:49:45 +0000 - -snapcraft (3.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * cmake plugin: use native primitives (#2397) (LP: #1802314) - * cmake plugin: use build snaps to search paths (#2399) (LP: #1802314) - * static: update to the latest flake8 (#2420) (LP: #1807409) - * project: state file path change (#2419) (LP: #1806746) - * nodejs plugin: fail gracefully when a package.json is missing (#2424) - (LP: #1807971) - * tests: use fixed version for idna in plainbox (#2426) - * tests: remove obsolete snap and external tests (#2421) - * snap: re-add pyc files for snapcraft (#2425) (LP: #1808199) - * tests: increase test timeout for plainbox (#2428) - * lifecycle: query for multipass install on darwin (#2427) (LP: #1807737) - * extractors: better appstream support for descriptions (#2430) (LP: #1778545) - * tests: re-enable spread tests on gce - * rust plugin: refactor to use the latest rustup (LP: #1809259) - * tests: temporarily disable osx tests - * snap: add build-package for xml - * appstream extractor: properly find desktop files (LP: #1805431) - * appstream extractor: support legacy launchables (LP: #1778546) - * snap: add xslt dependencies for lxml - * schema: allow before and after (#2443) (LP: #1808148) - * build providers: remove SIGUSR1 signal ignore workaround for multipass (#2447) - * cli: enable cleaning of parts (#2442) (LP: #1779136) - * tests: appstream unit tests are xenial specific - * tests: skip rust unit tests on s390x - * tests: use more fine grained assertions in lifecycle tests - * tests: remove rust revision testing for i386 - - [ Johan Dahlin ] - * tests: do not use `bash` as a reserved package name on staging (#2423) - - [ Ricardo N Feliciano ] - * cli: fix usage string in help command (#2429) (LP: #1745040) - - [ Anatoli Babenia ] - * repo: document package purpose (#2390) - - [ Kyle Fazzari ] - * repo,baseplugin: support trusting repo keys (#2437) (LP: #1811304) - - [ Michał Sawicz ] - * meta: make hooks executable instead of complaining they are not (#2440) - (LP: #1812003) - - -- Sergio Schvezov Thu, 24 Jan 2019 10:27:23 +0000 - -snapcraft (3.0.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * ci: use more travis primitives for osx tests (#2383) - * project_loader: do not install base if already installed (#2384) - * cli: consolidate re-execution (#2385) - * project: early snapcraft.yaml validation (#2388) - * build providers: fix osx non base and injection (#2394) - * Partial Revert "build providers: fix osx non base and injection" (#2396) - * tests: fix maven spread test (#2400) - * ci: reduce spread system declarations (#2401) - * project_loader: add git to build-packages for version: git (#2403) - * ci: update travis.yaml to use xenial and addons (#2392) - * packaging: update requests (#2402) - * cli: handle yaml errors for cleanbuild (#2404) - * multipass: avoid stdin where possible (#2408) - * build providers: preset the timezone (#2407) - * python plugin: process deps before and separately from setup.py (#2409) - * yaml_utils: allow unicode while encoding (#2414) - * cli: snapcraft init with a base (#2411) - * schema: add support for title (#2412) - * tests: use autopkgtest to leverage snap testing (#2380) - - [ Kyle Fazzari ] - * tests: missing full adapter in extension test (#2382) - * meta: add assumes if using "full" app adapter (#2379) - * project_loader: raise error if part in after is undefined (#2386) - * spread: change virt-enabled image name (#2387) - - [ Erik Berkun-Drevnig ] - * cli: hide progress bar for dumb terminals (#2369) - - [ John R. Lenton ] - * tools: broader shellcheck including spread (#2381) - * tools: pull in imporved cla_check.py from snapd (#2415) - - [ Anatoli Babenia ] - * tests: run black with --diff (#2391) - - [ Evan Dandrea ] - * build providers: checks for when multipass is missing (#2354) - - -- Sergio Schvezov Thu, 29 Nov 2018 16:22:52 +0000 - -snapcraft (3.0) xenial; urgency=medium - - [ Sergio Schvezov ] - * docker: support for testing snapcraft in proposed (#2245) - * snap: add the https transport (#2244) - * build providers: environment setup for projects (#2225) - * build providers: provide support to shell in (#2249) - * build providers: shell in provider if debug is used (#2252) - * build-providers: add support for --shell-after (#2253) - * build providers: add support for --shell (#2254) - * build providers: snapcraft images for multipass (#2258) - * build providers: allow setting ram and disk size (#2260) - * build providers: inject the base for classic (#2261) - * build providers: allow snapcraft channel selection (#2265) - * build providers: refresh packages on bring up (#2267) - * build providers: let the implementor pick the image (#2269) - * reporting: fail gracefully on submit errors (#2271) - * meta: friendlier message for incorrect app command (#2272) - * snap: use a newer PyYAML and drop patches (#2274) - * build providers: use the best CPU configuration (#2273) - * build providers: use the provider if exported (#2275) - * snap: move to a newer pysha3 (#2277) - * spread: move legacy wiki tests to spread (#2276) - * snap: pull early (#2278) - * build providers: re-exec as root (#2281) - * build providers: cleaner start and launch messaging (#2282) - * build providers: make use of time for multipass stop (#2284) - * meta: support relocatable prime for path verification (#2287) - * build providers: use multipass automatically when on darwin (#2288) - * snap: workaround the dirty tree (#2294) - * tests: use SNAPCRAFT_PACKAGE_TYPE everywhere (#2295) - * tests: move most tests to spread and reorder travis.yaml (#2301) - * snap: improve early base detection logic (#2309) - * meta: link the icon correctly across filesystems (#2313) - * project loader: remove remote parts support for bases (#2304) - * tests: use mocked plugins for list-plugins (#2315) - * tests: add spread suite for plainbox plugin (#2317) - * plugins: remove the tar-content plugin when using a base (#2319) - * plugins: remove the copy plugin when using a base (#2308) - * meta: add support for the license field (#2318) - * build providers: use the new snapcraft: remote for multipass (#2293) - * plugins: remove the python2 and python3 plugin when using a base (#2325) - * plugins: remove the ament plugin when using a base (#2324) - * plugins: remove implicit source (#2326) - * go plugin: support for bases (#2323) - * pluginhandler: remove legacy plugin loading without project (#2329) - * godeps plugin: support for bases (#2328) - * pluginhandler: remove big solidus workaround (#2330) - * pluginhandler: remove prepare, build and install scriptlets (#2327) - * waf plugin: support for bases (#2332) - * meson plugin: add support for bases (#2331) - * lifecycle: remove lxd support for bases (#2335) - * tests: remove dependency on snapcraft for integration tests (#2353) - * schema: enfore string for versions (#2334) - * lifecycle: switch to multipass by default (#2339) - * build providers: remove the qemu implementation (#2345) - * schema: remove the deprecated snap keyword for bases (#2344) - * tests: use valid snap names in unit tests (#2352) - * scons plugin: add support for bases (#2357) - * nodejs plugin: add support for bases (#2356) - * pluginhandler: library detection instead of injection (#2337) - * dotnet plugin: add support for bases (#2358) - * schema: remove deprecated plugin pull and build-properties (#2361) - * plainbox-provider plugin: add support for bases (#2360) - * multipass: change default CPU value (#2365) - * python plugin: add support for bases (#2362) - * maven plugin: add support for bases (#2364) - * gradle plugin: add support for bases (#2372) - * ant plugin: add support for bases (#2370) - * jdk plugin: remove jdk (#2376) - * build providers: destroy on create failures (#2374) - * cli: remove disable-parallel-build and geoip toggles (#2377) - - [ Kyle Fazzari ] - * yaml loading: properly handle unhashable types (#2247) - * pluginhandler: stop using alias for snapcraftctl (#2251) - * local source: don't include .snapcraft directory (#2256) - * meta: take charge of environment used to run commands (#2257) - * cli: show trace if no tty (#2259) - * catkin plugin: use SnapcraftException (#2255) - * project_loader: add preflight check (#2250) - * project: catch parent YAML exceptions (#2263) - * tests: disable integration tests using snaps in bionic container (#2266) - * catkin, rosdep: stop using FileNotFoundErrors (#2270) - * sanity checks: allow snap/local dir (#2268) - * sanity checks: run properly on build VMs (#2279) - * snapcraft snap: refactor override-build into a script (#2283) - * config: change default outdated action to clean (#2286) - * snapcraft snap: vendor legacy snapcraft (#2285) - * schema: add "legacy" adapter type (#2262) - * sources: properly handle pull failures (#2292) - * packaging: pin click to v6 in requirements.txt (#2298) - * meta: put environment into runner instead of app wrapper (#2291) - * part grammar processor: lazily capture attributes from plugin (#2296) - * pluginhandler: update build should overwrite organize (#2290) - * requirements.txt: stop using pymacaroons-pynacl (#2302) - * project_loader: add build-environment part property (#2322) - * catkin, catkin-tools plugins: add support for bases (#2333) - * schema, meta: support layout (#2338) - * schema, meta: support app command-chain (#2341) - * schema, meta: add "full" app adapter (#2343) - * ruby plugin: add support for base (#2346) - * extensions: support adding root properties (#2347) - * extensions: remove root extensions (#2348) - * extensions: use extension docstring (#2349) - * extensions: parse all declared extensions before applying (#2350) - * extensions: cleanup and generic tests (#2355) - * {make,cmake,autotools} plugin: add support for bases (#2363) - * qmake plugin: add support for bases (#2366) - * {kbuild,kernel} plugin: add support for bases (#2368) - * tests: add spread test exercising multipass build VMs (#2367) - * plugins: remove jhbuild (#2371) - * rust plugin: add support for bases (#2373) - * sanity checks: verify that command-chain is not used with legacy adapter (#2375) - - [ Evan Dandrea ] - * cli: use the better snapcraft.io/account URL (#2280) - - [ Adam Collard ] - * storeapi: use structured data for the conflicted current value (#2316) - - [ Erik Berkun-Drevnig ] - * rust plugin: do not ignore the cross compile target (#2264) - - [ Anthony Fok ] - * nodejs plugin: add support for ppc64el and s390x (#2310) (#2310) - * nodejs plugin: update to the latest 8.x LTS version (#2342) - - [ John Lenton ] - * yaml: replace yaml.safe_load() with CSafeLoader (#2218) - - -- Sergio Schvezov Sat, 20 Oct 2018 18:41:17 +0000 - -snapcraft (2.43.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * lxd: support new style snap injection (#2222) - * snap: prepare override scripts to allow rebuilding (#2223) - * lxd: wait for cloud-init (#2227) - * storeapi: handle releasing to a curly braced branch (#2228) - * snap: use set-version and set-grade (#2230) - * lxd: proper filename set when using architectures (#2232) - * tests: cover manifest generation with review-tools (#2235) - * reporting: record the released version for errors (#2238) - * file_utils: find tool when using docker and deb (#2240) - * elf: better messaging on glibc ABI incompatibilities (#2241) - * New upstream release (LP: #1789766) - - [ Kyle Fazzari ] - * spread: stop running catkin tests on 18.10 (#2221) - * snapcraftctl: run in isolation mode (#2224) - * templates: reimplement templates as python classes (#2226) - * templates: rename to extensions (#2233) - * cli: add list-extensions command (#2237) - * extensions: fix install path (#2236) - - [ Evan Dandrea ] - * reporting: improve messaging on errors (#2242) - - -- Sergio Schvezov Thu, 30 Aug 2018 00:08:05 +0000 - -snapcraft (2.43) xenial; urgency=medium - - [ Sergio Schvezov ] - * recording: expose the version of snapcraft (#2147) - * tests: add a fixture for OsRelease to simplify test setup - * recording: add the os-release ID and VERSION_ID to manifest.yaml - * nodejs plugin: update to the latest 6.x LTS point release (#2157) - * snap: use apt from the archive instead of compiling (#2156) - * build_providers: support for communicating with a qemu VM (#2155) - * many: refactor snapcraft.yaml loading out of load_config (#2160) - * tests: update codespell, the scope of checks and ordering of static - tests (#2162) - * rust plugin: fix cargo builds and run tests (#2170) - * build_providers: add ssh key managemet to the qemu build provider (#2168) - * ci: disable osx tests until a new pyyaml is released - * build_providers: inject snaps when running from a snap (#2174) - * code: use black as the standard style (#2180) - * cli: reserve the --debug option for snapcraft projects (#2185) - * state: allow parametrization of the global state file (#2186) - * errors: enable sentry by default (#2187) - * file_utils: get_tool_path to always return an absolute path (#2193) - * errors: support keyboard interrupts (#2198) - * build_providers: add a build image setup facility (#2192) - * build providers: better injection logic (#2196) - * Revert "ci: disable osx tests until a new pyyaml is released" (#2213) - * providers: multipass by default on darwin (#2215) - * New upstream release (LP: #1787419) - - [ Kyle Fazzari ] - * lifecycle: automatically stage dependencies (#2144) - * lifecycle: automatically clean dirty steps (#2145) - * travis: use LXD from 3.0 track (#2149) - * project_loader: process parts in consistent order (#2146) - * project_loader: allow loading null parts (#2153) - * tests: disable sentry (#2154) - * lifecycle: don't clean priming area if the snap is being tried (#2143) - * many: extract lifecycle ordering into own module (#2159) - * many: automatically redo step for specified part (#2152) - * tests: add lifecycle ordering tests (#2163) - * many: automatically detect dependency changes (#2165) - * project_loader: replace dict keys as well as values (#2166) - * {catkin,python} plugin: support cleaning (#2171) - * many: add shellcheck to static tests (#2172) - * lifecycle: detect local source changes (#2167) - * lifecycle: pass commands to containerbuild, not steps (#2178) - * cli: SNAPCRAFT_BUILD_ENVIRONMENT isn't deprecated (#2179) - * pluginhandler: use uname from the host (#2177) - * store: properly handle disabled deltas (#2181) - * tests: create basic integration test spread infrastructure (#2173) - * tests: add spread suite for autotools plugin (#2182) - * tests: add spread suite for cmake plugin (#2183) - * spread: stop testing 17.10 (#2197) - * tests: add spread suite for copy plugin (#2199) - * tests: add spread suite for nil plugin (#2201) - * tests: add spread suite for kbuild plugin (#2203) - * kbuild plugin: stop modifying kconfigfile (#2204) - * tests: support running spread suite in autopkgtests (#2188) - * tests: add spread suite for meson plugin (#2205) - * tests: add spread suite for godeps plugin (#2200) - * tests: add spread suite for scons plugin (#2208) - * tests: add spread suite for catkin plugin (#2206) - * tests: add spread suite for waf plugin (#2210) - * cli: add inspect subcommand (#2184) - * tests: add spread suite for tar-content plugin (#2209) - * tests: add spread suite for ament plugin (#2211) - * tests: add spread suite for ruby plugin (#2212) - * project_loader: add basic template support (#2189) - * spread tests: vendor gotty - * spread tests: vendor kbuild-template - * spread tests: vendor hello - * sentry: support disabling error reporting (#2214) - - [ Daniel Llewellyn ] - * jhbuild plugin: allow running as 'root' (#2141) - - [ Emmanuel Leblond ] - * python plugin: add process-dependency-links to the pull_properties (#2190) - * ci: improve pr template and tools' ignored files list (#2191) - - [ Celso Providelo ] - * tests: stricter match when running snapcraft revisions (#2195) - - [ Michael Vogt ] - * go plugin: do not install go debs if go snap is used (#2194) - - [ Jesse Sung ] - * kbuild plugin: read configs from source directory instead (#2202) - - [ Paolo Pisati ] - * kernel plugin: install .config as config-$kernelversion (#2207) - - -- Sergio Schvezov Thu, 16 Aug 2018 14:12:55 +0000 - -snapcraft (2.42.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * repo: rollback to using dpkg-deb for deb extraction (#2111) - * pluginhandler: correctly dedent the run script (#2117) - * many: dedup environment entries (#2120) - * packaging: load the correct libraries on armhf (#2126) - * delta: properly search for in-snap xdelta3 (#2127) - * package: disable user site-packages for snapcraft (#2131) - * tests: remove obsolete env var (#2130) - * errors: generic exception for common.run[_output] (#2132) - * tests: add subprocess to lifecycle tests - * ci: resurrect codecov (#2138) - * ci: remove unused cron tests (#2139) - * tests: generalize the plainbox-provider tests - * New upstream release (LP: #1771915) - - [ Kyle Fazzari ] - * HACKING: suggest snapcraft-pr for evaluating PRs (#2108) - * repo: fix all python shebangs in stage-packages (#2112) - * sources: don't clean target for FileBase sources (#2113) - * meta: stop creating empty snap directory in prime (#2114) - * snapcraft.yaml: update apt repo location (#2124) - * file_utils: don't let FileNotFoundError escape (#2123) - * storeapi: ensure snap ID is sane before using it (#2115) - * pluginhandler: collide with directories and symlinks (#1064) - * storeapi: handle 5xx error codes for all store endpoints (#2116) - * many: introduce variables for part src and build (#2122) - * repo: automatically prune unneeded stage-packages (#2119) - - [ Christian Dywan ] - * sources: clean up IncompatibleOptionsError (#2109) - * lxd: proper error classes for container errors (#2095) - - [ Jonathan Cave ] - * plainbox-provider plugin: include pip, setuptools and wheel as stage-packages (#2140) - - -- Segio Schvezov Fri, 18 May 2018 02:28:55 +0000 - -snapcraft (2.42) xenial; urgency=medium - - [ Sergio Schvezov ] - * elf: patch everything instead of a subset of elf files (#2081) - * elf: clear the current runpath before setting the rpath (#2085) - * python plugin: properly handle distutils on bionic - * ci: add the python integration tests on bionic for travis - * many: remove support for remote lxd per project containers (#2089) - * schema: allow refresh-mode and stop-mode (#2092) - * packaging: include changelog for setup.py's version detection (#2097) - * ci: enable OSX testing on travis (#2084) - * tests: use a common cache for the integration tests - * tests: remove the SharedCache fixture and uses of it - * many: allow building in containers with no version in project (#2104) - * errors: remove logic for SNAPCRAFT_SEND_ERROR_DATA - * errors: implement the always option to sent to sentry - * package: make use of snapcraftctl snapcraft features (#2103) - * nodejs plugin: lazy load the required tarballs (#2106) - * build_providers: new build provider using multipass (#2100) - * tests: use FakeSnapd for grammar tests - * tests: adapt the integration suite to work for all releases - * New upstream release (LP: #1767016) - - [ Kyle Fazzari ] - * project_loader: support architectures for CI (#2080) - * meta: soften warning about using passthrough (#2091) - * storeapi: better handle network errors and retries (#2094) - * grammar: support compound on..to statement (#2088) - * tests: fix arch-specific integration tests - - [ Christian Dywan ] - * python: bring back support for older versions of pip (#2055) - * tests: don't use os_release and repo from snapcraft (#2096) - * lxd: wait for on-going refreshes to finish (#2098) - * ci: setup AppVeyor (#2087) - * repo: catch error updating the package cache (#2079) - * tests: do not shadow deb_arch in architecture scenarios - * tests: parser tests need the cache - * tests: don't hard-code expected arch in VersionScriptTestCase - - [ Rakesh Singh ] - * dotnet plugin: add dotnet command to path for step overriding (#1909) - - [ Michael Vogt ] - * lifecycle: skip -all-root for base snaps (#2090) - - [ Matias Bordese ] - * tests: update metadata store integration test, no previous push - required (#2086) - - -- Sergio Schvezov Thu, 26 Apr 2018 01:58:29 +0000 - -snapcraft (2.41) xenial; urgency=medium - - [ Sergio Schvezov ] - * package: ensure all relevant files are in for sdist (#2060) - * errors: enable sending tracebacks to sentry (#1961) - * errors: improve the UX for sending error data (#2024) - * errors: remove stack data when sending to sentry (#2036) - * errors: feature flag error reports (#2066) - * packaging: simplify snapcraft.yaml (#2062) - * many: update the yaml loading logic (#2065) - * extractors: support for setup.py (#2070) - * lifecycle: handle missing version correctly (#2072) - * python plugin: do not invoke wheel install if empty (#2077) - * patches: improve ctypes patch for python 3.5 - * ci: enable subset of integration tests on bionic - * errors: skip the sentry test if raven is no installed (#2075) - * New upstream release (LP: #1763934) - - [ Kyle Fazzari ] - * many: add snapcraftctl command for scriptlets (#2002) - * kernel plugin: add kmod as build-package (#2041) - * cli: support exporting login to stdout (#2043) - * many: add override-pull scriptlet (#2045) - * pluginhandler: organize in build instead of stage (#2047) - * tests: extract sources suite from general suite (#2054) - * many: add override-stage scriptlet (#2049) - * tests: extract lifecycle suite from general suite (#2057) - * many: add override-prime scriptlet (#2052) - * elf: use snapped strip (#2051) - * storeapi: properly handle lacking permission for channel (#2050) - * many: add snapcraftctl set-version (#2063) - * storeapi: handle 500 error response when releasing snap (#2059) - * states: track override scriptlets (#2068) - * many: add snapcraftctl set-grade (#2067) - * meta: validate extracted and scriptlet metadata (#2073) - - [ Christian Dywan ] - * options: introduce Project and ProjectInfo (#1995) - * lxd: friendly error with suggestions if network is broken (#1930) - * lxd: merge existing image info contents when using containerbuilds (#1997) - * lxd: specify arch in lxc image list command (#2046) - * meta: implement passthrough of properties to snap.yaml (#2053) - - [ Guilhem Lettron ] - * nodejs plugin: add option for setting npm flags (#2038) - - [ Bjorn Tillenius ] - * python plugin: install python-distutils when run on bionic (#2058) - - [ Rakesh Singh ] - * dotnet plugin: add support enable configurable runtime version for - .NET Core applications (#1911) - - [ Colin Watson ] - * storeapi: fix formatting of some store errors (#2056) - - -- Sergio Schvezov Sat, 14 Apr 2018 12:13:35 +0000 - -snapcraft (2.40.1) xenial; urgency=medium - - [ Evan Dandrea ] - * readme: polish the landing page (#2022) - - [ Christian Dywan ] - * repo: catch error due to broken build packages (#2023) - - [ Sergio Schvezov ] - * pluginhandler: organize correcly for targets with leading / (#2034) - - -- Sergio Schvezov Tue, 27 Mar 2018 21:09:24 +0000 - -snapcraft (2.40) xenial; urgency=medium - - [ Sergio Schvezov ] - * elf: better debug messages (#1950) - * elf: clear execstack by default (#1945) - * repo: silence deb caching when fetching packages (#1949) - * meta: make sure adapter does not propagate (#1955) - * store: stringify message for StoreDeltaApplicationError (#1962) - * python plugin: find setup.py when source-subdir is used (#1975) - * ci: switch to stable lxd and unconfined containers (#1978) - * project_loader: improve the logic to install patchelf as a build tool (#1967) - * demos: avoid use of the wrapper for java-hello-world (#1968) - * lifecycle: when priming dependencies need to be primed (#1977) - * pluginhandler: simplify logic when elf patching is required (#1979) - * pluginhandler: add option to disable patchelf for a part (#1987) - * schema: add keep-execstack (#1986) - * elf: remove dead code (#2000) - * core: initial support for bases (#1993) - * docker: export SNAP_ARCH into the environment (#1994) - * tests: add SNAPCRAFT_KEEP_DATA_PATH for integration (#1996) - * errros: add a specific error when running commands from plugins (#2004) - * pluginhandler: special case go patchelf failures for classic confinement (#2005) - * many: use packaging logic to get patchelf (#2006) - * project_loader: fix the host to base compatibility check (#2009) - * pluginhandler: only do elf checking and patching for type app (#2012) - * demos: use realpath in command entry for java-hello-world (#2016) - * many: optimize retrieval of the linker version (#2017) - * tests: minor fixes to autopkgtests (#2018) - * elf: avoid duplicating rpath entries (#2019) - * pluginhandler: only resort to elf mangling if the snap type is app (#2021) - * New upstream release (LP: #1756939) - - [ Leo Arias ] - * tests: split the plugins tests in the same directory (#1944) - * tests: move test files out of the snapcraft dir (#1948) - * extractors: replace desktop file ids with paths (#1879) - * extractors: add support for common-id (#1960) - * tests: remove ProjectOptions dependency from the integration suite (#1965) - * tests: remove dependency of internal repo from integration suite (#1971) - * tests: remove the internal os_release dependency (#1981) - * tests: remove _options dependency from integration suite (#1972) - * tests: update store tests user (#1984) - * tests: document arm testing setup (#1985) - - [ Kyle Fazzari ] - * catkin plugin: extract Wstool into its own module (#1927) - * elf: remove all un-primed libs from soname cache (#1980) - * catkin plugin: support recursive rosinstall files (#1934) - * elf: only set rpaths to libs of the same arch (#1988) - * elf: don't parse elf more than necessary (#1989) - * catkin plugin: replace python calls in all profile.d scripts (#2007) - * docs: add execstack to HACKING.md's list of deb deps (#2015) - * integration tests: snap tests shouldn't be arch-specific (#2014) - - [ Gustavo Silva ] - * cli: add version command (#1746) - - [ John Lenton ] - * schema: improve the snap name's validator (#1957) - * snap: actually plug the completer in. (#1956) - * tests: adapt store tests with new snap name registration error messages - (#1964) - - [ Christian Dywan ] - * project_loader: handle invalid unicode chars (#1941) - * lxd: initialize remote lazily (#1916) - - [ Celso Providelo ] - * store: support for more granular store permissions (#1958) - - [ James Henstridge ] - * elf: only consider regular files as possible ELF binaries (#1976) - * elf: only patch elf files that aren't referenced by DT_NEEDED (#1959) - - -- Sergio Schvezov Mon, 19 Mar 2018 12:40:43 +0000 - -snapcraft (2.39.2) xenial; urgency=medium - - [ Sergio Schvezov ] - * tests: improvements to demos (#1938) - * tests: remove the webcam-webui demo (#1940) - * elf: contemplate more patching scenarios (#1935) - * New upstream release (LP: #1745488) - - [ Kyle Fazzari ] - * tests: remove duplicate tests (#1928) - * schema: remove underscore from version pattern (#1933) - * store: support pushing snap with no architectures (#1937) - * storeapi: handle errors even for >400 responses (#1936) - - [ Christian Dywan ] - * sources: proper errors for invalid handlers (#1929) - - [ Sylvain Pineau ] - * tests: update the plainbox-provider tests (#1931) - - -- Sergio Schvezov Tue, 20 Feb 2018 00:40:00 +0000 - -snapcraft (2.39.1) xenial; urgency=medium - - [ Sergio Schvezov ] - * tests: setup the correct environment for adt (#1907) - * snap: patch ctypes for the snap and remove libarchive (#1915) - * tests: update tests to work in adt (#1908) - * elf: cache crawled files (#1925) - - [ Leo Arias ] - * elf: use surrogate escape when decoding readelf output (#1903) - - [ Christian Dywan ] - * os-release: stub for when /etc/os-release doesn't exist (#1880) - - [ James Henstridge ] - * elf: pyelftools to parse ELF files rather than readelf (#1913) - - [ Christian Dywan ] - * lxd: raw.idmap expects host and container id respectively (#1904) - - [ Kyle Fazzari ] - * docker: add Dockerfiles for all risk levels (#1914) - * schema: update version regex (#1924) - * mypy: update to 0.560 (#1923) - * remote_parts: handle connection errors (#1906) - - -- Sergio Schvezov Thu, 15 Feb 2018 12:14:00 +0000 - -snapcraft (2.39) xenial; urgency=medium - - [ Sergio Schvezov ] - * tests: fix broken rust test snap (#1838) - * docker: instructions to build from the snap (#1840) - * cli: implement help (#1845) - * elf: warn if primed files will not work with the base's linker (#1843) - * setup: simplify bin/snapcraft and correct tests (#1860) - * pluginhandler: patch and handle elf files on glibc mismatch - * tests: split more tests and and add an invisible ticker for travis - * tests: call file directly for the HasArchitecture checker (#1868) - * kernel plugin: remove dependency on magic (#1867) - * elf: remove dependency on magic (#1870) - * elf: cleaner patchelf experience (#1873) - * Revert "meta: create XDG_RUNTIME_DIR in wrappers. (#1818)" (#1883) - * elf: better handling for newer libc6 (#1881) - * elf: make patchelf a dependency (#1888) - * docker: user proper tags in Readme.md (#1897) - * elf: do not strip rpaths that contain $ORIGIN (#1896) - * elf: check if file exists before checking if ELF (#1901) - - [ Kyle Fazzari ] - * cli: add expiration option to export-login (#1831) - * zip: support extracting non-unix zip files (#1862) - * cli: exported login should only be readable by owner (#1869) - * lifecycle: use in-snap mksquashfs if running from snap (#1891) - * cli: use C.UTF-8 if locale not set (#1893) - * docker: beta should use beta, edge use edge (#1895) - * docker: don't rely on snapcraft-classic - * many: use in-snap unsquashfs and readelf if running from snap (#1902) - - [ Leo Arias ] - * extractors: also support appdata.xml appstream files. (#1861) - * extractors: support appstream icon and desktop (#1853) - * tests: allow to overwrite the snapcraft install command (#1894) - - [ Christian Dywan ] - * lxd: change "Terminating" message to debug level (#1842) - * lxd: use user in container when mounting remote via sshfs (#1847) - * remote_parts: Use hashed folder based on parts URI (#1856) - * grammar: to statement (#1639) - * grammar: make on statement use host architecture (#1857) - * repo: use debian.arfile instead of dpkg-deb (#1878) - * lxd: always (re-)injects snaps if necessary (#1865) - - [ Paolo Pisati ] - * kernel plugin: update initrd.img-core path to boot/initrd.img (#1839) - * kbuild plugin: pick up CROSS_COMPILE only if not empty (#1874) - - [ Alberto Donato ] - * schema: add options to configure applications socket activation (#1617) - - [ Herman Yanush ] - * cli: humanize push message (#1833) - - [ Konrad Krawiec ] - * cmake plugin: update plugin help (#1832) - * storeapi: add docstrings for _snap_index_client.py (#1855) - - [ Marcin Mikołajczak ] - * static tests: add type hint for cli/assertions.py module (#1811) - - [ Alan Pope ] - * README: remove rocketchat link (#1854) - - [ Daniel Lim Wee Soong ] - * storeapi: add docstrings to _client (#1851) - * tests: add snap not found tests (#1849) - - [ Chris Glass ] - * cli: fixed typo in help (#1890) - - [ Jonathan Cave ] - * tests: remove plainbox part from plugin tests (#1889) - - -- Sergio Schvezov Thu, 25 Jan 2018 21:40:43 +0000 - -snapcraft (2.38) xenial; urgency=medium - - [ Sergio Schvezov ] - * ci: detect docker to auto setup core (#1801) - * ci: correctly run from snap (#1803) - * elf: strip the .note.go.buildid to make room for patching elf (#1798) - * meta: create XDG_RUNTIME_DIR in wrappers (#1818) - * tests: use a simpler test for bzr and python (#1821) - * repo: handle invalid snaps (#1815) - * tests: skip classic confined tests on armhf (#1822) - * elf: search for DT_NEEDED in prime and the base (#1823) - * pluginhandler: warn the inclusion of libraries from the host (#1829) - - [ Leo Arias ] - * tests: update the snap name already registered for store tests (#1797) - * tests: update the registered snap fake (#1799) - * tests: collect the autopkgtest results and save them to a repo (#1755) - * meta: refactor into a package (#1804) - * ci: don't fail if the snap transfer fails (#1808) - - [ Kyle Fazzari ] - * metadata: add infrastructure for extracting metadata from parts (#1825) - * extractors: support appstream metadata in parts (#1826) - - [ Michael Hudson-Doyle ] - * lxd: suppress traceback when lxc launch / init fails (#1771) - - [ Daniel Lim Wee Soong ] - * storeapi: breakdown code into modules (#1793) - - [ Tyler Hicks ] - * lifecycle: use the -no-fragments mksquashfs option (#1805) - - [ Daniel Lim Wee Soong ] - * ci: transfer generated snapcraft snap to transfer.sh (#1806) - - [ Christian Dywan ] - * repo: error for packages with broken dependencies (#1788) - * lxd: always install squashfuse (#1786) - * tests: run test_cleanbuild in LXD on Travis (#1807) - * grammar: support grammar-powered source (#1817) - - [ Ted Gould ] - * lifecycle: do not include source tarballs from previous runs (#1795) - - [ Konrad Krawiec ] - * static tests: add type hints to formatting_utils (#1809) - * tests: add more unit tests for formatting_utils (#1813) - - [ Ivan Fonseca ] - * static tests: type hinting for various modules (#1812) - - -- Sergio Schvezov Fri, 22 Dec 2017 16:55:56 +0000 - -snapcraft (2.37) xenial; urgency=medium - - [ Sergio Schvezov ] - * cli: improve the help command (#1789) - * many: set rpath for elf files for classic (#1781) - - [ Kyle Fazzari ] - * tests: run codespell as part of static tests (#1783) - * catkin-tools plugin: use stage-packages (#1779) - * cli: add export-login command (#1780) - - [ Leo Arias ] - * tests: move the ppa test trigger to lxd (#1758) - * tests: python plugin integration tests moved to a separate suite (#1791) - * tests: do not hit the network in python unit tests (#1792) - * cli: add what, why, and how to fix to the common errors (#1640) - * cli: include consistent commands to fix error conditions (#1790) - - [ Christian Dywan ] - * tests: move refresh to unit folder (#1787) - - [ Daniel Lim Wee Soong ] - * spelling: fixed spelling and added codespell support (#1770) - * cli: more complete error when registering a reserved name (#1778) - * static tests: add type hinting to file_utils.py (#1785) - - [ Sangbum Kim ] - * cli: show helpful message for 'snapcraft list-keys' with no keys (#1784) - - [ Marcin Mikołajczak ] - * tests: add --help command to the runtests.sh script (#1775) - - [ Matias Bordese ] - * storeapi: support for pushing binary metadata (#1774) - - -- Sergio Schvezov Wed, 06 Dec 2017 15:22:05 +0000 - -snapcraft (2.36) xenial; urgency=medium - - [ Sergio Schvezov ] - * static tests: upgrade to the newest flake8 (#1745) - * static tests: enable type checking by use of mypy (#1747) - * elf: conversion from libraries (#1744) - * many: ensure classic confined binaries use the correct interpreter (#1759) - * project: export the arch triplet into the environment (#1768) - - [ Kyle Fazzari ] - * TESTING: adt on lxc requires squashfuse (#1741) - * python plugin: use extracted pip (#1607) - * ament plugin: new plugin (#1583) (LP: #1686850) - * catkin plugin: support building entire workspace (#1743) (LP: #1721168) - * store: refactor acquirement of attenuated macaroon (#1765) - * snapcraft.yaml: use gcc to determine tuple (#1764) (LP: #1733922) - - [ Leo Arias ] - * tests: reorganize unit and integration suites to make them easier to split - for travis (#1638) - * tests: share the cache for ros tests (#1725) - * tests: run daily autopkgtest in travis (#1643) - * tests: add a script to run autopkgtests from the ppa (#1749) - * tests: add the missing quote on autopkgtest run (#1756) - * tests: move the catkin integration tests to a separate suite (#1760) - - [ Chris Ratliff ] - * catkin tools plugin: add catkin tools support (#1593) - - [ Paolo Pisati ] - * kernel plugin: respect the kernel-with-firmware property (#1750) - - [ Colin Watson ] - * nodejs plugin: pass proxy configuration to yarn (#1754) - - [ Christian Dywan ] - * lxd: always remove tmp_dir after execution (#1742) - * lxd: delete container only if parts is empty (#1762) - - [ Facundo Batista ] - * store: push-metadata added (#1634) - * tests: push metadata test needs to push the snap first (#1766) - - [ Daniel Watkins ] - * python plugin: use finer grained file filters for pip (#1763) - - [ Michael Hudson-Doyle ] - * lxd: let lxd choose the architecture (#1718) - - -- Sergio Schvezov Wed, 29 Nov 2017 04:20:26 +0000 - -snapcraft (2.35) xenial; urgency=medium - - [ Sergio Schvezov ] - * pluginhandler: error out on scriptlet errors - * meta: ensure main keys are ordered in snap.yaml - * demos: remove the py?-project demos - * store: switch to new endpoints - * static: fix flake8 errors in setup.py - * docker: add the environment variable to setup core (#1576) - * cli: add the pack command (#1565) - * store: handle revoked developers (#1554) - * lifecycle: split into its own package (#1626) - * libraries: exclude the full set of libc6 (#1632) - * lxd: better surfacing of errors (#1647) - * tests: fork skip into snaps_tests (#1652) - * sources: use arfile to extract debs (#1729) - * tests: dotnet only works on 16.04 (#1732) - * unit tests: make the check for output less strict (#1738) - * New upstream release (LP: #1729417) - - [ Kyle Fazzari ] - * project_loader: aliases are deprecated - * catkin plugin: don't assume catkin is in underlay - * catkin plugin: only append PYTHONPATH if set - * plugins: extract python finder functions - * plugins: extract sitecustomize logic from python - * dirs: set plugin, schema, and library dir for snap - * plugins: extract pip from python plugin - * catkin plugin: support rosdep pip dependencies (#1581) - * plugins: add ros2 boostrapper (#1582) - * travis: run snapd tests only if not cron (#1592) - * snapcraft.yaml: don't re-use build dir (#1601) - * schema: improve invalid app, hook, and part errors (#1615) - * plugins: build-attributes is already in the state (#1620) - * tests: skip catkin test on non-xenial (#1621) - * tests: don't hit internet in ros2 units (#1619) - * states: add scriptlets to build state (#1618) - * schema: sync patterns with snapd (#1622) - * snap: remove leaking LD_LIBRARY_PATH (#1635) - * store: guide to account creation upon login (#1616) - * repo: add elementary to deb distros (#1637) - * internal: more gracefully determine host OS (#1636) - * integration tests: skip shared ROS test on non-xenial (#1656) - * internal: don't reuse variable in OsRelease (#1653) - * integration tests: remove ruby version (#1727) - * unit tests: reset log level after test (#1735) - * autotools: cross-compile using --host instead of env (#1654) - * catkin plugin: check for pip packages in part only (#1717) - * ruby plugin: be smarter about arch-specific paths (#1730) - * demo tests: bump catkin timeout by a lot (#1731) - * many: account for python shebang args in rewrite - - [ Leo Arias ] - * typo: replace ocurred with occurred - * node plugin: record installed node packages in manifest - * node plugin: record the yarn.lock file - * tests: fix the TEST_STORE environment variable - * tests: add integration tests for build snaps - * recording: record the machine information collected by uname - * tests: add unit tests for the ruby plugin - * recording: record the packages installed in the machine - * tests: simplify a little the data in nodejs unit tests - * ci: use travis conditionals - * recording: record build-snaps installed during the pull - * tests: replace the first batch of demo tests with snapd integration tests - * rust plugin: record the Cargo.lock file - * rust plugin: record the versions of rustup, rustc and cargo - * tests: move ruby demo test to snapd integration suite (#1596) - * recording: record the snaps installed on the machine (#1567) - * style: use dedent for multiline strings (#1584) - * tests: refactor the fake snapd to not hardcode values (#1569) - * code style: remove the extra quotes in the dedent example (#1594) - * recording: do not crash when snapd is not installed (#1598) - * tests: fix the skip of snapd integration tests in armhf (#1595) - * tests: fix the duplicate plainbox test scenarios (#1608) - * tests: reenable the cleanbuild integration test (#1610) - * tests: remove the duplicate nodejs integration tests (#1609) - * tests: add /snap/bin to PATH in autopkgtests (#1603) - * tests: add the slow tag for ros snapd integration test (#1602) - * lxd: fix the unit test for the user id map (#1629) - * tests: allow to select a suite when running autopkgtests (#1630) - * tests: use the snapcraft snap for the integration tests (#1625) - * tests: move the plainbox test to the integration suite (#1642) - * lxd: fix the push in container builds (#1644) - * tests: use the common base handler on the fake snapd server (#1724) - * tests: split the integration autopkgtests (#1716) - * tests: in autopkgtests, use a tempdir in home, not in the tmpfs (#1657) - * recording: record information from the image in container builds (#1633) - * recording: pass the build info flag to the container (#1736) - * tests: add the home plug to the plainbox snap (#1740) - - [ Christian Dywan ] - * lxd: mount project folder via sshfs in case of a remote (#1302) - * lxd: Only pass target arch if specified explicitly - * repo: friendly, helpful error for unsupported distros (#1586) - * lxd: instructions for /etc/sub{u,g}id after failed start (#1553) - * lxd: pass SNAPCRAFT_PARTS_URI through into container (#1585) - * lxd: use SUDO_UID for ID mapping (#1588) - * lxd: don't inject local snaps on a different arch (#1577) - * lxd: don't re-inject the same snaps (#1568) - * lxd: split container classes into different files (#1627) - * cli: update parts cache in the container (#1546) - * lifecycle: clean after deleting container (#1587) - * lxd: snapcraft refresh in containers (#1412) - * lxd: distinguish argless clean from clean -s pull (#1655) - * lxd: refresh remote container (#1739) - * cli: pass remote from container_config to clean method (#1737) - - [ Mark Lee ] - * project_loader: quote more environment variable values (#1578) - - [ James Beedy ] - * ruby plugin: new plugin - - [ Aleix Pol ] - * repo: return a proper value in DummyRepo - * common: do not fail over on empty or faulty lines in os-release - - [ Jeff Dickey ] - * ci: install git in Dockerfile for '{version: git}' usage (#1575) - - [ Michael Vogt ] - * meta: add and adapter property for apps - - [ Chris Ratliff ] - * catkin plugin: allow ROS_MASTER_URI change (#1572) - - [ Rakesh Singh ] - * dotnet plugin: new plugin (#1574) - - [ Martin Wimpress ] - * nodejs plugin: update default node engine to 6.11.4 (#1589) - - [ Colin Watson ] - * options: fix core-dynamic-linker on ppc64el/s390x (#1600) - - [ Paolo Pisati ] - * kbuild plugin: if the parts build dir already contains a .config file, return immediately (#1606) - * cross compilation: enable cross compilation of i386 kernel on x86-64 … (#1613) - - [ Jonathan Cave ] - * plainbox-provider plugin: init PROVIDERPATH (#1611) - - [ Carlo Lobrano ] - * store: fix StoreReleaseError format for BAD REQUEST error (#1599) - * sources: get svn revision with --show-item flag (#1648) - * Removed dependency on VERSION_ID in os-release (#1623) - - [ Alfonso Sanchez Beato ] - * kernel plugin: use latest stable core snap (#1624) - - [ Nathan Haines ] - * ruby plugin: new stable release 2.4.2 (#1645) - - [ Marius Gripsgard ] - * sources: workaround for ZipFile.extractall not preserving permissions (#1723) - - -- Sergio Schvezov Wed, 01 Nov 2017 19:41:33 +0000 - -snapcraft (2.34) xenial; urgency=medium - - [ Sergio Schvezov ] - * core: cache FileBase entries when a checksum is provided (#1433) - * cli: better error message for missing mksquashfs (#1481) - * core: improve source caching logic (#1486) - * ci: speedup the CLA check (#1503) - * ci: disable the travis deploy stage for docs (#1510) - * vcs: ignore .vscode project settings (#1517) - * docs: add github processed templates (#1514) - * tour: remove the tour assets (#1520) - * project: introduce build-snaps (#1518) - * tests: update the meson test to latest meson requirements (#1532) - * jhbuild plugin: remove dependency on pkgconf (#1535) - * nodejs plugin: add prefix --prefix to global pkg add (#1539) - * New upstream release (LP: #1715399) - - [ Christian Dywan ] - * lxd: clean with no parts should only delete (#1434) - * lxd: inject snapcraft and core snaps into the container (#1364) - * lxd: wait on lock files before running apt commands (#1435) - * kbuild plugin: support Makefiles without an install target (#1432) - * kbuild plugin: move over the cross-compiling logic from the kernel plugin (#1417) - * lxd: path cannot have extra forward slashes (#1483) - * lxd: always remove existing device for project folder (#1488) - * lifecycle: outdated step should raise SnapcraftError (#1513) - * errors: introduce ContainerError (#1505) - * lxd: LXD not installed when using remote (#1516) - * lxd: use a unique temporary folder (#1519) - - [ Leo Arias ] - * recording: record the original snapcraft.yaml (#1407) - * ci: skip the CLA check for pull requests from the bot (#1482) - * docs: fix typo in plugin help (#1496) - * python plugin: record manifest (#1487) - * tests: use assertThat instead of assertEqual (#1501) - * python plugin: always record constraints and requirements contents (#1521) - * demos: update the name of the remote mqtt part (#1533) - - [ Kyle Fazzari ] - * options: properly handle missing compiler prefix (#1425) - * catkin plugin: include-roscore is a boolean (#1472) - * catkin plugin: default to release build (#1470) - * catkin plugin: rosinstall-files is a pull property (#1473) - * catkin plugin: support passing args to cmake (#1471) - * catkin plugin: extract rosdep into new package (#1392) - * cli: add global exception handler - * cli: stop handling exceptions in lifecycle - * plugins: use exceptions based on SnapcraftError - * cli: stop handling exceptions in parts - * cli: stop handling exceptions in assertions - * cli: stop handling exceptions in store - * many: use exceptions based on SnapcraftError - * rosdep: add support for multiple dependency types (#1479) - * errors: use function for exit code (#1491) - * cli: don't raise from excepthook (#1495) - * repo: make errors based on SnapcraftError (#1499) - * project loader: refactor into package (#1504) - * grammar: move into project_loader (#1500) - * many: simplify plugin loading (#1507) - * schema: version should have a max length of 32 (#1508) - * project_loader: process stage package grammar (#1509) - * project_loader: support grammar on build-packages (#1511) - - [ Alex T Newman ] - * windows: add cx_Freeze options targeting bin/snapcraft (#1478) - - [ Michael Vogt ] - * meta: add `base` as a type and top level property (#1419) - - [ Adam Collard ] - * ant plugin, gradle plugin: add support for authenticated proxies (#1490) - - [ fmanea ] - * demos: remove the unnecessary wrapper from the java demo (#1494) - - [ Neal Gompa (ニール・ゴンパ) ] - * repo: use os-release(5) to detect supported Linux distributions (#1527) - - [ Daniel Llewellyn ] - * jhbuild plugin: new plugin (#1298) - * jhbuild plugin: fix UnboundLocalError for chmod_path (#1534) - - -- Sergio Schvezov Wed, 06 Sep 2017 14:04:53 +0000 - -snapcraft (2.33) xenial; urgency=medium - - [ Sergio Schvezov ] - * tests: reduce the amount of test code in test_meta (#1391) - * meta: bash completion support (#1390) - * tests: fix issues with python 3.6 (#1398) - * cleanbuild: ensure we enter a shell on failures on --debug (#1404) - * tests: do not break a systems `bzr whoami` (#1357) - * snap: remove completer entry (#1409) - * core: minimal cross OS support. (#1413) - * core: a clean command should clean. (#1416) - * nodejs plugin: copy the content of out of tree symlinks (#1418) - * tests: adapt opencv's test expectations (#1426) - * New upstream release (LP: #1707060) - - [ Zygmunt Krynicki ] - * pluginhandler: don't clobber path on local import failure (#1361) - - [ Paolo Pisati ] - * core: use the correct kernel arch for s390 (#1376) - - [ Tim Süberkrüb ] - * core: support yaml merge tags (#1384) - - [ roxd ] - * schema: blank version should not be allowed in snapcraft.yaml (#1379) - - [ Paolo Pisati ] - * kernel plugin: add a default target per powerpc, ppc64el and s390x (#1377) - - [ Rex Tsai ] - * tests: mock download for rustup in the x-compile test. (#1396) - - [ Evan Dandrea ] - * file_utils: handle I/O errors in os.link (#1369) - * python plugin: correct capitalisation for PyPI. (#1401) - * python plugin: better explain dependency link processing (#1402) - - [ Leo Arias ] - * python plugin: output json in pip list (#1393) - * tests: allow to filter tests in docker (#1375) - * tests: document the test suites in the snapcraft repo (#1394) - * recording: rename the file to manifest.yaml (#1406) - * pluginhandler: check for collisions only in existing files (#1405) - * tests: run the tests in travis using LXD containers (#1415) - * tests: build the snapcraft snap in travis tests (#1411) - * ci: remove the old script to run with lxc (#1422) - - [ Christian Dywan ] - * cli: proper parsing of hidden options (#1378) - * core: clean the container on a full clean (#1372) - * lxd: do not assume user ID of 1000 for raw.idmap (#1385) - * autotools plugin: Enable cross-compilation support (#1383) - * lxd: distinguish FileNotFoundError for when not installed (#1400) - * waf plugin: enable cross-compilation support (#1397) - * ci: wait for apt/dpkg lock when setting lxd up (#1421) - * lxd: stop setting `$HOME` in containers (#1408) - * lxd: only remove container if one exists (#1403) - - [ Simon Davy ] - * meta: add support for `reload-command` (#1373) - - -- Sergio Schvezov Thu, 27 Jul 2017 20:19:01 +0000 - -snapcraft (2.32) xenial; urgency=medium - - [ Michał Sawicz ] - * autotools plugin: don't force uniqueness on configflags (#1337) - - [ filibtester ] - * integrations: use the snapcore/snapcraft docker image in travis (#1331) - - [ Jonathan Cave ] - * sources: allow source-type to specify local (#1352) - - [ Christian Dywan ] - * tests: run unit tests on Travis with mock armhf (#1316) - * rust plugin: add support for cross-compilation (#1350) - - [ Gustavo Silva ] - * deltas: improved message returned when delta is too big (#1345) - - [ Adam Collard ] - * cli: add --version command (#1366) - - [ Sergio Schvezov ] - * cli: provide a whoami command (#1332) - - [ Kyle Fazzari ] - * cli: stop leaking green text (#1360) - * catkin plugin: add support for ROS Lunar (#1367) - - [ Leo Arias ] - * tests: use pyramid as a router for the fake servers (#1363) - * tests: set up the spread execution in linode (#1374) - * integrations: add the snapcraft Dockerfile (#1370) - * tests: refactor the travis jobs using stages (#1318) - - -- Kyle Fazzari Fri, 23 Jun 2017 10:00:19 -0700 - -snapcraft (2.31) xenial; urgency=medium - - [ Sergio Schvezov ] - * storeapi: resume snap downloads (#1330) - * cli: remove double congrats messaging (#1351) - * cli: do not duplicate errors (#1347) - * cli: default options for the implicit snap command (#1353) - * go plugin: filter the main packages when go-packages are defined (#1355) - * New upstream release 2.31 (LP: #1692102) - - [ Colin Watson ] - * store: send X-Ubuntu-Series, not X-Ubuntu-Release (#1320) - - [ Kyle Fazzari ] - * docs: improve onboarding experience (#1336) - * catkin plugin: add support for rosinstall files (#1314) - * common: find data files via sys.prefix (#1356) - - [ Tim Süberkrüb ] - * qmake plugin: set default qt version (#1328) - * tour: use https for source urls (#1329) - - [ Leo Arias ] - * tests: small updates for manual kernel tests (#1327) - * tests: use the fake apt cache in deb unit tests (#1334) - * state: save all the build packages as global (#1340) - - [ Michał Sawicz ] - * sources: don't use --remote for updating git submodules (#1344) - - [ Christian Dywan ] - * go plugin: add support for cross-compilation (#1338) - * go plugin: cross compile with CGo (#1343) - * plugins: clarify wording of cross-compilation unsupported error (#1349) - - -- Sergio Schvezov Wed, 07 Jun 2017 22:51:40 -0300 - -snapcraft (2.30) xenial; urgency=medium - - [ Sergio Schvezov ] - * cli: new UI and internal refactor (#1307) - * cli: proper error for failed snap command (#1325) - * Revert "tests: remove the reusable parts tour test (#1321)" (#1324) - * store: collaboration UI for snaps (#1288) - * New upstream release 2.30 (LP: #1692102) - - [ Leo Arias ] - * tests: initial setup for the snapcraft snap tests with spread (#1272) - * misc: rename the snap dir variable to prime dir (#1279) - * recording: save the snapcraft.yaml in the resulting snap (#1278) - * tests: minor cleanups on the spread tests (#1287) - * tests: refactor tests with build and stage packages (#1290) - * parts: remove the deprecated snap keyword from the internal representation (#1282) - * tests: fix the recording tests to work in multiple architectures (#1292) - * recording: record stage packages installed in the snap (#1293) - * recording: record build packages installed in the snap (#1295) - * tests: increase the staging registration limit to 100 (#1271) - * tests: use C.UTF-8 for the docker locale (#1310) - * state: save the dependencies of build packages (#1299) - * recording: record global build-packages installed on the host (#1306) - * state: fix the name of the source details (#1312) - * meta: read and write the desktop file with utf-8 encoding (#1309) - * cleanbuild: set the container language to C.UTF-8 (#1304) - * tests: remove the reusable parts tour test (#1321) - * recording: save the details of the source pulled (#1317) - * state: ignore the 'any' architecture in the build packages apt cache (#1322) - * state: search for the build package that provides a virtual package (#1323) - - [ Facundo Batista ] - * store: support for channel branches in responses for release, close, and status (#1280) - - [ Christian Dywan ] - * lxd: pass through commands into the container (#1263) - * lxd: setup image and target arch for cross-compilation (#1286) - * lxd: support the architectures field from older LXDs (#1305) - * lxd: mock platform in the FakeLXD fixture (#1315) - - [ Paolo Pisati ] - * kernel plugin: learn how to assemble the ubuntu config using kconfigflavour (#1285) - * kernel plugin: slightly improve the messaging of check_config() (#1303) - * kernel plugin: do not warn about DMIID in config check (#1319) - * kernel_plugin: use CROSS_COMPILE to override the default toolchain (#1242) - * cli: allow uts machine arch as a valid --target-arch option (#1204) - * kernel plugin: verify kernel config is correct (#1223) - - [ Tim Süberkrüb ] - * sources: add support for 7-zip files (#1168) - - [ JulianLiu ] - * meson plugin: add a plugin for meson build system (#1294) - - [ roxd ] - * rust plugin: use of source-subdir without failing on pull (#1296) - - [ edvega ] - * sources: validate unknown source-type in yaml (#1297) - - [ Ricardo N Feliciano ] - * docs: add missing VCS dependencies to HACKING.md (#1311) - * cli: allow capital Y to accept the dev agreement (#1308) - - -- Sergio Schvezov Fri, 19 May 2017 17:39:34 -0300 - -snapcraft (2.29) xenial; urgency=medium - - [ Sergio Schvezov ] - * store: better retry strategy for GETs. (#1212) - * docs: remove docs and link to snappy-docs. (#1227) - * nodejs plugin: switch to the newer LTS. (#1228) - * pluginhandler: exclude `/snap/` from libraries. (#1231) - * nodejs plugin: switch to the newer LTS. (#1228) (#1232) - * core: find the correct libraries as a snap. (#1234) - * snap: use the gpg tarball instead of git://. (#1236) - * docs: update contribution guide. (#1233) - * repo: enable elementary. (#1237) - * cli: improve push output. (#1240) - * ci: split plugin integration tests out. (#1252) - * nodejs plugin: add support for yarn. (#1245) - * ci: fix travis ordering for locales generation. (#1254) - * core: support running from other operating systems. (#1257) - * meta: version from git support. (#1260) - * shell_utils: code cleanup (#1265) - * meta: override the version with version-script. (#1267) - * tests: fix package version pinning tests. (#1269) - * New upstream release 2.29 (LP: #1684965) - - [ Joe Talbott ] - * tests: Fix staging store test for Tracks. (#1225) - * pluginhandler: factor out state bits into the state package. (#1221) - * repo: track per-part build-packages. (#1193) - * sources: add VCS asset tracking (#1229) - - [ Facundo Batista ] - * store: better check of the error code in StatusTracker. (#1226) - - [ Leo Arias ] - * tests: add arm64 to the nightly tests (#1210) - * tests: update name registration window limit test. (#1224) - * ci: update the location of the upstream retry autopkgtests script. (#1243) - * tests: use launchpad as the source of the compressed test snap (#1259) - - [ Jumpei Ogawa ] - * repo: enable KDE Neon. (#1244) - - [ Kyle Fazzari ] - * catkin plugin: create completely valid environment (#1239) - - [ edvega ] - * help: replace dashes with underscores when printing plugins help (#1241) - * cli: remove empty lines in the unclean parts message (#1251) - - [ Celso Providelo ] - * store: new registration errors and test update. (#1247) - - [ Christian Dywan ] - * lxd: refactor Cleanbuilder into Containerbuild and add Project. (#1230) - - [ Parameswaran Sivatharman ] - * store: plumbing for collaboration support. (#1246) - - [ Colin Watson ] - * ant plugin: honour proxy configuration (#1256) - - [ Chris MacNaughton ] - * rust plugin: update plugin to set RUSTFLAGS (#1255) - - [ Andy Li ] - * docs: fixed links to doc. (#1250) - * tests: report coverage only in unit tests. (#1266) - - -- Sergio Schvezov Thu, 20 Apr 2017 13:57:08 -0300 - -snapcraft (2.28) xenial; urgency=medium - - [ Sergio Schvezov ] - * python plugin: use stage headers if applicable. (#1156) - * docs: use correct target to generate docs. (#1159) - * packaging: snapcraft as a snap (#1158) - * tour: make it work when its a snap. (#1217) - * cleanbuild: don't copy cache into container. (#1216) - * kernel plugin: fix modprobe output parsing. (#1208) - * kernel plugin: use default per arch targets. (#1209) - * python plugin: support pbr/setup.cfg projects. (#1202) - * cleanbuild: packaging independent detection. (#1199) - * tests: remove repo.Ubuntu patch for plugins. (#1194) - * store: enable delta uploads by default. (#1196) - * repo: refactor into a package. (#1192) - * core: resolve ld link first. (#1189) - * store: enable retries for store calls. (#1184) - * New upstream release 2.28 (LP: #1675391) - - [ Joe Talbott ] - * store: set User-Agent header in store requests. (#1188) - * store: Add track support to commands. (#1161) - * state: asset tracking - store versions of stage-packages. (#1142) - * store: remove 'Series' from channel map output (#1151) - * repo: support versioned stage-packages. (#1157) - * parser: use the project loader code to find the origin's snapcraft.yaml (#1141) - * cli: rename `history` to 'list-revisions' with `revisions` alias. (#1160) - * repo: add version support for build-packages (#1185) - * project: use a more likely to be found global build-package (#1218) - - [ Paolo Pisati ] - * demos: add the minimal config changes to boot a dragonboard410c (#1147) - * kernel plugin: if no dtb target is set when the arch is arm.* build them all. (#1148) - * kernel plugin: rework MAKEFLAGS from the environment (#1150) - - [ Kyle Fazzari ] - * contribution guide: add commit message template (#1153) - * demos: make ROS demos support exiting after success. (#1201) - * catkin plugin: support building with an underlay. (#1140) - * demos: add ROS content sharing demo. (#1186) - * repo: fixup with python, not sed (#1181) - * repo: ignore symlinks to libc. (#1174) - * sources: update documentation for source-subdir (#1177) - - [ Olivier Tilloy ] - * project: expose parallel_build_count to scriptlets. (#1154) - - [ Leo Arias ] - * docs: build and push the API docs to github pages. (#1126) - * tests: pass the autopkgtest secret to the container (#1162) - * tests: update the ftp source for integration test (#1169) - * ci: install wget in the container that triggers the beta tests. (#1167) - * demos: add a message to exit the mosquitto subscriber. (#1173) - * demos: add the mount-observe plug to be able to run godd. (#1172) - * tests: support bzr branches for external tests. (#1128) - * docs: update the directory where the API pages are generated (#1163) - * demos: remove the tomcat demo snap. (#1176) - * tests: make the kernel unit tests architecture independent. (#1178) - * tests: support snap directory in external tests (#1180) - * tests: run the master tests against the staging server (#1164) - * tests: take into account the new current link. (#1187) - * ci: run the CLA check in a docker container. (#1191) - * tests: add manual tests for the kernel snaps (#1198) - * ci: allow to run individual autopkgtest suites (#1200) - * tests: expect failures for the tests that can't be run in arm64. (#1145) - - [ Jonathan Cave ] - * plainbox-provider plugin: run validate (#1095) - - [ Michael Hudson-Doyle ] - * core: fix symlink resolution in get_core_dynamic_linker. (#1170) - - [ pachulo ] - * sources: add optional "source-checksum" property (#980) - - [ Colin Watson ] - * godeps plugin: add git to build-packages. (#1179) - - -- Sergio Schvezov Thu, 23 Mar 2017 16:11:14 -0300 - -snapcraft (2.27.1) xenial; urgency=medium - - [ Kyle Fazzari ] - * catkin plugin: produce build-ready staging area. (#1130) - - [ Leo Arias ] - * ci: trigger beta tests on travis every day. (#1144) - - [ Joe Talbott ] - * store: switch Track and Arch in the channel map display. (#1143) - - [ Sergio Schvezov ] - * Revert "repo: remove symlinks to libc. (#1100)" (#1146) - * New upstream release 2.27.1 (LP: #1665759) - - -- Sergio Schvezov Fri, 17 Feb 2017 19:58:48 +0000 - -snapcraft (2.27) xenial; urgency=medium - - [ Sergio Schvezov ] - * core: switch to using rpath for classic confinement. (#1094) - * python plugin: do the right thing with classic. (#1093) - * meta: support for the environment keyword. (#1103) - * meta: correct the deprecation for `setup/gui` use. (#1107) - * tests: avoid snapping progress to leak into report. (#1114) - * core: setup core to support classic confinement. (#1112) - * cleanbuild: allow talking to a remote. (#1121) - * plainbox-provider plugin: filter out sitecustomize. (#1139) - * New upstream release 2.27 (LP: #1663229) - - [ Kyle Fazzari ] - * schema,copy plugin: better errors when item has no value. (#1096) - * cleanbuild: include snap directory in tarball. (#1102) - * repo: remove symlinks to libc. (#1100) - * catkin plugin: don't pass args to setup.sh. (#1099) - * pluginhandler: support more complex stage-packages. (#1059) - * repo: cache stage packages for faster future use. (#1122) - - [ Leo Arias ] - * tests: do not rely on project_dir. (#1078) - * lifecycle: print the command needed to clean the dirty part (#1097) - * tests: check the CLA on all the commits. (#1106) - * tests: rename the integration test snaps. (#1105) - * misc: consistently use a dash for copyright years. (#1101) - * tests: rename the waf test snap. (#1111) - * ci: fix the env vars passed to the docker container in travis. (#1125) - * python plugin: exclude the RECORD files. (#1138) - - [ Marco Trevisan ] - * meta: properly get the icon extension from splitted name. (#1092) - * tests: fix or ignore PEP8 static errors. (#1085) - - [ John Lenton ] - * ux: print the version on startup when running with --debug (#1086) - - [ Dan Chapman ] - * qbs plugin: add plugin support for the Qt Build Suite (qbs). (#1079) - - [ Bayard Randel ] - * store: implement delta uploads in push. (#940) - - [ Joe Talbott ] - * store: expose store errors to the cli. (#1110) - * Make git pulls less error prone due to history changes. (#1052) - * store: add support for tracks in channels. (#1108) - - [ MZN Lab ] - * meta: allow `post-stop-command` for an app entry in `apps`. (#1109) - - [ Loïc Minier ] - * kernel plugin: fix kernel snap layout (#1115) - - -- Sergio Schvezov Thu, 09 Feb 2017 12:55:44 +0000 - -snapcraft (2.26) xenial; urgency=medium - - [ Sergio Schvezov ] - * misc: remove snapd "submodule". (#1051) - * meta: ensure snap.yaml is desktop free. (#1053) - * godeps plugin: support for go-packages. (#1057) - * store: proper error colors for login failures. (#1055) - * project: snapcraft.yaml in a snap directory. (#1073) - * project: new plugin directory location. (#1082) - * project: support for gui in snap. (#1083) - * New upstream release 2.26 (LP: #1656291) - - [ Shawn Wang ] - * store: fix sso_host for dev sso. (#1031) - - [ Matthew Aguirre ] - * gradle plugin: update gradle plugin to support both gradle and - gradlew. (#1024) - - [ Leo Arias ] - * ci: use python2 to check the CLA (#1044) - * ci: remove the old tarmac file. (#1072) - * ui: add a space after the developer agreement prompt (#1065) - * tests: use a temporary directory for snaps tests. (#1068) - * tests: increase the timeout for the ros snap test, decrease the rest (#1067) - * tests: add ubuntu user to sudoers on every adt platform. (#1091) - - [ Kyle Fazzari ] - * schema: print allowed length for length failures. (#1056) - * tour: add g++ as dependency to 01-reusable-part. (#1062) - * file_utils: copy symlinks to directories as symlinks. (#1063) - * repo: add multiarch support for stage packages. (#1050) - * catkin plugin: don't stage compilers. (#1070) - * sources: preserve symlinks to directories. (#1081) - - [ Bjorn Tillenius ] - * python plugin: download all python packages using one command. (#1071) - - [ Olivier Tilloy ] - * meta: proper Exec key in desktop files for apps named like their parent - package. (#1061) - * meta: rename desktop files to match the app names. (#1074) - - [ Marco Trevisan ] - * options: use actual userspace architecture for building in 32bit (#1060) - * autotools plugin: extend Make plugin instead of repeating code (#1076) - - [ Daniel Watkins ] - * tests: don't wait for lxd networking in cleanbuild test. (#1087) - - [ Joe Talbott ] - * parser: handle parser and network errors. (#1045) - * parser: use an XDG directory for sources. (#1032) - * parser: return an error code if an origin is missing a part. (#1058) - - -- Sergio Schvezov Fri, 27 Jan 2017 20:38:10 +0000 - -snapcraft (2.25) xenial; urgency=medium - - [ Joe Talbott ] - * parser: better message for missing snapcraft.yaml in origins. (#985) - * parser: clean up help. (#986) - * parser: improve output. (#984) - * pluginhandler: ensure staged files are included in the prime step. (#920) - * tests: add aliases integration test. (#1004) - - [ Matthew Aguirre ] - * Update ant plugin to use get_build_properties(). (#991) - * Updated autotools plugin to use get_build_properties(). (#999) - * godeps plugin: update godeps plugin to use get_pull_properties(). (#1000) - * catkin plugin: update catkin plugin to use get_pull_properties(). (#1001) - * make plugin: update make plugin to use get_build_properties(). (#992) - * nodejs plugin: update nodejs plugin to use get_build_properties(). (#996) - * go plugin: update go plugin to use get_build_properties(). (#997) - * python plugin: update python plugin to support get_pull_properties(). (#1002) - * qmake plugin: update qmake to use get_build_properties() and - get_pull_properties(). (#993) - * gulp plugins: update gulp plugin to use get_build_properties(). and - get_pull_properties(). (#998) - * kernel plugin: update kernel plugin to use get_build_properties(). (#995) - - [ Kyle Fazzari ] - * copy plugin: update copy plugin to use get_build_properties(). (#1022) - * tests: add alias integration test. (#1021) - * sources: support symlinks in deb sources. (#941) - * schema: replace `snap` filter with `prime` filter. (#1006) - * tests: reorganize plugin tests into subdirectory. (#1014) - * tests: reorganize command tests into subdirectory. (#1015) - * tests: reorganize state tests into subdirectory. (#1019) - * pluginhandler: add support for disabling system library migration. (#989) - * docs: document the `notify` daemon type. (#1041) - * docs: describe hooks. (#1042) - * lifecycle: clean without parsing if possible. (#1007) - * core: add support for hooks. (#1026) - - [ Marco Trevisan ] - * travis.yaml: use docker exec to split build phases. (#1012) - - [ Leo Arias ] - * tests: fix integration tests in armhf. (#971) - * ci: add a checklist in the pull request template. (#972) - * ci: check the license agreement on Travis. (#973) - * tests: fix snaps tests in armhf. (#990) - * rust plugin: add conditional compilation. (#1029) - * tests: use rust 1.12 to test the rust-revision in armhf. (#1040) - * rust plugin: use the part source path. (#1035) - * misc: delete bzr ignore. (#1038) - - [ Jonathon Love ] - * nodejs plugin: fix the plugin’s dependency installation. (#1023) - - [ Olivier Tilloy ] - * meta: add 'desktop' entry for apps. (#1010) - - [ Sergio Schvezov ] - * project loader: better error message for classic. (#1025) - * tests: fix broken unit test in master. (#1027) - * parts: better error message when defining parts. (#1036) - * schema: support the notify daemon type. (#1037) - * godeps plugin: work when GOBIN is set. (#1046) - * meta: support core libraries. (#1047) - * docs: update deprecation links. (#1048) - * rust plugin: respect fetch parameters. (#1034) - * New upstream release 2.25 (LP: #1656291) - - [ Bayard Randel ] - * store: implement push pre-check. (#1009) - - -- Sergio Schvezov Fri, 13 Jan 2017 12:07:10 +0000 - -snapcraft (2.24) xenial; urgency=medium - - [ Sergio Schvezov ] - * meta: support classic confinement (#946) - * project: support building classic snaps (#949) - * pluginhandler: convert to package (#954) - * tests: idempotent store installs. (#956) - * demos: do not force arch for the tomcat demo (#976) - * pluginhandler: install scriptlet support (#960) - * pluginhandler: prepare scriptlet support (#987) - * pluginhandler: build scriptlet support (#988) - * New upstream release 2.24 (LP: #1650632) - - [ Leo Arias ] - * tests: fix unittests for armhf (#945) - * tests: Use a more stable ftp source (#950) - * tests: use testtools as the base of all unit tests (#948) - - [ Marco Trevisan ] - * parser: add support for origin-{branch,commit,tag} (#931) - - [ Chris MacNaughton ] - * rust plugin: fetch dependencies in pull (#908) - - [ Larry Price ] - * file_utils: catch PermissionError when attempting to replace contents in - a readonly file (#892) - - [ Albert Astals Cid ] - * sources: add source value to error message (#958) - * cli: make plugins be an alias of list-plugins (#959) - - [ Joe Talbott ] - * meta: add `aliases` support to `apps` (#947) - * parser: Use the same version method as 'snapcraft' (#982) - - [ Kyle Fazzari ] - * sources: convert to package. (#955) - * sources: refactor base sources into module. (#957) - * sources: refactor bazaar source into module. (#962) - * sources: refactor deb source into module. (#963) - * sources: refactor local source into module. (#961) - * sources: refactor git source into module. (#964) - * sources: refactor mercurial source into module. (#965) - * sources: refactor rpm source into module. (#966) - * sources: refactor script source into module. (#967) - * sources: refactor subversion source into module. (#968) - * sources: refactor tar source into module. (#969) - * sources: refactor zip source into module. (#970) - * pluginhandler: prefer in-snap libraries to system libraries. (#977) - - [ Celso Providelo ] - * cli: implementing '[list-]registered' command (#890) - * store: support download and validate on branded stores. (#981) - - [ Matthew Aguirre ] - * maven plugin: implement get_build_properties() (#974) - * gradle plugin: implement get_build_properties() (#975) - * cmake plugin: implement get_build_properties() (#978) - * waf plugin: implement get_build_properties() (#979) - * scons plugin: implement get_build_properties() (#983) - - -- Sergio Schvezov Fri, 16 Dec 2016 17:10:40 +0000 - -snapcraft (2.23) xenial; urgency=medium - - [ Sergio Schvezov ] - * indicators: work with Content-Encoding set (#896) - * store: download without login (#903) - * readme: rocket instead of irc (#907) - * indicators: support TERM=dumb (#906) - * Update HACKING.md (#910) - * help: update stage-packages and build-packages (#913) - * meta: icon is now dedeprecated (#914) - * cache: cleanup logic to pass project name (#915) - * sources: missing command instead of package (#916) - * docs: update GUI related integrations (#919) - * pluginhandler: source management moved to the core (#923) - * store: return specific error when already owned (#942) - * nodejs plugin: install during pull to support npm run (#936) - * New upstream release 2.22.1 (LP: #1646993) - - [ Parameswaran Sivatharman ] - * store: login with option to agree to terms of service and human friendly errors (#866) - - [ Bayard Randel ] - * cache: snap revision caching on 'push'. (#889) - * deltas: migrate from xdelta to xdelta3. (#934) - - [ Leo Arias ] - * tools: new script to retry autopktests (#893) - * tests: remove the outdated all snaps image set up for snaps tests (#911) - * plugin: add a gradle demo and test (#912) - * tests: unset the proxies for gradle unit testing (#922) - * tests: use the connectivity check page to test the downloader snap (#925) - * tests: add a script to build external snaps (#927) - * tests: replace coveralls with codecov (#939) - * tests: replace subTests with TestScenarios (#943) - - [ Marco Trevisan ] - * repo: apt-mark new build-packages as automatically installed (#891) - * sources: add ftp support (#900) - * parser: support remote dependencies (#930) - * sources: add current dir to ignore list if we're iterating on parent (#926) - - [ Kelvin Li ] - * cache: enable prune for snap cache. (#905) - * deltas: initial implementation (#918) - - [ Celso Providelo ] - * cli: basic 'enable-ci' implementation. (#901) - * cli: implement `enable-ci travis --refresh` command (#932) - - [ John Lenton ] - * cli: _filedir takes an extension, not a glob (#933) - - [ Larry Price ] - * cmake plugin: utilize MakePlugin build logic within CMakePlugin (#935) - - [ Kyle Fazzari ] - * pluginhandler: incorporate all part properties into state tracking. (#937) - - -- Sergio Schvezov Fri, 02 Dec 2016 23:44:19 +0000 - -snapcraft (2.22.1) xenial; urgency=medium - - * Revert "Allow for architecture-specific packages (#876)" (#898) - * New upstream release 2.22.1 (LP: #1640585) - - -- Sergio Schvezov Fri, 11 Nov 2016 19:28:41 +0000 - -snapcraft (2.22) xenial; urgency=medium - - [ Sergio Schvezov ] - * Support for gadget snaps (#886) - * Remove license concepts (#880) - * store: send snapcraft version in a header (#894) - - - [ Bayard Randel ] - * repo: cache apt related files (config, packages) in 'apt' parent - directory. (#887) - - [ Kyle Fazzari ] - * catkin plugin: python nodes require gcc/g++ too. (#881) - - [ Neal Gompa ] - * sources: Add RPM source (#870) - - [ Stéphane Graber ] - * Always respect go-buildtags (#888) - * Allow for architecture-specific packages (#876) - - -- Sergio Schvezov Wed, 09 Nov 2016 19:50:29 +0000 - -snapcraft (2.21) xenial; urgency=medium - - [ Sergio Schvezov ] - * pluginhandler: parametrize call args (#868) - * python plugin: wheel and install in the proper order (#883) - * New upstream release 2.21 (LP: #1638513) - - [ Stéphane Graber ] - * sources: Add new `source-commit` field for VCS sources (#877) - - [ Kyle Fazzari ] - * python plugin: remove pip packages when cleaning pull. (#882) - - [ Chris Wayne ] - * Add some further bash-completion (#872) - - -- Sergio Schvezov Wed, 02 Nov 2016 10:11:37 +0000 - -snapcraft (2.20) xenial; urgency=medium - - [ Colin Watson ] - * Add `snapcraft create-key` (#824) - - [ Kyle Fazzari ] - * catkin plugin: add `rosdistro` to help documentation. (#850) - * catkin plugin: nicely handle an invalid rosdistro. (#851) - - [ Christian Ehrhardt ] - * Add Waf plugin (#716) - - [ Maximiliano Bertacchini ] - * Add `snapcraft history`. (#844) - * Add `snapcraft status`. (#845) - - [ Daniel Holbach ] - * Document the `grade` option. (#857) - - [ Celso Providelo ] - * Implementing channel-closing. (#847) - * `sign-build` to prompt users for key selection (#848) - * history/status alignment fixes. (#863) - - [ Leo Arias ] - * In the downloader demo test, use https (#860) - - [ Stéphane Graber ] - * Add powerpc (32bit big-endian) support (#862) - - [ Roberto Alsina ] - * gated and validate commands message improvements (#849) - * Handle 'broken' validations that don't match refresh-control (#867) - - [ Domas Monkus ] - * go plugin: Set GOBIN in the build environment. (#859) - - [ Robert Ancell ] - * Update documentation links to developer.ubuntu.com that now redirect to - snapcraft.io (#864) - - [ Daniel Holbach ] - * Bring docs/upload-your-snap.md in line with http://snapcraft.io/docs/… (#869) - - [ Sergio Schvezov ] - * Simplify the parser tests. (#852) - * Simplify the handler from uri tests. (#853) - * tools: script to talk to the staging servers (#855) - * Decouple state handling from plugin options. (#854) - * python plugin: allow usage of bzr (#858) - * python plugin: install from wheel for local setup.py (#861) - * New upstream release 2.20 (LP: #1636924) - - -- Sergio Schvezov Wed, 26 Oct 2016 13:58:31 -0300 - -snapcraft (2.19) xenial; urgency=medium - - [ Sergio Schvezov ] - * python plugin: only replace proper shebangs. (#830) - * yaml xpath for errors. (#828) - * sources: fix type when calling with depth (#829) - * kernel plugin: allow collecting the same mod deps (#837) - * pluginhandler: take the file encoding into account. (#838) - * python plugin: only download in pull and build in build. (#832) - * New upstream release 2.19 (LP: #1629472) - - [ Christopher James Halse Rogers ] - * nodejs plugin: Add mechanism to run `npm run` commands. (#810) - - [ Daniel Holbach ] - * Add links to IRC, mailing list and social media (#833) - - [ Celso Providelo ] - * 'sign-build' implementation. (#831) - - [ Kyle Fazzari ] - * catkin plugin: build with in-snap python. (#840) - * catkin plugin: Support ROS Kinetic. (#842) - - [ Roberto Alsina ] - * 'snapcraft validate' and 'snapcraft gated' implementation (#813) - - [ Joe Talbott ] - * Replace SNAPCRAFT_PART_INSTALL in the part attributes. (#841) - - -- Sergio Schvezov Fri, 30 Sep 2016 17:56:06 -0400 - -snapcraft (2.18.1) xenial; urgency=medium - - [ Carlo Lobrano ] - * snapcraft search: return ordered results (#820) - - [ Sam Yaple ] - * python plugin: Don't filter .pth files (#822) - - [ Sergio Schvezov ] - * Make copies of remote parts to avoid ordering issues (#821) - * plainbox-provider plugin: rewrite python shebangs (#823) - * New upstream release 2.18.1 (LP: #1626224) - - -- Sergio Schvezov Fri, 23 Sep 2016 13:50:26 -0300 - -snapcraft (2.18) xenial; urgency=medium - - [ Sam Yaple ] - * Check if option is url for pip (#785) - * Unify python plugin test (#793) - - [ Sergio Schvezov ] - * Improve lifecycle execution of steps (#794) - * Load project information in one location (#799) - * Replace uses of copy with dump (#798) - * Report the proper line number on bad yaml errors (#816) - * Support deb files as sources (#811) - * lxd: use built-in image streams. (#817) - * Make source-depth a parameter and opt-in (#814) - * New upstream release 2.18 (LP: #1626224) - - [ Kyle Fazzari ] - * Only discover dependencies once per file. (#796) - - [ Leo Arias ] - * In the busybox test, use the last installed data dir (#801) - * Add the TEST_STORE environment variable to the travis script (#802) - - [ Evan ] - * autotools plugin: Do not run the bootstrap directory as a script. (#803) - - [ Jonathon Love ] - * nodejs plugin: fix to install of dev-deps in package.json (#800) - - [ Colin Watson ] - * Fix parts integration tests (#807) - * Remove snapcraft.storeapi.common (#806) - * Adjust login to conform to UX specification (#804) - * Refresh discharge macaroon if necessary (#805) - - [ Joe Talbott ] - * Don't litter /tmp with test artifacts. (#808) - * Handle missing source type packages in the parser (#742) - - [ James Lucas ] - * make plugin: allow copying artifact dirs (#809) - - [ Enrique Hernández Bello ] - * Enable crosscompilation for dump and copy plugins (#791) - - [ Celso Providelo ] - * Replacing deprecated API for searching snaps. (#815) - - -- Sergio Schvezov Wed, 21 Sep 2016 15:55:08 +0000 - -snapcraft (2.17) xenial; urgency=medium - - [ Sergio Schvezov ] - * Python plugin improvements (#771) - * Organize without removing files on target (#780) - * Support globbing for organize (#784) - * New upstream release 2.17 (LP: #1621985) - - [ Leo Arias ] - * Use --force-dangerous when testing the installation of snaps (#778) - * Updated the contents of the snapcraft init template (#776) - * Add an integration test for autotools (#783) - - [ Colin Watson ] - * Implement basic form of `snapcraft register-key` (#726) - * Add `snapcraft list-keys` (#786) - - [ Joe Talbott ] - * parser - Handle project name and version variables (#773) - - [ Sam Yaple ] - * python plugin: do not compile pyc files when installing with pip (#779) - - [ Alexandre Abreu ] - * gulp plugin: fix the npm install prefix (#782) - - -- Sergio Schvezov Fri, 09 Sep 2016 19:25:24 +0000 - -snapcraft (2.16) xenial; urgency=medium - - [ Sergio Schvezov ] - * Allow registering private packages (#749) - * Allow debugging cleanbuild runs. (#740) - * storeapi: remove dependency on the 'success' attr (#750) - * Export important project variables (#757) - * Use a recursive iglob for filesets (#765) - * HACKING.md: list dependencies to install with pip (#770) - * Minor fixes for typos in --help (#769) - * Better file conflict message (#766) - * Update kernel meta to the latest spec. (#768) - * New upstream release 2.16 (LP: #1619776) - - [ Joe Talbott ] - * parser - Remove namespacing and subparts. (#705) - * Use block style for multiline YAML values. (#756) - - [ Leo Arias ] - * Enable the static checks for the integration tests (#755) - * Add the arm64 architecture to the nodejs plugin (#753) - * go plugins: remove the useless call to os.path.join (#759) - * Removed statements not used by the tests (#760) - * Make a copy of the snapcraft files common list before using it. (#767) - - [ Caio Begotti ] - * Implementing 'grade' support in snapcraft.yaml (#764) - - [ Scott Moser ] - * node plugin: run build in pull phase to download dependencies. (#762) - - [ Paolo Pisati ] - * kernel plugin: vmlinuz -> kernel.img hard link (#748) - - [ Loïc Minier ] - * Include /sbin and usr/sbin in wrappers (#763) - - [ Evan Dandrea ] - * ant plugin: add properties and build target options (#752) - - -- Sergio Schvezov Fri, 02 Sep 2016 19:32:40 +0000 - -snapcraft (2.15.1) xenial; urgency=medium - - [ Robert Ancell ] - * Remove .la files generated by autotools (#670) - - [ Joe Talbott ] - * Only show chown warnings with --debug. (#739) - * Actually return a non-zero error code on errors. (#745) - - [ Sergio Schvezov ] - * make plugin: fix artifact collecting (#743) - * testing: only run the ros demo on xenial (#744) - * New upstream release 2.15.1 (LP: #1614322) - - -- Sergio Schvezov Fri, 19 Aug 2016 13:43:42 -0300 - -snapcraft (2.15) xenial; urgency=medium - - [ Harald Sitter ] - * add multiple generator script options to autotools (#679) - - [ Sam Yaple ] - * python2 plugin: Add support for constraints (#657) - - [ Blake Rouse ] - * Add option disable-parallel for all plugins. (#698) - * python3 plugin: allow setup.py to work on some projects. (#718) - - [ Christian Ehrhardt ] - * fix checker errors to let runtests.sh pass again (#717) - - [ Dan Watkins ] - * python plugins: add process-dependency-links option (#734) - - [ Leo Arias ] - * Start the fake parts server only in the tests that need it. (#720) - (LP: #1611828) - * In travis, install the static checkers with pip. (#724) - * go plugin: add a go-buildtags property to the plugin (#733) - - [ Joe Talbott ] - * Set owner/group for directories in stage and prime (#723) - * parser - Return non-zero code for wiki errors. (#658) - * Factor parts config into snapcraft/internal/parts.py (#731) - - [ Kyle Fazzari ] - * Use link_or_copy instead of just hard-links for deb cache. (#728) - * Support having the snapcraft.yaml in a subdir. (#725) - * Dump plugin: Don't remove install directory. (#729) - - [ Jason Hobbs ] - * Add artifacts option to make plugin. (#715) - - [ Michael McCracken ] - * Clarification of make plugin help text (#730) - - [ Sergio Schvezov ] - * Globally cache stage-packages (#677) - * Remove store dispute logic (#732) - * New upstream release 2.15 (LP: #1614322) - - -- Sergio Schvezov Wed, 17 Aug 2016 17:47:44 -0300 - -snapcraft (2.14) xenial; urgency=medium - - [ Joe Talbott ] - * Add python package dependencies to setup.py (#550) - * parser - Support .snapcraft.yaml files. (#618) - * Preserve file ownership for 'os' snaps. (#690) - - [ Marius Gripsgard ] - * Add new plugin: rust (#579) - - [ Sam Yaple ] - * Fix python2 compile cache removal (#660) - * Improve python2 test coverage (#663) - - [ Stéphane Graber ] - * Implement "oneshot" daemon type (#693) - - [ Bogdana Vereha ] - * Replace 'strip' with 'prime' on intro page and step options. (#696) - - [ Kyle Fazzari ] - * Rewrite shebangs generated by the python plugins. (#680) - * Add godeps plugin. (#691) - * Catkin plugin: Enforce C.UTF-8 locale. (#497) - - [ Blake Rouse ] - * Pass --root to the python3 plugin on build (#694) - * Add make-install-var field to the make plugin. (#711) - - [ Sebastian Bacher ] - * Use the new snapcraft.io mailing list as contact information (#702) - * Update the completion list of commands (#703) - * Slightly tweak the "no jar files" errors (#704) - - [ Liu XiaoGuo ] - * Added "camera" plug into the example (#706) - - [ Simon Quigley ] - * Update the debian/control file (#662) - * Add detection for an existing `.snapcraft.yaml` file before running - `snapcraft init` (#669) - - [ Michael Hall ] - * Strip common path prefixes from linkname as well as name when extracting - a tarfile. (#688) - - [ Leo Arias ] - * Use requirements files in travis tests (#656) - * Enable integration tests to run in the production store (#687) - - [ Daniel Watkins ] - * Make formatting of daemon options consistent (#710) - - [ Sergio Schvezov ] - * Run tests from /tmp (#684) - * Use the proper requirements.txt path (#707) - * Improve the go plugins usability (#709) - * kernel plugin: kernel targets depending on debarch (#689) - * New plugin: dump (#712) - * rust plugin: mock downloads in unit tests (#713) - * New upstream release 2.14 (LP: #1611009) - - -- Sergio Schvezov Mon, 08 Aug 2016 14:58:39 +0000 - -snapcraft (2.13.1) xenial; urgency=medium - - [ Joe Talbott ] - * parser - Add handling for carriage returns in wiki (#681) - - [ Sergio Schvezov ] - * New upstream release 2.13.1 (LP: #1605107) - - -- Sergio Schvezov Mon, 25 Jul 2016 13:27:29 +0200 - -snapcraft (2.13) xenial; urgency=medium - - [ Colin Watson ] - * Make maven plugin honour https_proxy and proxy authentication (#642) - - [ Dustin Kirkland ] - * add a link for filing bugs/issues (#647) - - [ Joe Talbott ] - * parser - Handle malformed wiki entries. (#636) - * Use '/usr/bin/env python3' (#649) - * parser - Don't allow duplicate wiki entries. (#613) - * Preserve the ordering of the wiki entries (#596) - - [ Leo Arias ] - * Add the test for the downloader snap (#589) - * Do not clean before running the snaps tests (#637) - - [ Jonathan Cave ] - * Create Plainbox Provider plugin (#609) - - [ Matthew Aguirre ] - * ant plugin: use python std libraries and add tests (#644) - * new plugin: gradle (#651) - * maven plugin: support maven targets (#650) - * Put options before jar task in command. (#655) - - [ Kyle Fazzari ] - * Support "never" as a daemon restart condition. (#659) - - [ Simon Quigley ] - * Fix four typos in `snapcraft help` (#667) - - [ Robin Winslow ] - * Fix broken links to https://snapcraft.io (#665) - - * Implemented `snapcraft release` (#648) - * Implement `snapcraft push` (#653) - * Allow / in parts (#666) - * Capture the correct exception when not being able to decode json (#672) - * Special handling for pc files for conflicts (#676) - * New upstream release 2.13 (LP: #1605107) - - -- Sergio Schvezov Thu, 21 Jul 2016 08:12:25 +0000 - -snapcraft (2.12.1) xenial; urgency=medium - - * New upstream release 2.12.1 (LP: #1600090) - - -- Sergio Schvezov Fri, 08 Jul 2016 13:31:34 -0300 - -snapcraft (2.12) xenial; urgency=medium - - [ Simon Quigley ] - * Change apt-get to apt in HACKING.md (#564) (LP: #1591421) - * Changed the mailing list in HACKING.md from snappy-devel to snapcraft - (#577) - * Add Subversion support (#567) (LP: #1543243) - - [ Joe Talbott ] - * Include 'maintainer' and 'description' in the parser output. (#565) - (LP: #1591199) - * Support "```" wiki code tags in the parser. (#569) (LP: #1592133) - * Add snapcraft-parser integration test. (#560) (LP: #1590268) - * Make most wiki fields required. (#581) (LP: #LP: #1592133) - * Add more info about reusable parts. (#527) (LP: #1582499) - * Allow parts without a 'source' entry. (#599) - - [ Sergio Schvezov ] - * New plugin: gulp (#563) (LP: #1575880) - * Support for a hidden snapcraft.yaml (#582) (LP: #1587933) - * Implement `snapcraft update` for parts (#588) (LP: #1594643) - * Support updating cache without content-length (#598) (LP: #1595610) - * Switch from local copy to the proper python package. (LP: #1590813) - * Integrate with new remote parts (#590) (LP: #1594976) - * Implement `snapcraft define` for parts (#594) (LP: #1594643) - * Update the remote parts cache before demo tests (#604) (LP: #1596114) - * Add missing build-packages for rpath test (#605) (LP: #1596114) - * Implement snapcraft search (#608) (LP: #1596222) - - [ Bayard Randel ] - * Ignore .eggs dir. (#572) - - [ Leo Arias ] - * Use pexpect when testing the building of snaps (#573) (LP: #1592943) - * Allow to run a subset of integration tests. (#576) (LP: #1593009) - * Improve the store errors returning exceptions (#585) (LP: #1594636) - * Add the register command (#586) (LP: #1595012) - * Improve error reporting (#591) (LP: #1588023) - * Fix the store update test to register a unique name (#595) (LP: #1595319) - * Use a xenial docker container for travis executions (#597) (LP: #1532213) - * Simplify the list plugins integration test (#607) (LP: #1596112) - * Remove the unittests from the autopkgtest execution (#600) (LP: #1596068) - - [ Evan Dandrea ] - * Make lxd containers ephemeral. (#578) (LP: #1577548) - - [ Daniel Holbach ] - * it's myapps.developer.ubuntu.com (#587) (LP: #1594844) - - [ Rob Loach ] - * Fix Snap icons for Demos (#574) - - [ Kyle Fazzari ] - * Add qmake plugin. (#566) (LP: #1574774) - * Don't copy libraries that are already in prime. (#580) (LP: #1570895) - * Make app names more restrictive. (#555) (LP: #1589613) - - -- Sergio Schvezov Mon, 27 Jun 2016 13:58:10 -0300 - -snapcraft (2.11) xenial; urgency=medium - - [ Leo Arias ] - * Make the lxd unit test pass in architectures other than amd64. (#552) - (LP: #1589747) - * Support running the snaps tests in multiple architectures (#554) - (LP: #1589779) - * Make the IsDirty unit test case pass in architectures other than - amd64. (#553) (LP: #1589752) - - [ Didier Roche ] - * Reenable get and set tourdir unit tests (#551) (LP: #1589782) - * Add finale tour examples and tests (#558) (LP: #1590903) - * Reenable tour command (#559) (LP: #1590906) - - [ Sergio Schvezov ] - * Fix or address lintian errors and warnings (#561) (LP: #1588980) - - -- Sergio Schvezov Thu, 09 Jun 2016 18:46:22 -0300 - -snapcraft (2.10.1) xenial; urgency=medium - - * Backwards compatible clean with strip (#556) (LP: #1590256) - - -- Sergio Schvezov Wed, 08 Jun 2016 16:32:27 -0300 - -snapcraft (2.10) xenial; urgency=medium - - [ Martin Wimpress ] - * Correct autotools tests to use configflags (#521) - - [ Leo Arias ] - * Run the integration tests against a local fake server when the user - password is not in the environment. (#511) (LP: #1585023) - * Move the login and logout methods to a client. (#518) (LP: #1586504) - * Improve the config handling. (#519) (LP: #1586511) - * Fix the one-time password login. (#529) (LP: #1586832) - * Moved the download to the store client. (#530) (LP: #1586836) - * Moved the upload to the store client. (#531) (LP: #1586836) - * Updated the documentation about the icon. (#542) (LP: #1578231) - * Improve the error message when a part binary is not found. (#541) - (LP: #1582367) - * Re-enable the ROS demo for autopackage testing. (#520) (LP: #1588098) - * Add macaroon support to login, upload and download. (#532) (LP: #1586910) - * Set the no_proxy environment variable to access the local fake servers. - (#546) (LP: #1588631) - - [ Stephen Stewart ] - * nodejs plugin: Support configurable node version (#509) (LP: #1586104) - - [ Kyle Fazzari ] - * Use correct cross-build packages for ppc64le. (#539) (LP: #1570944) - - [ Sergio Schvezov ] - * Support zip files as source (#523) (LP: #1577062) - * A nicer error message for incorrect stage-packages (#524) (LP: #1568131) - * Support the assumes keyword (#525) (LP: #1586429) - * Improve the template for snapcraft init (#528) (LP: #1575581) - * Filter out *.snap from sourcedir (#535) (LP: #1575628) - * Support setting a gopath for a go project from vcs (#538) (LP: #1583426) - * Add a ticker for snapping (#540) (LP: #1582955) - * Rename strip to prime (#543) (LP: #1582515) - - [ Didier Roche ] - * Wrap plugin list output content (#534) (LP: #1587057) - * Add snapcraft examples to scaffold getting started tour (#513) - (LP: #1586137) - - [ Joe Talbott ] - * Add support for parsing the parts wiki (#545) (LP: #1587583) - - -- Sergio Schvezov Fri, 03 Jun 2016 13:37:58 -0300 - -snapcraft (2.9) xenial; urgency=medium - - [ Leo Arias ] - * autopkgtests: run the install examples tests in classic. (#481) - (LP: #1572764) - - [ Matteo Bertini ] - * Fix typo in description of the python3 example. (#504) - - [ Jamie Bennett ] - * Documentation: Use plugs instead of caps. (#507) - - [ Chris Wayne ] - * Add in bash completion. (#453) (LP: #1570506) - - [ Sergio Schvezov ] - * Fail validation if plugs or slots are declared at the part level (#514) - (LP: #1581166) - - [ Kyle Fazzari ] - * Make pull and build steps dirty if target arch changes. (#450) - (LP: #1564192) - * Add support for the confinement property. (#501) (LP: #1580819) - * Add support for the epoch property. (#502) (LP: #1581113) - - -- Sergio Schvezov Tue, 24 May 2016 23:32:11 -0300 - -snapcraft (2.8.8) xenial; urgency=medium - - * Create the 'partial' directory for apt. (#499) (LP: #1578007) - - -- Sergio Schvezov Wed, 04 May 2016 14:22:58 -0300 - -snapcraft (2.8.7) xenial; urgency=medium - - * Don't fail if there is no system library list (#496) (LP: #1577750) - - -- Sergio Schvezov Tue, 03 May 2016 10:58:57 -0300 - -snapcraft (2.8.6) xenial; urgency=medium - - [ Leo Arias ] - * Import mock from unittest. (#492) (LP: #1576998) - - [ Sergio Schvezov ] - * Remove missing replacement for unittest.mock (#494) (LP: #1576998) - - -- Sergio Schvezov Sat, 30 Apr 2016 18:44:44 -0300 - -snapcraft (2.8.5) xenial; urgency=medium - - [ Leo Arias ] - * autopkgtests: run the tests using the installed package (#464) - (LP: #1570992) - * Remove --allow-unauthenticated from examples tests (#482) (LP: #1573211) - * Update the assertion of the example install (#483) (LP: #1573243) - * Examples tests: Update the path to the snaps binaries (#484) (LP: #1573349) - * Examples tests: use systemctl instead of the removed snap service (#485) - (LP: #1573697) - * Update the mosquitto example SNAP_USER_DATA path. (#486) (LP: #1574857) - * Update the busybox test to use the snap data path. (LP: #1574901) - * Remove the integration tests coverage. (#488) (LP: #1575383) - - [ Sergio Schvezov ] - * Don't clean target before extracting npm (#489) (LP: #1575876) - * Don't delete the nodejs download on build. (#490) (LP: #1575882) - - [ Vincent Ladeuil ] - * Make upload more robust by ignoring spurious errors while polling the - scan status. (#480) (LP: #1572963) - - [ Kyle Fazzari ] - * docs/get-started.md: Stop discussing snappy-tools. (#454) (LP: #1568113) - - -- Sergio Schvezov Fri, 29 Apr 2016 14:59:47 -0300 - -snapcraft (2.8.4) xenial; urgency=medium - - [ Sergio Schvezov ] - * Use series 16 by default instead of rolling-core (#477) (LP: #1572602) - * kernel plugin: iterate over modules to add to initrd (#476) (LP: #1572118) - - [ Kyle Fazzari ] - * Follow symlinks while hard-linking migrated files. (#478) (LP: #1572664) - - -- Sergio Schvezov Wed, 20 Apr 2016 19:15:32 -0300 - -snapcraft (2.8.3) xenial; urgency=medium - - [ Kyle Fazzari ] - * docs/intro.md: Fix broken developer reference link. (#456) (LP: #1558296) - * Nil plugin: Make purpose more clear in documentation. (#455) (LP: #1549691) - * Use colors for logging. (#474) (LP: #1572186) - - [ Leo Arias ] - * Update the snapcraft-coverage script to use the internal dirs (#471) - (LP: #1571696) - - [ Sergio Schvezov ] - * Fix shell quoting (#473) (LP: #1572129) - - -- Sergio Schvezov Tue, 19 Apr 2016 17:59:45 -0300 - -snapcraft (2.8.2) xenial; urgency=medium - - * Don't expose broken or fragile APIs to plugins (LP: #1571264) - * catkin plugin: use ProjectOptions for repo setup (#468) (LP: #1571446) - * qml plugin: remove broken plugin (#469) (LP: #1551343) - - -- Sergio Schvezov Mon, 18 Apr 2016 10:06:15 -0300 - -snapcraft (2.8.1) xenial; urgency=medium - - [ Simon Busch ] - * Correctly pass arguments to Cleanbuilder (#458) (LP: #1570706) - - [ Sergio Schvezov ] - * Only add cross build packages when cross compiling. (#451) (LP: #1570414) - * Fix store downloading (#452) (LP: #1569734) - - [ Kyle Fazzari ] - * Add support for s390x architecture. (#461) (LP: #1570835) - * Add SNAP_LIBRARY_PATH to LD_LIBRARY_PATH. (#462) (LP: #1570945) - * Kernel plugin: Support lzma/xz-compressed initrd. (#463) (LP: #1569337) - - -- Kyle Fazzari Fri, 15 Apr 2016 23:35:47 +0000 - -snapcraft (2.8) xenial; urgency=medium - - [ Didier Roche ] - * Support .git extension detection (LP: #1566153) - - [ Kyle Fazzari ] - * Copy plugin: Follow symlinks if outside of snap. (LP: #1532515) - * Lifecycle: Check to see if prerequisite is already built. (#431) - (LP: #1537790) - * Clean command: Don't automatically clean reverse dependencies. (#436) - (LP: #1568004) - * Library crawling: Make ldd failure non-fatal. (#440) (LP: #1569280) - * Automatically re-strip if dirty. (#442) (LP: #1477904) - * Automatically re-stage if dirty. (#443) (LP: #1477904) - * Detect dirty build step. (#447) (LP: #1477904) - * Detect dirty pull step. (#448) (LP: #1477904) - - [ Sergio Schvezov ] - * Precedence and library path ordering. (LP: #1548232) - * Converge all commands into one (#433) (LP: #1567058, #1567041) - * Make geoip optional and local sources a default (#430) - (LP: #1536691, #1561068) - * Remove global state attributes from snapcraft.common (#434) (LP: #1551849) - * Remove support and mentions of the config hook. (#437) (LP: #1568826) - * Remove `old-security`. Use proper interfaces. (#441) (LP: #1569452) - * Make meta a class in snapcraft.internal.meta (#444) (LP: #1551849) - * Move common directories to ProjectOptions (#445) (LP: #1551849) - * debian/control: Update Standards-Version. - - [ Leo Arias ] - * Add a test for the mosquitto example. (#418) (LP: #1564181) - * Update the snap command in examples tests. (#439) (LP: #1569401) - - [ Vincent Ladeuil ] - * VCS tools are test dependencies, they should be installed to run the - tests or the tests should be skipped. (#427) (LP: #1566882) - - -- Sergio Schvezov Wed, 13 Apr 2016 21:37:32 -0300 - -snapcraft (2.7) xenial; urgency=medium - - [ Joe Talbott ] - * make plugin: Add 'make_parameters' option. (LP: #1563535) - - [ Sergio Schvezov ] - * Clear icon path before trying to write the new one (LP: #1561331) - * Raise a nice error when downloading without a login (LP: #1560553) - * Explain the new way to provide snap icons. (LP: #1561327) - * Redo pkg-config handling with library collecting (LP: #1549570) - * Properly validate the schema (LP: #1564607) - * kbuild: always run defconf with -j1 (LP: #1564191) - - [ Leonardo Arias Fonseca ] - * integration tests: use a temporary config path (LP: #1562000) - * Upload snaps passing the snap path. (LP: #1563760) - - [ Vincent Ladeuil ] - * Fix test isolation issues. (LP: 1563965) - - [ Kyle Fazzari ] - * docs/plugins.md: Update for new plugin API. (LP: #1561524) - * Suggest a full clean if step clean is missing state. (LP: #1561328) - * Copy plugin: Add support for sources. (LP: #1559154) - - -- Sergio Schvezov Mon, 04 Apr 2016 23:54:56 -0300 - -snapcraft (2.6.1) xenial; urgency=medium - - [ Kyle Fazzari ] - * Clean: Leave local plugins alone. (LP: #1561546) - - -- Kyle Fazzari Thu, 24 Mar 2016 16:04:13 +0000 - -snapcraft (2.6) xenial; urgency=medium - - [ Leonardo Arias Fonseca ] - * libpipeline example test: update the binary name (LP: #1560254) - * busybox example: update the expected results (LP: #1560255) - - [ Sergio Schvezov ] - * Snaps of type os should not squash with -all-root (LP: #1557513) - * Support architecture all packages when snapping (LP: #1560481) - * Add missing build-packages to the busybox example (LP: #1560586) - * Fixed assets go into a setup dir (LP: 1560969) - * Include and library paths must exist (LP: #1558965) - - [ Kyle Fazzari ] - * Support per-step clean command. (LP: #1537786) - - -- Sergio Schvezov Thu, 24 Mar 2016 01:16:12 -0300 - -snapcraft (2.5) xenial; urgency=medium - - [ Leonardo Arias Fonseca ] - * examples_tests: added more tests after the snaps are installed. - (LP: #1545071) - * travis: run the tests in the GCE trusty machine. (LP: #1555386) - - [ Daniel Holbach ] - * Remove information about PPA. snapcraft and tools will be kept - up-to-date in xenial. (LP: #1554565) - - [ Scott Sweeny ] - * Remove Unicode quotes from help text (LP: #1555733) - - [ Sergio Schvezov ] - * Correct local source pulling (LP: #1558446) - * New plugin: kbuild (LP: #1558623) - * Support downloading snaps (LP: #1558970) - - [ Alexander Sack ] - * New plugin: kernel (LP: #1552168) - - [ Kyle Fazzari ] - * Improve per-step state tracking. (LP: #1558810) - * Migrate old state to new. (LP: #1560158) - - -- Sergio Schvezov Mon, 21 Mar 2016 20:38:18 -0300 - -snapcraft (2.4) xenial; urgency=medium - - [ Kyle Fazzari ] - * Catkin plugin: Print nice message if dependency is invalid. (LP: #1548370) - * Catkin plugin: Handle rosdep empty and multiple dependencies. - (LP: #1548406, #1548404) - * Support --debug flag to enable backtrace and debug logs. (LP: #1548492) - * CMake plugin: Automatically find staged libs and files. (LP: #1550778) - * Copy entire source, even if source-subdir is specified. (LP: #1549676) - * Autotools plugin: Support installing via prefix. (LP: #1544629) - * Support parallel builds (enabled by default). (LP: #1551417) - - [ Sergio Schvezov ] - * Clear error when specifying a non existing command (LP: #1550496) - * Skip the BasePlugin from plugin search (LP: #1547681) - * Expose the invalid build-package on error (LP: #1547672) - * Migrate from skills to interfaces (LP: #1549427) - * Do not print which's output to stdout (LP: #1551939) - * catkin plugin: combine deps as a union of sets (LP: #1552498) - * Support for a daemon's restart-condition. (LP: #1552160) - * Remove the useles decode done in tests. (LP: #1552403) - - [ Leonardo Arias Fonseca ] - * travis: clean up the lxd script (LP: #1550379) - * examples_tests: update the expected result of the libpipeline test - (LP: #1554637, #1554641) - - -- Sergio Schvezov Wed, 09 Mar 2016 17:31:26 +0700 - -snapcraft (2.3.2) xenial; urgency=medium - - * debian/control: lxd should be a suggestion, not a direct dependency. - (LP: #1550381) - - -- Sergio Schvezov Fri, 26 Feb 2016 11:59:46 -0300 - -snapcraft (2.3.1) xenial; urgency=medium - - [ Leonardo Arias Fonseca (¡paz y baile!) ] - * examples_tests/libpipeline: skip the output assertion when skipping the - install. (LP: #1550108) - - -- Sergio Schvezov Fri, 26 Feb 2016 03:47:11 -0300 - -snapcraft (2.3) xenial; urgency=medium - - [ Leonardo Arias Fonseca ] - * storeapi: reduce the mccabe complexity (LP: #1541011) - * catkin plugin: do not overwrite the PYTHONPATH (LP: #1544790) - * examples_tests: fix the libpipeline test. (LP: #1546771) - * travis: update the command to wait for the network (LP: #1549880) - - [ Daniel Holbach ] - * Generate reference from snapcraft's "help" command. (LP: #1544540) - - [ Kyle Fazzari ] - * Remove roscore plugin. (LP: #1539169) - - [ Sergio Schvezov ] - * New command cleanbuild (using lxd) (LP: #1480144) - * Calling snapcraft with no args defaults to snap (LP: #1548915) - * More attributes for the squashfs'ed snap (LP: #1546821) - * Catch a failure to detach for the log (LP: #1549831) - - -- Sergio Schvezov Thu, 25 Feb 2016 16:00:05 -0300 - -snapcraft (2.2.2) xenial; urgency=medium - - * Set the "after" test's cmake project to use no language (LP: #1544792) - * Disable ros example in adt tests (LP: #1544790) - - -- Sergio Schvezov Fri, 12 Feb 2016 17:04:18 -0300 - -snapcraft (2.2.1) xenial; urgency=medium - - * Add missing build-packages for the opencv example (LP: #1544347) - * Add missing libc6 build dep to integration tests (LP: #1544343) - * Replace dpkg-architecure calls in tests (LP: #1544340) - * Replacing the need for pyversions (LP: #1541451) - - -- Sergio Schvezov Thu, 11 Feb 2016 17:03:57 -0300 - -snapcraft (2.2) xenial; urgency=medium - - [ Leo Arias ] - * Add the unit tests to the autopkgtest suite. (LP: #1541421) - * Use docopt to handle example tests arguments. (LP: #1533395) - * integration-tests: add login and logout tests (LP: #1541391) - * integration_tests: add a successful upload test (LP: #1541391) - * travis: remove the coveralls token (LP: #1543675) - * examples_tests: decode the output with utf-8 (LP: #1543714) - - [ Daniel Holbach ] - * Clear APT::Update::Post-Invoke-Success so we don't run scripts like - update-motd-updates-available. (LP: #1541894) - * Import https://developer.ubuntu.com/snappy/guides/mir-snaps/ - into docs/mir-snaps.md (LP: #1541404) - * Use image link which actually works (raw.githubusercontent.com shows - SVG as raw XML, rawgit.com works as expected). (LP: #1542422) - * Document Snapcraft's wrapper script (steal text from App Dev Manual 15.04) - (LP: #1543581) - * Briefly explain the filesystem layout. (LP: #1543596) - * move image into docs/images (LP: #1542422) - - [ Kyle Fazzari ] - * Improve snapcraft.yaml validation errors. (LP: #1524663) - * Snap: When snapping a directory, look for snap.yaml. (LP: #1541987) - * Upload: Obtain name from YAML instead of snap package. (LP: #1541353) - - [ Sergio Schvezov ] - * Support setting an output file when snapping (LP: #1542479) - * adt fixes for maven and noninteractive deb install (LP: #1542511) - - -- Sergio Schvezov Wed, 10 Feb 2016 14:56:42 -0300 - -snapcraft (2.1.1) xenial; urgency=medium - - [ Kyle Fazzari ] - * Make requests-toolbelt and progressbar a Depends. (LP: #1541349) - - [ Sergio Schvezov ] - * Add gcc as build-packages to integration tests (LP: #1541332) - - -- Sergio Schvezov Wed, 03 Feb 2016 11:00:00 -0300 - -snapcraft (2.1) xenial; urgency=medium - - [ Leo Arias ] - * Run the tests in a xenial LXC. (LP: #1532213) - * Added a mosquitto example. (LP: #1537778) - * Refactored the local source pull. (LP: #1538250) - * Full coverage of meta.py with unit tests. (LP: #1538260) - * Do not report coverage for static and examples tests. (LP: #1538643) - * Add tests errors as details. (LP: #1538354) - * Fix the examples tests using testtools as the base. (LP: #1539196) - * Use the git http protocol in the examples tests. (LP: #1539139) - - [ Daniel Holbach ] - * Make sure that version is set correctly no matter what (version for - 2.0.1 release was '2.0') (LP: #1537580) - * Add document about how to specify app metadata (commands, daemons, - config). (LP: #1535905) - * Add document about debugging snaps. (LP: #1538016) - * Try to capture what the articles are about in a more concise and - clearer way. (LP: #1539502) - * Add more links between the individual articles. (LP: #1539540) - - [ Reto Breitenmoser ] - * tar-content plugin: Add capability to unpack into a specific - destination directory (LP: #1478055) - - [ Kyle Fazzari ] - * Make plugin: Support non-standard makefile name. (LP: #1500759) - * Add documentation for how to use snapcraft upload. (LP: #1539234) - * Upload: Clean up output. (LP: #1538692, #1539814) - - [ Alexander Sack ] - * Fix FD leakage by closing apt cache in repo.py (LP: #1537705) - - [ Ricardo Kirkner ] - * Added support for uploading snap packages directly to the Ubuntu - Store. (LP: #1538657) - * Make sure otp is included in the login request only if it's not - empty (LP: #1539208) - - [ Sergio Schvezov ] - * Support $SNAPCRAFT_STAGE for parts (LP: #1538688) - * debian/control: add python3-responses (LP: #1539199) - * Use the log fixture for logging by default for ut (LP: #1539817) - * Make package installation dynamic to reduce deps (LP: #1539146) - * Remove legacy formatting and implement skills (LP: #1539122) - - -- Sergio Schvezov Tue, 02 Feb 2016 10:24:04 -0300 - -snapcraft (2.0.1) xenial; urgency=medium - - [ Vamshi Balanaga ] - * Added a unit test for pulling bazaar sources from URIs. (LP: #1536283) - - [ Kyle Fazzari ] - * Autotools plugin: Call make before make install (LP: #1536700) - * Add build-package description to docs/snapcraft-sytax.md (LP: #1536659) - * Git source: Make a shallow clone. (LP: #1536696) - * Git source: Support submodules. (LP: #1536698) - - [ Daniel Holbach ] - * Remove 'vendor' from snapcraft init template. Explain that the icon is optional. - (LP: #1536860) - * Add documentation about writing your own plugins. (LP: #1535897) - - -- Sergio Schvezov Sun, 24 Jan 2016 09:22:04 -0800 - -snapcraft (2.0) xenial; urgency=medium - - [ Daniel Holbach ] - * Add document detailing the snapcraft.yaml syntax. - * Add missing periods and fixed typos on the docs. - * Remove vendor for 'master'. - * Add document about parts. - * Add link to config docs. - - [ Sergio Schvezov ] - * Removing the `run` command. - * Update the release for the tomcat example snap. - * Wrapping exe should not remove the path. (LP: #1523912) - * cli: Move to docopt, help cmd migrated. - * cli: list-plugins cmd migrated to docopts. - * cli: clean cmd migrated to docopts. - * cli: init cmd migrated to docopts. - * cli: pull command migrated to docopts. - * clean cmd: migrate to use new lifecycle attribs. - * Removing dead test code. - * cli: build command migrated to docopts. - * cli: stage command migrated to docopts. - * Remove dead code from lifecycle. - * cli: strip command migrated to docopts. - * Obsolete deprecations from 1.x. - * Removing the 'vendor' keyword. (LP: #1510522) - * Making the icon optional. - * cli: snap command migrated to docopts. - * Enabling testing with new cli. - * Fix ordering for conditional negations in catkin tests. - * Replace use of `assert_not_called` in the catkin tests. - * General documentation update. - * Add licensing support. (LP: #1527453) - * snap.yaml implementation with support for readme.md and package.yaml. - (LP: #1532842) - * Documentation refresh. (LP: #1533021) - * Support the new security-override semantics. (LP: #1533293) - * Remove ssh module from snapcraft. (LP: #1533400) - * Fix environment variable handling. (LP: #1531481) - * Wrap apps with the app name. (LP: #1534150) - * Replace SNAP envvars with the 16.04 ones. (LP: #1533552) - * Add bin paths to the to the shebang replace paths in repo (LP: #1534812) - * Initialize CMAKE_PREFIX_PATH to [] for ros (LP: #1535309) - - [ Leo Arias ] - * Added a test to check that the snapcraft files are not copied to the build dir. - * On autopkgtests, run the integration tests, build the examples but do not - install them, do not run the unittests. - * Translated the plainbox tests to python. - * Workaround the failure of the binaries in trusty. - * Updated the runtests script error message. - * Updated the autopkg integration tests. - * Handle the filter parameter for examples tests. (LP: #1533393) - * Split the static and unit tests. (LP: #1533397) - * Added the option to output subunit results in examples tests. (LP: #1533403) - * Updated the tomcat example network capability. (LP: #1533736) - * Added the network-listener capability. (LP: #1534784) - * Add a security-override for chown. - - [ Kyle Fazzari ] - * Catkin plugin: Add support for ROS tools. - * Add contribution guide. - * Resolve FIXME in Catkin plugin. - * Add a tutorial for turning a ROS project into a .snap. - * Catkin plugin: use rosdep for dependency resolution. - * Python{2,3} plugins: Make site-packages link relative. (LP: #1523384) - * Catkin plugin: Refactor build. - * ROS documentation: Fix missing step to make script executable. - * Catkin plugin: Improve cmake path rewrite. - * Add support for mesa libraries. (LP: #1531620) - * Autotools plugin: Set autogen.sh executable. (LP: #1530995) - * Update CONTRIBUTING.md to require a bug. (LP: #1532195) - * Ensure rosdep resolves dependencies using Trusty. (LP: #1532241) - * Catkin plugin: Use in-snap python instead of OS-provided. (LP: #1533297) - * Always migrate files, even if they already exist. (LP: #1534800) - - [ Alexander Sack ] - * Use archive.apache.org URL to ensure we refer to a stable URL that does - not disappear on latest release. - - [ Jonathan Cave ] - * Go plugin creating invalid env. (LP: #1524374) - - [ Renat Galimov ] - * Close not called on the qml plugin. (LP: #1531994) - - [ Vamshi Balanaga ] - * Added test for git sources in sources.py (LP: #1534411) - - -- Leonardo Arias Fonseca Fri, 15 Jan 2016 17:52:01 -0600 - -snapcraft (1.0) xenial; urgency=medium - - [ Sergio Schvezov ] - * Wrapping exe should not be over match to replace (LP: #1523912) - - [ Alexander Sack ] - * Use archive.apache.org URL to ensure we refer to a stable URL that does - not disappear on latest release - - [ Daniel Holbach ] - * Add doc about the syntax of snapcraft.yaml - - [ Kyle Fazzari ] - * Add support for mesa libraries. (LP: #1531620) - * Add a tutorial for turning a ROS project into a .snap. - * Autotools plugin: - - Set autogen.sh executable. (LP: #1530995) - * Catkin plugin: - - Add support for ROS tools (e.g. roslaunch and rosrun). - - Use rosdep for dependency resolution. - - Refactor build. - - Improve cmake path rewrite. - - Ensure rosdep resolves dependencies using Trusty. (LP: #1532241) - * Python2 plugin: - - Make site-packages link relative. (LP: #1523384) - * Python3 plugin: - - Make site-packages link relative. (LP: #1523384) - - [ Renat Galimov ] - * Close not called on the qml plugin (LP: #1531994) - - -- Kyle Fazzari Thu, 7 Jan 2016 09:09:21 -0400 - -snapcraft (0.6) xenial; urgency=medium - - [ Leo Arias ] - * Start a suite for examples tests. - - [ Sergio Schvezov ] - * nodejs plugin: support 32 bit node - * go plugin: remove hardcoded arch triplet from test - * Don't filter out easy install's .pth and wrap config (LP: #1519724) - * Remove the `parts` arg from `init` (LP: #1516775) - * Support architectures override (LP: #1520248) - * Detect encoding of snapcraft.yaml before parsing (LP: #1518150) - - [ Daniel Holbach ] - * Document how to insall and setup snapcraft. - * Update documentation to include node.js example - * Document filesets and excludes - * Make the webcam example click-review happy. - * Initialize shebang variable when wrapping execs (LP: #1519702) - - -- Sergio Schvezov Mon, 30 Nov 2015 16:31:47 -0300 - -snapcraft (0.5) xenial; urgency=medium - - [ Sujeevan Vijayakumaran ] - * Added 'git@' to the automatic identification of the source. - * Updated HACKING: replaced Launchpad with GitHub. - - [ Bjorn Tillenius ] - * Rewrite the shebang and execute it in the wrapper script. - (Fixes LP: #1507357 and LP: #1516404) - - [ Leo Arias ] - * Replaced the usage of % with .format. (LP: #1477638) - * Fixed the id of the circle tests. - * Replaced the double quotes by single quotes on strings. (LP: #1476467) - * Added the instructions to run the tests in travis. - * Split the suite in two: unit and plainbox. - - [ Sergio Schvezov ] - * Don't wrap the vcs commands in the env. - * Making source management common to all types. - * Updating setup.py's metadata. - * Do not install recommends for build or stage pkgs. (LP: #1500375) - * Silence the init unit test and verify stdout. - * Making the examples individual tests. - * Inherit directly from the BasePlugin for autotools. - * Clean for parts and remove dup from lifecycle and plugin. - * Validate that 'plugins' is not used as a part name. - * Support the source-subdir keyword for parts. - * Adding a ticker to `snappy build`. (LP: #1502410) - * Exiting with error when a plugin cannot be loaded. - * catkin plugin: Get deps from the part's sourcedir, fail if needed. - * go plugin: - - Use CGO_LDFLAGS when building. (LP: #1513935) - - Support local sources. (LP: #1515132, LP: #1516228 and LP: #1499240) - - Implement a go-packages plugin property - * nodejs plugin: - - Initial implementation. (LP: #1518403) - - Support node-packages. - * copy plugin: Support globs. (LP: #1478054) - * catkin plugin: - - Typo s/packages.xml/package.xml/ - - Catch the correct exception for missing catkin pkg - - [ Ted Gould ] - * Adding a simple tree test to look at the dependency resolution - * Add schema for the core properties of the system that are independent of a part - * copy-plugin: test for directories. (LP: #1478052) - * python plugins: - - Support pip-packages. (LP: #1502132) - - fix conflicts with pyc files. (LP: #1502105) - - [ Paul Larson ] - * Don't throw exception on snapcraft run exit status. (LP: #1499242) - * Minor fixes to setup.py - - [ Jonathan Cave ] - * Add nil plugin. (LP: #1518404) - - -- Sergio Schvezov Tue, 24 Nov 2015 13:29:19 -0300 - -snapcraft (0.4) xenial; urgency=medium - - * Core: - - Install build packages if package is removed but not purged - (LP: #1507814) - - Don't crash on help for unexisting plugin (LP: #1510954) - - Don't follow links when checking for suid/guid for unpacked - debian package contents (LP: #1511161) - - Raise exceptions for plugin errors (LP: #1481122) - - Exit cleanly with an error when using the source keyword with a local - file (LP: #1499424) - - Add version flags to cli (LP: #1501222) - - Search the wiki for a part if the 'plugin' keyword is missing - (LP: #1506205) - - Developer documentation for the plugin base class added (LP: #1507075) - - Add subcommand to list plugins (LP: #1510160) - - Add subcommand for help (LP: #1510683) - * Plugins: - - autotools: use autoreconf if needed (LP: #1507648) - - maven: support maven flags (LP: #1502031) - - catkin: new plugin for building ros projects (LP: #1506201) - * Packaging: - - Run tests on package build (LP: #1506096) - - debian/control: python3-fixtures and python3-testscenarios build deps - (LP: #1507573) - - -- Sergio Schvezov Thu, 29 Oct 2015 12:52:04 -0300 - -snapcraft (0.3) wily; urgency=medium - - * Core imprevements: - - Add deprecation messages for deprecated keywords (LP: #1501494) - - Remove suid/guid from staged packages (LP: #1503495) - - Autodetect tar sources (LP: #1500726) - - Better logging if output is not a tty (LP: #1500887) - - Improved error message when a plugin is not found (LP: #1501980) - * Plugin improvements: - - Types instead of plugin as a keyword (LP: #1500857) - - Plugin names no longer have '-project' as a suffix (LP: #1501496) - - Local plugins can override built-in ones with x-[plugin].py - (LP: #1500873) - * New plugins: - - Scons support added (LP: #1500758) - * Bug fixes: - - Multi python3 install support (LP: #1499429) - - Use the system series for stage-packages (LP: #1501035) - - Handle framework policies (LP: #1501037). - - Don't infer a plugin name from a part name (LP: #1501413) - - Installation errors during build-package's setup now errors - (LP: #1502449) - - When a dangling symlink is found, try and satisfy it with a - package on the host (LP: #1500505) - - Support reextraction when using tar sources (LP: #1500728) - - Do not require geoip (LP: #1499158) - * Examples: - - Fix build requirements issue with python2 project (LP: #1501997) - - Add missing plugin keyword for the python2 example (LP: #1501977) - - Updated the upstream tarball for tomcat (LP: #1504174) - - -- Sergio Schvezov Fri, 25 Sep 2015 12:46:23 +0200 - -snapcraft (0.2.1) wily; urgency=medium - - * New upstream release (LP: #1499265) - * Brown-paper bag release: - - Fix install path of examples. Thanks Martin Pitt for noticing. - - Allow using local sources through an env var (mostly useful for - Launchpad building). - - -- Daniel Holbach Thu, 24 Sep 2015 11:37:36 +0200 - -snapcraft (0.2) wily; urgency=medium - - * Better setuptools support: - - Better support python-setuptools (LP: #1481864), - - Used builddir as a base for setup.py overrides (LP: #1498212), - - Fix build (LP: #1494825), - * Plugin fixes: - - Add demo.qml back to the snapcraft.yaml for the QML Demo (LP: #1491301), - - stage package libgudev-1.0-0, which makes the godd snap actually - work (LP: #1498347), - - Wrap setup.py calls to configure the shebang writer (LP: #1486680), - - Update tomcat upstream URL, fixes example again (LP: #1491303), - - Add libgudev-1.0-dev as build-tools for godd example, - Ensure C library configuration tools don't use system - paths (LP: #1496789), - * Snapcraft cli: - - On clean, check the contents of the parts dir only if it - exists. (LP: #1497371) - - Load the config before trying to run (LP: #1498140), - - Notify user why the password is being requested (LP: #1481499), - - Fix numerous issues in snapcraft run (LP: #1486659), - * Snapcraft stage-packages: - - Enable ports for architectures that are not amd64 or - i386 (LP: #1498157), - - Improving stage-package handling (LP: #1497453, LP: #1497582) - - Fetch all packages in a single download run with proper - progress (LP: #1498333), - * Snapcraft wiki queries excessive (LP: #1496381), - * Snapcraft internals: - - Add 'simple' and 'has-leftovers' flags to all integration tests job - units. (LP: #1484596), - - Regex for binary and service names (LP: #1495662), - - Provide a nice error when tabs found in snapcraft.yaml (LP: #1477875), - - Use relative paths for image creation (LP: #1497108), - - Use the python logger (LP: #1476452), - - snapcraft now has less exit points (LP: #1477639), - - Depend and Build-Depend on python3-requests (LP: #1496363), - - Add autopkgtest, reshuffle build-deps accordingly (LP: #1496392), - - Fix "snapcraft run" (LP: #1484720), - * Examples packaged (LP: #1498189). - - -- Sergio Schvezov Wed, 24 Sep 2015 14:23:24 -0300 - -snapcraft (0.1) wily; urgency=low - - * Initial release - - -- Michael Terry Mon, 6 Jul 2015 21:55:21 -0500 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index ec635144f6..0000000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -9 diff --git a/debian/control b/debian/control deleted file mode 100644 index 57cc3394df..0000000000 --- a/debian/control +++ /dev/null @@ -1,19 +0,0 @@ -Source: snapcraft -Section: utils -Priority: extra -Maintainer: Snapcraft Team -Build-Depends: debhelper -Homepage: https://github.com/canonical/snapcraft -Vcs-Git: https://github.com/canonical/snapcraft.git -Vcs-Browser: https://github.com/canonical/snapcraft -Standards-Version: 4.1.3 - -Package: snapcraft -Architecture: all -Depends: ${misc:Depends} -Suggests: lxd, snapd (>= 2.15~) -Description: easily craft snaps - Snapcraft aims to make upstream developers' lives easier and as such is not - a single toolset, but instead is a collection of tools that enable the - natural workflow of an upstream to be extended with a simple release step - into Snappy. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index e47c2839d4..0000000000 --- a/debian/copyright +++ /dev/null @@ -1,50 +0,0 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: Snapcraft -Upstream-Contact: Snapcraft Team -Source: https://github.com/canonical/snapcraft - -Files: * -Copyright: 2015-2017 Canonical Ltd -License: GPL-3 - 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 - 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 warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the full text of the GNU General Public License - version 3 can be found in the file /usr/share/common-licenses/GPL-3. - -Files: snapcraft/demos/qt4-text-editor/* snapcraft/demos/qt5-text-editor/* -Copyright: 2015 The Qt Company Ltd. -License: BSD-3-clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - . - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - . - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - . - 3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - . - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/debian/rules b/debian/rules deleted file mode 100755 index c7d2f48664..0000000000 --- a/debian/rules +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/make -f -# -*- Mode:Makefile; indent-tabs-mode:t; tab-width:4 -*- - -%: - dh $@ - -override_dh_auto_clean: - @echo - -override_dh_auto_build: - @echo - -override_dh_auto_install: - @echo diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 89ae9db8f8..0000000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) diff --git a/debian/source/options b/debian/source/options deleted file mode 100644 index eefd6f1b18..0000000000 --- a/debian/source/options +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore standard files (like .bzr) -tar-ignore diff --git a/debian/tests/control b/debian/tests/control deleted file mode 100644 index 472faaf334..0000000000 --- a/debian/tests/control +++ /dev/null @@ -1,9 +0,0 @@ -Tests: integrationtests-spread -Restrictions: needs-root, allow-stderr, isolation-machine, rw-build-tree -Depends: @, - fuse, - gcc, - git, - libc6-dev, - openssh-server, - snapd (>= 2.15~), diff --git a/debian/tests/get-release-codename b/debian/tests/get-release-codename deleted file mode 100755 index b15bea1d6a..0000000000 --- a/debian/tests/get-release-codename +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -# shellcheck disable=SC1091 -. /etc/os-release - -echo "$VERSION_CODENAME" diff --git a/debian/tests/integrationtests-spread b/debian/tests/integrationtests-spread deleted file mode 100755 index 11db5823cb..0000000000 --- a/debian/tests/integrationtests-spread +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/sh - -set -ex - -# required for the debian adt host -mkdir -p /etc/systemd/system/snapd.service.d/ -if [ "${http_proxy:-}" != "" ]; then - cat <<-EOF | tee /etc/systemd/system/snapd.service.d/proxy.conf - [Service] - Environment=http_proxy=$http_proxy - Environment=https_proxy=$http_proxy - EOF - - # ensure environment is updated - echo "http_proxy=$http_proxy" >> /etc/environment - echo "https_proxy=$http_proxy" >> /etc/environment -fi -systemctl daemon-reload - -# ensure we are not killed too easily -printf '%s\n' "-950" > /proc/$$/oom_score_adj || true - -# see what mem we have (for debugging) -cat /proc/meminfo - -# ensure we can do a connect to localhost -echo ubuntu:ubuntu | chpasswd -sed -i 's/\(PermitRootLogin\|PasswordAuthentication\)\>.*/\1 yes/' /etc/ssh/sshd_config -systemctl reload sshd.service - -# Spread will only build with recent go -snap install --classic go - -# Remove the snapcraft deb -sudo apt-get autoremove --purge --yes snapcraft - -export GOPATH=/tmp/go -/snap/bin/go get -u github.com/snapcore/spread/cmd/spread - -# Remove go now that spread is built -snap remove go - -# and now run spread against localhost -# shellcheck disable=SC1091 -. /etc/os-release -export SNAPCRAFT_PACKAGE_TYPE="snap" -export SNAPCRAFT_CHANNEL="latest/edge" -/tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)" - -# store journal info for inspection -journalctl --sync -journalctl -ab > "$ADT_ARTIFACTS/journal.txt" diff --git a/debian/snapcraft-completion b/snap/local/snapcraft-completion similarity index 100% rename from debian/snapcraft-completion rename to snap/local/snapcraft-completion diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4cdd587559..5bd15b1399 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -49,7 +49,7 @@ build-packages: parts: bash-completion: - source: debian + source: snap/local plugin: dump stage: - snapcraft-completion From 0633e5a65d2088d5b5165c41769bf81955803052 Mon Sep 17 00:00:00 2001 From: Callahan Date: Mon, 7 Oct 2024 09:56:32 -0500 Subject: [PATCH 121/151] chore: fix deprecation warnings (#5094) Fixes a handful of deprecation warnings from libraries. Signed-off-by: Callahan Kovacs --- snapcraft/application.py | 2 +- snapcraft/commands/validation_sets.py | 6 ++++-- snapcraft/linters/linters.py | 2 +- tests/unit/linters/test_linters.py | 4 ++-- tests/unit/services/test_package.py | 16 ++++++++-------- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/snapcraft/application.py b/snapcraft/application.py index f8fd99f980..0b0f96281c 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -141,7 +141,7 @@ def _register_default_plugins(self) -> None: @override def _configure_services(self, provider_name: str | None) -> None: - self.services.set_kwargs( + self.services.update_kwargs( "package", build_plan=self._build_plan, snapcraft_yaml_path=self._snapcraft_yaml_path, diff --git a/snapcraft/commands/validation_sets.py b/snapcraft/commands/validation_sets.py index 50f1ef97ed..8ab5fbfdb1 100644 --- a/snapcraft/commands/validation_sets.py +++ b/snapcraft/commands/validation_sets.py @@ -136,7 +136,9 @@ def _submit_validation_set( key_name: Optional[str], store_client: StoreClientCLI, ) -> None: - emit.debug(f"Posting assertion to build: {edited_validation_sets.json()}") + emit.debug( + f"Posting assertion to build: {edited_validation_sets.model_dump_json()}" + ) build_assertion = store_client.post_validation_sets_build_assertion( validation_sets=edited_validation_sets.marshal() ) @@ -148,7 +150,7 @@ def _submit_validation_set( response = store_client.post_validation_sets( signed_validation_sets=signed_validation_sets ) - emit.debug(f"Response: {response.json()}") + emit.debug(f"Response: {response.model_dump_json()}") def _generate_template( diff --git a/snapcraft/linters/linters.py b/snapcraft/linters/linters.py index 507c8642cc..798d796f7a 100644 --- a/snapcraft/linters/linters.py +++ b/snapcraft/linters/linters.py @@ -87,7 +87,7 @@ def report( issues_by_result.setdefault(issue.result, []).append(issue) if json_output: - display(json.dumps([x.dict(exclude_none=True) for x in issues])) + display(json.dumps([x.model_dump(exclude_none=True) for x in issues])) else: # show issues by result for result, header in _lint_reports.items(): diff --git a/tests/unit/linters/test_linters.py b/tests/unit/linters/test_linters.py index cf2adec6b7..7e9c87d3d3 100644 --- a/tests/unit/linters/test_linters.py +++ b/tests/unit/linters/test_linters.py @@ -256,8 +256,8 @@ def test_base_linter_is_file_ignored(): # The "test-path" Path must be ignored by the "main" filter and all categories. assert linter.is_file_ignored(Path("test-path")) - assert linter.is_file_ignored(Path("test-path", category="test-1")) - assert linter.is_file_ignored(Path("test-path", category="test-2")) + assert linter.is_file_ignored(Path("test-path"), category="test-1") + assert linter.is_file_ignored(Path("test-path"), category="test-2") # "test-1-path" is ignored by the "test-1" only assert not linter.is_file_ignored(Path("test-1-path")) diff --git a/tests/unit/services/test_package.py b/tests/unit/services/test_package.py index 57949bd4f8..9590130d01 100644 --- a/tests/unit/services/test_package.py +++ b/tests/unit/services/test_package.py @@ -76,7 +76,7 @@ def test_metadata( ): project_path = new_dir / "snapcraft.yaml" snapcraft_yaml(filename=project_path) - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=Path("work"), cache_dir=new_dir, @@ -122,7 +122,7 @@ def test_write_metadata( default_build_plan, new_dir, ): - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=Path("work"), cache_dir=new_dir, @@ -163,7 +163,7 @@ def test_write_metadata_with_manifest( new_dir, ): monkeypatch.setenv("SNAPCRAFT_BUILD_INFO", "1") - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=Path("work"), cache_dir=new_dir, @@ -180,7 +180,7 @@ def test_write_metadata_with_manifest( # This will be different every time due to started_at differing, we can check # that it's a valid manifest and compare some fields to snap.yaml. manifest_dict = yaml.safe_load((prime_dir / "snap" / "manifest.yaml").read_text()) - manifest = models.Manifest.parse_obj(manifest_dict) + manifest = models.Manifest.model_validate(manifest_dict) assert manifest.snapcraft_version == __version__ assert ( @@ -208,7 +208,7 @@ def test_write_metadata_with_project_hooks( shutil.move(new_dir / "snap" / "snapcraft.yaml", new_dir) shutil.rmtree(new_dir / "snap") - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=work_dir, cache_dir=new_dir, @@ -257,7 +257,7 @@ def test_write_metadata_with_built_hooks( new_dir, ): work_dir = new_dir / "work" - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=work_dir, cache_dir=new_dir, @@ -307,7 +307,7 @@ def test_write_metadata_with_project_gui( new_dir, ): work_dir = new_dir / "work" - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=work_dir, cache_dir=new_dir, @@ -367,7 +367,7 @@ def test_update_project_parse_info( ): work_dir = Path("work").resolve() - default_factory.set_kwargs( + default_factory.update_kwargs( "lifecycle", work_dir=work_dir, cache_dir=new_dir, From 24e70d6db3a05760e8a1aae551b3f5ba2f233e67 Mon Sep 17 00:00:00 2001 From: Anatoli Babenia Date: Mon, 7 Oct 2024 21:21:33 +0300 Subject: [PATCH 122/151] chore!: remove Dockerfile and document snapcraft-rocks registry (#5063) Signed-off-by: Anatoli Babenia --- Dockerfile | 14 --------- docker/Dockerfile | 74 ----------------------------------------------- docker/README.md | 30 ++++++++++++------- 3 files changed, 20 insertions(+), 98 deletions(-) delete mode 100644 Dockerfile delete mode 100644 docker/Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 35d4d115f1..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM ubuntu:xenial - -RUN apt-get update && \ - apt-get dist-upgrade --yes && \ - apt-get install --yes \ - git \ - snapcraft \ - && \ - apt-get autoclean --yes && \ - apt-get clean --yes - -# Required by click. -ENV LC_ALL C.UTF-8 -ENV SNAPCRAFT_SETUP_CORE 1 diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index bffb2192c4..0000000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,74 +0,0 @@ -ARG RISK=edge -ARG UBUNTU=xenial - -FROM ubuntu:$UBUNTU as builder -ARG RISK -ARG UBUNTU -RUN echo "Building snapcraft:$RISK in ubuntu:$UBUNTU" - -# Grab dependencies -RUN apt-get update -RUN apt-get dist-upgrade --yes -RUN apt-get install --yes \ - curl \ - jq \ - squashfs-tools - -# Grab the core snap (for backwards compatibility) from the stable channel and -# unpack it in the proper place. -RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core' | jq '.download_url' -r) --output core.snap -RUN mkdir -p /snap/core -RUN unsquashfs -d /snap/core/current core.snap - -# Grab the core18 snap (which snapcraft uses as a base) from the stable channel -# and unpack it in the proper place. -RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core18' | jq '.download_url' -r) --output core18.snap -RUN mkdir -p /snap/core18 -RUN unsquashfs -d /snap/core18/current core18.snap - -# Grab the core20 snap (which snapcraft uses as a base) from the stable channel -# and unpack it in the proper place. -RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/core20' | jq '.download_url' -r) --output core20.snap -RUN mkdir -p /snap/core20 -RUN unsquashfs -d /snap/core20/current core20.snap - -# Grab the snapcraft snap from the $RISK channel and unpack it in the proper -# place. -RUN curl -L $(curl -H 'X-Ubuntu-Series: 16' 'https://api.snapcraft.io/api/v1/snaps/details/snapcraft?channel='$RISK | jq '.download_url' -r) --output snapcraft.snap -RUN mkdir -p /snap/snapcraft -RUN unsquashfs -d /snap/snapcraft/current snapcraft.snap - -# Fix Python3 installation: Make sure we use the interpreter from -# the snapcraft snap: -RUN unlink /snap/snapcraft/current/usr/bin/python3 -RUN ln -s /snap/snapcraft/current/usr/bin/python3.* /snap/snapcraft/current/usr/bin/python3 -RUN echo /snap/snapcraft/current/lib/python3.*/site-packages >> /snap/snapcraft/current/usr/lib/python3/dist-packages/site-packages.pth - -# Create a snapcraft runner (TODO: move version detection to the core of -# snapcraft). -RUN mkdir -p /snap/bin -RUN echo "#!/bin/sh" > /snap/bin/snapcraft -RUN snap_version="$(awk '/^version:/{print $2}' /snap/snapcraft/current/meta/snap.yaml | tr -d \')" && echo "export SNAP_VERSION=\"$snap_version\"" >> /snap/bin/snapcraft -RUN echo 'exec "$SNAP/usr/bin/python3" "$SNAP/bin/snapcraft" "$@"' >> /snap/bin/snapcraft -RUN chmod +x /snap/bin/snapcraft - -# Multi-stage build, only need the snaps from the builder. Copy them one at a -# time so they can be cached. -FROM ubuntu:$UBUNTU -COPY --from=builder /snap/core /snap/core -COPY --from=builder /snap/core18 /snap/core18 -COPY --from=builder /snap/core20 /snap/core20 -COPY --from=builder /snap/snapcraft /snap/snapcraft -COPY --from=builder /snap/bin/snapcraft /snap/bin/snapcraft - -# Generate locale and install dependencies. -RUN apt-get update && apt-get dist-upgrade --yes && apt-get install --yes snapd sudo locales && locale-gen en_US.UTF-8 - -# Set the proper environment. -ENV LANG="en_US.UTF-8" -ENV LANGUAGE="en_US:en" -ENV LC_ALL="en_US.UTF-8" -ENV PATH="/snap/bin:/snap/snapcraft/current/usr/bin:$PATH" -ENV SNAP="/snap/snapcraft/current" -ENV SNAP_NAME="snapcraft" -ENV SNAP_ARCH="amd64" diff --git a/docker/README.md b/docker/README.md index 528fb9acfb..4615244cbb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,16 +1,26 @@ -# Creating docker containers for snapcraft +# Docker images for `snapcraft` -By default the `Dockerfile` builds Ubuntu 16.04 (Xenial) image with `snapcraft` from the `edge` channel. +OCI-compliant container images and their sources are officially supported by the +https://github.com/canonical/snapcraft-rocks/ project. - docker build . --no-cache +To build a snap with the docker container, you need to choose an image that +matches snap `base`. For example, to build `base: core24` snap: -It is however possible to choose the base Ubuntu version and the Snapcraft channel (risk levels): + docker run -it -v `pwd`:/project ghcr.io/canonical/snapcraft:8_core24 -- `edge` -- `beta` -- `candidate` -- `stable` + * `8` in `8_core24` is the version of snapcraft. + * `\; -v` construction at the end is required to see `snapcraft` output. -To do that, use `--build-arg RISK=` and `--build-arg UBUNTU=` arguments: +For more details, see official `snapcraft-rocks` repo from Canonical. - docker build . --no-cache --build-arg RISK=beta --build-arg UBUNTU=bionic +### Building snaps with `podman` + +`podman` was born as a rootless alternative to Docker. It is default on Fedora +to have `podman` instead of Docker, but SELinux there doesn't allow containers +to write to volumes, so we just turn this "feature" off with + `--security-opt label=disable`. + +```sh +podman run -it --rm --security-opt label=disable \ + -v `pwd`:/project ghcr.io/canonical/snapcraft:8_core24 \; -v +``` From 604e927299751955c246096fa8f01e5d020fafbc Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 4 Oct 2024 15:56:43 -0500 Subject: [PATCH 123/151] build(deps): switch to requests-unixsocket2 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 14 +++++++------- requirements-docs.txt | 14 +++++++------- requirements.txt | 14 +++++++------- setup.py | 9 ++++----- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index f9daa0d898..da45be35cf 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -24,13 +24,13 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.2.4 +craft-application==4.2.6 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 -craft-parts==2.1.1 +craft-parts==2.1.2 craft-platforms==0.1.1 -craft-providers==2.0.3 +craft-providers==2.0.4 craft-store==3.0.2 cryptography==43.0.1 cssutils==2.11.1 @@ -108,7 +108,7 @@ pyftpdlib==1.5.10 pygit2==1.13.3 Pygments==2.18.0 pylint==3.2.7 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.4 @@ -128,9 +128,9 @@ pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 regex==2024.7.24 -requests==2.31.0 +requests==2.32.3 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 ruamel.yaml==0.18.6 ruamel.yaml.clib==0.2.8 SecretStorage==3.3.3 @@ -182,7 +182,7 @@ types-toml==0.10.8.20240310 types-urllib3==1.26.25.14 typing_extensions==4.12.2 uc-micro-py==1.0.3 -urllib3==1.26.20 +urllib3==2.2.3 uvicorn==0.30.6 validators==0.33.0 venusian==3.1.0 diff --git a/requirements-docs.txt b/requirements-docs.txt index 06ab3d0070..1c690ec149 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -19,13 +19,13 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.2.4 +craft-application==4.2.6 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 -craft-parts==2.1.1 +craft-parts==2.1.2 craft-platforms==0.1.1 -craft-providers==2.0.3 +craft-providers==2.0.4 craft-store==3.0.2 cryptography==43.0.1 cssutils==2.11.1 @@ -81,7 +81,7 @@ pydantic_yaml==1.3.0 pyelftools==0.31 pygit2==1.13.3 Pygments==2.18.0 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.4 @@ -94,9 +94,9 @@ pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 regex==2024.7.24 -requests==2.31.0 +requests==2.32.3 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 ruamel.yaml==0.18.6 ruamel.yaml.clib==0.2.8 SecretStorage==3.3.3 @@ -136,7 +136,7 @@ tinydb==4.8.0 toml==0.10.2 typing_extensions==4.12.2 uc-micro-py==1.0.3 -urllib3==1.26.20 +urllib3==2.2.3 uvicorn==0.30.6 validators==0.33.0 wadllib==1.3.6 diff --git a/requirements.txt b/requirements.txt index f6eac1072b..d69ae9cef6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,13 +7,13 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.2.4 +craft-application==4.2.6 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 -craft-parts==2.1.1 +craft-parts==2.1.2 craft-platforms==0.1.1 -craft-providers==2.0.3 +craft-providers==2.0.4 craft-store==3.0.2 cryptography==43.0.1 distro==1.9.0 @@ -47,7 +47,7 @@ pydantic_core==2.20.1 pydantic_yaml==1.3.0 pyelftools==0.31 pygit2==1.13.3 -pylxd==2.3.4 +pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 pyparsing==3.1.4 @@ -58,9 +58,9 @@ pytz==2024.1 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -requests==2.31.0 +requests==2.32.3 requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 +requests-unixsocket2==0.4.2 ruamel.yaml==0.18.6 ruamel.yaml.clib==0.2.8 SecretStorage==3.3.3 @@ -72,7 +72,7 @@ tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 typing_extensions==4.12.2 -urllib3==1.26.20 +urllib3==2.2.3 validators==0.33.0 wadllib==1.3.6 wheel==0.44.0 diff --git a/setup.py b/setup.py index 351f5a9379..fdfb1be53b 100755 --- a/setup.py +++ b/setup.py @@ -98,13 +98,13 @@ def recursive_data_files(directory, install_directory): "attrs", "catkin-pkg; sys_platform == 'linux'", "click", - "craft-application>=4.2.4,<5.0.0", + "craft-application>=4.2.6,<5.0.0", "craft-archives~=2.0", "craft-cli~=2.6", "craft-grammar>=2.0.1,<3.0.0", - "craft-parts~=2.1", + "craft-parts>=2.1.2,<3.0.0", "craft-platforms~=0.1", - "craft-providers>=2.0.3,<3.0.0", + "craft-providers>=2.0.4,<3.0.0", "craft-store>=3.0.2,<4.0.0", "docutils<0.20", # Frozen until we can update sphinx dependencies. "gnupg", @@ -130,7 +130,7 @@ def recursive_data_files(directory, install_directory): "pyyaml", "raven", "requests-toolbelt", - "requests-unixsocket", + "requests-unixsocket2", "requests", "simplejson", "snap-helpers", @@ -138,7 +138,6 @@ def recursive_data_files(directory, install_directory): "toml", "tinydb", "typing-extensions", - "urllib3<2", # requests-unixsocket does not yet work with urllib3 v2.0+ "validators>=0.28.3", ] From 198c8dff9b3ae5f4bb292a8b3bc0cf855d1753ae Mon Sep 17 00:00:00 2001 From: Alex Lowe Date: Thu, 3 Oct 2024 14:41:51 -0400 Subject: [PATCH 124/151] fix(tests/legacy): don't send bad data --- tests/legacy/fake_servers/snapd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/legacy/fake_servers/snapd.py b/tests/legacy/fake_servers/snapd.py index 4616b28a32..be5c52a01f 100644 --- a/tests/legacy/fake_servers/snapd.py +++ b/tests/legacy/fake_servers/snapd.py @@ -53,7 +53,6 @@ def _handle_snaps(self): def _handle_snap_file(self, parsed_url): self.send_response(200) - self.send_header("Content-Length", len(parsed_url)) self.send_header("Content-type", "text/plain") self.end_headers() self.wfile.write(parsed_url.encode()) From 29b95ffc5007ea927626c515f466deb69d9f5d6a Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Mon, 7 Oct 2024 15:00:47 -0500 Subject: [PATCH 125/151] tests: fix python unit tests --- tests/unit/parts/plugins/test_python_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/parts/plugins/test_python_plugin.py b/tests/unit/parts/plugins/test_python_plugin.py index d41f6d295a..5fa6d87337 100644 --- a/tests/unit/parts/plugins/test_python_plugin.py +++ b/tests/unit/parts/plugins/test_python_plugin.py @@ -66,7 +66,7 @@ def test_get_build_commands(plugin, new_dir): f'PARTS_PYTHON_VENV_INTERP_PATH="{new_dir}/parts/my-part/install/bin/${{PARTS_PYTHON_INTERPRETER}}"', f"{new_dir}/parts/my-part/install/bin/pip install -U pip setuptools wheel", f"[ -f setup.py ] || [ -f pyproject.toml ] && {new_dir}/parts/my-part/install/bin/pip install -U .", - f'find "{new_dir}/parts/my-part/install" -type f -executable -print0 | xargs -0 \\\n' + f'find "{new_dir}/parts/my-part/install" -type f -executable -print0 | xargs --no-run-if-empty -0 \\\n' ' sed -i "1 s|^#\\!${PARTS_PYTHON_VENV_INTERP_PATH}.*$|#!/usr/bin/env ${PARTS_PYTHON_INTERPRETER}|"\n', dedent( f"""\ From 73a26a6e1c5821bf74a268634661431cddfcbc12 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 4 Oct 2024 16:54:22 -0500 Subject: [PATCH 126/151] docs(changelog): add 7.5.7 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index fbc344f8ac..61cc599580 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -68,6 +68,19 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. + +7.5.7 (2024-Oct-03) +------------------- + +Core +==== + +* Fix a bug where parallel installations of Snapcraft would not work if the + Snapcraft snap was installed from the store (`#4683`_, `#4927`_). + +For a complete list of commits, check out the `7.5.7`_ release on GitHub. + + 8.4.1 (2024-Sep-20) ------------------- @@ -1187,6 +1200,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4517: https://github.com/canonical/snapcraft/issues/4517 .. _#4520: https://github.com/canonical/snapcraft/issues/4520 .. _#4547: https://github.com/canonical/snapcraft/issues/4547 +.. _#4683: https://github.com/canonical/snapcraft/issues/4683 .. _#4685: https://github.com/canonical/snapcraft/issues/4685 .. _#4735: https://github.com/canonical/snapcraft/issues/4735 .. _#4744: https://github.com/canonical/snapcraft/issues/4744 @@ -1219,6 +1233,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4890: https://github.com/canonical/snapcraft/issues/4890 .. _#4908: https://github.com/canonical/snapcraft/issues/4908 .. _#4909: https://github.com/canonical/snapcraft/issues/4909 +.. _#4927: https://github.com/canonical/snapcraft/issues/4927 .. _#4930: https://github.com/canonical/snapcraft/issues/4930 .. _#4941: https://github.com/canonical/snapcraft/issues/4941 .. _#4942: https://github.com/canonical/snapcraft/issues/4942 @@ -1230,6 +1245,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#5048: https://github.com/canonical/snapcraft/issues/5048 .. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 +.. _7.5.7: https://github.com/canonical/snapcraft/releases/tag/7.5.7 .. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 .. _8.0.1: https://github.com/canonical/snapcraft/releases/tag/8.0.1 .. _8.0.2: https://github.com/canonical/snapcraft/releases/tag/8.0.2 From df6ae078fa833954c7428194ded4542106f3ca45 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 4 Oct 2024 16:51:55 -0500 Subject: [PATCH 127/151] docs(changelog): add 8.4.2 release notes Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index 61cc599580..61bd5ff297 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -69,6 +69,45 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.4.2 (2024-Oct-07) +------------------- + +Core +==== + +* Fix a regression where Snapcraft would fail to run on some architectures due + to a ``cryptography`` dependency that attempted to load legacy algorithms + (`#5077`_). + +* Fix a regression where Snapcraft would fail to run in a container if it was + not running as a snap (`#5079`_). + +* Fix a bug where parallel installations of Snapcraft would not work if the + Snapcraft snap was installed from the store (`#4683`_, `#4927`_). + +Plugins +####### + +Python +"""""" + +* Fix an issue where the ``python`` plugin would fail to build if the part + had no Python scripts. + +Remote build +============ + +* Fix a bug where the remote builder would ignore the user's response when a + build is interrupted and always clean the launchpad project (`#4929`_). + +Documentation +============= + +* Update Rust plugin doc with recent changes to the Rust toolchain. + +For a complete list of commits, check out the `8.4.2`_ release on GitHub. + + 7.5.7 (2024-Oct-03) ------------------- @@ -1234,6 +1273,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4908: https://github.com/canonical/snapcraft/issues/4908 .. _#4909: https://github.com/canonical/snapcraft/issues/4909 .. _#4927: https://github.com/canonical/snapcraft/issues/4927 +.. _#4929: https://github.com/canonical/snapcraft/issues/4929 .. _#4930: https://github.com/canonical/snapcraft/issues/4930 .. _#4941: https://github.com/canonical/snapcraft/issues/4941 .. _#4942: https://github.com/canonical/snapcraft/issues/4942 @@ -1243,6 +1283,8 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4995: https://github.com/canonical/snapcraft/issues/4995 .. _#5008: https://github.com/canonical/snapcraft/issues/5008 .. _#5048: https://github.com/canonical/snapcraft/issues/5048 +.. _#5077: https://github.com/canonical/snapcraft/issues/5077 +.. _#5079: https://github.com/canonical/snapcraft/issues/5079 .. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 .. _7.5.7: https://github.com/canonical/snapcraft/releases/tag/7.5.7 @@ -1273,3 +1315,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.3.4: https://github.com/canonical/snapcraft/releases/tag/8.3.4 .. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 .. _8.4.1: https://github.com/canonical/snapcraft/releases/tag/8.4.1 +.. _8.4.2: https://github.com/canonical/snapcraft/releases/tag/8.4.2 From 8a0a3dc62b9d5c581595dc222827313f61626408 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 08:05:06 -0500 Subject: [PATCH 128/151] build(deps): update dependency requests to v2.32.2 [security] (hotfix/8.4) (#5067) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docs/.sphinx/pinned-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index 9a3bb3b63e..20849f2683 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -16,7 +16,7 @@ pyenchant==3.2.2 Pygments==2.15.0 pyparsing==3.0.9 pytz==2022.6 -requests==2.31.0 +requests==2.32.2 six==1.16.0 snowballstemmer==2.2.0 soupsieve==2.3.2.post1 From f787635acdacf977990693c3ee5b5545a3ed3130 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 10 Oct 2024 08:26:12 -0500 Subject: [PATCH 129/151] feat: support standard and kernel-modules (#5100) Adds support for `standard` and `kernel-modules` component types. Fixes #5089 Signed-off-by: Callahan Kovacs --- snapcraft/models/project.py | 2 +- tests/unit/models/test_projects.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 507a1161cd..10c571ab35 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -572,7 +572,7 @@ class Component(models.CraftBaseModel): summary: SummaryStr description: str - type: Literal["test"] + type: Literal["test", "kernel-modules", "standard"] version: VersionStr | None = None hooks: dict[str, Hook] | None = None diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index 99ccc4c4ee..eb782b95f8 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -2600,16 +2600,17 @@ def test_components_valid(self, project, project_yaml_data, stub_component_data) assert test_project.components == components + @pytest.mark.parametrize("component_type", ["test", "kernel-modules", "standard"]) def test_component_type_valid( - self, project, project_yaml_data, stub_component_data + self, component_type, project, project_yaml_data, stub_component_data ): component = {"foo": stub_component_data} - component["foo"]["type"] = "test" + component["foo"]["type"] = component_type test_project = project.unmarshal(project_yaml_data(components=component)) assert test_project.components - assert test_project.components["foo"].type == "test" + assert test_project.components["foo"].type == component_type def test_component_type_invalid( self, project, project_yaml_data, stub_component_data From 852fe4970d390e135f113466edd8d218bf9b74ff Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 10 Oct 2024 18:25:29 -0500 Subject: [PATCH 130/151] docs(changelog): add 8.4.3 release notes (#5110) Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index 61bd5ff297..a874ccbce7 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -68,6 +68,19 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.4.3 (2024-Oct-10) +------------------- + +Core +==== + +Components +########## + +* Add support for component types ``kernel-modules`` and ``standard`` + (`#5089`_). + +For a complete list of commits, check out the `8.4.3`_ release on GitHub. 8.4.2 (2024-Oct-07) ------------------- @@ -1285,6 +1298,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#5048: https://github.com/canonical/snapcraft/issues/5048 .. _#5077: https://github.com/canonical/snapcraft/issues/5077 .. _#5079: https://github.com/canonical/snapcraft/issues/5079 +.. _#5089: https://github.com/canonical/snapcraft/issues/5089 .. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 .. _7.5.7: https://github.com/canonical/snapcraft/releases/tag/7.5.7 @@ -1316,3 +1330,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.4.0: https://github.com/canonical/snapcraft/releases/tag/8.4.0 .. _8.4.1: https://github.com/canonical/snapcraft/releases/tag/8.4.1 .. _8.4.2: https://github.com/canonical/snapcraft/releases/tag/8.4.2 +.. _8.4.3: https://github.com/canonical/snapcraft/releases/tag/8.4.3 From 8ee34ff833d9988e1586c90052417ade81b40ad7 Mon Sep 17 00:00:00 2001 From: Callahan Date: Fri, 11 Oct 2024 08:12:51 -0500 Subject: [PATCH 131/151] build(deps): bump craft-store to 2.6.2 (#5105) Fix a regression where Snapcraft would fail to run on some architectures due to a `cryptography` dependency that attempted to load legacy algorithms Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 98ba127424..d58ac54d20 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -16,7 +16,7 @@ craft-cli==1.2.0 craft-grammar==1.1.2 craft-parts==1.19.8 craft-providers==1.20.4 -craft-store==2.5.0 +craft-store==2.6.2 cryptography==40.0.2 Deprecated==1.2.14 dill==0.3.8 diff --git a/requirements.txt b/requirements.txt index 9cc2d2172a..6697bb75a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ craft-cli==1.2.0 craft-grammar==1.1.2 craft-parts==1.19.8 craft-providers==1.20.4 -craft-store==2.5.0 +craft-store==2.6.2 cryptography==40.0.2 Deprecated==1.2.14 distro==1.8.0 From 739a1b618cd8433fcae8a06e82e6699bb8b47bd3 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 10 Oct 2024 16:29:39 -0500 Subject: [PATCH 132/151] build(deps): bump craft-application to 4.2.7 Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index da45be35cf..02f505e465 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -24,7 +24,7 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.2.6 +craft-application==4.2.7 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index 1c690ec149..4047db4013 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -19,7 +19,7 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.2.6 +craft-application==4.2.7 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 diff --git a/requirements.txt b/requirements.txt index d69ae9cef6..bd589d3928 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.2.6 +craft-application==4.2.7 craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 diff --git a/setup.py b/setup.py index fdfb1be53b..2f0f14b462 100755 --- a/setup.py +++ b/setup.py @@ -98,7 +98,7 @@ def recursive_data_files(directory, install_directory): "attrs", "catkin-pkg; sys_platform == 'linux'", "click", - "craft-application>=4.2.6,<5.0.0", + "craft-application>=4.2.7,<5.0.0", "craft-archives~=2.0", "craft-cli~=2.6", "craft-grammar>=2.0.1,<3.0.0", From 08e5be465fa6ea44e11e340a556fa6764955596c Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 10 Oct 2024 09:49:31 -0500 Subject: [PATCH 133/151] tests: check parallel build count in project metadata Signed-off-by: Callahan Kovacs --- .../core24-suites/environment/test-variables/task.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/spread/core24-suites/environment/test-variables/task.yaml b/tests/spread/core24-suites/environment/test-variables/task.yaml index f9fb09d0a2..22d50aeed0 100644 --- a/tests/spread/core24-suites/environment/test-variables/task.yaml +++ b/tests/spread/core24-suites/environment/test-variables/task.yaml @@ -8,6 +8,7 @@ systems: environment: SNAP/test_variables: test-variables + SNAPCRAFT_PARALLEL_BUILD_COUNT: 5 prepare: | #shellcheck source=tests/spread/tools/snapcraft-yaml.sh @@ -35,7 +36,7 @@ execute: | "^SNAPCRAFT_PROJECT_GRADE=devel$" \ "^SNAPCRAFT_PROJECT_NAME=variables$" \ "^SNAPCRAFT_PROJECT_VERSION=1$" \ - "^SNAPCRAFT_PARALLEL_BUILD_COUNT=[0-9]\+$" \ + "^SNAPCRAFT_PARALLEL_BUILD_COUNT=5$" \ "^SNAPCRAFT_PROJECT_DIR=${root}$" \ "^SNAPCRAFT_PART_SRC=${root}/parts/hello/src$" \ "^SNAPCRAFT_PART_SRC_WORK=${root}/parts/hello/src$" \ @@ -48,7 +49,7 @@ execute: | "^CRAFT_ARCH_TRIPLET_BUILD_ON=x86_64-linux-gnu$" \ "^CRAFT_ARCH_BUILD_FOR=s390x$" \ "^CRAFT_ARCH_TRIPLET_BUILD_FOR=s390x-linux-gnu$" \ - "^CRAFT_PARALLEL_BUILD_COUNT=[0-9]\+$" \ + "^CRAFT_PARALLEL_BUILD_COUNT=5$" \ "^CRAFT_PROJECT_DIR=${root}$" \ "^CRAFT_PART_NAME=hello$" \ "^CRAFT_PART_SRC=${root}/parts/hello/src$" \ From c9050163f0124063d43bd8c312b030905c3e09bb Mon Sep 17 00:00:00 2001 From: Andrew Phelps <136256549+andrewphelpsj@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:48:09 -0500 Subject: [PATCH 134/151] feat(validation-sets): add support for components (#5059) --- snapcraft/commands/validation_sets.py | 6 ++ .../storeapi/v2/validation_sets.py | 14 +++- .../unit/store/v2/test_validation_sets.py | 21 ++++++ tests/unit/commands/test_validation_sets.py | 70 ++++++++++++++++++- 4 files changed, 109 insertions(+), 2 deletions(-) diff --git a/snapcraft/commands/validation_sets.py b/snapcraft/commands/validation_sets.py index 8ab5fbfdb1..d310d3aeeb 100644 --- a/snapcraft/commands/validation_sets.py +++ b/snapcraft/commands/validation_sets.py @@ -48,6 +48,12 @@ # the provided name. # presence: [required|optional|invalid] # Optional, defaults to required. # revision: # The revision of the snap. Optional. + # components: # Constraints to apply to the snap's components. Optional. + # : [required|optional|invalid] # Short form for specifying the component's presence. + # : # Long form that allows specifying the component's revision. + # presence: [required|optional|invalid] # Presence of the component. Required. + # revision: # The revision of the component, required if the snap's revision is given. + # Otherwise, not allowed. """ ) diff --git a/snapcraft_legacy/storeapi/v2/validation_sets.py b/snapcraft_legacy/storeapi/v2/validation_sets.py index 3dc7b68c45..90f0545c79 100644 --- a/snapcraft_legacy/storeapi/v2/validation_sets.py +++ b/snapcraft_legacy/storeapi/v2/validation_sets.py @@ -56,6 +56,16 @@ def _to_string( return data +Presence = Literal["required", "optional", "invalid"] + +class Component(models.CraftBaseModel): + """Represent a Component in a Validation Set.""" + + presence: Presence + """Component presence""" + + revision: int | None = None + """Component revision""" class Snap(models.CraftBaseModel): """Represent a Snap in a Validation Set.""" @@ -66,12 +76,14 @@ class Snap(models.CraftBaseModel): id: SnapId | None = None """Snap ID""" - presence: Literal["required", "optional", "invalid"] | None = None + presence: Presence | None = None """Snap presence""" revision: int | None = None """Snap revision""" + components: dict[str, Presence | Component] | None = None + """Snap components""" class EditableBuildAssertion(models.CraftBaseModel): """Subset of a build assertion that can be edited by the user. diff --git a/tests/legacy/unit/store/v2/test_validation_sets.py b/tests/legacy/unit/store/v2/test_validation_sets.py index 2820efab48..81fa911c6f 100644 --- a/tests/legacy/unit/store/v2/test_validation_sets.py +++ b/tests/legacy/unit/store/v2/test_validation_sets.py @@ -26,6 +26,13 @@ def fake_snap_data(): "id": "snap-id", "presence": "required", "revision": 42, + "components": { + "component-with-revision": { + "presence": "required", + "revision": 10, + }, + "component-without-revision": "invalid", + }, } @@ -125,6 +132,13 @@ def test_editable_build_assertion_marshal_as_str(fake_editable_build_assertion): "name": "snap-name", "presence": "required", "revision": "42", + "components": { + "component-with-revision": { + "presence": "required", + "revision": "10", + }, + "component-without-revision": "invalid", + }, }, ], } @@ -147,6 +161,13 @@ def test_build_assertion_marshal_as_str(fake_build_assertion): "name": "snap-name", "presence": "required", "revision": "42", + "components": { + "component-with-revision": { + "presence": "required", + "revision": "10", + }, + "component-without-revision": "invalid", + }, }, ], "timestamp": "2020-10-29T16:36:56Z", diff --git a/tests/unit/commands/test_validation_sets.py b/tests/unit/commands/test_validation_sets.py index 1e69a278d1..0deefb31ee 100644 --- a/tests/unit/commands/test_validation_sets.py +++ b/tests/unit/commands/test_validation_sets.py @@ -54,6 +54,28 @@ "id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2", }, + { + "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", + "name": "snap-name-3", + "presence": "optional", + "revision": "1", + "components": { + "comp-name-1": { + "presence": "required", + "revision": "11", + }, + }, + }, + { + "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", + "name": "snap-name-4", + "presence": "optional", + "components": { + "comp-name-1": "required", + "comp-name-2": "optional", + "comp-name-3": "invalid", + }, + }, ], "timestamp": "2020-10-29T16:36:56Z", "type": "validation-set", @@ -125,6 +147,28 @@ def edit_return_value(): "revision": "10", }, {"id": "XXSnapIDForXSnapName2XXXXXXXXXXX", "name": "snap-name-2"}, + { + "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", + "name": "snap-name-3", + "presence": "optional", + "revision": "1", + "components": { + "comp-name-1": { + "presence": "required", + "revision": "11", + }, + }, + }, + { + "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", + "name": "snap-name-4", + "presence": "optional", + "components": { + "comp-name-1": "required", + "comp-name-2": "optional", + "comp-name-3": "invalid", + }, + }, ], } ) @@ -157,6 +201,28 @@ def fake_edit_validation_sets(mocker, edit_return_value): "revision": 10, }, {"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}, + { + "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", + "name": "snap-name-3", + "presence": "optional", + "revision": 1, + "components": { + "comp-name-1": { + "presence": "required", + "revision": 11, + }, + }, + }, + { + "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", + "name": "snap-name-4", + "presence": "optional", + "components": { + "comp-name-1": "required", + "comp-name-2": "optional", + "comp-name-3": "invalid", + }, + }, ], } ) @@ -165,7 +231,9 @@ def fake_edit_validation_sets(mocker, edit_return_value): '{{"account-id": "AccountIDXXXOfTheRequestingUserX", "name": "certification-x1", ' '"revision": "222", "sequence": "9", "snaps": [{{"name": "snap-name-1", "id": ' '"XXSnapIDForXSnapName1XXXXXXXXXXX", "presence": "optional", "revision": "10"}}, ' - '{{"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}}], ' + '{{"name": "snap-name-2", "id": "XXSnapIDForXSnapName2XXXXXXXXXXX"}}, ' + '{{"name": "snap-name-3", "id": "XXSnapIDForXSnapName3XXXXXXXXXXX", "presence": "optional", "revision": "1", "components": {{"comp-name-1": {{"presence": "required", "revision": "11"}}}}}}, ' + '{{"name": "snap-name-4", "id": "XXSnapIDForXSnapName4XXXXXXXXXXX", "presence": "optional", "components": {{"comp-name-1": "required", "comp-name-2": "optional", "comp-name-3": "invalid"}}}}], ' '"authority-id": "AccountIDXXXOfTheRequestingUserX", "series": "16", "timestamp": ' '"2020-10-29T16:36:56Z", "type": "validation-set"}}\n\nSIGNED{key_name}' ) From ec4f9b16f19103f9ee0f86b20c19c568c2196adf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 20:57:24 +0000 Subject: [PATCH 135/151] build(deps): update dependency requests to v2.32.2 [security] (main) (#5064) --- docs/.sphinx/pinned-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index 9a3bb3b63e..20849f2683 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -16,7 +16,7 @@ pyenchant==3.2.2 Pygments==2.15.0 pyparsing==3.0.9 pytz==2022.6 -requests==2.31.0 +requests==2.32.2 six==1.16.0 snowballstemmer==2.2.0 soupsieve==2.3.2.post1 From fceb8c16982bde9cc6c2807e12bfa3959e5065f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:37:29 -0500 Subject: [PATCH 136/151] build(deps): update dependency starlette to v0.40.0 [security] (hotfix/8.4) (#5120) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 02f505e465..bc04964e9f 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -164,7 +164,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.4 +starlette==0.40.0 tabulate==0.9.0 testscenarios==0.5.0 testtools==2.7.2 diff --git a/requirements-docs.txt b/requirements-docs.txt index 4047db4013..d375741f96 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -130,7 +130,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.4 +starlette==0.40.0 tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 From f51b12efe4beba73032a7a788e8aff0d44ac6aa1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:51:05 -0400 Subject: [PATCH 137/151] build(deps): update dependency beautifulsoup4 to v4.12.3 (main) (#5121) --- docs/.sphinx/pinned-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.sphinx/pinned-requirements.txt b/docs/.sphinx/pinned-requirements.txt index 20849f2683..fe9e0eccd4 100644 --- a/docs/.sphinx/pinned-requirements.txt +++ b/docs/.sphinx/pinned-requirements.txt @@ -1,6 +1,6 @@ alabaster==0.7.16 Babel==2.11.0 -beautifulsoup4==4.11.1 +beautifulsoup4==4.12.3 certifi==2024.7.4 charset-normalizer==2.1.1 colorama==0.4.6 From e13bb0574877f50653dce44d147051b0217bd70e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 21:29:59 +0000 Subject: [PATCH 138/151] build(deps): update dependency black to v24.10.0 (main) (#5122) --- requirements-devel.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 6e94c1f36e..2fb6e1742c 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -8,7 +8,7 @@ attrs==24.2.0 autodocsumm==0.2.13 babel==2.16.0 beautifulsoup4==4.12.3 -black==24.8.0 +black==24.10.0 boolean.py==4.0 bracex==2.5 CacheControl==0.14.0 From 9fa59f7c4d20f284687d8e83fa7873881649c1cb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 08:20:34 -0500 Subject: [PATCH 139/151] build(deps): update dependency starlette to v0.40.0 [security] (main) (#5119) --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 2fb6e1742c..951ca5ebbc 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -164,7 +164,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.5 +starlette==0.40.0 tabulate==0.9.0 testscenarios==0.5.0 testtools==2.7.2 diff --git a/requirements-docs.txt b/requirements-docs.txt index 5764025e75..a91631c168 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -130,7 +130,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.38.5 +starlette==0.40.0 tabulate==0.9.0 tinydb==4.8.0 toml==0.10.2 From af8bc122963921725d9aab9eb4be4f53a10f5f57 Mon Sep 17 00:00:00 2001 From: Lincoln Wallace Date: Fri, 18 Oct 2024 14:51:55 -0300 Subject: [PATCH 140/151] feat: add env-injector extension (#4925) Add extension to inject environment variables into snaps by its command-chain. This extension takes snap options and transform them into environment variables Signed-off-by: Lincoln Wallace Co-authored-by: Callahan Kovacs Co-authored-by: Farshid Tavakolizadeh --- schema/snapcraft.json | 1 + snapcraft/extensions/_ros2_humble_meta.py | 4 +- snapcraft/extensions/_ros2_jazzy_meta.py | 4 +- snapcraft/extensions/_utils.py | 2 +- snapcraft/extensions/env_injector.py | 125 +++++++++++++++++ snapcraft/extensions/extension.py | 7 +- snapcraft/extensions/gnome.py | 2 +- snapcraft/extensions/kde_neon.py | 2 +- snapcraft/extensions/kde_neon_6.py | 2 +- snapcraft/extensions/registry.py | 2 + snapcraft/extensions/ros2_humble.py | 2 +- snapcraft/extensions/ros2_jazzy.py | 2 +- .../spread/extensions/env-injector/task.yaml | 127 ++++++++++++++++++ .../env-injector-hello/snap/hooks/configure | 1 + .../env-injector-hello/snap/snapcraft.yaml | 30 +++++ .../snaps/env-injector-hello/usr/bin/exec-env | 3 + tests/unit/commands/test_list_extensions.py | 2 + tests/unit/conftest.py | 10 +- tests/unit/extensions/test_env_injector.py | 112 +++++++++++++++ tests/unit/extensions/test_gnome.py | 4 +- tests/unit/extensions/test_kde_neon.py | 2 +- tests/unit/extensions/test_kde_neon_6.py | 2 +- tests/unit/extensions/test_registry.py | 1 + .../unit/parts/extensions/test_ros2_humble.py | 2 +- .../parts/extensions/test_ros2_humble_meta.py | 2 +- .../unit/parts/extensions/test_ros2_jazzy.py | 2 +- .../parts/extensions/test_ros2_jazzy_meta.py | 2 +- 27 files changed, 432 insertions(+), 25 deletions(-) create mode 100644 snapcraft/extensions/env_injector.py create mode 100644 tests/spread/extensions/env-injector/task.yaml create mode 100755 tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure create mode 100644 tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml create mode 100755 tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env create mode 100644 tests/unit/extensions/test_env_injector.py diff --git a/schema/snapcraft.json b/schema/snapcraft.json index 9da873f184..f0890b80f3 100644 --- a/schema/snapcraft.json +++ b/schema/snapcraft.json @@ -906,6 +906,7 @@ "uniqueItems": true, "items": { "enum": [ + "env-injector", "flutter-stable", "flutter-beta", "flutter-dev", diff --git a/snapcraft/extensions/_ros2_humble_meta.py b/snapcraft/extensions/_ros2_humble_meta.py index be3d3ecff8..3d23330d71 100644 --- a/snapcraft/extensions/_ros2_humble_meta.py +++ b/snapcraft/extensions/_ros2_humble_meta.py @@ -64,8 +64,8 @@ def get_root_snippet(self) -> Dict[str, Any]: return root_snippet @overrides - def get_app_snippet(self) -> Dict[str, Any]: - app_snippet = super().get_app_snippet() + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + app_snippet = super().get_app_snippet(app_name=app_name) python_paths = app_snippet["environment"]["PYTHONPATH"] new_python_paths = [ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.10/site-packages", diff --git a/snapcraft/extensions/_ros2_jazzy_meta.py b/snapcraft/extensions/_ros2_jazzy_meta.py index 9dcf31ad0d..b230119661 100644 --- a/snapcraft/extensions/_ros2_jazzy_meta.py +++ b/snapcraft/extensions/_ros2_jazzy_meta.py @@ -64,8 +64,8 @@ def get_root_snippet(self) -> Dict[str, Any]: return root_snippet @overrides - def get_app_snippet(self) -> Dict[str, Any]: - app_snippet = super().get_app_snippet() + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + app_snippet = super().get_app_snippet(app_name=app_name) python_paths = app_snippet["environment"]["PYTHONPATH"] new_python_paths = [ f"$SNAP/opt/ros/underlay_ws/opt/ros/{self.ROS_DISTRO}/lib/python3.12/site-packages", diff --git a/snapcraft/extensions/_utils.py b/snapcraft/extensions/_utils.py index 3aa885be35..d382ca44db 100644 --- a/snapcraft/extensions/_utils.py +++ b/snapcraft/extensions/_utils.py @@ -79,8 +79,8 @@ def _apply_extension( ) # Apply the app-specific components of the extension (if any) - app_extension = extension.get_app_snippet() for app_name in app_names: + app_extension = extension.get_app_snippet(app_name=app_name) app_definition = yaml_data["apps"][app_name] for property_name, property_value in app_extension.items(): app_definition[property_name] = _apply_extension_property( diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py new file mode 100644 index 0000000000..8c24222241 --- /dev/null +++ b/snapcraft/extensions/env_injector.py @@ -0,0 +1,125 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Extension to automatically set environment variables on snaps.""" + +from typing import Any, Dict, Optional, Tuple + +from overrides import overrides + +from .extension import Extension + + +class EnvInjector(Extension): + """Extension to automatically set environment variables on snaps. + + This extension allows you to transform snap options into environment + variables + + It configures your application to run a command-chain that transforms the + snap options into environment variables automatically. + + - To set global environment variables for all applications **inside** the snap: + + .. code-block:: shell + sudo snap set env.= + + - To set environment variables for a specific application **inside** the snap: + + .. code-block:: shell + sudo snap set apps..env.= + + - To set environment file inside the snap: + + .. code-block:: shell + sudo snap set env-file= + - To set environment file for a specific app: + + .. code-block:: shell + sudo snap set apps..envfile= + + """ + + @staticmethod + @overrides + def get_supported_bases() -> Tuple[str, ...]: + return ("core24",) + + @staticmethod + @overrides + def get_supported_confinement() -> Tuple[str, ...]: + return ("strict", "devmode", "classic") + + @staticmethod + @overrides + def is_experimental(base: Optional[str]) -> bool: + return True + + @overrides + def get_root_snippet(self) -> Dict[str, Any]: + return {} + + @overrides + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + """Return the app snippet to apply.""" + return { + "command-chain": ["bin/command-chain/env-exporter"], + "environment": { + "env_alias": f"{app_name}", + }, + } + + @overrides + def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: + return {} + + @overrides + def get_parts_snippet(self) -> Dict[str, Any]: + toolchain = self.get_toolchain(self.arch) + if toolchain is None: + raise ValueError( + f"Unsupported architecture for env-injector extension: {self.arch}" + ) + + return { + "env-injector/env-injector": { + "source": "https://github.com/canonical/snappy-env.git", + "source-tag": "v1.0.0-beta", + "plugin": "nil", + "build-snaps": ["rustup"], + "override-build": f""" + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + """, + } + } + + def get_toolchain(self, arch: str): + """Get the Rust toolchain for the current architecture.""" + toolchain = { + "amd64": "x86_64-unknown-linux-gnu", + "arm64": "aarch64-unknown-linux-gnu", + # 'armhf': 'armv8-unknown-linux-gnueabihf', # Tier 2 toolchain + # 'riscv64': 'riscv64gc-unknown-linux-gnu', # Tier 2 toolchain + # 'ppc64el': 'powerpc64-unknown-linux-gnu', # Tier 2 toolchain + # 's390x': 's390x-unknown-linux-gnu', # Tier 2 toolchain + } + return toolchain.get(arch) diff --git a/snapcraft/extensions/extension.py b/snapcraft/extensions/extension.py index 52cdfd902e..bf3c4bb04d 100644 --- a/snapcraft/extensions/extension.py +++ b/snapcraft/extensions/extension.py @@ -66,8 +66,11 @@ def get_root_snippet(self) -> Dict[str, Any]: """Return the root snippet to apply.""" @abc.abstractmethod - def get_app_snippet(self) -> Dict[str, Any]: - """Return the app snippet to apply.""" + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: + """Return the app snippet to apply. + + :param app_name: the name of the app where the snippet will be applied + """ @abc.abstractmethod def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: diff --git a/snapcraft/extensions/gnome.py b/snapcraft/extensions/gnome.py index 30e7eeeb23..0f0c3be7ab 100644 --- a/snapcraft/extensions/gnome.py +++ b/snapcraft/extensions/gnome.py @@ -85,7 +85,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: command_chain = ["snap/command-chain/desktop-launch"] if self.yaml_data["base"] == "core24": command_chain.insert(0, "snap/command-chain/gpu-2404-wrapper") diff --git a/snapcraft/extensions/kde_neon.py b/snapcraft/extensions/kde_neon.py index 587418b055..e4f0f7b534 100644 --- a/snapcraft/extensions/kde_neon.py +++ b/snapcraft/extensions/kde_neon.py @@ -89,7 +89,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "opengl", "wayland", "x11"], diff --git a/snapcraft/extensions/kde_neon_6.py b/snapcraft/extensions/kde_neon_6.py index ab4a362774..765b981762 100644 --- a/snapcraft/extensions/kde_neon_6.py +++ b/snapcraft/extensions/kde_neon_6.py @@ -86,7 +86,7 @@ def is_experimental(base: Optional[str]) -> bool: return False @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return { "command-chain": ["snap/command-chain/desktop-launch6"], "plugs": [ diff --git a/snapcraft/extensions/registry.py b/snapcraft/extensions/registry.py index 42c60af1d6..e3ed00d23d 100644 --- a/snapcraft/extensions/registry.py +++ b/snapcraft/extensions/registry.py @@ -20,6 +20,7 @@ from snapcraft import errors +from .env_injector import EnvInjector from .gnome import GNOME from .kde_neon import KDENeon from .kde_neon_6 import KDENeon6 @@ -38,6 +39,7 @@ ExtensionType = Type[Extension] _EXTENSIONS: Dict[str, "ExtensionType"] = { + "env-injector": EnvInjector, "gnome": GNOME, "ros2-humble": ROS2HumbleExtension, "ros2-humble-ros-core": ROS2HumbleRosCoreExtension, diff --git a/snapcraft/extensions/ros2_humble.py b/snapcraft/extensions/ros2_humble.py index 56f33f56dc..5467c3d884 100644 --- a/snapcraft/extensions/ros2_humble.py +++ b/snapcraft/extensions/ros2_humble.py @@ -87,7 +87,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: python_paths = [ f"$SNAP/opt/ros/{self.ROS_DISTRO}/lib/python3.10/site-packages", "$SNAP/usr/lib/python3/dist-packages", diff --git a/snapcraft/extensions/ros2_jazzy.py b/snapcraft/extensions/ros2_jazzy.py index 635d23fd2a..ae17efd5ba 100644 --- a/snapcraft/extensions/ros2_jazzy.py +++ b/snapcraft/extensions/ros2_jazzy.py @@ -85,7 +85,7 @@ def get_root_snippet(self) -> Dict[str, Any]: } @overrides - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: python_paths = [ f"$SNAP/opt/ros/{self.ROS_DISTRO}/lib/python3.12/site-packages", "$SNAP/usr/lib/python3/dist-packages", diff --git a/tests/spread/extensions/env-injector/task.yaml b/tests/spread/extensions/env-injector/task.yaml new file mode 100644 index 0000000000..69731ef048 --- /dev/null +++ b/tests/spread/extensions/env-injector/task.yaml @@ -0,0 +1,127 @@ +summary: Build and run a basic hello-world snap using extensions + +systems: + - ubuntu-24.04* + +environment: + + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS: "1" + SNAP_DIR: ../snaps/env-injector-hello + SNAP: env-injector-hello + +prepare: | + + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + set_base "$SNAP_DIR/snap/snapcraft.yaml" + +restore: | + + cd "$SNAP_DIR" + snapcraft clean + rm -f /var/snap/"${SNAP}"/common/*.env + rm -f ./*.snap + rm -rf ./squashfs-root + #shellcheck source=tests/spread/tools/snapcraft-yaml.sh + . "$TOOLS_DIR/snapcraft-yaml.sh" + restore_yaml "snap/snapcraft.yaml" + +execute: | + + assert_env() { + local snap_app="$1" + local env_name="$2" + local exp_value="$3" + + local actual_value + + if ! eval "$snap_app" | grep -q "^${env_name}="; then + echo "Environment variable '$env_name' is not set." + return 1 + fi + + if [ -z "$env_name" ]; then + empty=$( "$snap_app" | grep "=${exp_value}") + [ -z "$empty" ] || return 1 + fi + + actual_value=$( "$snap_app" | grep "^${env_name}=" | cut -d'=' -f2-) + + if [ "$actual_value" != "$exp_value" ]; then + echo "Environment variable '$env_name' does not match the expected value." + echo "Expected: '$env_name=$exp_value', but got: '$env_name=$actual_value'" + return 1 + fi + + return 0 + } + + cd "$SNAP_DIR" + SNAPCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=1 snapcraft + + unsquashfs "${SNAP}"_1.0_*.snap + + # Check that the env-exporter program is present + [ -f squashfs-root/bin/command-chain/env-exporter ] + + # Check that the exec-env script is present + [ -f squashfs-root/usr/bin/exec-env ] + + snap install "${SNAP}"_1.0_*.snap --dangerous + + echo "[env-injector] Creating global envfile" + echo 'HELLO_WORLD="Hello World"' >> /var/snap/"${SNAP}"/common/global.env + + # Load global envfile + snap set env-injector-hello envfile=/var/snap/"${SNAP}"/common/global.env + echo "[TEST] - Check if the global envfile is loaded for all apps" + assert_env "env-injector-hello.hello1" "HELLO_WORLD" "Hello World" || exit 1 + assert_env "env-injector-hello.hello2" "HELLO_WORLD" "Hello World" || exit 1 + assert_env "env-injector-hello.hello-demo" "HELLO_WORLD" "Hello World" || exit 1 + + echo "[env-injector] Creating app-specific envfile" + echo 'SCOPED=Scoped' >> /var/snap/"${SNAP}"/common/appenv.env + + # Load app-specific envfile + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/appenv.env + echo "[TEST] - Check if the app-specific envfile is loaded for the app" + assert_env "env-injector-hello.hello1" "SCOPED" "Scoped" || exit 1 + + echo "[env-injector] Setting global env variable" + # Set env vars: Global + snap set env-injector-hello env.global="World" + echo "[TEST] - Check if the global env var is set for all apps" + assert_env "env-injector-hello.hello1" "GLOBAL" "World" || exit 1 + assert_env "env-injector-hello.hello2" "GLOBAL" "World" || exit 1 + assert_env "env-injector-hello.hello-demo" "GLOBAL" "World" || exit 1 + + echo "[env-injector] Setting app-specific env variable" + # Set env vars: specific to each app + snap set env-injector-hello apps.hello1.env.hello="Hello" + snap set env-injector-hello apps.hello2.env.specific="City" + + echo "[TEST] - Check if the app-specific env var IS SET for the app hello1" + assert_env "env-injector-hello.hello1" "HELLO" "Hello" || exit 1 + echo "[TEST] - Check if the app-specific env var IS NOT SET for the app hello2" + ! assert_env "env-injector-hello.hello2" "HELLO" "Hello" || exit 1 + + echo "[TEST] - Check if the app-specific env var IS SET for the app hello2" + assert_env "env-injector-hello.hello2" "SPECIFIC" "City" || exit 1 + echo "[TEST] - Check if the app-specific env var IS NOT SET for the app hello1" + ! assert_env "env-injector-hello.hello1" "SPECIFIC" "City" || exit 1 + + snap set env-injector-hello env.word.dot="wrong" + echo "[TEST] - Check if the key with dot was ignored" + ! assert_env "env-injector-hello.hello1" "" "wrong" || exit 1 + + echo "[env-injector] Testing order of env vars" + echo 'ORDER="From envfile"' >> /var/snap/"${SNAP}"/common/local.env + snap set env-injector-hello apps.hello1.env.order="from app-specific" + snap set env-injector-hello apps.hello1.envfile=/var/snap/"${SNAP}"/common/local.env + echo "[TEST] - Check if local overrites global" + assert_env "env-injector-hello.hello1" "ORDER" "from app-specific" || exit 1 + + echo "[env-injector] Run hello-demo app" + snap set env-injector-hello apps.myapp.env.specific="City" + echo "[TEST] Make sure that alias is NOT rewritten" + assert_env "env-injector-hello.hello-demo" "SPECIFIC" "City" || exit 1 diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure b/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure new file mode 100755 index 0000000000..a9bf588e2f --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/hooks/configure @@ -0,0 +1 @@ +#!/bin/bash diff --git a/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml new file mode 100644 index 0000000000..fc925f609f --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/snap/snapcraft.yaml @@ -0,0 +1,30 @@ +name: env-injector-hello +version: "1.0" +summary: test the env-injector extension +description: This is a basic snap for testing env-injector extension + +grade: devel +base: core24 +confinement: strict + +apps: + hello1: + command: usr/bin/exec-env + extensions: [ env-injector ] + + hello2: + command: usr/bin/exec-env + extensions: [ env-injector ] + + hello-demo: + command: usr/bin/exec-env + environment: + # user-defined alias + env_alias: myapp + extensions: [ env-injector ] + +parts: + env: + plugin: dump + source: . + diff --git a/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env b/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env new file mode 100755 index 0000000000..b802721fcb --- /dev/null +++ b/tests/spread/extensions/snaps/env-injector-hello/usr/bin/exec-env @@ -0,0 +1,3 @@ +#!/usr/bin/bash + +env diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py index 1900e1a4d7..07bc97c1ec 100644 --- a/tests/unit/commands/test_list_extensions.py +++ b/tests/unit/commands/test_list_extensions.py @@ -38,6 +38,7 @@ def test_command(emitter, command): """\ Extension name Supported bases ---------------------- ---------------------- + env-injector core24 fake-extension core22, core24 flutter-beta core18 flutter-dev core18 @@ -87,6 +88,7 @@ def test_command_extension_dups(emitter, command): """\ Extension name Supported bases ---------------------- ---------------------- + env-injector core24 flutter-beta core18 flutter-dev core18 flutter-master core18 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 285cddb152..6f1b79b3cf 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -97,7 +97,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {"grade": "fake-grade"} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -136,7 +136,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -170,7 +170,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {"grade": "fake-grade"} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -206,7 +206,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: @@ -242,7 +242,7 @@ def is_experimental(base: Optional[str] = None) -> bool: def get_root_snippet(self) -> Dict[str, Any]: return {} - def get_app_snippet(self) -> Dict[str, Any]: + def get_app_snippet(self, *, app_name: str) -> Dict[str, Any]: return {"plugs": ["fake-plug", "fake-plug-extra"]} def get_part_snippet(self, *, plugin_name: str) -> Dict[str, Any]: diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py new file mode 100644 index 0000000000..0797b64f1d --- /dev/null +++ b/tests/unit/extensions/test_env_injector.py @@ -0,0 +1,112 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import pytest + +from snapcraft.extensions import env_injector + +############ +# Fixtures # +############ + + +@pytest.fixture +def envinjector_extension(): + return env_injector.EnvInjector( + yaml_data={"base": "core24", "parts": {}}, arch="amd64", target_arch="amd64" + ) + + +######################### +# EnvInjector Extension # +######################### + + +def test_get_supported_bases(envinjector_extension): + assert envinjector_extension.get_supported_bases() == ("core24",) + + +def test_get_supported_confinement(envinjector_extension): + assert envinjector_extension.get_supported_confinement() == ( + "strict", + "devmode", + "classic", + ) + + +def test_is_experimental(): + assert env_injector.EnvInjector.is_experimental(base="core24") is True + + +def test_get_root_snippet(envinjector_extension): + assert envinjector_extension.get_root_snippet() == {} + + +def test_get_app_snippet(envinjector_extension): + assert envinjector_extension.get_app_snippet(app_name="test") == { + "command-chain": ["bin/command-chain/env-exporter"], + "environment": { + "env_alias": "test", + }, + } + + +def test_get_toolchain_amd64(envinjector_extension): + assert envinjector_extension.get_toolchain("amd64") == "x86_64-unknown-linux-gnu" + + +def test_get_toolchain_arm64(envinjector_extension): + assert envinjector_extension.get_toolchain("arm64") == "aarch64-unknown-linux-gnu" + + +class TestGetPartSnippet: + """Tests for EnvInjector.get_part_snippet when using the default sdk snap name.""" + + def test_get_part_snippet(self, envinjector_extension): + self.assert_get_part_snippet(envinjector_extension) + + @staticmethod + def assert_get_part_snippet(envinjector_extension): + assert envinjector_extension.get_part_snippet(plugin_name="nil") == {} + + @pytest.mark.parametrize( + "unsupported_arch", ["armhf", "riscv64", "ppc64el", "s390x"] + ) + def test_get_parts_snippet(self, envinjector_extension, unsupported_arch): + toolchain = "x86_64-unknown-linux-gnu" + assert envinjector_extension.get_parts_snippet() == { + "env-injector/env-injector": { + "source": "https://github.com/canonical/snappy-env.git", + "source-tag": "v1.0.0-beta", + "plugin": "nil", + "build-snaps": ["rustup"], + "override-build": f""" + rustup default stable + rustup target add {toolchain} + + cargo build --target {toolchain} --release + mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain + + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain + """, + } + } + + envinjector_extension.arch = unsupported_arch + with pytest.raises( + ValueError, match="Unsupported architecture for env-injector extension" + ): + envinjector_extension.get_parts_snippet() diff --git a/tests/unit/extensions/test_gnome.py b/tests/unit/extensions/test_gnome.py index 7276dbb402..217194a2ce 100644 --- a/tests/unit/extensions/test_gnome.py +++ b/tests/unit/extensions/test_gnome.py @@ -81,14 +81,14 @@ def test_is_experimental(base): def test_get_app_snippet(gnome_extension): - assert gnome_extension.get_app_snippet() == { + assert gnome_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "gsettings", "opengl", "wayland", "x11"], } def test_get_app_snippet_core24(gnome_extension_core24): - assert gnome_extension_core24.get_app_snippet() == { + assert gnome_extension_core24.get_app_snippet(app_name="test-app") == { "command-chain": [ "snap/command-chain/gpu-2404-wrapper", "snap/command-chain/desktop-launch", diff --git a/tests/unit/extensions/test_kde_neon.py b/tests/unit/extensions/test_kde_neon.py index dda264abb4..35387a2a62 100644 --- a/tests/unit/extensions/test_kde_neon.py +++ b/tests/unit/extensions/test_kde_neon.py @@ -81,7 +81,7 @@ def test_is_experimental(): def test_get_app_snippet(kde_neon_extension): - assert kde_neon_extension.get_app_snippet() == { + assert kde_neon_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch"], "plugs": ["desktop", "desktop-legacy", "opengl", "wayland", "x11"], } diff --git a/tests/unit/extensions/test_kde_neon_6.py b/tests/unit/extensions/test_kde_neon_6.py index 47340f290f..b59d474d6e 100644 --- a/tests/unit/extensions/test_kde_neon_6.py +++ b/tests/unit/extensions/test_kde_neon_6.py @@ -88,7 +88,7 @@ def test_is_experimental(): def test_get_app_snippet(kde_neon_6_extension): - assert kde_neon_6_extension.get_app_snippet() == { + assert kde_neon_6_extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/desktop-launch6"], "plugs": [ "desktop", diff --git a/tests/unit/extensions/test_registry.py b/tests/unit/extensions/test_registry.py index c823c92c60..0fefd4048c 100644 --- a/tests/unit/extensions/test_registry.py +++ b/tests/unit/extensions/test_registry.py @@ -24,6 +24,7 @@ @pytest.mark.usefixtures("fake_extension_experimental") def test_get_extension_names(): assert extensions.get_extension_names() == [ + "env-injector", "gnome", "ros2-humble", "ros2-humble-ros-core", diff --git a/tests/unit/parts/extensions/test_ros2_humble.py b/tests/unit/parts/extensions/test_ros2_humble.py index 53ad5b921b..4bbd91d771 100644 --- a/tests/unit/parts/extensions/test_ros2_humble.py +++ b/tests/unit/parts/extensions/test_ros2_humble.py @@ -110,7 +110,7 @@ def test_get_app_snippet(self, setup_method_fixture): "${PYTHONPATH}", ] extension = setup_method_fixture() - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_humble_meta.py b/tests/unit/parts/extensions/test_ros2_humble_meta.py index 67a439be02..1ec41625b2 100644 --- a/tests/unit/parts/extensions/test_ros2_humble_meta.py +++ b/tests/unit/parts/extensions/test_ros2_humble_meta.py @@ -142,7 +142,7 @@ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev): "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages", ] extension = setup_method_fixture(extension_class) - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_jazzy.py b/tests/unit/parts/extensions/test_ros2_jazzy.py index 664d52ad7b..8a33a13d51 100644 --- a/tests/unit/parts/extensions/test_ros2_jazzy.py +++ b/tests/unit/parts/extensions/test_ros2_jazzy.py @@ -110,7 +110,7 @@ def test_get_app_snippet(self, setup_method_fixture): "${PYTHONPATH}", ] extension = setup_method_fixture() - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", diff --git a/tests/unit/parts/extensions/test_ros2_jazzy_meta.py b/tests/unit/parts/extensions/test_ros2_jazzy_meta.py index 7f5b996220..a772858e2b 100644 --- a/tests/unit/parts/extensions/test_ros2_jazzy_meta.py +++ b/tests/unit/parts/extensions/test_ros2_jazzy_meta.py @@ -142,7 +142,7 @@ def test_get_app_snippet(self, extension_name, extension_class, meta, meta_dev): "$SNAP/opt/ros/underlay_ws/usr/lib/python3/dist-packages", ] extension = setup_method_fixture(extension_class) - assert extension.get_app_snippet() == { + assert extension.get_app_snippet(app_name="test-app") == { "command-chain": ["snap/command-chain/ros2-launch"], "environment": { "ROS_VERSION": "2", From 7969ee8d56faa4e501a45e57c650796d769a8bbc Mon Sep 17 00:00:00 2001 From: Matt Culler Date: Fri, 18 Oct 2024 18:25:25 -0400 Subject: [PATCH 141/151] feat: use `craft-platforms` for build plans (#5111) - [ ] Have you followed the [guidelines for contributing](https://github.com/canonical/snapcraft/blob/main/CONTRIBUTING.md)? - [ ] Have you signed the [CLA](http://www.ubuntu.com/legal/contributors/)? - [ ] Have you successfully run `tox run -m lint`? - [ ] Have you successfully run `tox run -e test-py310`? (supported versions: `py39`, `py310`, `py311`, `py312`) ----- --------- Co-authored-by: Alex Lowe --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- setup.py | 2 +- snapcraft/models/project.py | 59 +++++++++++++++++++----------- tests/unit/models/test_projects.py | 34 ----------------- 6 files changed, 42 insertions(+), 59 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 951ca5ebbc..b34cf5f2ea 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -29,7 +29,7 @@ craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.2 -craft-platforms==0.1.1 +craft-platforms==0.4.0 craft-providers==2.0.4 craft-store==3.0.2 cryptography==43.0.1 diff --git a/requirements-docs.txt b/requirements-docs.txt index a91631c168..3c0d37570a 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -24,7 +24,7 @@ craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.2 -craft-platforms==0.1.1 +craft-platforms==0.4.0 craft-providers==2.0.4 craft-store==3.0.2 cryptography==43.0.1 diff --git a/requirements.txt b/requirements.txt index 0094f8ba12..f8f5854405 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ craft-archives==2.0.0 craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.2 -craft-platforms==0.1.1 +craft-platforms==0.4.0 craft-providers==2.0.4 craft-store==3.0.2 cryptography==43.0.1 diff --git a/setup.py b/setup.py index 545c7b11b9..275446acd9 100755 --- a/setup.py +++ b/setup.py @@ -103,7 +103,7 @@ def recursive_data_files(directory, install_directory): "craft-cli~=2.6", "craft-grammar>=2.0.1,<3.0.0", "craft-parts>=2.1.2,<3.0.0", - "craft-platforms~=0.1", + "craft-platforms~=0.4", "craft-providers>=2.0.4,<3.0.0", "craft-store>=3.0.2,<4.0.0", "docutils<0.20", # Frozen until we can update sphinx dependencies. diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 507a1161cd..d748ef347f 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -32,6 +32,7 @@ ) from craft_cli import emit from craft_grammar.models import Grammar # type: ignore[import-untyped] +from craft_platforms import Platforms, snap from craft_providers import bases from pydantic import ConfigDict, PrivateAttr, StringConstraints from typing_extensions import Annotated, Self, override @@ -277,6 +278,14 @@ def _get_partitions_from_components( return None +def _validate_mandatory_base(base: str | None, snap_type: str | None) -> None: + """Validate that the base is specified, if required by the snap_type.""" + if (base is not None) ^ (snap_type not in ["base", "kernel", "snapd"]): + raise ValueError( + "Snap base must be declared when type is not base, kernel or snapd" + ) + + class Socket(models.CraftBaseModel): """Snapcraft app socket definition.""" @@ -714,12 +723,7 @@ def _validate_adoptable_fields(self) -> Self: @pydantic.model_validator(mode="after") def _validate_mandatory_base(self): - snap_type = self.type - base = self.base - if (base is not None) ^ (snap_type not in ["base", "kernel", "snapd"]): - raise ValueError( - "Snap base must be declared when type is not base, kernel or snapd" - ) + _validate_mandatory_base(self.base, self.type) return self @pydantic.field_validator("name") @@ -1138,6 +1142,7 @@ class SnapcraftBuildPlanner(models.BuildPlanner): base: str | None = None build_base: str | None = None name: str + type: Literal["app", "base", "gadget", "kernel", "snapd"] | None = None platforms: dict[str, Platform] | None = None # type: ignore[assignment] architectures: list[str | Architecture] | None = None project_type: str | None = pydantic.Field(default=None, alias="type") @@ -1192,7 +1197,6 @@ def _validate_all_platforms( def get_build_plan(self) -> list[BuildInfo]: """Get the build plan for this project.""" - build_infos: list[BuildInfo] = [] effective_base = SNAPCRAFT_BASE_TO_PROVIDER_BASE[ str( get_effective_base( @@ -1205,8 +1209,6 @@ def get_build_plan(self) -> list[BuildInfo]: ) ].value - base = bases.BaseName("ubuntu", effective_base) - # set default value if self.platforms is None: self.platforms = { @@ -1221,16 +1223,31 @@ def get_build_plan(self) -> list[BuildInfo]: Platform.from_architectures(self.architectures) ) - for platform_entry, platform in self.platforms.items(): - for build_for in platform.build_for or [SnapArch(platform_entry).value]: - for build_on in platform.build_on or [SnapArch(platform_entry).value]: - build_infos.append( - BuildInfo( - platform=platform_entry, - build_on=build_on, - build_for=build_for, - base=base, - ) - ) + platforms = cast( + Platforms, + {name: platform.marshal() for name, platform in self.platforms.items()}, + ) - return build_infos + # In _validate_mandatory_base, we ensure that the possible values of + # 'base' and 'snap_type' are narrowed so they'll always match one of + # the two overloads of get_platforms_snap_build_plan. But, pyright and + # mypy aren't smart enough to realize this, so we need the type checker + # ignores. + _validate_mandatory_base(self.base, self.type) + return [ + BuildInfo( + platform=buildinfo.platform, + build_on=str(buildinfo.build_on), + build_for=str(buildinfo.build_for), + base=bases.BaseName( + name=buildinfo.build_base.distribution, + version=buildinfo.build_base.series, + ), + ) + for buildinfo in snap.get_platforms_snap_build_plan( # pyright: ignore[reportCallIssue] + base=self.base, # type: ignore[arg-type] + build_base=self.build_base, + snap_type=self.type, # type: ignore[arg-type] + platforms=platforms, + ) + ] diff --git a/tests/unit/models/test_projects.py b/tests/unit/models/test_projects.py index 99ccc4c4ee..1cbbdabbec 100644 --- a/tests/unit/models/test_projects.py +++ b/tests/unit/models/test_projects.py @@ -2486,40 +2486,6 @@ def test_platform_default(): ] -def test_build_planner_get_build_plan_base(mocker): - """Test `get_build_plan()` uses the correct base.""" - mock_get_effective_base = mocker.patch( - "snapcraft.models.project.get_effective_base", return_value="core24" - ) - planner = snapcraft.models.project.SnapcraftBuildPlanner.model_validate( - { - "name": "test-snap", - "base": "test-base", - "build-base": "test-build-base", - "platforms": {"amd64": None}, - "project_type": "test-type", - } - ) - - actual_build_infos = planner.get_build_plan() - - assert actual_build_infos == [ - BuildInfo( - platform="amd64", - build_on="amd64", - build_for="amd64", - base=BaseName(name="ubuntu", version="24.04"), - ) - ] - mock_get_effective_base.assert_called_once_with( - base="test-base", - build_base="test-build-base", - project_type="test-type", - name="test-snap", - translate_devel=False, - ) - - def test_project_platform_error_has_context(): """Platform validation errors include which platform entry is invalid.""" error = r"build-on\n Field required" From b6954111356aeb18b05f2bbefa156a5b97275c5c Mon Sep 17 00:00:00 2001 From: Pedro Avalos <42501726+pedro-avalos@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:01:46 -0500 Subject: [PATCH 142/151] feat: add kde-neon-6 to schema (#5127) --- schema/snapcraft.json | 1 + 1 file changed, 1 insertion(+) diff --git a/schema/snapcraft.json b/schema/snapcraft.json index f0890b80f3..c4c0a5b3ec 100644 --- a/schema/snapcraft.json +++ b/schema/snapcraft.json @@ -916,6 +916,7 @@ "gnome-3-34", "gnome-3-38", "kde-neon", + "kde-neon-6", "ros1-noetic", "ros1-noetic-desktop", "ros1-noetic-perception", From 47c3c21697e06650e242b584345560eda2854235 Mon Sep 17 00:00:00 2001 From: Tiago Nobrega Date: Mon, 21 Oct 2024 13:46:41 -0300 Subject: [PATCH 143/151] build(deps): bump craft-archives (#5124) This update fixes LP#2083013, where some package-repositories declarations could conflict with default sources present in Noble. --- requirements-devel.txt | 2 +- requirements-docs.txt | 2 +- requirements.txt | 2 +- .../core24/package-repositories/task.yaml | 1 + .../test-key-conflict/snapcraft.yaml | 40 +++++++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 tests/spread/core24/package-repositories/test-key-conflict/snapcraft.yaml diff --git a/requirements-devel.txt b/requirements-devel.txt index bc04964e9f..5e290613a8 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -25,7 +25,7 @@ codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 craft-application==4.2.7 -craft-archives==2.0.0 +craft-archives==2.0.1 craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.2 diff --git a/requirements-docs.txt b/requirements-docs.txt index d375741f96..aaa96ba4bb 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -20,7 +20,7 @@ charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 craft-application==4.2.7 -craft-archives==2.0.0 +craft-archives==2.0.1 craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.2 diff --git a/requirements.txt b/requirements.txt index bd589d3928..e4eb95994b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 craft-application==4.2.7 -craft-archives==2.0.0 +craft-archives==2.0.1 craft-cli==2.7.0 craft-grammar==2.0.1 craft-parts==2.1.2 diff --git a/tests/spread/core24/package-repositories/task.yaml b/tests/spread/core24/package-repositories/task.yaml index 96db7835e5..46c8063548 100644 --- a/tests/spread/core24/package-repositories/task.yaml +++ b/tests/spread/core24/package-repositories/task.yaml @@ -9,6 +9,7 @@ environment: SNAP/test_multi_keys: test-multi-keys SNAP/test_foreign_armhf: test-foreign-armhf SNAP/test_foreign_i386: test-foreign-i386 + SNAP/test_key_conflict: test-key-conflict prepare: | # Remove the currently installed "gpg" and "dirmngr" packages to ensure that diff --git a/tests/spread/core24/package-repositories/test-key-conflict/snapcraft.yaml b/tests/spread/core24/package-repositories/test-key-conflict/snapcraft.yaml new file mode 100644 index 0000000000..4252620f38 --- /dev/null +++ b/tests/spread/core24/package-repositories/test-key-conflict/snapcraft.yaml @@ -0,0 +1,40 @@ +name: test-key-conflict +version: '1.0' +summary: test package repos with keys already present on the system +description: test package repos with keys already present on the system +confinement: strict +base: core24 +platforms: + amd64: + +package-repositories: + # In core24+ this pair of url + suite is already listed in + # /etc/apt/sources.list.d/ubuntu.sources. Add it here to make sure this setup + # is working. + - type: apt + url: http://archive.ubuntu.com/ubuntu + suites: [noble] + components: [main, universe] + architectures: [i386] + key-id: F6ECB3762474EDA9D21B7022871920D1991BC93C + key-server: keyserver.ubuntu.com + # This ports.ubuntu.com repo is not affected by the bug, but add it here to + # ensure that a different repo with the same key-id still works. + - type: apt + url: http://ports.ubuntu.com/ubuntu-ports + suites: [noble] + architectures: [armhf] + components: [main] + key-id: F6ECB3762474EDA9D21B7022871920D1991BC93C + +parts: + mypart: + plugin: nil + stage-packages: + - zlib1g:i386 # To ensure the package-repo is setup correctly + - zlib1g:armhf + - hello # there is no hello:i386 in the archives + +apps: + test-key-conflict: + command: usr/bin/hello -g hello From 44cc83c8035b5c7be8efd67e7c97a1ae070246a1 Mon Sep 17 00:00:00 2001 From: Callahan Date: Thu, 24 Oct 2024 13:36:38 -0500 Subject: [PATCH 144/151] docs(changelog): add 8.4.4 and 7.5.8 release notes (#5130) Signed-off-by: Callahan Kovacs --- docs/reference/changelog.rst | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst index a874ccbce7..d05bb52c0e 100644 --- a/docs/reference/changelog.rst +++ b/docs/reference/changelog.rst @@ -68,6 +68,40 @@ Changelog For a complete list of commits, check out the `X.Y.Z`_ release on GitHub. +8.4.4 (2024-Oct-24) +------------------- + +Core +==== + +Bases +##### + +core24 +"""""" + +* Fix a bug where ``CRAFT_PARALLEL_BUILD_COUNT`` was not evaluated in + ``snapcraft.yaml`` files (`#4785`_). + +* Fix a bug where ``package-repositories`` declarations could conflict with + default sources present in Noble (`LP#2083013`_). + +For a complete list of commits, check out the `8.4.4`_ release on GitHub. + + +7.5.8 (2024-Oct-24) +------------------- + +Core +==== + +* Fix a regression where Snapcraft would fail to run on some architectures due + to a ``cryptography`` dependency that attempted to load legacy algorithms + (`#5077`_). + +For a complete list of commits, check out the `7.5.8`_ release on GitHub. + + 8.4.3 (2024-Oct-10) ------------------- @@ -1241,6 +1275,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _LP#2061603: https://bugs.launchpad.net/snapcraft/+bug/2061603 .. _LP#2064639: https://bugs.launchpad.net/snapcraft/+bug/2064639 .. _LP#2069783: https://bugs.launchpad.net/snapcraft/+bug/2069783 +.. _LP#2083013: https://bugs.launchpad.net/snapcraft/+bug/2083013 .. _#4142: https://github.com/canonical/snapcraft/issues/4142 .. _#4356: https://github.com/canonical/snapcraft/issues/4356 @@ -1266,6 +1301,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _#4780: https://github.com/canonical/snapcraft/issues/4780 .. _#4781: https://github.com/canonical/snapcraft/issues/4781 .. _#4783: https://github.com/canonical/snapcraft/issues/4783 +.. _#4785: https://github.com/canonical/snapcraft/issues/4785 .. _#4791: https://github.com/canonical/snapcraft/issues/4791 .. _#4798: https://github.com/canonical/snapcraft/issues/4798 .. _#4804: https://github.com/canonical/snapcraft/issues/4804 @@ -1302,6 +1338,7 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _7.5.6: https://github.com/canonical/snapcraft/releases/tag/7.5.6 .. _7.5.7: https://github.com/canonical/snapcraft/releases/tag/7.5.7 +.. _7.5.8: https://github.com/canonical/snapcraft/releases/tag/7.5.8 .. _8.0.0: https://github.com/canonical/snapcraft/releases/tag/8.0.0 .. _8.0.1: https://github.com/canonical/snapcraft/releases/tag/8.0.1 .. _8.0.2: https://github.com/canonical/snapcraft/releases/tag/8.0.2 @@ -1331,3 +1368,4 @@ For a complete list of commits, check out the `8.0.0`_ release on GitHub. .. _8.4.1: https://github.com/canonical/snapcraft/releases/tag/8.4.1 .. _8.4.2: https://github.com/canonical/snapcraft/releases/tag/8.4.2 .. _8.4.3: https://github.com/canonical/snapcraft/releases/tag/8.4.3 +.. _8.4.4: https://github.com/canonical/snapcraft/releases/tag/8.4.4 From 2b2cb6149462e865a2c103bae85e54c37f4eb611 Mon Sep 17 00:00:00 2001 From: Dariusz Duda Date: Thu, 7 Nov 2024 16:28:04 -0500 Subject: [PATCH 145/151] fix(docs): display examples correctly in RTD (#5139) Signed-off-by: Dariusz Duda --- snapcraft/commands/legacy.py | 3 ++- snapcraft/commands/manage.py | 6 ++++-- snapcraft/commands/status.py | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/snapcraft/commands/legacy.py b/snapcraft/commands/legacy.py index 6cb9ca78a1..0605548172 100644 --- a/snapcraft/commands/legacy.py +++ b/snapcraft/commands/legacy.py @@ -62,7 +62,8 @@ class StoreLegacyUploadMetadataCommand(LegacyAppCommand): If --force is used, it will force the local metadata into the Store, ignoring any possible conflict. - Examples: + Examples:: + snapcraft upload-metadata my-snap_0.1_amd64.snap snapcraft upload-metadata my-snap_0.1_amd64.snap --force """ diff --git a/snapcraft/commands/manage.py b/snapcraft/commands/manage.py index 4269ce00e2..a0889636ee 100644 --- a/snapcraft/commands/manage.py +++ b/snapcraft/commands/manage.py @@ -56,7 +56,8 @@ class StoreReleaseCommand(AppCommand): - is optional and dynamically creates a channel with a specific expiration date. - Examples: + Examples:: + snapcraft release my-snap 8 stable snapcraft release my-snap 8 stable/my-branch snapcraft release my-snap 9 beta,edge @@ -120,7 +121,8 @@ class StoreCloseCommand(AppCommand): As such, closing the 'candidate' channel for a snap would make that snap begin to track the 'stable' channel. - Examples: + Examples:: + snapcraft close my-snap --channel beta """ ) diff --git a/snapcraft/commands/status.py b/snapcraft/commands/status.py index 12ddae4b48..bdb55059f3 100644 --- a/snapcraft/commands/status.py +++ b/snapcraft/commands/status.py @@ -431,7 +431,8 @@ class StoreListRevisionsCommand(AppCommand): help_msg = "List published revisions for " overview = textwrap.dedent( """ - Examples: + Examples:: + snapcraft list-revisions my-snap snapcraft list-revisions my-snap --arch armhf snapcraft revisions my-snap From de05a733d413ba0e4d008478c200b07ff723290a Mon Sep 17 00:00:00 2001 From: Callahan Date: Fri, 8 Nov 2024 17:21:27 -0600 Subject: [PATCH 146/151] refactor: use init command from craft-application (#5129) The init command accepts additional parameters `--profile`, `--name`, and `project-dir`. If `--name` is not provided, the name of the snap will be the name of the directory in which snapcraft is initialised. Fixes #5098 Signed-off-by: Callahan Kovacs --- MANIFEST.in | 1 + requirements-devel.txt | 6 +- requirements-docs.txt | 6 +- requirements.txt | 6 +- setup.py | 8 +- snapcraft/application.py | 12 +- snapcraft/cli.py | 1 - snapcraft/commands/__init__.py | 2 - snapcraft/commands/init.py | 89 ------------ snapcraft/extensions/env_injector.py | 2 +- snapcraft/models/project.py | 6 +- snapcraft/parts/yaml_utils.py | 12 +- snapcraft/services/__init__.py | 2 + snapcraft/services/init.py | 83 +++++++++++ snapcraft/services/service_factory.py | 3 + .../templates/simple/snap/snapcraft.yaml.j2 | 17 +++ tests/spread/general/init/task.yaml | 52 ++++++- tests/spread/general/version-git/task.yaml | 4 +- tests/unit/commands/test_init.py | 112 ++++++++------- tests/unit/extensions/test_env_injector.py | 2 +- tests/unit/parts/test_yaml_utils.py | 13 ++ tests/unit/services/test_init.py | 129 ++++++++++++++++++ tools/docs/gen_cli_docs.py | 4 +- 23 files changed, 385 insertions(+), 187 deletions(-) delete mode 100644 snapcraft/commands/init.py create mode 100644 snapcraft/services/init.py create mode 100644 snapcraft/templates/simple/snap/snapcraft.yaml.j2 create mode 100644 tests/unit/services/test_init.py diff --git a/MANIFEST.in b/MANIFEST.in index f24832561c..b09a917f23 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include schema/* include extensions/* +recursive-include snapcraft/templates * diff --git a/requirements-devel.txt b/requirements-devel.txt index a11632f78f..67fee633e5 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -24,9 +24,9 @@ click==8.1.7 codespell==2.3.0 colorama==0.4.6 coverage==7.6.1 -craft-application==4.2.7 +craft-application==4.4.0 craft-archives==2.0.1 -craft-cli==2.7.0 +craft-cli==2.10.0 craft-grammar==2.0.1 craft-parts==2.1.2 craft-platforms==0.4.0 @@ -89,7 +89,7 @@ pbr==6.0.0 pexpect==4.9.0 plaster==1.1.2 plaster-pastedeploy==1.0.1 -platformdirs==4.2.2 +platformdirs==4.3.6 pluggy==1.5.0 polib==1.2.0 progressbar==2.5 diff --git a/requirements-docs.txt b/requirements-docs.txt index 024510c35c..b5494fa73e 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -19,9 +19,9 @@ chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 colorama==0.4.6 -craft-application==4.2.7 +craft-application==4.4.0 craft-archives==2.0.1 -craft-cli==2.7.0 +craft-cli==2.10.0 craft-grammar==2.0.1 craft-parts==2.1.2 craft-platforms==0.4.0 @@ -69,7 +69,7 @@ natsort==8.4.0 oauthlib==3.2.2 overrides==7.7.0 packaging==24.1 -platformdirs==4.2.2 +platformdirs==4.3.6 polib==1.2.0 progressbar==2.5 protobuf==5.27.3 diff --git a/requirements.txt b/requirements.txt index db561aecc0..364fdc3f76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,9 @@ cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 -craft-application==4.2.7 +craft-application==4.4.0 craft-archives==2.0.1 -craft-cli==2.7.0 +craft-cli==2.10.0 craft-grammar==2.0.1 craft-parts==2.1.2 craft-platforms==0.4.0 @@ -37,7 +37,7 @@ mypy-extensions==1.0.0 oauthlib==3.2.2 overrides==7.7.0 packaging==24.1 -platformdirs==4.2.2 +platformdirs==4.3.6 progressbar==2.5 protobuf==5.27.3 psutil==6.0.0 diff --git a/setup.py b/setup.py index 3fc201f7fd..45a840ce25 100755 --- a/setup.py +++ b/setup.py @@ -98,9 +98,9 @@ def recursive_data_files(directory, install_directory): "attrs", "catkin-pkg; sys_platform == 'linux'", "click", - "craft-application>=4.2.7,<5.0.0", + "craft-application~=4.4", "craft-archives~=2.0", - "craft-cli~=2.6", + "craft-cli~=2.9", "craft-grammar>=2.0.1,<3.0.0", "craft-parts>=2.1.2,<3.0.0", "craft-platforms~=0.4", @@ -174,6 +174,10 @@ def recursive_data_files(directory, install_directory): + recursive_data_files("keyrings", "share/snapcraft") + recursive_data_files("extensions", "share/snapcraft") ), + include_package_data=True, + package_data={ + "snapcraft": ["templates/*"], + }, python_requires=">=3.10", install_requires=install_requires, extras_require=extras_requires, diff --git a/snapcraft/application.py b/snapcraft/application.py index 0b0f96281c..51e1037727 100644 --- a/snapcraft/application.py +++ b/snapcraft/application.py @@ -150,16 +150,6 @@ def _configure_services(self, provider_name: str | None) -> None: super()._configure_services(provider_name) - @property - def command_groups(self): - """Replace craft-application's LifecycleCommand group.""" - _command_groups = super().command_groups - for index, command_group in enumerate(_command_groups): - if command_group.name == "Lifecycle": - _command_groups[index] = cli.CORE24_LIFECYCLE_COMMAND_GROUP - - return _command_groups - @override def _resolve_project_path(self, project_dir: pathlib.Path | None) -> pathlib.Path: """Overridden to handle the two possible locations for snapcraft.yaml.""" @@ -467,7 +457,7 @@ def create_app() -> Snapcraft: services=snapcraft_services, ) - for group in cli.COMMAND_GROUPS: + for group in [cli.CORE24_LIFECYCLE_COMMAND_GROUP, *cli.COMMAND_GROUPS]: app.add_command_group(group.name, group.commands) return app diff --git a/snapcraft/cli.py b/snapcraft/cli.py index 98d890ceeb..361ad8ed03 100644 --- a/snapcraft/cli.py +++ b/snapcraft/cli.py @@ -151,7 +151,6 @@ "Other", [ commands.LintCommand, - commands.InitCommand, ], ), ] diff --git a/snapcraft/commands/__init__.py b/snapcraft/commands/__init__.py index b1a8cdc5eb..f3ee75220f 100644 --- a/snapcraft/commands/__init__.py +++ b/snapcraft/commands/__init__.py @@ -28,7 +28,6 @@ ExtensionsCommand, ListExtensionsCommand, ) -from .init import InitCommand from .legacy import ( StoreLegacyCreateKeyCommand, StoreLegacyGatedCommand, @@ -67,7 +66,6 @@ __all__ = [ "ExpandExtensionsCommand", "ExtensionsCommand", - "InitCommand", "LintCommand", "ListExtensionsCommand", "ListPluginsCommand", diff --git a/snapcraft/commands/init.py b/snapcraft/commands/init.py deleted file mode 100644 index e968d6ee70..0000000000 --- a/snapcraft/commands/init.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- -# -# Copyright 2023-2024 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 -# 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 warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -"""Snapcraft init command.""" - -from pathlib import Path -from textwrap import dedent - -from craft_application.commands import AppCommand -from craft_cli import emit -from overrides import overrides - -from snapcraft import errors -from snapcraft.parts.yaml_utils import get_snap_project - -_TEMPLATE_YAML = dedent( - """\ - name: my-snap-name # you probably want to 'snapcraft register ' - base: core24 # the base snap is the execution environment for this snap - version: '0.1' # just for humans, typically '1.2+git' or '1.3.2' - summary: Single-line elevator pitch for your amazing snap # 79 char long summary - description: | - This is my-snap's description. You have a paragraph or two to tell the - most important story about your snap. Keep it under 100 words though, - we live in tweetspace and your description wants to look good in the snap - store. - - grade: devel # must be 'stable' to release into candidate/stable channels - confinement: devmode # use 'strict' once you have the right plugs and slots - - parts: - my-part: - # See 'snapcraft plugins' - plugin: nil - """ -) - - -class InitCommand(AppCommand): - """Initialize a snapcraft project.""" - - name = "init" - help_msg = "Initialize a snapcraft project." - overview = "Initialize a snapcraft project in the current directory." - - @overrides - def run(self, parsed_args): - """Initialize a snapcraft project in the current directory. - - :raises SnapcraftError: If a snapcraft.yaml already exists. - """ - emit.progress("Checking for an existing 'snapcraft.yaml'.") - - # if a project is found, then raise an error - try: - project = get_snap_project() - raise errors.SnapcraftError( - "could not initialize a new snapcraft project because " - f"{str(project.project_file)!r} already exists" - ) - # the `ProjectMissing` error means a new project can be initialized - except errors.ProjectMissing: - emit.progress("Could not find an existing 'snapcraft.yaml'.") - - snapcraft_yaml_path = Path("snap/snapcraft.yaml") - - emit.progress(f"Creating {str(snapcraft_yaml_path)!r}.") - - snapcraft_yaml_path.parent.mkdir(exist_ok=True) - snapcraft_yaml_path.write_text(_TEMPLATE_YAML, encoding="utf-8") - - emit.message(f"Created {str(snapcraft_yaml_path)!r}.") - emit.message( - "Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more " - "information about the snapcraft.yaml format." - ) diff --git a/snapcraft/extensions/env_injector.py b/snapcraft/extensions/env_injector.py index 8c24222241..7e99e53d9c 100644 --- a/snapcraft/extensions/env_injector.py +++ b/snapcraft/extensions/env_injector.py @@ -106,7 +106,7 @@ def get_parts_snippet(self) -> Dict[str, Any]: cargo build --target {toolchain} --release mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain """, } diff --git a/snapcraft/models/project.py b/snapcraft/models/project.py index 0b2b0bee59..31a363313d 100644 --- a/snapcraft/models/project.py +++ b/snapcraft/models/project.py @@ -224,7 +224,7 @@ def _validate_version_name(version: str, model_name: str) -> None: ) -def _validate_name(*, name: str, field_name: str) -> str: +def validate_name(*, name: str, field_name: str) -> str: """Validate a name. :param name: The name to validate. @@ -261,7 +261,7 @@ def _validate_component(name: str) -> str: raise ValueError( "component names cannot start with the reserved prefix 'snap-'" ) - return _validate_name(name=name, field_name="component") + return validate_name(name=name, field_name="component") def _get_partitions_from_components( @@ -729,7 +729,7 @@ def _validate_mandatory_base(self): @pydantic.field_validator("name") @classmethod def _validate_snap_name(cls, name): - return _validate_name(name=name, field_name="snap") + return validate_name(name=name, field_name="snap") @pydantic.field_validator("components") @classmethod diff --git a/snapcraft/parts/yaml_utils.py b/snapcraft/parts/yaml_utils.py index 0480a916f8..f91bb76d81 100644 --- a/snapcraft/parts/yaml_utils.py +++ b/snapcraft/parts/yaml_utils.py @@ -218,13 +218,21 @@ def apply_yaml( return yaml_data -def get_snap_project() -> _SnapProject: +def get_snap_project(project_dir: Path | None = None) -> _SnapProject: """Find the snapcraft.yaml to load. + :param project_dir: The directory to search for the project yaml file. If not + provided, the current working directory is used. + :raises SnapcraftError: if the project yaml file cannot be found. """ for snap_project in _SNAP_PROJECT_FILES: - if snap_project.project_file.exists(): + if project_dir: + snap_project_path = project_dir / snap_project.project_file + else: + snap_project_path = snap_project.project_file + + if snap_project_path.exists(): return snap_project raise errors.ProjectMissing() diff --git a/snapcraft/services/__init__.py b/snapcraft/services/__init__.py index 746d7dbafb..055e32006a 100644 --- a/snapcraft/services/__init__.py +++ b/snapcraft/services/__init__.py @@ -17,6 +17,7 @@ """Snapcraft services.""" from snapcraft.services.assertions import Assertion +from snapcraft.services.init import Init from snapcraft.services.lifecycle import Lifecycle from snapcraft.services.package import Package from snapcraft.services.provider import Provider @@ -26,6 +27,7 @@ __all__ = [ "Assertion", + "Init", "Lifecycle", "Package", "Provider", diff --git a/snapcraft/services/init.py b/snapcraft/services/init.py new file mode 100644 index 0000000000..de736debc1 --- /dev/null +++ b/snapcraft/services/init.py @@ -0,0 +1,83 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2024 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 +# 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 warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Service for initializing a project.""" + +import pathlib + +import craft_cli +from craft_application import services +from typing_extensions import override + +from snapcraft import errors +from snapcraft.models.project import validate_name +from snapcraft.parts.yaml_utils import get_snap_project + + +class Init(services.InitService): + """Service class for initializing a project.""" + + @override + def initialise_project( + self, + *, + project_dir: pathlib.Path, + project_name: str, + template_dir: pathlib.Path, + ) -> None: + try: + validate_name(name=project_name, field_name="snap") + if len(project_name) > 40: + raise ValueError("snap names must be 40 characters or less") + except ValueError as err: + raise errors.SnapcraftError( + message=f"Invalid snap name {project_name!r}: {str(err)}.", + resolution="Provide a valid name with '--name' or rename the project directory.", + ) from err + + super().initialise_project( + project_dir=project_dir, + project_name=project_name, + template_dir=template_dir, + ) + craft_cli.emit.message( + "Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more " + "information about the snapcraft.yaml format." + ) + + @override + def check_for_existing_files( + self, + *, + project_dir: pathlib.Path, + template_dir: pathlib.Path, + ) -> None: + try: + craft_cli.emit.progress("Checking for an existing 'snapcraft.yaml'.") + project = get_snap_project(project_dir) + # the `ProjectMissing` error means a new project can be initialised + except errors.ProjectMissing: + craft_cli.emit.debug("Could not find an existing 'snapcraft.yaml'.") + else: + raise errors.SnapcraftError( + "Could not initialise a new snapcraft project because " + f"{str(project.project_file)!r} already exists" + ) + + super().check_for_existing_files( + project_dir=project_dir, + template_dir=template_dir, + ) diff --git a/snapcraft/services/service_factory.py b/snapcraft/services/service_factory.py index bbb9f0630f..0d51c74168 100644 --- a/snapcraft/services/service_factory.py +++ b/snapcraft/services/service_factory.py @@ -33,6 +33,9 @@ class SnapcraftServiceFactory(ServiceFactory): project: models.Project | None = None # type: ignore[reportIncompatibleVariableOverride] # These are overrides of default ServiceFactory services + InitClass: type[services.Init] = ( # type: ignore[reportIncompatibleVariableOverride] + services.Init + ) LifecycleClass: type[services.Lifecycle] = ( # type: ignore[reportIncompatibleVariableOverride] services.Lifecycle ) diff --git a/snapcraft/templates/simple/snap/snapcraft.yaml.j2 b/snapcraft/templates/simple/snap/snapcraft.yaml.j2 new file mode 100644 index 0000000000..5b69f37f7f --- /dev/null +++ b/snapcraft/templates/simple/snap/snapcraft.yaml.j2 @@ -0,0 +1,17 @@ +name: {{ name }} # you probably want to 'snapcraft register ' +base: core24 # the base snap is the execution environment for this snap +version: '0.1' # just for humans, typically '1.2+git' or '1.3.2' +summary: Single-line elevator pitch for your amazing snap # 79 char long summary +description: | + This is my-snap's description. You have a paragraph or two to tell the + most important story about your snap. Keep it under 100 words though, + we live in tweetspace and your description wants to look good in the snap + store. + +grade: devel # must be 'stable' to release into candidate/stable channels +confinement: devmode # use 'strict' once you have the right plugs and slots + +parts: + my-part: + # See 'snapcraft plugins' + plugin: nil diff --git a/tests/spread/general/init/task.yaml b/tests/spread/general/init/task.yaml index d66d8b328d..e86e905028 100644 --- a/tests/spread/general/init/task.yaml +++ b/tests/spread/general/init/task.yaml @@ -1,16 +1,60 @@ summary: Run snapcraft init +systems: [ubuntu-22*] + +environment: + PROFILE: null + PROJECT_DIR: null + NAME: null + PROFILE/default_profile: null + PROFILE/simple_profile: simple + NAME/with_name: test-snap-name + PROJECT_DIR/with_project_dir: test-project-dir + NAME/with_project_dir_and_name: test-snap-name + PROJECT_DIR/with_project_dir_and_name: test-project-dir + restore: | + unset SNAPCRAFT_BUILD_ENVIRONMENT + + if [[ -n "$PROJECT_DIR" ]]; then + cd "$PROJECT_DIR" + fi + + snapcraft clean + rm -f ./*.snap + rm -rf ./snap execute: | - # unset SNAPCRAFT_BUILD_ENVIRONMENT=host unset SNAPCRAFT_BUILD_ENVIRONMENT - # initialize a new snapcraft project - snapcraft init + args=("init") + + if [[ -n "$PROFILE" ]]; then + args+=("--profile" "$PROFILE") + fi + + if [[ -n "$NAME" ]]; then + args+=("--name" "$NAME") + fi + + if [[ -n "$PROJECT_DIR" ]]; then + args+=("$PROJECT_DIR") + fi + + snapcraft "${args[@]}" + + if [[ -n "$PROJECT_DIR" ]]; then + cd "$PROJECT_DIR" + fi + + if [[ -n "$NAME" ]]; then + expected_name="$NAME" + else + expected_name="$(basename "$PWD")" + fi - # the base should be core24 + grep "^name: ${expected_name}" snap/snapcraft.yaml grep "^base: core24" snap/snapcraft.yaml # 'snapcraft init' should create a usable snapcraft.yaml file diff --git a/tests/spread/general/version-git/task.yaml b/tests/spread/general/version-git/task.yaml index 65655016f1..538541d415 100644 --- a/tests/spread/general/version-git/task.yaml +++ b/tests/spread/general/version-git/task.yaml @@ -31,10 +31,10 @@ execute: | # First with lxd snapcraft --use-lxd ls -l - test -f my-snap-name_5.5-dirty_amd64.snap + test -f test-snap_5.5-dirty_amd64.snap rm ./*.snap # Then on host. snapcraft --destructive-mode ls -l - test -f my-snap-name_5.5-dirty_amd64.snap + test -f test-snap_5.5-dirty_amd64.snap diff --git a/tests/unit/commands/test_init.py b/tests/unit/commands/test_init.py index 2398ee3915..a5b760dc61 100644 --- a/tests/unit/commands/test_init.py +++ b/tests/unit/commands/test_init.py @@ -14,28 +14,60 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import pathlib import sys -from pathlib import Path from textwrap import dedent -from unittest.mock import call import pytest -from snapcraft import cli +from snapcraft import application from snapcraft.models.project import Project -from snapcraft.parts.yaml_utils import _SNAP_PROJECT_FILES, apply_yaml, process_yaml - - -@pytest.fixture(autouse=True) -def mock_argv(mocker): - return mocker.patch.object(sys, "argv", ["snapcraft", "init"]) - - -def test_init_default(emitter, new_dir): +from snapcraft.parts.yaml_utils import apply_yaml, process_yaml + + +@pytest.fixture +def valid_new_dir(tmp_path, monkeypatch): + """Change to a new temporary directory whose name is a valid snap name.""" + new_dir = tmp_path / "test-snap-name-dir" + new_dir.mkdir() + monkeypatch.chdir(new_dir) + return new_dir + + +def _create_command( + *, + profile: str | None = None, + project_dir: str | None = None, + name: str | None = None, +): + """Build a snapcraft init command.""" + cmd = ["snapcraft", "init"] + if profile: + cmd.extend(["--profile", profile]) + if project_dir: + cmd.append(project_dir) + if name: + cmd.extend(["--name", name]) + return cmd + + +@pytest.mark.parametrize("profile", [None, "simple"]) +@pytest.mark.parametrize("name", [None, "test-snap-name"]) +@pytest.mark.parametrize("project_dir", [None, "test-project-dir"]) +def test_init_default(profile, name, project_dir, emitter, valid_new_dir, mocker): """Test the 'snapcraft init' command.""" - snapcraft_yaml = Path("snap/snapcraft.yaml") - - cli.run() + if name: + expected_name = name + elif project_dir: + expected_name = project_dir + else: + expected_name = str(valid_new_dir.name) + snapcraft_yaml = pathlib.Path(project_dir or valid_new_dir) / "snap/snapcraft.yaml" + cmd = _create_command(profile=profile, project_dir=project_dir, name=name) + mocker.patch.object(sys, "argv", cmd) + app = application.create_app() + + app.run() assert snapcraft_yaml.exists() # unmarshal the snapcraft.yaml to verify its contents @@ -43,7 +75,7 @@ def test_init_default(emitter, new_dir): project = Project.unmarshal(data) assert project == Project.unmarshal( { - "name": "my-snap-name", + "name": expected_name, "base": "core24", "version": "0.1", "summary": "Single-line elevator pitch for your amazing snap", @@ -61,46 +93,10 @@ def test_init_default(emitter, new_dir): "platforms": {"amd64": {"build-on": "amd64", "build-for": "amd64"}}, } ) - emitter.assert_interactions( - [ - call("progress", "Checking for an existing 'snapcraft.yaml'."), - call("progress", "Could not find an existing 'snapcraft.yaml'."), - call("progress", "Creating 'snap/snapcraft.yaml'."), - call("message", "Created 'snap/snapcraft.yaml'."), - call( - "message", - "Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more " - "information about the snapcraft.yaml format.", - ), - ] - ) - - -def test_init_snap_dir_exists(emitter, new_dir): - """'snapcraft init' should work even if the 'snap/' directory already exists.""" - snapcraft_yaml = Path("snap/snapcraft.yaml") - Path("snap").mkdir() - - cli.run() - - assert snapcraft_yaml.exists() - emitter.assert_message("Created 'snap/snapcraft.yaml'.") - - -@pytest.mark.parametrize( - "snapcraft_yaml", [project.project_file for project in _SNAP_PROJECT_FILES] -) -def test_init_exists(capsys, emitter, new_dir, snapcraft_yaml): - """Raise an error if a snapcraft.yaml file already exists.""" - snapcraft_yaml.parent.mkdir(parents=True, exist_ok=True) - snapcraft_yaml.touch() - - cli.run() - - out, err = capsys.readouterr() - assert not out - assert ( - "could not initialize a new snapcraft project because " - f"{str(snapcraft_yaml)!r} already exists" - ) in err emitter.assert_progress("Checking for an existing 'snapcraft.yaml'.") + emitter.assert_debug("Could not find an existing 'snapcraft.yaml'.") + emitter.assert_message( + "Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more " + "information about the snapcraft.yaml format." + ) + emitter.assert_message("Successfully initialised project.") diff --git a/tests/unit/extensions/test_env_injector.py b/tests/unit/extensions/test_env_injector.py index 0797b64f1d..b94691deda 100644 --- a/tests/unit/extensions/test_env_injector.py +++ b/tests/unit/extensions/test_env_injector.py @@ -99,7 +99,7 @@ def test_get_parts_snippet(self, envinjector_extension, unsupported_arch): cargo build --target {toolchain} --release mkdir -p $SNAPCRAFT_PART_INSTALL/bin/command-chain - + cp target/{toolchain}/release/env-exporter $SNAPCRAFT_PART_INSTALL/bin/command-chain """, } diff --git a/tests/unit/parts/test_yaml_utils.py b/tests/unit/parts/test_yaml_utils.py index fea87d3bce..23340fe92b 100644 --- a/tests/unit/parts/test_yaml_utils.py +++ b/tests/unit/parts/test_yaml_utils.py @@ -16,6 +16,7 @@ import io +import pathlib from textwrap import dedent import pytest @@ -248,3 +249,15 @@ def test_get_base_from_yaml(mocker): project_type="test-type", ) assert effective_base == "test-effective-base" + + +@pytest.mark.parametrize("project", yaml_utils._SNAP_PROJECT_FILES) +@pytest.mark.parametrize("project_dir", [None, "test-project-dir"]) +def test_get_snap_project(project, project_dir, new_dir): + project_dir = pathlib.Path(project_dir) if project_dir else new_dir + (project_dir / project.project_file).parent.mkdir(parents=True, exist_ok=True) + (project_dir / project.project_file).touch() + + actual_project = yaml_utils.get_snap_project(project_dir) + + assert actual_project == project diff --git a/tests/unit/services/test_init.py b/tests/unit/services/test_init.py new file mode 100644 index 0000000000..f073d2effd --- /dev/null +++ b/tests/unit/services/test_init.py @@ -0,0 +1,129 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# 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 . + +"""Tests for the init service.""" + +import importlib.resources +import pathlib + +import pytest + +from snapcraft import errors +from snapcraft.parts.yaml_utils import _SNAP_PROJECT_FILES + + +@pytest.fixture() +def init_service(default_factory): + from snapcraft.application import APP_METADATA + from snapcraft.services import Init + + service = Init(app=APP_METADATA, services=default_factory) + + return service + + +def template_dir(): + with importlib.resources.path("snapcraft", "templates") as _template_dir: + return _template_dir / "simple" + + +@pytest.mark.parametrize( + "name", + [ + "name", + "name-with-dashes", + "name0123", + "0123name", + "a234567890123456789012345678901234567890", + ], +) +def test_init_valid_name(name, init_service, new_dir, emitter): + """Initialise a project with a valid snap name.""" + init_service.initialise_project( + project_dir=new_dir, project_name=name, template_dir=template_dir() + ) + + assert (new_dir / "snap/snapcraft.yaml").exists() + emitter.assert_message( + "Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more " + "information about the snapcraft.yaml format." + ) + + +@pytest.mark.parametrize( + "name,error", + [ + ("name_with_underscores", "snap names can only use"), + ("name-with-UPPERCASE", "snap names can only use"), + ("name with spaces", "snap names can only use"), + ("-name-starts-with-hyphen", "snap names cannot start with a hyphen"), + ("name-ends-with-hyphen-", "snap names cannot end with a hyphen"), + ("name-has--two-hyphens", "snap names cannot have two hyphens in a row"), + ("123456", "snap names can only use"), + ( + "a2345678901234567890123456789012345678901", + "snap names must be 40 characters or less", + ), + ], +) +def test_init_invalid_name(name, error, init_service, new_dir): + """Error on invalid names.""" + expected_error = f"Invalid snap name {name!r}: {error}." + + with pytest.raises(errors.SnapcraftError, match=expected_error): + init_service.initialise_project( + project_dir=new_dir, + project_name=name, + template_dir=template_dir(), + ) + + +def test_init_snap_dir_exists(init_service, new_dir, emitter): + """Initialise a project even if the 'snap/' directory already exists.""" + snapcraft_yaml = new_dir / "snap/snapcraft.yaml" + snapcraft_yaml.parent.mkdir(parents=True) + + init_service.check_for_existing_files( + project_dir=new_dir, template_dir=template_dir() + ) + init_service.initialise_project( + project_dir=new_dir, project_name="test-snap-name", template_dir=template_dir() + ) + + assert snapcraft_yaml.exists() + emitter.assert_message( + "Go to https://docs.snapcraft.io/the-snapcraft-format/8337 for more " + "information about the snapcraft.yaml format." + ) + + +@pytest.mark.parametrize( + "project_file", [project.project_file for project in _SNAP_PROJECT_FILES] +) +def test_init_exists(init_service, new_dir, project_file): + """Raise an error if a snapcraft.yaml file already exists.""" + snapcraft_yaml = pathlib.Path(new_dir) / project_file + snapcraft_yaml.parent.mkdir(parents=True, exist_ok=True) + snapcraft_yaml.touch() + expected = ( + "Could not initialise a new snapcraft project because " + f"{str(project_file)!r} already exists" + ) + + with pytest.raises(errors.SnapcraftError, match=expected): + init_service.check_for_existing_files( + project_dir=new_dir, template_dir=template_dir() + ) diff --git a/tools/docs/gen_cli_docs.py b/tools/docs/gen_cli_docs.py index 84a7e9c5a0..429be288f1 100755 --- a/tools/docs/gen_cli_docs.py +++ b/tools/docs/gen_cli_docs.py @@ -82,6 +82,7 @@ def main(docs_dir): # Create a dispatcher like Snapcraft does to get access to the same options. app = application.create_app() + app._setup_logging() command_groups = app.command_groups # Create a dispatcher like Snapcraft does to get access to the same options. @@ -102,8 +103,7 @@ def main(docs_dir): g = group_path.open("w") for cmd_class in sorted(group.commands, key=lambda c: c.name): - # craft-application.AppCommand require 'app' and 'services' in the config - cmd = cmd_class(config={"app": {}, "services": {}}) + cmd = cmd_class(app.app_config) p = _CustomArgumentParser(help_builder) cmd.fill_parser(p) From 26750ac3770edb92cf76a1e9baeffd56b2b262de Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 13 Nov 2024 12:01:47 -0600 Subject: [PATCH 147/151] build(deps): upgrade to canonical-sphinx 0.2.0 Signed-off-by: Callahan Kovacs --- .readthedocs.yaml | 5 ++--- docs/conf.py | 1 + docs/index.rst | 30 ++++++++++-------------------- setup.py | 2 +- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index fa08f743a2..ba588141eb 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -16,9 +16,8 @@ build: - libapt-pkg-dev jobs: post_checkout: - - git fetch --unshallow || true # See https://docs.readthedocs.io/en/latest/build-customization.html#unshallow-git-clone - - git fetch --tags --depth 1 # Also fetch tags - - git describe # Useful for debugging + - git fetch --tags --unshallow # Also fetch tags + - git describe # Make sure we get a proper version # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/docs/conf.py b/docs/conf.py index e2ba3c7475..180d65a584 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,6 +38,7 @@ html_context = { "product_page": "github.com/canonical/snapcraft", "github_url": "https://github.com/canonical/snapcraft", + "display_contributors": False, } extensions = [ diff --git a/docs/index.rst b/docs/index.rst index 298a6eeacf..b6f8c41d50 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,26 +12,16 @@ Snapcraft reference/index explanation/index -.. grid:: 1 1 2 2 - - .. grid-item-card:: :ref:`Tutorial ` - - **Get started** with a hands-on introduction to Snapcraft - - .. grid-item-card:: :ref:`How-to guides ` - - **Step-by-step guides** covering key operations and common tasks - -.. grid:: 1 1 2 2 - :reverse: - - .. grid-item-card:: :ref:`Reference ` - - **Technical information** about Snapcraft - - .. grid-item-card:: :ref:`Explanation ` - - **Discussion and clarification** of key topics +.. list-table:: + + * - | :ref:`Tutorial ` + | **Get started** with a hands-on introduction to Snapcraft + - | :ref:`How-to guides ` + | **Step-by-step guides** covering key operations and common tasks + * - | :ref:`Reference ` + | **Technical information** about Snapcraft + - | :ref:`Explanation ` + | **Discussion and clarification** of key topics Project and community ===================== diff --git a/setup.py b/setup.py index 45a840ce25..4d95544b22 100755 --- a/setup.py +++ b/setup.py @@ -142,7 +142,7 @@ def recursive_data_files(directory, install_directory): ] docs_requires = { - "canonical-sphinx", + "canonical-sphinx[full]>=0.2.0", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinxcontrib-details-directive", From 69e4e551d003d408c9dbd0b0f200635b327f2b23 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 14 Nov 2024 08:50:25 -0600 Subject: [PATCH 148/151] build(deps): update deps with freeze-requirements Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 117 +++++++++++++++++++++-------------------- requirements-docs.txt | 83 +++++++++++++++-------------- requirements.txt | 49 +++++++++-------- 3 files changed, 124 insertions(+), 125 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 67fee633e5..4e23ef8bfa 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,46 +1,46 @@ alabaster==0.7.16 annotated-types==0.7.0 -anyio==4.4.0 +anyio==4.6.2.post1 apeye==1.4.1 apeye-core==1.1.5 -astroid==3.2.4 +astroid==3.3.5 attrs==24.2.0 -autodocsumm==0.2.13 +autodocsumm==0.2.14 babel==2.16.0 beautifulsoup4==4.12.3 black==24.10.0 boolean.py==4.0 -bracex==2.5 -CacheControl==0.14.0 +bracex==2.5.post1 +CacheControl==0.14.1 cachetools==5.5.0 -canonical-sphinx==0.1.0 +canonical-sphinx==0.2.0 canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 -certifi==2024.7.4 +certifi==2024.8.30 cffi==1.17.1 chardet==5.2.0 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 click==8.1.7 codespell==2.3.0 colorama==0.4.6 -coverage==7.6.1 +coverage==7.6.4 craft-application==4.4.0 craft-archives==2.0.1 -craft-cli==2.10.0 +craft-cli==2.10.1 craft-grammar==2.0.1 craft-parts==2.1.2 craft-platforms==0.4.0 craft-providers==2.0.4 craft-store==3.0.2 -cryptography==43.0.1 +cryptography==43.0.3 cssutils==2.11.1 dict2css==0.3.0.post1 -dill==0.3.8 -distlib==0.3.8 +dill==0.3.9 +distlib==0.3.9 distro==1.9.0 docutils==0.19 domdf-python-tools==3.9.0 -filelock==3.15.4 +filelock==3.16.1 fixtures==4.1.0 flake8==7.1.1 furo==2024.8.6 @@ -51,41 +51,42 @@ h11==0.14.0 html5lib==1.1 httplib2==0.22.0 hupper==1.12.1 -idna==3.7 +idna==3.10 imagesize==1.4.1 -importlib_metadata==8.2.0 iniconfig==2.0.0 isort==5.13.2 jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 jeepney==0.8.0 Jinja2==3.1.4 jsonschema==2.5.1 -keyring==24.3.1 +keyring==25.5.0 launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 -license-expression==30.3.1 +license-expression==30.4.0 linkify-it-py==2.0.3 lxml==5.3.0 macaroonbakery==1.3.4 Markdown==3.7 markdown-it-py==3.0.0 -MarkupSafe==2.1.5 +MarkupSafe==3.0.2 mccabe==0.7.0 mdit-py-plugins==0.4.2 mdurl==0.1.2 -more-itertools==10.4.0 -msgpack==1.0.8 -mypy==1.11.2 +more-itertools==10.5.0 +msgpack==1.1.0 +mypy==1.13.0 mypy-extensions==1.0.0 myst-parser==4.0.0 natsort==8.4.0 oauthlib==3.2.2 overrides==7.7.0 -packaging==24.1 +packaging==24.2 PasteDeploy==3.1.0 pathspec==0.12.1 -pbr==6.0.0 +pbr==6.1.0 pexpect==4.9.0 plaster==1.1.2 plaster-pastedeploy==1.0.1 @@ -93,48 +94,49 @@ platformdirs==4.3.6 pluggy==1.5.0 polib==1.2.0 progressbar==2.5 -protobuf==5.27.3 -psutil==6.0.0 +protobuf==5.28.3 +psutil==6.1.0 ptyprocess==0.7.0 +pyasynchat==1.0.4 +pyasyncore==1.0.4 pycodestyle==2.12.1 pycparser==2.22 -pydantic==2.8.2 -pydantic_core==2.20.1 -pydantic_yaml==1.3.0 +pydantic==2.9.2 +pydantic_core==2.23.4 pydocstyle==6.3.0 pyelftools==0.31 pyflakes==3.2.0 -pyftpdlib==1.5.10 +pyftpdlib==2.0.1 pygit2==1.13.3 Pygments==2.18.0 -pylint==3.2.7 +pylint==3.3.1 pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 -pyparsing==3.1.4 -pyproject-api==1.7.1 +pyparsing==3.2.0 +pyproject-api==1.8.0 pyramid==2.0.2 pyRFC3339==1.1 pyspelling==2.10 pytest==8.3.3 pytest-check==2.4.1 -pytest-cov==5.0.0 +pytest-cov==6.0.0 pytest-mock==3.14.0 pytest-subprocess==1.5.2 python-dateutil==2.9.0.post0 python-debian==0.1.49 -pytz==2024.1 +pytz==2024.2 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 ruamel.yaml==0.18.6 -ruamel.yaml.clib==0.2.8 +ruamel.yaml.clib==0.2.12 SecretStorage==3.3.3 -setuptools==72.2.0 +setuptools==75.5.0 simplejson==3.19.3 six==1.16.0 smmap==5.0.1 @@ -143,16 +145,16 @@ sniffio==1.3.1 snowballstemmer==2.2.0 soupsieve==2.6 Sphinx==7.3.7 -sphinx-autobuild==2024.4.16 -sphinx-autodoc-typehints==2.2.3 +sphinx-autobuild==2024.10.3 +sphinx-autodoc-typehints==2.3.0 sphinx-basic-ng==1.0.0b2 sphinx-copybutton==0.5.2 sphinx-jinja2-compat==0.3.0 -sphinx-lint==0.9.1 +sphinx-lint==1.0.0 sphinx-notfound-page==1.0.4 sphinx-prompt==1.8.0 sphinx-tabs==3.4.5 -sphinx-toolbox==3.8.0 +sphinx-toolbox==3.8.1 sphinx_design==0.6.1 sphinx_reredirects==0.1.5 sphinxcontrib-applehelp==2.0.0 @@ -164,18 +166,18 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.40.0 +starlette==0.41.2 tabulate==0.9.0 testscenarios==0.5.0 testtools==2.7.2 -tinydb==4.8.0 +tinydb==4.8.2 toml==0.10.2 tomlkit==0.13.2 -tox==4.18.0 +tox==4.23.2 translationstring==1.4 types-PyYAML==6.0.12.20240917 types-requests==2.31.0.6 -types-setuptools==71.1.0.20240818 +types-setuptools==75.3.0.20241112 types-simplejson==3.19.0.20240801 types-tabulate==0.9.0.20240106 types-toml==0.10.8.20240310 @@ -183,21 +185,20 @@ types-urllib3==1.26.25.14 typing_extensions==4.12.2 uc-micro-py==1.0.3 urllib3==2.2.3 -uvicorn==0.30.6 -validators==0.33.0 +uvicorn==0.32.0 +validators==0.34.0 venusian==3.1.0 -virtualenv==20.26.5 -wadllib==1.3.6 -watchfiles==0.23.0 -wcmatch==9.0 +virtualenv==20.27.1 +wadllib==2.0.0 +watchfiles==0.24.0 +wcmatch==10.0 webencodings==0.5.1 -WebOb==1.8.8 -websockets==12.0 -wheel==0.44.0 +WebOb==1.8.9 +websockets==14.1 +wheel==0.45.0 ws4py==0.5.1 yamllint==1.35.1 -zipp==3.20.2 zope.deprecation==5.0 -zope.interface==7.0.3 +zope.interface==7.1.1 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" -pyinstaller==5.13.2; sys.platform == "win32" +pyinstaller==5.13.1; sys.platform == "win32" diff --git a/requirements-docs.txt b/requirements-docs.txt index b5494fa73e..375669ddbc 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,39 +1,39 @@ alabaster==0.7.16 annotated-types==0.7.0 -anyio==4.4.0 +anyio==4.6.2.post1 apeye==1.4.1 apeye-core==1.1.5 attrs==24.2.0 -autodocsumm==0.2.13 +autodocsumm==0.2.14 babel==2.16.0 beautifulsoup4==4.12.3 boolean.py==4.0 -bracex==2.5 -CacheControl==0.14.0 -canonical-sphinx==0.1.0 +bracex==2.5.post1 +CacheControl==0.14.1 +canonical-sphinx==0.2.0 canonical-sphinx-extensions==0.0.23 catkin-pkg==1.0.0 -certifi==2024.7.4 +certifi==2024.8.30 cffi==1.17.1 chardet==5.2.0 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 click==8.1.7 colorama==0.4.6 craft-application==4.4.0 craft-archives==2.0.1 -craft-cli==2.10.0 +craft-cli==2.10.1 craft-grammar==2.0.1 craft-parts==2.1.2 craft-platforms==0.4.0 craft-providers==2.0.4 craft-store==3.0.2 -cryptography==43.0.1 +cryptography==43.0.3 cssutils==2.11.1 dict2css==0.3.0.post1 distro==1.9.0 docutils==0.19 domdf-python-tools==3.9.0 -filelock==3.15.4 +filelock==3.16.1 furo==2024.8.6 gitdb==4.0.11 GitPython==3.1.43 @@ -41,66 +41,66 @@ gnupg==2.3.1 h11==0.14.0 html5lib==1.1 httplib2==0.22.0 -idna==3.7 +idna==3.10 imagesize==1.4.1 -importlib_metadata==8.2.0 jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 jeepney==0.8.0 Jinja2==3.1.4 jsonschema==2.5.1 -keyring==24.3.1 +keyring==25.5.0 launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 -license-expression==30.3.1 +license-expression==30.4.0 linkify-it-py==2.0.3 lxml==5.3.0 macaroonbakery==1.3.4 Markdown==3.7 markdown-it-py==3.0.0 -MarkupSafe==2.1.5 +MarkupSafe==3.0.2 mdit-py-plugins==0.4.2 mdurl==0.1.2 -more-itertools==10.4.0 -msgpack==1.0.8 +more-itertools==10.5.0 +msgpack==1.1.0 mypy-extensions==1.0.0 myst-parser==4.0.0 natsort==8.4.0 oauthlib==3.2.2 overrides==7.7.0 -packaging==24.1 +packaging==24.2 platformdirs==4.3.6 polib==1.2.0 progressbar==2.5 -protobuf==5.27.3 -psutil==6.0.0 +protobuf==5.28.3 +psutil==6.1.0 pycparser==2.22 -pydantic==2.8.2 -pydantic_core==2.20.1 -pydantic_yaml==1.3.0 +pydantic==2.9.2 +pydantic_core==2.23.4 pyelftools==0.31 pygit2==1.13.3 Pygments==2.18.0 pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 -pyparsing==3.1.4 +pyparsing==3.2.0 pyRFC3339==1.1 pyspelling==2.10 python-dateutil==2.9.0.post0 python-debian==0.1.49 -pytz==2024.1 +pytz==2024.2 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 -regex==2024.7.24 +regex==2024.11.6 requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 ruamel.yaml==0.18.6 -ruamel.yaml.clib==0.2.8 +ruamel.yaml.clib==0.2.12 SecretStorage==3.3.3 -setuptools==72.2.0 +setuptools==75.5.0 simplejson==3.19.3 six==1.16.0 smmap==5.0.1 @@ -109,16 +109,16 @@ sniffio==1.3.1 snowballstemmer==2.2.0 soupsieve==2.6 Sphinx==7.3.7 -sphinx-autobuild==2024.4.16 -sphinx-autodoc-typehints==2.2.3 +sphinx-autobuild==2024.10.3 +sphinx-autodoc-typehints==2.3.0 sphinx-basic-ng==1.0.0b2 sphinx-copybutton==0.5.2 sphinx-jinja2-compat==0.3.0 -sphinx-lint==0.9.1 +sphinx-lint==1.0.0 sphinx-notfound-page==1.0.4 sphinx-prompt==1.8.0 sphinx-tabs==3.4.5 -sphinx-toolbox==3.8.0 +sphinx-toolbox==3.8.1 sphinx_design==0.6.1 sphinx_reredirects==0.1.5 sphinxcontrib-applehelp==2.0.0 @@ -130,21 +130,20 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 sphinxext-opengraph==0.9.1 -starlette==0.40.0 +starlette==0.41.2 tabulate==0.9.0 -tinydb==4.8.0 +tinydb==4.8.2 toml==0.10.2 typing_extensions==4.12.2 uc-micro-py==1.0.3 urllib3==2.2.3 -uvicorn==0.30.6 -validators==0.33.0 -wadllib==1.3.6 -watchfiles==0.23.0 -wcmatch==9.0 +uvicorn==0.32.0 +validators==0.34.0 +wadllib==2.0.0 +watchfiles==0.24.0 +wcmatch==10.0 webencodings==0.5.1 -websockets==12.0 -wheel==0.44.0 +websockets==14.1 +wheel==0.45.0 ws4py==0.5.1 -zipp==3.20.2 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" diff --git a/requirements.txt b/requirements.txt index 364fdc3f76..1058430dd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,80 +2,79 @@ annotated-types==0.7.0 attrs==24.2.0 boolean.py==4.0 catkin-pkg==1.0.0 -certifi==2024.7.4 +certifi==2024.8.30 cffi==1.17.1 chardet==5.2.0 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 click==8.1.7 craft-application==4.4.0 craft-archives==2.0.1 -craft-cli==2.10.0 +craft-cli==2.10.1 craft-grammar==2.0.1 craft-parts==2.1.2 craft-platforms==0.4.0 craft-providers==2.0.4 craft-store==3.0.2 -cryptography==43.0.1 +cryptography==43.0.3 distro==1.9.0 docutils==0.19 gnupg==2.3.1 httplib2==0.22.0 -idna==3.7 -importlib_metadata==8.2.0 +idna==3.10 jaraco.classes==3.4.0 +jaraco.context==6.0.1 +jaraco.functools==4.1.0 jeepney==0.8.0 +Jinja2==3.1.4 jsonschema==2.5.1 -keyring==24.3.1 +keyring==25.5.0 launchpadlib==2.0.0 lazr.restfulclient==0.14.6 lazr.uri==1.0.6 -license-expression==30.3.1 +license-expression==30.4.0 lxml==5.3.0 macaroonbakery==1.3.4 -more-itertools==10.4.0 +MarkupSafe==3.0.2 +more-itertools==10.5.0 mypy-extensions==1.0.0 oauthlib==3.2.2 overrides==7.7.0 -packaging==24.1 +packaging==24.2 platformdirs==4.3.6 progressbar==2.5 -protobuf==5.27.3 -psutil==6.0.0 +protobuf==5.28.3 +psutil==6.1.0 pycparser==2.22 -pydantic==2.8.2 -pydantic_core==2.20.1 -pydantic_yaml==1.3.0 +pydantic==2.9.2 +pydantic_core==2.23.4 pyelftools==0.31 pygit2==1.13.3 pylxd==2.3.5 pymacaroons==0.13.0 PyNaCl==1.5.0 -pyparsing==3.1.4 +pyparsing==3.2.0 pyRFC3339==1.1 python-dateutil==2.9.0.post0 python-debian==0.1.49 -pytz==2024.1 +pytz==2024.2 pyxdg==0.28 PyYAML==6.0.2 raven==6.10.0 requests==2.32.3 requests-toolbelt==1.0.0 requests-unixsocket2==0.4.2 -ruamel.yaml==0.18.6 -ruamel.yaml.clib==0.2.8 SecretStorage==3.3.3 -setuptools==72.2.0 +setuptools==75.5.0 simplejson==3.19.3 six==1.16.0 snap-helpers==0.4.2 tabulate==0.9.0 -tinydb==4.8.0 +tinydb==4.8.2 toml==0.10.2 typing_extensions==4.12.2 urllib3==2.2.3 -validators==0.33.0 -wadllib==1.3.6 -wheel==0.44.0 +validators==0.34.0 +wadllib==2.0.0 +wheel==0.45.0 ws4py==0.5.1 -zipp==3.20.2 python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" From 205a7c58444b0e0fa6f8ecee9c25687fcb848600 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Wed, 13 Nov 2024 12:00:38 -0600 Subject: [PATCH 149/151] build: use pip-compile Signed-off-by: Callahan Kovacs --- tools/freeze-requirements.sh | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tools/freeze-requirements.sh b/tools/freeze-requirements.sh index 5f086892ee..81ee20028b 100755 --- a/tools/freeze-requirements.sh +++ b/tools/freeze-requirements.sh @@ -25,7 +25,7 @@ python3 -m venv "$venv_dir" # shellcheck disable=SC1090,SC1091 source "$venv_dir/bin/activate" -pip install -U setuptools pip wheel +pip install -U setuptools pip wheel pip-tools # Pull in host python3-apt site package to avoid installation. site_pkgs="$(readlink -f "$venv_dir"/lib/python3.*/site-packages/)" @@ -36,17 +36,14 @@ dpkg -x ./*.deb . cp -r usr/lib/python3/dist-packages/* "$site_pkgs" popd -pip install -e . -pip freeze --exclude-editable > requirements.txt +pip-compile --upgrade --output-file requirements.txt requirements_fixups "requirements.txt" -pip install -e .[docs] -pip freeze --exclude-editable > requirements-docs.txt +pip-compile --upgrade --extra docs --output-file requirements-docs.txt requirements_fixups "requirements-docs.txt" # Set the configured python-apt and python-distutils-extra packages. -pip install -e .[dev] -pip freeze --exclude-editable > requirements-devel.txt +pip-compile --upgrade --extra dev --output-file requirements-devel.txt requirements_fixups "requirements-devel.txt" rm -rf "$venv_dir" From 032f7842218ad601fd9267467e96ae0eb0393a8d Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Thu, 14 Nov 2024 08:51:38 -0600 Subject: [PATCH 150/151] build(deps): update deps with freeze-requirements Signed-off-by: Callahan Kovacs --- requirements-devel.txt | 411 ++++++++++++++++++++++++++++++++--------- requirements-docs.txt | 398 ++++++++++++++++++++++++++++++++++++--- requirements.txt | 230 +++++++++++++++++++++-- 3 files changed, 899 insertions(+), 140 deletions(-) diff --git a/requirements-devel.txt b/requirements-devel.txt index 4e23ef8bfa..1b0da8041b 100644 --- a/requirements-devel.txt +++ b/requirements-devel.txt @@ -1,204 +1,431 @@ -alabaster==0.7.16 +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --extra=dev --output-file=requirements-devel.txt +# annotated-types==0.7.0 -anyio==4.6.2.post1 -apeye==1.4.1 -apeye-core==1.1.5 + # via + # craft-platforms + # pydantic astroid==3.3.5 + # via pylint attrs==24.2.0 -autodocsumm==0.2.14 -babel==2.16.0 -beautifulsoup4==4.12.3 + # via snapcraft (setup.py) black==24.10.0 -boolean.py==4.0 -bracex==2.5.post1 -CacheControl==0.14.1 + # via snapcraft (setup.py) +boolean-py==4.0 + # via license-expression cachetools==5.5.0 -canonical-sphinx==0.2.0 -canonical-sphinx-extensions==0.0.23 -catkin-pkg==1.0.0 + # via tox +catkin-pkg==1.0.0 ; sys_platform == "linux" + # via snapcraft (setup.py) certifi==2024.8.30 + # via requests cffi==1.17.1 + # via + # cryptography + # pygit2 + # pynacl chardet==5.2.0 + # via + # python-debian + # tox charset-normalizer==3.4.0 + # via requests click==8.1.7 -codespell==2.3.0 + # via + # black + # snapcraft (setup.py) +codespell[toml]==2.3.0 + # via snapcraft (setup.py) colorama==0.4.6 -coverage==7.6.4 + # via tox +coverage[toml]==7.6.4 + # via + # pytest-cov + # snapcraft (setup.py) craft-application==4.4.0 + # via snapcraft (setup.py) craft-archives==2.0.1 + # via + # craft-application + # snapcraft (setup.py) craft-cli==2.10.1 + # via + # craft-application + # snapcraft (setup.py) craft-grammar==2.0.1 + # via + # craft-application + # snapcraft (setup.py) craft-parts==2.1.2 + # via + # craft-application + # snapcraft (setup.py) craft-platforms==0.4.0 + # via + # craft-application + # snapcraft (setup.py) craft-providers==2.0.4 + # via + # craft-application + # snapcraft (setup.py) craft-store==3.0.2 + # via snapcraft (setup.py) cryptography==43.0.3 -cssutils==2.11.1 -dict2css==0.3.0.post1 + # via + # pylxd + # secretstorage dill==0.3.9 + # via pylint distlib==0.3.9 + # via virtualenv distro==1.9.0 + # via + # craft-archives + # craft-platforms + # lazr-restfulclient docutils==0.19 -domdf-python-tools==3.9.0 + # via + # catkin-pkg + # snapcraft (setup.py) filelock==3.16.1 + # via + # tox + # virtualenv fixtures==4.1.0 + # via snapcraft (setup.py) flake8==7.1.1 -furo==2024.8.6 -gitdb==4.0.11 -GitPython==3.1.43 + # via snapcraft (setup.py) gnupg==2.3.1 -h11==0.14.0 -html5lib==1.1 + # via snapcraft (setup.py) httplib2==0.22.0 + # via + # launchpadlib + # lazr-restfulclient hupper==1.12.1 + # via pyramid idna==3.10 -imagesize==1.4.1 + # via requests iniconfig==2.0.0 + # via pytest isort==5.13.2 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.1.0 + # via pylint +jaraco-classes==3.4.0 + # via + # craft-store + # keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.1.0 + # via keyring jeepney==0.8.0 -Jinja2==3.1.4 + # via + # keyring + # secretstorage +jinja2==3.1.4 + # via craft-application jsonschema==2.5.1 + # via snapcraft (setup.py) keyring==25.5.0 + # via craft-store launchpadlib==2.0.0 -lazr.restfulclient==0.14.6 -lazr.uri==1.0.6 + # via + # craft-archives + # snapcraft (setup.py) +lazr-restfulclient==0.14.6 + # via + # craft-archives + # launchpadlib + # snapcraft (setup.py) +lazr-uri==1.0.6 + # via + # craft-archives + # launchpadlib + # wadllib license-expression==30.4.0 -linkify-it-py==2.0.3 + # via craft-application lxml==5.3.0 + # via snapcraft (setup.py) macaroonbakery==1.3.4 -Markdown==3.7 -markdown-it-py==3.0.0 -MarkupSafe==3.0.2 + # via + # craft-store + # snapcraft (setup.py) +markupsafe==3.0.2 + # via jinja2 mccabe==0.7.0 -mdit-py-plugins==0.4.2 -mdurl==0.1.2 + # via + # flake8 + # pylint + # snapcraft (setup.py) more-itertools==10.5.0 -msgpack==1.1.0 + # via + # jaraco-classes + # jaraco-functools mypy==1.13.0 + # via snapcraft (setup.py) mypy-extensions==1.0.0 -myst-parser==4.0.0 -natsort==8.4.0 + # via + # black + # mypy + # snapcraft (setup.py) oauthlib==3.2.2 + # via lazr-restfulclient overrides==7.7.0 + # via + # craft-archives + # craft-grammar + # craft-parts + # craft-store + # snapcraft (setup.py) packaging==24.2 -PasteDeploy==3.1.0 + # via + # black + # craft-providers + # pyproject-api + # pytest + # snapcraft (setup.py) + # tox +pastedeploy==3.1.0 + # via plaster-pastedeploy pathspec==0.12.1 + # via + # black + # yamllint pbr==6.1.0 + # via + # fixtures + # testscenarios pexpect==4.9.0 + # via snapcraft (setup.py) plaster==1.1.2 + # via + # plaster-pastedeploy + # pyramid plaster-pastedeploy==1.0.1 + # via pyramid platformdirs==4.3.6 + # via + # black + # craft-application + # craft-cli + # pylint + # tox + # virtualenv pluggy==1.5.0 -polib==1.2.0 + # via + # pytest + # tox progressbar==2.5 + # via snapcraft (setup.py) protobuf==5.28.3 + # via macaroonbakery psutil==6.1.0 + # via gnupg ptyprocess==0.7.0 + # via pexpect pyasynchat==1.0.4 + # via pyftpdlib pyasyncore==1.0.4 + # via + # pyasynchat + # pyftpdlib pycodestyle==2.12.1 + # via + # flake8 + # snapcraft (setup.py) pycparser==2.22 + # via cffi pydantic==2.9.2 -pydantic_core==2.23.4 + # via + # craft-application + # craft-archives + # craft-grammar + # craft-parts + # craft-providers + # craft-store + # snapcraft (setup.py) +pydantic-core==2.23.4 + # via pydantic pydocstyle==6.3.0 + # via snapcraft (setup.py) pyelftools==0.31 + # via snapcraft (setup.py) pyflakes==3.2.0 + # via + # flake8 + # snapcraft (setup.py) pyftpdlib==2.0.1 + # via snapcraft (setup.py) pygit2==1.13.3 -Pygments==2.18.0 + # via + # craft-application + # snapcraft (setup.py) pylint==3.3.1 -pylxd==2.3.5 + # via snapcraft (setup.py) +pylxd==2.3.5 ; sys_platform == "linux" + # via snapcraft (setup.py) pymacaroons==0.13.0 -PyNaCl==1.5.0 + # via + # macaroonbakery + # snapcraft (setup.py) +pynacl==1.5.0 + # via + # macaroonbakery + # pymacaroons pyparsing==3.2.0 + # via + # catkin-pkg + # httplib2 pyproject-api==1.8.0 + # via tox pyramid==2.0.2 -pyRFC3339==1.1 -pyspelling==2.10 + # via snapcraft (setup.py) +pyrfc3339==1.1 + # via macaroonbakery pytest==8.3.3 + # via + # pytest-check + # pytest-cov + # pytest-mock + # pytest-subprocess + # snapcraft (setup.py) pytest-check==2.4.1 + # via snapcraft (setup.py) pytest-cov==6.0.0 + # via snapcraft (setup.py) pytest-mock==3.14.0 + # via snapcraft (setup.py) pytest-subprocess==1.5.2 + # via snapcraft (setup.py) + # via snapcraft (setup.py) python-dateutil==2.9.0.post0 -python-debian==0.1.49 + # via + # catkin-pkg + # pylxd +python-debian==0.1.49 ; sys_platform == "linux" + # via + # craft-archives + # snapcraft (setup.py) pytz==2024.2 + # via pyrfc3339 pyxdg==0.28 -PyYAML==6.0.2 + # via + # craft-parts + # craft-store + # snapcraft (setup.py) +pyyaml==6.0.2 + # via + # craft-application + # craft-cli + # craft-parts + # craft-providers + # snap-helpers + # snapcraft (setup.py) + # yamllint raven==6.10.0 -regex==2024.11.6 + # via snapcraft (setup.py) requests==2.32.3 + # via + # craft-application + # craft-parts + # craft-providers + # craft-store + # macaroonbakery + # pylxd + # requests-toolbelt + # requests-unixsocket2 + # snapcraft (setup.py) requests-toolbelt==1.0.0 + # via + # craft-store + # pylxd + # snapcraft (setup.py) requests-unixsocket2==0.4.2 -ruamel.yaml==0.18.6 -ruamel.yaml.clib==0.2.12 -SecretStorage==3.3.3 -setuptools==75.5.0 + # via + # craft-parts + # craft-providers + # snapcraft (setup.py) +secretstorage==3.3.3 + # via keyring simplejson==3.19.3 + # via snapcraft (setup.py) six==1.16.0 -smmap==5.0.1 + # via + # lazr-restfulclient + # macaroonbakery + # pymacaroons + # python-dateutil snap-helpers==0.4.2 -sniffio==1.3.1 + # via + # craft-application + # snapcraft (setup.py) snowballstemmer==2.2.0 -soupsieve==2.6 -Sphinx==7.3.7 -sphinx-autobuild==2024.10.3 -sphinx-autodoc-typehints==2.3.0 -sphinx-basic-ng==1.0.0b2 -sphinx-copybutton==0.5.2 -sphinx-jinja2-compat==0.3.0 -sphinx-lint==1.0.0 -sphinx-notfound-page==1.0.4 -sphinx-prompt==1.8.0 -sphinx-tabs==3.4.5 -sphinx-toolbox==3.8.1 -sphinx_design==0.6.1 -sphinx_reredirects==0.1.5 -sphinxcontrib-applehelp==2.0.0 -sphinxcontrib-details-directive==0.1.0 -sphinxcontrib-devhelp==2.0.0 -sphinxcontrib-htmlhelp==2.1.0 -sphinxcontrib-jquery==4.1 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==2.0.0 -sphinxcontrib-serializinghtml==2.0.0 -sphinxext-opengraph==0.9.1 -starlette==0.41.2 + # via pydocstyle tabulate==0.9.0 + # via snapcraft (setup.py) testscenarios==0.5.0 + # via snapcraft (setup.py) testtools==2.7.2 + # via testscenarios tinydb==4.8.2 + # via snapcraft (setup.py) toml==0.10.2 + # via snapcraft (setup.py) tomlkit==0.13.2 + # via pylint tox==4.23.2 + # via snapcraft (setup.py) translationstring==1.4 -types-PyYAML==6.0.12.20240917 + # via pyramid +types-pyyaml==6.0.12.20240917 + # via snapcraft (setup.py) types-requests==2.31.0.6 + # via snapcraft (setup.py) types-setuptools==75.3.0.20241112 + # via snapcraft (setup.py) types-simplejson==3.19.0.20240801 + # via snapcraft (setup.py) types-tabulate==0.9.0.20240106 + # via snapcraft (setup.py) types-toml==0.10.8.20240310 + # via snapcraft (setup.py) types-urllib3==1.26.25.14 -typing_extensions==4.12.2 -uc-micro-py==1.0.3 + # via types-requests +typing-extensions==4.12.2 + # via + # craft-application + # craft-platforms + # mypy + # pydantic + # pydantic-core + # snapcraft (setup.py) urllib3==2.2.3 -uvicorn==0.32.0 + # via + # requests + # requests-unixsocket2 validators==0.34.0 + # via snapcraft (setup.py) venusian==3.1.0 + # via pyramid virtualenv==20.27.1 + # via tox wadllib==2.0.0 -watchfiles==0.24.0 -wcmatch==10.0 -webencodings==0.5.1 -WebOb==1.8.9 -websockets==14.1 -wheel==0.45.0 + # via lazr-restfulclient +webob==1.8.9 + # via pyramid ws4py==0.5.1 + # via pylxd yamllint==1.35.1 -zope.deprecation==5.0 -zope.interface==7.1.1 + # via snapcraft (setup.py) +zope-deprecation==5.0 + # via pyramid +zope-interface==7.1.1 + # via pyramid + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" pyinstaller==5.13.1; sys.platform == "win32" diff --git a/requirements-docs.txt b/requirements-docs.txt index 375669ddbc..92a234cf50 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,149 +1,489 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --extra=docs --output-file=requirements-docs.txt +# alabaster==0.7.16 + # via sphinx annotated-types==0.7.0 + # via + # craft-platforms + # pydantic anyio==4.6.2.post1 + # via + # starlette + # watchfiles apeye==1.4.1 + # via sphinx-toolbox apeye-core==1.1.5 + # via apeye attrs==24.2.0 + # via snapcraft (setup.py) autodocsumm==0.2.14 + # via sphinx-toolbox babel==2.16.0 + # via sphinx beautifulsoup4==4.12.3 -boolean.py==4.0 + # via + # canonical-sphinx-extensions + # furo + # pyspelling + # sphinx-toolbox +boolean-py==4.0 + # via license-expression bracex==2.5.post1 -CacheControl==0.14.1 -canonical-sphinx==0.2.0 + # via wcmatch +cachecontrol[filecache]==0.14.1 + # via sphinx-toolbox +canonical-sphinx[full]==0.2.0 + # via snapcraft (setup.py) canonical-sphinx-extensions==0.0.23 -catkin-pkg==1.0.0 + # via canonical-sphinx +catkin-pkg==1.0.0 ; sys_platform == "linux" + # via snapcraft (setup.py) certifi==2024.8.30 + # via requests cffi==1.17.1 + # via + # cryptography + # pygit2 + # pynacl chardet==5.2.0 + # via python-debian charset-normalizer==3.4.0 + # via requests click==8.1.7 + # via + # snapcraft (setup.py) + # uvicorn colorama==0.4.6 + # via sphinx-autobuild craft-application==4.4.0 + # via snapcraft (setup.py) craft-archives==2.0.1 + # via + # craft-application + # snapcraft (setup.py) craft-cli==2.10.1 + # via + # craft-application + # snapcraft (setup.py) craft-grammar==2.0.1 + # via + # craft-application + # snapcraft (setup.py) craft-parts==2.1.2 + # via + # craft-application + # snapcraft (setup.py) craft-platforms==0.4.0 + # via + # craft-application + # snapcraft (setup.py) craft-providers==2.0.4 + # via + # craft-application + # snapcraft (setup.py) craft-store==3.0.2 + # via snapcraft (setup.py) cryptography==43.0.3 + # via + # pylxd + # secretstorage cssutils==2.11.1 + # via dict2css dict2css==0.3.0.post1 + # via sphinx-toolbox distro==1.9.0 + # via + # craft-archives + # craft-platforms + # lazr-restfulclient docutils==0.19 + # via + # canonical-sphinx-extensions + # catkin-pkg + # myst-parser + # snapcraft (setup.py) + # sphinx + # sphinx-prompt + # sphinx-tabs + # sphinx-toolbox domdf-python-tools==3.9.0 + # via + # apeye + # apeye-core + # dict2css + # sphinx-toolbox filelock==3.16.1 + # via + # cachecontrol + # sphinx-toolbox furo==2024.8.6 + # via canonical-sphinx gitdb==4.0.11 -GitPython==3.1.43 + # via gitpython +gitpython==3.1.43 + # via canonical-sphinx-extensions gnupg==2.3.1 + # via snapcraft (setup.py) h11==0.14.0 + # via uvicorn html5lib==1.1 + # via + # pyspelling + # sphinx-toolbox httplib2==0.22.0 + # via + # launchpadlib + # lazr-restfulclient idna==3.10 + # via + # anyio + # apeye-core + # requests imagesize==1.4.1 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.1.0 + # via sphinx +jaraco-classes==3.4.0 + # via + # craft-store + # keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.1.0 + # via keyring jeepney==0.8.0 -Jinja2==3.1.4 + # via + # keyring + # secretstorage +jinja2==3.1.4 + # via + # craft-application + # myst-parser + # sphinx + # sphinx-jinja2-compat jsonschema==2.5.1 + # via snapcraft (setup.py) keyring==25.5.0 + # via craft-store launchpadlib==2.0.0 -lazr.restfulclient==0.14.6 -lazr.uri==1.0.6 + # via + # craft-archives + # snapcraft (setup.py) +lazr-restfulclient==0.14.6 + # via + # craft-archives + # launchpadlib + # snapcraft (setup.py) +lazr-uri==1.0.6 + # via + # craft-archives + # launchpadlib + # wadllib license-expression==30.4.0 + # via craft-application linkify-it-py==2.0.3 + # via canonical-sphinx lxml==5.3.0 + # via + # pyspelling + # snapcraft (setup.py) macaroonbakery==1.3.4 -Markdown==3.7 + # via + # craft-store + # snapcraft (setup.py) +markdown==3.7 + # via pyspelling markdown-it-py==3.0.0 -MarkupSafe==3.0.2 + # via + # mdit-py-plugins + # myst-parser +markupsafe==3.0.2 + # via + # jinja2 + # sphinx-jinja2-compat mdit-py-plugins==0.4.2 + # via myst-parser mdurl==0.1.2 + # via markdown-it-py more-itertools==10.5.0 + # via + # cssutils + # jaraco-classes + # jaraco-functools msgpack==1.1.0 + # via cachecontrol mypy-extensions==1.0.0 + # via snapcraft (setup.py) myst-parser==4.0.0 + # via canonical-sphinx natsort==8.4.0 + # via domdf-python-tools oauthlib==3.2.2 + # via lazr-restfulclient overrides==7.7.0 + # via + # craft-archives + # craft-grammar + # craft-parts + # craft-store + # snapcraft (setup.py) packaging==24.2 + # via + # craft-providers + # snapcraft (setup.py) + # sphinx platformdirs==4.3.6 + # via + # apeye + # craft-application + # craft-cli polib==1.2.0 + # via sphinx-lint progressbar==2.5 + # via snapcraft (setup.py) protobuf==5.28.3 + # via macaroonbakery psutil==6.1.0 + # via gnupg pycparser==2.22 + # via cffi pydantic==2.9.2 -pydantic_core==2.23.4 + # via + # craft-application + # craft-archives + # craft-grammar + # craft-parts + # craft-providers + # craft-store + # snapcraft (setup.py) +pydantic-core==2.23.4 + # via pydantic pyelftools==0.31 + # via snapcraft (setup.py) pygit2==1.13.3 -Pygments==2.18.0 -pylxd==2.3.5 + # via + # craft-application + # snapcraft (setup.py) +pygments==2.18.0 + # via + # furo + # sphinx + # sphinx-prompt + # sphinx-tabs +pylxd==2.3.5 ; sys_platform == "linux" + # via snapcraft (setup.py) pymacaroons==0.13.0 -PyNaCl==1.5.0 + # via + # macaroonbakery + # snapcraft (setup.py) +pynacl==1.5.0 + # via + # macaroonbakery + # pymacaroons pyparsing==3.2.0 -pyRFC3339==1.1 + # via + # catkin-pkg + # httplib2 +pyrfc3339==1.1 + # via macaroonbakery pyspelling==2.10 + # via + # canonical-sphinx + # snapcraft (setup.py) + # via snapcraft (setup.py) python-dateutil==2.9.0.post0 -python-debian==0.1.49 + # via + # catkin-pkg + # pylxd +python-debian==0.1.49 ; sys_platform == "linux" + # via + # craft-archives + # snapcraft (setup.py) pytz==2024.2 + # via pyrfc3339 pyxdg==0.28 -PyYAML==6.0.2 + # via + # craft-parts + # craft-store + # snapcraft (setup.py) +pyyaml==6.0.2 + # via + # craft-application + # craft-cli + # craft-parts + # craft-providers + # myst-parser + # pyspelling + # snap-helpers + # snapcraft (setup.py) raven==6.10.0 + # via snapcraft (setup.py) regex==2024.11.6 + # via sphinx-lint requests==2.32.3 + # via + # apeye + # cachecontrol + # canonical-sphinx-extensions + # craft-application + # craft-parts + # craft-providers + # craft-store + # macaroonbakery + # pylxd + # requests-toolbelt + # requests-unixsocket2 + # snapcraft (setup.py) + # sphinx requests-toolbelt==1.0.0 + # via + # craft-store + # pylxd + # snapcraft (setup.py) requests-unixsocket2==0.4.2 -ruamel.yaml==0.18.6 -ruamel.yaml.clib==0.2.12 -SecretStorage==3.3.3 -setuptools==75.5.0 + # via + # craft-parts + # craft-providers + # snapcraft (setup.py) +ruamel-yaml==0.18.6 + # via sphinx-toolbox +ruamel-yaml-clib==0.2.12 + # via ruamel-yaml +secretstorage==3.3.3 + # via keyring simplejson==3.19.3 + # via snapcraft (setup.py) six==1.16.0 + # via + # html5lib + # lazr-restfulclient + # macaroonbakery + # pymacaroons + # python-dateutil smmap==5.0.1 + # via gitdb snap-helpers==0.4.2 + # via + # craft-application + # snapcraft (setup.py) sniffio==1.3.1 + # via anyio snowballstemmer==2.2.0 + # via sphinx soupsieve==2.6 -Sphinx==7.3.7 + # via + # beautifulsoup4 + # pyspelling +sphinx==7.3.7 + # via + # autodocsumm + # canonical-sphinx + # canonical-sphinx-extensions + # furo + # myst-parser + # sphinx-autobuild + # sphinx-autodoc-typehints + # sphinx-basic-ng + # sphinx-copybutton + # sphinx-design + # sphinx-notfound-page + # sphinx-prompt + # sphinx-reredirects + # sphinx-tabs + # sphinx-toolbox + # sphinxcontrib-details-directive + # sphinxcontrib-jquery + # sphinxext-opengraph sphinx-autobuild==2024.10.3 + # via snapcraft (setup.py) sphinx-autodoc-typehints==2.3.0 + # via + # snapcraft (setup.py) + # sphinx-toolbox sphinx-basic-ng==1.0.0b2 + # via furo sphinx-copybutton==0.5.2 + # via canonical-sphinx +sphinx-design==0.6.1 + # via canonical-sphinx sphinx-jinja2-compat==0.3.0 + # via sphinx-toolbox sphinx-lint==1.0.0 + # via snapcraft (setup.py) sphinx-notfound-page==1.0.4 + # via canonical-sphinx sphinx-prompt==1.8.0 + # via sphinx-toolbox +sphinx-reredirects==0.1.5 + # via canonical-sphinx sphinx-tabs==3.4.5 + # via + # canonical-sphinx + # sphinx-toolbox sphinx-toolbox==3.8.1 -sphinx_design==0.6.1 -sphinx_reredirects==0.1.5 + # via snapcraft (setup.py) sphinxcontrib-applehelp==2.0.0 + # via sphinx sphinxcontrib-details-directive==0.1.0 + # via snapcraft (setup.py) sphinxcontrib-devhelp==2.0.0 + # via sphinx sphinxcontrib-htmlhelp==2.1.0 + # via sphinx sphinxcontrib-jquery==4.1 + # via canonical-sphinx sphinxcontrib-jsmath==1.0.1 + # via sphinx sphinxcontrib-qthelp==2.0.0 + # via sphinx sphinxcontrib-serializinghtml==2.0.0 + # via sphinx sphinxext-opengraph==0.9.1 + # via canonical-sphinx starlette==0.41.2 + # via sphinx-autobuild tabulate==0.9.0 + # via + # snapcraft (setup.py) + # sphinx-toolbox tinydb==4.8.2 + # via snapcraft (setup.py) toml==0.10.2 -typing_extensions==4.12.2 + # via snapcraft (setup.py) +typing-extensions==4.12.2 + # via + # craft-application + # craft-platforms + # domdf-python-tools + # pydantic + # pydantic-core + # snapcraft (setup.py) + # sphinx-toolbox uc-micro-py==1.0.3 + # via linkify-it-py urllib3==2.2.3 + # via + # requests + # requests-unixsocket2 uvicorn==0.32.0 + # via sphinx-autobuild validators==0.34.0 + # via snapcraft (setup.py) wadllib==2.0.0 + # via lazr-restfulclient watchfiles==0.24.0 + # via sphinx-autobuild wcmatch==10.0 + # via pyspelling webencodings==0.5.1 + # via html5lib websockets==14.1 -wheel==0.45.0 + # via sphinx-autobuild ws4py==0.5.1 + # via pylxd + +# The following packages are considered to be unsafe in a requirements file: +# setuptools python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" diff --git a/requirements.txt b/requirements.txt index 1058430dd6..ca515eaa6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,80 +1,272 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --output-file=requirements.txt +# annotated-types==0.7.0 + # via + # craft-platforms + # pydantic attrs==24.2.0 -boolean.py==4.0 -catkin-pkg==1.0.0 + # via snapcraft (setup.py) +boolean-py==4.0 + # via license-expression +catkin-pkg==1.0.0 ; sys_platform == "linux" + # via snapcraft (setup.py) certifi==2024.8.30 + # via requests cffi==1.17.1 + # via + # cryptography + # pygit2 + # pynacl chardet==5.2.0 + # via python-debian charset-normalizer==3.4.0 + # via requests click==8.1.7 + # via snapcraft (setup.py) craft-application==4.4.0 + # via snapcraft (setup.py) craft-archives==2.0.1 + # via + # craft-application + # snapcraft (setup.py) craft-cli==2.10.1 + # via + # craft-application + # snapcraft (setup.py) craft-grammar==2.0.1 + # via + # craft-application + # snapcraft (setup.py) craft-parts==2.1.2 + # via + # craft-application + # snapcraft (setup.py) craft-platforms==0.4.0 + # via + # craft-application + # snapcraft (setup.py) craft-providers==2.0.4 + # via + # craft-application + # snapcraft (setup.py) craft-store==3.0.2 + # via snapcraft (setup.py) cryptography==43.0.3 + # via + # pylxd + # secretstorage distro==1.9.0 + # via + # craft-archives + # craft-platforms + # lazr-restfulclient docutils==0.19 + # via + # catkin-pkg + # snapcraft (setup.py) gnupg==2.3.1 + # via snapcraft (setup.py) httplib2==0.22.0 + # via + # launchpadlib + # lazr-restfulclient idna==3.10 -jaraco.classes==3.4.0 -jaraco.context==6.0.1 -jaraco.functools==4.1.0 + # via requests +jaraco-classes==3.4.0 + # via + # craft-store + # keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.1.0 + # via keyring jeepney==0.8.0 -Jinja2==3.1.4 + # via + # keyring + # secretstorage +jinja2==3.1.4 + # via craft-application jsonschema==2.5.1 + # via snapcraft (setup.py) keyring==25.5.0 + # via craft-store launchpadlib==2.0.0 -lazr.restfulclient==0.14.6 -lazr.uri==1.0.6 + # via + # craft-archives + # snapcraft (setup.py) +lazr-restfulclient==0.14.6 + # via + # craft-archives + # launchpadlib + # snapcraft (setup.py) +lazr-uri==1.0.6 + # via + # craft-archives + # launchpadlib + # wadllib license-expression==30.4.0 + # via craft-application lxml==5.3.0 + # via snapcraft (setup.py) macaroonbakery==1.3.4 -MarkupSafe==3.0.2 + # via + # craft-store + # snapcraft (setup.py) +markupsafe==3.0.2 + # via jinja2 more-itertools==10.5.0 + # via + # jaraco-classes + # jaraco-functools mypy-extensions==1.0.0 + # via snapcraft (setup.py) oauthlib==3.2.2 + # via lazr-restfulclient overrides==7.7.0 + # via + # craft-archives + # craft-grammar + # craft-parts + # craft-store + # snapcraft (setup.py) packaging==24.2 + # via + # craft-providers + # snapcraft (setup.py) platformdirs==4.3.6 + # via + # craft-application + # craft-cli progressbar==2.5 + # via snapcraft (setup.py) protobuf==5.28.3 + # via macaroonbakery psutil==6.1.0 + # via gnupg pycparser==2.22 + # via cffi pydantic==2.9.2 -pydantic_core==2.23.4 + # via + # craft-application + # craft-archives + # craft-grammar + # craft-parts + # craft-providers + # craft-store + # snapcraft (setup.py) +pydantic-core==2.23.4 + # via pydantic pyelftools==0.31 + # via snapcraft (setup.py) pygit2==1.13.3 -pylxd==2.3.5 + # via + # craft-application + # snapcraft (setup.py) +pylxd==2.3.5 ; sys_platform == "linux" + # via snapcraft (setup.py) pymacaroons==0.13.0 -PyNaCl==1.5.0 + # via + # macaroonbakery + # snapcraft (setup.py) +pynacl==1.5.0 + # via + # macaroonbakery + # pymacaroons pyparsing==3.2.0 -pyRFC3339==1.1 + # via + # catkin-pkg + # httplib2 +pyrfc3339==1.1 + # via macaroonbakery + # via snapcraft (setup.py) python-dateutil==2.9.0.post0 -python-debian==0.1.49 + # via + # catkin-pkg + # pylxd +python-debian==0.1.49 ; sys_platform == "linux" + # via + # craft-archives + # snapcraft (setup.py) pytz==2024.2 + # via pyrfc3339 pyxdg==0.28 -PyYAML==6.0.2 + # via + # craft-parts + # craft-store + # snapcraft (setup.py) +pyyaml==6.0.2 + # via + # craft-application + # craft-cli + # craft-parts + # craft-providers + # snap-helpers + # snapcraft (setup.py) raven==6.10.0 + # via snapcraft (setup.py) requests==2.32.3 + # via + # craft-application + # craft-parts + # craft-providers + # craft-store + # macaroonbakery + # pylxd + # requests-toolbelt + # requests-unixsocket2 + # snapcraft (setup.py) requests-toolbelt==1.0.0 + # via + # craft-store + # pylxd + # snapcraft (setup.py) requests-unixsocket2==0.4.2 -SecretStorage==3.3.3 -setuptools==75.5.0 + # via + # craft-parts + # craft-providers + # snapcraft (setup.py) +secretstorage==3.3.3 + # via keyring simplejson==3.19.3 + # via snapcraft (setup.py) six==1.16.0 + # via + # lazr-restfulclient + # macaroonbakery + # pymacaroons + # python-dateutil snap-helpers==0.4.2 + # via + # craft-application + # snapcraft (setup.py) tabulate==0.9.0 + # via snapcraft (setup.py) tinydb==4.8.2 + # via snapcraft (setup.py) toml==0.10.2 -typing_extensions==4.12.2 + # via snapcraft (setup.py) +typing-extensions==4.12.2 + # via + # craft-application + # craft-platforms + # pydantic + # pydantic-core + # snapcraft (setup.py) urllib3==2.2.3 + # via + # requests + # requests-unixsocket2 validators==0.34.0 + # via snapcraft (setup.py) wadllib==2.0.0 -wheel==0.45.0 + # via lazr-restfulclient ws4py==0.5.1 + # via pylxd + +# The following packages are considered to be unsafe in a requirements file: +# setuptools python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys.platform == "linux" From bd5e8c092fc4d359baa9b1ac988640e6505cc994 Mon Sep 17 00:00:00 2001 From: Callahan Kovacs Date: Fri, 15 Nov 2024 09:13:16 -0600 Subject: [PATCH 151/151] build: drop Makefile for docs Building docs is driven by the top-level requirements-docs.txt and tox file. Signed-off-by: Callahan Kovacs --- docs/Makefile | 66 --------------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 docs/Makefile diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index d208461f1e..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1,66 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -q -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build -VENV = sphinx-resources/.sphinx/venv/bin/activate - - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -install: - @echo "... setting up virtualenv" - python3 -m venv sphinx-resources/.sphinx/venv - . $(VENV); pip install --upgrade -r requirements.txt - - @echo "\n" \ - "--------------------------------------------------------------- \n" \ - "* watch, build and serve the documentation: make run \n" \ - "* only build: make html \n" \ - "* only serve: make serve \n" \ - "* clean built doc files: make clean-doc \n" \ - "* clean full environment: make clean \n" \ - "* check spelling: make spelling \n" \ - "* check inclusive language: make woke \n" \ - "--------------------------------------------------------------- \n" -run: - . $(VENV); sphinx-autobuild -c . -b html "$(SOURCEDIR)" "$(BUILDDIR)" - -html: - . $(VENV); $(SPHINXBUILD) -c . -b html "$(SOURCEDIR)" "$(BUILDDIR)" -w sphinx-resources/.sphinx/warnings.txt - -epub: - . $(VENV); $(SPHINXBUILD) -c . -b epub "$(SOURCEDIR)" "$(BUILDDIR)" -w sphinx-resources/.sphinx/warnings.txt - -serve: - cd "$(BUILDDIR)"; python3 -m http.server 8000 - -clean: clean-doc - rm -rf reference/commands - rm -rf sphinx-resources/.sphinx/venv - -clean-doc: - git clean -fx "$(BUILDDIR)" - -spelling: html - . $(VENV) ; python3 -m pyspelling -c sphinx-resources/.sphinx/spellingcheck.yaml - -linkcheck: - . $(VENV) ; $(SPHINXBUILD) -c . -b linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" - -woke: - type woke >/dev/null 2>&1 || { snap install woke; exit 1; } - woke *.rst **/*.rst -c https://github.com/canonical-web-and-design/Inclusive-naming/raw/main/config.yml - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - . $(VENV); $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)