Repository: conda/conda-build

Issue:
conda build KeyError when calling absolute path
I'm building a conda package by calling `conda-build` via an absolute path:

```
$ /opt/continuum/anaconda/bin/conda build conda.recipe/
```

But [this code](https://github.com/conda/conda-build/blob/bb128940c63b9f3f015d649df1789be54125d86a/conda_build/build.py#L298-L304) is calling conda from my PATH, which is:

```
$ which conda
/opt/wakari/anaconda/bin/conda
```

and that conda installation in `/opt/wakari` doesn't have `conda-env` installed, therefore I get an error like:

```
$ /opt/continuum/anaconda/bin/conda build conda.recipe/
BUILD START: delay-0.6.0-py27_0
updating index in: /home/vagrant/conda-bld/linux-64
updating index in: /home/vagrant/conda-bld/noarch

The following NEW packages will be INSTALLED:

    openssl:    1.0.2j-0
    pip:        8.1.2-py27_0
    python:     2.7.12-1
    readline:   6.2-2
    setuptools: 27.2.0-py27_0
    sqlite:     3.13.0-0
    tk:         8.5.18-0
    wheel:      0.29.0-py27_0
    zlib:       1.2.8-3

WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
WARNING conda.lock:touch(53): Failed to create lock, do not run conda in parallel processes [errno 13]
Copying /home/vagrant/delay to /home/vagrant/conda-bld/conda.recipe_1476158063265/work
Package: delay-0.6.0-py27_0
source tree in: /home/vagrant/conda-bld/conda.recipe_1476158063265/work
+ source /opt/continuum/anaconda/bin/activate /home/vagrant/conda-bld/conda.recipe_1476158063265/_b_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placeho
+ /home/vagrant/conda-bld/conda.recipe_1476158063265/_b_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placeho/bin/python setup.py install
running install
running bdist_egg
running egg_info
creating delay.egg-info
writing delay.egg-info/PKG-INFO
writing top-level names to delay.egg-info/top_level.txt
writing dependency_links to delay.egg-info/dependency_links.txt
writing manifest file 'delay.egg-info/SOURCES.txt'
reading manifest file 'delay.egg-info/SOURCES.txt'
writing manifest file 'delay.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build
creating build/lib
creating build/lib/delay
copying delay/models.py -> build/lib/delay
copying delay/__init__.py -> build/lib/delay
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/delay
copying build/lib/delay/models.py -> build/bdist.linux-x86_64/egg/delay
copying build/lib/delay/__init__.py -> build/bdist.linux-x86_64/egg/delay
byte-compiling build/bdist.linux-x86_64/egg/delay/models.py to models.pyc
byte-compiling build/bdist.linux-x86_64/egg/delay/__init__.py to __init__.pyc
installing package data to build/bdist.linux-x86_64/egg
running install_data
creating build/bdist.linux-x86_64/egg/delay/data
copying airline_delay_causes.csv -> build/bdist.linux-x86_64/egg/delay/data
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying delay.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying delay.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying delay.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying delay.egg-info/not-zip-safe -> build/bdist.linux-x86_64/egg/EGG-INFO
copying delay.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
creating dist
creating 'dist/delay-0.6.0-py2.7.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing delay-0.6.0-py2.7.egg
creating /home/vagrant/conda-bld/conda.recipe_1476158063265/_b_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placeho/lib/python2.7/site-packages/delay-0.6.0-py2.7.egg
Extracting delay-0.6.0-py2.7.egg to /home/vagrant/conda-bld/conda.recipe_1476158063265/_b_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placeho/lib/python2.7/site-packages
Adding delay 0.6.0 to easy-install.pth file

Installed /home/vagrant/conda-bld/conda.recipe_1476158063265/_b_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placeho/lib/python2.7/site-packages/delay-0.6.0-py2.7.egg
Processing dependencies for delay==0.6.0
Finished processing dependencies for delay==0.6.0
compiling .pyc files...
found egg dir: /home/vagrant/conda-bld/conda.recipe_1476158063265/_b_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placeho/lib/python2.7/site-packages/delay-0.6.0-py2.7.egg
number of files: 10
Fixing permissions
Using Anaconda Cloud api site https://api.anaconda.org
Traceback (most recent call last):
  File "/opt/continuum/anaconda/bin/conda-build", line 6, in <module>
    sys.exit(conda_build.cli.main_build.main())
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/cli/main_build.py", line 243, in main
    execute(sys.argv[1:])
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/cli/main_build.py", line 235, in execute
    already_built=None, config=config)
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/api.py", line 83, in build
    need_source_download=need_source_download, config=config)
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/build.py", line 1087, in build_tree
    config=recipe_config)
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/build.py", line 788, in build
    create_info_files(m, pkg_files, config=config, prefix=config.build_prefix)
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/build.py", line 389, in create_info_files
    write_about_json(m, config)
  File "/opt/continuum/anaconda/lib/python2.7/site-packages/conda_build/build.py", line 304, in write_about_json
    d['conda_env_version'] = conda_info['conda_env_version']
KeyError: 'conda_env_version'
```



Discussion:
Cool, that was a bad assumption on my part.  I'll make that key optional.  Thanks for reporting.

Won't the info that it's grabbing related to conda and conda-build versions still be wrong if it's calling conda on my path as opposed to the conda that I called via the absolute path? Perhaps that's not a big deal.

Yes, it would be better to get these bits of info from importing the modules and getting their `__version__` - dependent on sys.path, rather than PATH.  That is indeed the right way to do it.  Thanks for reiterating.


Resolving patch:
diff --git a/conda_build/build.py b/conda_build/build.py
--- a/conda_build/build.py
+++ b/conda_build/build.py
@@ -28,7 +28,6 @@
 import filelock
 
 # used to get version
-import conda
 from .conda_interface import cc
 from .conda_interface import envs_dirs, root_dir
 from .conda_interface import plan
@@ -294,14 +293,20 @@ def write_about_json(m, config):
             value = m.get_value('about/%s' % key)
             if value:
                 d[key] = value
+
+        bin_path = os.path.join(sys.prefix, "Scripts" if on_win else "bin", 'conda')
+
         # for sake of reproducibility, record some conda info
-        conda_info = subprocess.check_output(['conda', 'info', '--json', '-s'])
+        conda_info = subprocess.check_output([bin_path, 'info', '--json', '-s'])
         if hasattr(conda_info, 'decode'):
             conda_info = conda_info.decode(codec)
         conda_info = json.loads(conda_info)
         d['conda_version'] = conda_info['conda_version']
         d['conda_build_version'] = conda_info['conda_build_version']
-        d['conda_env_version'] = conda_info['conda_env_version']
+        # conda env will be in most, but not necessarily all installations.
+        #    Don't die if we don't see it.
+        if 'conda_env_version' in conda_info:
+            d['conda_env_version'] = conda_info['conda_env_version']
         d['offline'] = conda_info['offline']
         channels = conda_info['channels']
         stripped_channels = []
@@ -314,7 +319,7 @@ def write_about_json(m, config):
             d['env_vars'] = conda_info['env_vars']
         except KeyError:
             pass
-        pkgs = subprocess.check_output(['conda', 'list', '-n', 'root', '--json'])
+        pkgs = subprocess.check_output([bin_path, 'list', '-n', 'root', '--json'])
         if hasattr(pkgs, 'decode'):
             pkgs = pkgs.decode(codec)
         d['root_pkgs'] = json.loads(pkgs)
diff --git a/conda_build/cli/main_build.py b/conda_build/cli/main_build.py
--- a/conda_build/cli/main_build.py
+++ b/conda_build/cli/main_build.py
@@ -18,7 +18,6 @@
 from conda_build.cli.main_render import (set_language_env_vars, RecipeCompleter,
                                          render_recipe, get_render_parser, bldpkg_path)
 from conda_build.conda_interface import cc
-from conda_build.conda_interface import delete_trash
 from conda_build.conda_interface import add_parser_channels
 import conda_build.source as source
 from conda_build.utils import get_recipe_abspath, silence_loggers, rm_rf, print_skip_message


Test patch:
diff --git a/tests/test_build.py b/tests/test_build.py
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -3,17 +3,19 @@
 and is more unit-test oriented.
 """
 
-import copy
+import json
 import os
+import subprocess
 import sys
 
 import pytest
 
-from conda_build import build, api
+import conda
+from conda_build import build, api, __version__
 from conda_build.metadata import MetaData
 from conda_build.utils import rm_rf, on_win
 
-from .utils import testing_workdir, test_config, test_metadata, metadata_dir
+from .utils import testing_workdir, test_config, test_metadata, metadata_dir, put_bad_conda_on_path
 
 prefix_tests = {"normal": os.path.sep}
 if sys.platform == "win32":
@@ -142,3 +144,18 @@ def test_warn_on_old_conda_build(test_config, capfd):
 def test_sanitize_channel():
     test_url = 'https://conda.anaconda.org/t/ms-534991f2-4123-473a-b512-42025291b927/somechannel'
     assert build.sanitize_channel(test_url) == 'https://conda.anaconda.org/t/<TOKEN>/somechannel'
+
+
+def test_write_about_json_without_conda_on_path(testing_workdir, test_metadata):
+    with put_bad_conda_on_path(testing_workdir):
+        # verify that the correct (bad) conda is the one we call
+        with pytest.raises(subprocess.CalledProcessError):
+            subprocess.check_output('conda -h', env=os.environ, shell=True)
+        build.write_about_json(test_metadata, test_metadata.config)
+
+    output_file = os.path.join(test_metadata.config.info_dir, 'about.json')
+    assert os.path.isfile(output_file)
+    with open(output_file) as f:
+        about = json.load(f)
+    assert 'conda_version' in about
+    assert 'conda_build_version' in about
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -4,6 +4,7 @@
 
 import json
 import os
+import subprocess
 import sys
 import yaml
 
@@ -14,7 +15,8 @@
 
 from conda_build import api
 from conda_build.utils import get_site_packages, on_win, get_build_folders, package_has_file
-from .utils import testing_workdir, metadata_dir, testing_env, test_config, test_metadata
+from .utils import (testing_workdir, metadata_dir, testing_env, test_config, test_metadata,
+                    put_bad_conda_on_path)
 
 import conda_build.cli.main_build as main_build
 import conda_build.cli.main_sign as main_sign
@@ -32,6 +34,14 @@ def test_build():
     main_build.execute(args)
 
 
+# regression test for https://github.com/conda/conda-build/issues/1450
+def test_build_with_conda_not_on_path(testing_workdir):
+    with put_bad_conda_on_path(testing_workdir):
+        # using subprocess is not ideal, but it is the easiest way to ensure that PATH
+        #    is altered the way we want here.
+        subprocess.check_call('conda-build {0}'.format(os.path.join(metadata_dir, "python_run")),
+                              env=os.environ, shell=True)
+
 def test_build_add_channel():
     """This recipe requires the blinker package, which is only on conda-forge.
     This verifies that the -c argument works."""
diff --git a/tests/utils.py b/tests/utils.py
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,8 +1,10 @@
 from collections import defaultdict
+import contextlib
 import os
+import stat
 import subprocess
 import sys
-import tarfile
+
 
 import pytest
 
@@ -99,3 +101,23 @@ def add_mangling(filename):
         filename = os.path.join(os.path.dirname(filename), '__pycache__',
                                 os.path.basename(filename))
     return filename + 'c'
+
+
+@contextlib.contextmanager
+def put_bad_conda_on_path(testing_workdir):
+    path_backup = os.environ['PATH']
+    # it is easier to add an intentionally bad path than it is to try to scrub any existing path
+    os.environ['PATH'] = os.pathsep.join([testing_workdir, os.environ["PATH"]])
+
+    exe_name = 'conda.bat' if on_win else 'conda'
+    out_exe = os.path.join(testing_workdir, exe_name)
+    with open(out_exe, 'w') as f:
+        f.write("exit 1")
+    st = os.stat(out_exe)
+    os.chmod(out_exe, st.st_mode | 0o111)
+    try:
+        yield
+    except:
+        raise
+    finally:
+        os.environ['PATH'] = path_backup