diff --git a/conda_build/metadata.py b/conda_build/metadata.py index a28a6b7662..8bcc0cb223 100644 --- a/conda_build/metadata.py +++ b/conda_build/metadata.py @@ -23,7 +23,7 @@ from conda_build import exceptions, utils, variants, environ from conda_build.conda_interface import memoized from conda_build.features import feature_list -from conda_build.config import Config, get_or_merge_config +from conda_build.config import Config, get_or_merge_config, get_channel_urls from conda_build.utils import (ensure_list, find_recipe, expand_globs, get_installed_packages, HashableDict, insert_variant_versions) from conda_build.license_family import ensure_valid_license_family @@ -390,6 +390,7 @@ def parse(data, config, path=None): 'build/force_ignore_keys': list, 'build/merge_build_host': bool, 'build/msvc_compiler': text_type, + 'requirements/channels': list, 'requirements/build': list, 'requirements/host': list, 'requirements/run': list, @@ -505,7 +506,7 @@ def _str_version(package_meta): }, 'outputs': {'name', 'version', 'number', 'script', 'script_interpreter', 'build', 'requirements', 'test', 'about', 'files', 'type'}, - 'requirements': {'build', 'host', 'run', 'conflicts', 'run_constrained'}, + 'requirements': {'channels', 'build', 'host', 'run', 'conflicts', 'run_constrained'}, 'app': {'entry', 'icon', 'summary', 'type', 'cli_opts', 'own_environment'}, 'test': {'requires', 'commands', 'files', 'imports', 'source_files', 'downstreams'}, @@ -965,6 +966,10 @@ def parse_again(self, permit_undefined_jinja=False, allow_no_other_outputs=False if self.meta.get('build', {}).get('error_overdepending', False): self.config.error_overdepending = self.meta['build']['error_overdepending'] + additional_channels = ensure_list(self.get_value('requirements/channels') or []) + if additional_channels: + self._add_channels_to_config(additional_channels) + self.validate_features() self.ensure_no_pip_requirements() @@ -1156,6 +1161,11 @@ def build_number(self): build_int = "" return build_int + def _add_channels_to_config(self, channels): + for url in get_channel_urls({'channels': channels}): + if url not in self.config.channel_urls: + self.config.channel_urls.append(url) + def get_depends_top_and_out(self, typ): meta_requirements = ensure_list(self.get_value('requirements/' + typ, []))[:] req_names = set(req.split()[0] for req in meta_requirements if req) diff --git a/news/allow-channels-in-recipes.rst b/news/allow-channels-in-recipes.rst new file mode 100644 index 0000000000..1bcff0f67b --- /dev/null +++ b/news/allow-channels-in-recipes.rst @@ -0,0 +1,25 @@ +Enhancements: +------------- + +* Channels can now be added to recipes (at requirements > channels). + +Bug fixes: +---------- + +* + +Deprecations: +------------- + +* + +Docs: +----- + +* + +Other: +------ + +* + diff --git a/tests/test-recipes/metadata/_recipe_requiring_external_channel_in_yaml/meta.yaml b/tests/test-recipes/metadata/_recipe_requiring_external_channel_in_yaml/meta.yaml new file mode 100644 index 0000000000..ae417e66e0 --- /dev/null +++ b/tests/test-recipes/metadata/_recipe_requiring_external_channel_in_yaml/meta.yaml @@ -0,0 +1,11 @@ +package: + name: requires_external_channel + version: 1.0 + +requirements: + channels: + - conda_build_test + + build: + # package that does not exist on defaults, but only on our test channel + - conda_build_test_requirement diff --git a/tests/test_cli.py b/tests/test_cli.py index 9004b43335..5f3da86666 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -49,6 +49,23 @@ def test_build_add_channel(): main_build.execute(args) +def test_build_add_channel_from_yaml(): + """This recipe requires the conda_build_test_requirement package, which is + only on the conda_build_test channel. This verifies that specifying channels in the meta.yaml file works.""" + + args = [os.path.join(metadata_dir, "_recipe_requiring_external_channel_in_yaml")] + main_build.execute(args) + + +def test_build_duplicate_channel(): + """This recipe requires the conda_build_test_requirement package, which is + only on the conda_build_test channel. This verifies that specifying channels in the meta.yaml and using the -c argument simultaneously does not cause problems.""" + + args = ['-c', 'conda_build_test', '--no-activate', '--no-anaconda-upload', + os.path.join(metadata_dir, "_recipe_requiring_external_channel_in_yaml")] + main_build.execute(args) + + def test_build_without_channel_fails(testing_workdir): # remove the conda forge channel from the arguments and make sure that we fail. If we don't, # we probably have channels in condarc, and this is not a good test. @@ -76,6 +93,22 @@ def test_render_add_channel(): assert required_package_details[1] == '1.0', "Expected version number 1.0 on successful rendering, but got {}".format(required_package_details[1]) +def test_render_add_channel_from_yaml(): + """This recipe requires the conda_build_test_requirement package, which is + only on the conda_build_test channel. This verifies specifying channels in the meta.yaml file works for rendering.""" + with TemporaryDirectory() as tmpdir: + rendered_filename = os.path.join(tmpdir, 'out.yaml') + args = [os.path.join(metadata_dir, "_recipe_requiring_external_channel_in_yaml"), '--file', rendered_filename] + main_render.execute(args) + rendered_meta = yaml.safe_load(open(rendered_filename, 'r')) + required_package_string = [pkg for pkg in rendered_meta['requirements']['build'] if + 'conda_build_test_requirement' in pkg][0] + required_package_details = required_package_string.split(' ') + assert len(required_package_details) > 1, ("Expected version number on successful " + "rendering, but got only {}".format(required_package_details)) + assert required_package_details[1] == '1.0', "Expected version number 1.0 on successful rendering, but got {}".format(required_package_details[1]) + + def test_render_without_channel_fails(): # do make extra channel available, so the required package should not be found with TemporaryDirectory() as tmpdir: