Skip to content

Commit

Permalink
Merge pull request #1985 from wolfv/rattler-noarch-hints
Browse files Browse the repository at this point in the history
feat: add rattler-build noarch hints
  • Loading branch information
wolfv authored Jul 26, 2024
2 parents 0e2a3b6 + 4089c64 commit 2916933
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 40 deletions.
1 change: 1 addition & 0 deletions conda_smithy/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,7 @@ def __call__(self, args):
os.path.join(recipe),
conda_forge=args.conda_forge,
return_hints=True,
feedstock_dir=args.feedstock_dir,
)
if lints:
all_good = False
Expand Down
50 changes: 41 additions & 9 deletions conda_smithy/lint_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections.abc import Mapping
from glob import glob
from inspect import cleandoc
from pathlib import Path
from textwrap import indent

import github
Expand Down Expand Up @@ -49,7 +50,9 @@
lint_usage_of_legacy_patterns,
)
from conda_smithy.linter.utils import (
CONDA_BUILD_TOOL,
EXPECTED_SECTION_ORDER,
RATTLER_BUILD_TOOL,
find_local_config_file,
get_section,
)
Expand All @@ -63,6 +66,7 @@
ensure_valid_license_family,
)

from conda_smithy.configure_feedstock import _read_forge_config
from conda_smithy.utils import get_yaml, render_meta_yaml
from conda_smithy.validate_schema import validate_json_schema

Expand Down Expand Up @@ -267,7 +271,9 @@ def lintify_meta_yaml(
# 26: pin_subpackage is for subpackages and pin_compatible is for
# non-subpackages of the recipe. Contact @carterbox for troubleshooting
# this lint.
lint_pin_subpackages(meta, outputs_section, package_section, lints)
lint_pin_subpackages(
meta, outputs_section, package_section, lints, is_rattler_build
)

# 27: Check usage of whl files as a source
lint_check_usage_of_whls(recipe_fname, noarch_value, lints, hints)
Expand All @@ -283,13 +289,16 @@ def lintify_meta_yaml(
hint_pip_usage(build_section, hints)

# 2: suggest python noarch (skip on feedstocks)
raw_requirements_section = meta.get("requirements", {})
hint_suggest_noarch(
noarch_value,
build_requirements,
raw_requirements_section,
is_staged_recipes,
conda_forge,
recipe_fname,
hints,
is_rattler_build,
)

# 3: suggest fixing all recipe/*.sh shellcheck findings
Expand Down Expand Up @@ -523,17 +532,40 @@ def _format_validation_msg(error: jsonschema.ValidationError):
)


def main(recipe_dir, conda_forge=False, return_hints=False):
def main(
recipe_dir, conda_forge=False, return_hints=False, feedstock_dir=None
):
recipe_dir = os.path.abspath(recipe_dir)
recipe_meta = os.path.join(recipe_dir, "meta.yaml")
if not os.path.exists(recipe_dir):
raise OSError("Feedstock has no recipe/meta.yaml.")
build_tool = CONDA_BUILD_TOOL
if feedstock_dir:
feedstock_dir = os.path.abspath(feedstock_dir)
forge_config = _read_forge_config(feedstock_dir)
if forge_config.get("conda_build_tool", "") == RATTLER_BUILD_TOOL:
build_tool = RATTLER_BUILD_TOOL

if build_tool == RATTLER_BUILD_TOOL:
recipe_file = os.path.join(recipe_dir, "recipe.yaml")
else:
recipe_file = os.path.join(recipe_dir, "meta.yaml")

with open(recipe_meta) as fh:
content = render_meta_yaml("".join(fh))
meta = get_yaml().load(content)
if not os.path.exists(recipe_file):
raise OSError(
f"Feedstock has no recipe/{os.path.basename(recipe_file)}"
)

results, hints = lintify_meta_yaml(meta, recipe_dir, conda_forge)
if build_tool == CONDA_BUILD_TOOL:
with open(recipe_file) as fh:
content = render_meta_yaml("".join(fh))
meta = get_yaml().load(content)
else:
meta = get_yaml().load(Path(recipe_file))

results, hints = lintify_meta_yaml(
meta,
recipe_dir,
conda_forge,
is_rattler_build=build_tool == RATTLER_BUILD_TOOL,
)
validation_errors, validation_hints = lintify_forge_yaml(
recipe_dir=recipe_dir
)
Expand Down
6 changes: 6 additions & 0 deletions conda_smithy/linter/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
Collection of errors that can be raised by the linter.
"""

HINT_NO_ARCH = """Whenever possible python packages should use noarch.
See https://conda-forge.org/docs/maintainer/knowledge_base.html#noarch-builds"""
55 changes: 33 additions & 22 deletions conda_smithy/linter/hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import sys
from glob import glob

from conda_smithy.linter import rattler_linter
from conda_smithy.linter.errors import HINT_NO_ARCH
from conda_smithy.linter.utils import find_local_config_file, is_selector_line
from conda_smithy.utils import get_yaml

Expand All @@ -23,7 +25,14 @@ def hint_pip_usage(build_section, hints):


def hint_suggest_noarch(
noarch_value, build_reqs, is_staged_recipes, conda_forge, meta_fname, hints
noarch_value,
build_reqs,
raw_requirements_section,
is_staged_recipes,
conda_forge,
recipe_fname,
hints,
is_rattler_build: bool = False,
):
if (
noarch_value is None
Expand All @@ -32,30 +41,32 @@ def hint_suggest_noarch(
and ("pip" in build_reqs)
and (is_staged_recipes or not conda_forge)
):
with open(meta_fname) as fh:
in_runreqs = False
no_arch_possible = True
for line in fh:
line_s = line.strip()
if line_s == "host:" or line_s == "run:":
in_runreqs = True
runreqs_spacing = line[: -len(line.lstrip())]
continue
if line_s.startswith("skip:") and is_selector_line(line):
no_arch_possible = False
break
if in_runreqs:
if runreqs_spacing == line[: -len(line.lstrip())]:
in_runreqs = False
if is_rattler_build:
rattler_linter.hint_noarch_usage(
build_reqs, raw_requirements_section, hints
)
else:
with open(recipe_fname) as fh:
in_runreqs = False
no_arch_possible = True
for line in fh:
line_s = line.strip()
if line_s == "host:" or line_s == "run:":
in_runreqs = True
runreqs_spacing = line[: -len(line.lstrip())]
continue
if is_selector_line(line):
if line_s.startswith("skip:") and is_selector_line(line):
no_arch_possible = False
break
if no_arch_possible:
hints.append(
"Whenever possible python packages should use noarch. "
"See https://conda-forge.org/docs/maintainer/knowledge_base.html#noarch-builds"
)
if in_runreqs:
if runreqs_spacing == line[: -len(line.lstrip())]:
in_runreqs = False
continue
if is_selector_line(line):
no_arch_possible = False
break
if no_arch_possible:
hints.append(HINT_NO_ARCH)


def hint_shellcheck_usage(recipe_dir, hints):
Expand Down
48 changes: 39 additions & 9 deletions conda_smithy/linter/lints.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,13 @@ def lint_require_lower_bound_on_python_version(
)


def lint_pin_subpackages(meta, outputs_section, package_section, lints):
def lint_pin_subpackages(
meta,
outputs_section,
package_section,
lints,
is_rattler_build: bool = False,
):
subpackage_names = []
for out in outputs_section:
if "name" in out:
Expand All @@ -505,15 +511,21 @@ def lint_pin_subpackages(meta, outputs_section, package_section, lints):
def check_pins(pinning_section):
if pinning_section is None:
return
for pin in fnmatch.filter(pinning_section, "compatible_pin*"):
filter_pin = (
"pin_compatible*" if is_rattler_build else "compatible_pin*"
)
for pin in fnmatch.filter(pinning_section, filter_pin):
if pin.split()[1] in subpackage_names:
lints.append(
"pin_subpackage should be used instead of"
f" pin_compatible for `{pin.split()[1]}`"
" because it is one of the known outputs of this recipe:"
f" {subpackage_names}."
)
for pin in fnmatch.filter(pinning_section, "subpackage_pin*"):
filter_pin = (
"pin_subpackage*" if is_rattler_build else "subpackage_pin*"
)
for pin in fnmatch.filter(pinning_section, filter_pin):
if pin.split()[1] not in subpackage_names:
lints.append(
"pin_compatible should be used instead of"
Expand All @@ -523,12 +535,30 @@ def check_pins(pinning_section):
)

def check_pins_build_and_requirements(top_level):
if "build" in top_level and "run_exports" in top_level["build"]:
check_pins(top_level["build"]["run_exports"])
if "requirements" in top_level and "run" in top_level["requirements"]:
check_pins(top_level["requirements"]["run"])
if "requirements" in top_level and "host" in top_level["requirements"]:
check_pins(top_level["requirements"]["host"])
if not is_rattler_build:
if "build" in top_level and "run_exports" in top_level["build"]:
check_pins(top_level["build"]["run_exports"])
if (
"requirements" in top_level
and "run" in top_level["requirements"]
):
check_pins(top_level["requirements"]["run"])
if (
"requirements" in top_level
and "host" in top_level["requirements"]
):
check_pins(top_level["requirements"]["host"])
else:
if (
"requirements" in top_level
and "run_exports" in top_level["requirements"]
):
if "strong" in top_level["requirements"]["run_exports"]:
check_pins(
top_level["requirements"]["run_exports"]["strong"]
)
else:
check_pins(top_level["requirements"]["run_exports"])

check_pins_build_and_requirements(meta)
for out in outputs_section:
Expand Down
37 changes: 37 additions & 0 deletions conda_smithy/linter/rattler_linter.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from typing import Any, Dict, List

from conda_smithy.linter.errors import HINT_NO_ARCH

REQUIREMENTS_ORDER = ["build", "host", "run"]

EXPECTED_SINGLE_OUTPUT_SECTION_ORDER = [
Expand All @@ -20,3 +24,36 @@
"about",
"extra",
]


def hint_noarch_usage(
build_section: Dict[str, Any],
requirement_section: Dict[str, Any],
hints: List[str],
):
build_reqs = requirement_section.get("build", None)
if (
build_reqs
and not any(
[
b.startswith("${{")
and ("compiler('c')" in b or 'compiler("c")' in b)
for b in build_reqs
]
)
and ("pip" in build_reqs)
):
no_arch_possible = True
if "skip" in build_section:
no_arch_possible = False

for _, section_requirements in requirement_section.items():
if any(
isinstance(requirement, dict)
for requirement in section_requirements
):
no_arch_possible = False
break

if no_arch_possible:
hints.append(HINT_NO_ARCH)
Loading

0 comments on commit 2916933

Please sign in to comment.