Repository: conda/conda-build

Issue:
Failure to find osx-64 python.app during build test 
I am submitting this as a new issue, although it is clearly related to #7, because this has just become a problem for my builds (https://github.com/nexpy/nexpy/blob/master/conda-recipe/meta.yaml but without commenting out the `osx_is_app` line), although they seemed to work fine a few months ago.

<details>
 <summary>Current conda install</summary>
  <pre><code>
              platform : osx-64
          conda version : 4.3.4
       conda is private : False
      conda-env version : 4.3.4
    conda-build version : 2.1.1
         python version : 2.7.12.final.0
       requests version : 2.12.4
       root environment : /Users/rosborn/anaconda  (writable)
    default environment : /Users/rosborn/anaconda
       envs directories : /Users/rosborn/anaconda/envs
          package cache : /Users/rosborn/anaconda/pkgs
           channel URLs : https://conda.anaconda.org/conda-canary/osx-64
                          https://conda.anaconda.org/conda-canary/noarch
                          https://conda.anaconda.org/nexpy/osx-64
                          https://conda.anaconda.org/nexpy/noarch
                          https://repo.continuum.io/pkgs/free/osx-64
                          https://repo.continuum.io/pkgs/free/noarch
                          https://repo.continuum.io/pkgs/r/osx-64
                          https://repo.continuum.io/pkgs/r/noarch
                          https://repo.continuum.io/pkgs/pro/osx-64
                          https://repo.continuum.io/pkgs/pro/noarch
            config file : /Users/rosborn/.condarc
           offline mode : False
             user-agent : conda/4.3.4 requests/2.12.4 CPython/2.7.12 Darwin/15.6.0 OSX/10.11.6
                UID:GID : 938267710:660979062
  </code></pre>
</details>
<br>
The error is in running the test at the end, even though pythonw is installed in the test's bin directory.
<br>
<details>
 <summary>Error message</summary>
  <pre><code>
+ /bin/bash -x -e /Users/rosborn/anaconda/conda-bld/conda-recipe_1484588846841/test_tmp/run_test.sh
+ nexpy --help
/bin/bash: /Users/rosborn/anaconda/conda-bld/conda-recipe_1484588846841/_t_env/bin/python.app: No such file or directory
  </code></pre>
</details>
<br>
It looks to me as if the problem is that the code in post.py (https://github.com/conda/conda-build/blob/master/conda_build/post.py) tries to run python.app, but doesn't have pythonw as a fallback option if it's not there.


Discussion:
I'm not actually sure that we can have fallbacks there - that's the shebang that is saying which executable to run.  I can certainly try to use pythonw on MacOS there rather than python.app, but I'm not sure exactly what the consequences of that are.  I think the key issue is that we need some way of distinguishing GUI-enabled programs from CLI-only programs.  If pythonw does not mess up CLI-only apps, then that is probably the best way forward.
I thought it would be possible to check if either python.app or pythonw exist within the bin directory before writing the shebang. It is only failing on the final test in a temporary environment that is deleted straight after the test, so it doesn't have to be particularly robust.

Resolving patch:
diff --git a/conda_build/build.py b/conda_build/build.py
--- a/conda_build/build.py
+++ b/conda_build/build.py
@@ -1321,6 +1321,10 @@ def test(recipedir_or_package_or_metadata, config, move_broken=True):
             if utils.on_win:
                 tf.write("if errorlevel 1 exit 1\n")
         if py_files:
+            test_python = config.test_python
+            # use pythonw for import tests when osx_is_app is set
+            if metadata.get_value('build/osx_is_app') and sys.platform == 'darwin':
+                test_python = test_python + 'w'
             tf.write("{python} -s {test_file}\n".format(
                 python=config.test_python,
                 test_file=join(config.test_dir, 'run_test.py')))
diff --git a/conda_build/metadata.py b/conda_build/metadata.py
--- a/conda_build/metadata.py
+++ b/conda_build/metadata.py
@@ -442,6 +442,16 @@ def parse_again(self, config=None, permit_undefined_jinja=False):
                 self.meta['requirements']['run'] = run_requirements
 
         self.validate_features()
+        self.append_requirements()
+
+    def append_requirements(self):
+        """For dynamic determination of build or run reqs, based on configuration"""
+        reqs = self.meta.get('requirements', {})
+        run_reqs = reqs.get('run', [])
+        build_reqs = reqs.get('build', [])
+        if bool(self.get_value('build/osx_is_app', False)) and self.config.platform == 'osx':
+            run_reqs.append('python.app')
+        self.meta['requirements'] = reqs
 
     def parse_until_resolved(self, config):
         # undefined_jinja_vars is refreshed by self.parse again
diff --git a/conda_build/post.py b/conda_build/post.py
--- a/conda_build/post.py
+++ b/conda_build/post.py
@@ -71,7 +71,7 @@ def fix_shebang(f, prefix, build_python, osx_is_app=False):
 
     encoding = sys.stdout.encoding or 'utf8'
 
-    py_exec = ('/bin/bash ' + prefix + '/bin/python.app'
+    py_exec = ('/bin/bash ' + prefix + '/bin/pythonw'
                if sys.platform == 'darwin' and osx_is_app else
                prefix + '/bin/' + basename(build_python))
     new_data = SHEBANG_PAT.sub(b'#!' + py_exec.encode(encoding), data, count=1)
@@ -430,7 +430,7 @@ def post_build(m, files, prefix, build_python, croot):
     binary_relocation = m.binary_relocation()
     if not binary_relocation:
         print("Skipping binary relocation logic")
-    osx_is_app = bool(m.get_value('build/osx_is_app', False))
+    osx_is_app = bool(m.get_value('build/osx_is_app', False)) and sys.platform == 'darwin'
 
     check_symlinks(files, prefix, croot)
 


Test patch:
diff --git a/tests/test-recipes/metadata/_nexpy/bld.bat b/tests/test-recipes/metadata/_nexpy/bld.bat
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_nexpy/bld.bat
@@ -0,0 +1,2 @@
+"%PYTHON%" setup.py install
+if errorlevel 1 exit 1
diff --git a/tests/test-recipes/metadata/_nexpy/build.sh b/tests/test-recipes/metadata/_nexpy/build.sh
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_nexpy/build.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+$PYTHON setup.py install --single-version-externally-managed --record installed_files.txt
diff --git a/tests/test-recipes/metadata/_nexpy/meta.yaml b/tests/test-recipes/metadata/_nexpy/meta.yaml
new file mode 100644
--- /dev/null
+++ b/tests/test-recipes/metadata/_nexpy/meta.yaml
@@ -0,0 +1,56 @@
+package:
+  name: nexpy
+  version: "0.9.2"
+
+source:
+  git_url: https://github.com/nexpy/nexpy.git
+  git_tag: v0.9.2
+
+build:
+  entry_points:
+    - nexpy = nexpy.nexpygui:main
+  number: 0
+  osx_is_app: True
+
+requirements:
+  build:
+    - python
+    - setuptools
+    - nexusformat >=0.4.5
+    - numpy >=1.6.0
+    - scipy
+    - h5py
+    - jupyter
+    - ipython >=4.0.0
+    - matplotlib >=1.4.0
+    - six
+
+  run:
+    - python
+    - nexusformat >=0.4.4
+    - numpy >=1.6.0
+    - scipy
+    - h5py
+    - jupyter
+    - ipython >=4.0.0
+    - matplotlib >=1.4.0
+
+test:
+  imports:
+    - nexpy
+    - nexpy.api
+    - nexpy.api.frills
+    - nexpy.api.frills.functions
+    - nexpy.api.nexus
+    - nexpy.definitions
+    - nexpy.gui
+    - nexpy.plugins
+    - nexpy.readers
+
+  commands:
+    - nexpy --help
+
+about:
+  home: http://nexpy.github.io/nexpy/
+  license: BSD License
+  summary: 'NeXpy: A Python GUI to analyze NeXus data'
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
@@ -826,3 +826,12 @@ def test_workdir_removal_warning_no_remove(test_config, caplog):
     recipe = os.path.join(metadata_dir, '_test_uses_src_dir')
     api.build(recipe, config=test_config, remove_work_dir=False)
     assert "Not removing work directory after build" in caplog.text()
+
+
+@pytest.mark.skipif(sys.platform != 'darwin', reason="relevant to mac only")
+def test_append_python_app_osx(test_config):
+    """Recipes that use osx_is_app need to have python.app in their runtime requirements."""
+    recipe = os.path.join(metadata_dir, '_nexpy')
+    # tests will fail here if python.app is not added to the run reqs by conda-build, because
+    #    without it, pythonw will be missing.
+    api.build(recipe, config=test_config, channel_urls=('nexpy', ))