diff --git a/.github/ISSUE_TEMPLATE/1_feature.yml b/.github/ISSUE_TEMPLATE/1_feature.yml index 0064a1c53d..f24cf7fdad 100644 --- a/.github/ISSUE_TEMPLATE/1_feature.yml +++ b/.github/ISSUE_TEMPLATE/1_feature.yml @@ -41,7 +41,7 @@ body: id: what attributes: label: What should happen? - description: What should be the user experience with the feature? Describe from a user perpective what they would do and see. + description: What should be the user experience with the feature? Describe from a user perspective what they would do and see. - type: textarea id: context attributes: diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index ed22cae254..b823a45165 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check CLA - uses: conda/actions/check-cla@v23.7.0 + uses: conda/actions/check-cla@v23.10.0 with: # [required] # A token with ability to comment, label, and modify the commit status @@ -31,6 +31,6 @@ jobs: label: cla-signed # [required] - # Token for opening singee PR in the provided `cla_repo` + # Token for opening signee PR in the provided `cla_repo` # (`pull_request: write` for fine-grained PAT; `repo` and `workflow` for classic PAT) cla_token: ${{ secrets.CLA_FORK_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1e9e46e754..371b874431 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -34,7 +34,7 @@ jobs: days-before-issue-stale: 90 days-before-issue-close: 21 steps: - - uses: conda/actions/read-yaml@v23.7.0 + - uses: conda/actions/read-yaml@v23.10.0 id: read_yaml with: path: https://raw.githubusercontent.com/conda/infra/main/.github/messages.yml diff --git a/RELEASE.md b/RELEASE.md index 45e605e9eb..d45614facc 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -392,6 +392,9 @@ To publish the release, go to the project's release page (e.g., https://github.c ## 10. Hand off to Anaconda's packaging team. +> **Note:** +> This step should NOT be done past Thursday morning EST; please start the process on a Monday, Tuesday, or Wednesday instead in order to avoid any potential debugging sessions over evenings or weekends. +
Internal process diff --git a/conda_build/build.py b/conda_build/build.py index fa62a238d3..1d66cf114f 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -75,7 +75,6 @@ env_path_backup_var_exists, get_conda_channel, get_rc_urls, - pkgs_dirs, prefix_placeholder, reset_context, root_dir, @@ -3420,18 +3419,6 @@ def test( # folder destination _extract_test_files_from_package(metadata) - # When testing a .tar.bz2 in the pkgs dir, clean_pkg_cache() will remove it. - # Prevent this. When https://github.com/conda/conda/issues/5708 gets fixed - # I think we can remove this call to clean_pkg_cache(). - in_pkg_cache = ( - not hasattr(recipedir_or_package_or_metadata, "config") - and os.path.isfile(recipedir_or_package_or_metadata) - and recipedir_or_package_or_metadata.endswith(CONDA_PACKAGE_EXTENSIONS) - and os.path.dirname(recipedir_or_package_or_metadata) in pkgs_dirs[0] - ) - if not in_pkg_cache: - environ.clean_pkg_cache(metadata.dist(), metadata.config) - copy_test_source_files(metadata, metadata.config.test_dir) # this is also copying tests/source_files from work_dir to testing workdir diff --git a/conda_build/conda_interface.py b/conda_build/conda_interface.py index 25ecc9cef9..dba4e4b1a7 100644 --- a/conda_build/conda_interface.py +++ b/conda_build/conda_interface.py @@ -74,10 +74,19 @@ win_path_to_unix, ) from conda.models.channel import get_conda_build_local_url # noqa: F401 -from conda.models.dist import Dist, IndexRecord # noqa: F401 +from conda.models.dist import Dist # noqa: F401 +from conda.models.records import PackageRecord from .deprecations import deprecated +deprecated.constant( + "3.28.0", + "4.0.0", + "IndexRecord", + PackageRecord, + addendum="Use `conda.models.records.PackageRecord` instead.", +) + # TODO: Go to references of all properties below and import them from `context` instead binstar_upload = context.binstar_upload default_python = context.default_python diff --git a/conda_build/environ.py b/conda_build/environ.py index 7ef1b2a33d..5afcf93c4d 100644 --- a/conda_build/environ.py +++ b/conda_build/environ.py @@ -41,6 +41,7 @@ reset_context, root_dir, ) +from .deprecations import deprecated # these are things that we provide env vars for more explicitly. This list disables the # pass-through of variant values to env vars for these keys. @@ -1214,6 +1215,7 @@ def remove_existing_packages(dirs, fns, config): utils.rm_rf(entry) +@deprecated("3.28.0", "4.0.0") def clean_pkg_cache(dist, config): locks = [] diff --git a/conda_build/metadata.py b/conda_build/metadata.py index 0681bcf90c..d2d87912bf 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -21,6 +21,7 @@ from conda_build.features import feature_list from conda_build.license_family import ensure_valid_license_family from conda_build.utils import ( + DEFAULT_SUBDIRS, HashableDict, ensure_list, expand_globs, @@ -29,7 +30,7 @@ insert_variant_versions, ) -from .conda_interface import MatchSpec, envs_dirs, md5_file, non_x86_linux_machines +from .conda_interface import MatchSpec, envs_dirs, md5_file try: import yaml @@ -121,25 +122,36 @@ def get_selectors(config: Config) -> dict[str, bool]: # Remember to update the docs of any of this changes plat = config.host_subdir d = dict( - linux=plat.startswith("linux-"), linux32=bool(plat == "linux-32"), linux64=bool(plat == "linux-64"), - emscripten=plat.startswith("emscripten-"), - wasi=plat.startswith("wasi-"), arm=plat.startswith("linux-arm"), - osx=plat.startswith("osx-"), unix=plat.startswith(("linux-", "osx-", "emscripten-")), - win=plat.startswith("win-"), win32=bool(plat == "win-32"), win64=bool(plat == "win-64"), - x86=plat.endswith(("-32", "-64")), - x86_64=plat.endswith("-64"), - wasm32=bool(plat.endswith("-wasm32")), os=os, environ=os.environ, nomkl=bool(int(os.environ.get("FEATURE_NOMKL", False))), ) + # Add the current platform to the list of subdirs to enable conda-build + # to bootstrap new platforms without a new conda release. + subdirs = list(DEFAULT_SUBDIRS) + [plat] + + # filter out noarch and other weird subdirs + subdirs = [subdir for subdir in subdirs if "-" in subdir] + + subdir_oses = {subdir.split("-")[0] for subdir in subdirs} + subdir_archs = {subdir.split("-")[1] for subdir in subdirs} + + for subdir_os in subdir_oses: + d[subdir_os] = plat.startswith(f"{subdir_os}-") + + for arch in subdir_archs: + arch_full = ARCH_MAP.get(arch, arch) + d[arch_full] = plat.endswith(f"-{arch}") + 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 @@ -147,15 +159,16 @@ def get_selectors(config: Config) -> dict[str, bool]: py = py[0] # go from "3.6 *_cython" -> "36" # or from "3.6.9" -> "36" - py = int("".join(py.split(" ")[0].split(".")[:2])) + 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(30 <= py < 40), - py2k=bool(20 <= py < 30), + py3k=bool(py_major == "3"), + py2k=bool(py_major == "2"), py26=bool(py == 26), py27=bool(py == 27), py33=bool(py == 33), @@ -182,9 +195,6 @@ def get_selectors(config: Config) -> dict[str, bool]: d["lua"] = lua d["luajit"] = bool(lua[0] == "2") - for machine in non_x86_linux_machines: - d[machine] = bool(plat.endswith("-%s" % machine)) - for feature, value in feature_list: d[feature] = value d.update(os.environ) diff --git a/conda_build/render.py b/conda_build/render.py index 881898dc9d..fa428e07f6 100644 --- a/conda_build/render.py +++ b/conda_build/render.py @@ -384,7 +384,7 @@ def execute_download_actions(m, actions, env, package_subset=None, require_files with utils.LoggingContext(): pfe.execute() for pkg_dir in pkgs_dirs: - _loc = os.path.join(pkg_dir, index[pkg].fn) + _loc = os.path.join(pkg_dir, index.get(pkg, pkg).fn) if os.path.isfile(_loc): pkg_loc = _loc break diff --git a/conda_build/utils.py b/conda_build/utils.py index af5678247e..06a5c79c6d 100644 --- a/conda_build/utils.py +++ b/conda_build/utils.py @@ -67,6 +67,7 @@ from glob import glob from conda.api import PackageCacheData # noqa +from conda.base.constants import KNOWN_SUBDIRS # NOQA because it is not used in this file. from conda_build.conda_interface import rm_rf as _rm_rf # noqa @@ -104,25 +105,7 @@ mmap_PROT_READ = 0 if on_win else mmap.PROT_READ mmap_PROT_WRITE = 0 if on_win else mmap.PROT_WRITE -DEFAULT_SUBDIRS = { - "emscripten-wasm32", - "wasi-wasm32", - "linux-64", - "linux-32", - "linux-s390x", - "linux-ppc64", - "linux-ppc64le", - "linux-armv6l", - "linux-armv7l", - "linux-aarch64", - "win-64", - "win-32", - "win-arm64", - "osx-64", - "osx-arm64", - "zos-z", - "noarch", -} +DEFAULT_SUBDIRS = set(KNOWN_SUBDIRS) RUN_EXPORTS_TYPES = { "weak", diff --git a/docs/source/resources/define-metadata.rst b/docs/source/resources/define-metadata.rst index d314349b2b..c9e1ddd32b 100644 --- a/docs/source/resources/define-metadata.rst +++ b/docs/source/resources/define-metadata.rst @@ -1928,10 +1928,10 @@ variables are booleans. * - osx - True if the platform is macOS. * - arm64 - - True if the platform is macOS and the Python architecture - is arm64. + - True if the platform is either macOS or Windows and the + Python architecture is arm64. * - unix - - True if the platform is either macOS or Linux. + - True if the platform is either macOS or Linux or emscripten. * - win - True if the platform is Windows. * - win32 @@ -1965,6 +1965,11 @@ The use of the Python version selectors, `py27`, `py34`, etc. is discouraged in favor of the more general comparison operators. Additional selectors in this series will not be added to conda-build. +Note that for each subdir with OS and architecture that `conda` supports, +two preprocessing selectors are created for the OS and the architecture separately +except when the architecture is not a valid python expression (`*-32` and `*-64` +in particular). + Because the selector is any valid Python expression, complicated logic is possible: diff --git a/news/5009-use-conda-known-subdirs b/news/5009-use-conda-known-subdirs new file mode 100644 index 0000000000..a9423202f4 --- /dev/null +++ b/news/5009-use-conda-known-subdirs @@ -0,0 +1,24 @@ +### Enhancements + +* Use subdirs known to conda for selector definitions. (#5009) + This allows conda_build to support new architectures with just + a new version of conda. For new OSes, there are more information + needed for conda_build to work properly, including whether the + new OS is a UNIX-like platform, the shared library prefix, and + the binary archive format for the platform. + +### Bug fixes + +* + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/news/5031-post-conda-5708-cleanup b/news/5031-post-conda-5708-cleanup new file mode 100644 index 0000000000..f698066c97 --- /dev/null +++ b/news/5031-post-conda-5708-cleanup @@ -0,0 +1,19 @@ +### Enhancements + +* Remove unnecessary cache clearing from `conda_buidl.build.test`. (#5031) + +### Bug fixes + +* + +### Deprecations + +* Mark `conda_build.environ.clean_pkg_cache` as pending deprecation. (#5031) + +### Docs + +* + +### Other + +* diff --git a/news/5037-conda-libmamba-solver-pins b/news/5037-conda-libmamba-solver-pins new file mode 100644 index 0000000000..d4044fac0f --- /dev/null +++ b/news/5037-conda-libmamba-solver-pins @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* Fallback to solved record filename to find the downloaded tarball in `get_upstream_pins`. (#4991 via #5037) + +### Deprecations + +* + +### Docs + +* + +### Other + +* diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 2d41b594d6..e122b45b4b 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -4,11 +4,21 @@ import os import subprocess +import sys import pytest +from conda.base.context import context +from pytest import MonkeyPatch from conda_build import api -from conda_build.metadata import MetaData, _hash_dependencies, select_lines, yamlize +from conda_build.config import Config +from conda_build.metadata import ( + MetaData, + _hash_dependencies, + get_selectors, + select_lines, + yamlize, +) from conda_build.utils import DEFAULT_SUBDIRS from .utils import metadata_dir, thisdir @@ -350,3 +360,96 @@ def test_yamlize_versions(): ) assert yml == ["1.2.3", "1.2.3.4"] + + +OS_ARCH = ( + "aarch64", + "arm", + "arm64", + "armv6l", + "armv7l", + "emscripten", + "freebsd", + "linux", + "linux32", + "linux64", + "osx", + "ppc64", + "ppc64le", + "riscv64", + "s390x", + "unix", + "wasi", + "wasm32", + "win", + "win32", + "win64", + "x86", + "x86_64", + "z", + "zos", +) + + +@pytest.mark.parametrize( + ( + "subdir", # defined in conda.base.constants.KNOWN_SUBDIRS + "expected", # OS_ARCH keys expected to be True + ), + [ + ("emscripten-wasm32", {"unix", "emscripten", "wasm32"}), + ("wasi-wasm32", {"wasi", "wasm32"}), + ("freebsd-64", {"freebsd", "x86", "x86_64"}), + ("linux-32", {"unix", "linux", "linux32", "x86"}), + ("linux-64", {"unix", "linux", "linux64", "x86", "x86_64"}), + ("linux-aarch64", {"unix", "linux", "aarch64"}), + ("linux-armv6l", {"unix", "linux", "arm", "armv6l"}), + ("linux-armv7l", {"unix", "linux", "arm", "armv7l"}), + ("linux-ppc64", {"unix", "linux", "ppc64"}), + ("linux-ppc64le", {"unix", "linux", "ppc64le"}), + ("linux-riscv64", {"unix", "linux", "riscv64"}), + ("linux-s390x", {"unix", "linux", "s390x"}), + ("osx-64", {"unix", "osx", "x86", "x86_64"}), + ("osx-arm64", {"unix", "osx", "arm64"}), + ("win-32", {"win", "win32", "x86"}), + ("win-64", {"win", "win64", "x86", "x86_64"}), + ("win-arm64", {"win", "arm64"}), + ("zos-z", {"zos", "z"}), + ], +) +@pytest.mark.parametrize("nomkl", [0, 1]) +def test_get_selectors( + monkeypatch: MonkeyPatch, + subdir: str, + expected: set[str], + nomkl: int, +): + monkeypatch.setenv("FEATURE_NOMKL", str(nomkl)) + + config = Config(host_subdir=subdir) + assert get_selectors(config) == { + # defaults + "build_platform": context.subdir, + "lua": "5", # see conda_build.variants.DEFAULT_VARIANTS["lua"] + "luajit": False, # lua[0] == 2 + "np": 122, # see conda_build.variants.DEFAULT_VARIANTS["numpy"] + "os": os, + "pl": "5.26.2", # see conda_build.variants.DEFAULT_VARIANTS["perl"] + "py": int(f"{sys.version_info.major}{sys.version_info.minor}"), + "py26": sys.version_info.major == 2 and sys.version_info.minor == 6, + "py27": sys.version_info.major == 2 and sys.version_info.minor == 7, + "py2k": sys.version_info.major == 2, + "py33": sys.version_info.major == 3 and sys.version_info.minor == 3, + "py34": sys.version_info.major == 3 and sys.version_info.minor == 4, + "py35": sys.version_info.major == 3 and sys.version_info.minor == 5, + "py36": sys.version_info.major == 3 and sys.version_info.minor == 6, + "py3k": sys.version_info.major == 3, + "nomkl": bool(nomkl), + # default OS/arch values + **{key: False for key in OS_ARCH}, + # environment variables + "environ": os.environ, + **os.environ, + # override with True values + **{key: True for key in expected}, + } diff --git a/tests/test_utils.py b/tests/test_utils.py index 7423bf6931..b5536cdf6d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,5 @@ # Copyright (C) 2014 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause -import contextlib import os import subprocess import sys @@ -9,6 +8,7 @@ import filelock import pytest +from pytest import MonkeyPatch import conda_build.utils as utils from conda_build.exceptions import BuildLockError @@ -383,132 +383,112 @@ def test_get_lock(testing_workdir): assert lock1.lock_file == lock1_unnormalized.lock_file -@contextlib.contextmanager -def _generate_tmp_tree(): - # dirA - # |\- dirB - # | |\- fileA - # | \-- fileB - # \-- dirC - # |\- fileA - # \-- fileB - import shutil - import tempfile - - try: - tmp = os.path.realpath(os.path.normpath(tempfile.mkdtemp())) - - dA = os.path.join(tmp, "dirA") - dB = os.path.join(dA, "dirB") - dC = os.path.join(dA, "dirC") - for d in (dA, dB, dC): - os.mkdir(d) - - f1 = os.path.join(dB, "fileA") - f2 = os.path.join(dB, "fileB") - f3 = os.path.join(dC, "fileA") - f4 = os.path.join(dC, "fileB") - for f in (f1, f2, f3, f4): - Path(f).touch() - - yield tmp, (dA, dB, dC), (f1, f2, f3, f4) - finally: - shutil.rmtree(tmp) - - -def test_rec_glob(): - with _generate_tmp_tree() as (tmp, _, (f1, f2, f3, f4)): - assert sorted(utils.rec_glob(tmp, "fileA")) == [f1, f3] - assert sorted(utils.rec_glob(tmp, ("fileA", "fileB"), ignores="dirB")) == [ - f3, - f4, - ] - assert sorted(utils.rec_glob(tmp, "fileB", ignores=("dirC",))) == [f2] +def test_rec_glob(tmp_path: Path): + (dirA := tmp_path / "dirA").mkdir() + (dirB := tmp_path / "dirB").mkdir() + (path1 := dirA / "fileA").touch() + (path2 := dirA / "fileB").touch() + (path3 := dirB / "fileA").touch() + (path4 := dirB / "fileB").touch() -def test_find_recipe(): - with _generate_tmp_tree() as (tmp, (dA, dB, dC), (f1, f2, f3, f4)): - f5 = os.path.join(tmp, "meta.yaml") - f6 = os.path.join(dA, "meta.yml") - f7 = os.path.join(dB, "conda.yaml") - f8 = os.path.join(dC, "conda.yml") + assert {str(path1), str(path3)} == set(utils.rec_glob(tmp_path, "fileA")) + assert {str(path3), str(path4)} == set( + utils.rec_glob( + tmp_path, + ("fileA", "fileB"), + ignores="dirA", + ) + ) + assert {str(path2)} == set(utils.rec_glob(tmp_path, "fileB", ignores=["dirB"])) - # check that each of these are valid recipes - for f in (f5, f6, f7, f8): - Path(f).touch() - assert utils.find_recipe(tmp) == f - os.remove(f) +@pytest.mark.parametrize("file", ["meta.yaml", "meta.yml", "conda.yaml", "conda.yml"]) +def test_find_recipe(tmp_path: Path, file: str): + # check that each of these are valid recipes + for path in ( + tmp_path / file, + tmp_path / "dirA" / file, + tmp_path / "dirA" / "dirB" / file, + tmp_path / "dirA" / "dirC" / file, + ): + path.parent.mkdir(parents=True, exist_ok=True) + path.touch() + assert path.samefile(utils.find_recipe(tmp_path)) + path.unlink() -def test_find_recipe_relative(): - with _generate_tmp_tree() as (tmp, (dA, dB, dC), (f1, f2, f3, f4)): - f5 = os.path.join(dA, "meta.yaml") - Path(f5).touch() - # check that even when given a relative recipe path we still return - # the absolute path - saved = os.getcwd() - os.chdir(tmp) - try: - assert utils.find_recipe("dirA") == f5 - finally: - os.chdir(saved) +@pytest.mark.parametrize("file", ["meta.yaml", "meta.yml", "conda.yaml", "conda.yml"]) +def test_find_recipe_relative(tmp_path: Path, monkeypatch: MonkeyPatch, file: str): + (dirA := tmp_path / "dirA").mkdir() + (path := dirA / file).touch() + # check that even when given a relative recipe path we still return + # the absolute path + monkeypatch.chdir(tmp_path) + assert path.samefile(utils.find_recipe("dirA")) -def test_find_recipe_no_meta(): - with _generate_tmp_tree() as (tmp, _, (f1, f2, f3, f4)): - # no meta files in tmp - with pytest.raises(IOError): - utils.find_recipe(tmp) +def test_find_recipe_no_meta(tmp_path: Path): + # no recipe in tmp_path + with pytest.raises(IOError): + utils.find_recipe(tmp_path) -def test_find_recipe_file(): - with _generate_tmp_tree() as (tmp, _, (f1, f2, f3, f4)): - f5 = os.path.join(tmp, "meta.yaml") - Path(f5).touch() - # file provided is valid meta - assert utils.find_recipe(f5) == f5 +def test_find_recipe_file(tmp_path: Path): + # provided recipe is valid + (path := tmp_path / "meta.yaml").touch() + assert path.samefile(utils.find_recipe(path)) -def test_find_recipe_file_bad(): - with _generate_tmp_tree() as (tmp, _, (f1, f2, f3, f4)): - # file provided is not valid meta - with pytest.raises(IOError): - utils.find_recipe(f1) +def test_find_recipe_file_bad(tmp_path: Path): + # missing recipe is invalid + path = tmp_path / "not_a_recipe" + with pytest.raises(IOError): + utils.find_recipe(path) + # provided recipe is invalid + path.touch() + with pytest.raises(IOError): + utils.find_recipe(path) -def test_find_recipe_multipe_base(): - with _generate_tmp_tree() as (tmp, (dA, dB, dC), (f1, f2, f3, f4)): - f5 = os.path.join(tmp, "meta.yaml") - f6 = os.path.join(dB, "meta.yaml") - f7 = os.path.join(dC, "conda.yaml") - for f in (f5, f6, f7): - Path(f).touch() - # multiple meta files, use the one in base level - assert utils.find_recipe(tmp) == f5 +@pytest.mark.parametrize("file", ["meta.yaml", "meta.yml", "conda.yaml", "conda.yml"]) +def test_find_recipe_multipe_base(tmp_path: Path, file: str): + (dirA := tmp_path / "dirA").mkdir() + (dirB := dirA / "dirB").mkdir() + (dirC := dirA / "dirC").mkdir() + (path1 := tmp_path / file).touch() + (dirA / file).touch() + (dirB / file).touch() + (dirC / file).touch() -def test_find_recipe_multipe_bad(): - with _generate_tmp_tree() as (tmp, (dA, dB, dC), (f1, f2, f3, f4)): - f5 = os.path.join(dB, "meta.yaml") - f6 = os.path.join(dC, "conda.yaml") - for f in (f5, f6): - Path(f).touch() + # multiple recipe, use the one at the top level + assert path1.samefile(utils.find_recipe(tmp_path)) - # nothing in base - with pytest.raises(IOError): - utils.find_recipe(tmp) - f7 = os.path.join(tmp, "meta.yaml") - f8 = os.path.join(tmp, "conda.yaml") - for f in (f7, f8): - Path(f).touch() +@pytest.mark.parametrize("stem", ["meta", "conda"]) +def test_find_recipe_multipe_bad(tmp_path: Path, stem: str): + (dirA := tmp_path / "dirA").mkdir() + (dirB := dirA / "dirB").mkdir() + (dirC := dirA / "dirC").mkdir() - # too many in base - with pytest.raises(IOError): - utils.find_recipe(tmp) + # create multiple nested recipes at the same depth + (dirB / f"{stem}.yml").touch() + (dirC / f"{stem}.yaml").touch() + + # too many equal priority recipes found + with pytest.raises(IOError): + utils.find_recipe(tmp_path) + + # create multiple recipes at the top level + (tmp_path / f"{stem}.yml").touch() + (tmp_path / f"{stem}.yaml").touch() + + # too many recipes in the top level + with pytest.raises(IOError): + utils.find_recipe(tmp_path) class IsCondaPkgTestData(NamedTuple):