Skip to content

Commit

Permalink
feat: add uv plugin (#792)
Browse files Browse the repository at this point in the history
* feat: add uv plugin

* style: one-off formatting fix

* fix: revert temporary test change

* fix: set default python

* refactor(test): rearrange python plugin tests to more easily support new ones

* fix: rewrite shebangs on uv plugin scripts

* chore: clean up

* chore: update schema with uv plugin

* fix(test): include black dependency test for uv

* test: correct usrmerge fix test to check all python versions

* fix: install python in the spread rock

* fix: add python to stage-packages instead

* style: run prettier formatter

* chore: pr feedback, use python3-minimal

* chore: pr feedback, add link to issue

* docs: add uv and go use plugin docs

* docs: remove trailing whitespace

* fix: revert to full python installation

* chore: pr feedback, test importing black

Co-authored-by: Alex Lowe <alex.lowe@canonical.com>

* style: fix linter warnings

---------

Co-authored-by: Alex Lowe <alex.lowe@canonical.com>
  • Loading branch information
bepri and lengau authored Jan 29, 2025
1 parent 380dd3c commit 449dad9
Show file tree
Hide file tree
Showing 31 changed files with 1,653 additions and 708 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"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/uv_plugin.rst",
# Extra non-craft-parts exclusions can be added after this comment
]

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Rockcraft.
/common/craft-parts/reference/plugins/cmake_plugin
/common/craft-parts/reference/plugins/dump_plugin
/common/craft-parts/reference/plugins/go_plugin
/common/craft-parts/reference/plugins/go_use_plugin
/common/craft-parts/reference/plugins/make_plugin
plugins/maven_plugin
/common/craft-parts/reference/plugins/meson_plugin
Expand All @@ -26,3 +27,4 @@ Rockcraft.
/common/craft-parts/reference/plugins/qmake_plugin
/common/craft-parts/reference/plugins/rust_plugin
/common/craft-parts/reference/plugins/scons_plugin
plugins/uv_plugin
7 changes: 7 additions & 0 deletions docs/reference/plugins/uv_plugin.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.. include:: /common/craft-parts/reference/plugins/uv_plugin.rst
:end-before: .. _uv-details-begin:

.. include:: _python_common.rst

.. include:: /common/craft-parts/reference/plugins/uv_plugin.rst
:start-after: .. _uv-details-end:
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ dependencies = [
"craft-application~=4.6.0",
"craft-archives>=2.0.0",
"craft-cli>=2.15.0",
"craft-parts~=2.1.4",
"craft-parts~=2.3.0",
"craft-platforms~=0.3",
"craft-providers>=2.0.4",
"overrides",
Expand Down
3 changes: 1 addition & 2 deletions rockcraft/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

"""Rockcraft-specific plugins."""

from .python_plugin import PythonPlugin
from .register import get_plugins, register

__all__ = ["PythonPlugin", "get_plugins", "register"]
__all__ = ["get_plugins", "register"]
13 changes: 13 additions & 0 deletions rockcraft/plugins/python_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@

import craft_parts

from .poetry_plugin import PoetryPlugin
from .python_plugin import PythonPlugin
from .uv_plugin import UvPlugin

# Template for the sitecustomize module that we'll add to the payload so that
# the pip-installed packages are found regardless of how the interpreter is
# called.
Expand Down Expand Up @@ -128,3 +132,12 @@ def wrap_build_commands(parts_commands: list[str]) -> list[str]:
)

return commands


def get_python_plugins() -> dict[str, craft_parts.plugins.plugins.PluginType]:
"""Get a dict of all supported Python-based plugins."""
return {
"poetry": PoetryPlugin,
"python": PythonPlugin,
"uv": UvPlugin,
}
7 changes: 2 additions & 5 deletions rockcraft/plugins/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@

from .ant_plugin import AntPlugin
from .maven_plugin import MavenPlugin
from .poetry_plugin import PoetryPlugin
from .python_plugin import PythonPlugin
from .python_common import get_python_plugins


def register() -> None:
Expand All @@ -34,7 +33,5 @@ def get_plugins() -> dict[str, PluginType]:
"""Get a dict of Rockcraft-specific plugins."""
return {
"ant": AntPlugin,
"poetry": PoetryPlugin,
"python": PythonPlugin,
"maven": MavenPlugin,
}
} | get_python_plugins()
70 changes: 70 additions & 0 deletions rockcraft/plugins/uv_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2025 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 <http://www.gnu.org/licenses/>.

"""The Rockcraft uv plugin."""

from textwrap import dedent

from craft_parts.plugins import uv_plugin
from overrides import override # type: ignore[reportUnknownVariableType]

from rockcraft.plugins import python_common


class UvPlugin(uv_plugin.UvPlugin):
"""A uv plugin for Rockcraft."""

@override
def _should_remove_symlinks(self) -> bool:
"""Overridden because for ubuntu bases we must always remove the symlinks."""
return python_common.should_remove_symlinks(self._part_info)

@override
def _get_system_python_interpreter(self) -> str | None:
"""Overridden because Python must always be provided by the parts.
The uv plugin requires a name to reference Python, so we must depend
on a relative python3 being installed. Should return None once
https://github.com/canonical/craft-parts/issues/991 is closed."""
return "python3"

@override
def _get_script_interpreter(self) -> str:
"""Overridden because Python is always available in /bin."""
return python_common.get_script_interpreter()

@override
def _get_rewrite_shebangs_commands(self) -> list[str]:
"""Overridden because the original uv plugin does not rewrite shebangs."""
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|^#\\!.*$|{script_interpreter}|"'
return [
dedent(
f"""\
{find_cmd} | {xargs_cmd} \\
{sed_cmd}
"""
)
]

@override
def get_build_commands(self) -> list[str]:
"""Overridden to add a sitecustomize.py."""
return python_common.wrap_build_commands(super().get_build_commands())
3 changes: 2 additions & 1 deletion rockcraft/services/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from rockcraft import layers
from rockcraft.models.project import Project
from rockcraft.plugins.python_common import get_python_plugins


class RockcraftLifecycleService(LifecycleService):
Expand Down Expand Up @@ -80,7 +81,7 @@ def _python_usrmerge_fix(step_info: StepInfo) -> None:
# Can't inspect the files without a StepState.
return

if state.part_properties["plugin"] not in ("python", "poetry"):
if state.part_properties["plugin"] not in get_python_plugins().keys():
# Be conservative and don't try to fix the files if they didn't come
# from a Python plugin.
return
Expand Down
Loading

0 comments on commit 449dad9

Please sign in to comment.