Repository: conda/conda-build

Issue:
conda build --check fails with multiple sources?
I have a conda meta.yaml file with the following contents but `conda build --check` fails with the following error;
```
Source cache directory is: /tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/conda-bld/src_cache
Found source in cache: gcc-5.4.0_608df76dec.tar.bz2
Extracting download
Applying patch: '/tmp/conda/gcc/nostdc/stdint.patch'
Trying to apply patch as-is
INFO:conda_build.source:Trying to apply patch as-is
patching file gcc/config.gcc
Hunk #1 succeeded at 1805 (offset -29 lines).
Adding in variants from internal_defaults
INFO:conda_build.variants:Adding in variants from internal_defaults
Adding in variants from gcc/nostdc/conda_build_config.lm32.yaml
INFO:conda_build.variants:Adding in variants from gcc/nostdc/conda_build_config.lm32.yaml
Solving environment: ...working... done
Attempting to finalize metadata for gcc-lm32-elf-nostdc
INFO:conda_build.metadata:Attempting to finalize metadata for gcc-lm32-elf-nostdc
Solving environment: ...working... done
Traceback (most recent call last):
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/bin/conda-build", line 11, in <module>
    sys.exit(main())
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/cli/main_build.py", line 424, in main
    execute(sys.argv[1:])
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/cli/main_build.py", line 411, in execute
    outputs = [action(recipe, config) for recipe in args.recipe]
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/cli/main_build.py", line 411, in <listcomp>
    outputs = [action(recipe, config) for recipe in args.recipe]
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/cli/main_build.py", line 338, in check_action
    return api.check(recipe, config=config)
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/api.py", line 143, in check
    return all(m[0].check_fields() for m in metadata)
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/api.py", line 143, in <genexpr>
    return all(m[0].check_fields() for m in metadata)
  File "/tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/lib/python3.6/site-packages/conda_build/metadata.py", line 1092, in check_fields
    if key not in FIELDS[section]:
TypeError: unhashable type: 'dict'
```

The meta.yaml has sources defined in the ["Multiple source packages"](https://conda.io/docs/user-guide/tasks/build-packages/define-metadata.html#source-from-multiple-sources), see below;
```yaml
package:
  name: gcc-{{ toolchain_arch }}-elf-newlib
  version: 5.4.0

source:
  - folder: gcc
    fn: gcc-5.4.0.tar.bz2
    url: ftp://ftp.gnu.org/gnu/gcc/gcc-5.4.0/gcc-5.4.0.tar.bz2
    sha256: 608df76dec2d34de6558249d8af4cbee21eceddbcb580d666f7a5a583ca3303a
    patches:
     - stdint.patch
  - folder: newlib
    fn: newlib-2.5.0.20170228.tar.gz
    url: ftp://sourceware.org/pub/newlib/newlib-2.5.0.20170228.tar.gz
    sha256: 29923e9696b091dc2f880018e747c8cf282a557d2cdb60f760d9ec6a1f97337d
```

I added some extra debug prints and got the following;
```python
    def check_fields(self):
        for section, submeta in iteritems(self.meta):
            # anything goes in the extra section
            if section == 'extra':
                continue
            if section not in FIELDS:
                raise ValueError("unknown section: %s" % section)
            for key in submeta:
                try:
                    if key not in FIELDS[section]:
                        raise ValueError("in section %r: unknown key %r" %
                                 (section, key))
                except TypeError:
                    print("Error in section %s - %s (FIELDS: %r)" % (section, key, FIELDS))
                    raise
        return True
-----
Error in section source - {'folder': 'gcc', 'fn': 'gcc-5.4.0.tar.bz2', 'url': 'ftp://ftp.gnu.org/gnu/gcc/gcc-5.4.0/gcc-5.4.0.tar.bz2', 'sha256': '608df76dec2d34de6558249d8af4cbee21eceddbcb580d666f7a5a583ca3303a', 'patches': ['stdint.patch']} (FIELDS: {'package': {'name', 'version'}, 'source': {'path', 'git_url', 'sha256', 'hg_tag', 'hg_url', 'fn', 'git_rev', 'folder', 'svn_ignore_externals', 'svn_url', 'git_branch', 'git_depth', 'md5', 'sha1', 'url', 'svn_rev', 'patches', 'git_tag'}, 'build': {'missing_dso_whitelist', 'skip', 'preferred_env', 'detect_binary_files_with_prefix', 'binary_relocation', 'preferred_env_executable_paths', 'ignore_run_exports', 'requires_features', 'number', 'pin_depends', 'force_ignore_keys', 'entry_points', 'pre-link', 'always_include_files', 'osx_is_app', 'ignore_prefix_files', 'binary_has_prefix_files', 'track_features', 'rpaths', 'script_env', 'run_exports', 'features', 'noarch_python', 'string', 'noarch', 'no_link', 'has_prefix_files', 'msvc_compiler', 'script', 'skip_compile_pyc', 'preserve_egg_dir', 'pre-unlink', 'merge_build_host', 'provides_features', 'force_use_keys', 'include_recipe', 'post-link'}, 'outputs': {'name', 'version', 'number', 'script', 'files', 'requirements', 'build', 'test', 'type', 'about', 'script_interpreter'}, 'requirements': {'run_constrained', 'conflicts', 'host', 'run', 'build'}, 'app': {'own_environment', 'icon', 'cli_opts', 'entry', 'type', 'summary'}, 'test': {'files', 'commands', 'downstreams', 'source_files', 'requires', 'imports'}, 'about': {'license', 'license_family', 'dev_url', 'license_url', 'description', 'home', 'doc_source_url', 'license_file', 'readme', 'doc_url', 'summary'}})
```

```
     active environment : None
       user config file : /home/travis/.condarc
 populated config files : /home/travis/.condarc
          conda version : 4.5.10
    conda-build version : 3.13.0
         python version : 3.6.5.final.0
       base environment : /tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda  (writable)
           channel URLs : https://conda.anaconda.org/mithro/linux-64
                          https://conda.anaconda.org/mithro/noarch
                          https://conda.anaconda.org/timvideos/linux-64
                          https://conda.anaconda.org/timvideos/noarch
                          https://repo.anaconda.com/pkgs/main/linux-64
                          https://repo.anaconda.com/pkgs/main/noarch
                          https://repo.anaconda.com/pkgs/free/linux-64
                          https://repo.anaconda.com/pkgs/free/noarch
                          https://repo.anaconda.com/pkgs/r/linux-64
                          https://repo.anaconda.com/pkgs/r/noarch
                          https://repo.anaconda.com/pkgs/pro/linux-64
                          https://repo.anaconda.com/pkgs/pro/noarch
          package cache : /tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/pkgs
                          /home/travis/.conda/pkgs
       envs directories : /tmp/really-really-really-really-really-really-really-really-really-really-really-really-really-long-path/conda/envs
                          /home/travis/.conda/envs
               platform : linux-64
             user-agent : conda/4.5.10 requests/2.18.4 CPython/3.6.5 Linux/4.14.12-041412-generic ubuntu/14.04 glibc/2.19
                UID:GID : 2000:2000
             netrc file : None
           offline mode : False
```

Is this a bug in conda, or am I doing something wrong in my meta.yaml file?


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
@@ -331,7 +331,7 @@ def parse(data, config, path=None):
         if not res[field]:
             res[field] = {}
         # source field may be either a dictionary, or a list of dictionaries
-        if field in ('source', 'outputs'):
+        if field in OPTIONALLY_ITERABLE_FIELDS:
             if not (isinstance(res[field], dict) or (hasattr(res[field], '__iter__') and not
                         isinstance(res[field], string_types))):
                 raise RuntimeError("The %s field should be a dict or list of dicts, not "
@@ -503,6 +503,9 @@ def _str_version(package_meta):
               },
 }
 
+# Fields that may either be a dictionary or a list of dictionaries.
+OPTIONALLY_ITERABLE_FIELDS = ('source', 'outputs')
+
 
 def check_bad_chrs(s, field):
     bad_chrs = '=@#$%^&*:;"\'\\|<>?/ '
@@ -1082,16 +1085,22 @@ def get_value(self, name, default=None, autotype=True):
         return value
 
     def check_fields(self):
+        def check_field(key, section):
+            if key not in FIELDS[section]:
+                raise ValueError("in section %r: unknown key %r" %
+                                 (section, key))
         for section, submeta in iteritems(self.meta):
             # anything goes in the extra section
             if section == 'extra':
                 continue
             if section not in FIELDS:
                 raise ValueError("unknown section: %s" % section)
-            for key in submeta:
-                if key not in FIELDS[section]:
-                    raise ValueError("in section %r: unknown key %r" %
-                             (section, key))
+            for key_or_dict in submeta:
+                if section in OPTIONALLY_ITERABLE_FIELDS and isinstance(key_or_dict, dict):
+                    for key in key_or_dict.keys():
+                        check_field(key, section)
+                else:
+                    check_field(key_or_dict, section)
         return True
 
     def name(self, fail_ok=False):


Test patch:
diff --git a/tests/test_check.py b/tests/test_check.py
new file mode 100644
--- /dev/null
+++ b/tests/test_check.py
@@ -0,0 +1,9 @@
+import os
+
+from conda_build import api
+from .utils import metadata_dir
+
+
+def test_check_multiple_sources():
+    recipe = os.path.join(metadata_dir, 'multiple_sources')
+    assert api.check(recipe)