Skip to content

Commit

Permalink
Implement abstraction over artifact sources, simplify github provider
Browse files Browse the repository at this point in the history
filiptibell committed Mar 26, 2024

Verified

This commit was signed with the committer’s verified signature.
filiptibell Filip Tibell
1 parent 340a5ef commit 4a05a78
Showing 12 changed files with 276 additions and 247 deletions.
46 changes: 23 additions & 23 deletions lib/descriptor/mod.rs
Original file line number Diff line number Diff line change
@@ -24,13 +24,13 @@ pub enum DescriptionParseError {
used to check for compatibility between two or more specified systems.
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Descripor {
pub struct Descriptor {
os: OS,
arch: Arch,
toolchain: Option<Toolchain>,
}

impl Descripor {
impl Descriptor {
/**
Get the description for the current host system.
*/
@@ -70,7 +70,7 @@ impl Descripor {
- Windows and Linux 64-bit can run 32-bit executables
- macOS Apple Silicon can run x64 (Intel) executables
*/
pub fn is_compatible_with(&self, other: &Descripor) -> bool {
pub fn is_compatible_with(&self, other: &Descriptor) -> bool {
// Operating system must _always_ match
(self.os == other.os)
&& (
@@ -119,7 +119,7 @@ impl Descripor {
}
}

impl FromStr for Descripor {
impl FromStr for Descriptor {
type Err = DescriptionParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let os = OS::detect(s).ok_or(DescriptionParseError::OS)?;
@@ -138,17 +138,17 @@ impl FromStr for Descripor {
mod tests {
use super::*;

fn check_desc(description: &str, expected: Descripor) {
fn check_desc(description: &str, expected: Descriptor) {
assert_eq!(
Descripor::detect(description),
Descriptor::detect(description),
Some(expected),
"{description}"
);
}

#[test]
fn current_description() {
let current = Descripor::current_system();
let current = Descriptor::current_system();
assert_eq!(current.os, OS::current_system());
assert_eq!(current.arch, Arch::current_system());
assert_eq!(current.toolchain, Toolchain::current_system());
@@ -159,39 +159,39 @@ mod tests {
// Windows
check_desc(
"windows-x64-msvc",
Descripor {
Descriptor {
os: OS::Windows,
arch: Arch::X64,
toolchain: Some(Toolchain::Msvc),
},
);
check_desc(
"win64",
Descripor {
Descriptor {
os: OS::Windows,
arch: Arch::X64,
toolchain: None,
},
);
check_desc(
"windows-x86-gnu",
Descripor {
Descriptor {
os: OS::Windows,
arch: Arch::X86,
toolchain: Some(Toolchain::Gnu),
},
);
check_desc(
"windows-x86",
Descripor {
Descriptor {
os: OS::Windows,
arch: Arch::X86,
toolchain: None,
},
);
check_desc(
"win32",
Descripor {
Descriptor {
os: OS::Windows,
arch: Arch::X86,
toolchain: None,
@@ -200,23 +200,23 @@ mod tests {
// macOS
check_desc(
"aarch64-macos",
Descripor {
Descriptor {
os: OS::MacOS,
arch: Arch::Arm64,
toolchain: None,
},
);
check_desc(
"macos-x64-gnu",
Descripor {
Descriptor {
os: OS::MacOS,
arch: Arch::X64,
toolchain: Some(Toolchain::Gnu),
},
);
check_desc(
"macos-x64",
Descripor {
Descriptor {
os: OS::MacOS,
arch: Arch::X64,
toolchain: None,
@@ -225,23 +225,23 @@ mod tests {
// Linux
check_desc(
"linux-x86_64-gnu",
Descripor {
Descriptor {
os: OS::Linux,
arch: Arch::X64,
toolchain: Some(Toolchain::Gnu),
},
);
check_desc(
"linux-gnu-x86",
Descripor {
Descriptor {
os: OS::Linux,
arch: Arch::X86,
toolchain: Some(Toolchain::Gnu),
},
);
check_desc(
"armv7-linux-musl",
Descripor {
Descriptor {
os: OS::Linux,
arch: Arch::Arm32,
toolchain: Some(Toolchain::Musl),
@@ -254,15 +254,15 @@ mod tests {
// macOS universal binaries should parse as x64 (most compatible)
check_desc(
"macos-universal",
Descripor {
Descriptor {
os: OS::MacOS,
arch: Arch::X64,
toolchain: None,
},
);
check_desc(
"darwin-universal",
Descripor {
Descriptor {
os: OS::MacOS,
arch: Arch::X64,
toolchain: None,
@@ -284,7 +284,7 @@ mod tests {
"unknown-armv7-musl",
];
for description in INVALID_DESCRIPTIONS {
assert_eq!(Descripor::detect(description), None);
assert_eq!(Descriptor::detect(description), None);
}
}

@@ -304,7 +304,7 @@ mod tests {
"armv7-linux-musl",
];
for description in VALID_STRINGS {
assert!(description.parse::<Descripor>().is_ok());
assert!(description.parse::<Descriptor>().is_ok());
}
}

@@ -318,7 +318,7 @@ mod tests {
];
for description in INVALID_OS_STRINGS {
assert_eq!(
description.parse::<Descripor>(),
description.parse::<Descriptor>(),
Err(DescriptionParseError::OS)
);
}
16 changes: 15 additions & 1 deletion lib/manifests/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{path::Path, str::FromStr};
use std::{collections::HashMap, path::Path, str::FromStr};

use toml_edit::{DocumentMut, Formatted, Item, Value};

@@ -88,6 +88,20 @@ impl AuthManifest {
token.as_str().map(|s| s.to_string())
}

/**
Gets all authentication tokens found in the manifest.
*/
pub fn get_all_tokens(&self) -> HashMap<ArtifactProvider, String> {
self.document
.iter()
.filter_map(|(key, value)| {
let provider = ArtifactProvider::from_str(key).ok()?;
let token = value.as_str()?.to_string();
Some((provider, token))
})
.collect()
}

/**
Sets the authentication token for the given artifact provider.
2 changes: 2 additions & 0 deletions lib/result.rs
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@ pub enum RokitError {
Json(#[from] serde_json::Error),
#[error("Zip file error: {0}")]
Zip(#[from] zip::result::ZipError),
#[error("GitHub error: {0}")]
GitHub(#[from] octocrab::Error),
}

pub type RokitResult<T> = Result<T, RokitError>;
57 changes: 47 additions & 10 deletions lib/sources/artifact.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ use tracing::{debug, instrument};
use url::Url;

use crate::{
descriptor::Descriptor,
result::{RokitError, RokitResult},
tool::ToolSpec,
};
@@ -92,25 +93,22 @@ impl fmt::Display for ArtifactFormat {
pub struct Artifact {
pub provider: ArtifactProvider,
pub format: Option<ArtifactFormat>,
pub id: Option<String>,
pub url: Option<Url>,
pub name: Option<String>,
pub tool_spec: ToolSpec,
pub source_url: Url,
pub download_url: Url,
}

impl Artifact {
pub(crate) fn from_github_release_asset(asset: &Asset, spec: &ToolSpec) -> Self {
let download_url = asset.browser_download_url.clone();
let format = ArtifactFormat::from_path_or_url(&asset.name).or_else(|| {
// TODO: The url path here is percent-encoded ... we should
// probably decode it first before guessing the artifact format
ArtifactFormat::from_path_or_url(download_url.path())
});
let format = ArtifactFormat::from_path_or_url(&asset.name);
Self {
provider: ArtifactProvider::GitHub,
format,
id: Some(asset.id.to_string()),
url: Some(asset.url.clone()),
name: Some(asset.name.clone()),
tool_spec: spec.clone(),
source_url: asset.url.clone(),
download_url,
}
}

@@ -137,4 +135,43 @@ impl Artifact {

file_opt.ok_or(RokitError::ExtractFileMissing)
}

/**
Sorts the given artifacts by system compatibility.
See also:
- [`Descriptor::current_system`]
- [`Descriptor::is_compatible_with`]
- [`Descriptor::sort_by_preferred_compat`]
*/
pub fn sort_by_system_compatibility(artifacts: impl AsRef<[Self]>) -> Vec<Self> {
let current_desc = Descriptor::current_system();

let mut compatible_artifacts = artifacts
.as_ref()
.iter()
.filter_map(|artifact| {
let name = artifact.name.as_deref()?;
if let Some(asset_desc) = Descriptor::detect(name) {
if current_desc.is_compatible_with(&asset_desc) {
Some((asset_desc, artifact))
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();

compatible_artifacts.sort_by(|(desc_a, _), (desc_b, _)| {
current_desc.sort_by_preferred_compat(desc_a, desc_b)
});

compatible_artifacts
.into_iter()
.map(|(_, artifact)| artifact.clone())
.collect()
}
}
173 changes: 60 additions & 113 deletions lib/sources/github.rs
Original file line number Diff line number Diff line change
@@ -4,50 +4,41 @@ use std::{
time::Duration,
};

use http::{header::ACCEPT, StatusCode};
use http::{header::ACCEPT, HeaderMap, HeaderValue, StatusCode};
use http_body_util::BodyExt;
use octocrab::{models::repos::Release, Error, Octocrab, OctocrabBuilder, Result};
use secrecy::{ExposeSecret, SecretString};
use semver::Version;
use tokio::time::sleep;
use tracing::{debug, info, instrument};

use crate::{
descriptor::Descripor,
tool::{ToolId, ToolSpec},
};
use crate::tool::{ToolId, ToolSpec};

use super::{Artifact, ArtifactProvider};

const BASE_URI: &str = "https://api.github.com";

const ERR_AUTH_UNRECOGNIZED: &str =
"Unrecognized access token format - must begin with `ghp_` or `gho_`.";
const ERR_AUTH_DEVICE_INTERACTIVE: &str =
const _ERR_AUTH_DEVICE_INTERACTIVE: &str =
"Device authentication flow may only be used in an interactive terminal.";

// NOTE: Users typically install somewhat recent tools, and fetching
// a smaller number of releases here lets us install tools much faster.
const RESULTS_PER_PAGE: u8 = 8;

pub struct GitHubSource {
#[derive(Debug, Clone)]
pub struct GithubProvider {
client: Octocrab,
}

impl GitHubSource {
impl GithubProvider {
/**
Creates a new GitHub source instance.
This instance is unauthenticated and may be rate limited and/or unable to access
private resources. To authenticate using an access token, use `new_authenticated`.
*/
pub fn new() -> Result<Self> {
let client = crab_builder().build()?;
Ok(Self { client })
}

/**
Creates a new authorized GitHub source instance with a personal access token.
Creates a new authenticated GitHub source instance with a token.
May be used with either personal access tokens or tokens generated using the GitHub device flow.
*/
@@ -81,15 +72,15 @@ impl GitHubSource {
Returns the access token if authentication is successful, but *does not* store it.
A new client instance must be created using `new_authenticated` to use it.
*/
pub async fn auth_with_device<C, I, S>(&self, client_id: C, scope: I) -> Result<String>
pub async fn _auth_with_device<C, I, S>(&self, client_id: C, scope: I) -> Result<String>
where
C: Into<SecretString>,
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
if !stdout().is_terminal() {
return Err(Error::Other {
source: ERR_AUTH_DEVICE_INTERACTIVE.into(),
source: _ERR_AUTH_DEVICE_INTERACTIVE.into(),
backtrace: Backtrace::capture(),
});
}
@@ -122,135 +113,91 @@ impl GitHubSource {
}

/**
Fetches a page of releases for a given tool.
Fetches the latest release for a given tool.
*/
#[instrument(skip(self), fields(%tool_id), level = "debug")]
pub async fn get_releases(&self, tool_id: &ToolId, page: u32) -> Result<Vec<Release>> {
debug!("fetching releases for tool");
pub async fn get_latest_release(&self, tool_id: &ToolId) -> Result<Vec<Artifact>> {
debug!("fetching latest release for tool");

let repository = self.client.repos(tool_id.author(), tool_id.name());
let releases = repository
.releases()
.list()
.per_page(RESULTS_PER_PAGE)
.page(page)
.send()
.await?;
let releases = repository.releases();

Ok(releases.items)
let release = releases.get_latest().await?;
let version = release
.tag_name
.trim_start_matches('v')
.parse::<Version>()
.map_err(|e| Error::Other {
source: e.into(),
backtrace: Backtrace::capture(),
})?;

let tool_spec: ToolSpec = (tool_id.clone(), version).into();
Ok(artifacts_from_release(release, &tool_spec))
}

/**
Fetches a specific release for a given tool.
*/
#[instrument(skip(self), fields(%tool_spec), level = "debug")]
pub async fn find_release(&self, tool_spec: &ToolSpec) -> Result<Option<Release>> {
pub async fn get_specific_release(&self, tool_spec: &ToolSpec) -> Result<Vec<Artifact>> {
debug!("fetching release for tool");

let repository = self.client.repos(tool_spec.author(), tool_spec.name());
let releases = repository.releases();

let tag_with_prefix = format!("v{}", tool_spec.version());
match releases.get_by_tag(&tag_with_prefix).await {
Err(err) if is_github_not_found(&err) => {}
Err(err) => return Err(err),
Ok(release) => return Ok(Some(release)),
}

let tag_without_prefix = tool_spec.version().to_string();
match releases.get_by_tag(&tag_without_prefix).await {
Err(err) if is_github_not_found(&err) => Ok(None),
Err(err) => Err(err),
Ok(release) => Ok(Some(release)),
}
}

/**
Finds the latest version of a tool, optionally allowing prereleases.
If no releases are found, or no non-prerelease releases are found, this will return `None`.
*/
#[instrument(skip(self), fields(%tool_id), level = "debug")]
pub async fn find_latest_version(
&self,
tool_id: &ToolId,
allow_prereleases: bool,
) -> Result<Option<Version>> {
debug!("fetching latest version for tool");

let releases = self.get_releases(tool_id, 1).await?;
Ok(releases.into_iter().find_map(|release| {
if allow_prereleases || !release.prerelease {
let version = release.tag_name.trim_start_matches('v');
Version::parse(version).ok()
} else {
None
}
}))
}

/**
Finds compatible release artifacts for the given release and description.

The resulting list of artifacts will be sorted by preferred compatibility.
See [`Description::is_compatible_with`] and
[`Description::sort_by_preferred_compat`] for more information.
*/
pub fn find_compatible_artifacts(
&self,
tool_spec: &ToolSpec,
release: &Release,
description: &Descripor,
) -> Vec<Artifact> {
let mut compatible_artifacts = release
.assets
.iter()
.filter_map(|asset| {
if let Some(asset_desc) = Descripor::detect(asset.name.as_str()) {
if description.is_compatible_with(&asset_desc) {
let artifact = Artifact::from_github_release_asset(asset, tool_spec);
Some((asset_desc, artifact))
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();

compatible_artifacts.sort_by(|(desc_a, _), (desc_b, _)| {
description.sort_by_preferred_compat(desc_a, desc_b)
});
let release = match releases.get_by_tag(&tag_with_prefix).await {
Err(err) if is_github_not_found(&err) => releases.get_by_tag(&tag_without_prefix).await,
Err(err) => Err(err),
Ok(release) => Ok(release),
}?;

compatible_artifacts
.into_iter()
.map(|(_, artifact)| artifact)
.collect()
Ok(artifacts_from_release(release, tool_spec))
}

/**
Downloads the contents of the given artifact.
*/
#[instrument(skip(self, artifact), level = "debug")]
pub async fn download_artifact_contents(&self, artifact: &Artifact) -> Result<Vec<u8>> {
assert_eq!(
artifact.provider,
ArtifactProvider::GitHub,
"artifact must be from GitHub"
);

debug!("downloading artifact contents");

if artifact.provider != ArtifactProvider::GitHub {
return Err(Error::Other {
source: "Artifact provider mismatch".into(),
backtrace: Backtrace::capture(),
});
}
let url = format!(
"{BASE_URI}/repos/{owner}/{repo}/releases/assets/{id}",
owner = artifact.tool_spec.author(),
repo = artifact.tool_spec.name(),
id = artifact.id.as_ref().expect("GitHub artifacts must have id")
);
let headers = {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/octet-stream"));
headers
};

let response = self.client._get(artifact.download_url.as_str()).await?;
let bytes = response.into_body().collect().await?.to_bytes().to_vec();
let response = self.client._get_with_headers(url, Some(headers)).await?;
let bytes = response.into_body().collect().await?.to_bytes();

Ok(bytes)
Ok(bytes.to_vec())
}
}

fn artifacts_from_release(release: Release, spec: &ToolSpec) -> Vec<Artifact> {
release
.assets
.iter()
.map(|asset| Artifact::from_github_release_asset(asset, spec))
.collect::<Vec<_>>()
}

// So generic, such wow

use octocrab::{DefaultOctocrabBuilderConfig, NoAuth, NoSvc, NotLayerReady};
3 changes: 2 additions & 1 deletion lib/sources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod artifact;
mod extraction;
mod github;
mod source;

pub use self::artifact::{Artifact, ArtifactFormat, ArtifactProvider};
pub use self::github::GitHubSource;
pub use self::source::ArtifactSource;
77 changes: 77 additions & 0 deletions lib/sources/source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::collections::HashMap;

use crate::{
result::RokitResult,
tool::{ToolId, ToolSpec},
};

use super::{github::GithubProvider, Artifact, ArtifactProvider};

/**
A source for artifacts.
Provides high-level access abstracting over individual providers such as GitHub, ...
*/
#[derive(Debug, Clone)]
pub struct ArtifactSource {
github: GithubProvider,
}

impl ArtifactSource {
/**
Creates a new artifact source.
This source is unauthenticated and may be rate limited and/or unable to access
private resources. To authenticate using auth tokens, use `new_authenticated`.
*/
pub fn new() -> RokitResult<Self> {
let github = GithubProvider::new()?;
Ok(Self { github })
}

/**
Creates a new authenticated artifact source.
*/
pub fn new_authenticated(auth: &HashMap<ArtifactProvider, String>) -> RokitResult<Self> {
let github = match auth.get(&ArtifactProvider::GitHub) {
Some(token) => GithubProvider::new_authenticated(token)?,
None => GithubProvider::new()?,
};
Ok(Self { github })
}

/**
Gets the latest release for a tool.
*/
pub async fn get_latest_release(
&self,
provider: ArtifactProvider,
id: &ToolId,
) -> RokitResult<Vec<Artifact>> {
Ok(match provider {
ArtifactProvider::GitHub => self.github.get_latest_release(id).await?,
})
}

/**
Gets a specific release for a tool.
*/
pub async fn get_specific_release(
&self,
provider: ArtifactProvider,
spec: &ToolSpec,
) -> RokitResult<Vec<Artifact>> {
Ok(match provider {
ArtifactProvider::GitHub => self.github.get_specific_release(spec).await?,
})
}

/**
Downloads the contents of an artifact.
*/
pub async fn download_artifact_contents(&self, artifact: &Artifact) -> RokitResult<Vec<u8>> {
Ok(match &artifact.provider {
ArtifactProvider::GitHub => self.github.download_artifact_contents(artifact).await?,
})
}
}
54 changes: 25 additions & 29 deletions src/cli/add.rs
Original file line number Diff line number Diff line change
@@ -3,15 +3,15 @@ use clap::Parser;
use console::style;

use rokit::{
descriptor::Descripor,
manifests::RokitManifest,
manifests::{AuthManifest, RokitManifest},
sources::{Artifact, ArtifactProvider, ArtifactSource},
storage::Home,
tool::{ToolAlias, ToolId},
};

use crate::util::{
discover_rokit_manifest_dir, finish_progress_bar, github_tool_source, new_progress_bar,
prompt_for_trust, ToolIdOrSpec,
discover_rokit_manifest_dir, finish_progress_bar, new_progress_bar, prompt_for_trust,
ToolIdOrSpec,
};

/// Adds a new tool to Rokit and installs it.
@@ -53,7 +53,8 @@ impl AddSubcommand {

// 2. Load tool source, manifest, and do a preflight check
// to make sure we don't overwrite any existing tool(s)
let source = github_tool_source(home).await?;
let auth = AuthManifest::load(home.path()).await?;
let source = ArtifactSource::new_authenticated(&auth.get_all_tokens())?;
let manifest_path = if self.global {
home.path().to_path_buf()
} else {
@@ -76,19 +77,29 @@ impl AddSubcommand {

// 3. If we only got an id without a specified version, we
// will fetch the latest non-prerelease release and use that
let pb = new_progress_bar("Fetching", 5, 1);
let spec = match self.tool.clone() {
let pb = new_progress_bar("Fetching", 3, 1);
let (spec, artifact) = match self.tool.clone() {
ToolIdOrSpec::Spec(spec) => {
let artifacts = source
.get_specific_release(ArtifactProvider::GitHub, &spec)
.await?;
let artifact = Artifact::sort_by_system_compatibility(&artifacts)
.first()
.cloned()
.with_context(|| format!("No compatible artifact found for {id}"))?;
pb.inc(1);
spec
(spec, artifact)
}
ToolIdOrSpec::Id(id) => {
let version = source
.find_latest_version(&id, false)
.await?
.with_context(|| format!("Failed to find latest release for {id}"))?;
let artifacts = source
.get_latest_release(ArtifactProvider::GitHub, &id)
.await?;
let artifact = Artifact::sort_by_system_compatibility(&artifacts)
.first()
.cloned()
.with_context(|| format!("No compatible artifact found for {id}"))?;
pb.inc(1);
id.into_spec(version)
(artifact.tool_spec.clone(), artifact)
}
};

@@ -97,37 +108,22 @@ impl AddSubcommand {
manifest.save(manifest_path).await?;

// 5. Download and install the tool
let description = Descripor::current_system();
if !tool_cache.is_installed(&spec) || self.force {
pb.set_message("Downloading");
let release = source
.find_release(&spec)
.await?
.with_context(|| format!("Failed to find release for {spec}"))?;
pb.inc(1);
let artifact = source
.find_compatible_artifacts(&spec, &release, &description)
.first()
.cloned()
.with_context(|| format!("No compatible artifact found for {spec}"))?;
pb.inc(1);
let contents = source
.download_artifact_contents(&artifact)
.await
.with_context(|| format!("Failed to download contents for {spec}"))?;
pb.inc(1);

pb.set_message("Installing");
let extracted = artifact
.extract_contents(contents)
.await
.with_context(|| format!("Failed to extract contents for {spec}"))?;
tool_storage.replace_tool_contents(&spec, extracted).await?;
pb.inc(1);

tool_cache.add_installed(spec.clone());
} else {
pb.inc(4);
pb.inc(2);
}

// 6. Create the tool alias link
26 changes: 14 additions & 12 deletions src/cli/install.rs
Original file line number Diff line number Diff line change
@@ -5,11 +5,14 @@ use clap::Parser;

use console::style;
use futures::{stream::FuturesUnordered, TryStreamExt};
use rokit::{descriptor::Descripor, manifests::RokitManifest, storage::Home};
use rokit::{
manifests::{AuthManifest, RokitManifest},
sources::{Artifact, ArtifactProvider, ArtifactSource},
storage::Home,
};

use crate::util::{
discover_rokit_manifest_dirs, finish_progress_bar, github_tool_source, new_progress_bar,
prompt_for_trust_specs,
discover_rokit_manifest_dirs, finish_progress_bar, new_progress_bar, prompt_for_trust_specs,
};

/// Adds a new tool using Rokit and installs it.
@@ -27,12 +30,13 @@ pub struct InstallSubcommand {
impl InstallSubcommand {
pub async fn run(self, home: &Home) -> Result<()> {
let force = self.force;
let (manifest_paths, source) =
tokio::try_join!(discover_rokit_manifest_dirs(home), github_tool_source(home))?;

let auth = AuthManifest::load(home.path()).await?;
let source = ArtifactSource::new_authenticated(&auth.get_all_tokens())?;
let manifest_paths = discover_rokit_manifest_dirs(home).await?;

let tool_cache = home.tool_cache();
let tool_storage = home.tool_storage();
let description = Descripor::current_system();

// 1. Gather tool specifications from all known manifests

@@ -91,14 +95,12 @@ impl InstallSubcommand {
return anyhow::Ok(tool_spec);
}

let release = source
.find_release(&tool_spec)
.await?
.with_context(|| format!("Failed to find release for {tool_spec}"))?;
let artifacts = source
.get_specific_release(ArtifactProvider::GitHub, &tool_spec)
.await?;
pb.inc(1);

let artifact = source
.find_compatible_artifacts(&tool_spec, &release, &description)
let artifact = Artifact::sort_by_system_compatibility(&artifacts)
.first()
.cloned()
.with_context(|| format!("No compatible artifact found for {tool_spec}"))?;
50 changes: 11 additions & 39 deletions src/runner/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::{
env::{args, consts::EXE_EXTENSION},
path::PathBuf,
process::exit,
str::FromStr,
};
use std::{env::args, process::exit, str::FromStr};

use anyhow::{Context, Result};

use rokit::{storage::Home, system::run_interruptible, tool::ToolAlias};
use rokit::{
storage::Home,
system::{current_exe_name, run_interruptible},
tool::ToolAlias,
};

use crate::util::discover_closest_tool_spec;

@@ -18,11 +17,13 @@ pub struct Runner {

impl Runner {
pub fn new() -> Self {
Self::default()
Self {
exe_name: current_exe_name(),
}
}

pub fn should_run(&self) -> bool {
self.exe_name != env!("CARGO_PKG_NAME")
self.exe_name != env!("CARGO_BIN_NAME")
}

pub async fn run(&self) -> Result<()> {
@@ -43,35 +44,6 @@ impl Runner {

impl Default for Runner {
fn default() -> Self {
let arg0 = args()
.next()
.expect("Arg0 was not passed to Rokit - no tool can run");

let exe_path = PathBuf::from(arg0);
let exe_name = exe_path
.file_name()
.expect("Invalid file name passed as arg0")
.to_str()
.expect("Non-UTF8 file name passed as arg0");

// NOTE: Shells on Windows can be weird sometimes and pass arg0
// using either a lowercase or uppercase extension, so we fix that
let exe_name = if !EXE_EXTENSION.is_empty() {
let suffix_lower = EXE_EXTENSION.to_ascii_lowercase();
let suffix_upper = EXE_EXTENSION.to_ascii_uppercase();
if let Some(stripped) = exe_name.strip_suffix(&suffix_lower) {
stripped
} else if let Some(stripped) = exe_name.strip_suffix(&suffix_upper) {
stripped
} else {
exe_name
}
} else {
exe_name
};

Self {
exe_name: exe_name.to_string(),
}
Self::new()
}
}
2 changes: 0 additions & 2 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@ mod discovery;
mod id_or_spec;
mod progress;
mod prompts;
mod sources;
mod tracing;

pub use self::discovery::{
@@ -12,5 +11,4 @@ pub use self::discovery::{
pub use self::id_or_spec::ToolIdOrSpec;
pub use self::progress::{finish_progress_bar, new_progress_bar};
pub use self::prompts::{prompt_for_trust, prompt_for_trust_specs};
pub use self::sources::github_tool_source;
pub use self::tracing::init as init_tracing;
17 changes: 0 additions & 17 deletions src/util/sources.rs

This file was deleted.

0 comments on commit 4a05a78

Please sign in to comment.