From 24656d2ace9b67eb6ddace41792c208e1e303305 Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Wed, 12 Jun 2024 15:06:41 -0400 Subject: [PATCH 01/12] Update exceptions imports/usage in metadata.py --- conda_build/metadata.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index d3ee86f214..1704abef1e 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -21,9 +21,16 @@ from conda.models.match_spec import MatchSpec from frozendict import deepfreeze -from . import exceptions, utils +from . import utils from .config import Config, get_or_merge_config from .deprecations import deprecated +from .exceptions import ( + CondaBuildException, + DependencyNeedsBuildingError, + RecipeError, + UnableToParse, + UnableToParseMissingJinja2, +) from .features import feature_list from .license_family import ensure_valid_license_family from .utils import ( @@ -356,10 +363,10 @@ def yamlize(data): jinja2 # Avoid pyflakes failure: 'jinja2' imported but unused except ImportError: - raise exceptions.UnableToParseMissingJinja2(original=e) + raise UnableToParseMissingJinja2(original=e) print("Problematic recipe:", file=sys.stderr) print(data, file=sys.stderr) - raise exceptions.UnableToParse(original=e) + raise UnableToParse(original=e) def ensure_valid_fields(meta): @@ -400,9 +407,7 @@ def _trim_None_strings(meta_dict): def ensure_valid_noarch_value(meta): build_noarch = meta.get("build", {}).get("noarch") if build_noarch and build_noarch not in NOARCH_TYPES: - raise exceptions.CondaBuildException( - f"Invalid value for noarch: {build_noarch}" - ) + raise CondaBuildException(f"Invalid value for noarch: {build_noarch}") def _get_all_dependencies(metadata, envs=("host", "build", "run")): @@ -444,7 +449,7 @@ def check_circular_dependencies( error = "Circular dependencies in recipe: \n" for pair in pairs: error += " {} <-> {}\n".format(*pair) - raise exceptions.RecipeError(error) + raise RecipeError(error) def _check_circular_dependencies( @@ -477,7 +482,7 @@ def _check_circular_dependencies( error = "Circular dependencies in recipe: \n" for pair in pairs: error += " {} <-> {}\n".format(*pair) - raise exceptions.RecipeError(error) + raise RecipeError(error) def _check_run_constrained(metadata_tuples): @@ -495,7 +500,7 @@ def _check_run_constrained(metadata_tuples): f"Reason: {exc}" ) if errors: - raise exceptions.RecipeError("\n".join(["", *errors])) + raise RecipeError("\n".join(["", *errors])) def _variants_equal(metadata, output_metadata): @@ -539,7 +544,7 @@ def ensure_matching_hashes(output_metadata): error += "Mismatching package: {} (id {}); dep: {}; consumer package: {}\n".format( *prob ) - raise exceptions.RecipeError( + raise RecipeError( "Mismatching hashes in recipe. Exact pins in dependencies " "that contribute to the hash often cause this. Can you " "change one or more exact pins to version bound constraints?\n" @@ -1124,7 +1129,7 @@ def finalize_outputs_pass( fm.name(), deepfreeze({k: fm.config.variant[k] for k in fm.get_used_vars()}), ] = (output_d, fm) - except exceptions.DependencyNeedsBuildingError as e: + except DependencyNeedsBuildingError as e: if not permit_unsatisfiable_variants: raise else: From 7cb171582469ed235e9c9a8eb1bcf5b88158236c Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Wed, 12 Jun 2024 15:08:33 -0400 Subject: [PATCH 02/12] Replace sys.exit call with CondaBuildUserError for select_lines() function --- conda_build/metadata.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 1704abef1e..e1a32b2d72 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -26,6 +26,7 @@ from .deprecations import deprecated from .exceptions import ( CondaBuildException, + CondaBuildUserError, DependencyNeedsBuildingError, RecipeError, UnableToParse, @@ -343,12 +344,12 @@ def select_lines(text: str, namespace: dict[str, Any], variants_in_place: bool) if value: lines.append(line) except Exception as e: - sys.exit( - f"Error: Invalid selector in meta.yaml line {i + 1}:\n" - f"offending line:\n" - f"{line}\n" + raise CondaBuildUserError( + f"Invalid selector in meta.yaml line {i + 1}:\n" + f"offending selector:\n" + f" [{selector}]\n" f"exception:\n" - f"{e.__class__.__name__}: {e}\n" + f" {e.__class__.__name__}: {e}\n" ) return "\n".join(lines) + "\n" From ecb9084a04d5cc49cc1c179e23687edb5709989e Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Wed, 12 Jun 2024 15:10:16 -0400 Subject: [PATCH 03/12] Add unit test for CondaBuildUserError exception in select_lines test --- tests/test_metadata.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 1b9fc34258..3d260a753e 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -15,6 +15,7 @@ from conda_build import api from conda_build.config import Config +from conda_build.exceptions import CondaBuildUserError from conda_build.metadata import ( FIELDS, OPTIONALLY_ITERABLE_FIELDS, @@ -549,3 +550,11 @@ def test_get_section(testing_metadata: MetaData): assert isinstance(section, list) else: assert isinstance(section, dict) + + +def test_select_lines_invalid(): + with pytest.raises( + CondaBuildUserError, + match=r"Invalid selector in meta\.yaml", + ): + select_lines("text # [{bad]", {}, variants_in_place=True) From 20273fb7b0420b07bbbd4c24bd1ebe67b26227ca Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Wed, 12 Jun 2024 15:19:42 -0400 Subject: [PATCH 04/12] Replace sys.exit call in _git_clean() and add unit test --- conda_build/metadata.py | 24 +++++++----------------- tests/test_metadata.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index e1a32b2d72..a70242ffec 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -773,28 +773,18 @@ def _git_clean(source_meta): and complain. """ - git_rev_tags_old = ("git_branch", "git_tag") git_rev = "git_rev" - git_rev_tags = (git_rev,) + git_rev_tags_old - - has_rev_tags = tuple(bool(source_meta.get(tag, "")) for tag in git_rev_tags) - if sum(has_rev_tags) > 1: - msg = "Error: multiple git_revs:" - msg += ", ".join( - f"{key}" for key, has in zip(git_rev_tags, has_rev_tags) if has - ) - sys.exit(msg) + keys = [key for key in (git_rev, "git_branch", "git_tag") if key in source_meta] + if not keys: + # git_branch, git_tag, nor git_rev specified, return as-is + return source_meta + elif len(keys) > 1: + raise CondaBuildUserError(f"Multiple git_revs: {', '.join(keys)}") # make a copy of the input so we have no side-effects ret_meta = source_meta.copy() - # loop over the old versions - for key, has in zip(git_rev_tags[1:], has_rev_tags[1:]): - # update if needed - if has: - ret_meta[git_rev_tags[0]] = ret_meta[key] - # and remove - ret_meta.pop(key, None) + ret_meta[git_rev] = ret_meta.pop(keys[0]) return ret_meta diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 3d260a753e..ff554eb58f 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -5,6 +5,7 @@ import os import subprocess import sys +from contextlib import nullcontext from itertools import product from typing import TYPE_CHECKING @@ -22,6 +23,7 @@ MetaData, _hash_dependencies, get_selectors, + sanitize, select_lines, yamlize, ) @@ -558,3 +560,29 @@ def test_select_lines_invalid(): match=r"Invalid selector in meta\.yaml", ): select_lines("text # [{bad]", {}, variants_in_place=True) + + +@pytest.mark.parametrize( + "keys,expected", + [ + pytest.param([], {}, id="git_tag"), + pytest.param(["git_tag"], {"git_rev": "rev"}, id="git_tag"), + pytest.param(["git_branch"], {"git_rev": "rev"}, id="git_branch"), + pytest.param(["git_rev"], {"git_rev": "rev"}, id="git_rev"), + pytest.param(["git_tag", "git_branch"], None, id="git_tag + git_branch"), + pytest.param(["git_tag", "git_rev"], None, id="git_tag + git_rev"), + pytest.param(["git_branch", "git_rev"], None, id="git_branch + git_rev"), + pytest.param( + ["git_tag", "git_branch", "git_rev"], + None, + id="git_tag + git_branch + git_rev", + ), + ], +) +def test_sanitize_source(keys: list[str], expected: dict[str, str] | None) -> None: + with pytest.raises( + CondaBuildUserError, match=r"Multiple git_revs:" + ) if expected is None else nullcontext(): + assert sanitize({"source": {key: "rev" for key in keys}}) == { + "source": expected + } From eeac2c9285183d678765c1b0d6bf895400a1957c Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Wed, 12 Jun 2024 15:21:53 -0400 Subject: [PATCH 05/12] Replace sys.exit call in check_bad_chrs() and add unit test --- conda_build/metadata.py | 15 ++++++++------- tests/test_metadata.py | 23 ++++++++++++++++++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index a70242ffec..ad48b7a301 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -797,15 +797,16 @@ def _str_version(package_meta): return package_meta -def check_bad_chrs(s, field): - bad_chrs = "=@#$%^&*:;\"'\\|<>?/ " +def check_bad_chrs(value: str, field: str) -> None: + bad_chrs = set("=@#$%^&*:;\"'\\|<>?/ ") if field in ("package/version", "build/string"): - bad_chrs += "-" + bad_chrs.add("-") if field != "package/version": - bad_chrs += "!" - for c in bad_chrs: - if c in s: - sys.exit(f"Error: bad character '{c}' in {field}: {s}") + bad_chrs.add("!") + if invalid := bad_chrs.intersection(value): + raise CondaBuildUserError( + f"Bad character(s) ({''.join(sorted(invalid))}) in {field}: {value}." + ) def get_package_version_pin(build_reqs, name): diff --git a/tests/test_metadata.py b/tests/test_metadata.py index ff554eb58f..84b6ae4ac5 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -22,6 +22,7 @@ OPTIONALLY_ITERABLE_FIELDS, MetaData, _hash_dependencies, + check_bad_chrs, get_selectors, sanitize, select_lines, @@ -581,8 +582,28 @@ def test_select_lines_invalid(): ) def test_sanitize_source(keys: list[str], expected: dict[str, str] | None) -> None: with pytest.raises( - CondaBuildUserError, match=r"Multiple git_revs:" + CondaBuildUserError, + match=r"Multiple git_revs:", ) if expected is None else nullcontext(): assert sanitize({"source": {key: "rev" for key in keys}}) == { "source": expected } + + +@pytest.mark.parametrize( + "value,field,invalid", + [ + pytest.param("good", "field", None, id="valid field"), + pytest.param("!@d&;-", "field", "!&;@", id="invalid field"), + pytest.param("good", "package/version", None, id="valid package/version"), + pytest.param("!@d&;-", "package/version", "&-;@", id="invalid package/version"), + pytest.param("good", "build/string", None, id="valid build/string"), + pytest.param("!@d&;-", "build/string", "!&-;@", id="invalid build/string"), + ], +) +def test_check_bad_chrs(value: str, field: str, invalid: str) -> None: + with pytest.raises( + CondaBuildUserError, + match=rf"Bad character\(s\) \({invalid}\) in {field}: {value}\.", + ) if invalid else nullcontext(): + check_bad_chrs(value, field) From 65846a6768d37267cddde63a844679f46dbc15ed Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Thu, 13 Jun 2024 13:39:16 -0400 Subject: [PATCH 06/12] Update import statements and remove sys.exit call from yamlize() --- conda_build/metadata.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index ad48b7a301..77a7cbbc07 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -15,6 +15,7 @@ from os.path import isfile, join from typing import TYPE_CHECKING, NamedTuple, overload +import yaml from bs4 import UnicodeDammit from conda.base.context import context from conda.gateways.disk.read import compute_sum @@ -30,7 +31,6 @@ DependencyNeedsBuildingError, RecipeError, UnableToParse, - UnableToParseMissingJinja2, ) from .features import feature_list from .license_family import ensure_valid_license_family @@ -59,13 +59,6 @@ OutputDict = dict[str, Any] OutputTuple = tuple[OutputDict, "MetaData"] -try: - import yaml -except ImportError: - sys.exit( - "Error: could not import yaml (required to read meta.yaml " - "files of conda recipes)" - ) try: Loader = yaml.CLoader @@ -358,13 +351,6 @@ def yamlize(data): try: return yaml.load(data, Loader=StringifyNumbersLoader) except yaml.error.YAMLError as e: - if "{{" in data: - try: - import jinja2 - - jinja2 # Avoid pyflakes failure: 'jinja2' imported but unused - except ImportError: - raise UnableToParseMissingJinja2(original=e) print("Problematic recipe:", file=sys.stderr) print(data, file=sys.stderr) raise UnableToParse(original=e) From b0a7125d98f87fa673c8011ccecacabed2485f9f Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Thu, 13 Jun 2024 13:42:21 -0400 Subject: [PATCH 07/12] Mark _get_env_path() for deprecation --- conda_build/metadata.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 77a7cbbc07..007bd68f6f 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -12,12 +12,12 @@ import warnings from collections import OrderedDict from functools import lru_cache -from os.path import isfile, join +from os.path import isdir, isfile, join from typing import TYPE_CHECKING, NamedTuple, overload import yaml from bs4 import UnicodeDammit -from conda.base.context import context +from conda.base.context import locate_prefix_by_name from conda.gateways.disk.read import compute_sum from conda.models.match_spec import MatchSpec from frozendict import deepfreeze @@ -54,6 +54,7 @@ ) if TYPE_CHECKING: + from pathlib import Path from typing import Any, Literal, Self OutputDict = dict[str, Any] @@ -865,20 +866,17 @@ def build_string_from_metadata(metadata): return build_str -# This really belongs in conda, and it is int conda.cli.common, -# but we don't presently have an API there. -def _get_env_path(env_name_or_path): - if not os.path.isdir(env_name_or_path): - for envs_dir in list(context.envs_dirs) + [os.getcwd()]: - path = os.path.join(envs_dir, env_name_or_path) - if os.path.isdir(path): - env_name_or_path = path - break - bootstrap_metadir = os.path.join(env_name_or_path, "conda-meta") - if not os.path.isdir(bootstrap_metadir): - print(f"Bootstrap environment '{env_name_or_path}' not found") - sys.exit(1) - return env_name_or_path +@deprecated( + "24.7", "24.9", addendum="Use `conda.base.context.locate_prefix_by_name` instead." +) +def _get_env_path( + env_name_or_path: str | os.PathLike | Path, +) -> str | os.PathLike | Path: + return ( + env_name_or_path + if isdir(env_name_or_path) + else locate_prefix_by_name(env_name_or_path) + ) def _get_dependencies_from_environment(env_name_or_path): From 2f9eea15f5efafbe2da0516ca313b4a4b640d911 Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Thu, 13 Jun 2024 13:48:25 -0400 Subject: [PATCH 08/12] Remove sys.exit call from parse_until_resolved() and add unit test --- conda_build/metadata.py | 19 +++++-------------- tests/test_metadata.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 007bd68f6f..9b3ef5e19b 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -1408,12 +1408,11 @@ def parse_until_resolved( ): """variant contains key-value mapping for additional functions and values for jinja2 variables""" - # undefined_jinja_vars is refreshed by self.parse again - undefined_jinja_vars = () # store the "final" state that we think we're in. reloading the meta.yaml file # can reset it (to True) final = self.final - # always parse again at least once. + + # always parse again at least once self.parse_again( permit_undefined_jinja=True, allow_no_other_outputs=allow_no_other_outputs, @@ -1421,6 +1420,8 @@ def parse_until_resolved( ) self.final = final + # recursively parse again so long as each iteration has fewer undefined jinja variables + undefined_jinja_vars = () while set(undefined_jinja_vars) != set(self.undefined_jinja_vars): undefined_jinja_vars = self.undefined_jinja_vars self.parse_again( @@ -1429,18 +1430,8 @@ def parse_until_resolved( bypass_env_check=bypass_env_check, ) self.final = final - if undefined_jinja_vars: - self.parse_again( - permit_undefined_jinja=False, - allow_no_other_outputs=allow_no_other_outputs, - bypass_env_check=bypass_env_check, - ) - sys.exit( - f"Undefined Jinja2 variables remain ({self.undefined_jinja_vars}). Please enable " - "source downloading and try again." - ) - # always parse again at the end, too. + # always parse again at the end without permit_undefined_jinja self.parse_again( permit_undefined_jinja=False, allow_no_other_outputs=allow_no_other_outputs, diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 84b6ae4ac5..975cf8cf4f 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -34,6 +34,8 @@ from .utils import metadata_dir, metadata_path, thisdir if TYPE_CHECKING: + from pathlib import Path + from pytest import MonkeyPatch @@ -607,3 +609,18 @@ def test_check_bad_chrs(value: str, field: str, invalid: str) -> None: match=rf"Bad character\(s\) \({invalid}\) in {field}: {value}\.", ) if invalid else nullcontext(): check_bad_chrs(value, field) + + +def test_parse_until_resolved(testing_metadata: MetaData, tmp_path: Path) -> None: + (recipe := tmp_path / (name := "meta.yaml")).write_text("{{ UNDEFINED[:2] }}") + testing_metadata._meta_path = recipe + testing_metadata._meta_name = name + + with pytest.raises( + CondaBuildUserError, + match=( + rf"Failed to render jinja template in {recipe}:\n" + r"'UNDEFINED' is undefined" + ), + ): + testing_metadata.parse_until_resolved() From f77866aac41a4a18e91c0d0523d59c6d133ab046 Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Thu, 13 Jun 2024 13:50:22 -0400 Subject: [PATCH 09/12] Remove sys.exit call from _get_contents() --- conda_build/metadata.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 9b3ef5e19b..08a80063ad 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -15,6 +15,7 @@ from os.path import isdir, isfile, join from typing import TYPE_CHECKING, NamedTuple, overload +import jinja2 import yaml from bs4 import UnicodeDammit from conda.base.context import locate_prefix_by_name @@ -1996,17 +1997,6 @@ def _get_contents( permit_undefined_jinja: If True, *any* use of undefined jinja variables will evaluate to an emtpy string, without emitting an error. """ - try: - import jinja2 - except ImportError: - print("There was an error importing jinja2.", file=sys.stderr) - print( - "Please run `conda install jinja2` to enable jinja template support", - file=sys.stderr, - ) # noqa - with open(self.meta_path) as fd: - return fd.read() - from .jinja_context import ( FilteredLoader, UndefinedNeverFail, @@ -2088,8 +2078,8 @@ def _get_contents( except jinja2.TemplateError as ex: if "'None' has not attribute" in str(ex): ex = "Failed to run jinja context function" - sys.exit( - f"Error: Failed to render jinja template in {self.meta_path}:\n{str(ex)}" + raise CondaBuildUserError( + f"Failed to render jinja template in {self.meta_path}:\n{str(ex)}" ) finally: if "CONDA_BUILD_STATE" in os.environ: From 3792dfb76b715fe9575ce27f5b994fe3482bd59c Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Thu, 13 Jun 2024 15:11:12 -0400 Subject: [PATCH 10/12] Ignore deprecation warnings in metadata.py tests, point to correct exception in test_jinja_typo in test_api_build.py --- tests/test_api_build.py | 3 ++- tests/test_metadata.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_api_build.py b/tests/test_api_build.py index 4199b9ec68..9fac286dd2 100644 --- a/tests/test_api_build.py +++ b/tests/test_api_build.py @@ -38,6 +38,7 @@ from conda_build.exceptions import ( BuildScriptException, CondaBuildException, + CondaBuildUserError, DependencyNeedsBuildingError, OverDependingError, OverLinkingError, @@ -503,7 +504,7 @@ def test_recursive_fail(testing_config): @pytest.mark.sanity def test_jinja_typo(testing_config): - with pytest.raises(SystemExit, match="GIT_DSECRIBE_TAG"): + with pytest.raises(CondaBuildUserError, match="GIT_DSECRIBE_TAG"): api.build( os.path.join(fail_dir, "source_git_jinja2_oops"), config=testing_config ) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 975cf8cf4f..972e1d210c 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -206,6 +206,7 @@ def test_clobber_section_data(testing_metadata): @pytest.mark.serial +@pytest.mark.filterwarnings("ignore", category=PendingDeprecationWarning) def test_build_bootstrap_env_by_name(testing_metadata): assert not any( "git" in pkg for pkg in testing_metadata.meta["requirements"].get("build", []) @@ -224,6 +225,7 @@ def test_build_bootstrap_env_by_name(testing_metadata): subprocess.check_call(cmd.split()) +@pytest.mark.filterwarnings("ignore", category=PendingDeprecationWarning) def test_build_bootstrap_env_by_path(testing_metadata): assert not any( "git" in pkg for pkg in testing_metadata.meta["requirements"].get("build", []) From ab8811f66d31f5c3856267cf78f394cd51dcb913 Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Fri, 14 Jun 2024 12:20:57 -0400 Subject: [PATCH 11/12] Relax match string in error assertion for Windows --- tests/test_metadata.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 972e1d210c..b8dc9df8e4 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -620,9 +620,6 @@ def test_parse_until_resolved(testing_metadata: MetaData, tmp_path: Path) -> Non with pytest.raises( CondaBuildUserError, - match=( - rf"Failed to render jinja template in {recipe}:\n" - r"'UNDEFINED' is undefined" - ), + match=("Failed to render jinja template"), ): testing_metadata.parse_until_resolved() From 9d740b3bbe181713359ba87d860621cede854134 Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Tue, 25 Jun 2024 15:06:21 -0400 Subject: [PATCH 12/12] Revert 'cleanup' code changes from #5255 --- conda_build/metadata.py | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 08a80063ad..527fc99e6a 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -15,7 +15,6 @@ from os.path import isdir, isfile, join from typing import TYPE_CHECKING, NamedTuple, overload -import jinja2 import yaml from bs4 import UnicodeDammit from conda.base.context import locate_prefix_by_name @@ -32,6 +31,7 @@ DependencyNeedsBuildingError, RecipeError, UnableToParse, + UnableToParseMissingJinja2, ) from .features import feature_list from .license_family import ensure_valid_license_family @@ -61,6 +61,13 @@ OutputDict = dict[str, Any] OutputTuple = tuple[OutputDict, "MetaData"] +try: + import yaml +except ImportError: + sys.exit( + "Error: could not import yaml (required to read meta.yaml " + "files of conda recipes)" + ) try: Loader = yaml.CLoader @@ -353,6 +360,13 @@ def yamlize(data): try: return yaml.load(data, Loader=StringifyNumbersLoader) except yaml.error.YAMLError as e: + if "{{" in data: + try: + import jinja2 + + jinja2 # Avoid pyflakes failure: 'jinja2' imported but unused + except ImportError: + raise UnableToParseMissingJinja2(original=e) print("Problematic recipe:", file=sys.stderr) print(data, file=sys.stderr) raise UnableToParse(original=e) @@ -760,9 +774,12 @@ def _git_clean(source_meta): If more than one field is used to specified, exit and complain. """ - + git_rev_tags_old = ("git_branch", "git_tag") git_rev = "git_rev" + git_rev_tags = (git_rev,) + git_rev_tags_old + has_rev_tags = tuple(bool(source_meta.get(tag, "")) for tag in git_rev_tags) + keys = [key for key in (git_rev, "git_branch", "git_tag") if key in source_meta] if not keys: # git_branch, git_tag, nor git_rev specified, return as-is @@ -772,7 +789,13 @@ def _git_clean(source_meta): # make a copy of the input so we have no side-effects ret_meta = source_meta.copy() - ret_meta[git_rev] = ret_meta.pop(keys[0]) + # loop over the old versions + for key, has in zip(git_rev_tags[1:], has_rev_tags[1:]): + # update if needed + if has: + ret_meta[git_rev_tags[0]] = ret_meta[key] + # and remove + ret_meta.pop(key, None) return ret_meta @@ -1997,6 +2020,17 @@ def _get_contents( permit_undefined_jinja: If True, *any* use of undefined jinja variables will evaluate to an emtpy string, without emitting an error. """ + try: + import jinja2 + except ImportError: + print("There was an error importing jinja2.", file=sys.stderr) + print( + "Please run `conda install jinja2` to enable jinja template support", + file=sys.stderr, + ) # noqa + with open(self.meta_path) as fd: + return fd.read() + from .jinja_context import ( FilteredLoader, UndefinedNeverFail,