Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7db3a74
fix: missing_run_exports linting checks either all `outputs:` or only…
dlaehnemann Jan 26, 2026
bb181a8
docs: adjust lint explanation to reflect outputs structure
dlaehnemann Jan 26, 2026
6bb573a
fix: lint for no `requirements:` on top level if `outputs:` specified
dlaehnemann Jan 29, 2026
51aef90
fix: working outputs-specific solution for `should_be_noarch_generic`…
dlaehnemann Jan 29, 2026
c6f60fa
fix: do check_deps() per output, if `outputs:` specified, and include…
dlaehnemann Jan 30, 2026
75d2aea
fix: should_be_noarch_generic now works with the new per-output setup
dlaehnemann Jan 30, 2026
1529fee
fix: attempt of introducing an `extra: skip-recipes:` mechanism, but …
dlaehnemann Jan 30, 2026
ff6c061
fix: generalize disallowed top-level sections lint for when `outputs:…
dlaehnemann Feb 5, 2026
f42ebf6
fix: add `package_location` to `check_deps()` arguments consistently
dlaehnemann Feb 5, 2026
4d95392
chore: ruff format
dlaehnemann Mar 30, 2026
8aee847
fix: default to empty list in `extra/skip-recipes` lookup
dlaehnemann Mar 30, 2026
fc7afe6
tests: add missing_run_exports_outputs linting test case
dlaehnemann Mar 30, 2026
80edbe8
fix: also test for unnecessary run_exports in main build section when…
dlaehnemann Mar 30, 2026
f60edb8
chore: .gitignore the .vscode folder
dlaehnemann Mar 30, 2026
4eb39df
tests: annotate offending entry in new unnecessary_run_exports test
dlaehnemann Mar 30, 2026
d3de03c
fix: adjust outputs test cases to new unnecessary run_exports lint
dlaehnemann Mar 31, 2026
e14e568
tests: add a bunch of outputs: linting test cases
dlaehnemann Mar 31, 2026
82e0de0
tests: remove unused `skip-recipes` functionality
dlaehnemann Mar 31, 2026
4d474eb
chore: ruff format
dlaehnemann Mar 31, 2026
a04a716
tests: some more outputs lint cases
dlaehnemann Mar 31, 2026
e736007
feat: generalize a function to get all occurrences of a particular se…
dlaehnemann Apr 1, 2026
ee6e7bf
Merge branch 'master' into fix/adjust-linting-to-new-outputs-structure
epruesse Apr 9, 2026
3dadcd5
feat: add Recipe method to check_for_missing_inherited_section()
dlaehnemann Apr 16, 2026
1362ea0
fix: adjust missing_summary lint to use new function to check_for_mis…
dlaehnemann Apr 16, 2026
4cf8b0d
fix: annotation of missing_summary_outputs.yaml linting case
dlaehnemann Apr 16, 2026
4674af5
feat: adjust all about section lints, including outputs test cases
dlaehnemann Apr 16, 2026
d39d6f1
fix: test repodata_patches_no_version_bump lint, and attempt fix of it
dlaehnemann Apr 21, 2026
df26fc3
feat: add Recipe.get_inherited_value() function
dlaehnemann Apr 21, 2026
98b73a6
Merge branch 'fix/adjust-linting-to-new-outputs-structure' of github.…
dlaehnemann Apr 21, 2026
45383b8
fix: adjust new compiler_needs_stdlib_c LintCheck to new check_deps()…
dlaehnemann Apr 22, 2026
742002d
fix: adjust build number lints, add build number only under outputs test
dlaehnemann Apr 22, 2026
b436bf4
fix: only run build_number_needs_bump lint if missing_build_number di…
dlaehnemann Apr 22, 2026
dda7189
test: add outputs recipe with missing build section(s)
dlaehnemann Apr 22, 2026
8943e75
fix: do not support build numbers per output for now (none of the exi…
dlaehnemann May 27, 2026
20b6764
fix: remove debugging print statement
dlaehnemann May 28, 2026
a178196
fix: add caveat in uses_vcs_url LintCheck, that it only checks top-le…
dlaehnemann May 28, 2026
91b66d9
feat: generalize Recipe.get_all_section_occurrences, so it includes s…
dlaehnemann May 28, 2026
b2ad6ed
docs: properly document Recipe.get_all_section_occurrences function
dlaehnemann May 28, 2026
db64a64
feat: adjust extra: lints to multiple outputs, include new linting ca…
dlaehnemann May 28, 2026
9bdc9dd
Merge branch 'master' into fix/adjust-linting-to-new-outputs-structure
dlaehnemann May 28, 2026
e49a144
fix: indentation errors after manual main branch conflict resolution
dlaehnemann May 28, 2026
9ffce98
fix: actually use package_location where necessary, not prefixing it …
dlaehnemann May 28, 2026
283eadd
chore: ruff
dlaehnemann May 28, 2026
7327d85
fix: address type hint issues
dlaehnemann May 29, 2026
aeedfcc
fix: address ty complaint about leading underscores in inherited func…
dlaehnemann May 29, 2026
53dca0f
fix: type hints to allow None instead of str entries in _find_proxy_s…
dlaehnemann May 29, 2026
1d02135
fix: try fixing ty complaint by guarding against None values instead
dlaehnemann May 29, 2026
b549b73
fix: use implicit False for None check
dlaehnemann May 29, 2026
4fd3236
Merge branch 'master' into fix/adjust-linting-to-new-outputs-structure
dlaehnemann Jun 9, 2026
9153024
ci: re-trigger CI without bioconda org permissions
dlaehnemann Jun 10, 2026
ea267b4
fix: revert build number checks to only top-level recipe entries, as …
dlaehnemann Jun 12, 2026
2d7b258
fix: remove now unused Recipe.get_inherited_value
dlaehnemann Jun 12, 2026
5e69982
chore: remove extra whitespace
dlaehnemann Jun 12, 2026
a7149e1
ci: retrigger without respective bioconda org rights
dlaehnemann Jun 15, 2026
4a40cb2
ci: another retrigger
dlaehnemann Jun 15, 2026
7fc5f74
Merge branch 'master' into fix/adjust-linting-to-new-outputs-structure
dlaehnemann Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ docs/recipes/
*~
_bioconda_recipes
docs/source/developer/_autosummary
# user-specific VSCode configs
.vscode

# Mac OS Files
.DS_Store
24 changes: 21 additions & 3 deletions bioconda_utils/lint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,20 @@ def run(self, recipe: _recipe.Recipe, fix: bool = False) -> list[LintMessage]:
if isinstance(src, dict):
self.check_source(cast(dict[str, Any], src), f"source/{num}")

# Run depends checks
self.check_deps(recipe.get_deps_dict())
# Run depends checks, per outputs: package if necessary
outputs = recipe.get("outputs", dict())
deps = recipe.get_deps_dict()
if outputs:
for i in range(len(outputs)):
output_location = f"outputs/{i}/"
# filter down to dependencies for this outputs: package
output_deps = dict()
for dep in deps:
if any(output_location in d for d in deps[dep]):
output_deps[dep] = deps[dep]
self.check_deps(output_deps, output_location)
else:
self.check_deps(deps, "")

return self.messages

Expand All @@ -281,7 +293,7 @@ def check_source(self, source: dict[str, Any], section: str) -> None:
``source/0`` (1,2,3...).
"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
"""Execute check on recipe dependencies

Example format for **deps**::
Expand All @@ -298,6 +310,12 @@ def check_deps(self, deps: dict[str, list[str]]) -> None:
Args:
deps: Dictionary mapping requirements occurring in the recipe
to their locations within the recipe.
package_location: Path to the main location for the build and
requirements sections. Empty string for the top
level in single-package recipes, something like
``outputs/0/`` for recipes with packages specified
in an outputs section.

"""

def fix(self, _message, _data, /) -> bool:
Expand Down
56 changes: 46 additions & 10 deletions bioconda_utils/lint/check_build_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class should_use_compilers(LintCheck):
"rust",
)

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
for compiler in self.compilers:
for location in deps.get(compiler, []):
self.message(section=location)
Expand All @@ -55,7 +55,7 @@ class compilers_must_be_in_build(LintCheck):

"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
for dep in deps:
if dep.startswith("compiler_"):
for location in deps[dep]:
Expand All @@ -70,7 +70,7 @@ class compiler_needs_stdlib_c(LintCheck):
``requirements: build:`` section.
"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
compiler = False
stdlib = False
for dep, locations in deps.items():
Expand Down Expand Up @@ -124,7 +124,7 @@ def _check_line(line: str) -> bool:
return True
return False

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "setuptools" not in deps:
return # no setuptools, no problem

Expand All @@ -150,7 +150,7 @@ class cython_must_be_in_host(LintCheck):
- cython
"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "cython" in deps:
if any("host" not in location for location in deps["cython"]):
self.message()
Expand All @@ -169,13 +169,13 @@ class cython_needs_compiler(LintCheck):

severity = WARNING

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "cython" in deps and "compiler_c" not in deps and "compiler_cxx" not in deps:
self.message()


class missing_run_exports(LintCheck):
"""Recipe should have a run_exports statement that ensures correct pinning in downstream packages
"""Recipe should have a run_exports statement for each package, ensuring correct pinning in downstream packages

This ensures that the package is automatically pinned to a compatible version if
it is used as a dependency in another recipe.
Expand All @@ -186,6 +186,15 @@ class missing_run_exports(LintCheck):
libraries) but also for e.g. Python packages, as those might also
introduce breaking changes in their APIs or command line interfaces.

A ``run_exports:`` specification should be specified in the relevant
``build:`` section for all packages that are built. This means either:
(i) in the main ``build`` section, if only one package is built from this
recipe, or:
(ii) in each outputs' ``build:`` section, if multiple ``outputs:`` are
specified. In this case, the main recipe ``build:`` section does not
refer to a package that is being built, but only to the recipe, so a
``run_exports:`` section for it does not make sense.

We distinguish between four cases.

**Case 1:** If the software follows semantic versioning (or it has at least a normal version string (like 1.2.3) and the actual strategy of the devs is unknown), add run_exports to the recipe like this::
Expand Down Expand Up @@ -233,6 +242,33 @@ class missing_run_exports(LintCheck):
"""

def check_recipe(self, recipe: _recipe.Recipe) -> None:
build = recipe.meta.get("build", dict())
if "run_exports" not in build:
self.message()
build_sections = recipe.get_all_section_occurrences(
section="build",
outputs_exclusive=True,
missing_as_empty=True,
)
for build in build_sections:
if "run_exports" not in build_sections[build]:
self.message()


class unnecessary_run_exports_in_main_build_section_with_multiple_outputs(LintCheck):
"""Recipe should not have a run_exports statement in main build section if it specifies multiple outputs.

A ``run_exports:`` specification should be specified in the relevant
``build:`` section for all packages that are built. This means either:
(i) in the main ``build`` section, if only one package is built from this
recipe, or:
(ii) in each outputs' ``build:`` section, if multiple ``outputs:`` are
specified.

This recipe specifies multiple ``outputs:``, so the main recipe ``build:``
section does not refer to a package that is being built, but only to the
recipe. Thus, a ``run_exports:`` section for it does not make sense.
"""

def check_recipe(self, recipe):
outputs = recipe.get("outputs", dict())
if outputs:
if "run_exports" in recipe.meta.get("build", dict()):
self.message()
85 changes: 72 additions & 13 deletions bioconda_utils/lint/check_completeness.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class missing_build_number(LintCheck):

build:
number: 0

Currently, bioconda-utils does not support specifying build numbers
under outputs, so only a single global build number in the top-level
build section is supported.

"""

def check_recipe(self, recipe: _recipe.Recipe) -> None:
Expand All @@ -24,6 +29,29 @@ def check_recipe(self, recipe: _recipe.Recipe) -> None:


class missing_home(LintCheck):
"""The recipe is missing an ``about/home`` entry with a homepage URL.

If the recipe specifies just one package, please add::

about:
home: <URL to homepage>

If the recipe specifies multiple `outputs:` packages, each needs to have
an `about: summary:` defined. Each package can either inherit this from the
global recipe (if it has this section!), or specify its own. For example,
package `one` inherits and package `two` specifies its own::

outputs:
one:
...
two:
about:
home: <URL to other homepage>
about:
home: <URL to homepage>

"""

"""The recipe is missing a homepage URL

Please add::
Expand All @@ -34,38 +62,69 @@ class missing_home(LintCheck):
"""

def check_recipe(self, recipe: _recipe.Recipe) -> None:
if not recipe.get("about/home", ""):
self.message(section="about")
missing_section = recipe.check_for_missing_inherited_section("about/home")
if missing_section:
self.message(section=missing_section)


class missing_summary(LintCheck):
"""The recipe is missing a summary
"""The recipe is missing an ``about/summary`` section.

Please add::
If the recipe specifies just one package, please add::

about:
summary: One line briefly describing package
about:
summary: One line briefly describing package

If the recipe specifies multiple `outputs:` packages, each needs to have
an `about: summary:` defined. Each package can either inherit this from the
global recipe (if it has this section!), or specify its own. For example,
package `one` inherits and package `two` specifies its own::

outputs:
one:
...
two:
about:
summary: Another one-liner
about:
summary: Some one-liner

"""

def check_recipe(self, recipe: _recipe.Recipe) -> None:
if not recipe.get("about/summary", ""):
self.message(section="about")
missing_section = recipe.check_for_missing_inherited_section("about/summary")
if missing_section:
self.message(section=missing_section)


class missing_license(LintCheck):
"""The recipe is missing the ``about/license`` key.
"""The recipe is missing an ``about/license`` section.

Please add::
If the recipe specifies just one package, please add::

about:
license: <name of license>
license: <name of license>

If the recipe specifies multiple `outputs:` packages, each needs to have
an `about: license:` defined. Each package can either inherit this from the
global recipe (if it has this section!), or specify its own. For example,
package `one` inherits and package `two` specifies its own::

outputs:
one:
...
two:
about:
license: <name of license>
about:
license: <name of license>

"""

def check_recipe(self, recipe: _recipe.Recipe) -> None:
if not recipe.get("about/license", ""):
self.message(section="about")
missing_section = recipe.check_for_missing_inherited_section("about/license")
if missing_section:
self.message(section=missing_section)


class missing_tests(LintCheck):
Expand Down
8 changes: 4 additions & 4 deletions bioconda_utils/lint/check_deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class uses_perl_threaded(LintCheck):

"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "perl-threaded" in deps:
self.message(data=True)

Expand All @@ -29,7 +29,7 @@ class uses_javajdk(LintCheck):

"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "java-jdk" in deps:
self.message(data=True)

Expand All @@ -45,7 +45,7 @@ class deprecated_numpy_spec(LintCheck):

"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "numpy" not in deps:
return
for path in deps["numpy"]:
Expand All @@ -66,7 +66,7 @@ class uses_matplotlib(LintCheck):

"""

def check_deps(self, deps: dict[str, list[str]]) -> None:
def check_deps(self, deps: dict[str, list[str]], package_location: str) -> None:
if "matplotlib" in deps:
self.message(data=True)

Expand Down
Loading
Loading