1
0
Fork 0

Merging upstream version 4.1.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 21:52:21 +01:00
parent 43620d628e
commit 8e2d7f3f0f
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
14 changed files with 374 additions and 21 deletions

View file

@ -65,6 +65,8 @@ jobs:
if: matrix.os == 'windows-latest' && matrix.language == 'perl' if: matrix.os == 'windows-latest' && matrix.language == 'perl'
- uses: haskell/actions/setup@v2 - uses: haskell/actions/setup@v2
if: matrix.language == 'haskell' if: matrix.language == 'haskell'
- uses: r-lib/actions/setup-r@v2
if: matrix.os == 'ubuntu-latest' && matrix.language == 'r'
- name: install deps - name: install deps
run: python -mpip install -e . -r requirements-dev.txt run: python -mpip install -e . -r requirements-dev.txt

View file

@ -10,11 +10,11 @@ repos:
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer - id: requirements-txt-fixer
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.5.0 rev: v2.7.0
hooks: hooks:
- id: setup-cfg-fmt - id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports - repo: https://github.com/asottile/reorder-python-imports
rev: v3.13.0 rev: v3.14.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/) exclude: ^(pre_commit/resources/|testing/resources/python3_hooks_repo/)
@ -24,7 +24,7 @@ repos:
hooks: hooks:
- id: add-trailing-comma - id: add-trailing-comma
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.17.0 rev: v3.19.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py39-plus] args: [--py39-plus]
@ -37,7 +37,7 @@ repos:
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2 rev: v1.14.1
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-pyyaml] additional_dependencies: [types-pyyaml]

View file

@ -1,3 +1,21 @@
4.1.0 - 2025-01-20
==================
### Features
- Add `language: julia`.
- #3348 PR by @fredrikekre.
- #2689 issue @jmuchovej.
### Fixes
- Disable automatic toolchain switching for `language: golang`.
- #3304 PR by @AleksaC.
- #3300 issue by @AleksaC.
- #3149 issue by @nijel.
- Fix `language: r` installation when initiated by RStudio.
- #3389 PR by @lorenzwalthert.
- #3385 issue by @lorenzwalthert.
4.0.1 - 2024-10-08 4.0.1 - 2024-10-08
================== ==================

View file

@ -10,6 +10,7 @@ from pre_commit.languages import dotnet
from pre_commit.languages import fail from pre_commit.languages import fail
from pre_commit.languages import golang from pre_commit.languages import golang
from pre_commit.languages import haskell from pre_commit.languages import haskell
from pre_commit.languages import julia
from pre_commit.languages import lua from pre_commit.languages import lua
from pre_commit.languages import node from pre_commit.languages import node
from pre_commit.languages import perl from pre_commit.languages import perl
@ -33,6 +34,7 @@ languages: dict[str, Language] = {
'fail': fail, 'fail': fail,
'golang': golang, 'golang': golang,
'haskell': haskell, 'haskell': haskell,
'julia': julia,
'lua': lua, 'lua': lua,
'node': node, 'node': node,
'perl': perl, 'perl': perl,

View file

@ -75,6 +75,7 @@ def get_env_patch(venv: str, version: str) -> PatchesT:
return ( return (
('GOROOT', os.path.join(venv, '.go')), ('GOROOT', os.path.join(venv, '.go')),
('GOTOOLCHAIN', 'local'),
( (
'PATH', ( 'PATH', (
os.path.join(venv, 'bin'), os.pathsep, os.path.join(venv, 'bin'), os.pathsep,
@ -145,6 +146,7 @@ def install_environment(
env = no_git_env(dict(os.environ, GOPATH=gopath)) env = no_git_env(dict(os.environ, GOPATH=gopath))
env.pop('GOBIN', None) env.pop('GOBIN', None)
if version != 'system': if version != 'system':
env['GOTOOLCHAIN'] = 'local'
env['GOROOT'] = os.path.join(env_dir, '.go') env['GOROOT'] = os.path.join(env_dir, '.go')
env['PATH'] = os.pathsep.join(( env['PATH'] = os.pathsep.join((
os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'], os.path.join(env_dir, '.go', 'bin'), os.environ['PATH'],

View file

@ -0,0 +1,132 @@
from __future__ import annotations
import contextlib
import os
import shutil
from collections.abc import Generator
from collections.abc import Sequence
from pre_commit import lang_base
from pre_commit.envcontext import envcontext
from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET
from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output_b
ENVIRONMENT_DIR = 'juliaenv'
health_check = lang_base.basic_health_check
get_default_version = lang_base.basic_get_default_version
def run_hook(
prefix: Prefix,
entry: str,
args: Sequence[str],
file_args: Sequence[str],
*,
is_local: bool,
require_serial: bool,
color: bool,
) -> tuple[int, bytes]:
# `entry` is a (hook-repo relative) file followed by (optional) args, e.g.
# `bin/id.jl` or `bin/hook.jl --arg1 --arg2` so we
# 1) shell parse it and join with args with hook_cmd
# 2) prepend the hooks prefix path to the first argument (the file), unless
# it is a local script
# 3) prepend `julia` as the interpreter
cmd = lang_base.hook_cmd(entry, args)
script = cmd[0] if is_local else prefix.path(cmd[0])
cmd = ('julia', script, *cmd[1:])
return lang_base.run_xargs(
cmd,
file_args,
require_serial=require_serial,
color=color,
)
def get_env_patch(target_dir: str, version: str) -> PatchesT:
return (
('JULIA_LOAD_PATH', target_dir),
# May be set, remove it to not interfer with LOAD_PATH
('JULIA_PROJECT', UNSET),
)
@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None]:
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
with envcontext(get_env_patch(envdir, version)):
yield
def install_environment(
prefix: Prefix,
version: str,
additional_dependencies: Sequence[str],
) -> None:
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
with in_env(prefix, version):
# TODO: Support language_version with juliaup similar to rust via
# rustup
# if version != 'system':
# ...
# Copy Project.toml to hook env if it exist
os.makedirs(envdir, exist_ok=True)
project_names = ('JuliaProject.toml', 'Project.toml')
project_found = False
for project_name in project_names:
project_file = prefix.path(project_name)
if not os.path.isfile(project_file):
continue
shutil.copy(project_file, envdir)
project_found = True
break
# If no project file was found we create an empty one so that the
# package manager doesn't error
if not project_found:
open(os.path.join(envdir, 'Project.toml'), 'a').close()
# Copy Manifest.toml to hook env if it exists
manifest_names = ('JuliaManifest.toml', 'Manifest.toml')
for manifest_name in manifest_names:
manifest_file = prefix.path(manifest_name)
if not os.path.isfile(manifest_file):
continue
shutil.copy(manifest_file, envdir)
break
# Julia code to instantiate the hook environment
julia_code = """
@assert length(ARGS) > 0
hook_env = ARGS[1]
deps = join(ARGS[2:end], " ")
# We prepend @stdlib here so that we can load the package manager even
# though `get_env_patch` limits `JULIA_LOAD_PATH` to just the hook env.
pushfirst!(LOAD_PATH, "@stdlib")
using Pkg
popfirst!(LOAD_PATH)
# Instantiate the environment shipped with the hook repo. If we have
# additional dependencies we disable precompilation in this step to
# avoid double work.
precompile = isempty(deps) ? "1" : "0"
withenv("JULIA_PKG_PRECOMPILE_AUTO" => precompile) do
Pkg.instantiate()
end
# Add additional dependencies (with precompilation)
if !isempty(deps)
withenv("JULIA_PKG_PRECOMPILE_AUTO" => "1") do
Pkg.REPLMode.pkgstr("add " * deps)
end
end
"""
cmd_output_b(
'julia', '-e', julia_code, '--', envdir, *additional_dependencies,
cwd=prefix.prefix_dir,
)

View file

@ -15,27 +15,50 @@ from pre_commit.envcontext import PatchesT
from pre_commit.envcontext import UNSET from pre_commit.envcontext import UNSET
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from pre_commit.util import cmd_output_b
from pre_commit.util import win_exe from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'renv' ENVIRONMENT_DIR = 'renv'
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
get_default_version = lang_base.basic_get_default_version get_default_version = lang_base.basic_get_default_version
_RENV_ACTIVATED_OPTS = (
'--no-save', '--no-restore', '--no-site-file', '--no-environ',
)
def _execute_vanilla_r_code_as_script(
def _execute_r(
code: str, *, code: str, *,
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str, prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
cli_opts: Sequence[str],
) -> str: ) -> str:
with in_env(prefix, version), _r_code_in_tempfile(code) as f: with in_env(prefix, version), _r_code_in_tempfile(code) as f:
_, out, _ = cmd_output( _, out, _ = cmd_output(
_rscript_exec(), *RSCRIPT_OPTS, f, *args, cwd=cwd, _rscript_exec(), *cli_opts, f, *args, cwd=cwd,
) )
return out.rstrip('\n') return out.rstrip('\n')
def _execute_r_in_renv(
code: str, *,
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
) -> str:
return _execute_r(
code=code, prefix=prefix, version=version, args=args, cwd=cwd,
cli_opts=_RENV_ACTIVATED_OPTS,
)
def _execute_vanilla_r(
code: str, *,
prefix: Prefix, version: str, args: Sequence[str] = (), cwd: str,
) -> str:
return _execute_r(
code=code, prefix=prefix, version=version, args=args, cwd=cwd,
cli_opts=('--vanilla',),
)
def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str: def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str:
return _execute_vanilla_r_code_as_script( return _execute_r_in_renv(
'cat(renv::settings$r.version())', 'cat(renv::settings$r.version())',
prefix=prefix, version=version, prefix=prefix, version=version,
cwd=envdir, cwd=envdir,
@ -43,7 +66,7 @@ def _read_installed_version(envdir: str, prefix: Prefix, version: str) -> str:
def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str: def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str:
return _execute_vanilla_r_code_as_script( return _execute_r_in_renv(
'cat(as.character(getRversion()))', 'cat(as.character(getRversion()))',
prefix=prefix, version=version, prefix=prefix, version=version,
cwd=envdir, cwd=envdir,
@ -53,7 +76,7 @@ def _read_executable_version(envdir: str, prefix: Prefix, version: str) -> str:
def _write_current_r_version( def _write_current_r_version(
envdir: str, prefix: Prefix, version: str, envdir: str, prefix: Prefix, version: str,
) -> None: ) -> None:
_execute_vanilla_r_code_as_script( _execute_r_in_renv(
'renv::settings$r.version(as.character(getRversion()))', 'renv::settings$r.version(as.character(getRversion()))',
prefix=prefix, version=version, prefix=prefix, version=version,
cwd=envdir, cwd=envdir,
@ -161,7 +184,7 @@ def _cmd_from_hook(
_entry_validate(cmd) _entry_validate(cmd)
cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local) cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local)
return (cmd[0], *RSCRIPT_OPTS, *cmd_part, *args) return (cmd[0], *_RENV_ACTIVATED_OPTS, *cmd_part, *args)
def install_environment( def install_environment(
@ -204,14 +227,15 @@ def install_environment(
renv::install(prefix_dir) renv::install(prefix_dir)
}} }}
""" """
_execute_vanilla_r(
with _r_code_in_tempfile(r_code_inst_environment) as f: r_code_inst_environment,
cmd_output_b(_rscript_exec(), '--vanilla', f, cwd=env_dir) prefix=prefix, version=version, cwd=env_dir,
)
_write_current_r_version(envdir=env_dir, prefix=prefix, version=version) _write_current_r_version(envdir=env_dir, prefix=prefix, version=version)
if additional_dependencies: if additional_dependencies:
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))' r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
_execute_vanilla_r_code_as_script( _execute_r_in_renv(
code=r_code_inst_add, prefix=prefix, version=version, code=r_code_inst_add, prefix=prefix, version=version,
args=additional_dependencies, args=additional_dependencies,
cwd=env_dir, cwd=env_dir,

View file

@ -1,6 +1,6 @@
[metadata] [metadata]
name = pre_commit name = pre_commit
version = 4.0.1 version = 4.1.0
description = A framework for managing and maintaining multi-language pre-commit hooks. description = A framework for managing and maintaining multi-language pre-commit hooks.
long_description = file: README.md long_description = file: README.md
long_description_content_type = text/markdown long_description_content_type = text/markdown

View file

@ -57,8 +57,7 @@ def make_archive(name: str, repo: str, ref: str, destdir: str) -> str:
arcs.sort() arcs.sort()
with gzip.GzipFile(output_path, 'wb', mtime=0) as gzipf: with gzip.GzipFile(output_path, 'wb', mtime=0) as gzipf:
# https://github.com/python/typeshed/issues/5491 with tarfile.open(fileobj=gzipf, mode='w') as tf:
with tarfile.open(fileobj=gzipf, mode='w') as tf: # type: ignore
for arcname, abspath in arcs: for arcname, abspath in arcs:
tf.add( tf.add(
abspath, abspath,

View file

@ -1,10 +1,18 @@
from __future__ import annotations from __future__ import annotations
import pytest
from pre_commit.languages import docker_image from pre_commit.languages import docker_image
from pre_commit.util import cmd_output_b
from testing.language_helpers import run_language from testing.language_helpers import run_language
from testing.util import xfailif_windows from testing.util import xfailif_windows
@pytest.fixture(autouse=True, scope='module')
def _ensure_image_available():
cmd_output_b('docker', 'run', '--rm', 'ubuntu:22.04', 'echo')
@xfailif_windows # pragma: win32 no cover @xfailif_windows # pragma: win32 no cover
def test_docker_image_hook_via_entrypoint(tmp_path): def test_docker_image_hook_via_entrypoint(tmp_path):
ret = run_language( ret = run_language(

View file

@ -27,7 +27,7 @@ def _csproj(tool_name):
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework> <TargetFramework>net8</TargetFramework>
<PackAsTool>true</PackAsTool> <PackAsTool>true</PackAsTool>
<ToolCommandName>{tool_name}</ToolCommandName> <ToolCommandName>{tool_name}</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath> <PackageOutputPath>./nupkg</PackageOutputPath>

View file

@ -11,11 +11,13 @@ from pre_commit.commands.install_uninstall import install
from pre_commit.envcontext import envcontext from pre_commit.envcontext import envcontext
from pre_commit.languages import golang from pre_commit.languages import golang
from pre_commit.store import _make_local_repo from pre_commit.store import _make_local_repo
from pre_commit.util import CalledProcessError
from pre_commit.util import cmd_output from pre_commit.util import cmd_output
from testing.fixtures import add_config_to_repo from testing.fixtures import add_config_to_repo
from testing.fixtures import make_config_from_repo from testing.fixtures import make_config_from_repo
from testing.language_helpers import run_language from testing.language_helpers import run_language
from testing.util import cmd_output_mocked_pre_commit_home from testing.util import cmd_output_mocked_pre_commit_home
from testing.util import cwd
from testing.util import git_commit from testing.util import git_commit
@ -165,3 +167,70 @@ def test_during_commit_all(tmp_path, tempdir_factory, store, in_git_dir):
fn=cmd_output_mocked_pre_commit_home, fn=cmd_output_mocked_pre_commit_home,
tempdir_factory=tempdir_factory, tempdir_factory=tempdir_factory,
) )
def test_automatic_toolchain_switching(tmp_path):
go_mod = '''\
module toolchain-version-test
go 1.23.1
'''
main_go = '''\
package main
func main() {}
'''
tmp_path.joinpath('go.mod').write_text(go_mod)
mod_dir = tmp_path.joinpath('toolchain-version-test')
mod_dir.mkdir()
main_file = mod_dir.joinpath('main.go')
main_file.write_text(main_go)
with pytest.raises(CalledProcessError) as excinfo:
run_language(
path=tmp_path,
language=golang,
version='1.22.0',
exe='golang-version-test',
)
assert 'go.mod requires go >= 1.23.1' in excinfo.value.stderr.decode()
def test_automatic_toolchain_switching_go_fmt(tmp_path, monkeypatch):
go_mod_hook = '''\
module toolchain-version-test
go 1.22.0
'''
go_mod = '''\
module toolchain-version-test
go 1.23.1
'''
main_go = '''\
package main
func main() {}
'''
hook_dir = tmp_path.joinpath('hook')
hook_dir.mkdir()
hook_dir.joinpath('go.mod').write_text(go_mod_hook)
test_dir = tmp_path.joinpath('test')
test_dir.mkdir()
test_dir.joinpath('go.mod').write_text(go_mod)
main_file = test_dir.joinpath('main.go')
main_file.write_text(main_go)
with cwd(test_dir):
ret, out = run_language(
path=hook_dir,
language=golang,
version='1.22.0',
exe='go fmt',
file_args=(str(main_file),),
)
assert ret == 1
assert 'go.mod requires go >= 1.23.1' in out.decode()

View file

@ -0,0 +1,97 @@
from __future__ import annotations
from pre_commit.languages import julia
from testing.language_helpers import run_language
from testing.util import cwd
def _make_hook(tmp_path, julia_code):
src_dir = tmp_path.joinpath('src')
src_dir.mkdir()
src_dir.joinpath('main.jl').write_text(julia_code)
tmp_path.joinpath('Project.toml').write_text(
'[deps]\n'
'Example = "7876af07-990d-54b4-ab0e-23690620f79a"\n',
)
def test_julia_hook(tmp_path):
code = """
using Example
function main()
println("Hello, world!")
end
main()
"""
_make_hook(tmp_path, code)
expected = (0, b'Hello, world!\n')
assert run_language(tmp_path, julia, 'src/main.jl') == expected
def test_julia_hook_manifest(tmp_path):
code = """
using Example
println(pkgversion(Example))
"""
_make_hook(tmp_path, code)
tmp_path.joinpath('Manifest.toml').write_text(
'manifest_format = "2.0"\n\n'
'[[deps.Example]]\n'
'git-tree-sha1 = "11820aa9c229fd3833d4bd69e5e75ef4e7273bf1"\n'
'uuid = "7876af07-990d-54b4-ab0e-23690620f79a"\n'
'version = "0.5.4"\n',
)
expected = (0, b'0.5.4\n')
assert run_language(tmp_path, julia, 'src/main.jl') == expected
def test_julia_hook_args(tmp_path):
code = """
function main(argv)
foreach(println, argv)
end
main(ARGS)
"""
_make_hook(tmp_path, code)
expected = (0, b'--arg1\n--arg2\n')
assert run_language(
tmp_path, julia, 'src/main.jl --arg1 --arg2',
) == expected
def test_julia_hook_additional_deps(tmp_path):
code = """
using TOML
function main()
project_file = Base.active_project()
dict = TOML.parsefile(project_file)
for (k, v) in dict["deps"]
println(k, " = ", v)
end
end
main()
"""
_make_hook(tmp_path, code)
deps = ('TOML=fa267f1f-6049-4f14-aa54-33bafae1ed76',)
ret, out = run_language(tmp_path, julia, 'src/main.jl', deps=deps)
assert ret == 0
assert b'Example = 7876af07-990d-54b4-ab0e-23690620f79a' in out
assert b'TOML = fa267f1f-6049-4f14-aa54-33bafae1ed76' in out
def test_julia_repo_local(tmp_path):
env_dir = tmp_path.joinpath('envdir')
env_dir.mkdir()
local_dir = tmp_path.joinpath('local')
local_dir.mkdir()
local_dir.joinpath('local.jl').write_text(
'using TOML; foreach(println, ARGS)',
)
with cwd(local_dir):
deps = ('TOML=fa267f1f-6049-4f14-aa54-33bafae1ed76',)
expected = (0, b'--local-arg1\n--local-arg2\n')
assert run_language(
env_dir, julia, 'local.jl --local-arg1 --local-arg2',
deps=deps, is_local=True,
) == expected

View file

@ -286,7 +286,7 @@ def test_health_check_without_version(prefix, installed_environment, version):
prefix, env_dir = installed_environment prefix, env_dir = installed_environment
# simulate old pre-commit install by unsetting the installed version # simulate old pre-commit install by unsetting the installed version
r._execute_vanilla_r_code_as_script( r._execute_r_in_renv(
f'renv::settings$r.version({version})', f'renv::settings$r.version({version})',
prefix=prefix, version=C.DEFAULT, cwd=env_dir, prefix=prefix, version=C.DEFAULT, cwd=env_dir,
) )