Skip to content

Commit

Permalink
Rust plugin: rewrite the Rust plugin ...
Browse files Browse the repository at this point in the history
... using the new plugin system

Signed-off-by: Zixing Liu <zixing.liu@canonical.com>
  • Loading branch information
liushuyu committed Aug 16, 2023
1 parent 5ec6572 commit b26285f
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 223 deletions.
2 changes: 2 additions & 0 deletions snapcraft/parts/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
from .kernel_plugin import KernelPlugin
from .python_plugin import PythonPlugin
from .register import register
from .rust_plugin import RustPlugin

__all__ = [
"ColconPlugin",
"CondaPlugin",
"FlutterPlugin",
"KernelPlugin",
"PythonPlugin",
"RustPlugin",
"register",
]
2 changes: 2 additions & 0 deletions snapcraft/parts/plugins/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .flutter_plugin import FlutterPlugin
from .kernel_plugin import KernelPlugin
from .python_plugin import PythonPlugin
from .rust_plugin import RustPlugin


def register() -> None:
Expand All @@ -32,3 +33,4 @@ def register() -> None:
craft_parts.plugins.register({"flutter": FlutterPlugin})
craft_parts.plugins.register({"python": PythonPlugin})
craft_parts.plugins.register({"kernel": KernelPlugin})
craft_parts.plugins.register({"rust": RustPlugin})
185 changes: 185 additions & 0 deletions snapcraft/parts/plugins/rust_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright (C) 2023 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 Snapcraft Rust plugin."""

import logging
import subprocess
from typing import Any, Dict, List, Set, cast

from craft_parts import plugins
from overrides import overrides

logger = logging.getLogger(__name__)


class RustPluginProperties(plugins.PluginProperties, plugins.PluginModel):
"""The part properties used by the Rust plugin."""

source: str
rust_channel: str = "stable"
rust_features: List[str] = []
rust_path: List[str] = ["."]
rust_use_global_lto: bool = False
rust_no_default_features: bool = False

@classmethod
@overrides
def unmarshal(cls, data: Dict[str, Any]) -> "RustPluginProperties":
"""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="rust",
required=["source"],
)
return cls(**plugin_data)


class RustPlugin(plugins.Plugin):
"""A Snapcraft plugin for Rust applications.
This Rust plugin is useful for building Rust based parts.
Rust uses cargo to drive the build.
This plugin uses the common plugin keywords as well as those for "sources".
For more information check the 'plugins' topic for the former and the
'sources' topic for the latter.
Additionally, this plugin uses the following plugin-specific keywords:
- rust-channel
(string, default "stable")
Used to select which Rust channel or version to use.
It can be one of "stable", "beta", "nightly" or a version number.
If you don't want this plugin to install Rust toolchain for you,
you can put "none" for this option.
- rust-features
(list of strings)
Features used to build optional dependencies
- rust-path
(list of strings, default [.])
Build specific crates inside the workspace
- rust-no-default-features
(boolean, default False)
Whether to disable the default features in this crate.
Equivalent to setting `--no-default-features` on the commandline.
- rust-use-global-lto
(boolean, default False)
Whether to use global LTO.
This option may significantly impact the build performance but
reducing the final binary size.
This will forcibly enable LTO for all the crates you specified,
regardless of whether you have LTO enabled in the Cargo.toml file
"""

properties_class = RustPluginProperties

@overrides
def get_build_snaps(self) -> Set[str]:
return set()

@overrides
def get_build_packages(self) -> Set[str]:
return {"curl", "gcc", "git", "pkg-config", "findutils"}

def _check_rustup(self) -> bool:
try:
rustup_version = subprocess.check_output(["rustup", "--version"])
return "rustup" in rustup_version.decode("utf-8")
except (subprocess.CalledProcessError, FileNotFoundError):
return False

def _get_setup_rustup(self, channel: str) -> List[str]:
return [
f"""\
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --no-modify-path --profile=minimal --default-toolchain {channel}
"""
]

@overrides
def get_build_environment(self) -> Dict[str, str]:
return {
"PATH": "${HOME}/.cargo/bin:${PATH}",
}

@overrides
def get_build_commands(self) -> List[str]:
options = cast(RustPluginProperties, self._options)

rust_install_cmd: List[str] = []
rust_build_cmd: List[str] = []
config_cmd: List[str] = []

if options.rust_channel == "none":
rust_install_cmd = []
elif not self._check_rustup():
logger.info("Rustup not found, installing it")
rust_install_cmd = self._get_setup_rustup(options.rust_channel)
else:
logger.info("Switch rustup channel to %s", options.rust_channel)
rust_install_cmd = [f"rustup default {options.rust_channel}"]

if options.rust_features:
features_string = " ".join(options.rust_features)
config_cmd.extend(["--features", f"'{features_string}'"])

if options.rust_use_global_lto:
logger.info("Adding overrides for LTO support")
config_cmd.extend(
[
"--config 'profile.release.lto = true'",
"--config 'profile.release.codegen-units = 1'",
]
)

if options.rust_no_default_features:
config_cmd.append("--no-default-features")

for crate in options.rust_path:
logger.info("Generating build commands for %s", crate)
config_cmd_string = " ".join(config_cmd)
# TODO(liushuyu): the current fallback installation method will also install
# compiler plugins into the final Snap, which is likely not what we want.
rust_build_cmd_single = f"""\
if cargo read-manifest --manifest-path "{crate}"/Cargo.toml > /dev/null; then
cargo install -f --locked --path "{crate}" --root "${{SNAPCRAFT_PART_INSTALL}}" {config_cmd_string}
# remove the installation metadata
rm -f "${{SNAPCRAFT_PART_INSTALL}}"/.crates{{.toml,2.json}}
else
# virtual workspace is a bit tricky,
# we need to build the whole workspace and then copy the binaries ourselves
pushd "{crate}"
cargo build --workspace --release {config_cmd_string}
# install the final binaries
# TODO(liushuyu): this will also install proc macros in the workspace,
# which the user may not want to keep in the final installation
find ./target/release -maxdepth 1 -executable -exec install -Dvm755 {{}} "${{SNAPCRAFT_PART_INSTALL}}" ';'
popd
fi"""
rust_build_cmd.append(rust_build_cmd_single)
return rust_install_cmd + rust_build_cmd
4 changes: 0 additions & 4 deletions snapcraft/plugins/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
PluginV2,
PythonPlugin,
QMakePlugin,
RustPlugin,
autotools,
catkin,
catkin_tools,
Expand All @@ -52,7 +51,6 @@
npm,
python,
qmake,
rust,
)

__all__ = [
Expand All @@ -72,7 +70,6 @@
"npm",
"python",
"qmake",
"rust",
"PluginV2",
"AutotoolsPlugin",
"CatkinPlugin",
Expand All @@ -90,5 +87,4 @@
"NpmPlugin",
"PythonPlugin",
"QMakePlugin",
"RustPlugin",
]
1 change: 0 additions & 1 deletion snapcraft_legacy/plugins/_plugin_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"npm": v2.NpmPlugin,
"python": v2.PythonPlugin,
"qmake": v2.QMakePlugin,
"rust": v2.RustPlugin,
}
else:
# We cannot import the plugins on anything but linux.
Expand Down
1 change: 0 additions & 1 deletion snapcraft_legacy/plugins/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,3 @@
from .npm import NpmPlugin # noqa: F401
from .python import PythonPlugin # noqa: F401
from .qmake import QMakePlugin # noqa: F401
from .rust import RustPlugin # noqa: F401
109 changes: 0 additions & 109 deletions snapcraft_legacy/plugins/v2/rust.py

This file was deleted.

Loading

0 comments on commit b26285f

Please sign in to comment.