invest/scripts/convert-requirements-to-con...

111 lines
3.9 KiB
Python

# encoding=UTF-8
"""convert-requirements-to-conda-yml.py"""
import argparse
import platform
import sys
YML_TEMPLATE = """channels:
- conda-forge
- defaults
dependencies:
{conda_dependencies}
{pip_dependencies}
"""
def build_environment_from_requirements(cli_args):
"""Build a conda environment.yml from requirements.txt files.
This script assumes a couple of rules about what should be installed by
conda and what should be installed by pip:
1. If the requirement is installed from git or hg, it should be
installed by pip.
2. If the requirement is followed by the commend '# pip-only', it
should be installed by pip.
3. If the requirement is available on conda-forge, it should be
installed by conda.
4. Otherwise, it should be installed by pip.
Arguments:
cli_args (list): A list of command-line arguments.
Returns:
``None``
"""
parser = argparse.ArgumentParser(description=(
'Convert a set of pip requirements.txt files into an environment '
'file for use by `conda create`.'
), prog=__file__)
parser.add_argument('req', nargs='+',
help='A requirements.txt file to analyze')
args = parser.parse_args(cli_args)
requirements_files = args.req
pip_requirements = set()
conda_requirements = set()
for requirement_file in requirements_files:
with open(requirement_file) as file:
for line in file:
line = line.strip()
# Blank line or comment
if len(line) == 0 or line.startswith('#'):
continue
if line.endswith('# pip-only'):
# Conda prefers that we explicitly include pip as a
# requirement if we're using pip.
conda_requirements.add('pip')
pip_requirements.add(line)
# If an scm needs to be installed for pip to clone to a
# revision, add it to the conda package list.
#
# Bazaar (bzr, which pip supports) is not listed;
# deprecated as of 2016 and not available on conda-forge.
install_compiler = False
for prefix, scm_conda_pkg in [("git+", "git"),
("hg+", "mercurial"),
("svn+", "subversion")]:
if line.startswith(prefix):
conda_requirements.add(scm_conda_pkg)
install_compiler = True
break # The line can only match 1 prefix
# It's less common (like for pygeoprocessing) to have linux
# wheels. Install a compiler if we're on linux, to be
# safe.
if platform.system() == 'Linux' or install_compiler:
# Always make the compiler available
# cxx-compiler works on all OSes.
# NOTE: do not use this as a dependency in a
# conda-forge package recipe!
# https://anaconda.org/conda-forge/cxx-compiler
conda_requirements.add('cxx-compiler')
else:
conda_requirements.add(line)
conda_deps_string = '\n'.join(
[f'- {dep}' for dep in sorted(conda_requirements, key=str.casefold)])
if pip_requirements:
pip_deps_string = '- pip:\n' + '\n'.join(
[' - %s' % dep for dep in sorted(pip_requirements, key=str.casefold)])
else:
pip_deps_string = ''
print(YML_TEMPLATE.format(
conda_dependencies=conda_deps_string,
pip_dependencies=pip_deps_string))
if __name__ == '__main__':
build_environment_from_requirements(sys.argv[1:])
# TODO: resolve dependencies by calling conda?