diff --git a/src/idiap_devtools/scripts/pixi.py b/src/idiap_devtools/scripts/pixi.py index f114f391339d21bdb823aa2c6632a1ab002b3bfd..78edebf9bec313743cd687a11928a6ea22d347e1 100644 --- a/src/idiap_devtools/scripts/pixi.py +++ b/src/idiap_devtools/scripts/pixi.py @@ -18,7 +18,8 @@ logger = setup(__name__.split(".", 1)[0]) epilog=""" Examples: - 1. Creates a pixi configuration file for a project you just checked out: + 1. Creates a **draft** pixi configuration file for a project you just checked + out: .. code:: sh @@ -27,7 +28,7 @@ Examples: ... >>> - 2. Creates a pixi configuration file for a project you checked out at + 2. Creates a draft pixi configuration file for a project you checked out at directory my-project: .. code:: sh @@ -40,10 +41,10 @@ Examples: .. 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. + 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. """, ) @@ -70,29 +71,11 @@ Examples: 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.""" @@ -105,27 +88,34 @@ def pixi( the_profile = Profile(profile) - def _make_requirement_dict( - requirements: list[str], version: dict[str, str] - ) -> dict[str, str]: + python_to_conda = { + v: k + for k, v in the_profile.data["conda"]["to_python"].items() + if not k.startswith("__") + } + + version = the_profile.conda_constraints(python) + assert version is not None + + def _make_requirement_dict(requirements: list[str]) -> dict[str, str]: retval: dict[str, str] = {} for k in requirements: pr = packaging.requirements.Requirement(k) + name = ( + pr.name + if pr.name not in python_to_conda + else python_to_conda[pr.name] + ) if pr.name in version: - retval[pr.name] = version[pr.name] + retval[name] = version[name] if pr.specifier: - if pr.name in retval: - retval[pr.name] = ",".join( - (retval[pr.name], str(pr.specifier)) - ) + if name in retval: + retval[name] = ",".join((retval[name], str(pr.specifier))) else: - retval[pr.name] = str(pr.specifier) - retval.setdefault(pr.name, "*") + retval[name] = str(pr.specifier) + retval.setdefault(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(): @@ -143,7 +133,7 @@ def pixi( + pyproject["project"].get("maintainers", []) ], description=pyproject["project"]["description"], - license=pyproject["project"]["license"]["text"], + # license=pyproject["project"]["license"]["text"], readme="README.md", homepage=pyproject["project"]["urls"]["homepage"], repository=pyproject["project"]["urls"]["repository"], @@ -159,7 +149,7 @@ def pixi( config["dependencies"] = {"python": python + ".*"} config["dependencies"].update( _make_requirement_dict( - pyproject.get("project", {}).get("dependencies", []), version + pyproject.get("project", {}).get("dependencies", []) ) ) @@ -168,12 +158,12 @@ def pixi( # setup standardized build procedure config.setdefault("feature", {}).setdefault("build", {})["dependencies"] = ( _make_requirement_dict( - pyproject.get("build-system", {}).get("requires", []), version + pyproject.get("build-system", {}).get("requires", []) ) ) # add pip so that the build works config["feature"]["build"]["dependencies"].update( - _make_requirement_dict(["pip"], version) + _make_requirement_dict(["pip"]) ) config["feature"]["build"]["tasks"] = dict( build="pip install --no-build-isolation --no-dependencies --editable ." @@ -189,15 +179,31 @@ def pixi( ): config.setdefault("feature", {}).setdefault(feature, {})[ "dependencies" - ] = _make_requirement_dict(deps, version) + ] = _make_requirement_dict(deps) if "pre-commit" in config["feature"][feature]["dependencies"]: config["feature"][feature]["tasks"] = { - "qa": "pre-commit run --all-files" + "qa": "pre-commit run --all-files", + "qa-install": "pre-commit install", } # this feature can be separated from the rest config.setdefault("environments", {})["qa"] = [feature] - cmds.append("To do quality-assurance, run: `pixi run qa`") + cmds.append( + "To install pre-commit hook, run: `pixi run qa-install`" + ) + cmds.append("To run quality-assurance, run: `pixi run qa`") + + # if ruff is part of pre-commit configuration, then also add that + # dependence to the qa stack + precommit_config = project_dir / ".pre-commit-config.yaml" + if ( + precommit_config.exists() + and "ruff" in precommit_config.open().read() + and "ruff" not in config["feature"][feature]["dependencies"] + ): + config["feature"][feature]["dependencies"]["ruff"] = "*" + config["feature"][feature]["tasks"]["ruff"] = "ruff check" + cmds.append("To run a simple ruff check, run: `pixi run ruff`") if "sphinx" in config["feature"][feature]["dependencies"]: config["feature"][feature]["tasks"] = { @@ -226,6 +232,7 @@ def pixi( cmds.append("To do run test, run: `pixi run test`") # backup previous installation plan, if one exists + output = project_dir / "pixi.toml" if output.exists(): backup = output.parent / (output.name + "~") shutil.copy(output, backup)