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

add support for dart as a hook language #2027

Merged
merged 1 commit into from Aug 30, 2021
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
26 changes: 11 additions & 15 deletions azure-pipelines.yml
Expand Up @@ -26,9 +26,9 @@ jobs:
Write-Host "##vso[task.prependpath]C:\Strawberry\perl\site\bin"
Write-Host "##vso[task.prependpath]C:\Strawberry\c\bin"
displayName: Add strawberry perl to PATH
- task: PowerShell@2
inputs:
filePath: "testing/get-r.ps1"
- bash: testing/get-dart.sh
displayName: install dart
- powershell: testing/get-r.ps1
displayName: install R
- template: job--python-tox.yml@asottile
parameters:
Expand All @@ -38,13 +38,11 @@ jobs:
pre_test:
- task: UseRubyVersion@0
- template: step--git-install.yml
- bash: |
testing/get-coursier.sh
echo '##vso[task.prependpath]/tmp/coursier'
- bash: testing/get-coursier.sh
displayName: install coursier
- bash: |
testing/get-swift.sh
echo '##vso[task.prependpath]/tmp/swift/usr/bin'
- bash: testing/get-dart.sh
displayName: install dart
- bash: testing/get-swift.sh
displayName: install swift
- bash: testing/get-r.sh
displayName: install R
Expand All @@ -54,13 +52,11 @@ jobs:
os: linux
pre_test:
- task: UseRubyVersion@0
- bash: |
testing/get-coursier.sh
echo '##vso[task.prependpath]/tmp/coursier'
- bash: testing/get-coursier.sh
displayName: install coursier
- bash: |
testing/get-swift.sh
echo '##vso[task.prependpath]/tmp/swift/usr/bin'
- bash: testing/get-dart.sh
displayName: install dart
- bash: testing/get-swift.sh
displayName: install swift
- bash: testing/get-r.sh
displayName: install R
2 changes: 2 additions & 0 deletions pre_commit/languages/all.py
Expand Up @@ -7,6 +7,7 @@
from pre_commit.hook import Hook
from pre_commit.languages import conda
from pre_commit.languages import coursier
from pre_commit.languages import dart
from pre_commit.languages import docker
from pre_commit.languages import docker_image
from pre_commit.languages import dotnet
Expand Down Expand Up @@ -44,6 +45,7 @@ class Language(NamedTuple):
# BEGIN GENERATED (testing/gen-languages-all)
'conda': Language(name='conda', ENVIRONMENT_DIR=conda.ENVIRONMENT_DIR, get_default_version=conda.get_default_version, healthy=conda.healthy, install_environment=conda.install_environment, run_hook=conda.run_hook), # noqa: E501
'coursier': Language(name='coursier', ENVIRONMENT_DIR=coursier.ENVIRONMENT_DIR, get_default_version=coursier.get_default_version, healthy=coursier.healthy, install_environment=coursier.install_environment, run_hook=coursier.run_hook), # noqa: E501
'dart': Language(name='dart', ENVIRONMENT_DIR=dart.ENVIRONMENT_DIR, get_default_version=dart.get_default_version, healthy=dart.healthy, install_environment=dart.install_environment, run_hook=dart.run_hook), # noqa: E501
'docker': Language(name='docker', ENVIRONMENT_DIR=docker.ENVIRONMENT_DIR, get_default_version=docker.get_default_version, healthy=docker.healthy, install_environment=docker.install_environment, run_hook=docker.run_hook), # noqa: E501
'docker_image': Language(name='docker_image', ENVIRONMENT_DIR=docker_image.ENVIRONMENT_DIR, get_default_version=docker_image.get_default_version, healthy=docker_image.healthy, install_environment=docker_image.install_environment, run_hook=docker_image.run_hook), # noqa: E501
'dotnet': Language(name='dotnet', ENVIRONMENT_DIR=dotnet.ENVIRONMENT_DIR, get_default_version=dotnet.get_default_version, healthy=dotnet.healthy, install_environment=dotnet.install_environment, run_hook=dotnet.run_hook), # noqa: E501
Expand Down
109 changes: 109 additions & 0 deletions pre_commit/languages/dart.py
@@ -0,0 +1,109 @@
import contextlib
import os.path
import shutil
import tempfile
from typing import Generator
from typing import Sequence
from typing import Tuple

import pre_commit.constants as C
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import Var
from pre_commit.hook import Hook
from pre_commit.languages import helpers
from pre_commit.prefix import Prefix
from pre_commit.util import clean_path_on_failure
from pre_commit.util import win_exe
from pre_commit.util import yaml_load

ENVIRONMENT_DIR = 'dartenv'

get_default_version = helpers.basic_get_default_version
healthy = helpers.basic_healthy


def get_env_patch(venv: str) -> PatchesT:
return (
('PATH', (os.path.join(venv, 'bin'), os.pathsep, Var('PATH'))),
)


@contextlib.contextmanager
def in_env(prefix: Prefix) -> Generator[None, None, None]:
directory = helpers.environment_dir(ENVIRONMENT_DIR, C.DEFAULT)
envdir = prefix.path(directory)
with envcontext(get_env_patch(envdir)):
yield


def install_environment(
prefix: Prefix,
version: str,
additional_dependencies: Sequence[str],
) -> None:
helpers.assert_version_default('dart', version)

envdir = prefix.path(helpers.environment_dir(ENVIRONMENT_DIR, version))
bin_dir = os.path.join(envdir, 'bin')

def _install_dir(prefix_p: Prefix, pub_cache: str) -> None:
dart_env = {**os.environ, 'PUB_CACHE': pub_cache}

with open(prefix_p.path('pubspec.yaml')) as f:
pubspec_contents = yaml_load(f)

helpers.run_setup_cmd(prefix_p, ('dart', 'pub', 'get'), env=dart_env)

for executable in pubspec_contents['executables']:
helpers.run_setup_cmd(
prefix_p,
(
'dart', 'compile', 'exe',
'--output', os.path.join(bin_dir, win_exe(executable)),
prefix_p.path('bin', f'{executable}.dart'),
),
env=dart_env,
)

with clean_path_on_failure(envdir):
os.makedirs(bin_dir)

with tempfile.TemporaryDirectory() as tmp:
_install_dir(prefix, tmp)

for dep_s in additional_dependencies:
with tempfile.TemporaryDirectory() as dep_tmp:
dep, _, version = dep_s.partition(':')
if version:
dep_cmd: Tuple[str, ...] = (dep, '--version', version)
else:
dep_cmd = (dep,)

helpers.run_setup_cmd(
prefix,
('dart', 'pub', 'cache', 'add', *dep_cmd),
env={**os.environ, 'PUB_CACHE': dep_tmp},
)

# try and find the 'pubspec.yaml' that just got added
for root, _, filenames in os.walk(dep_tmp):
if 'pubspec.yaml' in filenames:
with tempfile.TemporaryDirectory() as copied:
pkg = os.path.join(copied, 'pkg')
shutil.copytree(root, pkg)
_install_dir(Prefix(pkg), dep_tmp)
break
else:
raise AssertionError(
f'could not find pubspec.yaml for {dep_s}',
)


def run_hook(
hook: Hook,
file_args: Sequence[str],
color: bool,
) -> Tuple[int, bytes]:
with in_env(hook.prefix):
return helpers.run_xargs(hook, hook.cmd, file_args, color=color)
4 changes: 2 additions & 2 deletions pre_commit/languages/helpers.py
Expand Up @@ -48,8 +48,8 @@ def exe_exists(exe: str) -> bool:
)


def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...]) -> None:
cmd_output_b(*cmd, cwd=prefix.prefix_dir)
def run_setup_cmd(prefix: Prefix, cmd: Tuple[str, ...], **kwargs: Any) -> None:
cmd_output_b(*cmd, cwd=prefix.prefix_dir, **kwargs)


@overload
Expand Down
3 changes: 2 additions & 1 deletion pre_commit/languages/python.py
Expand Up @@ -21,6 +21,7 @@
from pre_commit.util import clean_path_on_failure
from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.util import win_exe

ENVIRONMENT_DIR = 'py_env'

Expand Down Expand Up @@ -172,7 +173,7 @@ def healthy(prefix: Prefix, language_version: str) -> bool:
if not os.path.exists(pyvenv_cfg):
return False

exe_name = 'python.exe' if sys.platform == 'win32' else 'python'
exe_name = win_exe('python')
py_exe = prefix.path(bin_dir(envdir), exe_name)
cfg = _read_pyvenv_cfg(pyvenv_cfg)

Expand Down
4 changes: 4 additions & 0 deletions pre_commit/resources/empty_template_pubspec.yaml
@@ -0,0 +1,4 @@
name: pre_commit_empty_pubspec
environment:
sdk: '>=2.10.0'
executables: {}
2 changes: 1 addition & 1 deletion pre_commit/store.py
Expand Up @@ -189,7 +189,7 @@ def _git_cmd(*args: str) -> None:
LOCAL_RESOURCES = (
'Cargo.toml', 'main.go', 'go.mod', 'main.rs', '.npmignore',
'package.json', 'pre_commit_placeholder_package.gemspec', 'setup.py',
'environment.yml', 'Makefile.PL',
'environment.yml', 'Makefile.PL', 'pubspec.yaml',
'renv.lock', 'renv/activate.R', 'renv/LICENSE.renv',
)

Expand Down
4 changes: 4 additions & 0 deletions pre_commit/util.py
Expand Up @@ -268,3 +268,7 @@ def handle_remove_readonly(
def parse_version(s: str) -> Tuple[int, ...]:
"""poor man's version comparison"""
return tuple(int(p) for p in s.split('.'))


def win_exe(s: str) -> str:
return s if sys.platform != 'win32' else f'{s}.exe'
6 changes: 3 additions & 3 deletions testing/gen-languages-all
Expand Up @@ -2,9 +2,9 @@
import sys

LANGUAGES = [
'conda', 'coursier', 'docker', 'docker_image', 'dotnet', 'fail', 'golang',
'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust', 'script',
'swift', 'system',
'conda', 'coursier', 'dart', 'docker', 'docker_image', 'dotnet', 'fail',
'golang', 'node', 'perl', 'pygrep', 'python', 'r', 'ruby', 'rust',
'script', 'swift', 'system',
]
FIELDS = [
'ENVIRONMENT_DIR', 'get_default_version', 'healthy', 'install_environment',
Expand Down
4 changes: 3 additions & 1 deletion testing/get-coursier.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# This is a script used in CI to install coursier
set -euxo pipefail
set -euo pipefail

COURSIER_URL="https://github.com/coursier/coursier/releases/download/v2.0.0/cs-x86_64-pc-linux"
COURSIER_HASH="e2e838b75bc71b16bcb77ce951ad65660c89bda7957c79a0628ec7146d35122f"
Expand All @@ -11,3 +11,5 @@ rm -f "$ARTIFACT"
curl --location --silent --output "$ARTIFACT" "$COURSIER_URL"
echo "$COURSIER_HASH $ARTIFACT" | sha256sum --check
chmod ugo+x /tmp/coursier/cs

echo '##vso[task.prependpath]/tmp/coursier'
17 changes: 17 additions & 0 deletions testing/get-dart.sh
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail

VERSION=2.13.4

if [ "$OSTYPE" = msys ]; then
URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-windows-x64-release.zip"
echo "##vso[task.prependpath]$(cygpath -w /tmp/dart-sdk/bin)"
else
URL="https://storage.googleapis.com/dart-archive/channels/stable/release/${VERSION}/sdk/dartsdk-linux-x64-release.zip"
echo '##vso[task.prependpath]/tmp/dart-sdk/bin'
fi

curl --silent --location --output /tmp/dart.zip "$URL"

unzip -q -d /tmp /tmp/dart.zip
rm /tmp/dart.zip
4 changes: 3 additions & 1 deletion testing/get-swift.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# This is a script used in CI to install swift
set -euxo pipefail
set -euo pipefail

. /etc/lsb-release
if [ "$DISTRIB_CODENAME" = "bionic" ]; then
Expand All @@ -25,3 +25,5 @@ fi

mkdir -p /tmp/swift
tar -xf "$TGZ" --strip 1 --directory /tmp/swift

echo '##vso[task.prependpath]/tmp/swift/usr/bin'
4 changes: 4 additions & 0 deletions testing/resources/dart_repo/.pre-commit-hooks.yaml
@@ -0,0 +1,4 @@
- id: hello-world-dart
name: hello world dart
entry: hello-world-dart
language: dart
6 changes: 6 additions & 0 deletions testing/resources/dart_repo/bin/hello-world-dart.dart
@@ -0,0 +1,6 @@
import 'package:ansicolor/ansicolor.dart';

void main() {
AnsiPen pen = new AnsiPen()..red();
print("hello hello " + pen("world"));
}
10 changes: 10 additions & 0 deletions testing/resources/dart_repo/pubspec.yaml
@@ -0,0 +1,10 @@
environment:
sdk: '>=2.10.0 <3.0.0'

name: hello_world_dart

executables:
hello-world-dart:

dependencies:
ansicolor: ^2.0.1
@@ -1,4 +1,4 @@
- id: dotnet example hook
- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni
language: dotnet
Expand Down
@@ -1,4 +1,4 @@
- id: dotnet example hook
- id: dotnet-example-hook
name: dotnet example hook
entry: testeroni
language: dotnet
Expand Down
5 changes: 3 additions & 2 deletions tests/languages/python_test.py
Expand Up @@ -9,6 +9,7 @@
from pre_commit.languages import python
from pre_commit.prefix import Prefix
from pre_commit.util import make_executable
from pre_commit.util import win_exe


def test_read_pyvenv_cfg(tmpdir):
Expand Down Expand Up @@ -112,7 +113,7 @@ def test_unhealthy_python_goes_missing(python_dir):

python.install_environment(prefix, C.DEFAULT, ())

exe_name = 'python' if sys.platform != 'win32' else 'python.exe'
exe_name = win_exe('python')
py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name)
os.remove(py_exe)

Expand Down Expand Up @@ -158,7 +159,7 @@ def test_unhealthy_then_replaced(python_dir):
python.install_environment(prefix, C.DEFAULT, ())

# simulate an exe which returns an old version
exe_name = 'python.exe' if sys.platform == 'win32' else 'python'
exe_name = win_exe('python')
py_exe = prefix.path(python.bin_dir('py_env-default'), exe_name)
os.rename(py_exe, f'{py_exe}.tmp')

Expand Down
42 changes: 41 additions & 1 deletion tests/repository_test.py
Expand Up @@ -1043,10 +1043,50 @@ def test_local_perl_additional_dependencies(store):
def test_dotnet_hook(tempdir_factory, store, repo):
_test_hook_repo(
tempdir_factory, store, repo,
'dotnet example hook', [], b'Hello from dotnet!\n',
'dotnet-example-hook', [], b'Hello from dotnet!\n',
)


def test_dart_hook(tempdir_factory, store):
_test_hook_repo(
tempdir_factory, store, 'dart_repo',
'hello-world-dart', [], b'hello hello world\n',
)


def test_local_dart_additional_dependencies(store):
config = {
'repo': 'local',
'hooks': [{
'id': 'local-dart',
'name': 'local-dart',
'entry': 'hello-world-dart',
'language': 'dart',
'additional_dependencies': ['hello_world_dart'],
}],
}
hook = _get_hook(config, store, 'local-dart')
ret, out = _hook_run(hook, (), color=False)
assert (ret, _norm_out(out)) == (0, b'hello hello world\n')


def test_local_dart_additional_dependencies_versioned(store):
config = {
'repo': 'local',
'hooks': [{
'id': 'local-dart',
'name': 'local-dart',
'entry': 'secure-random -l 4 -b 16',
'language': 'dart',
'additional_dependencies': ['encrypt:5.0.0'],
}],
}
hook = _get_hook(config, store, 'local-dart')
ret, out = _hook_run(hook, (), color=False)
assert ret == 0
re_assert.Matches('^[a-f0-9]{8}\r?\n$').assert_matches(out.decode())


def test_non_installable_hook_error_for_language_version(store, caplog):
config = {
'repo': 'local',
Expand Down