Skip to content
Snippets Groups Projects
Commit 6c670525 authored by André Anjos's avatar André Anjos :speech_balloon:
Browse files

[scripts.pixi] Introduce script to dump prototype pixi.toml configuration files for the user

parent 237d2a77
No related branches found
No related tags found
1 merge request!14Provide support for auto-generating pixi configuration files
......@@ -24,3 +24,6 @@ _citools/
_work/
.mypy_cache/
.pytest_cache/
.pixi/
pixi.lock
pixi.toml
......@@ -8,6 +8,7 @@ from ..click import AliasedGroup
from .env import env
from .fullenv import fullenv
from .gitlab import gitlab
from .pixi import pixi
from .update_pins import update_pins
......@@ -23,4 +24,5 @@ def cli():
cli.add_command(env)
cli.add_command(fullenv)
cli.add_command(gitlab)
cli.add_command(pixi)
cli.add_command(update_pins)
# Copyright © 2022 Idiap Research Institute <contact@idiap.ch>
#
# SPDX-License-Identifier: BSD-3-Clause
import pathlib
import sys
import click
from ..click import PreserveIndentCommand, validate_profile, verbosity_option
from ..logging import setup
logger = setup(__name__.split(".", 1)[0])
@click.command(
cls=PreserveIndentCommand,
epilog="""
Examples:
1. Creates a pixi configuration file for a project you just checked out:
.. code:: sh
$ devtool pixi -vv .
$ pixi run python
...
>>>
2. Creates a pixi configuration file for a project you checked out at
directory my-project:
.. code:: sh
$ devtool pixi -vv my-project
$ cd my-project
$ pixi run python
...
>>>
.. tip::
You may hand-edit the output file ``pixi.toml`` to adjust for details, add
conda or Python packages you'd like to complement your work environment.
An example would be adding debuggers such as ``pdbpp`` to the installation
plan.
""",
)
@click.argument(
"project-dir",
nargs=1,
required=True,
type=click.Path(path_type=pathlib.Path),
)
@click.option(
"-P",
"--profile",
default="default",
show_default=True,
callback=validate_profile,
help="Directory containing the development profile (and a file named "
"profile.toml), or the name of a configuration key pointing to the "
"development profile to use",
)
@click.option(
"-p",
"--python",
default=("%d.%d" % sys.version_info[:2]),
show_default=True,
help="Version of python to build the environment for",
)
@click.option(
"-i/-I",
"--ignore-template/--no-ignore-template",
default=False,
show_default=True,
help="If set, then ignores any project-based templates found "
"on ``conda/pixi.toml.in``",
)
@click.option(
"-o",
"--output",
default="pixi.toml",
show_default=True,
help="The name of the environment plan file",
type=click.Path(path_type=pathlib.Path),
)
@verbosity_option(logger=logger)
def pixi(
project_dir,
profile,
python,
ignore_template,
output,
**_,
) -> None:
"""Create a pixi recipe for a project."""
import shutil
import packaging.requirements
from ..profile import Profile
the_profile = Profile(profile)
def _make_requirement_dict(
requirements: list[str], version: dict[str, str]
) -> dict[str, str]:
retval: dict[str, str] = {}
for k in requirements:
pr = packaging.requirements.Requirement(k)
if pr.name in version:
retval[pr.name] = version[pr.name]
if pr.specifier:
if pr.name in retval:
retval[pr.name] = ",".join(
(retval[pr.name], str(pr.specifier))
)
else:
retval[pr.name] = str(pr.specifier)
retval.setdefault(pr.name, "*")
return retval
version = the_profile.conda_constraints(python)
assert version is not None
# loads the pyproject.toml file (easy)
pyproject = project_dir / "pyproject.toml"
if pyproject.exists():
import tomli
pyproject = tomli.load(pyproject.open("rb"))
# build output TOML pixi file
config = {}
config["project"] = dict(
name=pyproject["project"]["name"],
authors=[
f"{k['name']} <{k['email']}>"
for k in pyproject["project"].get("authors", [])
+ pyproject["project"].get("maintainers", [])
],
description=pyproject["project"]["description"],
license=pyproject["project"]["license"]["text"],
readme="README.md",
homepage=pyproject["project"]["urls"]["homepage"],
repository=pyproject["project"]["urls"]["repository"],
documentation=pyproject["project"]["urls"]["documentation"],
)
conda_config = the_profile.conda_config(
python=python, public=True, stable=True
)
config["project"]["channels"] = conda_config.channels
config["project"]["platforms"] = ["linux-64", "osx-arm64"]
config["dependencies"] = {"python": python + ".*"}
config["dependencies"].update(
_make_requirement_dict(
pyproject.get("project", {}).get("dependencies", []), version
)
)
cmds = []
# setup standardized build procedure
config.setdefault("feature", {}).setdefault("build", {})["dependencies"] = (
_make_requirement_dict(
pyproject.get("build-system", {}).get("requires", []), version
)
)
# add pip so that the build works
config["feature"]["build"]["dependencies"].update(
_make_requirement_dict(["pip"], version)
)
config["feature"]["build"]["tasks"] = dict(
build="pip install --no-build-isolation --no-dependencies --editable ."
)
config.setdefault("environments", {}).setdefault("default", []).insert(
0, "build"
)
cmds.append("To install, run: `pixi run build`")
# adds optional features
for feature, deps in (
pyproject.get("project", {}).get("optional-dependencies", {}).items()
):
config.setdefault("feature", {}).setdefault(feature, {})[
"dependencies"
] = _make_requirement_dict(deps, version)
if "pre-commit" in config["feature"][feature]["dependencies"]:
config["feature"][feature]["tasks"] = {
"qa": "pre-commit run --all-files"
}
# this feature can be separated from the rest
config.setdefault("environments", {})["qa"] = [feature]
cmds.append("To do quality-assurance, run: `pixi run qa`")
if "sphinx" in config["feature"][feature]["dependencies"]:
config["feature"][feature]["tasks"] = {
"doc": {
"cmd": "rm -rf doc/api && rm -rf html && sphinx-build -aEW doc html",
"depends_on": "build",
}
}
# this feature needs to have the package installed
config.setdefault("environments", {}).setdefault(
"default", []
).insert(0, feature)
cmds.append("To do build docs, run: `pixi run doc`")
if "pytest" in config["feature"][feature]["dependencies"]:
config["feature"][feature]["tasks"] = {
"test": {
"cmd": "pytest -sv tests/",
"depends_on": "build",
}
}
# this feature needs to have the package installed
config.setdefault("environments", {}).setdefault(
"default", []
).insert(0, feature)
cmds.append("To do run test, run: `pixi run test`")
# backup previous installation plan, if one exists
if output.exists():
backup = output.parent / (output.name + "~")
shutil.copy(output, backup)
with output.open("w") as f:
import tomlkit
tomlkit.dump(config, f)
click.secho(
f"pixi configuration recorded at {str(output)}",
fg="yellow",
bold=True,
)
for k in cmds:
click.secho(k, fg="yellow", bold=True)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment