From 87d1f9c08578390ee0084021f1980798d94086eb Mon Sep 17 00:00:00 2001 From: Oliver Borchert Date: Mon, 12 Oct 2026 14:38:16 +0200 Subject: [PATCH] feat: Always check dependency licenses with conda-deny --- template/.github/workflows/ci.yml.jinja | 14 ++++++++++ template/README.md.jinja | 15 +++++++++++ template/pixi.toml.jinja | 36 +++++++++++++++++++++++++ tests/test_template.py | 19 +++++++++++++ 4 files changed, 84 insertions(+) diff --git a/template/.github/workflows/ci.yml.jinja b/template/.github/workflows/ci.yml.jinja index d6c60ad..c225378 100644 --- a/template/.github/workflows/ci.yml.jinja +++ b/template/.github/workflows/ci.yml.jinja @@ -28,6 +28,20 @@ jobs: run: pixi run lint env: CLICOLOR_FORCE: 1 + + conda-deny: + name: Verify Software License Compliance + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - name: Check out branch + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Set up pixi + uses: prefix-dev/setup-pixi@9dabb60412d3d2d967a8d682f7ce01317af40514 # v0.11.0 + - name: Run conda-deny + run: pixi run conda-deny-check + env: + CLICOLOR_FORCE: 1 {%- endraw %} pytest: diff --git a/template/README.md.jinja b/template/README.md.jinja index d213251..181af49 100644 --- a/template/README.md.jinja +++ b/template/README.md.jinja @@ -21,3 +21,18 @@ cd {{ project_slug }} pixi run pre-commit-install pixi run test ``` + +## Dependency licenses + +CI runs [conda-deny](https://github.com/Quantco/conda-deny) to check dependency +licenses across all environments and platforms in `pixi.lock`. Run it locally with: + +```bash +pixi run conda-deny-check +``` + +The starter allowlist in `[tool.conda-deny]` in `pixi.toml` covers the generated +development and test environments, including their runtime dependencies. +Review and customize it for your project's license policy. Dependencies with +unlisted or missing licenses fail the check. The local project itself is excluded; +set and review its license separately. diff --git a/template/pixi.toml.jinja b/template/pixi.toml.jinja index 96c6b81..d993897 100644 --- a/template/pixi.toml.jinja +++ b/template/pixi.toml.jinja @@ -47,10 +47,12 @@ tombi = "*" pre-commit-hooks = "*" typos = "*" zizmor = "*" +conda-deny = "*" [feature.lint.tasks] pre-commit-install = "lefthook install" lint = "lefthook run pre-commit --all-files" +conda-deny-check = "conda-deny check" {%- if minimal_python_version <= "py310" %} [feature.py310.dependencies] @@ -94,3 +96,37 @@ py313 = ["py313", "test"] {%- if minimal_python_version <= "py314" %} py314 = ["py314", "test"] {%- endif %} + +[tool.conda-deny] +# Starter allowlist for the generated development and test environments. +# Review this list for your project before adding dependencies. +safe-licenses = [ + "0BSD", + "AFL-2.1", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "EPL-2.0", + "GPL-3.0-only", + "GPL-3.0-only WITH GCC-exception-3.1", + "ISC", + "LGPL-2.1-only", + "LGPL-2.1-or-later", + "LGPL-3.0-only", + "MIT", + "MPL-2.0", + "PSF-2.0", + "Python-2.0", + "TCL", + "X11", + "Zlib", + "blessing", + "bzip2-1.0.6", + "LicenseRef-Public-Domain", + # Windows runtime dependencies distributed through conda-forge. + "LicenseRef-MicrosoftVisualCpp2015-2022Runtime", + "LicenseRef-MicrosoftWindowsSDK10", +] +# Exclude the local project; this check covers third-party dependencies. +ignore-packages = [{ package = "{{ project_slug }}" }] diff --git a/tests/test_template.py b/tests/test_template.py index 5de2af4..2b79879 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -1,6 +1,8 @@ import subprocess +import tomllib import pytest +from ruamel.yaml import YAML def test_generation(generated_project, project_slug): @@ -106,3 +108,20 @@ def test_minimal_python_version(generate_project, minimal_python_version: str): else: assert env not in pixi_toml assert env not in ci + + +def test_conda_deny(generated_project): + manifest = tomllib.loads((generated_project / "pixi.toml").read_text()) + lint = manifest["feature"]["lint"] + assert "conda-deny" in lint["dependencies"] + task = lint["tasks"]["conda-deny-check"] + assert task == "conda-deny check" + policy = manifest["tool"]["conda-deny"] + assert "MIT" in policy["safe-licenses"] + assert policy["ignore-packages"] == [{"package": manifest["workspace"]["name"]}] + workflow = YAML(typ="safe").load( + (generated_project / ".github/workflows/ci.yml").read_text() + ) + job = workflow["jobs"]["conda-deny"] + assert "if" not in job + assert any(step.get("run") == "pixi run conda-deny-check" for step in job["steps"])