Repository: conda/conda-build

Issue:
Support conda 4.4 optional/constrained dependencies
With https://github.com/conda/conda/pull/4982, conda is ready to support constrained dependencies, and now we need to add the capability into meta.yaml.

Under the requirements section in meta.yaml, we need to add a `run_constrained` key that takes a list of specs, just like `build` and `run`.  The result should be an additional `constrains` key in `index.json`, parallel to `depends` that comes from the `run` key in meta.yaml.


Discussion:

Resolving patch:
diff --git a/conda_build/metadata.py b/conda_build/metadata.py
--- a/conda_build/metadata.py
+++ b/conda_build/metadata.py
@@ -264,6 +264,7 @@ def parse(data, config, path=None):
     'requirements/host': list,
     'requirements/run': list,
     'requirements/conflicts': list,
+    'requirements/run_constrained': list,
     'test/requires': list,
     'test/files': list,
     'test/source_files': list,
@@ -370,7 +371,7 @@ def _git_clean(source_meta):
               'pin_depends', 'include_recipe',  # pin_depends is experimental still
               'preferred_env', 'preferred_env_executable_paths',
               ],
-    'requirements': ['build', 'host', 'run', 'conflicts'],
+    'requirements': ['build', 'host', 'run', 'conflicts', 'run_constrained'],
     'app': ['entry', 'icon', 'summary', 'type', 'cli_opts',
             'own_environment'],
     'test': ['requires', 'commands', 'files', 'imports', 'source_files'],
@@ -961,6 +962,11 @@ def info_index(self):
         if preferred_env:
             d['preferred_env'] = preferred_env
 
+        # conda 4.4+ optional dependencies
+        constrains = self.get_value('requirements/run_constrained')
+        if constrains:
+            d['constrains'] = constrains
+
         if self.get_value('build/features'):
             d['features'] = ' '.join(self.get_value('build/features'))
         if self.get_value('build/track_features'):
@@ -1276,10 +1282,12 @@ def get_output_metadata(self, output):
             if hasattr(output_reqs, 'keys'):
                 build_reqs = output_reqs.get('build', []) + requirements.get('build', [])
                 run_reqs = output_reqs.get('run', [])
+                constrain_reqs = output_reqs.get('run_constrained', [])
             else:
                 output_reqs = ensure_list(output_reqs)
                 build_reqs = output_reqs + requirements.get('build', [])
                 run_reqs = output_reqs
+                constrain_reqs = []
             if 'name' in output:
                 # since we are copying reqs from the top-level package, which
                 #   can depend on subpackages, make sure that we filter out
@@ -1290,6 +1298,8 @@ def get_output_metadata(self, output):
 
             requirements['build'] = build_reqs
             requirements['run'] = run_reqs
+            if constrain_reqs:
+                requirements['run_constrained'] = constrain_reqs
             output_metadata.meta['requirements'] = requirements
             output_metadata.meta['package']['version'] = output.get('version') or self.version()
             extra = self.meta.get('extra', {})


Test patch:
diff --git a/tests/test-recipes/metadata/_run_constrained/meta.yaml b/tests/test-recipes/metadata/_run_constrained/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_run_constrained/meta.yaml
@@ -0,0 +1,7 @@
+package:
+  name: test_run_constrained
+  version: 1.0
+
+requirements:
+  run_constrained:
+    - bzip2  1.*
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
@@ -998,3 +998,12 @@ def test_only_lua_env(testing_config):
     testing_config.prefix_length = 80
     testing_config.set_build_id = False
     api.build(recipe, config=testing_config)
+
+
+def test_run_constrained_stores_constrains_info(testing_config):
+    recipe = os.path.join(metadata_dir, '_run_constrained')
+    out_file = api.build(recipe, config=testing_config)[0]
+    info_contents = json.loads(package_has_file(out_file, 'info/index.json'))
+    assert 'constrains' in info_contents
+    assert len(info_contents['constrains']) == 1
+    assert info_contents['constrains'][0] == 'bzip2  1.*'