use std::fs;
use std::path::PathBuf;
use clap::ValueEnum;
use xshell::Shell;
use crate::environment::{get_workspace_root, 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,
#[default]
Recent,
Existing,
}
impl LockFile {
pub fn filename(self) -> &'static str {
match self {
Self::Minimal => "Cargo-minimal.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::Recent => update_recent_lockfile(sh),
Self::Existing => {
Ok(())
}
}
}
fn restore(self, sh: &Shell) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Minimal | Self::Recent => {
let workspace_root = get_workspace_root(sh)?;
fs::copy(workspace_root.join(self.filename()), workspace_root.join(CARGO_LOCK))?;
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) -> 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 _guard = LockFileGuard::new(sh)?;
LockFile::Minimal.derive(sh)?;
LockFile::Recent.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()?;
rbmt_eprintln!("Generating minimal versions lockfile...");
remove_lock_file(sh)?;
rbmt_cmd!(sh, "cargo check --all-features -Z minimal-versions").run()?;
copy_lock_file(sh, LockFile::Minimal)?;
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()?;
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(())
}