Optimize bins::BinFile (#422)

* Extract `infer_bin_dir_template` and call it once in `collect_bin_files`
* Ensure `product.name.is_some()` is true
* Avoid cloning `str` in `BinFile::from_product`
* Optimize: Take reference in `bins::Data`
* Optimize: Construct `CompactString` only when needed

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2022-09-26 06:39:41 +10:00 committed by GitHub
parent 3da5cb9d9c
commit 2cc12f9b69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 64 deletions

View file

@ -28,6 +28,40 @@ fn is_valid_path(path: &Path) -> bool {
) )
} }
/// Must be called after the archive is downloaded and extracted.
/// This function might uses blocking I/O.
pub fn infer_bin_dir_template(data: &Data) -> Cow<'static, str> {
let name = &data.name;
let target = &data.target;
let version = &data.version;
// Make sure to update
// fetchers::gh_crate_meta::hosting::{FULL_FILENAMES,
// NOVERSION_FILENAMES} if you update this array.
let possible_dirs = [
format!("{name}-{target}-v{version}"),
format!("{name}-{target}-{version}"),
format!("{name}-{version}-{target}"),
format!("{name}-v{version}-{target}"),
format!("{name}-{target}"),
name.to_string(),
];
let default_bin_dir_template = Cow::Borrowed("{ bin }{ binary_ext }");
possible_dirs
.into_iter()
.find(|dirname| Path::new(dirname).is_dir())
.map(|mut dir| {
dir.reserve_exact(1 + default_bin_dir_template.len());
dir += "/";
dir += &default_bin_dir_template;
Cow::Owned(dir)
})
// Fallback to no dir
.unwrap_or(default_bin_dir_template)
}
pub struct BinFile { pub struct BinFile {
pub base_name: CompactString, pub base_name: CompactString,
pub source: PathBuf, pub source: PathBuf,
@ -36,10 +70,12 @@ pub struct BinFile {
} }
impl BinFile { impl BinFile {
/// Must be called after the archive is downloaded and extracted. pub fn from_product(
/// This function might uses blocking I/O. data: &Data<'_>,
pub fn from_product(data: &Data, product: &Product) -> Result<Self, BinstallError> { product: &Product,
let base_name = CompactString::from(product.name.clone().unwrap()); bin_dir: &str,
) -> Result<Self, BinstallError> {
let base_name = product.name.as_deref().unwrap();
let binary_ext = if data.target.contains("windows") { let binary_ext = if data.target.contains("windows") {
".exe" ".exe"
@ -48,63 +84,34 @@ impl BinFile {
}; };
let ctx = Context { let ctx = Context {
name: &data.name, name: data.name,
repo: data.repo.as_ref().map(|s| &s[..]), repo: data.repo,
target: &data.target, target: data.target,
version: &data.version, version: data.version,
bin: &base_name, bin: base_name,
format: binary_ext, format: binary_ext,
binary_ext, binary_ext,
}; };
// Generate install paths // Generate install paths
// Source path is the download dir + the generated binary path // Source path is the download dir + the generated binary path
let source_file_path = if let Some(bin_dir) = &data.meta.bin_dir { let path = ctx.render(bin_dir)?;
let path = ctx.render(bin_dir)?;
let path_normalized = Path::new(&path).normalize();
if path_normalized.components().next().is_none() { let path_normalized = Path::new(&path).normalize();
return Err(BinstallError::EmptySourceFilePath);
}
if !is_valid_path(&path_normalized) { if path_normalized.components().next().is_none() {
return Err(BinstallError::InvalidSourceFilePath { return Err(BinstallError::EmptySourceFilePath);
path: path_normalized.into_owned(), }
});
}
match path_normalized { if !is_valid_path(&path_normalized) {
Cow::Borrowed(..) => path, return Err(BinstallError::InvalidSourceFilePath {
Cow::Owned(path) => path.to_string_lossy().into_owned(), path: path_normalized.into_owned(),
} });
} else { }
let name = ctx.name;
let target = ctx.target;
let version = ctx.version;
let bin = ctx.bin;
// Make sure to update let source_file_path = match path_normalized {
// fetchers::gh_crate_meta::hosting::{FULL_FILENAMES, Cow::Borrowed(..) => path,
// NOVERSION_FILENAMES} if you update this array. Cow::Owned(path) => path.to_string_lossy().into_owned(),
let possible_dirs = [
format!("{name}-{target}-v{version}"),
format!("{name}-{target}-{version}"),
format!("{name}-{version}-{target}"),
format!("{name}-v{version}-{target}"),
format!("{name}-{target}"),
name.to_string(),
];
let dir = possible_dirs
.into_iter()
.find(|dirname| Path::new(dirname).is_dir())
.map(Cow::Owned)
// Fallback to no dir
.unwrap_or_else(|| Cow::Borrowed("."));
debug!("Using dir = {dir}");
format!("{dir}/{bin}{binary_ext}")
}; };
let source = if data.meta.pkg_fmt == Some(PkgFmt::Bin) { let source = if data.meta.pkg_fmt == Some(PkgFmt::Bin) {
@ -123,7 +130,7 @@ impl BinFile {
.join(&ctx.render("{ bin }{ binary-ext }")?); .join(&ctx.render("{ bin }{ binary-ext }")?);
Ok(Self { Ok(Self {
base_name, base_name: CompactString::from(base_name),
source, source,
dest, dest,
link, link,
@ -199,11 +206,11 @@ impl BinFile {
} }
/// Data required to get bin paths /// Data required to get bin paths
pub struct Data { pub struct Data<'a> {
pub name: String, pub name: &'a str,
pub target: String, pub target: &'a str,
pub version: String, pub version: &'a str,
pub repo: Option<String>, pub repo: Option<&'a str>,
pub meta: PkgMeta, pub meta: PkgMeta,
pub bin_path: PathBuf, pub bin_path: PathBuf,
pub install_path: PathBuf, pub install_path: PathBuf,

View file

@ -11,7 +11,7 @@ pub enum RepositoryHost {
Unknown, Unknown,
} }
/// Make sure to update possible_dirs in `bins::BinFile` /// Make sure to update possible_dirs in `bins::infer_bin_dir_template`
/// if you modified FULL_FILENAMES or NOVERSION_FILENAMES. /// if you modified FULL_FILENAMES or NOVERSION_FILENAMES.
pub const FULL_FILENAMES: &[&str] = &[ pub const FULL_FILENAMES: &[&str] = &[
"{ name }-{ target }-v{ version }.{ archive-format }", "{ name }-{ target }-v{ version }.{ archive-format }",

View file

@ -1,4 +1,5 @@
use std::{ use std::{
borrow::Cow,
collections::BTreeSet, collections::BTreeSet,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
@ -168,7 +169,7 @@ async fn resolve_inner(
} }
} }
let (meta, binaries) = ( let (meta, mut binaries) = (
package package
.metadata .metadata
.as_ref() .as_ref()
@ -177,6 +178,8 @@ async fn resolve_inner(
manifest.bin, manifest.bin,
); );
binaries.retain(|product| product.name.is_some());
// Check binaries // Check binaries
if binaries.is_empty() { if binaries.is_empty() {
return Err(BinstallError::UnspecifiedBinaries); return Err(BinstallError::UnspecifiedBinaries);
@ -342,19 +345,26 @@ fn collect_bin_files(
// List files to be installed // List files to be installed
// based on those found via Cargo.toml // based on those found via Cargo.toml
let bin_data = bins::Data { let bin_data = bins::Data {
name: package.name.clone(), name: &package.name,
target: fetcher.target().to_string(), target: fetcher.target(),
version: package.version.clone(), version: &package.version,
repo: package.repository.clone(), repo: package.repository.as_deref(),
meta, meta,
bin_path, bin_path,
install_path, install_path,
}; };
let bin_dir = bin_data
.meta
.bin_dir
.as_deref()
.map(Cow::Borrowed)
.unwrap_or_else(|| bins::infer_bin_dir_template(&bin_data));
// Create bin_files // Create bin_files
let bin_files = binaries let bin_files = binaries
.iter() .iter()
.map(|p| bins::BinFile::from_product(&bin_data, p)) .map(|p| bins::BinFile::from_product(&bin_data, p, &bin_dir))
.collect::<Result<Vec<_>, BinstallError>>()?; .collect::<Result<Vec<_>, BinstallError>>()?;
let mut source_set = BTreeSet::new(); let mut source_set = BTreeSet::new();