Your IP : 3.135.220.239
from __future__ import annotations
import contextlib
import os
import shlex
import shutil
import tempfile
import textwrap
from typing import Generator
from typing 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
from pre_commit.util import win_exe
ENVIRONMENT_DIR = 'renv'
RSCRIPT_OPTS = ('--no-save', '--no-restore', '--no-site-file', '--no-environ')
get_default_version = lang_base.basic_get_default_version
health_check = lang_base.basic_health_check
@contextlib.contextmanager
def _r_code_in_tempfile(code: str) -> Generator[str, None, None]:
"""
To avoid quoting and escaping issues, avoid `Rscript [options] -e {expr}`
but use `Rscript [options] path/to/file_with_expr.R`
"""
with tempfile.TemporaryDirectory() as tmpdir:
fname = os.path.join(tmpdir, 'script.R')
with open(fname, 'w') as f:
f.write(_inline_r_setup(textwrap.dedent(code)))
yield fname
def get_env_patch(venv: str) -> PatchesT:
return (
('R_PROFILE_USER', os.path.join(venv, 'activate.R')),
('RENV_PROJECT', UNSET),
)
@contextlib.contextmanager
def in_env(prefix: Prefix, version: str) -> Generator[None, None, None]:
envdir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
with envcontext(get_env_patch(envdir)):
yield
def _prefix_if_file_entry(
entry: list[str],
prefix: Prefix,
*,
is_local: bool,
) -> Sequence[str]:
if entry[1] == '-e' or is_local:
return entry[1:]
else:
return (prefix.path(entry[1]),)
def _rscript_exec() -> str:
r_home = os.environ.get('R_HOME')
if r_home is None:
return 'Rscript'
else:
return os.path.join(r_home, 'bin', win_exe('Rscript'))
def _entry_validate(entry: list[str]) -> None:
"""
Allowed entries:
# Rscript -e expr
# Rscript path/to/file
"""
if entry[0] != 'Rscript':
raise ValueError('entry must start with `Rscript`.')
if entry[1] == '-e':
if len(entry) > 3:
raise ValueError('You can supply at most one expression.')
elif len(entry) > 2:
raise ValueError(
'The only valid syntax is `Rscript -e {expr}`'
'or `Rscript path/to/hook/script`',
)
def _cmd_from_hook(
prefix: Prefix,
entry: str,
args: Sequence[str],
*,
is_local: bool,
) -> tuple[str, ...]:
cmd = shlex.split(entry)
_entry_validate(cmd)
cmd_part = _prefix_if_file_entry(cmd, prefix, is_local=is_local)
return (cmd[0], *RSCRIPT_OPTS, *cmd_part, *args)
def install_environment(
prefix: Prefix,
version: str,
additional_dependencies: Sequence[str],
) -> None:
lang_base.assert_version_default('r', version)
env_dir = lang_base.environment_dir(prefix, ENVIRONMENT_DIR, version)
os.makedirs(env_dir, exist_ok=True)
shutil.copy(prefix.path('renv.lock'), env_dir)
shutil.copytree(prefix.path('renv'), os.path.join(env_dir, 'renv'))
r_code_inst_environment = f"""\
prefix_dir <- {prefix.prefix_dir!r}
options(
repos = c(CRAN = "https://cran.rstudio.com"),
renv.consent = TRUE
)
source("renv/activate.R")
renv::restore()
activate_statement <- paste0(
'suppressWarnings({{',
'old <- setwd("', getwd(), '"); ',
'source("renv/activate.R"); ',
'setwd(old); ',
'renv::load("', getwd(), '");}})'
)
writeLines(activate_statement, 'activate.R')
is_package <- tryCatch(
{{
path_desc <- file.path(prefix_dir, 'DESCRIPTION')
suppressWarnings(desc <- read.dcf(path_desc))
"Package" %in% colnames(desc)
}},
error = function(...) FALSE
)
if (is_package) {{
renv::install(prefix_dir)
}}
"""
with _r_code_in_tempfile(r_code_inst_environment) as f:
cmd_output_b(_rscript_exec(), '--vanilla', f, cwd=env_dir)
if additional_dependencies:
r_code_inst_add = 'renv::install(commandArgs(trailingOnly = TRUE))'
with in_env(prefix, version):
with _r_code_in_tempfile(r_code_inst_add) as f:
cmd_output_b(
_rscript_exec(), *RSCRIPT_OPTS,
f,
*additional_dependencies,
cwd=env_dir,
)
def _inline_r_setup(code: str) -> str:
"""
Some behaviour of R cannot be configured via env variables, but can
only be configured via R options once R has started. These are set here.
"""
with_option = [
textwrap.dedent("""\
options(
install.packages.compile.from.source = "never",
pkgType = "binary"
)
"""),
code,
]
return '\n'.join(with_option)
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]:
cmd = _cmd_from_hook(prefix, entry, args, is_local=is_local)
return lang_base.run_xargs(
cmd,
file_args,
require_serial=require_serial,
color=color,
)