Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow packing a directory #4794

Merged
merged 1 commit into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion snapcraft/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def create_app() -> Snapcraft:
craft_app_commands.lifecycle.BuildCommand,
craft_app_commands.lifecycle.StageCommand,
craft_app_commands.lifecycle.PrimeCommand,
craft_app_commands.lifecycle.PackCommand,
commands.PackCommand,
commands.SnapCommand, # Hidden (legacy compatibility)
commands.RemoteBuildCommand,
unimplemented.Plugins,
Expand Down
3 changes: 2 additions & 1 deletion snapcraft/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
StoreLegacyUploadMetadataCommand,
StoreLegacyValidateCommand,
)
from .lifecycle import SnapCommand
from .lifecycle import PackCommand, SnapCommand
from .manage import StoreCloseCommand, StoreReleaseCommand
from .names import (
StoreLegacyListCommand,
Expand All @@ -60,6 +60,7 @@
"ExpandExtensions",
"ListExtensions",
"RemoteBuildCommand",
"PackCommand",
"SnapCommand",
"StoreCloseCommand",
"StoreEditValidationSetsCommand",
Expand Down
95 changes: 93 additions & 2 deletions snapcraft/commands/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,107 @@
"""Snapcraft lifecycle commands."""

import argparse
import pathlib
import textwrap
from typing import Any

import craft_application.commands
from craft_cli import emit
from typing_extensions import override

# pylint: disable=too-many-ancestors
import snapcraft.pack


class SnapCommand(craft_application.commands.lifecycle.PackCommand):
class PackCommand(craft_application.commands.LifecycleCommand):
"""Snapcraft pack command."""

name = "pack"
help_msg = "Create the final artifact"
overview = textwrap.dedent(
"""
Process parts and create a snap file containing the project payload
with the provided metadata. If a directory is specified, pack its
contents instead.
"""
)

@override
def _fill_parser(self, parser: argparse.ArgumentParser) -> None:
"""Add arguments specific to the pack command."""
super()._fill_parser(parser)

parser.add_argument(
"directory",
metavar="directory",
type=str,
nargs="?",
default=None,
help="Directory to pack",
)
parser.add_argument(
"--output",
"-o",
type=pathlib.Path,
default=pathlib.Path(),
help="Output directory for created packages",
)

@override
def _run(
self,
parsed_args: argparse.Namespace,
step_name: str | None = None,
**kwargs: Any,
) -> None:
"""Pack a directory or run the lifecycle and pack all artifacts."""
if parsed_args.directory:
emit.progress("Packing...")
snap_filename = snapcraft.pack.pack_snap(
parsed_args.directory, output=str(parsed_args.output)
)
emit.message(f"Packed {snap_filename}")
else:
super()._run(parsed_args, step_name="prime")
self._services.package.update_project()
mr-cal marked this conversation as resolved.
Show resolved Hide resolved
self._services.package.write_metadata(self._services.lifecycle.prime_dir)

emit.progress("Packing...")
packages = self._services.package.pack(
self._services.lifecycle.prime_dir, parsed_args.output
)

if not packages:
emit.progress("No packages created.", permanent=True)
elif len(packages) == 1:
emit.progress(f"Packed {packages[0].name}", permanent=True)
else:
package_names = ", ".join(pkg.name for pkg in packages)
emit.progress(f"Packed: {package_names}", permanent=True)

@override
def needs_project(self, parsed_args: argparse.Namespace) -> bool:
"""Project is not required to pack a directory."""
if parsed_args.directory:
emit.debug("Not loading project because a directory was provided.")
return False

emit.debug("Loading project because a directory was not provided.")
return True

@override
def run_managed(self, parsed_args: argparse.Namespace) -> bool:
"""Return whether the command should run in managed mode or not.

Packing a directory always runs locally.
"""
if parsed_args.directory:
emit.debug("Not running managed mode because a directory was provided.")
return False

return super().run_managed(parsed_args)
mr-cal marked this conversation as resolved.
Show resolved Hide resolved


class SnapCommand(PackCommand):
"""Deprecated legacy command to pack the final snap payload."""

name = "snap"
Expand Down
8 changes: 8 additions & 0 deletions tests/spread/core22/packing/task.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ environment:
CMD/pack: pack
CMD/pack_output: pack -o output.snap
CMD/pack_output_subdir: pack --output subdir/output.snap
CMD/pack_directory: pack prime
CMD/pack_directory_output: pack prime --output output.snap
CMD/pack_directory_output_subdir: pack prime --output subdir/output.snap
CMD/snap: snap
CMD/snap_output: snap --output output.snap
CMD/snap_output_subdir: snap -o subdir/output.snap
Expand All @@ -25,6 +28,11 @@ restore: |
restore_yaml "snap/snapcraft.yaml"

execute: |
# create a local directory to pack
if [[ "$SPREAD_VARIANT" =~ "pack_directory" ]]; then
snapcraft prime --destructive-mode
fi

# shellcheck disable=SC2086
snapcraft $CMD --destructive-mode

Expand Down
16 changes: 15 additions & 1 deletion tests/spread/core24/packing/task.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ environment:
CMD/pack: pack
CMD/pack_output: pack -o output.snap
CMD/pack_output_subdir: pack --output subdir/output.snap
CMD/pack_directory: pack prime
CMD/pack_directory_output: pack prime --output output.snap
CMD/pack_directory_output_subdir: pack prime --output subdir/output.snap
CMD/snap: snap
CMD/snap_output: snap --output output.snap
CMD/snap_output_subdir: snap -o subdir/output.snap
Expand All @@ -17,14 +20,25 @@ prepare: |
# set_base "$SNAP_DIR/snap/snapcraft.yaml"

restore: |
snapcraft clean
rm -Rf subdir ./*.snap

# 'pack_directory' tests are run in destructive-mode
if [[ "$SPREAD_VARIANT" =~ "pack_directory" ]]; then
snapcraft clean --destructive-mode
else
snapcraft clean
fi

#shellcheck source=tests/spread/tools/snapcraft-yaml.sh
. "$TOOLS_DIR/snapcraft-yaml.sh"
restore_yaml "snap/snapcraft.yaml"

execute: |
# create a local directory to pack
if [[ "$SPREAD_VARIANT" =~ "pack_directory" ]]; then
snapcraft prime --destructive-mode
fi

# shellcheck disable=SC2086
snapcraft $CMD

Expand Down
2 changes: 1 addition & 1 deletion tests/unit/commands/test_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def test_core22_pack_command_with_directory(mocker):
def test_snap_command_fallback(tmp_path, emitter, mocker, fake_services):
"""Test that the snap command is falling back to the pack command."""
parsed_args = argparse.Namespace(parts=[], output=tmp_path)
mock_pack = mocker.patch("craft_application.commands.lifecycle.PackCommand._run")
mock_pack = mocker.patch("snapcraft.commands.lifecycle.PackCommand._run")
cmd = lifecycle.SnapCommand({"app": APP_METADATA, "services": fake_services})
cmd.run(parsed_args=parsed_args)
mock_pack.assert_called_once()
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
import pytest
import yaml
from craft_application import util
from craft_application.commands.lifecycle import PackCommand
from craft_parts.packages import snaps
from craft_providers import bases

from snapcraft import application, services
from snapcraft.commands import PackCommand
from snapcraft.errors import ClassicFallback, SnapcraftError
from snapcraft.models.project import Architecture
from snapcraft.parts.yaml_utils import CURRENT_BASES, ESM_BASES, LEGACY_BASES
Expand Down
Loading