Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for conda as a language #1232

Merged
merged 1 commit into from Dec 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions azure-pipelines.yml
Expand Up @@ -22,6 +22,9 @@ jobs:
COVERAGE_IGNORE_WINDOWS: '# pragma: windows no cover'
TOX_TESTENV_PASSENV: COVERAGE_IGNORE_WINDOWS
TEMP: C:\Temp # remove when dropping python2
pre_test:
- powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts"
displayName: Add conda to PATH
- template: job--python-tox.yml@asottile
parameters:
toxenvs: [py37]
Expand Down
2 changes: 2 additions & 0 deletions pre_commit/languages/all.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals

from pre_commit.languages import conda
from pre_commit.languages import docker
from pre_commit.languages import docker_image
from pre_commit.languages import fail
Expand Down Expand Up @@ -52,6 +53,7 @@
# """

languages = {
'conda': conda,
'docker': docker,
'docker_image': docker_image,
'fail': fail,
Expand Down
66 changes: 66 additions & 0 deletions pre_commit/languages/conda.py
@@ -0,0 +1,66 @@
import contextlib
import os

from pre_commit.envcontext import envcontext
from pre_commit.envcontext import UNSET
from pre_commit.envcontext import Var
from pre_commit.languages import helpers
from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output_b

ENVIRONMENT_DIR = 'conda'
get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy


def get_env_patch(env):
# On non-windows systems executable live in $CONDA_PREFIX/bin, on Windows
# they can be in $CONDA_PREFIX/bin, $CONDA_PREFIX/Library/bin,
# $CONDA_PREFIX/Scripts and $CONDA_PREFIX. Whereas the latter only
# seems to be used for python.exe.
path = (os.path.join(env, 'bin'), os.pathsep, Var('PATH'))
if os.name == 'nt': # pragma: no cover (platform specific)
path = (env, os.pathsep) + path
path = (os.path.join(env, 'Scripts'), os.pathsep) + path
path = (os.path.join(env, 'Library', 'bin'), os.pathsep) + path
asottile marked this conversation as resolved.
Show resolved Hide resolved

return (
('PYTHONHOME', UNSET),
('VIRTUAL_ENV', UNSET),
asottile marked this conversation as resolved.
Show resolved Hide resolved
('CONDA_PREFIX', env),
('PATH', path),
)


@contextlib.contextmanager
def in_env(prefix, language_version):
directory = helpers.environment_dir(ENVIRONMENT_DIR, language_version)
envdir = prefix.path(directory)
with envcontext(get_env_patch(envdir)):
yield


def install_environment(prefix, version, additional_dependencies):
helpers.assert_version_default('conda', version)
directory = helpers.environment_dir(ENVIRONMENT_DIR, version)
asottile marked this conversation as resolved.
Show resolved Hide resolved

env_dir = prefix.path(directory)
with clean_path_on_failure(env_dir):
cmd_output_b(
'conda', 'env', 'create', '-p', env_dir, '--file',
'environment.yml', cwd=prefix.prefix_dir,
)
if additional_dependencies:
asottile marked this conversation as resolved.
Show resolved Hide resolved
cmd_output_b(
'conda', 'install', '-p', env_dir, *additional_dependencies,
cwd=prefix.prefix_dir
asottile marked this conversation as resolved.
Show resolved Hide resolved
)


def run_hook(hook, file_args, color):
# TODO: Some rare commands need to be run using `conda run` but mostly we
# can run them withot which is much quicker and produces a better
# output.
# cmd = ('conda', 'run', '-p', env_dir) + hook.cmd
with in_env(hook.prefix, hook.language_version):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
9 changes: 9 additions & 0 deletions pre_commit/resources/empty_template_environment.yml
@@ -0,0 +1,9 @@
channels:
- conda-forge
- defaults
dependencies:
# This cannot be empty as otherwise no environment will be created.
# We're using openssl here as it is available on all system and will
# most likely be always installed anyways.
# See https://github.com/conda/conda/issues/9487
- openssl
2 changes: 1 addition & 1 deletion pre_commit/store.py
Expand Up @@ -173,7 +173,7 @@ def _git_cmd(*args):

LOCAL_RESOURCES = (
'Cargo.toml', 'main.go', 'main.rs', '.npmignore', 'package.json',
'pre_commit_dummy_package.gemspec', 'setup.py',
'pre_commit_dummy_package.gemspec', 'setup.py', 'environment.yml',
)

def make_local(self, deps):
Expand Down
10 changes: 10 additions & 0 deletions testing/resources/conda_hooks_repo/.pre-commit-hooks.yaml
@@ -0,0 +1,10 @@
- id: sys-exec
name: sys-exec
entry: python -c 'import os; import sys; print(sys.executable.split(os.path.sep)[-2]) if os.name == "nt" else print(sys.executable.split(os.path.sep)[-3])'
language: conda
files: \.py$
- id: additional-deps
name: additional-deps
entry: python
language: conda
files: \.py$
6 changes: 6 additions & 0 deletions testing/resources/conda_hooks_repo/environment.yml
@@ -0,0 +1,6 @@
channels:
- conda-forge
- defaults
dependencies:
- python
- pip
40 changes: 40 additions & 0 deletions tests/repository_test.py
Expand Up @@ -79,6 +79,46 @@ def _test_hook_repo(
assert _norm_out(out) == expected


def test_conda_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'conda_hooks_repo',
asottile marked this conversation as resolved.
Show resolved Hide resolved
'sys-exec', [os.devnull],
b'conda-default\n',
)


def test_conda_with_additional_dependencies_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'conda_hooks_repo',
'additional-deps', [os.devnull],
b'OK\n',
config_kwargs={
'hooks': [{
'id': 'additional-deps',
'args': ['-c', 'import mccabe; print("OK")'],
'additional_dependencies': ['mccabe'],
}],
},
)


def test_local_conda_additional_dependencies(store):
config = {
'repo': 'local',
'hooks': [{
'id': 'local-conda',
'name': 'local-conda',
'entry': 'python',
'language': 'conda',
'args': ['-c', 'import mccabe; print("OK")'],
'additional_dependencies': ['mccabe'],
}],
}
ret, out = _get_hook(config, store, 'local-conda').run((), color=False)
assert ret == 0
assert _norm_out(out) == b'OK\n'


def test_python_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'python_hooks_repo',
Expand Down