launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28519
[Merge] ~jugmac00/lpcraft:add-miniconda-plugin into lpcraft:main
Jürgen Gmach has proposed merging ~jugmac00/lpcraft:add-miniconda-plugin into lpcraft:main.
Commit message:
Add news entry for adding the Miniconda plugin
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~jugmac00/lpcraft/+git/lpcraft/+merge/423699
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~jugmac00/lpcraft:add-miniconda-plugin into lpcraft:main.
diff --git a/NEWS.rst b/NEWS.rst
index 7a51838..de18653 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -13,6 +13,8 @@ Version history
- Allow interpolation of the ``run`` commands.
+- Add Miniconda plugin.
+
0.0.14 (2022-05-18)
===================
diff --git a/lpcraft/plugin/tests/test_plugins.py b/lpcraft/plugin/tests/test_plugins.py
index 6976849..fe7d930 100644
--- a/lpcraft/plugin/tests/test_plugins.py
+++ b/lpcraft/plugin/tests/test_plugins.py
@@ -324,3 +324,162 @@ class TestPlugins(CommandBaseTestCase):
plugin_config = fake_plugin[0].get_plugin_config()
self.assertEqual(plugin_config.python_version, "3.8")
self.assertIsNone(getattr(plugin_config, "series", None))
+
+ @patch("lpcraft.commands.run.get_provider")
+ @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+ def test_miniconda_plugin(
+ self, mock_get_host_architecture, mock_get_provider
+ ):
+ launcher = Mock(spec=launch)
+ provider = makeLXDProvider(lxd_launcher=launcher)
+ mock_get_provider.return_value = provider
+ execute_run = launcher.return_value.execute_run
+ execute_run.return_value = subprocess.CompletedProcess([], 0)
+ config = dedent(
+ """
+ pipeline:
+ - build
+
+ jobs:
+ build:
+ series: focal
+ architectures: amd64
+ plugin: miniconda
+ conda-channels:
+ - conda-forge
+ conda-packages:
+ - mamba
+ - pip
+ conda-python: 3.8
+ run: |
+ pip install --upgrade pytest
+ """
+ )
+ Path(".launchpad.yaml").write_text(config)
+ pre_run_command = dedent(
+ """
+ if [ ! -d "$HOME/miniconda3" ]; then
+ wget -O /tmp/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
+ chmod +x /tmp/miniconda.sh
+ /tmp/miniconda.sh -b
+ fi
+ export PATH=$HOME/miniconda3/bin:$PATH
+ conda remove --all -q -y -n $CONDA_ENV
+ conda create -n $CONDA_ENV -q -y -c conda-forge -c defaults PYTHON=3.8 mamba pip
+ source activate $CONDA_ENV
+ """ # noqa:E501
+ )
+
+ run_command = dedent(
+ """
+ export PATH=$HOME/miniconda3/bin:$PATH
+ source activate $CONDA_ENV
+ pip install --upgrade pytest
+ """
+ )
+ post_run_command = (
+ "export PATH=$HOME/miniconda3/bin:$PATH; "
+ "source activate $CONDA_ENV; conda env export"
+ )
+
+ self.run_command("run")
+
+ self.assertEqual(
+ [
+ call(
+ ["apt", "update"],
+ cwd=PosixPath("/root/lpcraft/project"),
+ env={"CONDA_ENV": "lpci"},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ [
+ "apt",
+ "install",
+ "-y",
+ "git",
+ "python3-dev",
+ "python3-pip",
+ "python3-venv",
+ "wget",
+ ],
+ cwd=PosixPath("/root/lpcraft/project"),
+ env={"CONDA_ENV": "lpci"},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ [
+ "bash",
+ "--noprofile",
+ "--norc",
+ "-ec",
+ pre_run_command,
+ ],
+ cwd=PosixPath("/root/lpcraft/project"),
+ env={"CONDA_ENV": "lpci"},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ [
+ "bash",
+ "--noprofile",
+ "--norc",
+ "-ec",
+ run_command,
+ ],
+ cwd=PosixPath("/root/lpcraft/project"),
+ env={"CONDA_ENV": "lpci"},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ call(
+ [
+ "bash",
+ "--noprofile",
+ "--norc",
+ "-ec",
+ post_run_command,
+ ],
+ cwd=PosixPath("/root/lpcraft/project"),
+ env={"CONDA_ENV": "lpci"},
+ stdout=ANY,
+ stderr=ANY,
+ ),
+ ],
+ execute_run.call_args_list,
+ )
+
+ def test_miniconda_plugin_works_without_plugin_settings(self):
+ config = dedent(
+ """
+ pipeline:
+ - build
+
+ jobs:
+ build:
+ series: focal
+ architectures: amd64
+ plugin: miniconda
+ run: |
+ pip install --upgrade pytest
+ """
+ )
+ config_path = Path(".launchpad.yaml")
+ config_path.write_text(config)
+ config_obj = lpcraft.config.Config.load(config_path)
+ self.assertEqual(config_obj.jobs["build"][0].plugin, "miniconda")
+ pm = get_plugin_manager(config_obj.jobs["build"][0])
+ plugins = pm.get_plugins()
+ plugin_match = [
+ _ for _ in plugins if _.__class__.__name__ == "MiniCondaPlugin"
+ ]
+ self.assertEqual(
+ [
+ "defaults",
+ ],
+ plugin_match[0].conda_channels,
+ )
+ self.assertEqual(["PYTHON=3.8", "pip"], plugin_match[0].conda_packages)
diff --git a/lpcraft/plugins/plugins.py b/lpcraft/plugins/plugins.py
index 9117da8..c114030 100644
--- a/lpcraft/plugins/plugins.py
+++ b/lpcraft/plugins/plugins.py
@@ -3,11 +3,13 @@
from __future__ import annotations # isort:skip
-__all__ = ["ToxPlugin", "PyProjectBuildPlugin"]
+__all__ = ["ToxPlugin", "PyProjectBuildPlugin", "MiniCondaPlugin"]
-from typing import TYPE_CHECKING
+import textwrap
+from typing import TYPE_CHECKING, ClassVar, List, Optional, cast
import pydantic
+from pydantic import StrictStr
from lpcraft.plugin import hookimpl
from lpcraft.plugins import register
@@ -95,3 +97,119 @@ class PyProjectBuildPlugin(BasePlugin):
def lpcraft_execute_run(self) -> str:
# XXX jugmac00 2022-01-20: we should consider using a PPA
return "python3 -m pip install build==0.7.0; python3 -m build"
+
+
+@register(name="miniconda")
+class MiniCondaPlugin(BasePlugin):
+ """Installs `miniconda3` and resets the environment.
+
+ Usage:
+ In `.launchpad.yaml`, create the following structure:
+
+ .. code-block:: yaml
+
+ jobs:
+ myjob:
+ plugin: miniconda
+ conda-packages:
+ - mamba
+ - numpy=1.17
+ - scipy
+ - pip
+ conda-python: 3.8
+ run: |
+ conda install ....
+ pip install --upgrade pytest
+ python -m build .
+ """
+
+ class Config(BaseConfig):
+ conda_packages: Optional[List[StrictStr]]
+ conda_python: Optional[StrictStr]
+ conda_channels: Optional[List[StrictStr]]
+
+ @pydantic.validator("conda_python", pre=True)
+ def validate_conda_python(cls, v: str | float | int) -> str:
+ return str(v)
+
+ INTERPOLATES_RUN_COMMAND = True
+ DEFAULT_CONDA_PACKAGES: ClassVar[tuple[str, ...]] = ("pip",)
+ DEFAULT_CONDA_PYTHON = "3.8"
+ DEFAULT_CONDA_CHANNELS = ("defaults",)
+
+ def get_plugin_config(self) -> "MiniCondaPlugin.Config":
+ return cast(MiniCondaPlugin.Config, self.config.plugin_config)
+
+ @property
+ def conda_packages(self) -> list[str]:
+ conda_packages: set[str] = set()
+ conda_python = self.DEFAULT_CONDA_PYTHON
+ plugin_config = self.get_plugin_config()
+ if plugin_config.conda_python:
+ conda_python = plugin_config.conda_python
+ if plugin_config.conda_packages:
+ conda_packages.update(set(plugin_config.conda_packages))
+ conda_packages.add(f"PYTHON={conda_python}")
+ conda_packages.update(self.DEFAULT_CONDA_PACKAGES)
+ return sorted(conda_packages)
+
+ @property
+ def conda_channels(self) -> list[str]:
+ conda_channels: list[str] = []
+ plugin_config = self.get_plugin_config()
+ if plugin_config.conda_channels:
+ conda_channels.extend(plugin_config.conda_channels)
+ for channel in set(self.DEFAULT_CONDA_CHANNELS) - set(conda_channels):
+ conda_channels.append(channel)
+ return conda_channels
+
+ @hookimpl # type: ignore
+ def lpcraft_set_environment(self) -> dict[str, str]:
+ # `CONDA_ENV` sets the name of the Conda virtual environment
+ return {"CONDA_ENV": "lpci"}
+
+ @hookimpl # type: ignore
+ def lpcraft_install_packages(self) -> list[str]:
+ return [
+ "git",
+ "python3-dev",
+ "python3-pip",
+ "python3-venv",
+ "wget",
+ ]
+
+ @hookimpl # type: ignore
+ def lpcraft_execute_before_run(self) -> str:
+ run = self.config.run_before or ""
+ conda_channels = " ".join(f"-c {_}" for _ in self.conda_channels)
+ return textwrap.dedent(
+ f"""
+ if [ ! -d "$HOME/miniconda3" ]; then
+ wget -O /tmp/miniconda.sh https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
+ chmod +x /tmp/miniconda.sh
+ /tmp/miniconda.sh -b
+ fi
+ export PATH=$HOME/miniconda3/bin:$PATH
+ conda remove --all -q -y -n $CONDA_ENV
+ conda create -n $CONDA_ENV -q -y {conda_channels} {' '.join(self.conda_packages)}
+ source activate $CONDA_ENV
+ {run}""" # noqa:E501
+ )
+
+ @hookimpl # type: ignore
+ def lpcraft_execute_run(self) -> str:
+ run = self.config.run or ""
+ return textwrap.dedent(
+ f"""
+ export PATH=$HOME/miniconda3/bin:$PATH
+ source activate $CONDA_ENV
+ {run}"""
+ )
+
+ @hookimpl # type: ignore
+ def lpcraft_execute_after_run(self) -> str:
+ run = f"; {self.config.run_after}" if self.config.run_after else ""
+ return (
+ "export PATH=$HOME/miniconda3/bin:$PATH; "
+ f"source activate $CONDA_ENV; conda env export{run}"
+ )
Follow ups