Repository: conda-forge/conda-smithy

Issue:
ENH: correctly rerender CI configs with new stdlib jinja-function
This is in the context of https://github.com/regro/cf-scripts/pull/2135 (see links therein).

I tried rendering a recipe with `- {{ stdlib("c") }}`, and unsurprisingly it failed. Adding the following patch 
```diff
--- a/conda_smithy/utils.py
+++ b/conda_smithy/utils.py
@@ -93,6 +93,7 @@ def render_meta_yaml(text):
     env.globals.update(
         dict(
             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",
```
eliminates the failure (edit: this turned into #1846), but while trying to write tests for this, I got stuck with understanding where in smithy the actual content of the CBC gets generated, and more importantly, where the values are taken from.

For example, I cannot find how gcc gets inserted as the c_compiler - it's neither specified in the code, nor injected by the fixture or the test, nor does it read the global pinning AFAICT.

I tried to follow what's happening in 
https://github.com/conda-forge/conda-smithy/blob/main/conda_smithy/configure_feedstock.py and others, and have followed several (apparently) false leads already, so I thought I'd ask if someone can tell me where to look.

To be clear, what I want to ensure is that a line like `- {{ stdlib("c") }}` ends up inserting the correct `c_stdlib{,_version}: ...` into the generated ci-config files (pretty much exactly like we do for the compilers), c.f. also https://github.com/conda-forge/conda-forge-pinning-feedstock/pull/5499.

CC @beckermr


Discussion:
I wonder if conda build has a default somewhere. 
Sure, that's a probable answer where "gcc" comes from. But luckily we shouldn't need conda-build (or changes to it) here - we only need to pick up settings for `c_stdlib{,_version}` from a local (or global) `conda_build_config.yaml`, and populate the CI configs correctly.

So far, even if I extend the `config_yaml` fixture to contain the information from https://github.com/conda-forge/conda-forge-pinning-feedstock/pull/5499, it doesn't get inserted into the CI yamls, and I don't know where I need to look to make that happen...
I'd check the configure feedstock code bits. There are explicit lists of variables there. I am betting this needs to be added to one of those lists. 
Try line 359 in configure_feedstock.py
> Try line 359 in configure_feedstock.py

Thanks for the hint! I had remembered something about `always_keep_keys`, but I didn't touch it because it didn't contain anything about the compilers, which was generally a good thing to look for, as compiler & stdlib work _very_ similarly.

I tried adding `c_stdlib{,_version}` to `always_keep_keys`, and indeed they then get included into the config files, but their values are empty. I think the issue is that smithy(?) does not yet know that the presence of `- {{ stdlib("c") }}` means that it has to look for `c_stdlib{,_version}` keys. I'm not sure how that's done on the compiler side either, can't find relevant code for "compiler"/"stub"/etc.
That seems like a conda build issue, not a smithy one. Smithy uses conda build to do basically all of the handling of these keys. 
Here is where conda-build sets the gcc default: https://github.com/conda/conda-build/blob/f60b5fc40f502398530e4c143118e7fbaaaa5bef/conda_build/variants.py#L76
We may need to expand the regex that looks for variables?

https://github.com/conda/conda-build/blob/f60b5fc40f502398530e4c143118e7fbaaaa5bef/conda_build/variants.py#L725
We may also need to add stdlib to the base default variant: https://github.com/conda/conda-build/blob/f60b5fc40f502398530e4c143118e7fbaaaa5bef/conda_build/variants.py#L119
cc @mbargull for comments since he knows conda-build very well.
Thanks a lot for the pointers. I was hoping we could avoid touching conda-build again, but that was hopelessly optimistic it seems. I'll try to look at things more closely and come up with a PR.
It may be possible to adjust / wrap the conda-build internals with functions in smithy, but yeah, we may need to go back to conda-builds.
The `compiler(<lang>)` -> `<lang>_compiler` -> `<package>_<target-platform>` is bolted on in a few places.
You probably just missed one (likely in `find_used_variables_in_text` which @beckermr mentioned if that one hasn't been adjusted) and tests covering output of variant selection used in smithy.
Presumably fixed with https://github.com/conda/conda-build/pull/5195 (if CI confirms the added test passes).
(Since `{{ stdlib(...) }}` is an as of yet not used feature, this might not make it into a `24.1.x` patch release but should be fixed for `conda-build=24.3` which I guess will be the next release some time next month.)
> (Since `{{ stdlib(...) }}` is an as of yet not used feature, this might not make it into a `24.1.x` patch release but should be fixed for `conda-build=24.3` which I guess will be the next release some time next month.)

I'm aware of that of course, though the "not yet used feature"  argument goes both ways - there's no risk in backporting a fix.

In any case, if conda-build decides not to backport, I'll try to make the case for a conda-forge-specific backport on the feedstock. There's large body of work backed up on this (e.g. to unblock the macOS situation, resp. fulfill our announcement to move to cos7 in the summer), so I think there would be good arguments also conda-forge-internally. And if even that fails, I'll wait for 24.3. ;-)

Resolving patch:
diff --git a/conda_smithy/configure_feedstock.py b/conda_smithy/configure_feedstock.py
--- a/conda_smithy/configure_feedstock.py
+++ b/conda_smithy/configure_feedstock.py
@@ -509,6 +509,8 @@ def _collapse_subpackage_variants(
         "macos_machine",
         "channel_sources",
         "channel_targets",
+        "c_stdlib",
+        "c_stdlib_version",
         "docker_image",
         "build_number_decrement",
         # The following keys are required for some of our aarch64 builds


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -196,6 +196,51 @@ def py_recipe(config_yaml, request):
     )
 
 
+@pytest.fixture(scope="function")
+def stdlib_recipe(config_yaml, request):
+    with open(os.path.join(config_yaml, "recipe", "meta.yaml"), "w") as fh:
+        fh.write(
+            """
+package:
+    name: stdlib-test
+    version: 1.0.0
+requirements:
+    build:
+        - {{ compiler("c") }}
+        - {{ stdlib("c") }}
+    host:
+        - zlib
+about:
+    home: home
+    """
+        )
+    with open(
+        os.path.join(config_yaml, "recipe", "stdlib_config.yaml"), "w"
+    ) as f:
+        f.write(
+            """\
+c_stdlib:
+  - sysroot                     # [linux]
+  - macosx_deployment_target    # [osx]
+  - vs                          # [win]
+c_stdlib_version:               # [unix]
+  - 2.12                        # [linux64]
+  - 2.17                        # [aarch64 or ppc64le]
+  - 10.9                        # [osx and x86_64]
+  - 11.0                        # [osx and arm64]
+"""
+        )
+    return RecipeConfigPair(
+        str(config_yaml),
+        _load_forge_config(
+            config_yaml,
+            exclusive_config_file=os.path.join(
+                config_yaml, "recipe", "stdlib_config.yaml"
+            ),
+        ),
+    )
+
+
 @pytest.fixture(scope="function")
 def upload_on_branch_recipe(config_yaml, request):
     with open(os.path.join(config_yaml, "recipe", "meta.yaml"), "w") as fh:
diff --git a/tests/test_configure_feedstock.py b/tests/test_configure_feedstock.py
--- a/tests/test_configure_feedstock.py
+++ b/tests/test_configure_feedstock.py
@@ -1,6 +1,7 @@
 import copy
 import logging
 import os
+import re
 import textwrap
 
 import pytest
@@ -210,6 +211,41 @@ def test_py_matrix_on_azure(py_recipe, jinja_env):
     assert len(os.listdir(matrix_dir)) == 6
 
 
+def test_stdlib_on_azure(stdlib_recipe, jinja_env):
+    configure_feedstock.render_azure(
+        jinja_env=jinja_env,
+        forge_config=stdlib_recipe.config,
+        forge_dir=stdlib_recipe.recipe,
+    )
+    # this configuration should be run
+    assert stdlib_recipe.config["azure"]["enabled"]
+    matrix_dir = os.path.join(stdlib_recipe.recipe, ".ci_support")
+    assert os.path.isdir(matrix_dir)
+    # find stdlib-config in generated yaml files (plus version, on unix)
+    with open(os.path.join(matrix_dir, "linux_64_.yaml")) as f:
+        linux_lines = f.readlines()
+        linux_content = "".join(linux_lines)
+    # multiline pattern to ensure we don't match other stuff accidentally
+    assert bool(re.match(r"(?s).*c_stdlib:\s*- sysroot", linux_content))
+    assert bool(
+        re.match(r"(?s).*c_stdlib_version:\s*- ['\"]?2\.\d+", linux_content)
+    )
+    with open(os.path.join(matrix_dir, "osx_64_.yaml")) as f:
+        osx_lines = f.readlines()
+        osx_content = "".join(osx_lines)
+    assert bool(
+        re.match(r"(?s).*c_stdlib:\s*- macosx_deployment_target", osx_content)
+    )
+    assert bool(
+        re.match(r"(?s).*c_stdlib_version:\s*- ['\"]?1\d\.\d+", osx_content)
+    )
+    with open(os.path.join(matrix_dir, "win_64_.yaml")) as f:
+        win_lines = f.readlines()
+        win_content = "".join(win_lines)
+    assert bool(re.match(r"(?s).*c_stdlib:\s*- vs", win_content))
+    # no stdlib-version expected on windows
+
+
 def test_upload_on_branch_azure(upload_on_branch_recipe, jinja_env):
     configure_feedstock.render_azure(
         jinja_env=jinja_env,