Skip to content
Merged
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
137 changes: 97 additions & 40 deletions src/cmd/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@

//! The `clone` subcommand.

use clap::{Arg, ArgMatches, Command};
use futures::future::join_all;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command as SysCommand;

use clap::{Arg, ArgMatches, Command};
use indexmap::IndexMap;
use tokio::runtime::Runtime;

use crate::config;
use crate::config::{Locked, LockedSource};
use crate::error::*;
use crate::sess::{Session, SessionIo};
use crate::sess::{DependencyRef, DependencySource, Session, SessionIo};

/// Assemble the `clone` subcommand.
pub fn new() -> Command {
Expand All @@ -37,7 +38,7 @@ pub fn new() -> Command {
/// Execute the `clone` subcommand.
pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
let dep = &matches.get_one::<String>("name").unwrap().to_lowercase();
sess.dependency_with_name(dep)?;
let depref = sess.dependency_with_name(dep)?;

let path_mod = matches.get_one::<String>("path").unwrap(); // TODO make this option for config in the Bender.yml file?

Expand All @@ -57,6 +58,17 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
}
}

// Check if dependency is a git dependency
match sess.dependency_source(depref) {
DependencySource::Git { .. } | DependencySource::Registry => {}
DependencySource::Path { .. } => {
Err(Error::new(format!(
"Dependency `{}` is a path dependency. `clone` is only implemented for git dependencies.",
dep
)))?;
}
}

// Create dir
if !path.join(path_mod).exists()
&& !SysCommand::new("mkdir")
Expand All @@ -68,43 +80,30 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
{
Err(Error::new(format!("Creating dir {} failed", path_mod,)))?;
}
let rt = Runtime::new()?;
let io = SessionIo::new(sess);

// Copy dependency to dir for proper workflow
if path.join(path_mod).join(dep).exists() {
eprintln!("{} already has a directory in {}.", dep, path_mod);
eprintln!("Please manually ensure the correct checkout.");
} else {
let rt = Runtime::new()?;
let io = SessionIo::new(sess);

let ids = matches
.get_many::<String>("name")
.unwrap()
.map(|n| Ok((n, sess.dependency_with_name(n)?)))
.collect::<Result<Vec<_>>>()?;
debugln!("main: obtain checkouts {:?}", ids);
let checkouts = rt
.block_on(join_all(
ids.iter()
.map(|&(_, id)| io.checkout(id, false, &[]))
.collect::<Vec<_>>(),
))
.into_iter()
.collect::<Result<Vec<_>>>()?;
debugln!("main: checkouts {:#?}", checkouts);
for c in checkouts {
if let Some(s) = c.to_str() {
if !Path::new(s).exists() {
Err(Error::new(format!("`{dep}` path `{s}` does not exist")))?;
}
let command = SysCommand::new("cp")
.arg("-rf")
.arg(s)
.arg(path.join(path_mod).join(dep).to_str().unwrap())
.status();
if !command.unwrap().success() {
Err(Error::new(format!("Copying {} failed", dep,)))?;
}
let id =
sess.dependency_with_name(&matches.get_one::<String>("name").unwrap().to_lowercase())?;
debugln!("main: obtain checkout {:?}", id);
let checkout = rt.block_on(io.checkout(id, false, &[]))?;
debugln!("main: checkout {:#?}", checkout);
if let Some(s) = checkout.to_str() {
if !Path::new(s).exists() {
Err(Error::new(format!("`{dep}` path `{s}` does not exist")))?;
}
let command = SysCommand::new("cp")
.arg("-rf")
.arg(s)
.arg(path.join(path_mod).join(dep).to_str().unwrap())
.status();
if !command.unwrap().success() {
Err(Error::new(format!("Copying {} failed", dep,)))?;
}
}

Expand Down Expand Up @@ -178,7 +177,6 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
let mut new_str = String::new();
if local_file_str.contains("overrides:") {
let split = local_file_str.split('\n');
let test = split.clone().next_back().unwrap().is_empty();
for i in split {
if i.contains(dep) {
new_str.push('#');
Expand All @@ -189,7 +187,7 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
new_str.push_str(&dep_str);
}
}
if test {
if local_file_str.ends_with('\n') {
// Ensure trailing newline is not duplicated
new_str.pop();
}
Expand Down Expand Up @@ -220,6 +218,8 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
let mut locked: Locked = serde_yaml_ng::from_reader(&file)
.map_err(|cause| Error::chain(format!("Syntax error in lockfile {:?}.", path), cause))?;

let path_deps = get_path_subdeps(&io, &rt, &path.join(path_mod).join(dep), depref)?;

let mut mod_package = locked.packages[dep].clone();
mod_package.revision = None;
mod_package.version = None;
Expand All @@ -231,6 +231,19 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
.to_path_buf(),
);
locked.packages.insert(dep.to_string(), mod_package);
for path_dep in path_deps {
let mut mod_package = locked.packages[&path_dep.0].clone();
mod_package.revision = None;
mod_package.version = None;
mod_package.source = LockedSource::Path(
path_dep
.1
.strip_prefix(path)
.unwrap_or(&path_dep.1)
.to_path_buf(),
);
locked.packages.insert(path_dep.0.clone(), mod_package);
}

let file = File::create(path.join("Bender.lock"))
.map_err(|cause| Error::chain(format!("Cannot create lockfile {:?}.", path), cause))?;
Expand Down Expand Up @@ -315,12 +328,56 @@ pub fn run(sess: &Session, path: &Path, matches: &ArgMatches) -> Result<()> {
Ok(())
}

/// Create a directory symlink.
#[cfg(unix)]
fn symlink_dir(p: &Path, q: &Path) -> Result<()> {
pub fn symlink_dir(p: &Path, q: &Path) -> Result<()> {
Ok(std::os::unix::fs::symlink(p, q)?)
}

/// Create a directory symlink.
#[cfg(windows)]
fn symlink_dir(p: &Path, q: &Path) -> Result<()> {
pub fn symlink_dir(p: &Path, q: &Path) -> Result<()> {
Ok(std::os::windows::fs::symlink_dir(p, q)?)
}

/// A helper function to recursively get all path subdependencies of a dependency.
pub fn get_path_subdeps(
io: &SessionIo,
rt: &Runtime,
path: &Path,
depref: DependencyRef,
) -> Result<IndexMap<String, PathBuf>> {
let binding = IndexMap::new();
let old_path = io.get_package_path(depref);
let mut path_deps = match rt.block_on(io.dependency_manifest(depref, false, &[]))? {
Some(m) => &m.dependencies,
None => &binding,
}
.iter()
.filter_map(|(k, v)| match v {
config::Dependency::Path(p) => {
if p.starts_with(&old_path) {
Some((
k.clone(),
path.join(p.strip_prefix(&old_path).unwrap()).to_path_buf(),
))
} else {
None
}
}
_ => None,
})
.collect::<IndexMap<String, std::path::PathBuf>>();
let path_dep_list = path_deps
.iter()
.map(|(k, _)| k.clone())
.collect::<Vec<String>>();
for name in &path_dep_list {
get_path_subdeps(io, rt, path, io.sess.dependency_with_name(name)?)?
.into_iter()
.for_each(|(k, v)| {
path_deps.insert(k.clone(), v.clone());
});
}
Ok(path_deps)
}
148 changes: 141 additions & 7 deletions src/cmd/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@

//! The `snapshot` subcommand.

use clap::{Arg, ArgAction, ArgMatches, Command};
use std::path::PathBuf;
use std::process::Command as SysCommand;

use clap::{Arg, ArgAction, ArgMatches, Command};
use indexmap::IndexMap;
use tokio::runtime::Runtime;

use crate::cmd::clone::{get_path_subdeps, symlink_dir};
use crate::config::{Dependency, Locked, LockedSource};
use crate::error::*;
use crate::sess::{DependencySource, Session, SessionIo};
Expand Down Expand Up @@ -160,6 +164,25 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
}
}

let rt = Runtime::new()?;
let io = SessionIo::new(sess);
let mut path_subdeps: IndexMap<String, PathBuf> = IndexMap::new();

for (name, url, _) in &snapshot_list {
// let old_path = io.get_package_path(depref);
// let new_path = io.get_depsource_path(name, &DependencySource::Git(url.clone()));
get_path_subdeps(
&io,
&rt,
&io.get_depsource_path(name, &DependencySource::Git(url.clone())),
sess.dependency_with_name(name)?,
)?
.into_iter()
.for_each(|(k, v)| {
path_subdeps.insert(k, v);
});
}

// Update the Bender.lock file with the new hash
use std::fs::File;
let file = File::open(sess.root.join("Bender.lock"))
Expand All @@ -168,12 +191,25 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
Error::chain(format!("Syntax error in lockfile {:?}.", sess.root), cause)
})?;

for (name, url, hash) in snapshot_list {
let mut mod_package = locked.packages.get_mut(&name).unwrap().clone();
mod_package.revision = Some(hash);
for (name, url, hash) in &snapshot_list {
let mut mod_package = locked.packages.get_mut(name).unwrap().clone();
mod_package.revision = Some(hash.to_string());
mod_package.version = None;
mod_package.source = LockedSource::Git(url);
locked.packages.insert(name, mod_package);
mod_package.source = LockedSource::Git(url.to_string());
locked.packages.insert(name.to_string(), mod_package);
}

for (path_dep, path_dep_path) in &path_subdeps {
let mut mod_package = locked.packages[path_dep].clone();
mod_package.revision = None;
mod_package.version = None;
mod_package.source = LockedSource::Path(
path_dep_path
.strip_prefix(sess.root)
.unwrap_or(&path_dep_path)
.to_path_buf(),
);
locked.packages.insert(path_dep.clone(), mod_package);
}

let file = File::create(sess.root.join("Bender.lock"))
Expand All @@ -187,8 +223,106 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
let rt = Runtime::new()?;
let io = SessionIo::new(sess);
let _srcs = rt.block_on(io.sources(matches.get_flag("forcibly"), &[]))?;
}

let snapshotted_deps = snapshot_list
.iter()
.map(|(name, _, _)| name.as_str())
.collect::<Vec<&str>>();

let subdeps = path_subdeps
.iter()
.map(|(name, _)| name.as_str())
.collect::<Vec<&str>>();

// TODO may need to update symlinks
let updated_deps: Vec<&str> = [snapshotted_deps.clone(), subdeps.clone()].concat();

// Update any possible workspace symlinks
for (link_path, pkg_name) in &sess.manifest.workspace.package_links {
if updated_deps.contains(&pkg_name.as_str()) {
debugln!("main: maintaining link to {} at {:?}", pkg_name, link_path);

// Determine the checkout path for this package.
let pkg_path = if snapshotted_deps.contains(&pkg_name.as_str()) {
&io.get_depsource_path(
&pkg_name,
&DependencySource::Git(
snapshot_list
.iter()
.find(|(n, _, _)| n == pkg_name)
.unwrap()
.1
.clone(),
),
)
} else {
path_subdeps.get(pkg_name).unwrap()
};
// let pkg_path = &path.join(path_mod).join(dep);
let pkg_path = link_path
.parent()
.and_then(|path| pathdiff::diff_paths(pkg_path, path))
.unwrap_or_else(|| pkg_path.into());

// Check if there is something at the destination path that needs to be
// removed.
if link_path.exists() {
let meta = link_path.symlink_metadata().map_err(|cause| {
Error::chain(
format!("Failed to read metadata of path {:?}.", link_path),
cause,
)
})?;
if !meta.file_type().is_symlink() {
warnln!(
"[W15] Skipping link to package {} at {:?} since there is something there",
pkg_name,
link_path
);
continue;
}
if link_path.read_link().map(|d| d != pkg_path).unwrap_or(true) {
debugln!("main: removing existing link {:?}", link_path);
std::fs::remove_file(link_path).map_err(|cause| {
Error::chain(
format!("Failed to remove symlink at path {:?}.", link_path),
cause,
)
})?;
}
}

// Create the symlink if there is nothing at the destination.
if !link_path.exists() {
stageln!("Linking", "{} ({:?})", pkg_name, link_path);
if let Some(parent) = link_path.parent() {
std::fs::create_dir_all(parent).map_err(|cause| {
Error::chain(format!("Failed to create directory {:?}.", parent), cause)
})?;
}
let previous_dir = match link_path.parent() {
Some(parent) => {
let d = std::env::current_dir().unwrap();
std::env::set_current_dir(parent).unwrap();
Some(d)
}
None => None,
};
symlink_dir(&pkg_path, link_path).map_err(|cause| {
Error::chain(
format!(
"Failed to create symlink to {:?} at path {:?}.",
pkg_path, link_path
),
cause,
)
})?;
if let Some(d) = previous_dir {
std::env::set_current_dir(d).unwrap();
}
}
eprintln!("{} symlink updated", pkg_name);
}
}

Ok(())
Expand Down
Loading