Skip to content

Commit 6e42a98

Browse files
committed
Add support for julia hooks
This patch adds 2nd class support for hooks using julia as the language. pre-commit will install any dependencies defined in the hooks repo `Project.toml` file, with support for `additional_dependencies` as well. Julia doesn't (yet) have a way to install binaries/scripts so for julia hooks the `entry` value is a (relative) path to a julia script within the hooks repository. When executing a julia hook the (globally installed) julia interpreter is prepended to the entry. Example `.pre-commit-hooks.yaml`: ```yaml - id: foo name: ... language: julia entry: bin/foo.jl --arg1 ``` Example hooks repo: https://github.com/fredrikekre/runic-pre-commit/tree/fe/julia Accompanying pre-commit.com PR: pre-commit/pre-commit.com#998 Fixes pre-commit#2689.
1 parent 9da45a6 commit 6e42a98

3 files changed

Lines changed: 138 additions & 0 deletions

File tree

.pre-commit-config.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ repos:
4242
- id: mypy
4343
additional_dependencies: [types-pyyaml]
4444
exclude: ^testing/resources/
45+
- repo: https://github.com/fredrikekre/runic-pre-commit
46+
rev: fe/julia
47+
hooks:
48+
- id: runic-julia

pre_commit/all_languages.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pre_commit.languages import fail
1111
from pre_commit.languages import golang
1212
from pre_commit.languages import haskell
13+
from pre_commit.languages import julia
1314
from pre_commit.languages import lua
1415
from pre_commit.languages import node
1516
from pre_commit.languages import perl
@@ -33,6 +34,7 @@
3334
'fail': fail,
3435
'golang': golang,
3536
'haskell': haskell,
37+
'julia': julia,
3638
'lua': lua,
3739
'node': node,
3840
'perl': perl,

pre_commit/languages/julia.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from __future__ import annotations
2+
3+
import contextlib
4+
from collections.abc import Generator
5+
from collections.abc import Sequence
6+
7+
from pre_commit import lang_base
8+
from pre_commit.envcontext import envcontext
9+
from pre_commit.envcontext import PatchesT
10+
from pre_commit.prefix import Prefix
11+
from pre_commit.util import cmd_output_b
12+
13+
ENVIRONMENT_DIR = 'juliaenv'
14+
health_check = lang_base.basic_health_check
15+
get_default_version = lang_base.basic_get_default_version
16+
17+
18+
def run_hook(
19+
prefix: Prefix,
20+
entry: str,
21+
args: Sequence[str],
22+
file_args: Sequence[str],
23+
*,
24+
is_local: bool,
25+
require_serial: bool,
26+
color: bool,
27+
) -> tuple[int, bytes]:
28+
# `entry` is a (hook-repo relative) file followed by (optional) args, e.g.
29+
# `bin/id.jl` or `bin/hook.jl --arg1 --arg2` so we
30+
# 1) shell parse it and join with args with hook_cmd
31+
# 2) prepend the hooks prefix path to the first argument (the file)
32+
# 3) prepend `julia` as the interpreter
33+
cmd = lang_base.hook_cmd(entry, args)
34+
cmd = ('julia', prefix.path(cmd[0]), *cmd[1:])
35+
return lang_base.run_xargs(
36+
cmd,
37+
file_args,
38+
require_serial=require_serial,
39+
color=color,
40+
)
41+
42+
43+
def get_env_patch(target_dir: str, version: str) -> PatchesT:
44+
return (
45+
# Single entry pointing to the hook env
46+
('JULIA_LOAD_PATH', target_dir),
47+
# May be set, remove it to not interfer with LOAD_PATH
48+
('JULIA_PROJECT', ''),
49+
)
50+
51+
52+
@contextlib.contextmanager
53+
def in_env(prefix: Prefix, version: str) -> Generator[None]:
54+
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
55+
with envcontext(get_env_patch(envdir, version)):
56+
yield
57+
58+
59+
def install_environment(
60+
prefix: Prefix,
61+
version: str,
62+
additional_dependencies: Sequence[str],
63+
) -> None:
64+
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
65+
with contextlib.ExitStack() as ctx:
66+
ctx.enter_context(in_env(prefix, version))
67+
68+
# TODO: Support language_version with juliaup similar to rust via
69+
# rustup
70+
# if version != 'system':
71+
# ...
72+
73+
joined_deps = ''
74+
if len(additional_dependencies) > 0:
75+
# Pkg.REPLMode.pkgstr
76+
joined_deps = ' '.join(additional_dependencies)
77+
78+
# Julia code to setup and instantiate the hook environment
79+
# TODO: This would be easier to read and work with if it can be put in
80+
# a .jl file instead.
81+
julia_code = f"""
82+
hook_env = "{envdir}"
83+
mkdir(hook_env)
84+
# Copy Project.toml to hook env
85+
project_names = ("JuliaProject.toml", "Project.toml")
86+
project_found = false
87+
for project_name in project_names
88+
isfile(project_name) || continue
89+
cp(project_name, joinpath(hook_env, project_name))
90+
global project_found = true
91+
break
92+
end
93+
if !project_found
94+
error("No (Julia)Project.toml found in hooks repository")
95+
end
96+
97+
# Copy Manifest.toml to hook env (not mandatory)
98+
manifest_names = ("JuliaManifest.toml", "Manifest.toml")
99+
for manifest_name in manifest_names
100+
isfile(manifest_name) || continue
101+
cp(manifest_name, joinpath(hook_env, manifest_name))
102+
break
103+
end
104+
105+
# We prepend @stdlib here so that we can load the package manager even
106+
# though `get_env_patch` limits `JULIA_LOAD_PATH` to just the hook env.
107+
pushfirst!(LOAD_PATH, "@stdlib")
108+
using Pkg
109+
popfirst!(LOAD_PATH)
110+
111+
# Template in any additional_dependencies
112+
joined_deps = "{joined_deps}"
113+
114+
# Instantiate the environment shipped with the hook repo. If we have
115+
# additional dependencies we disable precompilation in this step to
116+
# avoid double work.
117+
precompile = isempty(joined_deps) ? "1" : "0"
118+
withenv("JULIA_PKG_PRECOMPILE_AUTO" => precompile) do
119+
Pkg.instantiate()
120+
end
121+
122+
# Add additional_dependencies (with precompilation)
123+
if !isempty(joined_deps)
124+
withenv("JULIA_PKG_PRECOMPILE_AUTO" => "1") do
125+
Pkg.REPLMode.pkgstr("add " * joined_deps)
126+
end
127+
end
128+
"""
129+
cmd_output_b(
130+
'julia', '-e', julia_code,
131+
cwd=prefix.prefix_dir,
132+
)

0 commit comments

Comments
 (0)