Repository: conda/conda-build

Issue:
3.0.5 fails with numpy x.x in 'host' section
Minimal example to reproduce:
```yaml
package:
  name: foobar

requirements:
  host:
    - numpy x.x
```

This works fine when 'host' is replaced with 'build'

The problem seems to be in https://github.com/conda/conda-build/blob/3.0.5/conda_build/render.py#L66-L67 where the replacement doesn't take place when the value of variable env is 'host' . I am not sure, but perhaps it should be:
```py
if env in ['build', 'host']:
```




Discussion:

Resolving patch:
diff --git a/conda_build/environ.py b/conda_build/environ.py
--- a/conda_build/environ.py
+++ b/conda_build/environ.py
@@ -69,7 +69,8 @@ def get_lua_include_dir(config):
 
 
 @memoized
-def verify_git_repo(git_exe, git_dir, git_url, git_commits_since_tag, debug=False, expected_rev='HEAD'):
+def verify_git_repo(git_exe, git_dir, git_url, git_commits_since_tag, debug=False,
+                    expected_rev='HEAD'):
     env = os.environ.copy()
     log = utils.get_logger(__name__)
 
diff --git a/conda_build/metadata.py b/conda_build/metadata.py
--- a/conda_build/metadata.py
+++ b/conda_build/metadata.py
@@ -459,7 +459,7 @@ def build_string_from_metadata(metadata):
                 for name in ensure_list(names):
                     if ms.name == name and name in build_pkg_names:
                         # only append numpy when it is actually pinned
-                        if name == 'numpy' and (not hasattr(ms, 'version') or not ms.version):
+                        if name == 'numpy' and not metadata.numpy_xx:
                             continue
                         if metadata.noarch == name or (metadata.get_value('build/noarch_python') and
                                                     name == 'python'):
@@ -1401,6 +1401,21 @@ def extract_requirements_text(self):
             text = match.group(1) if match else ""
         return text
 
+    @property
+    def numpy_xx(self):
+        '''This is legacy syntax that we need to support for a while.  numpy x.x means
+        "pin run as build" for numpy.  It was special-cased to only numpy.'''
+        text = self.extract_requirements_text()
+        uses_xx = bool(re.search(r'numpy\s*x\.x', text))
+        if uses_xx:
+            log = utils.get_logger(__name__)
+            log.warn("Recipe at {path} uses numpy x.x.  This is deprecated as of conda-build 3.0, "
+                     "and will be removed in conda-build 4.0.  Please consider using variants with "
+                     "pin_run_as_build instead.  More info at "
+                     "https://conda.io/docs/building/variants.html#customizing-compatibility"
+                     .format(path=self.path))
+        return uses_xx
+
     @property
     def uses_subpackage(self):
         outputs = self.get_section('outputs')
diff --git a/conda_build/render.py b/conda_build/render.py
--- a/conda_build/render.py
+++ b/conda_build/render.py
@@ -63,7 +63,7 @@ def get_env_dependencies(m, env, variant, exclude_pattern=None,
     if env == 'build' and m.is_cross and m.config.build_subdir == m.config.host_subdir:
         specs.extend([ms.spec for ms in m.ms_depends('host')])
     # replace x.x with our variant's numpy version, or else conda tries to literally go get x.x
-    if env == 'build':
+    if env in ('build', 'host'):
         specs = [spec.replace(' x.x', ' {}'.format(variant.get('numpy', ""))) for spec in specs]
     subpackages = []
     dependencies = []
@@ -449,7 +449,16 @@ def distribute_variants(metadata, variants, permit_unsatisfiable_variants=False,
         if 'python' in build_reqs or 'python' in host_reqs:
             conform_dict['python'] = variant['python']
 
+        pin_run_as_build = variant.get('pin_run_as_build', {})
+        if mv.numpy_xx and 'numpy' not in pin_run_as_build:
+            pin_run_as_build['numpy'] = {'min_pin': 'x.x', 'max_pin': 'x.x'}
+
         mv.config.variants = conform_variants_to_value(mv.config.variants, conform_dict)
+        numpy_pinned_variants = []
+        for _variant in mv.config.variants:
+            _variant['pin_run_as_build'] = pin_run_as_build
+            numpy_pinned_variants.append(_variant)
+        mv.config.variants = numpy_pinned_variants
 
         if not need_reparse_in_env:
             mv.parse_until_resolved(allow_no_other_outputs=allow_no_other_outputs,


Test patch:
diff --git a/tests/test-recipes/metadata/_numpy_xx/meta.yaml b/tests/test-recipes/metadata/_numpy_xx/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx/meta.yaml
@@ -0,0 +1,12 @@
+package:
+  name: conda-build-test-numpy-build-run
+  version: 1.0
+
+requirements:
+  build:
+    - python
+    # legacy "pin run as build"
+    - numpy x.x
+  run:
+    - python
+    - numpy
diff --git a/tests/test-recipes/metadata/_numpy_xx/run_test.bat b/tests/test-recipes/metadata/_numpy_xx/run_test.bat
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx/run_test.bat
@@ -0,0 +1,5 @@
+@echo on
+conda list -p "%PREFIX%" --canonical
+if errorlevel 1 exit 1
+conda list -p "%PREFIX%" --canonical | grep "conda-build-test-numpy-build-run-1\.0-np112py..h......._0"
+if errorlevel 1 exit 1
diff --git a/tests/test-recipes/metadata/_numpy_xx/run_test.py b/tests/test-recipes/metadata/_numpy_xx/run_test.py
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx/run_test.py
@@ -0,0 +1,28 @@
+import os
+import json
+import glob
+
+
+def main():
+    prefix = os.environ['PREFIX']
+
+    info_files = glob.glob(os.path.join(prefix, 'conda-meta',
+                             'conda-build-test-numpy-build-run-1.0-np112py*0.json'))
+    assert len(info_files) == 1
+    info_file = info_files[0]
+    with open(info_file, 'r') as fh:
+        info = json.load(fh)
+
+    # numpy with no version, python with no version, python with version pin
+    assert len(info['depends']) == 2
+    depends = sorted(info['depends'])
+    # With no version
+    assert depends[0].startswith('numpy')
+    assert depends[1].startswith('python ')
+
+    import numpy
+    assert numpy.__version__[:4] == '1.12'
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test-recipes/metadata/_numpy_xx/run_test.sh b/tests/test-recipes/metadata/_numpy_xx/run_test.sh
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx/run_test.sh
@@ -0,0 +1,3 @@
+conda list -p $PREFIX --canonical
+# Test the build string. Should contain NumPy, but not the version
+conda list -p $PREFIX --canonical | grep "conda-build-test-numpy-build-run-1\.0-np112py..h......._0"
diff --git a/tests/test-recipes/metadata/_numpy_xx_host/meta.yaml b/tests/test-recipes/metadata/_numpy_xx_host/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx_host/meta.yaml
@@ -0,0 +1,11 @@
+package:
+  name: conda-build-test-numpy-build-run
+  version: 1.0
+
+requirements:
+  host:
+    - python
+    - numpy  x.x
+  run:
+    - python
+    - numpy
diff --git a/tests/test-recipes/metadata/_numpy_xx_host/run_test.bat b/tests/test-recipes/metadata/_numpy_xx_host/run_test.bat
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx_host/run_test.bat
@@ -0,0 +1,5 @@
+@echo on
+conda list -p "%PREFIX%" --canonical
+if errorlevel 1 exit 1
+conda list -p "%PREFIX%" --canonical | grep "conda-build-test-numpy-build-run-1\.0-np112py..h......._0"
+if errorlevel 1 exit 1
diff --git a/tests/test-recipes/metadata/_numpy_xx_host/run_test.py b/tests/test-recipes/metadata/_numpy_xx_host/run_test.py
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx_host/run_test.py
@@ -0,0 +1,28 @@
+import os
+import json
+import glob
+
+
+def main():
+    prefix = os.environ['PREFIX']
+
+    info_files = glob.glob(os.path.join(prefix, 'conda-meta',
+                             'conda-build-test-numpy-build-run-1.0-np112py*0.json'))
+    assert len(info_files) == 1
+    info_file = info_files[0]
+    with open(info_file, 'r') as fh:
+        info = json.load(fh)
+
+    # numpy with no version, python with no version, python with version pin
+    assert len(info['depends']) == 2
+    depends = sorted(info['depends'])
+    # With no version
+    assert depends[0].startswith('numpy')
+    assert depends[1].startswith('python ')
+
+    import numpy
+    assert numpy.__version__[:4] == '1.12'
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tests/test-recipes/metadata/_numpy_xx_host/run_test.sh b/tests/test-recipes/metadata/_numpy_xx_host/run_test.sh
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_numpy_xx_host/run_test.sh
@@ -0,0 +1,3 @@
+conda list -p $PREFIX --canonical
+# Test the build string. Should contain NumPy, but not the version
+conda list -p $PREFIX --canonical | grep "conda-build-test-numpy-build-run-1\.0-np112py..h......._0"
diff --git a/tests/test-recipes/metadata/_pin_compatible/meta.yaml b/tests/test-recipes/metadata/_pin_compatible/meta.yaml
--- a/tests/test-recipes/metadata/_pin_compatible/meta.yaml
+++ b/tests/test-recipes/metadata/_pin_compatible/meta.yaml
@@ -4,8 +4,6 @@ package:
 
 requirements:
   build:
-    - numpy  1.11.2
-    - python
+    - zlib   1.2.8
   run:
-    - {{ pin_compatible('numpy') }}
-    - python
+    - {{ pin_compatible('zlib') }}
diff --git a/tests/test_api_build.py b/tests/test_api_build.py
--- a/tests/test_api_build.py
+++ b/tests/test_api_build.py
@@ -1073,3 +1073,13 @@ def test_setup_py_data_in_env(testing_config):
     # make sure it fails with our special python logic
     with pytest.raises(subprocess.CalledProcessError):
         api.build(recipe, config=testing_config, python='3.4')
+
+
+def test_numpy_xx(testing_config):
+    recipe = os.path.join(metadata_dir, '_numpy_xx')
+    api.build(recipe, config=testing_config, numpy='1.12')
+
+
+def test_numpy_xx_host(testing_config):
+    recipe = os.path.join(metadata_dir, '_numpy_xx_host')
+    api.build(recipe, config=testing_config, numpy='1.12')
diff --git a/tests/test_api_render.py b/tests/test_api_render.py
--- a/tests/test_api_render.py
+++ b/tests/test_api_render.py
@@ -105,11 +105,7 @@ def test_output_without_jinja_does_not_download(mock_source, testing_workdir, te
 def test_pin_compatible_semver(testing_config):
     recipe_dir = os.path.join(metadata_dir, '_pin_compatible')
     metadata = api.render(recipe_dir, config=testing_config)[0][0]
-    assert 'numpy  >=1.11.2,<2.0a0' in metadata.get_value('requirements/run')
-    # not terribly important, but might be nice for continuity's sake
-    #    This is broken right now, because compound pins like we do here have never been supported
-    #    in the build string.
-    # assert 'np111' in api.get_output_file_path(metadata)
+    assert 'zlib  >=1.2.8,<2.0a0' in metadata.get_value('requirements/run')
 
 
 def test_host_entries_finalized(testing_config):