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, )