diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 906ce0b628..e215024101 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -107,6 +107,59 @@ def remove_constructor(cls, tag): used_vars_cache = {} +def _get_package_dependent_selectors(config: Config) -> dict[str, bool]: + """Get package-dependent selectors got get_selectors below. + + Derives selectors from the config and variants to be injected + into the Jinja environment prior to templating. + + Args: + config (Config): The config object + + Returns: + dict[str, bool]: Dictionary of selectors for Jinja + """ + defaults = variants.get_default_variant(config) + py = config.variant.get("python", defaults["python"]) + # there are times when python comes in as a tuple + if not hasattr(py, "split"): + py = py[0] + # go from "3.6 *_cython" -> "36" + # or from "3.6.9" -> "36" + py_major, py_minor, *_ = py.split(" ")[0].split(".") + py = int(f"{py_major}{py_minor}") + d = dict( + py=py, + py3k=bool(py_major == "3"), + py2k=bool(py_major == "2"), + py26=bool(py == 26), + py27=bool(py == 27), + py33=bool(py == 33), + py34=bool(py == 34), + py35=bool(py == 35), + py36=bool(py == 36), + ) + + np = config.variant.get("numpy") + if not np: + np = defaults["numpy"] + if config.verbose: + utils.get_logger(__name__).warn( + "No numpy version specified in conda_build_config.yaml. " + "Falling back to default numpy value of {}".format(defaults["numpy"]) + ) + d["np"] = int("".join(np.split(".")[:2])) + + pl = config.variant.get("perl", defaults["perl"]) + d["pl"] = pl + + lua = config.variant.get("lua", defaults["lua"]) + d["lua"] = lua + d["luajit"] = bool(lua[0] == "2") + + return d + + def get_selectors(config: Config) -> dict[str, bool]: """Aggregates selectors for use in recipe templating. @@ -152,48 +205,9 @@ def get_selectors(config: Config) -> dict[str, bool]: if arch == "32": d["x86"] = plat.endswith(("-32", "-64")) - defaults = variants.get_default_variant(config) - py = config.variant.get("python", defaults["python"]) - # there are times when python comes in as a tuple - if not hasattr(py, "split"): - py = py[0] - # go from "3.6 *_cython" -> "36" - # or from "3.6.9" -> "36" - py_major, py_minor, *_ = py.split(" ")[0].split(".") - py = int(f"{py_major}{py_minor}") - d["build_platform"] = config.build_subdir - d.update( - dict( - py=py, - py3k=bool(py_major == "3"), - py2k=bool(py_major == "2"), - py26=bool(py == 26), - py27=bool(py == 27), - py33=bool(py == 33), - py34=bool(py == 34), - py35=bool(py == 35), - py36=bool(py == 36), - ) - ) - - np = config.variant.get("numpy") - if not np: - np = defaults["numpy"] - if config.verbose: - utils.get_logger(__name__).warn( - "No numpy version specified in conda_build_config.yaml. " - "Falling back to default numpy value of {}".format(defaults["numpy"]) - ) - d["np"] = int("".join(np.split(".")[:2])) - - pl = config.variant.get("perl", defaults["perl"]) - d["pl"] = pl - - lua = config.variant.get("lua", defaults["lua"]) - d["lua"] = lua - d["luajit"] = bool(lua[0] == "2") + d.update(_get_package_dependent_selectors(config)) for feature, value in feature_list: d[feature] = value diff --git a/conda_build/variants.py b/conda_build/variants.py index d798a6e79a..35c136a12e 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -94,6 +94,13 @@ "R": "r_base", } +PACKAGE_SELECTOR_MAP = { + "python": ("py", "py3k", "py2k", "py26", "py27", "py33", "py34", "py35", "py36"), + "numpy": ("np",), + "perl": ("pl",), + "lua": ("lua", "luajit"), +} + @lru_cache(maxsize=None) def _get_default_compilers(platform, py_ver): @@ -739,14 +746,28 @@ def find_used_variables_in_text(variant, recipe_text, selectors_only=False): ] else: variant_lines = [ - line for line in recipe_lines if v in line.replace("-", "_") + line + for line in recipe_lines + if v in line.replace("-", "_") + or any(v_sel in line for v_sel in PACKAGE_SELECTOR_MAP.get(v, ())) ] if not variant_lines: continue + v_regex = re.escape(v) + # Recognize package-dependent selectors: e.g., "py>2" marks "python" as used. + v_sel_regex = "|".join( + ( + v_regex, + *(re.escape(sel) for sel in PACKAGE_SELECTOR_MAP.get(v, ())), + ) + ) v_req_regex = "[-_]".join(map(re.escape, v.split("_"))) + variant_regex = r"\{\s*(?:pin_[a-z]+\(\s*?['\"])?%s[^'\"]*?\}\}" % v_regex - selector_regex = r"^[^#\[]*?\#?\s\[[^\]]*?(?!\]]" % v_regex + selector_regex = ( + r"^[^#\[]*?\#?\s\[[^\]]*?(?!\]]" % v_sel_regex + ) conditional_regex = ( r"(?:^|[^\{])\{%\s*(?:el)?if\s*.*" + v_regex + r"\s*(?:[^%]*?)?%\}" ) diff --git a/news/5139-special-selector-variants b/news/5139-special-selector-variants new file mode 100644 index 0000000000..dc25fc09fb --- /dev/null +++ b/news/5139-special-selector-variants @@ -0,0 +1,19 @@ +### Enhancements + +* Add python (et al.) to used variants even if only referenced via special selectors. (#5139) + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/tests/test-recipes/variants/32_variant_implicit_via_special_selector/meta.yaml b/tests/test-recipes/variants/32_variant_implicit_via_special_selector/meta.yaml new file mode 100644 index 0000000000..b6362ea448 --- /dev/null +++ b/tests/test-recipes/variants/32_variant_implicit_via_special_selector/meta.yaml @@ -0,0 +1,13 @@ +package: + name: test_variant_implicit_via_special_selector + version: 1.0 + +build: + # "python" variant implicitly injected via "py" selector, but not explicit. + skip: true # [py<33] + +outputs: # [with_outputs] + - name: test_variant_implicit_via_special_selector_subpackage # [with_outputs] + requirements: # [with_outputs] + host: # [with_outputs] + - python # [with_outputs] diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 05e67b540b..213186188d 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -5,6 +5,7 @@ import os import subprocess import sys +from itertools import chain import pytest from conda import __version__ as conda_version @@ -24,7 +25,7 @@ yamlize, ) from conda_build.utils import DEFAULT_SUBDIRS -from conda_build.variants import DEFAULT_VARIANTS +from conda_build.variants import DEFAULT_VARIANTS, PACKAGE_SELECTOR_MAP from .utils import metadata_dir, metadata_path, thisdir @@ -437,7 +438,10 @@ def test_get_selectors( monkeypatch.setenv("FEATURE_NOMKL", str(nomkl)) config = Config(host_subdir=subdir) - assert get_selectors(config) == { + selectors = get_selectors(config) + for selector in chain(*PACKAGE_SELECTOR_MAP.values()): + assert selector in selectors + assert selectors == { # defaults "build_platform": context.subdir, "lua": DEFAULT_VARIANTS["lua"], diff --git a/tests/test_variants.py b/tests/test_variants.py index 89ebb67999..cdc547f955 100644 --- a/tests/test_variants.py +++ b/tests/test_variants.py @@ -14,6 +14,7 @@ from conda_build import api, exceptions from conda_build.utils import ensure_list, package_has_file from conda_build.variants import ( + PACKAGE_SELECTOR_MAP, combine_specs, dict_of_lists_to_list_of_dicts, filter_combined_spec_to_used_keys, @@ -660,6 +661,19 @@ def test_variant_subkeys_retained(): get_all_replacements(outputs[0][1].config.variant) +@pytest.mark.parametrize("with_outputs", [False, True]) +def test_variant_implicit_via_special_selector(with_outputs): + ms = api.render( + os.path.join(variants_dir, "32_variant_implicit_via_special_selector"), + variants={"python": ["2.7", "3.3", "3.4"], "with_outputs": [with_outputs]}, + finalize=False, + bypass_env_check=True, + ) + assert sorted(m.config.variant["python"] for m, _, _ in ms) == ["3.3", "3.4"] + for m, _, _ in ms: + assert m.get_used_vars().intersection(PACKAGE_SELECTOR_MAP) == {"python"} + + @pytest.mark.parametrize( "internal_defaults, low_prio_config, high_prio_config, expected", [