From 7f85da1b9dedf8224574fb1264c2b182c36290be Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Tue, 15 May 2018 20:59:18 -0700 Subject: [PATCH 1/5] Add Rust support --- pre_commit/languages/all.py | 2 ++ pre_commit/languages/rust.py | 68 ++++++++++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 71 insertions(+) create mode 100644 pre_commit/languages/rust.py diff --git a/pre_commit/languages/all.py b/pre_commit/languages/all.py index 504c28a0..be74ffd3 100644 --- a/pre_commit/languages/all.py +++ b/pre_commit/languages/all.py @@ -9,6 +9,7 @@ from pre_commit.languages import pygrep from pre_commit.languages import python from pre_commit.languages import python_venv from pre_commit.languages import ruby +from pre_commit.languages import rust from pre_commit.languages import script from pre_commit.languages import swift from pre_commit.languages import system @@ -60,6 +61,7 @@ languages = { 'python': python, 'python_venv': python_venv, 'ruby': ruby, + 'rust': rust, 'script': script, 'swift': swift, 'system': system, diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py new file mode 100644 index 00000000..e6884c34 --- /dev/null +++ b/pre_commit/languages/rust.py @@ -0,0 +1,68 @@ +from __future__ import unicode_literals + +import contextlib +import os.path + +import toml + +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.util import cmd_output +from pre_commit.xargs import xargs + + +ENVIRONMENT_DIR = 'rustenv' +get_default_version = helpers.basic_get_default_version +healthy = helpers.basic_healthy + + +def get_env_patch(target_dir): + return ( + ( + 'PATH', + (os.path.join(target_dir, 'release'), os.pathsep, Var('PATH')), + ), + ) + + +@contextlib.contextmanager +def in_env(prefix): + target_dir = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) + with envcontext(get_env_patch(target_dir)): + yield + + +def _add_dependencies(cargo_toml_path, additional_dependencies): + with open(cargo_toml_path, 'r+') as f: + cargo_toml = toml.load(f) + for dep in additional_dependencies: + name, _, spec = dep.partition(':') + cargo_toml['dependencies'][name] = spec or '*' + f.seek(0) + toml.dump(cargo_toml, f) + f.truncate() + + +def install_environment(prefix, version, additional_dependencies): + helpers.assert_version_default('rust', version) + directory = prefix.path( + helpers.environment_dir(ENVIRONMENT_DIR, 'default'), + ) + + if len(additional_dependencies) > 0: + _add_dependencies(prefix.path('Cargo.toml'), additional_dependencies) + + with clean_path_on_failure(directory): + cmd_output( + 'cargo', 'build', '--release', '--bins', '--target-dir', directory, + cwd=prefix.prefix_dir, + ) + + +def run_hook(prefix, hook, file_args): + with in_env(prefix): + return xargs(helpers.to_cmd(hook), file_args) diff --git a/setup.py b/setup.py index c4504774..831dc000 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ setup( 'nodeenv>=0.11.1', 'pyyaml', 'six', + 'toml', 'virtualenv', ], entry_points={ From 2a37fcd3fe53b7d03e2e563a7915446a8d87f407 Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Mon, 21 May 2018 22:02:03 -0700 Subject: [PATCH 2/5] Add support for Rust CLI dependencies Also consistently build the hook using `cargo install`. --- pre_commit/languages/rust.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index e6884c34..541a333c 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -22,7 +22,7 @@ def get_env_patch(target_dir): return ( ( 'PATH', - (os.path.join(target_dir, 'release'), os.pathsep, Var('PATH')), + (os.path.join(target_dir, 'bin'), os.pathsep, Var('PATH')), ), ) @@ -47,20 +47,36 @@ def _add_dependencies(cargo_toml_path, additional_dependencies): f.truncate() -def install_environment(prefix, version, additional_dependencies): +def install_environment(prefix, version, additional_deps): helpers.assert_version_default('rust', version) directory = prefix.path( helpers.environment_dir(ENVIRONMENT_DIR, 'default'), ) - if len(additional_dependencies) > 0: - _add_dependencies(prefix.path('Cargo.toml'), additional_dependencies) + # There are two cases where we might want to specify more dependencies: + # as dependencies for the library being built, and as binary packages + # to be `cargo install`'d. + # + # Unlike e.g. Python, if we just `cargo install` a library, it won't be + # used for compilation. And if we add a crate providing a binary to the + # `Cargo.toml`, the binary won't be built. + # + # Because of this, we allow specifying "cli" dependencies by prefixing + # with 'cli:'. + cli_deps = {dep for dep in additional_deps if dep.startswith('cli:')} + lib_deps = set(additional_deps) - cli_deps + + if len(lib_deps) > 0: + _add_dependencies(prefix.path('Cargo.toml'), lib_deps) with clean_path_on_failure(directory): - cmd_output( - 'cargo', 'build', '--release', '--bins', '--target-dir', directory, - cwd=prefix.prefix_dir, - ) + packages_to_install = {()} | {(dep[len('cli:'):],) for dep in cli_deps} + + for package in packages_to_install: + cmd_output( + 'cargo', 'install', '--bins', '--root', directory, *package, + cwd=prefix.prefix_dir + ) def run_hook(prefix, hook, file_args): From b4edf2ce50df13100eb600c7232670edc03a6651 Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Mon, 21 May 2018 22:57:30 -0700 Subject: [PATCH 3/5] Add tests for Rust --- pre_commit/languages/rust.py | 18 ++++++-- .../rust_hooks_repo/.pre-commit-hooks.yaml | 5 ++ testing/resources/rust_hooks_repo/Cargo.lock | 3 ++ testing/resources/rust_hooks_repo/Cargo.toml | 3 ++ testing/resources/rust_hooks_repo/src/main.rs | 3 ++ tests/repository_test.py | 46 +++++++++++++++++++ 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml create mode 100644 testing/resources/rust_hooks_repo/Cargo.lock create mode 100644 testing/resources/rust_hooks_repo/Cargo.toml create mode 100644 testing/resources/rust_hooks_repo/src/main.rs diff --git a/pre_commit/languages/rust.py b/pre_commit/languages/rust.py index 541a333c..41053f88 100644 --- a/pre_commit/languages/rust.py +++ b/pre_commit/languages/rust.py @@ -39,6 +39,7 @@ def in_env(prefix): def _add_dependencies(cargo_toml_path, additional_dependencies): with open(cargo_toml_path, 'r+') as f: cargo_toml = toml.load(f) + cargo_toml.setdefault('dependencies', {}) for dep in additional_dependencies: name, _, spec = dep.partition(':') cargo_toml['dependencies'][name] = spec or '*' @@ -47,7 +48,7 @@ def _add_dependencies(cargo_toml_path, additional_dependencies): f.truncate() -def install_environment(prefix, version, additional_deps): +def install_environment(prefix, version, additional_dependencies): helpers.assert_version_default('rust', version) directory = prefix.path( helpers.environment_dir(ENVIRONMENT_DIR, 'default'), @@ -63,14 +64,23 @@ def install_environment(prefix, version, additional_deps): # # Because of this, we allow specifying "cli" dependencies by prefixing # with 'cli:'. - cli_deps = {dep for dep in additional_deps if dep.startswith('cli:')} - lib_deps = set(additional_deps) - cli_deps + cli_deps = { + dep for dep in additional_dependencies if dep.startswith('cli:') + } + lib_deps = set(additional_dependencies) - cli_deps if len(lib_deps) > 0: _add_dependencies(prefix.path('Cargo.toml'), lib_deps) with clean_path_on_failure(directory): - packages_to_install = {()} | {(dep[len('cli:'):],) for dep in cli_deps} + packages_to_install = {()} + for cli_dep in cli_deps: + cli_dep = cli_dep[len('cli:'):] + package, _, version = cli_dep.partition(':') + if version != '': + packages_to_install.add((package, '--version', version)) + else: + packages_to_install.add((package,)) for package in packages_to_install: cmd_output( diff --git a/testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml b/testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml new file mode 100644 index 00000000..df1269ff --- /dev/null +++ b/testing/resources/rust_hooks_repo/.pre-commit-hooks.yaml @@ -0,0 +1,5 @@ +- id: rust-hook + name: rust example hook + entry: rust-hello-world + language: rust + files: '' diff --git a/testing/resources/rust_hooks_repo/Cargo.lock b/testing/resources/rust_hooks_repo/Cargo.lock new file mode 100644 index 00000000..36fbfda2 --- /dev/null +++ b/testing/resources/rust_hooks_repo/Cargo.lock @@ -0,0 +1,3 @@ +[[package]] +name = "rust-hello-world" +version = "0.1.0" diff --git a/testing/resources/rust_hooks_repo/Cargo.toml b/testing/resources/rust_hooks_repo/Cargo.toml new file mode 100644 index 00000000..cd83b435 --- /dev/null +++ b/testing/resources/rust_hooks_repo/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "rust-hello-world" +version = "0.1.0" diff --git a/testing/resources/rust_hooks_repo/src/main.rs b/testing/resources/rust_hooks_repo/src/main.rs new file mode 100644 index 00000000..ad379d6e --- /dev/null +++ b/testing/resources/rust_hooks_repo/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("hello world"); +} diff --git a/tests/repository_test.py b/tests/repository_test.py index 67b8f3f6..6fece071 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -20,6 +20,7 @@ from pre_commit.languages import node from pre_commit.languages import pcre from pre_commit.languages import python from pre_commit.languages import ruby +from pre_commit.languages import rust from pre_commit.repository import Repository from pre_commit.util import cmd_output from testing.fixtures import config_with_local_hooks @@ -282,6 +283,51 @@ def test_golang_hook(tempdir_factory, store): ) +@pytest.mark.integration +def test_rust_hook(tempdir_factory, store): + _test_hook_repo( + tempdir_factory, store, 'rust_hooks_repo', + 'rust-hook', [], b'hello world\n', + ) + + +@pytest.mark.integration +@pytest.mark.parametrize('dep', ('cli:shellharden:3.1.0', 'cli:shellharden')) +def test_additional_rust_cli_dependencies_installed( + tempdir_factory, store, dep, +): + path = make_repo(tempdir_factory, 'rust_hooks_repo') + config = make_config_from_repo(path) + # A small rust package with no dependencies. + config['hooks'][0]['additional_dependencies'] = [dep] + repo = Repository.create(config, store) + repo.require_installed() + (prefix, _, _, _), = repo._venvs() + binaries = os.listdir(prefix.path( + helpers.environment_dir(rust.ENVIRONMENT_DIR, 'default'), 'bin', + )) + assert 'shellharden' in binaries + + +@pytest.mark.integration +def test_additional_rust_lib_dependencies_installed( + tempdir_factory, store, +): + path = make_repo(tempdir_factory, 'rust_hooks_repo') + config = make_config_from_repo(path) + # A small rust package with no dependencies. + deps = ['shellharden:3.1.0'] + config['hooks'][0]['additional_dependencies'] = deps + repo = Repository.create(config, store) + repo.require_installed() + (prefix, _, _, _), = repo._venvs() + binaries = os.listdir(prefix.path( + helpers.environment_dir(rust.ENVIRONMENT_DIR, 'default'), 'bin', + )) + assert 'rust-hello-world' in binaries + assert 'shellharden' not in binaries + + @pytest.mark.integration def test_missing_executable(tempdir_factory, store): _test_hook_repo( From 23fe0be2863e6cf13c1b1bcf830fb1047035df8c Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Tue, 22 May 2018 20:20:46 -0700 Subject: [PATCH 4/5] Add Rust to CI --- .travis.yml | 2 ++ appveyor.yml | 2 ++ tests/repository_test.py | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8f91d702..9327173f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,8 @@ before_install: fi - git --version - './get-swift.sh && export PATH="/tmp/swift/usr/bin:$PATH"' + - 'curl -sSf https://sh.rustup.rs | bash -s -- -y' + - export PATH="$HOME/.cargo/bin:$PATH" after_success: coveralls cache: directories: diff --git a/appveyor.yml b/appveyor.yml index ddb9af3c..772caf4d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,8 @@ install: - pip install tox virtualenv --upgrade - "mkdir -p C:\\Temp" - "SET TMPDIR=C:\\Temp" + - "curl -sSf https://sh.rustup.rs | bash -s -- -y" + - "SET PATH=%USERPROFILE%\\.cargo\\bin;%PATH%" # Not a C# project build: false diff --git a/tests/repository_test.py b/tests/repository_test.py index 6fece071..ba7be1fe 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -306,6 +306,8 @@ def test_additional_rust_cli_dependencies_installed( binaries = os.listdir(prefix.path( helpers.environment_dir(rust.ENVIRONMENT_DIR, 'default'), 'bin', )) + # normalize for windows + binaries = [os.path.splitext(binary)[0] for binary in binaries] assert 'shellharden' in binaries @@ -324,6 +326,8 @@ def test_additional_rust_lib_dependencies_installed( binaries = os.listdir(prefix.path( helpers.environment_dir(rust.ENVIRONMENT_DIR, 'default'), 'bin', )) + # normalize for windows + binaries = [os.path.splitext(binary)[0] for binary in binaries] assert 'rust-hello-world' in binaries assert 'shellharden' not in binaries From 5ac2ba0f7b2139670a656b2d6030e3c083160de6 Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Thu, 24 May 2018 19:42:58 -0700 Subject: [PATCH 5/5] Make local hooks work --- pre_commit/resources/empty_template/Cargo.toml | 7 +++++++ pre_commit/resources/empty_template/main.rs | 1 + tests/repository_test.py | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 pre_commit/resources/empty_template/Cargo.toml create mode 100644 pre_commit/resources/empty_template/main.rs diff --git a/pre_commit/resources/empty_template/Cargo.toml b/pre_commit/resources/empty_template/Cargo.toml new file mode 100644 index 00000000..3dfeffaf --- /dev/null +++ b/pre_commit/resources/empty_template/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "__fake_crate" +version = "0.0.0" + +[[bin]] +name = "__fake_cmd" +path = "main.rs" diff --git a/pre_commit/resources/empty_template/main.rs b/pre_commit/resources/empty_template/main.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/pre_commit/resources/empty_template/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/tests/repository_test.py b/tests/repository_test.py index ba7be1fe..2ca399ce 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -604,6 +604,24 @@ def test_local_golang_additional_dependencies(store): assert _norm_out(ret[1]) == b"Hello, Go examples!\n" +def test_local_rust_additional_dependencies(store): + config = { + 'repo': 'local', + 'hooks': [{ + 'id': 'hello', + 'name': 'hello', + 'entry': 'hello', + 'language': 'rust', + 'additional_dependencies': ['cli:hello-cli:0.2.2'], + }], + } + repo = Repository.create(config, store) + (_, hook), = repo.hooks + ret = repo.run_hook(hook, ()) + assert ret[0] == 0 + assert _norm_out(ret[1]) == b"Hello World!\n" + + def test_reinstall(tempdir_factory, store, log_info_mock): path = make_repo(tempdir_factory, 'python_hooks_repo') config = make_config_from_repo(path)