Skip to content

Commit

Permalink
Merge pull request #2039 from conda-forge/lint-pip
Browse files Browse the repository at this point in the history
feat: add linting for pip build backend
  • Loading branch information
beckermr committed Aug 22, 2024
2 parents f82b58f + 0a8133f commit 5c18d51
Show file tree
Hide file tree
Showing 5 changed files with 442 additions and 24 deletions.
58 changes: 35 additions & 23 deletions conda_smithy/lint_recipe.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import json
import os
import sys
Expand All @@ -11,11 +12,17 @@
import github
import jsonschema
import requests
from conda_build.metadata import (
ensure_valid_license_family,
)
from rattler_build_conda_compat import loader as rattler_loader
from ruamel.yaml.constructor import DuplicateKeyError

from conda_smithy.configure_feedstock import _read_forge_config
from conda_smithy.linter import conda_recipe_v1_linter
from conda_smithy.linter.hints import (
hint_check_spdx,
hint_pip_no_build_backend,
hint_pip_usage,
hint_shellcheck_usage,
hint_suggest_noarch,
Expand Down Expand Up @@ -58,19 +65,8 @@
RATTLER_BUILD_TOOL,
find_local_config_file,
get_section,
load_linter_toml_metdata,
)

if sys.version_info[:2] < (3, 11):
import tomli as tomllib
else:
import tomllib

from conda_build.metadata import (
ensure_valid_license_family,
)
from rattler_build_conda_compat import loader as rattler_loader

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 @@ -499,9 +495,10 @@ def run_conda_forge_specific(

# 5: Package-specific hints
# (e.g. do not depend on matplotlib, only matplotlib-base)
build_reqs = requirements_section.get("build") or []
host_reqs = requirements_section.get("host") or []
run_reqs = requirements_section.get("run") or []
# we use a copy here since the += below mofiies the original list
build_reqs = copy.deepcopy(requirements_section.get("build") or [])
host_reqs = copy.deepcopy(requirements_section.get("host") or [])
run_reqs = copy.deepcopy(requirements_section.get("run") or [])
for out in outputs_section:
if recipe_version == 1:
output_requirements = rattler_loader.load_all_requirements(out)
Expand All @@ -517,14 +514,7 @@ def run_conda_forge_specific(
else:
run_reqs += _req

hints_toml_url = "https://raw.githubusercontent.com/conda-forge/conda-forge-pinning-feedstock/main/recipe/linter_hints/hints.toml"
hints_toml_req = requests.get(hints_toml_url)
if hints_toml_req.status_code != 200:
# too bad, but not important enough to throw an error;
# linter will rerun on the next commit anyway
return
hints_toml_str = hints_toml_req.content.decode("utf-8")
specific_hints = tomllib.loads(hints_toml_str)["hints"]
specific_hints = (load_linter_toml_metdata() or {}).get("hints", [])

for rq in build_reqs + host_reqs + run_reqs:
dep = rq.split(" ")[0].strip()
Expand Down Expand Up @@ -572,6 +562,28 @@ def run_conda_forge_specific(
"The feedstock has no `.ci_support` files and thus will not build any packages."
)

# 8: Ensure the recipe specifies a Python build backend if needed
host_or_build_reqs = (requirements_section.get("host") or []) or (
requirements_section.get("build") or []
)
hint_pip_no_build_backend(host_or_build_reqs, recipe_name, hints)
for out in outputs_section:
if recipe_version == 1:
output_requirements = rattler_loader.load_all_requirements(out)
build_reqs = output_requirements.get("build") or []
host_reqs = output_requirements.get("host") or []
else:
_req = out.get("requirements") or {}
if isinstance(_req, Mapping):
build_reqs = _req.get("build") or []
host_reqs = _req.get("host") or []
else:
build_reqs = []
host_reqs = []

name = out.get("name", "").strip()
hint_pip_no_build_backend(host_reqs or build_reqs, name, hints)

# 9: No duplicates in conda-forge.yml
if (
not is_staged_recipes
Expand Down
33 changes: 32 additions & 1 deletion conda_smithy/linter/hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

from conda_smithy.linter import conda_recipe_v1_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.linter.utils import (
VALID_PYTHON_BUILD_BACKENDS,
find_local_config_file,
is_selector_line,
)
from conda_smithy.utils import get_yaml


Expand Down Expand Up @@ -174,3 +178,30 @@ def hint_check_spdx(about_section, hints):
"Documentation on acceptable licenses can be found "
"[here]( https://conda-forge.org/docs/maintainer/adding_pkgs.html#spdx-identifiers-and-expressions )."
)


def hint_pip_no_build_backend(host_or_build_section, package_name, hints):
if host_or_build_section and any(
req.split(" ")[0] == "pip" for req in host_or_build_section
):
found_backend = False
for backend in VALID_PYTHON_BUILD_BACKENDS:
if any(
req.split(" ")[0]
in [
backend,
backend.replace("-", "_"),
backend.replace("_", "-"),
]
for req in host_or_build_section
):
found_backend = True
break

if not found_backend:
hints.append(
f"No valid build backend found for Python recipe for package `{package_name}` using `pip`. Python recipes using `pip` need to "
"explicitly specify a build backend in the `host` section. "
"If your recipe has built with only `pip` in the `host` section in the past, you likely should "
"add `setuptools` to the `host` section of your recipe."
)
33 changes: 33 additions & 0 deletions conda_smithy/linter/utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import copy
import os
import re
import sys
from collections.abc import Sequence
from functools import lru_cache
from glob import glob
from typing import Dict, List, Mapping, Optional, Union

import requests
from conda.models.version import InvalidVersionSpec, VersionOrder
from conda_build.metadata import (
FIELDS as _CONDA_BUILD_FIELDS,
)
from rattler_build_conda_compat import loader as rattler_loader
from rattler_build_conda_compat.recipe_sources import get_all_sources

if sys.version_info[:2] < (3, 11):
import tomli as tomllib
else:
import tomllib

FIELDS = copy.deepcopy(_CONDA_BUILD_FIELDS)

# Just in case 'extra' moves into conda_build
Expand Down Expand Up @@ -46,6 +54,19 @@
CONDA_BUILD_TOOL = "conda-build"
RATTLER_BUILD_TOOL = "rattler-build"

VALID_PYTHON_BUILD_BACKENDS = [
"setuptools",
"flit-core",
"hatchling",
"poetry-core",
"pdm-backend",
"pdm-pep517",
"meson-python",
"scikit-build-core",
"maturin",
"jupyter_packaging",
]


def get_section(parent, name, lints, recipe_version: int = 0):
if recipe_version == 0:
Expand Down Expand Up @@ -185,3 +206,15 @@ def _lint_package_version(version: Optional[str]) -> Optional[str]:
VersionOrder(ver)
except InvalidVersionSpec as e:
return invalid_version.format(ver=ver, err=e)


@lru_cache(maxsize=1)
def load_linter_toml_metdata():
hints_toml_url = "https://raw.githubusercontent.com/conda-forge/conda-forge-pinning-feedstock/main/recipe/linter_hints/hints.toml"
hints_toml_req = requests.get(hints_toml_url)
if hints_toml_req.status_code != 200:
# too bad, but not important enough to throw an error;
# linter will rerun on the next commit anyway
return None
hints_toml_str = hints_toml_req.content.decode("utf-8")
return tomllib.loads(hints_toml_str)
23 changes: 23 additions & 0 deletions news/2039-pip-backend-hint.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Added hint for missing ``pip`` build backend in the ``host`` section of the recipe. (#2039)

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
Loading

0 comments on commit 5c18d51

Please sign in to comment.