Skip to content

Commit

Permalink
feat: metadata will render recipe with context (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
nichmor authored Aug 2, 2024
1 parent 2a26a02 commit 2dba8d5
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 27 deletions.
67 changes: 59 additions & 8 deletions src/rattler_build_conda_compat/render.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# mypy: ignore-errors

from __future__ import annotations

from collections import OrderedDict
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
from typing import Dict, List, Optional
from typing import Any, Dict, List, Optional
import yaml
from ruamel.yaml import YAML
from conda_build.metadata import (
Expand All @@ -19,11 +23,12 @@
validate_spec,
combine_specs,
)
from conda_build.metadata import get_selectors
from conda_build.metadata import get_selectors, check_bad_chrs
from conda_build.config import Config

from rattler_build_conda_compat.loader import parse_recipe_config_file
from rattler_build_conda_compat.utils import find_recipe
from rattler_build_conda_compat.jinja.jinja import render_recipe_with_context
from rattler_build_conda_compat.loader import load_yaml, parse_recipe_config_file
from rattler_build_conda_compat.utils import _get_recipe_metadata, find_recipe


class MetaData(CondaMetaData):
Expand Down Expand Up @@ -59,10 +64,56 @@ def __init__(

self.requirements_path = os.path.join(self.path, "requirements.txt")

def parse_recipe(self):
yaml = YAML()
with open(os.path.join(self.path, self._meta_name), "r") as recipe_yaml:
return yaml.load(recipe_yaml)
def parse_recipe(self) -> dict[str, Any]:
recipe_path: Path = Path(self.path) / self._meta_name

yaml_content = load_yaml(recipe_path.read_text())

return render_recipe_with_context(yaml_content)

def name(self) -> str:
"""
Overrides the conda_build.metadata.MetaData.name method.
Returns the name of the package.
If recipe has multiple outputs, it will return the name of the `recipe` field.
Otherwise it will return the name of the `package` field.
Raises:
- CondaBuildUserError: If the `name` contains bad characters.
- ValueError: If the name is not lowercase or missing.
"""
name = _get_recipe_metadata(self.meta, "name")

if not name:
raise ValueError(f"Error: package/name missing in: {self.meta_path!r}")

if name != name.lower():
raise ValueError(f"Error: package/name must be lowercase, got: {name!r}")

check_bad_chrs(name, "package/name")
return name

def version(self) -> str:
"""
Overrides the conda_build.metadata.MetaData.version method.
Returns the version of the package.
If recipe has multiple outputs, it will return the version of the `recipe` field.
Otherwise it will return the version of the `package` field.
Raises:
- CondaBuildUserError: If the `version` contains bad characters.
- ValueError: If the version starts with a period or version is missing.
"""
version = _get_recipe_metadata(self.meta, "version")

if not version:
raise ValueError(f"Error: package/version missing in: {self.meta_path!r}")

check_bad_chrs(version, "package/version")
if version.startswith("."):
raise ValueError(f"Fully-rendered version can't start with period - got {version!r}")
return version

def render_recipes(self, variants) -> List[Dict]:
platform_and_arch = f"{self.config.platform}-{self.config.arch}"
Expand Down
17 changes: 16 additions & 1 deletion src/rattler_build_conda_compat/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations
import fnmatch
from logging import getLogger
import os
from pathlib import Path
from typing import Iterable
from typing import Any, Iterable, Literal


VALID_METAS = ("recipe.yaml",)
Expand Down Expand Up @@ -171,3 +172,17 @@ def has_recipe(recipe_dir: Path) -> bool:
return False
except OSError:
return False


_Metadata = Literal["name", "version"]


def _get_recipe_metadata(meta: dict[str, Any], field: _Metadata) -> str:
"""
Get recipe metadata ( name or version ).
It will extract from recipe or package section, depending on the presence of multiple outputs.
"""
if "outputs" in meta:
return meta.get("recipe", {}).get(field, "")
else:
return meta.get("package", {}).get(field, "")
1 change: 1 addition & 0 deletions tests/__snapshots__/test_jinja.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
- test -f ${PREFIX}/condabin/mamba
recipe:
name: mamba-split
version: 1.5.8
source:
sha256: 6ddaf4b0758eb7ca1250f427bc40c2c3ede43257a60bac54e4320a4de66759a6
url: https://github.com/mamba-org/mamba/archive/refs/tags/2024.03.25.tar.gz
Expand Down
34 changes: 17 additions & 17 deletions tests/__snapshots__/test_rattler_render.ambr
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
# serializer version: 1
# name: test_environ_is_passed_to_rattler_build
list([
CommentedMap({
'package': CommentedMap({
'name': 'py-test',
'version': '1.0.0',
dict({
'about': dict({
}),
'build': CommentedMap({
'skip': CommentedSeq([
'build': dict({
'skip': list([
"env.get('TEST_SHOULD_BE_PASSED') == 'false'",
]),
}),
'requirements': CommentedMap({
'build': CommentedSeq([
"${{ compiler('c') }}",
"${{ compiler('cuda') }}",
'extra': dict({
'final': True,
}),
'package': dict({
'name': 'py-test',
'version': '1.0.0',
}),
'requirements': dict({
'build': list([
'c_compiler_stub',
'cuda_compiler_stub',
]),
'host': CommentedSeq([
'host': list([
'python',
]),
'run': CommentedSeq([
'run': list([
'python',
]),
}),
'about': dict({
}),
'extra': dict({
'final': True,
}),
}),
])
# ---
Expand Down
22 changes: 22 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,25 @@ def old_recipe_dir(tmpdir: Path) -> Path:
meta.touch()

return recipe_dir


@pytest.fixture()
def mamba_recipe() -> Path:
return Path("tests/data/mamba_recipe.yaml")


@pytest.fixture()
def rich_recipe() -> Path:
return Path("tests/data/rich_recipe.yaml")


@pytest.fixture()
def feedstock_dir_with_recipe(tmpdir: Path) -> Path:
feedstock_dir = tmpdir / "feedstock"

feedstock_dir.mkdir()

recipe_dir = feedstock_dir / "recipe"
recipe_dir.mkdir()

return feedstock_dir
1 change: 1 addition & 0 deletions tests/data/mamba_recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ context:

recipe:
name: mamba-split
version: ${{ mamba_version }}

source:
url: https://github.com/mamba-org/mamba/archive/refs/tags/${{ release }}.tar.gz
Expand Down
55 changes: 55 additions & 0 deletions tests/data/rich_recipe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/prefix-dev/recipe-format/main/schema.json

context:
version: "13.4.2"
name: rich

package:
name: ${{ name}}
version: ${{ version }}

source:
- url:
- https://example.com/rich-${{ version }}.tar.gz # this will give a 404!
- https://pypi.io/packages/source/r/rich/rich-${{ version }}.tar.gz
sha256: d653d6bccede5844304c605d5aac802c7cf9621efd700b46c7ec2b51ea914898

build:
# Thanks to `noarch: python` this package works on all platforms
noarch: python
script:
- python -m pip install . -vv --no-deps --no-build-isolation

requirements:
host:
- pip
- poetry-core >=1.0.0
- python 3.10
run:
# sync with normalized deps from poetry-generated setup.py
- markdown-it-py >=2.2.0
- pygments >=2.13.0,<3.0.0
- python 3.10
- typing_extensions >=4.0.0,<5.0.0

tests:
- package_contents:
site_packages:
- rich
- python:
imports:
- rich

about:
homepage: https://github.com/Textualize/rich
license: MIT
license_file: LICENSE
summary: Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal
description: |
Rich is a Python library for rich text and beautiful formatting in the terminal.
The Rich API makes it easy to add color and style to terminal output. Rich
can also render pretty tables, progress bars, markdown, syntax highlighted
source code, tracebacks, and more — out of the box.
documentation: https://rich.readthedocs.io
repository: https://github.com/Textualize/rich
24 changes: 23 additions & 1 deletion tests/test_rattler_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import TYPE_CHECKING, Any

from rattler_build_conda_compat.loader import parse_recipe_config_file
from rattler_build_conda_compat.render import render
from rattler_build_conda_compat.render import MetaData, render

if TYPE_CHECKING:
from pathlib import Path
Expand Down Expand Up @@ -43,3 +43,25 @@ def test_environ_is_passed_to_rattler_build(env_recipe, snapshot) -> None:

finally:
os.environ.pop("TEST_SHOULD_BE_PASSED", None)


def test_metadata_for_single_output(feedstock_dir_with_recipe: Path, rich_recipe: Path) -> None:
(feedstock_dir_with_recipe / "recipe" / "recipe.yaml").write_text(
rich_recipe.read_text(), encoding="utf8"
)

rattler_metadata = MetaData(feedstock_dir_with_recipe)

assert rattler_metadata.name() == "rich"
assert rattler_metadata.version() == "13.4.2"


def test_metadata_for_multiple_output(feedstock_dir_with_recipe: Path, mamba_recipe: Path) -> None:
(feedstock_dir_with_recipe / "recipe" / "recipe.yaml").write_text(
mamba_recipe.read_text(), encoding="utf8"
)

rattler_metadata = MetaData(feedstock_dir_with_recipe)

assert rattler_metadata.name() == "mamba-split"
assert rattler_metadata.version() == "1.5.8"

0 comments on commit 2dba8d5

Please sign in to comment.