Impl try multiple default bin dirs (#417)

From @passcod :

* Make bin-dir an Option
* Use cargo-release as a test case
* WIP: Try multiple default bin dirs
* Update bins.rs
* Update cargo_toml_binstall.rs

From @NobodyXu :
* Optimize & Prepare for support multi `bin-path`s
* Ensure quickinstall bin_dir work as usual.
* Rm dup entries in `FULL_FILENAMES`
* Add second version of `NOVERSION_FILENAMES`
* Impl multiple default `bin-dir`s
* Improve doc for `BinFile::from_product`
* Fix tests.sh

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
Co-authored-by: Félix Saparelli <felix@passcod.name>
This commit is contained in:
Jiahao XU 2022-09-25 22:15:20 +10:00 committed by GitHub
parent c27c3b80b5
commit e034d69e12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 88 additions and 67 deletions

View file

@ -7,10 +7,11 @@ unset CARGO_HOME
# Install binaries using cargo-binstall # Install binaries using cargo-binstall
# shellcheck disable=SC2086 # shellcheck disable=SC2086
"./$1" binstall --log-level debug --no-confirm b3sum cargo-binstall cargo-watch "./$1" binstall --log-level debug --no-confirm b3sum cargo-release cargo-binstall cargo-watch
# Test that the installed binaries can be run # Test that the installed binaries can be run
b3sum --version b3sum --version
cargo-release release --version
cargo-binstall --help >/dev/null cargo-binstall --help >/dev/null
cargo binstall --help >/dev/null cargo binstall --help >/dev/null
cargo watch -V cargo watch -V

View file

@ -1,4 +1,7 @@
use std::path::{Path, PathBuf}; use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use cargo_toml::Product; use cargo_toml::Product;
use compact_str::CompactString; use compact_str::CompactString;
@ -20,6 +23,8 @@ pub struct BinFile {
} }
impl BinFile { impl BinFile {
/// Must be called after the archive is downloaded and extracted.
/// This function might uses blocking I/O.
pub fn from_product(data: &Data, product: &Product) -> Result<Self, BinstallError> { pub fn from_product(data: &Data, product: &Product) -> Result<Self, BinstallError> {
let base_name = CompactString::from(product.name.clone().unwrap()); let base_name = CompactString::from(product.name.clone().unwrap());
@ -41,7 +46,38 @@ impl BinFile {
// 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 = ctx.render(&data.meta.bin_dir)?; let source_file_path = if let Some(bin_dir) = &data.meta.bin_dir {
ctx.render(bin_dir)?
} else {
let name = ctx.name;
let target = ctx.target;
let version = ctx.version;
let bin = ctx.bin;
// 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 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) {
data.bin_path.clone() data.bin_path.clone()
} else { } else {

View file

@ -11,8 +11,8 @@ use crate::{
manifests::cargo_toml_binstall::{PkgFmt, PkgMeta}, manifests::cargo_toml_binstall::{PkgFmt, PkgMeta},
}; };
mod gh_crate_meta; pub(crate) mod gh_crate_meta;
mod quickinstall; pub(crate) mod quickinstall;
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Fetcher: Send + Sync { pub trait Fetcher: Send + Sync {

View file

@ -22,7 +22,7 @@ use crate::{
use super::Data; use super::Data;
mod hosting; pub(crate) mod hosting;
use hosting::RepositoryHost; use hosting::RepositoryHost;
pub struct GhCrateMeta { pub struct GhCrateMeta {
@ -143,10 +143,10 @@ impl super::Fetcher for GhCrateMeta {
} }
async fn fetch_and_extract(&self, dst: &Path) -> Result<(), BinstallError> { async fn fetch_and_extract(&self, dst: &Path) -> Result<(), BinstallError> {
let (url, _pkg_fmt) = self.resolution.get().unwrap(); // find() is called first let (url, pkg_fmt) = self.resolution.get().unwrap(); // find() is called first
debug!("Downloading package from: '{url}'"); debug!("Downloading package from: '{url}' dst:{dst:?} fmt:{pkg_fmt:?}");
Download::new(self.client.clone(), url.clone()) Download::new(self.client.clone(), url.clone())
.and_extract(self.pkg_fmt(), dst) .and_extract(*pkg_fmt, dst)
.await .await
} }

View file

@ -11,6 +11,20 @@ pub enum RepositoryHost {
Unknown, Unknown,
} }
/// Make sure to update possible_dirs in `bins::BinFile`
/// if you modified FULL_FILENAMES or NOVERSION_FILENAMES.
pub const FULL_FILENAMES: &[&str] = &[
"{ name }-{ target }-v{ version }.{ archive-format }",
"{ name }-{ target }-{ version }.{ archive-format }",
"{ name }-{ version }-{ target }.{ archive-format }",
"{ name }-v{ version }-{ target }.{ archive-format }",
];
pub const NOVERSION_FILENAMES: &[&str] = &[
"{ name }-{ target }.{ archive-format }",
"{ name }.{ archive-format }",
];
impl RepositoryHost { impl RepositoryHost {
pub fn guess_git_hosting_services(repo: &Url) -> Result<Self, BinstallError> { pub fn guess_git_hosting_services(repo: &Url) -> Result<Self, BinstallError> {
use RepositoryHost::*; use RepositoryHost::*;
@ -27,35 +41,24 @@ impl RepositoryHost {
pub fn get_default_pkg_url_template(self) -> Option<Vec<String>> { pub fn get_default_pkg_url_template(self) -> Option<Vec<String>> {
use RepositoryHost::*; use RepositoryHost::*;
let full_filenames = &[
"{ name }-{ target }-v{ version }.{ archive-format }",
"{ name }-{ target }-{ version }.{ archive-format }",
"{ name }-{ version }-{ target }.{ archive-format }",
"{ name }-v{ version }-{ target }.{ archive-format }",
"{ name }-{ version }-{ target }.{ archive-format }",
"{ name }-v{ version }-{ target }.{ archive-format }",
];
let noversion_filenames = &["{ name }-{ target }.{ archive-format }"];
match self { match self {
GitHub => Some(apply_filenames_to_paths( GitHub => Some(apply_filenames_to_paths(
&[ &[
"{ repo }/releases/download/{ version }", "{ repo }/releases/download/{ version }",
"{ repo }/releases/download/v{ version }", "{ repo }/releases/download/v{ version }",
], ],
&[full_filenames, noversion_filenames], &[FULL_FILENAMES, NOVERSION_FILENAMES],
)), )),
GitLab => Some(apply_filenames_to_paths( GitLab => Some(apply_filenames_to_paths(
&[ &[
"{ repo }/-/releases/{ version }/downloads/binaries", "{ repo }/-/releases/{ version }/downloads/binaries",
"{ repo }/-/releases/v{ version }/downloads/binaries", "{ repo }/-/releases/v{ version }/downloads/binaries",
], ],
&[full_filenames, noversion_filenames], &[FULL_FILENAMES, NOVERSION_FILENAMES],
)), )),
BitBucket => Some(apply_filenames_to_paths( BitBucket => Some(apply_filenames_to_paths(
&["{ repo }/downloads"], &["{ repo }/downloads"],
&[full_filenames], &[FULL_FILENAMES],
)), )),
SourceForge => Some( SourceForge => Some(
apply_filenames_to_paths( apply_filenames_to_paths(
@ -63,7 +66,7 @@ impl RepositoryHost {
"{ repo }/files/binaries/{ version }", "{ repo }/files/binaries/{ version }",
"{ repo }/files/binaries/v{ version }", "{ repo }/files/binaries/v{ version }",
], ],
&[full_filenames, noversion_filenames], &[FULL_FILENAMES, NOVERSION_FILENAMES],
) )
.into_iter() .into_iter()
.map(|url| format!("{url}/download")) .map(|url| format!("{url}/download"))

View file

@ -61,6 +61,7 @@ impl super::Fetcher for QuickInstall {
fn target_meta(&self) -> PkgMeta { fn target_meta(&self) -> PkgMeta {
let mut meta = self.data.meta.clone(); let mut meta = self.data.meta.clone();
meta.pkg_fmt = Some(self.pkg_fmt()); meta.pkg_fmt = Some(self.pkg_fmt());
meta.bin_dir = Some("{ bin }{ binary-ext }".to_string());
meta meta
} }

View file

@ -11,9 +11,6 @@ pub use package_formats::*;
mod package_formats; mod package_formats;
/// Default binary name template (may be overridden in package Cargo.toml)
pub const DEFAULT_BIN_DIR: &str = "{ name }-{ target }-v{ version }/{ bin }{ binary-ext }";
/// `binstall` metadata container /// `binstall` metadata container
/// ///
/// Required to nest metadata under `package.metadata.binstall` /// Required to nest metadata under `package.metadata.binstall`
@ -26,7 +23,7 @@ pub struct Meta {
/// Metadata for binary installation use. /// Metadata for binary installation use.
/// ///
/// Exposed via `[package.metadata]` in `Cargo.toml` /// Exposed via `[package.metadata]` in `Cargo.toml`
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default)] #[serde(rename_all = "kebab-case", default)]
pub struct PkgMeta { pub struct PkgMeta {
/// URL template for package downloads /// URL template for package downloads
@ -36,7 +33,7 @@ pub struct PkgMeta {
pub pkg_fmt: Option<PkgFmt>, pub pkg_fmt: Option<PkgFmt>,
/// Path template for binary files in packages /// Path template for binary files in packages
pub bin_dir: String, pub bin_dir: Option<String>,
/// Public key for package verification (base64 encoded) /// Public key for package verification (base64 encoded)
pub pub_key: Option<String>, pub pub_key: Option<String>,
@ -45,18 +42,6 @@ pub struct PkgMeta {
pub overrides: BTreeMap<String, PkgOverride>, pub overrides: BTreeMap<String, PkgOverride>,
} }
impl Default for PkgMeta {
fn default() -> Self {
Self {
pkg_url: None,
pkg_fmt: None,
bin_dir: DEFAULT_BIN_DIR.to_string(),
pub_key: None,
overrides: BTreeMap::new(),
}
}
}
impl PkgMeta { impl PkgMeta {
pub fn clone_without_overrides(&self) -> Self { pub fn clone_without_overrides(&self) -> Self {
Self { Self {
@ -77,7 +62,7 @@ impl PkgMeta {
self.pkg_fmt = Some(*o); self.pkg_fmt = Some(*o);
} }
if let Some(o) = &pkg_override.bin_dir { if let Some(o) = &pkg_override.bin_dir {
self.bin_dir = o.clone(); self.bin_dir = Some(o.clone());
} }
} }
} }

View file

@ -176,6 +176,11 @@ async fn resolve_inner(
manifest.bin, manifest.bin,
); );
// Check binaries
if binaries.is_empty() {
return Err(BinstallError::UnspecifiedBinaries);
}
let desired_targets = opts.desired_targets.get().await; let desired_targets = opts.desired_targets.get().await;
let mut handles: Vec<(Arc<dyn Fetcher>, _)> = Vec::with_capacity(desired_targets.len() * 2); let mut handles: Vec<(Arc<dyn Fetcher>, _)> = Vec::with_capacity(desired_targets.len() * 2);
@ -229,7 +234,7 @@ async fn resolve_inner(
&bin_path, &bin_path,
&package, &package,
&install_path, &install_path,
binaries.clone(), &binaries,
) )
.await .await
{ {
@ -266,26 +271,17 @@ async fn resolve_inner(
} }
/// * `fetcher` - `fetcher.find()` must return `Ok(true)`. /// * `fetcher` - `fetcher.find()` must return `Ok(true)`.
/// * `binaries` - must not be empty
async fn download_extract_and_verify( async fn download_extract_and_verify(
fetcher: &dyn Fetcher, fetcher: &dyn Fetcher,
bin_path: &Path, bin_path: &Path,
package: &Package<Meta>, package: &Package<Meta>,
install_path: &Path, install_path: &Path,
// TODO: Use &[Product] binaries: &[Product],
binaries: Vec<Product>,
) -> Result<Vec<bins::BinFile>, BinstallError> { ) -> Result<Vec<bins::BinFile>, BinstallError> {
// Build final metadata // Build final metadata
let meta = fetcher.target_meta(); let meta = fetcher.target_meta();
let bin_files = collect_bin_files(
fetcher,
package,
meta,
binaries,
bin_path.to_path_buf(),
install_path.to_path_buf(),
)?;
// Download and extract it. // Download and extract it.
// If that fails, then ignore this fetcher. // If that fails, then ignore this fetcher.
fetcher.fetch_and_extract(bin_path).await?; fetcher.fetch_and_extract(bin_path).await?;
@ -316,6 +312,15 @@ async fn download_extract_and_verify(
// Verify that all the bin_files exist // Verify that all the bin_files exist
block_in_place(|| { block_in_place(|| {
let bin_files = collect_bin_files(
fetcher,
package,
meta,
binaries,
bin_path.to_path_buf(),
install_path.to_path_buf(),
)?;
for bin_file in bin_files.iter() { for bin_file in bin_files.iter() {
bin_file.check_source_exists()?; bin_file.check_source_exists()?;
} }
@ -324,25 +329,15 @@ async fn download_extract_and_verify(
}) })
} }
/// * `binaries` - must not be empty
fn collect_bin_files( fn collect_bin_files(
fetcher: &dyn Fetcher, fetcher: &dyn Fetcher,
package: &Package<Meta>, package: &Package<Meta>,
mut meta: PkgMeta, meta: PkgMeta,
binaries: Vec<Product>, binaries: &[Product],
bin_path: PathBuf, bin_path: PathBuf,
install_path: PathBuf, install_path: PathBuf,
) -> Result<Vec<bins::BinFile>, BinstallError> { ) -> Result<Vec<bins::BinFile>, BinstallError> {
// Update meta
if fetcher.source_name() == "QuickInstall" {
// TODO: less of a hack?
meta.bin_dir = "{ bin }{ binary-ext }".to_string();
}
// Check binaries
if binaries.is_empty() {
return Err(BinstallError::UnspecifiedBinaries);
}
// 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 {