Skip to content
Open
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
10 changes: 10 additions & 0 deletions example_projects/git-monorepo/rproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
name = "git monorepo"
r_version = "4.4"

repositories = [
]

dependencies = [
{ name = "pkg", git = "https://github.com/a2-ai/git.monorepo.pkg", branch = "main", directory = "R/pkg" },
]
3 changes: 3 additions & 0 deletions example_projects/local-monorepo/local/R/pkg/.Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
^pkg\.Rproj$
^\.Rproj\.user$
^bootstrap\.R$
1 change: 1 addition & 0 deletions example_projects/local-monorepo/local/R/pkg/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.Rproj.user
12 changes: 12 additions & 0 deletions example_projects/local-monorepo/local/R/pkg/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Package: pkg
Title: What the Package Does (One Line, Title Case)
Version: 0.0.0.9000
Authors@R:
person("First", "Last", , "first.last@example.com", role = c("aut", "cre"))
Description: What the package does (one paragraph).
License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
license
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Config/build/bootstrap: TRUE
4 changes: 4 additions & 0 deletions example_projects/local-monorepo/local/R/pkg/NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated by roxygen2: do not edit by hand

export(hello_location)
export(world)
4 changes: 4 additions & 0 deletions example_projects/local-monorepo/local/R/pkg/R/hello.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#' @export
hello_location <- function(location = pkg:::world()) {
print(paste("hello", location))
}
10 changes: 10 additions & 0 deletions example_projects/local-monorepo/local/R/pkg/bootstrap.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env Rscript

# Bootstrap script to copy shared code from monorepo into R package
# Based on tree-sitter-r pattern

files <- c("world.R")
upstream_directory <- file.path("..", "..", "src")
upstream <- file.path(upstream_directory, files)
destination <- file.path("R", files)
file.copy(upstream, destination, overwrite = TRUE)
17 changes: 17 additions & 0 deletions example_projects/local-monorepo/local/R/pkg/pkg.Rproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Version: 1.0

RestoreWorkspace: No
SaveWorkspace: No
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
Encoding: UTF-8

AutoAppendNewline: Yes
StripTrailingWhitespace: Yes
LineEndingConversion: Posix

BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace
4 changes: 4 additions & 0 deletions example_projects/local-monorepo/local/src/world.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#' @export
world <- function() {
"world"
}
10 changes: 10 additions & 0 deletions example_projects/local-monorepo/rproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
name = "local-monorepo"
r_version = "4.5"

repositories = [
]

dependencies = [
{ name = "pkg", path = "./local", directory = "R/pkg" },
]
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ pub enum ConfigDependency {
install_suggestions: bool,
#[serde(default)]
dependencies_only: bool,
#[serde(default)]
directory: Option<String>,
},
Url {
url: HttpUrl,
Expand Down
30 changes: 25 additions & 5 deletions src/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub enum Source {
path: PathBuf,
/// Only for tarballs
sha: Option<String>,
directory: Option<String>,
},
Builtin {
builtin: bool,
Expand Down Expand Up @@ -87,11 +88,18 @@ impl Source {
Self::Repository { repository } => {
table.insert("repository", Value::from(repository.as_str()));
}
Self::Local { path, sha } => {
Self::Local {
path,
sha,
directory,
} => {
table.insert("path", Value::from(path.display().to_string()));
if let Some(s) = sha {
table.insert("sha", Value::from(s));
}
if let Some(d) = directory {
table.insert("directory", Value::from(d));
}
}
Self::RUniverse {
repository,
Expand Down Expand Up @@ -228,11 +236,23 @@ impl fmt::Debug for Source {
Self::Repository { repository } => {
write!(f, "repository(url: {repository})")
}
Self::Local { path, sha } => {
if let Some(sha) = sha {
write!(f, "local(path: {}, sha: {sha})", path.display())
Self::Local {
path,
sha,
directory,
} => {
if let Some(sha_val) = sha {
write!(
f,
"local(path: {}, sha: {sha_val}, directory: {directory:?})",
path.display()
)
} else {
write!(f, "local(path: {})", path.display())
write!(
f,
"local(path: {}, directory: {directory:?})",
path.display()
)
}
}
Self::Url { url, sha } => {
Expand Down
6 changes: 5 additions & 1 deletion src/package/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,13 @@ impl Package {
/// Error only occurs if the DESCRIPTION file cannot be parsed
pub fn is_binary_package(
path: impl AsRef<Path>,
sub_dir: Option<impl AsRef<Path>>,
name: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
let path = path.as_ref();
let mut path = path.as_ref().to_owned();
if let Some(sub_dir) = sub_dir {
path = path.join(sub_dir);
}

// If the package does not have a parse-able DESCRIPTION file, the entire package is not valid and we should not try to install it
let pkg = parse_description_file_in_folder(&path)?;
Expand Down
13 changes: 11 additions & 2 deletions src/package/remotes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ pub enum PackageRemote {
Url(String),
// TODO: put more stuff here once we handle bioc in rproject.toml
Bioc(String),
Local(String),
Local {
path: String,
directory: Option<String>,
},
Other(String),
}

Expand Down Expand Up @@ -141,7 +144,13 @@ pub(crate) fn parse_remote(content: &str) -> (Option<String>, PackageRemote) {
// Who knows what it could be the package name if you have a URL
RemoteType::Url => (String::new(), PackageRemote::Url(content.to_string())),
RemoteType::Bioc => (String::new(), PackageRemote::Bioc(content.to_string())),
RemoteType::Local => (String::new(), PackageRemote::Local(content.to_string())),
RemoteType::Local => (
String::new(),
PackageRemote::Local {
path: content.to_string(),
directory: None,
},
),
};

if package_name.is_empty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ source: src/package/remotes.rs
description: "local::/pkgs/testthat"
expression: "format!(\"{name:?} => {remote:?}\")"
---
None => Local("/pkgs/testthat")
None => Local { path: "/pkgs/testthat", directory: None }
47 changes: 47 additions & 0 deletions src/r_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,53 @@ impl RCmd for RCommandLine {
src_backup_dir.push(sub_dir);
}

if src_backup_dir.join("bootstrap.R").exists() {
log::debug!(
"bootstrap.R is found for {}. Checking if Config/build/bootstrap is truthy...",
destination.display()
);
let to_bootstrap = match fs::read_to_string(src_backup_dir.join("DESCRIPTION")) {
Ok(s) => {
if !s.contains("Config/build/bootstrap: T") {
log::trace!("Config/build/bootstrap is not truthy in the DESCRIPTION file");
false
} else {
true
}
}
Err(e) => {
log::trace!(
"Could not read description file at {} to check if Config/build/bootstrap is truthy: {e}. Assuming truthy and bootstrapping...",
src_backup_dir.join("DESCRIPTION").display()
);
true
}
};

if to_bootstrap {
log::debug!("Bootstrapping {}...", destination.display());
let output = Command::new(self.effective_r_command())
.arg("-f")
.arg("bootstrap.R")
.current_dir(&src_backup_dir)
.output()
.map_err(|e| InstallError {
source: InstallErrorKind::Command(e),
})?;

if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr).map_err(|e| InstallError {
source: InstallErrorKind::Utf8(e),
})?;

log::warn!(
"Failed to bootstrap package at {}: {stderr}. Proceeding in case package has been previously bootstrapped...",
src_backup_dir.display()
);
}
}
}

let canonicalized_libraries = libraries
.iter()
.map(|lib| lib.as_ref().canonicalize())
Expand Down
2 changes: 0 additions & 2 deletions src/resolver/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,6 @@ pub struct UnresolvedDependency<'d> {
// The first parent we encountered requiring that package
pub(crate) parent: Option<Cow<'d, str>>,
pub(crate) remote: Option<PackageRemote>,
pub(crate) local_path: Option<PathBuf>,
pub(crate) url: Option<String>,
}

Expand All @@ -317,7 +316,6 @@ impl<'d> UnresolvedDependency<'d> {
version_requirement: item.version_requirement.clone(),
parent: item.parent.clone(),
remote: None,
local_path: item.local_path.clone(),
url: None,
}
}
Expand Down
69 changes: 43 additions & 26 deletions src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ pub(crate) struct QueueItem<'d> {
force_source: Option<bool>,
parent: Option<Cow<'d, str>>,
remote: Option<PackageRemote>,
local_path: Option<PathBuf>,
// Only for top level dependencies. Checks whether the config dependency is matching
// what we have in the lockfile, we have one.
matching_in_lockfile: Option<bool>,
Expand Down Expand Up @@ -138,14 +137,21 @@ impl<'d> Resolver<'d> {

fn local_lookup(
&self,
local_path: &Path,
directory: Option<String>,
item: &QueueItem<'d>,
) -> Result<(ResolvedDependency<'d>, Vec<QueueItem<'d>>), Box<dyn std::error::Error>> {
let local_path = item.local_path.as_ref().unwrap();
let canon_path = match fs::canonicalize(self.project_dir.join(local_path)) {
let local_path = local_path.to_owned();

let mut canon_path = match fs::canonicalize(self.project_dir.join(&local_path)) {
Ok(canon_path) => canon_path,
Err(_) => return Err(format!("{} doesn't exist.", local_path.display()).into()),
};

if let Some(sub_dir) = &directory {
canon_path = canon_path.join(sub_dir);
}

let (package, sha) = if canon_path.is_file() {
// We have a file, it should be a tarball.
// even though we might have to extract again in sync?
Expand Down Expand Up @@ -176,7 +182,8 @@ impl<'d> Resolver<'d> {
let (resolved_dep, deps) = ResolvedDependency::from_local_package(
&package,
Source::Local {
path: local_path.clone(),
path: local_path.to_owned(),
directory,
sha,
},
item.install_suggestions,
Expand Down Expand Up @@ -385,7 +392,7 @@ impl<'d> Resolver<'d> {
)
.into());
}
let is_binary = is_binary_package(&install_path, &package.name)?;
let is_binary = is_binary_package(&install_path, Option::<&Path>::None, &package.name)?;
let (resolved_dep, deps) = ResolvedDependency::from_url_package(
&package,
if is_binary {
Expand Down Expand Up @@ -462,7 +469,6 @@ impl<'d> Resolver<'d> {
force_source: d.force_source(),
parent: None,
remote: None,
local_path: d.local_path(),
matching_in_lockfile: self.lockfile.and_then(|l| {
l.get_package(d.name(), Some(d))
.map(|p| p.is_matching(d, &self.repo_urls))
Expand All @@ -483,25 +489,6 @@ impl<'d> Resolver<'d> {
}
}

// If we have a local path, we don't need to check anything at all, just the actual path
if item.local_path.is_some() {
match self.local_lookup(&item) {
Ok((resolved_dep, items)) => {
processed
.entry(resolved_dep.name.to_string())
.or_insert_with(HashSet::new)
.insert(item.version_requirement.clone());
result.add_found(resolved_dep);
queue.extend(items);
continue;
}
Err(e) => result
.failed
.push(UnresolvedDependency::from_item(&item).with_error(format!("{e}"))),
}
continue;
}

// First let's check if it's a builtin package if the R version is matching if the package
// is not listed from a specific repo
if !item.has_required_repo() {
Expand Down Expand Up @@ -585,6 +572,24 @@ impl<'d> Resolver<'d> {
}
}
}
PackageRemote::Local { path, directory } => {
// If we have a local path, we don't need to check anything at all, just the actual path
match self.local_lookup(path.as_ref(), directory.clone(), &item) {
Ok((resolved_dep, items)) => {
processed
.entry(resolved_dep.name.to_string())
.or_insert_with(HashSet::new)
.insert(item.version_requirement.clone());
result.add_found(resolved_dep);
queue.extend(items);
continue;
}
Err(e) => result.failed.push(
UnresolvedDependency::from_item(&item).with_error(format!("{e}")),
),
}
continue;
}
_ => {
result.failed.push(
UnresolvedDependency::from_item(&item)
Expand Down Expand Up @@ -636,7 +641,19 @@ impl<'d> Resolver<'d> {
}
}
}
Some(ConfigDependency::Local { .. }) => unreachable!("handled beforehand"),
Some(ConfigDependency::Local {
path, directory, ..
}) => match self.local_lookup(path, directory.clone(), &item) {
Ok((resolved_dep, items)) => {
result.add_found(resolved_dep);
queue.extend(items);
}
Err(e) => {
result.failed.push(
UnresolvedDependency::from_item(&item).with_error(format!("{e}")),
);
}
},
Some(ConfigDependency::Git {
git,
tag,
Expand Down
Loading
Loading