use crate::utils::{cargo, normalize, Error, Result, INTERNAL_ERR};
use cargo_metadata::Metadata;
use clap::{crate_version, Clap};
use crates_io_api::SyncClient;
use semver::Version;
use std::{
env::{current_dir, var_os},
fs::{create_dir_all, remove_file, write},
path::PathBuf,
process::Command,
};
#[derive(Debug, Clap)]
pub struct Dep {
dep: String,
#[clap(short, long)]
version: Option<Version>,
#[clap(long, hidden = true, requires_all = &["name", "path", "lib-path"])]
to_version: Option<Version>,
#[clap(long, hidden = true, requires_all = &["name", "to-version", "lib-path"], conflicts_with_all = &["version"])]
path: Option<String>,
#[clap(long, hidden = true, requires_all = &["path", "to-version", "lib-path"])]
name: Option<String>,
#[clap(long, hidden = true, requires_all = &["path", "name", "to-version"])]
lib_path: Option<String>,
#[clap(long, hidden = true)]
suppress_cargo_output: bool,
}
fn get_path(path: &Option<String>) -> Result<String> {
let path = current_dir()?.join(path.as_ref().expect(INTERNAL_ERR));
Ok(format!(
r#"{{ path = "{}" }}"#,
path.canonicalize().unwrap().to_string_lossy(),
))
}
impl Dep {
pub fn run(&self, metadata: Metadata) -> Result {
let dep = normalize(&self.dep);
let pkg = metadata
.packages
.iter()
.find(|x| normalize(&x.name) == dep)
.ok_or(Error::PackageNotFound {
id: self.dep.clone(),
})?;
let (upgrader, upgrader_version, to_version, lib_version) = if let Some(name) = &self.name {
(
name.to_string(),
get_path(&self.path)?,
self.to_version.as_ref().expect(INTERNAL_ERR).to_string(),
get_path(&self.lib_path)?,
)
} else {
let upgrader = format!("{}_up", &dep);
let client = SyncClient::new();
let krate = client.get_crate(&upgrader).map_err(|_| Error::NoUpgrader {
id: dep.clone(),
upgrader,
})?;
(
krate.crate_data.name.clone(),
self.version
.as_ref()
.map_or_else(|| krate.crate_data.max_version.clone(), |x| x.to_string()),
String::from("3.0.0-beta.1"),
format!(r#""={}""#, crate_version!()),
)
};
let cargo_home = PathBuf::from(var_os("CARGO_HOME").ok_or(Error::NoCargoHome)?);
let cache_dir = cargo_home.join("cargo-up-cache");
let lock_file = cache_dir.join("Cargo.lock");
if lock_file.exists() {
remove_file(lock_file)?;
}
create_dir_all(cache_dir.join("src"))?;
write(
cache_dir.join("Cargo.toml"),
format!(
r#"
[package]
name = "runner"
version = "0.0.0"
edition = "2018"
publish = false
[dependencies]
log = "0.4"
env_logger = "0.7"
cargo-up = {}
{} = {}
"#,
lib_version, upgrader, upgrader_version
),
)?;
write(
cache_dir.join("src").join("main.rs"),
format!(
r#"
use cargo_up::{{semver::Version, Runner}};
use std::path::Path;
// To type check the returned runner
fn runner() -> Runner {{
{}::runner()
}}
fn main() {{
env_logger::builder()
.format_timestamp(None)
.init();
runner().run(
Path::new("{}"),
"{}",
Version::parse("{}").unwrap(),
Version::parse("{}").unwrap(),
).unwrap();
}}
"#,
&upgrader,
&metadata.workspace_root.to_string_lossy(),
&dep,
pkg.version.to_string(),
&to_version,
),
)?;
let (_, err) = cargo(&cache_dir, &["build"], !self.suppress_cargo_output)?;
if !err.contains("Finished") {
panic!("unable to build");
}
let status = Command::new(cache_dir.join("target").join("debug").join("runner"))
.current_dir(&cache_dir)
.spawn()
.map_err(|err| Error::Runner { err })?
.wait()?;
if !status.success() {
panic!("exit status bad");
}
Ok(())
}
}