Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 80 additions & 55 deletions R/attach_sess.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,66 @@ vscode_r_prepare_sess <- function(pkg_path, managed_root, consent_dir,
}
runtime <- sess_runtime_identity()
managed_library <- sess_managed_library(managed_root, expected)
# Ask the extension for a single-use installation grant. reason is
# "missing", "mismatch", or "dependencies".
request_consent <- function(reason) {
if (!dir.exists(consent_dir)) {
stop("The extension's sess consent service is unavailable. Restart VS Code and try again.")
}
new_id_part <- function() {
temporary <- basename(tempfile(pattern = "request-", tmpdir = consent_dir))
gsub("[^A-Za-z0-9_-]", "", sub("^request-", "", temporary))
}
id <- paste0(new_id_part(), new_id_part())
if (!grepl("^[A-Za-z0-9_-]{16,64}$", id)) {
stop("Could not create a unique sess installation request.")
}
request <- paste(
"vscode-r-sess-consent-v1", id, expected, runtime, reason, sep = "\n")
request_path <- file.path(consent_dir, paste0(id, ".request"))
response_path <- file.path(consent_dir, paste0(id, ".response"))
temporary_path <- tempfile(pattern = paste0(id, "-"), tmpdir = consent_dir)
on.exit(unlink(c(temporary_path, request_path, response_path)), add = TRUE)
writeLines(request, temporary_path, useBytes = TRUE)
if (.Platform$OS.type == "unix") {
Sys.chmod(temporary_path, "0600")
}
if (!file.rename(temporary_path, request_path)) {
stop("Could not request permission to install bundled sess.")
}

deadline <- Sys.time() + timeout_seconds
response <- ""
while (Sys.time() < deadline && dir.exists(consent_dir) && !nzchar(response)) {
if (file.exists(response_path)) {
lines <- tryCatch(
readLines(response_path, warn = FALSE, n = 2L),
error = function(e) character())
if (length(lines) == 1L && lines %in% c("approve", "decline")) {
response <- lines
} else {
stop("Invalid response to the sess installation request.")
}
} else {
Sys.sleep(0.2)
}
}
response
}
configured_repo <- function() {
configured <- getOption("repos")
repo <- if ("CRAN" %in% names(configured)) {
configured[["CRAN"]]
} else if (length(configured)) {
configured[[1L]]
} else {
"https://cloud.r-project.org"
}
if (!length(repo) || is.na(repo) || !nzchar(repo) || identical(repo, "@CRAN@")) {
repo <- "https://cloud.r-project.org"
}
repo
}

loaded <- "sess" %in% loadedNamespaces()
if (loaded) {
Expand Down Expand Up @@ -74,8 +134,24 @@ vscode_r_prepare_sess <- function(pkg_path, managed_root, consent_dir,
error = function(e) character()
)
installed <- sess_find_source_library(expected, managed_library)
if (length(ready_revision) == 1L && identical(ready_revision, expected) &&
!is.null(installed)) {
ready <- length(ready_revision) == 1L && identical(ready_revision, expected) &&
!is.null(installed)
missing_dependencies <- if (ready) sess_missing_dependencies(installed) else character()
if (ready && length(missing_dependencies)) {
# The managed copy is shared by every R with this platform and version.
# Dependencies an earlier setup found in its own libraries may be absent here.
message("The vscode-R managed sess needs R packages that this R cannot find: ",
paste(missing_dependencies, collapse = ", "))
if (!identical(request_consent("dependencies"), "approve")) {
message("The missing packages were not installed. The session watcher was not attached.")
return(NULL)
}
installer <- new.env(parent = baseenv())
sys.source(installer_helper, envir = installer)
installer$sess_install_missing_dependencies(missing_dependencies, managed_library,
configured_repo())
ns <- sess_load_namespace(installed, expected)
} else if (ready) {
ns <- sess_load_namespace(installed, expected)
} else if (followed_setup) {
return(NULL)
Expand All @@ -88,48 +164,7 @@ vscode_r_prepare_sess <- function(pkg_path, managed_root, consent_dir,
} else {
"missing"
}
if (!dir.exists(consent_dir)) {
stop("The extension's sess consent service is unavailable. Restart VS Code and try again.")
}
new_id_part <- function() {
temporary <- basename(tempfile(pattern = "request-", tmpdir = consent_dir))
gsub("[^A-Za-z0-9_-]", "", sub("^request-", "", temporary))
}
id <- paste0(new_id_part(), new_id_part())
if (!grepl("^[A-Za-z0-9_-]{16,64}$", id)) {
stop("Could not create a unique sess installation request.")
}
request <- paste(
"vscode-r-sess-consent-v1", id, expected, runtime, reason, sep = "\n")
request_path <- file.path(consent_dir, paste0(id, ".request"))
response_path <- file.path(consent_dir, paste0(id, ".response"))
temporary_path <- tempfile(pattern = paste0(id, "-"), tmpdir = consent_dir)
on.exit(unlink(c(temporary_path, request_path, response_path)), add = TRUE)
writeLines(request, temporary_path, useBytes = TRUE)
if (.Platform$OS.type == "unix") {
Sys.chmod(temporary_path, "0600")
}
if (!file.rename(temporary_path, request_path)) {
stop("Could not request permission to install bundled sess.")
}

deadline <- Sys.time() + timeout_seconds
response <- ""
while (Sys.time() < deadline && dir.exists(consent_dir) && !nzchar(response)) {
if (file.exists(response_path)) {
lines <- tryCatch(
readLines(response_path, warn = FALSE, n = 2L),
error = function(e) character())
if (length(lines) == 1L && lines %in% c("approve", "decline")) {
response <- lines
} else {
stop("Invalid response to the sess installation request.")
}
} else {
Sys.sleep(0.2)
}
}
if (!identical(response, "approve")) {
if (!identical(request_consent(reason), "approve")) {
message("Bundled sess was not installed. The session watcher was not attached.")
return(NULL)
}
Expand All @@ -140,17 +175,7 @@ vscode_r_prepare_sess <- function(pkg_path, managed_root, consent_dir,
(length(ready_link) && !is.na(ready_link) && nzchar(ready_link))) {
stop("Could not clear the previous vscode-R managed sess completion marker.")
}
configured <- getOption("repos")
repo <- if ("CRAN" %in% names(configured)) {
configured[["CRAN"]]
} else if (length(configured)) {
configured[[1L]]
} else {
"https://cloud.r-project.org"
}
if (!length(repo) || is.na(repo) || !nzchar(repo) || identical(repo, "@CRAN@")) {
repo <- "https://cloud.r-project.org"
}
repo <- configured_repo()
dir.create(managed_library, recursive = TRUE, showWarnings = FALSE)
if (file.access(managed_library, 2L) != 0L) {
stop("The vscode-R managed sess library is not writable.")
Expand Down
41 changes: 31 additions & 10 deletions R/sess-package-install.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,42 @@ sess_verify_package <- function(library, required, expected_revision, interactiv
if (!identical(as.integer(status), 0L)) stop("The installed sess package failed its compatibility/load check.")
}

# Pass the caller's library search path to installation/verification children,
# including project libraries, without sourcing the caller's startup profile.
sess_set_install_environment <- function(library) {
previous <- Sys.getenv(c("R_LIBS", "R_PROFILE_USER", "R_ENVIRON_USER"),
unset = NA_character_, names = TRUE)
Sys.setenv(R_LIBS = paste(unique(c(library, .libPaths())), collapse = .Platform$path.sep),
R_PROFILE_USER = "", R_ENVIRON_USER = "")
previous
}

sess_restore_install_environment <- function(previous) {
Sys.unsetenv(names(previous)[is.na(previous)])
if (any(!is.na(previous))) do.call(Sys.setenv, as.list(previous[!is.na(previous)]))
}

# Install only the given dependencies beside an already prepared managed sess.
sess_install_missing_dependencies <- function(packages, library, repos) {
previous <- sess_set_install_environment(library)
on.exit(sess_restore_install_environment(previous), add = TRUE)
sess_install_dependencies(packages, library, repos)
still_missing <- packages[!nzchar(vapply(packages, function(package) {
system.file(package = package, lib.loc = unique(c(.libPaths(), library)))
}, ""))]
if (length(still_missing)) {
stop("Could not install: ", paste(still_missing, collapse = ", "))
}
invisible(TRUE)
}

sess_install <- function(pkg_path, library, repos, interactive = FALSE) {
description <- read.dcf(file.path(pkg_path, "DESCRIPTION"))
required <- description[1L, "Version"]
expected_revision <- description[1L, "Config/vscode-R/source-revision"]

# Pass the caller's library search path to installation/verification children,
# including project libraries, without sourcing the caller's startup profile.
keys <- c("R_LIBS", "R_PROFILE_USER", "R_ENVIRON_USER")
previous <- Sys.getenv(keys, unset = NA_character_, names = TRUE)
on.exit({
Sys.unsetenv(keys[is.na(previous)])
if (any(!is.na(previous))) do.call(Sys.setenv, as.list(previous[!is.na(previous)]))
}, add = TRUE)
Sys.setenv(R_LIBS = paste(unique(c(library, .libPaths())), collapse = .Platform$path.sep),
R_PROFILE_USER = "", R_ENVIRON_USER = "")
previous <- sess_set_install_environment(library)
on.exit(sess_restore_install_environment(previous), add = TRUE)

deps <- if ("Imports" %in% colnames(description)) description[1L, "Imports"] else ""
deps <- trimws(gsub("\\s*\\(.*\\)", "", unlist(strsplit(deps, ","))))
Expand Down
29 changes: 25 additions & 4 deletions R/sess_source.R
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,36 @@ sess_loaded_source_revision <- function() {
sess_source_revision(file.path(path, "DESCRIPTION"))
}

sess_load_namespace <- function(library, revision, normal_libraries = .libPaths()) {
paths <- unique(c(library, normal_libraries))
support_paths <- unique(c(normal_libraries, library))
sess_imports <- function(library) {
description <- read.dcf(file.path(library, "sess", "DESCRIPTION"))
imports <- if ("Imports" %in% colnames(description)) {
if ("Imports" %in% colnames(description)) {
trimws(gsub("\\s*\\(.*\\)", "", unlist(strsplit(description[1L, "Imports"], ","))))
} else {
character()
}
}

# Imports, plus ps for processx's .onLoad, that neither the normal libraries nor
# the managed library provide to this R.
sess_missing_dependencies <- function(library, normal_libraries = .libPaths()) {
support_paths <- unique(c(normal_libraries, library))
imports <- sess_imports(library)
required <- unique(c(imports, if ("processx" %in% imports) "ps"))
found <- vapply(required, function(package) {
nzchar(system.file(package = package, lib.loc = support_paths))
}, FALSE)
required[!found]
}

sess_load_namespace <- function(library, revision, normal_libraries = .libPaths()) {
paths <- unique(c(library, normal_libraries))
support_paths <- unique(c(normal_libraries, library))
imports <- sess_imports(library)
missing <- sess_missing_dependencies(library, normal_libraries)
if (length(missing)) {
stop("sess needs R packages that this R cannot find: ", paste(missing, collapse = ", "),
". Install them with the project's package manager, then restart R.")
}
# processx may load ps during .onLoad. Load it first so dependencies found
# only in the managed library remain visible without changing .libPaths().
if ("processx" %in% imports) {
Expand Down
Loading