Skip to content

Commit

Permalink
Merge branch 'main' into ROCKS-970/util-function-for-determining-if-p…
Browse files Browse the repository at this point in the history
…arts-have-slices
  • Loading branch information
cmatsuoka authored Oct 8, 2024
2 parents 8eb8c11 + c736ead commit 3085c51
Show file tree
Hide file tree
Showing 25 changed files with 354 additions and 91 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.0.0
current_version = 2.1.2
commit = True
tag = True

Expand Down
2 changes: 0 additions & 2 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// Configuration file for RenovateBot: https://docs.renovatebot.com/configuration-options
extends: ["config:recommended", ":semanticCommitTypeAll(build)"],
ignoreDeps: [
"types-requests", // Don't update until we can support urllib3 >= 2.0
"urllib3", // Can't update, see setup.py
],
labels: ["dependencies"], // For convenient searching in GitHub
baseBranches: ["$default", "/^hotfix\\/.*/"],
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/security-scan.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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
2 changes: 1 addition & 1 deletion craft_parts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

"""Craft a project from several parts."""

__version__ = "2.0.0"
__version__ = "2.1.2"

from . import plugins
from .actions import Action, ActionProperties, ActionType
Expand Down
24 changes: 19 additions & 5 deletions craft_parts/dirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pathlib import Path
from types import MappingProxyType

from craft_parts.errors import PartitionNotFound, PartitionUsageError
from craft_parts.utils import partition_utils


Expand Down Expand Up @@ -74,14 +75,27 @@ def __init__(
)
)

def _validate_requested_partition(
self, dir_name: str, partition: str | None = None
) -> None:
"""Ensure the requested partition is valid."""
if self._partitions:
if not partition:
raise PartitionUsageError(
error_list=[
f"Partitions are enabled, you must specify which partition's {dir_name!r} you want."
],
partitions=self._partitions,
)
if partition not in self._partitions:
raise PartitionNotFound(partition, self._partitions)

def get_stage_dir(self, partition: str | None = None) -> Path:
"""Get the stage directory for the given partition."""
if self._partitions and partition not in self._partitions:
raise ValueError(f"Unknown partition {partition}")
self._validate_requested_partition("stage_dir", partition)
return self.stage_dirs[partition]

def get_prime_dir(self, partition: str | None = None) -> Path:
"""Get the stage directory for the given partition."""
if self._partitions and partition not in self._partitions:
raise ValueError(f"Unknown partition {partition}")
"""Get the prime directory for the given partition."""
self._validate_requested_partition("prime_dir", partition)
return self.prime_dirs[partition]
26 changes: 24 additions & 2 deletions craft_parts/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,17 +652,22 @@ class PartitionUsageError(PartitionError):
"""Error for a list of invalid partition usages.
:param error_list: Iterable of strings describing the invalid usages.
:param partitions: Iterable of the names of valid partitions.
:param brief: Override brief message.
"""

def __init__(
self, error_list: Iterable[str], partitions: Iterable[str] | None
self,
error_list: Iterable[str],
partitions: Iterable[str] | None,
brief: str | None = None,
) -> None:
valid_partitions = (
f"\nValid partitions: {', '.join(partitions)}" if partitions else ""
)

super().__init__(
brief="Invalid usage of partitions",
brief=brief or "Invalid usage of partitions",
details="\n".join(error_list) + valid_partitions,
resolution="Correct the invalid partition name(s) and try again.",
)
Expand All @@ -688,3 +693,20 @@ def __init__(self, warning_list: Iterable[str]) -> None:
),
)
Warning.__init__(self)


class PartitionNotFound(PartitionUsageError):
"""A partition has been specified that does not exist.
:param partition_name: The name of the partition that does not exist.
:param partitions: Iterable of the names of valid partitions.
"""

def __init__(self, partition_name: str, partitions: Iterable[str]) -> None:
# Allow callers catching this exception easy access to the partition name
self.partition_name = partition_name
super().__init__(
brief=f"Requested partition does not exist: {partition_name!r}",
partitions=partitions,
error_list=[],
)
18 changes: 12 additions & 6 deletions craft_parts/executor/part_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,9 @@ def _run_prime(
and self._track_stage_packages
and is_deb_based()
):
prime_dirs = list(self._part.prime_dirs.values())
primed_stage_packages = _get_primed_stage_packages(
contents.files, prime_dir=self._part.prime_dir
contents.files, prime_dirs=prime_dirs
)
else:
primed_stage_packages = set()
Expand Down Expand Up @@ -1132,11 +1133,16 @@ def _parts_with_overlay_in_step(step: Step, *, part_list: list[Part]) -> list[Pa
return [p for p in oparts if states.get_step_state_path(p, step).exists()]


def _get_primed_stage_packages(snap_files: set[str], *, prime_dir: Path) -> set[str]:
def _get_primed_stage_packages(
snap_files: set[str], *, prime_dirs: list[Path]
) -> set[str]:
primed_stage_packages: set[str] = set()
for _snap_file in snap_files:
snap_file = str(prime_dir / _snap_file)
stage_package = read_origin_stage_package(snap_file)
if stage_package:
primed_stage_packages.add(stage_package)
for prime_dir in prime_dirs:
snap_file = prime_dir / _snap_file
if not snap_file.exists():
continue
stage_package = read_origin_stage_package(str(snap_file))
if stage_package:
primed_stage_packages.add(stage_package)
return primed_stage_packages
5 changes: 3 additions & 2 deletions craft_parts/packages/snaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import contextlib
import logging
import os
import pathlib
import subprocess
import sys
from collections.abc import Iterator, Sequence
Expand Down Expand Up @@ -200,7 +201,7 @@ def is_valid(self) -> bool:
store_channels = self._get_store_channels()
return self.channel in store_channels

def download(self, *, directory: str | None = None) -> None:
def download(self, *, directory: str | pathlib.Path | None = None) -> None:
"""Download a given snap."""
# We use the `snap download` command here on recommendation
# of the snapd team.
Expand Down Expand Up @@ -270,7 +271,7 @@ def refresh(self) -> None:
self._is_installed = None


def download_snaps(*, snaps_list: Sequence[str], directory: str) -> None:
def download_snaps(*, snaps_list: Sequence[str], directory: str | pathlib.Path) -> None:
"""Download snaps of the format <snap-name>/<channel> into directory.
The target directory is created if it does not exist.
Expand Down
43 changes: 27 additions & 16 deletions craft_parts/plugins/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,33 +99,39 @@ class JavaPlugin(Plugin):
symlink creation.
"""

def _get_java_post_build_commands(self) -> list[str]:
"""Get the bash commands to structure a Java build in the part's install dir.
:return: The returned list contains the bash commands to do the following:
- Create bin/ and jar/ directories in ${CRAFT_PART_INSTALL};
- Find the ``java`` executable (provided by whatever jre the part used) and
link it as ${CRAFT_PART_INSTALL}/bin/java;
- Hardlink the .jar files generated in ${CRAFT_PART_BUILD} to
${CRAFT_PART_INSTALL}/jar.
"""
def _get_java_link_commands(self) -> list[str]:
"""Get the bash commands to provide /bin/java symlink."""
# pylint: disable=line-too-long
link_java = [
return [
'# Find the "java" executable and make a link to it in CRAFT_PART_INSTALL/bin/java',
"mkdir -p ${CRAFT_PART_INSTALL}/bin",
"java_bin=$(find ${CRAFT_PART_INSTALL} -name java -type f -executable)",
"ln -s --relative $java_bin ${CRAFT_PART_INSTALL}/bin/java",
]
# pylint: enable=line-too-long

link_jars = [
def _get_jar_link_commands(self) -> list[str]:
"""Get the bash commands to provide ${CRAFT_STAGE}/jars."""
# pylint: disable=line-too-long
return [
"# Find all the generated jars and hardlink them inside CRAFT_PART_INSTALL/jar/",
"mkdir -p ${CRAFT_PART_INSTALL}/jar",
r'find ${CRAFT_PART_BUILD}/ -iname "*.jar" -exec ln {} ${CRAFT_PART_INSTALL}/jar \;',
]
# pylint: enable=line-too-long

return link_java + link_jars
def _get_java_post_build_commands(self) -> list[str]:
"""Get the bash commands to structure a Java build in the part's install dir.
:return: The returned list contains the bash commands to do the following:
- Create bin/ and jar/ directories in ${CRAFT_PART_INSTALL};
- Find the ``java`` executable (provided by whatever jre the part used) and
link it as ${CRAFT_PART_INSTALL}/bin/java;
- Hardlink the .jar files generated in ${CRAFT_PART_BUILD} to
${CRAFT_PART_INSTALL}/jar.
"""
return self._get_java_link_commands() + self._get_jar_link_commands()


class BasePythonPlugin(Plugin):
Expand Down Expand Up @@ -236,11 +242,16 @@ def _get_rewrite_shebangs_commands(self) -> list[str]:
shebangs in the final environment should be handled.
"""
script_interpreter = self._get_script_interpreter()
find_cmd = (
f'find "{self._part_info.part_install_dir}" -type f -executable -print0'
)
xargs_cmd = "xargs --no-run-if-empty -0"
sed_cmd = f'sed -i "1 s|^#\\!${{PARTS_PYTHON_VENV_INTERP_PATH}}.*$|{script_interpreter}|"'
return [
textwrap.dedent(
f"""\
find "{self._part_info.part_install_dir}" -type f -executable -print0 | xargs -0 \\
sed -i "1 s|^#\\!${{PARTS_PYTHON_VENV_INTERP_PATH}}.*$|{script_interpreter}|"
{find_cmd} | {xargs_cmd} \\
{sed_cmd}
"""
)
]
Expand Down
37 changes: 19 additions & 18 deletions craft_parts/plugins/npm_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ def get_build_environment(self) -> dict[str, str]:
@override
def get_pull_commands(self) -> list[str]:
"""Return a list of commands to run during the pull step."""
return []

@override
def get_build_commands(self) -> list[str]:
"""Return a list of commands to run during the build step."""
cmd = []
options = cast(NpmPluginProperties, self._options)
if options.npm_include_node:
arch = self._get_architecture()
Expand All @@ -277,25 +283,30 @@ def get_pull_commands(self) -> list[str]:
self._node_binary_path = os.path.join(
self._part_info.part_cache_dir, file_name
)
return [
cmd += [
dedent(
f"""\
if [ ! -f "{self._node_binary_path}" ]; then
mkdir -p "{self._part_info.part_cache_dir}"
curl --retry 5 -s "{checksum_uri}" -o "{self._part_info.part_cache_dir}"/SHASUMS256.txt
curl --retry 5 -s "{node_uri}" -o "{self._node_binary_path}"
fi
cd "{self._part_info.part_cache_dir}"
pushd "{self._part_info.part_cache_dir}"
sha256sum --ignore-missing --strict -c SHASUMS256.txt
popd
"""
)
]
return []

@override
def get_build_commands(self) -> list[str]:
"""Return a list of commands to run during the build step."""
cmd = [
if self._node_binary_path is not None:
cmd += [
dedent(
f"""\
tar -xzf "{self._node_binary_path}" -C "${{CRAFT_PART_INSTALL}}/" \
--no-same-owner --strip-components=1
"""
),
]
cmd += [
dedent(
"""\
NPM_VERSION="$(npm --version)"
Expand All @@ -308,14 +319,4 @@ def get_build_commands(self) -> list[str]:
"""
)
]
if self._node_binary_path is not None:
cmd.insert(
0,
dedent(
f"""\
tar -xzf "{self._node_binary_path}" -C "${{CRAFT_PART_INSTALL}}/" \
--no-same-owner --strip-components=1
"""
),
)
return cmd
43 changes: 43 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@
Changelog
*********

2.1.2 (2024-10-04)
------------------

- Replace the dependency on requests-unixsocket with requests-unixsocket2

Bug Fixes:

- Fixed an issue where the ``python`` plugin would fail to build if the part
had no Python scripts.

Documentation:

- Update the :doc:`Rust
plugin`</common/craft-parts/reference/plugins/rust_plugin>` doc with recent
changes to the Rust toolchain.

2.1.1 (2024-09-13)
------------------

- This release brings the bug fix from ``1.33.1`` into the ``2.1.x`` series.

1.33.1 (2024-09-13)
-------------------

- Fix NPM plugin to be stateless, allowing lifecycle steps to be
executed in separate runs.

2.1.0 (2024-09-09)
------------------

New features:

- Add a :doc:`Poetry plugin</common/craft-parts/reference/plugins/poetry_plugin>`
for Python projects that use the `Poetry`_ build system.
- Add a new error message when getting a directory for a non-existent partition.

Bug fixes:

- Fix a regression where numeric part properties could not be parsed.
- Fix a bug where stage-packages tracking would fail when files were organized
into a non-default partition.

2.0.0 (2024-08-08)
------------------

Expand Down Expand Up @@ -540,3 +582,4 @@ Bug fixes:


.. _craft-cli issue #172: https://github.com/canonical/craft-cli/issues/172
.. _Poetry: https://python-poetry.org
Loading

0 comments on commit 3085c51

Please sign in to comment.