2018-06-26 04:14:29 -07:00
|
|
|
"""
|
|
|
|
|
Wrap conda commandline program
|
|
|
|
|
"""
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import json
|
2018-06-26 18:35:58 -07:00
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
# Use sys.executable to call conda to avoid needing to fudge PATH
|
|
|
|
|
CONDA_EXECUTABLE = [sys.executable, '-m', 'conda']
|
2018-06-26 04:14:29 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_conda_env(prefix):
|
|
|
|
|
"""
|
|
|
|
|
Ensure a conda environment in the prefix
|
|
|
|
|
"""
|
|
|
|
|
abspath = os.path.abspath(prefix)
|
|
|
|
|
try:
|
|
|
|
|
output = json.loads(
|
2018-06-26 18:35:58 -07:00
|
|
|
subprocess.check_output(CONDA_EXECUTABLE + ['create', '--json', '--prefix', abspath]).decode()
|
2018-06-26 04:14:29 -07:00
|
|
|
)
|
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
output = json.loads(e.output.decode())
|
|
|
|
|
if 'error' in output and output['error'] == f'CondaValueError: prefix already exists: {abspath}':
|
|
|
|
|
return
|
|
|
|
|
raise
|
|
|
|
|
if 'success' in output and output['success'] == True:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_conda_packages(prefix, packages):
|
|
|
|
|
"""
|
2018-06-27 03:05:24 -07:00
|
|
|
Ensure packages (from conda-forge) are installed in the conda prefix.
|
2018-06-26 04:14:29 -07:00
|
|
|
"""
|
|
|
|
|
abspath = os.path.abspath(prefix)
|
|
|
|
|
# Let subprocess errors propagate
|
|
|
|
|
# FIXME: raise different exception when using
|
2018-06-26 18:35:58 -07:00
|
|
|
raw_output = subprocess.check_output(CONDA_EXECUTABLE + [
|
|
|
|
|
'install',
|
2018-06-27 03:05:24 -07:00
|
|
|
'-c', 'conda-forge', # Make customizable if we ever need to
|
2018-06-26 04:14:29 -07:00
|
|
|
'--json',
|
|
|
|
|
'--prefix', abspath
|
|
|
|
|
] + packages).decode()
|
|
|
|
|
# `conda install` outputs JSON lines for fetch updates,
|
|
|
|
|
# and a undelimited output at the end. There is no reasonable way to
|
|
|
|
|
# parse this outside of this kludge.
|
|
|
|
|
filtered_output = '\n'.join([
|
|
|
|
|
l for l in raw_output.split('\n')
|
2018-06-26 18:36:20 -07:00
|
|
|
# Sometimes the JSON messages start with a \x00. The lstrip removes these.
|
|
|
|
|
# conda messages seem to randomly throw \x00 in places for no reason
|
|
|
|
|
if not l.lstrip('\x00').startswith('{"fetch"')
|
2018-06-26 04:14:29 -07:00
|
|
|
])
|
2018-06-26 18:36:20 -07:00
|
|
|
output = json.loads(filtered_output.lstrip('\x00'))
|
2018-06-26 04:14:29 -07:00
|
|
|
if 'success' in output and output['success'] == True:
|
|
|
|
|
return
|
2018-06-26 17:38:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_pip_packages(prefix, packages):
|
|
|
|
|
"""
|
|
|
|
|
Ensure pip packages are installed in the given conda prefix.
|
|
|
|
|
"""
|
|
|
|
|
abspath = os.path.abspath(prefix)
|
|
|
|
|
pip_executable = [os.path.join(abspath, 'bin', 'python'), '-m', 'pip']
|
|
|
|
|
|
2018-06-27 02:14:20 -07:00
|
|
|
subprocess.check_output(pip_executable + [
|
2018-06-26 17:38:56 -07:00
|
|
|
'install',
|
|
|
|
|
'--no-cache-dir',
|
|
|
|
|
] + packages)
|
2018-07-18 01:00:48 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_pip_requirements(prefix, requirements_path):
|
|
|
|
|
"""
|
|
|
|
|
Ensure pip packages from given requirements_path are installed in given conda prefix.
|
|
|
|
|
|
|
|
|
|
requirements_path can be a file or a URL.
|
|
|
|
|
"""
|
|
|
|
|
abspath = os.path.abspath(prefix)
|
|
|
|
|
pip_executable = [os.path.join(abspath, 'bin', 'python'), '-m', 'pip']
|
|
|
|
|
|
|
|
|
|
subprocess.check_output(pip_executable + [
|
|
|
|
|
'install',
|
|
|
|
|
'-r',
|
|
|
|
|
requirements_path
|
|
|
|
|
])
|