use std::fs;
use std::path::PathBuf;
use clap::ValueEnum;
use xshell::Shell;
use crate::environment::{get_workspace_root, CmdExt, ProgressGuard};
use crate::toolchain::{prepare_toolchain, Toolchain};
const CARGO_LOCK: &str = "Cargo.lock";
const CARGO_LOCK_BACKUP: &str = "Cargo.lock.backup";
pub struct LockFileGuard {
backup_path: PathBuf,
restore_path: PathBuf,
}
impl LockFileGuard {
pub fn new(sh: &Shell) -> Result<Self, Box<dyn std::error::Error>> {
let workspace_root = get_workspace_root(sh)?;
let source = workspace_root.join(CARGO_LOCK);
let backup = workspace_root.join(CARGO_LOCK_BACKUP);
if source.exists() {
fs::copy(&source, &backup)?;
}
Ok(Self { backup_path: backup, restore_path: source })
}
}
impl Drop for LockFileGuard {
fn drop(&mut self) {
if self.backup_path.exists() {
if let Err(e) = fs::copy(&self.backup_path, &self.restore_path) {
eprintln!("Warning: Failed to restore Cargo.lock from backup: {}", e);
return;
}
if let Err(e) = fs::remove_file(&self.backup_path) {
eprintln!("Warning: Failed to remove Cargo.lock backup: {}", e);
}
}
}
}
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
pub enum LockFile {
Minimal,
Maximum,
#[default]
Recent,
Existing,
}
#[derive(Debug, Clone, Copy, ValueEnum, Default)]
pub enum GeneratableLockFile {
Minimal,
Maximum,
#[default]
Recent,
}
impl From<GeneratableLockFile> for LockFile {
fn from(lockfile: GeneratableLockFile) -> Self {
match lockfile {
GeneratableLockFile::Minimal => Self::Minimal,
GeneratableLockFile::Maximum => Self::Maximum,
GeneratableLockFile::Recent => Self::Recent,
}
}
}
impl LockFile {
pub fn filename(self) -> &'static str {
match self {
Self::Minimal => "Cargo-minimal.lock",
Self::Maximum => "Cargo-maximum.lock",
Self::Recent => "Cargo-recent.lock",
Self::Existing => CARGO_LOCK,
}
}
pub fn derive(self, sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Minimal => derive_minimal_lockfile(sh),
Self::Maximum => derive_maximum_lockfile(sh),
Self::Recent => update_recent_lockfile(sh),
Self::Existing => {
Ok(())
}
}
}
fn restore(self, sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Minimal | Self::Maximum | Self::Recent => {
let workspace_root = get_workspace_root(sh)?;
let source = workspace_root.join(self.filename());
let dest = workspace_root.join(CARGO_LOCK);
fs::copy(&source, &dest).map_err(|e| -> Box<dyn std::error::Error> {
format!(
"Failed to restore {} lockfile (workspace: {:?}, from: {:?}, to: {:?}): {}",
self.filename(),
workspace_root,
source,
dest,
e
)
.into()
})?;
Ok(())
}
Self::Existing => {
Ok(())
}
}
}
pub fn activate(self, sh: &Shell) -> Result<LockFileGuard, Box<dyn std::error::Error>> {
let guard = LockFileGuard::new(sh)?;
self.restore(sh)?;
Ok(guard)
}
}
pub fn run(
sh: &Shell,
lockfiles: &[GeneratableLockFile],
) -> Result<(), Box<dyn std::error::Error>> {
let _progress = ProgressGuard::new();
prepare_toolchain(sh, Toolchain::Nightly)?;
let workspace_root = get_workspace_root(sh)?;
rbmt_eprintln!("Updating lock files in: {}", workspace_root.display());
let _lockfile_guard = LockFileGuard::new(sh)?;
for &lockfile in lockfiles {
LockFile::from(lockfile).derive(sh)?;
}
rbmt_eprintln!("Lock files updated successfully");
Ok(())
}
fn derive_minimal_lockfile(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
rbmt_eprintln!("Checking direct minimal versions...");
remove_lock_file(sh)?;
rbmt_cmd!(sh, "cargo check --all-features -Z direct-minimal-versions").run_with_capture()?;
rbmt_eprintln!("Generating minimal versions lockfile...");
remove_lock_file(sh)?;
rbmt_cmd!(sh, "cargo check --all-features -Z minimal-versions").run_with_capture()?;
copy_lock_file(sh, LockFile::Minimal)?;
Ok(())
}
fn derive_maximum_lockfile(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
rbmt_eprintln!("Generating maximum versions lockfile...");
remove_lock_file(sh)?;
rbmt_cmd!(sh, "cargo generate-lockfile").run_with_capture()?;
copy_lock_file(sh, LockFile::Maximum)?;
Ok(())
}
fn update_recent_lockfile(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
rbmt_eprintln!("Generating recent versions lockfile...");
remove_lock_file(sh)?;
let _ = LockFile::Recent.restore(sh);
rbmt_cmd!(sh, "cargo check --all-features").run_with_capture()?;
copy_lock_file(sh, LockFile::Recent)?;
Ok(())
}
fn remove_lock_file(sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
let lock_path = get_workspace_root(sh)?.join(CARGO_LOCK);
if lock_path.exists() {
fs::remove_file(&lock_path)?;
}
Ok(())
}
fn copy_lock_file(sh: &Shell, target: LockFile) -> Result<(), Box<dyn std::error::Error>> {
let workspace_root = get_workspace_root(sh)?;
fs::copy(workspace_root.join(CARGO_LOCK), workspace_root.join(target.filename()))?;
Ok(())
}