Skip to content

Commit

Permalink
Merge pull request #2027 from pre-commit/dart
Browse files Browse the repository at this point in the history
add support for dart as a hook language
  • Loading branch information
asottile committed Aug 30, 2021
2 parents 2d03991 + f8e21cb commit 66c51a3
Show file tree
Hide file tree
Showing 19 changed files with 227 additions and 29 deletions.
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

0 comments on commit 66c51a3

Please sign in to comment.