Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add lint for conda forge specific stuff #2022

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
88 changes: 59 additions & 29 deletions conda_smithy/lint_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
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
Expand Down Expand Up @@ -219,7 +220,9 @@ def lintify_meta_yaml(

# 14: Run conda-forge specific lints
if conda_forge:
run_conda_forge_specific(meta, recipe_dir, lints, hints)
run_conda_forge_specific(
meta, recipe_dir, lints, hints, is_rattler_build=is_rattler_build
)

# 15: Check if we are using legacy patterns
lint_usage_of_legacy_patterns(requirements_section, lints)
Expand Down Expand Up @@ -354,21 +357,36 @@ def lintify_meta_yaml(
return lints, hints


def run_conda_forge_specific(meta, recipe_dir, lints, hints):
def run_conda_forge_specific(
meta, recipe_dir, lints, hints, is_rattler_build: bool = False
):
gh = github.Github(os.environ["GH_TOKEN"])

# Retrieve sections from meta
package_section = get_section(meta, "package", lints)
extra_section = get_section(meta, "extra", lints)
sources_section = get_section(meta, "source", lints)
requirements_section = get_section(meta, "requirements", lints)
outputs_section = get_section(meta, "outputs", lints)
package_section = get_section(
meta, "package", lints, is_rattler_build=is_rattler_build
)
extra_section = get_section(
meta, "extra", lints, is_rattler_build=is_rattler_build
)
sources_section = get_section(
meta, "source", lints, is_rattler_build=is_rattler_build
)
requirements_section = get_section(
meta, "requirements", lints, is_rattler_build=is_rattler_build
)
outputs_section = get_section(
meta, "outputs", lints, is_rattler_build=is_rattler_build
)

# Fetch list of recipe maintainers
maintainers = extra_section.get("recipe-maintainers", [])

recipe_dirname = os.path.basename(recipe_dir) if recipe_dir else "recipe"
recipe_name = package_section.get("name", "").strip()
if is_rattler_build:
recipe_name = rattler_linter.get_recipe_name(meta)
else:
recipe_name = package_section.get("name", "").strip()
is_staged_recipes = recipe_dirname != "recipe"

# 1: Check that the recipe does not exist in conda-forge or bioconda
Expand Down Expand Up @@ -411,11 +429,16 @@ def run_conda_forge_specific(meta, recipe_dir, lints, hints):
)

url = None
for source_section in sources_section:
if str(source_section.get("url")).startswith(
"https://pypi.io/packages/source/"
):
url = source_section["url"]
if is_rattler_build:
for source_url in sources_section:
if source_url.startswith("https://pypi.io/packages/source/"):
url = source_url
else:
for source_section in sources_section:
if str(source_section.get("url")).startswith(
"https://pypi.io/packages/source/"
):
url = source_section["url"]
if url:
# get pypi name from urls like "https://pypi.io/packages/source/b/build/build-0.4.0.tar.gz"
pypi_name = url.split("/")[6]
Expand Down Expand Up @@ -451,32 +474,39 @@ def run_conda_forge_specific(meta, recipe_dir, lints, hints):

# 4: Do not delete example recipe
if is_staged_recipes and recipe_dir is not None:
example_meta_fname = os.path.abspath(
os.path.join(recipe_dir, "..", "example", "meta.yaml")
)

if not os.path.exists(example_meta_fname):
msg = (
"Please do not delete the example recipe found in "
"`recipes/example/meta.yaml`."
for recipe_name in ("meta.yaml", "recipe.yaml"):
example_fname = os.path.abspath(
os.path.join(recipe_dir, "..", "example", recipe_name)
)

if msg not in lints:
lints.append(msg)
if not os.path.exists(example_fname):
msg = (
"Please do not delete the example recipe found in "
f"`recipes/example/{recipe_name}`."
)

if msg not in lints:
lints.append(msg)
Comment on lines +477 to +489
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. There's no example/recipe.yaml to start with.


# 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 []
for out in outputs_section:
_req = out.get("requirements") or {}
if isinstance(_req, Mapping):
build_reqs += _req.get("build") or []
host_reqs += _req.get("host") or []
run_reqs += _req.get("run") or []
if is_rattler_build:
output_requirements = rattler_loader.load_all_requirements(out)
build_reqs += output_requirements.get("build") or []
host_reqs += output_requirements.get("host") or []
run_reqs += output_requirements.get("run") or []
else:
run_reqs += _req
_req = out.get("requirements") or {}
if isinstance(_req, Mapping):
build_reqs += _req.get("build") or []
host_reqs += _req.get("host") or []
run_reqs += _req.get("run") or []
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)
Expand Down
34 changes: 21 additions & 13 deletions conda_smithy/linter/rattler_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,33 @@ def hint_noarch_usage(
hints.append(HINT_NO_ARCH)


def lint_recipe_name(
recipe_content: RecipeWithContext,
lints: List[str],
) -> None:
def get_recipe_name(recipe_content: RecipeWithContext) -> str:
rendered_context_recipe = render_recipe_with_context(recipe_content)
package_name = (
rendered_context_recipe.get("package", {}).get("name", "").strip()
)
recipe_name = (
rendered_context_recipe.get("recipe", {}).get("name", "").strip()
)
name = package_name or recipe_name
return package_name or recipe_name


def get_recipe_version(recipe_content: RecipeWithContext) -> str:
rendered_context_recipe = render_recipe_with_context(recipe_content)
package_version = (
rendered_context_recipe.get("package", {}).get("version", "").strip()
)
recipe_version = (
rendered_context_recipe.get("recipe", {}).get("version", "").strip()
)
return package_version or recipe_version


def lint_recipe_name(
recipe_content: RecipeWithContext,
lints: List[str],
) -> None:
name = get_recipe_name(recipe_content)

lint_msg = _lint_recipe_name(name)
if lint_msg:
Expand All @@ -136,14 +151,7 @@ def lint_package_version(
recipe_content: RecipeWithContext,
lints: List[str],
) -> None:
rendered_context_recipe = render_recipe_with_context(recipe_content)
package_version = (
rendered_context_recipe.get("package", {}).get("version", "").strip()
)
recipe_version = (
rendered_context_recipe.get("recipe", {}).get("version", "").strip()
)
version = package_version or recipe_version
version = get_recipe_version(recipe_content)

lint_msg = _lint_package_version(version)

Expand Down
8 changes: 4 additions & 4 deletions conda_smithy/linter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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_url_sources

FIELDS = copy.deepcopy(_CONDA_BUILD_FIELDS)

Expand Down Expand Up @@ -69,15 +70,14 @@ def get_meta_section(parent, name, lints):
return section


def get_rattler_section(meta, name) -> Union[Dict, List[Dict]]:
def get_rattler_section(meta, name) -> Union[Dict, List[Dict], List[str]]:
if name == "requirements":
return rattler_loader.load_all_requirements(meta)
elif name == "tests":
return rattler_loader.load_all_tests(meta)
elif name == "source":
source: List[Dict] = meta.get("source", [])
if isinstance(source, Dict):
return [source]
sources = get_all_url_sources(meta)
return list(sources)

return meta.get(name, {})

Expand Down
3 changes: 1 addition & 2 deletions news/linting.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
**Added:**

* Added more lints / hints for conda v2 recipes. (#2000, #2003, #2008, #2016)
* Added more lints / hints for conda v2 recipes. (#2000, #2003, #2008, #2016, #2022)

**Changed:**

Expand Down
Loading