-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add uv plugin to charmcraft (#2050)
Needs canonical/craft-parts#945 Closes #2040. CRAFT-3816 --------- Co-authored-by: Alex Lowe <[email protected]>
- Loading branch information
Showing
18 changed files
with
475 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Copyright 2024 Canonical Ltd. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# For further info, check https://github.com/canonical/charmcraft | ||
"""Charmcraft-specific uv plugin.""" | ||
|
||
from pathlib import Path | ||
|
||
from craft_parts.plugins import uv_plugin | ||
from overrides import override | ||
|
||
from charmcraft import utils | ||
|
||
|
||
class UvPlugin(uv_plugin.UvPlugin): | ||
@override | ||
def get_build_environment(self) -> dict[str, str]: | ||
return utils.extend_python_build_environment(super().get_build_environment()) | ||
|
||
@override | ||
def _get_venv_directory(self) -> Path: | ||
return self._part_info.part_install_dir / "venv" | ||
|
||
@override | ||
def _get_pip(self) -> str: | ||
return 'uv pip --python="${PARTS_PYTHON_VENV_INTERP_PATH}"' | ||
|
||
@override | ||
def _get_package_install_commands(self) -> list[str]: | ||
# Find the `uv sync` command and modify it to not install the project | ||
orig_cmds = super()._get_package_install_commands() | ||
for idx, cmd in enumerate(orig_cmds): | ||
if cmd.startswith("uv sync"): | ||
orig_cmds[idx] += " --no-install-project" | ||
break | ||
|
||
return [ | ||
*orig_cmds, | ||
*utils.get_charm_copy_commands( | ||
self._part_info.part_build_dir, self._part_info.part_install_dir | ||
), | ||
] | ||
|
||
@override | ||
def _should_remove_symlinks(self) -> bool: | ||
return True | ||
|
||
@override | ||
def get_build_commands(self) -> list[str]: | ||
return [ | ||
*super().get_build_commands(), | ||
*utils.get_venv_cleanup_commands( | ||
self._get_venv_directory(), keep_bins=False | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
name: my-charm | ||
type: charm | ||
title: My uv charm | ||
summary: An operator charm using uv. | ||
description: | | ||
An operator charm that uses uv for its project. | ||
base: [email protected] | ||
platforms: | ||
amd64: | ||
parts: | ||
my-charm: | ||
source: . | ||
plugin: uv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
.. _craft_parts_uv_plugin: | ||
|
||
uv plugin | ||
========= | ||
|
||
The uv plugin is designed for Python charms that use `uv`_ as the build system | ||
and are written with the `Operator framework`_. | ||
|
||
.. include:: /common/craft-parts/reference/plugins/uv_plugin.rst | ||
:start-after: .. _craft_parts_uv_plugin-keywords: | ||
:end-before: .. _craft_parts_uv_plugin-environment_variables: | ||
|
||
python-keep-bins | ||
~~~~~~~~~~~~~~~~ | ||
**Type**: boolean | ||
**Default**: False | ||
|
||
Whether to keep Python scripts in the virtual environment's :file:`bin` | ||
directory. | ||
|
||
.. include:: /common/craft-parts/reference/plugins/uv_plugin.rst | ||
:start-after: .. _craft_parts_poetry_plugin-environment_variables: | ||
:end-before: .. _uv-details-end: | ||
|
||
How it works | ||
------------ | ||
|
||
During the build step, the plugin performs the following actions: | ||
|
||
#. It creates a virtual environment in the | ||
:ref:`${CRAFT_PART_INSTALL}/venv <craft_parts_step_execution_environment>` | ||
directory. | ||
#. It runs :command:`uv sync` to install the packages referenced in the | ||
:file:`pyproject.toml` and :file:`uv.lock` files, along with any optional | ||
groups or extras specified. | ||
#. It copies any existing :file:`src` and :file:`lib` directories from your | ||
charm project into the final charm. | ||
|
||
Example | ||
------- | ||
|
||
The following :file:`charmcraft.yaml` file can be used with a uv project to | ||
craft a charm with Ubuntu 24.04 as its base: | ||
|
||
.. literalinclude:: uv-charmcraft.yaml | ||
:language: yaml | ||
|
||
|
||
.. _uv: https://docs.astral.sh/uv/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Copyright 2024 Canonical Ltd. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
# For further info, check https://github.com/canonical/charmcraft | ||
|
||
import platform | ||
import subprocess | ||
import sys | ||
from pathlib import Path | ||
from typing import Any | ||
|
||
import distro | ||
import pytest | ||
from craft_application import util as app_util | ||
|
||
from charmcraft import services | ||
from charmcraft.models import project | ||
|
||
pytestmark = [ | ||
pytest.mark.skipif(sys.platform != "linux", reason="craft-parts is linux-only") | ||
] | ||
|
||
|
||
@pytest.fixture | ||
def charm_project(basic_charm_dict: dict[str, Any], project_path: Path, request): | ||
return project.PlatformCharm.unmarshal( | ||
basic_charm_dict | ||
| { | ||
"base": f"{distro.id()}@{distro.version()}", | ||
"platforms": {app_util.get_host_architecture(): None}, | ||
"parts": { | ||
"my-charm": { | ||
"plugin": "uv", | ||
"source": str(project_path), | ||
"source-type": "local", | ||
} | ||
}, | ||
} | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def uv_project(project_path: Path, monkeypatch) -> None: | ||
subprocess.run( | ||
[ | ||
"uv", | ||
"init", | ||
"--name=test-charm", | ||
f"--python={platform.python_version()}", | ||
"--no-progress", | ||
"--no-workspace", | ||
], | ||
cwd=project_path, | ||
check=True, | ||
) | ||
subprocess.run(["uv", "add", "ops"], cwd=project_path, check=True) | ||
monkeypatch.delenv("UV_FROZEN", raising=False) | ||
subprocess.run( | ||
[ | ||
"uv", | ||
"lock", | ||
], | ||
cwd=project_path, | ||
check=True, | ||
) | ||
source_dir = project_path / "src" | ||
source_dir.mkdir() | ||
(source_dir / "charm.py").write_text("# Charm file") | ||
|
||
|
||
@pytest.mark.slow | ||
@pytest.mark.usefixtures("uv_project") | ||
def test_uv_plugin( | ||
build_plan, service_factory: services.CharmcraftServiceFactory, tmp_path: Path | ||
): | ||
install_path = tmp_path / "parts" / "my-charm" / "install" | ||
stage_path = tmp_path / "stage" | ||
service_factory.lifecycle._build_plan = build_plan | ||
|
||
service_factory.lifecycle.run("stage") | ||
|
||
# Check that the part install directory looks correct. | ||
assert (install_path / "src" / "charm.py").read_text() == "# Charm file" | ||
assert (install_path / "venv" / "lib").is_dir() | ||
|
||
# Check that the stage directory looks correct. | ||
assert (stage_path / "src" / "charm.py").read_text() == "# Charm file" | ||
assert (stage_path / "venv" / "lib").is_dir() | ||
assert not (stage_path / "venv" / "lib64").is_symlink() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
type: charm | ||
name: test-charm | ||
summary: test-charm | ||
description: test-charm | ||
|
||
base: [email protected] | ||
platforms: | ||
amd64: | ||
arm64: | ||
riscv64: | ||
|
||
parts: | ||
my-part: | ||
plugin: uv | ||
source: . | ||
build-snaps: [astral-uv] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def main(): | ||
print("Hello from uv!") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[project] | ||
name = "testcharm" | ||
version = "0.1.0" | ||
description = "a revolutionary charm" | ||
requires-python = ">=3.10" | ||
dependencies = ["overrides", "ops"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
summary: pack a charm with uv | ||
|
||
restore: | | ||
rm -rf ./*.charm | ||
execute: | | ||
charmcraft pack 2>&1 | ||
CHARM_OUTPUT=$(find . -type f -name "*.charm") | ||
charmcraft analyse $CHARM_OUTPUT |
Oops, something went wrong.