feat: Add new cmdline option --no-track (#1111)

Same as `cargo-install`'s `--no-track`.
It is also passed to `cargo-install` if it is invoked.

Also fixed `fs::atomic_symlink_file` which on Windows could fallback to
non-atomic install if symlinking failed.

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-06-03 19:15:18 +10:00 committed by GitHub
parent a849db3ef4
commit 1432093dcc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 194 additions and 43 deletions

View file

@ -11,7 +11,10 @@ use tracing::debug;
use crate::{
errors::BinstallError,
fs::{atomic_install, atomic_symlink_file},
fs::{
atomic_install, atomic_install_noclobber, atomic_symlink_file,
atomic_symlink_file_noclobber,
},
helpers::download::ExtractedFiles,
manifests::cargo_toml_binstall::{PkgFmt, PkgMeta},
};
@ -180,28 +183,48 @@ impl BinFile {
}
}
pub fn install_bin(&self) -> Result<(), BinstallError> {
fn pre_install_bin(&self) -> Result<(), BinstallError> {
if !self.source.try_exists()? {
return Err(BinstallError::BinFileNotFound(self.source.clone()));
}
debug!(
"Atomically install file from '{}' to '{}'",
self.source.display(),
self.dest.display()
);
#[cfg(unix)]
std::fs::set_permissions(
&self.source,
std::os::unix::fs::PermissionsExt::from_mode(0o755),
)?;
Ok(())
}
pub fn install_bin(&self) -> Result<(), BinstallError> {
self.pre_install_bin()?;
debug!(
"Atomically install file from '{}' to '{}'",
self.source.display(),
self.dest.display()
);
atomic_install(&self.source, &self.dest)?;
Ok(())
}
pub fn install_bin_noclobber(&self) -> Result<(), BinstallError> {
self.pre_install_bin()?;
debug!(
"Installing file from '{}' to '{}' only if dst not exists",
self.source.display(),
self.dest.display()
);
atomic_install_noclobber(&self.source, &self.dest)?;
Ok(())
}
pub fn install_link(&self) -> Result<(), BinstallError> {
if let Some(link) = &self.link {
let dest = self.link_dest();
@ -216,6 +239,20 @@ impl BinFile {
Ok(())
}
pub fn install_link_noclobber(&self) -> Result<(), BinstallError> {
if let Some(link) = &self.link {
let dest = self.link_dest();
debug!(
"Create link '{}' pointing to '{}' only if dst not exists",
link.display(),
dest.display()
);
atomic_symlink_file_noclobber(dest, link)?;
}
Ok(())
}
fn link_dest(&self) -> &Path {
if cfg!(target_family = "unix") {
Path::new(self.dest.file_name().unwrap())

View file

@ -3,6 +3,54 @@ use std::{fs, io, path::Path};
use tempfile::{NamedTempFile, TempPath};
use tracing::{debug, warn};
fn copy_to_tempfile(src: &Path, dst: &Path) -> io::Result<NamedTempFile> {
let mut src_file = fs::File::open(src)?;
let parent = dst.parent().unwrap();
debug!("Creating named tempfile at '{}'", parent.display());
let mut tempfile = NamedTempFile::new_in(parent)?;
debug!(
"Copying from '{}' to '{}'",
src.display(),
tempfile.path().display()
);
io::copy(&mut src_file, tempfile.as_file_mut())?;
debug!("Retrieving permissions of '{}'", src.display());
let permissions = src_file.metadata()?.permissions();
debug!(
"Setting permissions of '{}' to '{permissions:#?}'",
tempfile.path().display()
);
tempfile.as_file().set_permissions(permissions)?;
Ok(tempfile)
}
/// Install a file.
///
/// This is a blocking function, must be called in `block_in_place` mode.
pub fn atomic_install_noclobber(src: &Path, dst: &Path) -> io::Result<()> {
debug!(
"Attempting to rename from '{}' to '{}'.",
src.display(),
dst.display()
);
let tempfile = copy_to_tempfile(src, dst)?;
debug!(
"Persisting '{}' to '{}', fail if dst already exists",
tempfile.path().display(),
dst.display()
);
tempfile.persist_noclobber(dst)?;
Ok(())
}
/// Atomically install a file.
///
/// This is a blocking function, must be called in `block_in_place` mode.
@ -33,29 +81,7 @@ pub fn atomic_install(src: &Path, dst: &Path) -> io::Result<()> {
// Fallback to creating NamedTempFile on the parent dir of
// dst.
let mut src_file = fs::File::open(src)?;
let parent = dst.parent().unwrap();
debug!("Creating named tempfile at '{}'", parent.display());
let mut tempfile = NamedTempFile::new_in(parent)?;
debug!(
"Copying from '{}' to '{}'",
src.display(),
tempfile.path().display()
);
io::copy(&mut src_file, tempfile.as_file_mut())?;
debug!("Retrieving permissions of '{}'", src.display());
let permissions = src_file.metadata()?.permissions();
debug!(
"Setting permissions of '{}' to '{permissions:#?}'",
tempfile.path().display()
);
tempfile.as_file().set_permissions(permissions)?;
persist(tempfile.into_temp_path(), dst)?;
persist(copy_to_tempfile(src, dst)?.into_temp_path(), dst)?;
} else {
debug!("Attempting at atomically succeeded.");
}
@ -63,17 +89,30 @@ pub fn atomic_install(src: &Path, dst: &Path) -> io::Result<()> {
Ok(())
}
fn symlink_file(original: &Path, link: &Path) -> io::Result<()> {
fn symlink_file_inner(dest: &Path, link: &Path) -> io::Result<()> {
#[cfg(target_family = "unix")]
std::os::unix::fs::symlink(original, link)?;
std::os::unix::fs::symlink(dest, link)?;
// Symlinks on Windows are disabled in some editions, so creating one is unreliable.
#[cfg(target_family = "windows")]
std::os::windows::fs::symlink_file(original, link)
.or_else(|_| std::fs::copy(original, link).map(drop))?;
std::os::windows::fs::symlink_file(dest, link)?;
Ok(())
}
pub fn atomic_symlink_file_noclobber(dest: &Path, link: &Path) -> io::Result<()> {
match symlink_file_inner(dest, link) {
Ok(_) => Ok(()),
#[cfg(target_family = "windows")]
// Symlinks on Windows are disabled in some editions, so creating one is unreliable.
// Fallback to copy if it fails.
Err(_) => atomic_install_noclobber(dest, link),
#[cfg(not(target_family = "windows"))]
Err(err) => Err(err),
}
}
/// Atomically install symlink "link" to a file "dst".
///
/// This is a blocking function, must be called in `block_in_place` mode.
@ -82,6 +121,8 @@ pub fn atomic_symlink_file(dest: &Path, link: &Path) -> io::Result<()> {
debug!("Creating tempPath at '{}'", parent.display());
let temp_path = NamedTempFile::new_in(parent)?.into_temp_path();
// Remove this file so that we can create a symlink
// with the name.
fs::remove_file(&temp_path)?;
debug!(
@ -89,9 +130,18 @@ pub fn atomic_symlink_file(dest: &Path, link: &Path) -> io::Result<()> {
temp_path.display(),
dest.display()
);
symlink_file(dest, &temp_path)?;
persist(temp_path, link)
match symlink_file_inner(dest, &temp_path) {
Ok(_) => persist(temp_path, link),
#[cfg(target_family = "windows")]
// Symlinks on Windows are disabled in some editions, so creating one is unreliable.
// Fallback to copy if it fails.
Err(_) => atomic_install(dest, link),
#[cfg(not(target_family = "windows"))]
Err(err) => Err(err),
}
}
fn persist(temp_path: TempPath, to: &Path) -> io::Result<()> {

View file

@ -25,6 +25,7 @@ pub struct Options {
pub force: bool,
pub quiet: bool,
pub locked: bool,
pub no_track: bool,
pub version_req: Option<VersionReq>,
pub manifest_path: Option<PathBuf>,

View file

@ -51,15 +51,26 @@ impl Resolution {
impl ResolutionFetch {
pub fn install(self, opts: &Options) -> Result<CrateInfo, BinstallError> {
type InstallFp = fn(&bins::BinFile) -> Result<(), BinstallError>;
let (install_bin, install_link): (InstallFp, InstallFp) = match (opts.no_track, opts.force)
{
(true, true) | (false, _) => (bins::BinFile::install_bin, bins::BinFile::install_link),
(true, false) => (
bins::BinFile::install_bin_noclobber,
bins::BinFile::install_link_noclobber,
),
};
info!("Installing binaries...");
for file in &self.bin_files {
file.install_bin()?;
install_bin(file)?;
}
// Generate symlinks
if !opts.no_symlinks {
for file in &self.bin_files {
file.install_link()?;
install_link(file)?;
}
}
@ -158,6 +169,10 @@ impl ResolutionSource {
cmd.arg("--root").arg(cargo_root);
}
if opts.no_track {
cmd.arg("--no-track");
}
if !opts.dry_run {
let mut child = opts
.jobserver_client