Repository: conda/conda-build

Issue:
should raise error when cross-compiling and recipe has only build deps
With only build deps, cross-compiling python packages ends up with empty packages, because stuff is installed only into host.  We should either kill off the build prefix (make it host, like we do for native-native), or raise an error, rather than produce empty packages.


Discussion:

Resolving patch:
diff --git a/conda_build/config.py b/conda_build/config.py
--- a/conda_build/config.py
+++ b/conda_build/config.py
@@ -80,7 +80,8 @@ def _ensure_dir(path):
             Setting('remove_work_dir', True),
             Setting('_host_platform', None),
             Setting('_host_arch', None),
-            Setting('filename_hashing', cc_conda_build.get('filename_hashing', 'true').lower() == 'true'),
+            Setting('filename_hashing', cc_conda_build.get('filename_hashing',
+                                                           'true').lower() == 'true'),
             Setting('keep_old_work', False),
             Setting('_src_cache_root', cc_conda_build.get('cache_dir')),
             Setting('copy_test_source_files', True),
@@ -123,6 +124,12 @@ def _ensure_dir(path):
                     cc_conda_build.get('run_package_verify_scripts', [])),
             Setting('run_package_verify_scripts',
                     cc_conda_build.get('run_package_verify_scripts', [])),
+
+            # Recipes that have no host section, only build, should bypass the build/host line.
+            # This is to make older recipes still work with cross-compiling.  True cross-compiling
+            # involving compilers (not just python) will still require recipe modification to have
+            # distinct host and build sections, but simple python stuff should work without.
+            Setting('build_prefix_override', False)
             ]
 
 
@@ -423,7 +430,8 @@ def build_prefix(self):
         """The temporary folder where the build environment is created.  The build env contains
         libraries that may be linked, but only if the host env is not specified.  It is placed on
         PATH."""
-        if (self.host_subdir != self.build_subdir and self.host_subdir != 'noarch'):
+        if (self.host_subdir != self.build_subdir and self.host_subdir != 'noarch' and not
+                self.build_prefix_override):
             prefix = join(self.build_folder, '_build_env')
         else:
             prefix = self.host_prefix
diff --git a/conda_build/render.py b/conda_build/render.py
--- a/conda_build/render.py
+++ b/conda_build/render.py
@@ -287,7 +287,6 @@ def finalize_metadata(m, permit_unsatisfiable_variants=False):
     build_deps, build_actions, build_unsat = get_env_dependencies(m, 'build', m.config.variant,
                                         exclude_pattern,
                                         permit_unsatisfiable_variants=permit_unsatisfiable_variants)
-    rendered_metadata = m.copy()
 
     extra_run_specs_from_build = get_upstream_pins(m, build_actions, 'build')
 
@@ -311,6 +310,7 @@ def finalize_metadata(m, permit_unsatisfiable_variants=False):
                               extra_run_specs_from_host.get('weak', []) +
                               extra_run_specs_from_build.get('strong', []))
     else:
+        m.config.build_prefix_override = not m.uses_new_style_compiler_activation
         host_deps = []
         host_unsat = None
         extra_run_specs = (extra_run_specs_from_build.get('strong', []) +
@@ -336,6 +336,7 @@ def finalize_metadata(m, permit_unsatisfiable_variants=False):
     for _env, values in (('build', build_deps), ('host', host_deps), ('run', versioned_run_deps)):
         if values:
             requirements[_env] = list({strip_channel(dep) for dep in values})
+    rendered_metadata = m.copy()
     rendered_metadata.meta['requirements'] = requirements
 
     if rendered_metadata.pin_depends == 'strict':


Test patch:
diff --git a/tests/test-recipes/metadata/_cross_prefix_elision/conda_build_config.yaml b/tests/test-recipes/metadata/_cross_prefix_elision/conda_build_config.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_cross_prefix_elision/conda_build_config.yaml
@@ -0,0 +1,4 @@
+# force cross-compiling
+target_platform:
+  - linux-64     [not linux]
+  - osx-64     [linux]
diff --git a/tests/test-recipes/metadata/_cross_prefix_elision/meta.yaml b/tests/test-recipes/metadata/_cross_prefix_elision/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_cross_prefix_elision/meta.yaml
@@ -0,0 +1,11 @@
+# for packages that list only build requirements (not host), we should treat build as host
+#      otherwise, we can end up with python in build, and then new things don't get installed, because host
+#      is where we bundle stuff from.
+
+package:
+  name: test_cross_prefix_elision
+  version: 1.0
+
+requirements:
+  build:
+    - python
diff --git a/tests/test-recipes/metadata/_cross_prefix_elision_compiler_used/conda_build_config.yaml b/tests/test-recipes/metadata/_cross_prefix_elision_compiler_used/conda_build_config.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_cross_prefix_elision_compiler_used/conda_build_config.yaml
@@ -0,0 +1,4 @@
+# force cross-compiling
+target_platform:
+  - linux-64     [not linux]
+  - osx-64     [linux]
diff --git a/tests/test-recipes/metadata/_cross_prefix_elision_compiler_used/meta.yaml b/tests/test-recipes/metadata/_cross_prefix_elision_compiler_used/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_cross_prefix_elision_compiler_used/meta.yaml
@@ -0,0 +1,14 @@
+# for packages that list only build requirements (not host), we should treat build as host
+#      otherwise, we can end up with python in build, and then new things don't get installed, because host
+#      is where we bundle stuff from.  However, when {{ compiler() }} is in the recipe, we don't do this,
+#      because people are then using advanced features and shouldn't need the training wheels, which might
+#      get in the way.
+
+package:
+  name: test_cross_prefix_elision_compiler_used
+  version: 1.0
+
+requirements:
+  build:
+    - python
+    - {{ compiler('c') }}
diff --git a/tests/test-recipes/metadata/_python_xx/run_test.py b/tests/test-recipes/metadata/_python_xx/run_test.py
--- a/tests/test-recipes/metadata/_python_xx/run_test.py
+++ b/tests/test-recipes/metadata/_python_xx/run_test.py
@@ -16,7 +16,7 @@ def main():
 
     # numpy with no version, python with no version, python with version pin
     depends = sorted(info['depends'])
-    assert depends[0].startswith('python ')
+    assert any(dep.startswith('python ') for dep in depends), depends
     assert sys.version[:3] == ("3.4")
 
 
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
@@ -10,6 +10,7 @@
 import pytest
 
 from conda_build import api, render
+from conda_build.conda_interface import subdir
 
 from .utils import metadata_dir
 
@@ -135,3 +136,16 @@ def test_pin_depends(testing_config):
     m = api.render(recipe, config=testing_config)[0][0]
     # the recipe python is not pinned, but having pin_depends set will force it to be.
     assert any(re.search('python\s+[23]\.', dep) for dep in m.meta['requirements']['run'])
+
+
+def test_cross_recipe_with_only_build_section(testing_config):
+    recipe = os.path.join(metadata_dir, '_cross_prefix_elision')
+    metadata = api.render(recipe, config=testing_config, bypass_env_check=True)[0][0]
+    assert metadata.config.host_subdir != subdir
+    assert metadata.config.build_prefix == metadata.config.host_prefix
+    assert metadata.config.build_prefix_override
+    recipe = os.path.join(metadata_dir, '_cross_prefix_elision_compiler_used')
+    metadata = api.render(recipe, config=testing_config, bypass_env_check=True)[0][0]
+    assert metadata.config.host_subdir != subdir
+    assert metadata.config.build_prefix != metadata.config.host_prefix
+    assert not metadata.config.build_prefix_override