diff --git a/conda_build/inspect_pkg.py b/conda_build/inspect_pkg.py
index 0e33dac8b7..abd3953c9d 100644
--- a/conda_build/inspect_pkg.py
+++ b/conda_build/inspect_pkg.py
@@ -31,7 +31,7 @@
get_package_obj_files,
get_untracked_obj_files,
)
-from conda_build.os_utils.liefldd import codefile_type
+from conda_build.os_utils.liefldd import codefile_class, machofile
from conda_build.os_utils.macho import get_rpaths, human_filetype
from conda_build.utils import (
comma_join,
@@ -363,14 +363,16 @@ def inspect_objects(packages, prefix=sys.prefix, groupby="package"):
info = []
for f in obj_files:
- f_info = {}
path = join(prefix, f)
- filetype = codefile_type(path)
- if filetype == "machofile":
- f_info["filetype"] = human_filetype(path, None)
- f_info["rpath"] = ":".join(get_rpaths(path))
- f_info["filename"] = f
- info.append(f_info)
+ codefile = codefile_class(path)
+ if codefile == machofile:
+ info.append(
+ {
+ "filetype": human_filetype(path, None),
+ "rpath": ":".join(get_rpaths(path)),
+ "filename": f,
+ }
+ )
output_string += print_object_info(info, groupby)
if hasattr(output_string, "decode"):
diff --git a/conda_build/jinja_context.py b/conda_build/jinja_context.py
index 61219be134..9d507e43a6 100644
--- a/conda_build/jinja_context.py
+++ b/conda_build/jinja_context.py
@@ -494,34 +494,42 @@ def native_compiler(language, config):
return compiler
-def compiler(language, config, permit_undefined_jinja=False):
- """Support configuration of compilers. This is somewhat platform specific.
+def _target(language, config, permit_undefined_jinja=False, component="compiler"):
+ """Support configuration of compilers/stdlib. This is somewhat platform specific.
- Native compilers never list their host - it is always implied. Generally, they are
+ Native compilers/stdlib never list their host - it is always implied. Generally, they are
metapackages, pointing at a package that does specify the host. These in turn may be
metapackages, pointing at a package where the host is the same as the target (both being the
native architecture).
"""
- compiler = native_compiler(language, config)
+ if component == "compiler":
+ package_prefix = native_compiler(language, config)
+ else:
+ package_prefix = language
+
version = None
if config.variant:
target_platform = config.variant.get("target_platform", config.subdir)
- language_compiler_key = f"{language}_compiler"
- # fall back to native if language-compiler is not explicitly set in variant
- compiler = config.variant.get(language_compiler_key, compiler)
- version = config.variant.get(language_compiler_key + "_version")
+ language_key = f"{language}_{component}"
+ # fall back to native if language-key is not explicitly set in variant
+ package_prefix = config.variant.get(language_key, package_prefix)
+ version = config.variant.get(language_key + "_version")
else:
target_platform = config.subdir
- # support cross compilers. A cross-compiler package will have a name such as
+ # support cross components. A cross package will have a name such as
# gcc_target
# gcc_linux-cos6-64
- compiler = "_".join([compiler, target_platform])
+ package = f"{package_prefix}_{target_platform}"
if version:
- compiler = " ".join((compiler, version))
- compiler = ensure_valid_spec(compiler, warn=False)
- return compiler
+ package = f"{package} {version}"
+ package = ensure_valid_spec(package, warn=False)
+ return package
+
+
+# ensure we have compiler in namespace
+compiler = partial(_target, component="compiler")
def ccache(method, config, permit_undefined_jinja=False):
@@ -788,7 +796,16 @@ def context_processor(
skip_build_id=skip_build_id,
),
compiler=partial(
- compiler, config=config, permit_undefined_jinja=permit_undefined_jinja
+ _target,
+ config=config,
+ permit_undefined_jinja=permit_undefined_jinja,
+ component="compiler",
+ ),
+ stdlib=partial(
+ _target,
+ config=config,
+ permit_undefined_jinja=permit_undefined_jinja,
+ component="stdlib",
),
cdt=partial(cdt, config=config, permit_undefined_jinja=permit_undefined_jinja),
ccache=partial(
diff --git a/conda_build/os_utils/ldd.py b/conda_build/os_utils/ldd.py
index 77daf4ab10..32eea125a2 100644
--- a/conda_build/os_utils/ldd.py
+++ b/conda_build/os_utils/ldd.py
@@ -8,12 +8,7 @@
from conda_build.conda_interface import linked_data, untracked
from conda_build.os_utils.macho import otool
-from conda_build.os_utils.pyldd import (
- codefile_class,
- inspect_linkages,
- is_codefile,
- machofile,
-)
+from conda_build.os_utils.pyldd import codefile_class, inspect_linkages, machofile
LDD_RE = re.compile(r"\s*(.*?)\s*=>\s*(.*?)\s*\(.*\)")
LDD_NOT_FOUND_RE = re.compile(r"\s*(.*?)\s*=>\s*not found")
@@ -118,7 +113,7 @@ def get_package_obj_files(dist, prefix):
files = get_package_files(dist, prefix)
for f in files:
path = join(prefix, f)
- if is_codefile(path):
+ if codefile_class(path):
res.append(f)
return res
@@ -130,7 +125,7 @@ def get_untracked_obj_files(prefix):
files = untracked(prefix)
for f in files:
path = join(prefix, f)
- if is_codefile(path):
+ if codefile_class(path):
res.append(f)
return res
diff --git a/conda_build/os_utils/liefldd.py b/conda_build/os_utils/liefldd.py
index 2cf6ce92ad..26a768a4f6 100644
--- a/conda_build/os_utils/liefldd.py
+++ b/conda_build/os_utils/liefldd.py
@@ -1,9 +1,6 @@
# Copyright (C) 2014 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
-try:
- from collections.abc import Hashable
-except ImportError:
- from collections.abc import Hashable
+from __future__ import annotations
import hashlib
import json
@@ -11,34 +8,34 @@
import struct
import sys
import threading
+from collections.abc import Hashable
from fnmatch import fnmatch
from functools import partial
+from pathlib import Path
from subprocess import PIPE, Popen
+from ..deprecations import deprecated
from .external import find_executable
# lief cannot handle files it doesn't know about gracefully
# TODO :: Remove all use of pyldd
# Currently we verify the output of each against the other
-from .pyldd import codefile_type as codefile_type_pyldd
+from .pyldd import DLLfile, EXEfile, elffile, machofile
+from .pyldd import codefile_type as _codefile_type
from .pyldd import inspect_linkages as inspect_linkages_pyldd
-codefile_type = codefile_type_pyldd
-have_lief = False
try:
import lief
lief.logging.disable()
have_lief = True
-except:
- pass
+except ImportError:
+ have_lief = False
+@deprecated("3.28.0", "4.0.0", addendum="Use `isinstance(value, str)` instead.")
def is_string(s):
- try:
- return isinstance(s, basestring)
- except NameError:
- return isinstance(s, str)
+ return isinstance(s, str)
# Some functions can operate on either file names
@@ -46,17 +43,16 @@ def is_string(s):
# these are to be avoided, or if not avoided they
# should be passed a binary when possible as that
# will prevent having to parse it multiple times.
-def ensure_binary(file):
- if not is_string(file):
+def ensure_binary(file: str | os.PathLike | Path | lief.Binary) -> lief.Binary | None:
+ if isinstance(file, lief.Binary):
return file
- else:
- try:
- if not os.path.exists(file):
- return []
- return lief.parse(file)
- except:
- print(f"WARNING: liefldd: failed to ensure_binary({file})")
- return None
+ elif not Path(file).exists():
+ return None
+ try:
+ return lief.parse(str(file))
+ except BaseException:
+ print(f"WARNING: liefldd: failed to ensure_binary({file})")
+ return None
def nm(filename):
@@ -77,25 +73,57 @@ def nm(filename):
print("No symbols found")
-def codefile_type_liefldd(file, skip_symlinks=True):
- binary = ensure_binary(file)
- result = None
- if binary:
- if binary.format == lief.EXE_FORMATS.PE:
- if lief.PE.DLL_CHARACTERISTICS:
- if binary.header.characteristics & lief.PE.HEADER_CHARACTERISTICS.DLL:
- result = "DLLfile"
- else:
- result = "EXEfile"
+if have_lief:
+
+ def codefile_class(
+ path: str | os.PathLike | Path,
+ skip_symlinks: bool = False,
+ ) -> type[DLLfile | EXEfile | machofile | elffile] | None:
+ # same signature as conda.os_utils.pyldd.codefile_class
+ if not (binary := ensure_binary(path)):
+ return None
+ elif (
+ binary.format == lief.EXE_FORMATS.PE
+ and lief.PE.HEADER_CHARACTERISTICS.DLL in binary.header.characteristics_list
+ ):
+ return DLLfile
+ elif binary.format == lief.EXE_FORMATS.PE:
+ return EXEfile
elif binary.format == lief.EXE_FORMATS.MACHO:
- result = "machofile"
+ return machofile
elif binary.format == lief.EXE_FORMATS.ELF:
- result = "elffile"
- return result
-
+ return elffile
+ else:
+ return None
-if have_lief:
- codefile_type = codefile_type_liefldd
+else:
+ from .pyldd import codefile_class
+
+
+@deprecated(
+ "3.28.0",
+ "4.0.0",
+ addendum="Use `conda_build.os_utils.liefldd.codefile_class` instead.",
+)
+def codefile_type_liefldd(*args, **kwargs) -> str | None:
+ codefile = codefile_class(*args, **kwargs)
+ return codefile.__name__ if codefile else None
+
+
+deprecated.constant(
+ "3.28.0",
+ "4.0.0",
+ "codefile_type_pyldd",
+ _codefile_type,
+ addendum="Use `conda_build.os_utils.pyldd.codefile_class` instead.",
+)
+deprecated.constant(
+ "3.28.0",
+ "4.0.0",
+ "codefile_type",
+ _codefile_type,
+ addendum="Use `conda_build.os_utils.liefldd.codefile_class` instead.",
+)
def _trim_sysroot(sysroot):
@@ -111,7 +139,9 @@ def get_libraries(file):
if binary.format == lief.EXE_FORMATS.PE:
result = binary.libraries
else:
- result = [lib if is_string(lib) else lib.name for lib in binary.libraries]
+ result = [
+ lib if isinstance(lib, str) else lib.name for lib in binary.libraries
+ ]
# LIEF returns LC_ID_DYLIB name @rpath/libbz2.dylib in binary.libraries. Strip that.
binary_name = None
if binary.format == lief.EXE_FORMATS.MACHO:
@@ -505,7 +535,7 @@ def inspect_linkages_lief(
while tmp_filename:
if (
not parent_exe_dirname
- and codefile_type(tmp_filename) == "EXEfile"
+ and codefile_class(tmp_filename) == EXEfile
):
parent_exe_dirname = os.path.dirname(tmp_filename)
tmp_filename = parents_by_filename[tmp_filename]
@@ -595,7 +625,7 @@ def get_linkages(
result_pyldd = []
debug = False
if not have_lief or debug:
- if codefile_type(filename) not in ("DLLfile", "EXEfile"):
+ if codefile_class(filename) not in (DLLfile, EXEfile):
result_pyldd = inspect_linkages_pyldd(
filename,
resolve_filenames=resolve_filenames,
@@ -607,7 +637,7 @@ def get_linkages(
return result_pyldd
else:
print(
- f"WARNING: failed to get_linkages, codefile_type('{filename}')={codefile_type(filename)}"
+ f"WARNING: failed to get_linkages, codefile_class('{filename}')={codefile_class(filename)}"
)
return {}
result_lief = inspect_linkages_lief(
diff --git a/conda_build/os_utils/pyldd.py b/conda_build/os_utils/pyldd.py
index 1e1cd4e4cc..42b89711ae 100644
--- a/conda_build/os_utils/pyldd.py
+++ b/conda_build/os_utils/pyldd.py
@@ -1,5 +1,7 @@
# Copyright (C) 2014 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import annotations
+
import argparse
import glob
import logging
@@ -7,9 +9,12 @@
import re
import struct
import sys
+from pathlib import Path
from conda_build.utils import ensure_list, get_logger
+from ..deprecations import deprecated
+
logging.basicConfig(level=logging.INFO)
@@ -1028,46 +1033,60 @@ def codefile(file, arch="any", initial_rpaths_transitive=[]):
return inscrutablefile(file, list(initial_rpaths_transitive))
-def codefile_class(filename, skip_symlinks=False):
- if os.path.islink(filename):
- if skip_symlinks:
- return None
- else:
- filename = os.path.realpath(filename)
- if os.path.isdir(filename):
+def codefile_class(
+ path: str | os.PathLike | Path,
+ skip_symlinks: bool = False,
+) -> type[DLLfile | EXEfile | machofile | elffile] | None:
+ # same signature as conda.os_utils.liefldd.codefile_class
+ path = Path(path)
+ if skip_symlinks and path.is_symlink():
return None
- if filename.endswith((".dll", ".pyd")):
+ path = path.resolve()
+
+ def _get_magic_bit(path: Path) -> bytes:
+ with path.open("rb") as handle:
+ bit = handle.read(4)
+ return struct.unpack(BIG_ENDIAN + "L", bit)[0]
+
+ if path.is_dir():
+ return None
+ elif path.suffix.lower() in (".dll", ".pyd"):
return DLLfile
- if filename.endswith(".exe"):
+ elif path.suffix.lower() == ".exe":
return EXEfile
- # Java .class files share 0xCAFEBABE with Mach-O FAT_MAGIC.
- if filename.endswith(".class"):
+ elif path.suffix.lower() == ".class":
+ # Java .class files share 0xCAFEBABE with Mach-O FAT_MAGIC.
return None
- if not os.path.exists(filename) or os.path.getsize(filename) < 4:
+ elif not path.exists() or path.stat().st_size < 4:
+ return None
+ elif (magic := _get_magic_bit(path)) == ELF_HDR:
+ return elffile
+ elif magic in (FAT_MAGIC, MH_MAGIC, MH_CIGAM, MH_CIGAM_64):
+ return machofile
+ else:
return None
- with open(filename, "rb") as file:
- (magic,) = struct.unpack(BIG_ENDIAN + "L", file.read(4))
- file.seek(0)
- if magic in (FAT_MAGIC, MH_MAGIC, MH_CIGAM, MH_CIGAM_64):
- return machofile
- elif magic == ELF_HDR:
- return elffile
- return None
-def is_codefile(filename, skip_symlinks=True):
- klass = codefile_class(filename, skip_symlinks=skip_symlinks)
- if not klass:
- return False
- return True
+@deprecated(
+ "3.28.0",
+ "4.0.0",
+ addendum="Use `conda_build.os_utils.pyldd.codefile_class` instead.",
+)
+def is_codefile(path: str | os.PathLike | Path, skip_symlinks: bool = True) -> bool:
+ return bool(codefile_class(path, skip_symlinks=skip_symlinks))
-def codefile_type(filename, skip_symlinks=True):
- "Returns None, 'machofile' or 'elffile'"
- klass = codefile_class(filename, skip_symlinks=skip_symlinks)
- if not klass:
- return None
- return klass.__name__
+@deprecated(
+ "3.28.0",
+ "4.0.0",
+ addendum="Use `conda_build.os_utils.pyldd.codefile_class` instead.",
+)
+def codefile_type(
+ path: str | os.PathLike | Path,
+ skip_symlinks: bool = True,
+) -> str | None:
+ codefile = codefile_class(path, skip_symlinks=skip_symlinks)
+ return codefile.__name__ if codefile else None
def _trim_sysroot(sysroot):
diff --git a/conda_build/post.py b/conda_build/post.py
index 5a8e5b439e..5cef3beed7 100644
--- a/conda_build/post.py
+++ b/conda_build/post.py
@@ -53,18 +53,24 @@
have_lief,
set_rpath,
)
-from conda_build.os_utils.pyldd import codefile_type
+from conda_build.os_utils.pyldd import (
+ DLLfile,
+ EXEfile,
+ codefile_class,
+ elffile,
+ machofile,
+)
filetypes_for_platform = {
- "win": ("DLLfile", "EXEfile"),
- "osx": ["machofile"],
- "linux": ["elffile"],
+ "win": (DLLfile, EXEfile),
+ "osx": (machofile,),
+ "linux": (elffile,),
}
def fix_shebang(f, prefix, build_python, osx_is_app=False):
path = join(prefix, f)
- if codefile_type(path):
+ if codefile_class(path):
return
elif islink(path):
return
@@ -405,7 +411,7 @@ def osx_ch_link(path, link_dict, host_prefix, build_prefix, files):
".. seems to be linking to a compiler runtime, replacing build prefix with "
"host prefix and"
)
- if not codefile_type(link):
+ if not codefile_class(link):
sys.exit(
"Error: Compiler runtime library in build prefix not found in host prefix %s"
% link
@@ -841,7 +847,7 @@ def _collect_needed_dsos(
sysroots = list(sysroots_files.keys())[0]
for f in files:
path = join(run_prefix, f)
- if not codefile_type(path):
+ if not codefile_class(path):
continue
build_prefix = build_prefix.replace(os.sep, "/")
run_prefix = run_prefix.replace(os.sep, "/")
@@ -901,10 +907,9 @@ def _map_file_to_package(
for subdir2, _, filez in os.walk(prefix):
for file in filez:
fp = join(subdir2, file)
- dynamic_lib = (
- any(fnmatch(fp, ext) for ext in ("*.so*", "*.dylib*", "*.dll"))
- and codefile_type(fp, skip_symlinks=False) is not None
- )
+ dynamic_lib = any(
+ fnmatch(fp, ext) for ext in ("*.so*", "*.dylib*", "*.dll")
+ ) and codefile_class(fp, skip_symlinks=False)
static_lib = any(fnmatch(fp, ext) for ext in ("*.a", "*.lib"))
# Looking at all the files is very slow.
if not dynamic_lib and not static_lib:
@@ -945,7 +950,7 @@ def _map_file_to_package(
)
}
all_lib_exports[prefix][rp_po] = exports
- # Check codefile_type to filter out linker scripts.
+ # Check codefile_class to filter out linker scripts.
if dynamic_lib:
contains_dsos[prefix_owners[prefix][rp_po][0]] = True
elif static_lib:
@@ -1213,8 +1218,8 @@ def _show_linking_messages(
)
for f in files:
path = join(run_prefix, f)
- filetype = codefile_type(path)
- if not filetype or filetype not in filetypes_for_platform[subdir.split("-")[0]]:
+ codefile = codefile_class(path)
+ if codefile not in filetypes_for_platform[subdir.split("-")[0]]:
continue
warn_prelude = "WARNING ({},{})".format(pkg_name, f.replace(os.sep, "/"))
err_prelude = " ERROR ({},{})".format(pkg_name, f.replace(os.sep, "/"))
@@ -1312,15 +1317,15 @@ def check_overlinking_impl(
files_to_inspect = []
filesu = []
- for f in files:
- path = join(run_prefix, f)
- filetype = codefile_type(path)
- if filetype and filetype in filetypes_for_platform[subdir.split("-")[0]]:
- files_to_inspect.append(f)
- filesu.append(f.replace("\\", "/"))
+ for file in files:
+ path = join(run_prefix, file)
+ codefile = codefile_class(path)
+ if codefile in filetypes_for_platform[subdir.split("-")[0]]:
+ files_to_inspect.append(file)
+ filesu.append(file.replace("\\", "/"))
if not files_to_inspect:
- return dict()
+ return {}
sysroot_substitution = "$SYSROOT"
build_prefix_substitution = "$PATH"
@@ -1629,18 +1634,18 @@ def post_process_shared_lib(m, f, files, host_prefix=None):
if not host_prefix:
host_prefix = m.config.host_prefix
path = join(host_prefix, f)
- codefile_t = codefile_type(path)
- if not codefile_t or path.endswith(".debug"):
+ codefile = codefile_class(path)
+ if not codefile or path.endswith(".debug"):
return
rpaths = m.get_value("build/rpaths", ["lib"])
- if codefile_t == "elffile":
+ if codefile == elffile:
mk_relative_linux(
f,
host_prefix,
rpaths=rpaths,
method=m.get_value("build/rpaths_patcher", None),
)
- elif codefile_t == "machofile":
+ elif codefile == machofile:
if m.config.host_platform != "osx":
log = utils.get_logger(__name__)
log.warn(
@@ -1730,7 +1735,7 @@ def check_symlinks(files, prefix, croot):
# symlinks to binaries outside of the same dir don't work. RPATH stuff gets confused
# because ld.so follows symlinks in RPATHS
# If condition exists, then copy the file rather than symlink it.
- if not dirname(link_path) == dirname(real_link_path) and codefile_type(f):
+ if not dirname(link_path) == dirname(real_link_path) and codefile_class(f):
os.remove(path)
utils.copy_into(real_link_path, path)
elif real_link_path.startswith(real_build_prefix):
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 321d3f0dd0..a76998de35 100644
--- a/conda_build/utils.py
+++ b/conda_build/utils.py
@@ -102,9 +102,10 @@
FileNotFoundError = FileNotFoundError
on_win = sys.platform == "win32"
+on_mac = sys.platform == "darwin"
+on_linux = sys.platform == "linux"
codec = getpreferredencoding() or "utf-8"
-on_win = sys.platform == "win32"
root_script_dir = os.path.join(root_dir, "Scripts" if on_win else "bin")
mmap_MAP_PRIVATE = 0 if on_win else mmap.MAP_PRIVATE
mmap_PROT_READ = 0 if on_win else mmap.PROT_READ
diff --git a/docs/source/resources/compiler-tools.rst b/docs/source/resources/compiler-tools.rst
index d206d1c947..d4832b5a0c 100644
--- a/docs/source/resources/compiler-tools.rst
+++ b/docs/source/resources/compiler-tools.rst
@@ -394,6 +394,71 @@ not available. You'd need to create a metapackage ``m2w64-gcc_win-64`` to
point at the ``m2w64-gcc`` package, which does exist on the msys2 channel on
`repo.anaconda.com `_.
+Expressing the relation between compiler and its standard library
+=================================================================
+
+For most languages, certainly for "c" and for "cxx", compiling any given
+program *may* create a run-time dependence on symbols from the respective
+standard library. For example, the standard library for C on linux is generally
+``glibc``, and a core component of your operating system. Conda is not able to
+change or supersede this library (it would be too risky to try to). A similar
+situation exists on MacOS and on Windows.
+
+Compiler packages usually have two ways to deal with this dependence:
+
+* assume the package must be there (like ``glibc`` on linux).
+* always add a run-time requirement on the respective stdlib (e.g. ``libcxx``
+ on MacOS).
+
+However, even if we assume the package must be there, the information about the
+``glibc`` version is still a highly relevant piece of information, which is
+also why it is reflected in the ``__glibc``
+`virtual package `_.
+
+For example, newer packages may decide over time to increase the lowest version
+of ``glibc`` that they support. We therefore need a way to express this
+dependence in a way that conda will be able to understand, so that (in
+conjunction with the ``__glibc`` virtual package) the environment resolver will
+not consider those packages on machines whose ``glibc`` version is too old.
+
+The way to do this is to use the Jinja2 function ``{{ stdlib('c') }}``, which
+matches ``{{ compiler('c') }}`` in as many ways as possible. Let's start again
+with the ``conda_build_config.yaml``::
+
+ c_stdlib:
+ - sysroot # [linux]
+ - macosx_deployment_target # [osx]
+ c_stdlib_version:
+ - 2.17 # [linux]
+ - 10.13 # [osx]
+
+In the recipe we would then use::
+
+ requirements:
+ build:
+ - {{ compiler('c') }}
+ - {{ stdlib('c') }}
+
+This would then express that the resulting package requires ``sysroot ==2.17``
+(corresponds to ``glibc``) on linux and ``macosx_deployment_target ==10.13`` on
+MacOS in the build environment, respectively. How this translates into a
+run-time dependence can be defined in the metadata of the respective conda
+(meta-)package which represents the standard library (i.e. those defined under
+``c_stdlib`` above).
+
+In this example, ``sysroot 2.17`` would generate a run-export on
+``__glibc >=2.17`` and ``macosx_deployment_target 10.13`` would similarly
+generate ``__osx >=10.13``. This way, we enable packages to define their own
+expectations about the standard library in a unified way, and without
+implicitly depending on some global assumption about what the lower version
+on a given platform must be.
+
+In principle, this facility would make it possible to also express the
+dependence on separate stdlib implementations (like ``musl`` instead of
+``glibc``), or to remove the need to assume that a C++ compiler always needs to
+add a run-export on the C++ stdlib -- it could then be left up to packages
+themselves whether they need ``{{ stdlib('cxx') }}`` or not.
+
Anaconda compilers implicitly add RPATH pointing to the conda environment
=========================================================================
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/news/5040-codefile b/news/5040-codefile
new file mode 100644
index 0000000000..c4f85ca7cf
--- /dev/null
+++ b/news/5040-codefile
@@ -0,0 +1,21 @@
+### Enhancements
+
+*
+
+### Bug fixes
+
+*
+
+### Deprecations
+
+* Mark `conda_build.os_utils.pyldd.is_string` as pending deprecation. Use `isinstance(value, str)` instead. (#5040)
+* Mark `conda_build.os_utils.pyldd.is_codefile` as pending deprecation. Use `conda_build.os_utils.pyldd.codefile_class` instead. (#5040)
+* Mark `conda_build.os_utils.pyldd.codefile_type` as pending deprecation. Use `conda_build.os_utils.pyldd.codefile_class` instead. (#5040)
+
+### Docs
+
+*
+
+### Other
+
+*
diff --git a/tests/data/ldd/clear.elf b/tests/data/ldd/clear.elf
new file mode 100755
index 0000000000..52013aa3ee
Binary files /dev/null and b/tests/data/ldd/clear.elf differ
diff --git a/tests/data/ldd/clear.exe b/tests/data/ldd/clear.exe
new file mode 100644
index 0000000000..bd7543feba
Binary files /dev/null and b/tests/data/ldd/clear.exe differ
diff --git a/tests/data/ldd/clear.macho b/tests/data/ldd/clear.macho
new file mode 100755
index 0000000000..8de24d5608
Binary files /dev/null and b/tests/data/ldd/clear.macho differ
diff --git a/tests/data/ldd/jansi.dll b/tests/data/ldd/jansi.dll
new file mode 100755
index 0000000000..81433eef25
Binary files /dev/null and b/tests/data/ldd/jansi.dll differ
diff --git a/tests/data/ldd/uuid.pyd b/tests/data/ldd/uuid.pyd
new file mode 100644
index 0000000000..f99ad10b9b
Binary files /dev/null and b/tests/data/ldd/uuid.pyd differ
diff --git a/tests/os_utils/test_codefile.py b/tests/os_utils/test_codefile.py
new file mode 100644
index 0000000000..a3e38342da
--- /dev/null
+++ b/tests/os_utils/test_codefile.py
@@ -0,0 +1,40 @@
+# Copyright (C) 2014 Anaconda, Inc
+# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import annotations
+
+from pathlib import Path
+from typing import Callable
+
+import pytest
+
+from conda_build.os_utils.liefldd import codefile_class as liefldd_codefile_class
+from conda_build.os_utils.pyldd import DLLfile, EXEfile, elffile, machofile
+from conda_build.os_utils.pyldd import codefile_class as pyldd_codefile_class
+
+LDD = Path(__file__).parent.parent / "data" / "ldd"
+
+
+@pytest.mark.parametrize(
+ "path,expect",
+ [
+ pytest.param(__file__, None, id="Unknown"),
+ pytest.param(LDD / "jansi.dll", DLLfile, id="DLL"),
+ pytest.param(LDD / "uuid.pyd", DLLfile, id="PYD"),
+ pytest.param(LDD / "clear.exe", EXEfile, id="EXE"),
+ pytest.param(LDD / "clear.macho", machofile, id="MACHO"),
+ pytest.param(LDD / "clear.elf", elffile, id="ELF"),
+ ],
+)
+@pytest.mark.parametrize(
+ "codefile_class",
+ [
+ pytest.param(pyldd_codefile_class, id="pyldd"),
+ pytest.param(liefldd_codefile_class, id="liefldd"),
+ ],
+)
+def test_codefile_class(
+ path: str | Path,
+ expect: type[DLLfile | EXEfile | machofile | elffile] | None,
+ codefile_class: Callable,
+):
+ assert codefile_class(path) == expect
diff --git a/tests/test-recipes/metadata/_stdlib_jinja2/conda_build_config.yaml b/tests/test-recipes/metadata/_stdlib_jinja2/conda_build_config.yaml
new file mode 100644
index 0000000000..a6ac88cd33
--- /dev/null
+++ b/tests/test-recipes/metadata/_stdlib_jinja2/conda_build_config.yaml
@@ -0,0 +1,8 @@
+c_stdlib: # [unix]
+ - sysroot # [linux]
+ - macosx_deployment_target # [osx]
+c_stdlib_version: # [unix]
+ - 2.12 # [linux64]
+ - 2.17 # [aarch64 or ppc64le]
+ - 10.13 # [osx and x86_64]
+ - 11.0 # [osx and arm64]
diff --git a/tests/test-recipes/metadata/_stdlib_jinja2/meta.yaml b/tests/test-recipes/metadata/_stdlib_jinja2/meta.yaml
new file mode 100644
index 0000000000..c655aac2ca
--- /dev/null
+++ b/tests/test-recipes/metadata/_stdlib_jinja2/meta.yaml
@@ -0,0 +1,9 @@
+package:
+ name: stdlib-test
+ version: 1.0
+
+requirements:
+ host:
+ - {{ stdlib('c') }}
+ # - {{ stdlib('cxx') }}
+ # - {{ stdlib('fortran') }}
diff --git a/tests/test_metadata.py b/tests/test_metadata.py
index 37319f0de4..e122b45b4b 100644
--- a/tests/test_metadata.py
+++ b/tests/test_metadata.py
@@ -223,6 +223,33 @@ def test_compiler_metadata_cross_compiler():
)
+@pytest.mark.parametrize(
+ "platform,arch,stdlibs",
+ [
+ ("linux", "64", {"sysroot_linux-64 2.12.*"}),
+ ("linux", "aarch64", {"sysroot_linux-aarch64 2.17.*"}),
+ ("osx", "64", {"macosx_deployment_target_osx-64 10.13.*"}),
+ ("osx", "arm64", {"macosx_deployment_target_osx-arm64 11.0.*"}),
+ ],
+)
+def test_native_stdlib_metadata(
+ platform: str, arch: str, stdlibs: set[str], testing_config
+):
+ testing_config.platform = platform
+ metadata = api.render(
+ os.path.join(metadata_dir, "_stdlib_jinja2"),
+ config=testing_config,
+ variants={"target_platform": f"{platform}-{arch}"},
+ platform=platform,
+ arch=arch,
+ permit_unsatisfiable_variants=True,
+ finalize=False,
+ bypass_env_check=True,
+ python="3.11", # irrelevant
+ )[0][0]
+ assert stdlibs <= set(metadata.meta["requirements"]["host"])
+
+
def test_hash_build_id(testing_metadata):
testing_metadata.config.variant["zlib"] = "1.2"
testing_metadata.meta["requirements"]["host"] = ["zlib"]