diff --git a/conda_build/render.py b/conda_build/render.py index 0e40e4a98e..d26392e46d 100644 --- a/conda_build/render.py +++ b/conda_build/render.py @@ -410,31 +410,44 @@ def add_upstream_pins(m, permit_unsatisfiable_variants, exclude_pattern): permit_unsatisfiable_variants, exclude_pattern) if m.noarch or m.noarch_python: extra_run_specs = set(extra_run_specs_from_host.get('noarch', [])) + extra_run_constrained_specs = set([]) else: extra_run_specs = set(extra_run_specs_from_host.get('strong', []) + extra_run_specs_from_host.get('weak', []) + extra_run_specs_from_build.get('strong', [])) + extra_run_constrained_specs = set( + extra_run_specs_from_host.get('strong_constrains', []) + + extra_run_specs_from_host.get('weak_constrains', []) + + extra_run_specs_from_build.get('strong_constrains', []) + ) else: host_deps = [] host_unsat = [] if m.noarch or m.noarch_python: if m.build_is_host: extra_run_specs = set(extra_run_specs_from_build.get('noarch', [])) + extra_run_constrained_specs = set([]) build_deps = set(build_deps or []).update(extra_run_specs_from_build.get('noarch', [])) else: extra_run_specs = set([]) + extra_run_constrained_specs = set([]) build_deps = set(build_deps or []) else: extra_run_specs = set(extra_run_specs_from_build.get('strong', [])) + extra_run_constrained_specs = set(extra_run_specs_from_build.get('strong_constrains', [])) if m.build_is_host: extra_run_specs.update(extra_run_specs_from_build.get('weak', [])) + extra_run_constrained_specs.update(extra_run_specs_from_build.get('weak_constrains', [])) build_deps = set(build_deps or []).update(extra_run_specs_from_build.get('weak', [])) else: host_deps = set(extra_run_specs_from_build.get('strong', [])) run_deps = extra_run_specs | set(utils.ensure_list(requirements.get('run'))) + run_constrained_deps = extra_run_constrained_specs | set(utils.ensure_list(requirements.get('run_constrained'))) - for section, deps in (('build', build_deps), ('host', host_deps), ('run', run_deps)): + for section, deps in ( + ('build', build_deps), ('host', host_deps), ('run', run_deps), ('run_constrained', run_constrained_deps), + ): if deps: requirements[section] = list(deps) diff --git a/conda_build/utils.py b/conda_build/utils.py index 53b2422509..398daadc9b 100644 --- a/conda_build/utils.py +++ b/conda_build/utils.py @@ -115,6 +115,8 @@ def glob(pathname, recursive=True): "weak", "strong", "noarch", + "weak_constrains", + "strong_constrains", } PY_TMPL = """ diff --git a/docs/source/resources/define-metadata.rst b/docs/source/resources/define-metadata.rst index 43960d31be..20a669b049 100644 --- a/docs/source/resources/define-metadata.rst +++ b/docs/source/resources/define-metadata.rst @@ -747,6 +747,30 @@ pinning relative to versions present at build time: With this example, if libpng were version 1.6.34, this pinning expression would evaluate to ``>=1.6.34,<1.7``. +If build and link dependencies need to impose constraints on the run environment +but not necessarily pull in additional packages, then this can be done by +altering the :ref:`Run_constrained` entries. In addtion to ``weak``/``strong`` +``run_exports`` which add to the ``run`` requirements, ``weak_constrains`` and +``strong_constrains`` add to the ``run_constrained`` requirements. +With these, e.g., minimum versions of compatible but not required packages (like +optional plugins for the linked dependency, or certain system attributes) can be +expressed: + +.. + TODO: Replace example below with actual ones that use constrains run_exports. + +.. code-block:: yaml + + requirements: + build: + - build-tool # has a strong run_constrained export + host: + - link-dependency # has a weak run_constrained export + run: + run_constrained: + # - system-dependency >=min <-- implicitly added by build-tool + # - optional-plugin >=min <-- implicitly added by link-dependency + Note that ``run_exports`` can be specified both in the build section and on a per-output basis for split packages. diff --git a/news/gh-4125-run_exports-constains.rst b/news/gh-4125-run_exports-constains.rst new file mode 100644 index 0000000000..cfd7adff28 --- /dev/null +++ b/news/gh-4125-run_exports-constains.rst @@ -0,0 +1,25 @@ +Enhancements: +------------- + +* Add weak_constrains and strong_constrains run_exports types. + +Bug fixes: +---------- + +* + +Deprecations: +------------- + +* + +Docs: +----- + +* + +Other: +------ + +* + diff --git a/tests/test-recipes/metadata/_run_exports_constrains/meta.yaml b/tests/test-recipes/metadata/_run_exports_constrains/meta.yaml new file mode 100644 index 0000000000..63f41c31b6 --- /dev/null +++ b/tests/test-recipes/metadata/_run_exports_constrains/meta.yaml @@ -0,0 +1,29 @@ +package: + name: test_run_exports_constrains + version: 1.0 + +outputs: + - name: run_exports_constrains + build: + run_exports: + strong: + - strong_run_export + weak: + - weak_run_export + strong_constrains: + - strong_constrains_export + weak_constrains: + - weak_constrains_export + + - name: run_exports_constrains_only_weak + build: + run_exports: + weak: + - weak_run_export + weak_constrains: + - weak_constrains_export + + - name: strong_run_export + - name: weak_run_export + - name: strong_constrains_export + - name: weak_constrains_export diff --git a/tests/test_api_build.py b/tests/test_api_build.py index ce4280a08c..e8786f800c 100644 --- a/tests/test_api_build.py +++ b/tests/test_api_build.py @@ -1052,6 +1052,43 @@ def test_run_exports_noarch_python(testing_metadata, testing_config): assert 'python 3.6 with_run_exports' not in m.meta['requirements'].get('run', []) +def test_run_exports_constrains(testing_metadata, testing_config, testing_workdir): + api.build(os.path.join(metadata_dir, '_run_exports_constrains'), config=testing_config, + notest=True) + + testing_metadata.meta['requirements']['build'] = ['run_exports_constrains'] + testing_metadata.meta['requirements']['host'] = [] + api.output_yaml(testing_metadata, 'in_build/meta.yaml') + m = api.render(os.path.join(testing_workdir, 'in_build'), config=testing_config)[0][0] + reqs_set = lambda section: set(m.meta['requirements'].get(section, [])) + assert {'strong_run_export'} == reqs_set('run') + assert {'strong_constrains_export'} == reqs_set('run_constrained') + + testing_metadata.meta['requirements']['build'] = [] + testing_metadata.meta['requirements']['host'] = ['run_exports_constrains'] + api.output_yaml(testing_metadata, 'in_host/meta.yaml') + m = api.render(os.path.join(testing_workdir, 'in_host'), config=testing_config)[0][0] + reqs_set = lambda section: set(m.meta['requirements'].get(section, [])) + assert {'strong_run_export', 'weak_run_export'} == reqs_set('run') + assert {'strong_constrains_export', 'weak_constrains_export'} == reqs_set('run_constrained') + + testing_metadata.meta['requirements']['build'] = ['run_exports_constrains_only_weak'] + testing_metadata.meta['requirements']['host'] = [] + api.output_yaml(testing_metadata, 'only_weak_in_build/meta.yaml') + m = api.render(os.path.join(testing_workdir, 'only_weak_in_build'), config=testing_config)[0][0] + reqs_set = lambda section: set(m.meta['requirements'].get(section, [])) + assert set() == reqs_set('run') + assert set() == reqs_set('run_constrained') + + testing_metadata.meta['requirements']['build'] = [] + testing_metadata.meta['requirements']['host'] = ['run_exports_constrains_only_weak'] + api.output_yaml(testing_metadata, 'only_weak_in_host/meta.yaml') + m = api.render(os.path.join(testing_workdir, 'only_weak_in_host'), config=testing_config)[0][0] + reqs_set = lambda section: set(m.meta['requirements'].get(section, [])) + assert {'weak_run_export'} == reqs_set('run') + assert {'weak_constrains_export'} == reqs_set('run_constrained') + + def test_pin_subpackage_exact(testing_config): recipe = os.path.join(metadata_dir, '_pin_subpackage_exact') ms = api.render(recipe, config=testing_config)