diff --git a/.coveragerc b/.coveragerc index 7c462b3c..c6d704c6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -14,6 +14,8 @@ omit = exclude_lines = # Have to re-enable the standard pragma \#\s*pragma: no cover + # We optionally substitute this + ${COVERAGE_IGNORE_WINDOWS} # Don't complain if tests don't hit defensive assertion code: ^\s*raise AssertionError\b diff --git a/.travis.yml b/.travis.yml index a01003c3..55f7c310 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,9 @@ language: python python: 3.5 +dist: trusty +sudo: required +services: + - docker env: # These should match the tox env list - TOXENV=py27 - TOXENV=py34 @@ -18,11 +22,11 @@ before_install: export PATH="/tmp/git/bin:$PATH" fi - git --version + - | + ./get-swift.sh + export PATH="/tmp/swift/usr/bin:$PATH" after_success: - coveralls -sudo: required -services: - - docker cache: directories: - $HOME/.cache/pip diff --git a/appveyor.yml b/appveyor.yml index d59a852f..c02598dc 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,7 @@ environment: + global: + COVERAGE_IGNORE_WINDOWS: '# pragma: windows no cover' + TOX_TESTENV_PASSENV: COVERAGE_IGNORE_WINDOWS matrix: - TOXENV: py27 - TOXENV: py35 diff --git a/get-swift.sh b/get-swift.sh new file mode 100755 index 00000000..e5cc570b --- /dev/null +++ b/get-swift.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# This is a script used in travis-ci to install swift +set -ex + +. /etc/lsb-release +if [ "$DISTRIB_CODENAME" = "trusty" ]; then + SWIFT_URL='https://swift.org/builds/swift-3.0.2-release/ubuntu1404/swift-3.0.2-RELEASE/swift-3.0.2-RELEASE-ubuntu14.04.tar.gz' +else + SWIFT_URL='https://swift.org/builds/swift-3.0.2-release/ubuntu1604/swift-3.0.2-RELEASE/swift-3.0.2-RELEASE-ubuntu16.04.tar.gz' +fi + +mkdir -p /tmp/swift +pushd /tmp/swift + wget "$SWIFT_URL" -O swift.tar.gz + tar -xf swift.tar.gz --strip 1 +popd diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index a517ee85..cc7af88c 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -6,6 +6,7 @@ from pre_commit.languages import pcre from pre_commit.languages import python from pre_commit.languages import ruby from pre_commit.languages import script +from pre_commit.languages import swift from pre_commit.languages import system # A language implements the following constant and two functions in its module: @@ -47,6 +48,7 @@ languages = { 'python': python, 'ruby': ruby, 'script': script, + 'swift': swift, 'system': system, } diff --git a/pre_commit/languages/docker.py b/pre_commit/languages/docker.py index cfd38ddb..2e9129a7 100644 --- a/pre_commit/languages/docker.py +++ b/pre_commit/languages/docker.py @@ -16,30 +16,30 @@ ENVIRONMENT_DIR = 'docker' PRE_COMMIT_LABEL = 'PRE_COMMIT' -def md5(s): +def md5(s): # pragma: windows no cover return hashlib.md5(five.to_bytes(s)).hexdigest() -def docker_tag(repo_cmd_runner): +def docker_tag(repo_cmd_runner): # pragma: windows no cover return 'pre-commit-{}'.format( md5(os.path.basename(repo_cmd_runner.path())) ).lower() -def docker_is_running(): +def docker_is_running(): # pragma: windows no cover try: return cmd_output('docker', 'ps')[0] == 0 except CalledProcessError: return False -def assert_docker_available(): +def assert_docker_available(): # pragma: windows no cover assert docker_is_running(), ( 'Docker is either not running or not configured in this environment' ) -def build_docker_image(repo_cmd_runner, **kwargs): +def build_docker_image(repo_cmd_runner, **kwargs): # pragma: windows no cover pull = kwargs.pop('pull') assert not kwargs, kwargs cmd = ( @@ -56,7 +56,7 @@ def install_environment( repo_cmd_runner, version='default', additional_dependencies=(), -): +): # pragma: windows no cover assert repo_cmd_runner.exists('Dockerfile'), ( 'No Dockerfile was found in the hook repository' ) @@ -65,9 +65,9 @@ def install_environment( ) assert_docker_available() - directory = repo_cmd_runner.path(helpers.environment_dir( - ENVIRONMENT_DIR, 'default', - )) + directory = repo_cmd_runner.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) # Docker doesn't really have relevant disk environment, but pre-commit # still needs to cleanup it's state files on failure @@ -76,7 +76,7 @@ def install_environment( os.mkdir(directory) -def run_hook(repo_cmd_runner, hook, file_args): +def run_hook(repo_cmd_runner, hook, file_args): # pragma: windows no cover assert_docker_available() # Rebuild the docker image in case it has gone missing, as many people do # automated cleanup of docker images. diff --git a/pre_commit/languages/node.py b/pre_commit/languages/node.py index a5919824..ef557a16 100644 --- a/pre_commit/languages/node.py +++ b/pre_commit/languages/node.py @@ -14,7 +14,7 @@ from pre_commit.xargs import xargs ENVIRONMENT_DIR = 'node_env' -def get_env_patch(venv): +def get_env_patch(venv): # pragma: windows no cover return ( ('NODE_VIRTUAL_ENV', venv), ('NPM_CONFIG_PREFIX', venv), @@ -25,9 +25,8 @@ def get_env_patch(venv): @contextlib.contextmanager -def in_env(repo_cmd_runner, language_version): - envdir = os.path.join( - repo_cmd_runner.prefix_dir, +def in_env(repo_cmd_runner, language_version): # pragma: windows no cover + envdir = repo_cmd_runner.path( helpers.environment_dir(ENVIRONMENT_DIR, language_version), ) with envcontext(get_env_patch(envdir)): @@ -38,7 +37,7 @@ def install_environment( repo_cmd_runner, version='default', additional_dependencies=(), -): +): # pragma: windows no cover additional_dependencies = tuple(additional_dependencies) assert repo_cmd_runner.exists('package.json') directory = helpers.environment_dir(ENVIRONMENT_DIR, version) @@ -62,6 +61,6 @@ def install_environment( ) -def run_hook(repo_cmd_runner, hook, file_args): +def run_hook(repo_cmd_runner, hook, file_args): # pragma: windows no cover with in_env(repo_cmd_runner, hook['language_version']): return xargs(helpers.to_cmd(hook), file_args) diff --git a/pre_commit/languages/python.py b/pre_commit/languages/python.py index 11be01d0..1e60a3ed 100644 --- a/pre_commit/languages/python.py +++ b/pre_commit/languages/python.py @@ -32,8 +32,7 @@ def get_env_patch(venv): @contextlib.contextmanager def in_env(repo_cmd_runner, language_version): - envdir = os.path.join( - repo_cmd_runner.prefix_dir, + envdir = repo_cmd_runner.path( helpers.environment_dir(ENVIRONMENT_DIR, language_version), ) with envcontext(get_env_patch(envdir)): diff --git a/pre_commit/languages/ruby.py b/pre_commit/languages/ruby.py index 2e01012a..32682f52 100644 --- a/pre_commit/languages/ruby.py +++ b/pre_commit/languages/ruby.py @@ -18,7 +18,7 @@ from pre_commit.xargs import xargs ENVIRONMENT_DIR = 'rbenv' -def get_env_patch(venv, language_version): +def get_env_patch(venv, language_version): # pragma: windows no cover patches = ( ('GEM_HOME', os.path.join(venv, 'gems')), ('RBENV_ROOT', venv), @@ -34,16 +34,17 @@ def get_env_patch(venv, language_version): @contextlib.contextmanager -def in_env(repo_cmd_runner, language_version): - envdir = os.path.join( - repo_cmd_runner.prefix_dir, +def in_env(repo_cmd_runner, language_version): # pragma: windows no cover + envdir = repo_cmd_runner.path( helpers.environment_dir(ENVIRONMENT_DIR, language_version), ) with envcontext(get_env_patch(envdir, language_version)): yield -def _install_rbenv(repo_cmd_runner, version='default'): +def _install_rbenv( + repo_cmd_runner, version='default', +): # pragma: windows no cover directory = helpers.environment_dir(ENVIRONMENT_DIR, version) with tarfile.open(resource_filename('rbenv.tar.gz')) as tf: @@ -86,7 +87,7 @@ def _install_rbenv(repo_cmd_runner, version='default'): activate_file.write('export RBENV_VERSION="{}"\n'.format(version)) -def _install_ruby(runner, version): +def _install_ruby(runner, version): # pragma: windows no cover try: helpers.run_setup_cmd(runner, ('rbenv', 'download', version)) except CalledProcessError: # pragma: no cover (usually find with download) @@ -98,7 +99,7 @@ def install_environment( repo_cmd_runner, version='default', additional_dependencies=(), -): +): # pragma: windows no cover additional_dependencies = tuple(additional_dependencies) directory = helpers.environment_dir(ENVIRONMENT_DIR, version) with clean_path_on_failure(repo_cmd_runner.path(directory)): @@ -126,6 +127,6 @@ def install_environment( ) -def run_hook(repo_cmd_runner, hook, file_args): +def run_hook(repo_cmd_runner, hook, file_args): # pragma: windows no cover with in_env(repo_cmd_runner, hook['language_version']): return xargs(helpers.to_cmd(hook), file_args) diff --git a/pre_commit/languages/swift.py b/pre_commit/languages/swift.py new file mode 100644 index 00000000..d362e01e --- /dev/null +++ b/pre_commit/languages/swift.py @@ -0,0 +1,56 @@ +from __future__ import unicode_literals + +import contextlib +import os + +from pre_commit.envcontext import envcontext +from pre_commit.envcontext import Var +from pre_commit.languages import helpers +from pre_commit.util import clean_path_on_failure +from pre_commit.xargs import xargs + +ENVIRONMENT_DIR = 'swift_env' +BUILD_DIR = '.build' +BUILD_CONFIG = 'release' + + +def get_env_patch(venv): # pragma: windows no cover + bin_path = os.path.join(venv, BUILD_DIR, BUILD_CONFIG) + return (('PATH', (bin_path, os.pathsep, Var('PATH'))),) + + +@contextlib.contextmanager +def in_env(repo_cmd_runner): # pragma: windows no cover + envdir = repo_cmd_runner.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) + with envcontext(get_env_patch(envdir)): + yield + + +def install_environment( + repo_cmd_runner, + version='default', + additional_dependencies=(), +): # pragma: windows no cover + assert version == 'default', ( + 'Pre-commit does not support language_version for docker ' + ) + directory = repo_cmd_runner.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) + + # Build the swift package + with clean_path_on_failure(directory): + os.mkdir(directory) + repo_cmd_runner.run(( + 'swift', 'build', + '-C', '{prefix}', + '-c', BUILD_CONFIG, + '--build-path', os.path.join(directory, BUILD_DIR), + )) + + +def run_hook(repo_cmd_runner, hook, file_args): # pragma: windows no cover + with in_env(repo_cmd_runner): + return xargs(helpers.to_cmd(hook), file_args) diff --git a/testing/resources/swift_hooks_repo/.gitignore b/testing/resources/swift_hooks_repo/.gitignore new file mode 100644 index 00000000..02c08753 --- /dev/null +++ b/testing/resources/swift_hooks_repo/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj diff --git a/testing/resources/swift_hooks_repo/Package.swift b/testing/resources/swift_hooks_repo/Package.swift new file mode 100644 index 00000000..6e02c188 --- /dev/null +++ b/testing/resources/swift_hooks_repo/Package.swift @@ -0,0 +1,5 @@ +import PackageDescription + +let package = Package( + name: "swift_hooks_repo" +) diff --git a/testing/resources/swift_hooks_repo/Sources/main.swift b/testing/resources/swift_hooks_repo/Sources/main.swift new file mode 100644 index 00000000..f7cf60e1 --- /dev/null +++ b/testing/resources/swift_hooks_repo/Sources/main.swift @@ -0,0 +1 @@ +print("Hello, world!") diff --git a/testing/resources/swift_hooks_repo/hooks.yaml b/testing/resources/swift_hooks_repo/hooks.yaml new file mode 100644 index 00000000..c08df87d --- /dev/null +++ b/testing/resources/swift_hooks_repo/hooks.yaml @@ -0,0 +1,6 @@ +- id: swift-hooks-repo + name: Swift hooks repo example + description: Runs the hello world app generated by swift package init --type executable (binary called swift_hooks_repo here) + entry: swift_hooks_repo + language: swift + files: \.(swift)$ diff --git a/testing/util.py b/testing/util.py index cf9dde9d..8fdf5777 100644 --- a/testing/util.py +++ b/testing/util.py @@ -6,6 +6,7 @@ import shutil import jsonschema import pytest +from pre_commit import parse_shebang from pre_commit.languages.docker import docker_is_running from pre_commit.util import cmd_output from pre_commit.util import cwd @@ -68,6 +69,11 @@ skipif_slowtests_false = pytest.mark.skipif( reason='slowtests=false', ) +skipif_cant_run_swift = pytest.mark.skipif( + parse_shebang.find_executable('swift') is None, + reason='swift isn\'t installed or can\'t be found' +) + xfailif_windows_no_ruby = pytest.mark.xfail( os.name == 'nt', reason='Ruby support not yet implemented on windows.', diff --git a/tests/repository_test.py b/tests/repository_test.py index ad3c8234..e37b304d 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -30,6 +30,7 @@ from testing.fixtures import make_config_from_repo from testing.fixtures import make_repo from testing.fixtures import modify_manifest from testing.util import skipif_cant_run_docker +from testing.util import skipif_cant_run_swift from testing.util import skipif_slowtests_false from testing.util import xfailif_no_pcre_support from testing.util import xfailif_windows_no_node @@ -214,6 +215,15 @@ def test_system_hook_with_spaces(tempdir_factory, store): ) +@skipif_cant_run_swift +@pytest.mark.integration +def test_swift_hook(tempdir_factory, store): + _test_hook_repo( + tempdir_factory, store, 'swift_hooks_repo', + 'swift-hooks-repo', [], b'Hello, world!\n', + ) + + @pytest.mark.integration def test_missing_executable(tempdir_factory, store): _test_hook_repo(