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

fix: omit also jinja functions #37

Merged
merged 13 commits into from
Aug 1, 2024
Empty file.
29 changes: 29 additions & 0 deletions src/rattler_build_conda_compat/jinja/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

from rattler_build_conda_compat.jinja.utils import _MissingUndefined


def _version_to_build_string(some_string: str | _MissingUndefined) -> str:
"""
Converts some version by removing the . character and returning only the first two elements of the version.
If piped value is undefined, it returns the undefined value as is.
"""
if isinstance(some_string, _MissingUndefined):
inner_value = f"{some_string._undefined_name} | version_to_build_string" # noqa: SLF001
return f"${{{{ {inner_value} }}}}"
# We first split the string by whitespace and take the first part
split = some_string.split()[0] if some_string.split() else some_string
# We then split the string by . and take the first two parts
parts = split.split(".")
major = parts[0] if len(parts) > 0 else ""
minor = parts[1] if len(parts) > 1 else ""
return f"{major}{minor}"


def _bool(value: str) -> bool:
return bool(value)


def _split(s: str, sep: str = " ") -> list[str]:
"""Filter that split a string by a separator"""
return s.split(sep)
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,78 @@

import jinja2
import yaml
from jinja2 import DebugUndefined

from rattler_build_conda_compat.jinja.filters import _bool, _split, _version_to_build_string
from rattler_build_conda_compat.jinja.objects import (
_stub_compatible_pin,
_stub_is_linux,
_stub_is_unix,
_stub_is_win,
_stub_match,
_stub_subpackage_pin,
_StubEnv,
)
from rattler_build_conda_compat.jinja.utils import _MissingUndefined
from rattler_build_conda_compat.loader import load_yaml


class RecipeWithContext(TypedDict, total=False):
context: dict[str, str]


class _MissingUndefined(DebugUndefined):
def __str__(self) -> str:
"""
By default, `DebugUndefined` return values in the form `{{ value }}`.
`rattler-build` has a different syntax, so we need to override this method,
and return the value in the form `${{ value }}`.
"""
return f"${super().__str__()}"


def jinja_env() -> jinja2.Environment:
"""
Create a `rattler-build` specific Jinja2 environment with modified syntax.
Target platform, build platform, and mpi are set to linux-64 by default.
"""
return jinja2.Environment(

env = jinja2.Environment(
variable_start_string="${{",
variable_end_string="}}",
trim_blocks=True,
lstrip_blocks=True,
autoescape=True,
autoescape=jinja2.select_autoescape(default_for_string=False),
undefined=_MissingUndefined,
)

env_obj = _StubEnv()

# inject rattler-build recipe functions in jinja environment
env.globals.update(
{
"compiler": lambda x: x + "_compiler_stub",
"stdlib": lambda x: x + "_stdlib_stub",
"pin_subpackage": _stub_subpackage_pin,
"pin_compatible": _stub_compatible_pin,
"cdt": lambda *args, **kwargs: "cdt_stub", # noqa: ARG005
"env": env_obj,
"match": _stub_match,
"is_unix": _stub_is_unix,
"is_win": _stub_is_win,
"is_linux": _stub_is_linux,
"unix": True,
"linux": True,
"target_platform": "linux-64",
"build_platform": "linux-64",
"mpi": "mpi",
}
)

# inject rattler-build recipe filters in jinja environment
env.filters.update(
{
"version_to_buildstring": _version_to_build_string,
"split": _split,
Copy link
Member

Choose a reason for hiding this comment

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

I actually don't know if this is implemented in Python Jinja or not. Is it missing from Python jinja?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It was missing from python jinja

"bool": _bool,
}
)
return env


def load_recipe_context(context: dict[str, str], jinja_env: jinja2.Environment) -> dict[str, str]:
"""
Load all string values from the context dictionary as Jinja2 templates.
Use linux-64 as default target_platform, build_platform, and mpi.
"""
# Process each key-value pair in the dictionary
for key, value in context.items():
Expand All @@ -56,6 +92,7 @@ def render_recipe_with_context(recipe_content: RecipeWithContext) -> dict[str, A
"""
Render the recipe using known values from context section.
Unknown values are not evaluated and are kept as it is.
Target platform, build platform, and mpi are set to linux-64 by default.

Examples:
---
Expand Down
38 changes: 38 additions & 0 deletions src/rattler_build_conda_compat/jinja/objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations


class _StubEnv:
"""A class to represent the env object used in rattler-build recipe."""

def get(self, env_var: str, default: str | None = None) -> str:
if default:
return f"""${{{{ env.get("{env_var}", default="{default}") }}}}"""

return f"""${{{{ env.get("{env_var}")}}}}"""
Copy link
Member

Choose a reason for hiding this comment

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

If I understand correctly, this just returns the jinja string as is? Fine with me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

made them all to return just the simple string


def exists(self, env_var: str) -> str:
return f"""${{{{ env.exists("{env_var}") }}}}"""


def _stub_compatible_pin(*args, **kwargs) -> str: # noqa: ARG001, ANN003, ANN002
return f"${{{{ compatible_pin {args[0]} }}}}"


def _stub_subpackage_pin(*args, **kwargs) -> str: # noqa: ARG001, ANN003, ANN002
return f"subpackage_pin {args[0]}"
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure I understand the difference. Here you return simple string, but in other functions you return ${{ .... }}.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

made them all to return just the simple string



def _stub_match(*args, **kwargs) -> str: # noqa: ARG001, ANN003, ANN002
return f"match {args[0]}"


def _stub_is_unix(platform: str) -> str:
return f"${{{{ is_unix {platform} }}}}"


def _stub_is_win(platform: str) -> str:
return f"${{{{ is_win {platform} }}}}"


def _stub_is_linux(platform: str) -> str:
return f"${{{{ is_linux {platform} }}}}"
11 changes: 11 additions & 0 deletions src/rattler_build_conda_compat/jinja/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from jinja2 import DebugUndefined


class _MissingUndefined(DebugUndefined):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Cant this error be returned by the functions to the user? In that case I think this thing shouldnt be private.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you should in that case also either not put it in the utils module or reexport it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This error is not returned to the user ( jinja is raising directly jinja2.Undefined ) and it's something specific for our error handling - that's why I would prefer to keep private

def __str__(self) -> str:
"""
By default, `DebugUndefined` return values in the form `{{ value }}`.
`rattler-build` has a different syntax, so we need to override this method,
and return the value in the form `${{ value }}`.
"""
return f"${super().__str__()}"
144 changes: 137 additions & 7 deletions tests/__snapshots__/test_jinja.ambr
Original file line number Diff line number Diff line change
@@ -1,15 +1,145 @@
# serializer version: 1
# name: test_render_recipe_with_context
'''
about:
description: '# Mamba, the Fast Cross-Platform Package Manager

${{ env.get("MY_ENV_VAR")}}

${{ env.get("MY_ENV_VAR", default="default_value") }}

${{ env.exists("MY_ENV_VAR") }}

'
homepage: https://github.com/mamba-org/mamba
license: BSD-3-Clause
license_family: BSD
license_file: LICENSE
repository: https://github.com/mamba-org/mamba
summary: A fast drop-in alternative to conda, using libsolv for dependency resolution
build:
string: ${{ blas_variant }}${{ hash }}_foo-bla
number: '2'
context:
name: foo
name_version: foo-bla
version: bla
package:
name: foo
version: bla
build_number: '2'
libmamba_version: 1.5.8
libmambapy_version: 1.5.8
mamba_version: 1.5.8
name: mamba
release: 2024.03.25
outputs:
- build:
script:
- build_mamba.sh
- ''
package:
name: libmamba
version: 1.5.8
requirements:
build:
- cxx_compiler_stub
- cmake
- ninja
- ''
host:
- libsolv >=0.7.23
- libcurl >=8.4.0
- fmt
- ''
ignore_run_exports:
by_name:
- spdlog
- python
run:
- libsolv >=0.7.23
run_exports:
- subpackage_pin libmamba
tests:
- script:
- else:
- if not exist %LIBRARY_PREFIX%\include\mamba\version.hpp (exit 1)
if: unix
then:
- test -d ${PREFIX}/include/mamba
- build:
script:
- build_mamba.sh
- ''
package:
name: libmambapy
version: 1.5.8
requirements:
build:
- cxx_compiler_stub
- cmake
- ninja
- if: build_platform != target_platform
then:
- python
- cross-python_linux-64
- pybind11
- pybind11-abi
host:
- python
- nlohmann_json
- subpackage_pin libmamba
ignore_run_exports:
by_name:
- spdlog
run:
- python
- subpackage_pin libmamba
run_exports:
- subpackage_pin libmambapy
tests:
- python:
imports:
- libmambapy
- libmambapy.bindings
- script:
- python -c "import libmambapy._version; assert libmambapy._version.__version__
== '1.5.8'"
- build:
python:
entry_points:
- mamba = mamba.mamba:main
script:
- build_mamba.sh
- ''
- ''
string: py${{ python | version_to_build_string }}h${{ hash }}_2
package:
name: mamba
version: 1.5.8
requirements:
build:
- if: build_platform != target_platform
then:
- python
- cross-python_linux-64
run:
- python
- conda >=23.9,<24
- subpackage_pin libmambapy
tests:
- python:
imports:
- mamba
- script:
- mamba --help
- python -c "import mamba._version; assert mamba._version.__version__ == '1.5.8'"
- if: linux
then:
- test -f ${PREFIX}/etc/profile.d/mamba.sh
- mamba create -n test_py2 python=2.7 --dry-run
- mamba install xtensor xsimd -c conda-forge --dry-run
- if: unix
then:
- test -f ${PREFIX}/condabin/mamba
recipe:
name: mamba-split
source:
sha256: 6ddaf4b0758eb7ca1250f427bc40c2c3ede43257a60bac54e4320a4de66759a6
url: https://github.com/mamba-org/mamba/archive/refs/tags/2024.03.25.tar.gz

'''
# ---
11 changes: 0 additions & 11 deletions tests/data/context.yaml

This file was deleted.

Loading