diff --git a/crates/ark/src/modules/positron/repos.R b/crates/ark/src/modules/positron/repos.R index 989a38341..76e12247b 100644 --- a/crates/ark/src/modules/positron/repos.R +++ b/crates/ark/src/modules/positron/repos.R @@ -39,3 +39,34 @@ apply_repo_defaults <- function( } options(repos = repos) } + +#' Set the Posit Package Manager repository +#' +#' Sets the Posit Package Manager repository URL for the current session. The +#' URL will be processed to point to a Linux distribution-specific binary URL if +#' applicable. +#' +#' This function only overrides the CRAN repository when Ark has previously set +#' it or when it uses placeholder `"@CRAN@"`. +#' +#' @param url A PPM repository URL. Must be in the form +#' `"https://host/repo/snapshot"`, e.g., +#' `"https://packagemanager.posit.co/cran/latest"`. +#' +#' @return `NULL`, invisibly. +#' +#' @export +.ps.setPPMRepo <- function(url) { + # Use Ark's built-in PPM binary URL detection. + url <- .ps.Call("ps_get_ppm_binary_url", url) + + # If Ark has already set the repos option, it should be safe to do so again. + repos <- getOption("repos") + if (isTRUE(attr(repos, "IDE"))) { + repos[["CRAN"]] <- url + return(options(repos = repos)) + } + + # Otherwise we defer to the existing application logic. + apply_repo_defaults(c(CRAN = url)) +} diff --git a/crates/ark/src/repos.rs b/crates/ark/src/repos.rs index 83ce87b9d..2b50e63fb 100644 --- a/crates/ark/src/repos.rs +++ b/crates/ark/src/repos.rs @@ -13,9 +13,11 @@ use std::io::BufRead; use std::io::BufReader; use std::path::PathBuf; +use anyhow::Context; use harp::exec::RFunction; use harp::exec::RFunctionExt; use harp::RObject; +use libr::SEXP; use crate::modules::ARK_ENVS; @@ -346,6 +348,28 @@ fn get_ppm_binary_package_repo(repo_url: Option) -> String { } } +#[harp::register] +pub extern "C-unwind" fn ps_get_ppm_binary_url(url: SEXP) -> anyhow::Result { + let url_string = unsafe { RObject::view(url).to::().context("`url` must be a string")? }; + if url_string.is_empty() { + return Err(anyhow::anyhow!("Empty Package Manager URL provided")); + } + + // Validate the PPM URL structure. + let parsed = url::Url::parse(&url_string).context("Invalid URL format")?; + let segments = parsed + .path_segments() + .ok_or_else(|| anyhow::anyhow!("Package Manager URL must have a path"))?; + if segments.count() != 2 { + return Err(anyhow::anyhow!( + "Package Manager URL must have exactly 2 path segments (e.g., /cran/latest)" + )); + } + + let final_url = get_ppm_binary_package_repo(Some(parsed)); + Ok(RObject::from(final_url).sexp) +} + #[cfg(test)] mod tests { use super::*;