Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::cli::Network;
use crate::{cli::Network, say};
use eyre::Result;
use fs_err as fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION");

Expand Down Expand Up @@ -51,8 +51,63 @@ impl Config {
Ok(())
}

pub(crate) fn version_dir(&self, version: &str) -> PathBuf {
self.versions_dir.join(version)
pub(crate) fn migrate_legacy_versions(&self) -> Result<()> {
if !self.versions_dir.exists() {
return Ok(());
}

let default_repo = NetworkConfig::FOUNDRY.repo;

for entry in fs::read_dir(&self.versions_dir)? {
let entry = entry?;
let path = entry.path();

if !path.is_dir() {
continue;
}

let name = entry.file_name();
let name = name.to_string_lossy();

if name.contains('/') || self.is_owner_dir(&path) {
continue;
}

if self.is_legacy_version_dir(&path) {
let new_path = self.version_dir(default_repo, &name);
fs::create_dir_all(new_path.parent().unwrap())?;
say!("migrating legacy version '{name}' to {default_repo}/{name}");
fs::rename(&path, &new_path)?;
}
}

Ok(())
}

fn is_legacy_version_dir(&self, path: &Path) -> bool {
for bin in NetworkConfig::FOUNDRY.bins {
let bin_name = if cfg!(windows) { format!("{bin}.exe") } else { bin.to_string() };
if path.join(&bin_name).exists() {
return true;
}
}
false
}

fn is_owner_dir(&self, path: &Path) -> bool {
// Owner dirs have repo subdirs, which have version subdirs.
fn has_dir(path: &Path, mut f: impl FnMut(&Path) -> bool) -> bool {
fs::read_dir(path)
.into_iter()
.flatten()
.flatten()
.any(|entry| f(&entry.path()) && entry.metadata().is_ok_and(|m| m.is_dir()))
}
has_dir(path, |p| has_dir(p, |_| true))
}

pub(crate) fn version_dir(&self, repo: &str, version: &str) -> PathBuf {
self.versions_dir.join(repo).join(version)
}

pub(crate) fn bin_path(&self, name: &str) -> PathBuf {
Expand Down
83 changes: 58 additions & 25 deletions src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ async fn install_prebuilt(config: &Config, args: &Cli) -> Result<()> {
let (version, tag) =
normalize_version(args.version.as_deref().unwrap_or(config.network.default_version));

let repo = config.network.repo;

say!("installing {} (version {version}, tag {tag})", config.network.display_name);

let target = Target::detect(args.platform.as_deref(), args.arch.as_deref())?;
Expand All @@ -41,23 +43,24 @@ async fn install_prebuilt(config: &Config, args: &Cli) -> Result<()> {
format!("https://github.com/{}/releases/download/{tag}/", config.network.repo);

let hashes = if config.network.has_attestation && !args.force {
fetch_and_verify_attestation(config, &downloader, &release_url, &version, &target).await?
fetch_and_verify_attestation(config, repo, &downloader, &release_url, &version, &target)
.await?
} else if args.force {
say!("skipped SHA verification due to --force flag");
None
} else {
None
};

download_and_extract(config, &downloader, &release_url, &version, &tag, &target).await?;
download_and_extract(config, repo, &downloader, &release_url, &version, &tag, &target).await?;

if let Some(ref hashes) = hashes {
verify_installed_binaries(config, &tag, hashes)?;
verify_installed_binaries(config, repo, &tag, hashes)?;
}

download_manpages(config, &downloader, &release_url, &version).await;

use_version(config, &tag)?;
use_version(config, repo, &tag)?;
say!("done!");

Ok(())
Expand Down Expand Up @@ -180,7 +183,7 @@ async fn install_from_source(config: &Config, repo: &str, args: &Cli) -> Result<
bail!("cargo build failed");
}

let version_dir = config.version_dir(&version);
let version_dir = config.version_dir(repo, &version);
fs::create_dir_all(&version_dir)?;

for bin in config.network.bins {
Expand All @@ -190,14 +193,15 @@ async fn install_from_source(config: &Config, repo: &str, args: &Cli) -> Result<
}
}

use_version(config, &version)?;
use_version(config, repo, &version)?;
say!("done");

Ok(())
}

async fn fetch_and_verify_attestation(
config: &Config,
repo: &str,
downloader: &Downloader,
release_url: &str,
version: &str,
Expand Down Expand Up @@ -234,7 +238,7 @@ async fn fetch_and_verify_attestation(

let hashes = parse_attestation_payload(&artifact_json)?;

let version_dir = config.version_dir(version);
let version_dir = config.version_dir(repo, version);

if version_dir.exists() {
let mut all_match = true;
Expand All @@ -260,7 +264,7 @@ async fn fetch_and_verify_attestation(

if all_match {
say!("version {version} already installed and verified, activating...");
use_version(config, version)?;
use_version(config, repo, version)?;
say!("done!");
std::process::exit(0);
}
Expand Down Expand Up @@ -297,6 +301,7 @@ fn parse_attestation_payload(json: &str) -> Result<HashMap<String, String>> {

async fn download_and_extract(
config: &Config,
repo: &str,
downloader: &Downloader,
release_url: &str,
version: &str,
Expand All @@ -319,7 +324,7 @@ async fn download_and_extract(

downloader.download_to_file(&archive_url, &archive_path).await?;

let version_dir = config.version_dir(tag);
let version_dir = config.version_dir(repo, tag);
fs::create_dir_all(&version_dir)?;

if target.platform == Platform::Win32 {
Expand All @@ -345,12 +350,13 @@ async fn download_and_extract(

fn verify_installed_binaries(
config: &Config,
repo: &str,
tag: &str,
hashes: &HashMap<String, String>,
) -> Result<()> {
say!("verifying downloaded binaries against the attestation file");

let version_dir = config.version_dir(tag);
let version_dir = config.version_dir(repo, tag);
let mut failed = false;

for bin in config.network.bins {
Expand Down Expand Up @@ -425,23 +431,50 @@ pub(crate) fn list(config: &Config) -> Result<()> {
let bins = config.network.bins;

if config.versions_dir.exists() {
for entry in fs::read_dir(&config.versions_dir)? {
let entry = entry?;
let version_name = entry.file_name();
let version_name = version_name.to_string_lossy();
for owner_entry in fs::read_dir(&config.versions_dir)? {
let owner_entry = owner_entry?;
let owner_path = owner_entry.path();
if !owner_path.is_dir() {
continue;
}

let owner_name = owner_entry.file_name();
let owner_name = owner_name.to_string_lossy();

for repo_entry in fs::read_dir(&owner_path)? {
let repo_entry = repo_entry?;
let repo_path = repo_entry.path();
if !repo_path.is_dir() {
continue;
}

let repo_name = repo_entry.file_name();
let repo_name = repo_name.to_string_lossy();

for version_entry in fs::read_dir(&repo_path)? {
let version_entry = version_entry?;
let version_path = version_entry.path();
if !version_path.is_dir() {
continue;
}

let version_name = version_entry.file_name();
let version_name = version_name.to_string_lossy();

say!("{version_name}");
say!("{owner_name}/{repo_name} {version_name}");

for bin in bins {
let bin_path = entry.path().join(bin_name(bin));
if bin_path.exists() {
match get_bin_version(&bin_path) {
Ok(v) => say!("- {v}"),
Err(_) => say!("- {bin} (unknown version)"),
for bin in bins {
let bin_path = version_path.join(bin_name(bin));
if bin_path.exists() {
match get_bin_version(&bin_path) {
Ok(v) => say!("- {v}"),
Err(_) => say!("- {bin} (unknown version)"),
}
}
}
eprintln!();
}
}
eprintln!();
}
} else {
for bin in bins {
Expand All @@ -458,11 +491,11 @@ pub(crate) fn list(config: &Config) -> Result<()> {
Ok(())
}

pub(crate) fn use_version(config: &Config, version: &str) -> Result<()> {
let version_dir = config.version_dir(version);
pub(crate) fn use_version(config: &Config, repo: &str, version: &str) -> Result<()> {
let version_dir = config.version_dir(repo, version);

if !version_dir.exists() {
bail!("version {version} not installed");
bail!("version {version} not installed for {repo}");
}

for bin in config.network.bins {
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ async fn run(cli: Cli) -> Result<()> {
}

let config = Arc::new(Config::new(cli.network)?);
config.migrate_legacy_versions()?;

if cli.update {
return self_update::run(&config).await;
Expand All @@ -63,7 +64,7 @@ async fn run(cli: Cli) -> Result<()> {
if cli.list {
install::list(&config)?;
} else if let Some(ref version) = cli.use_version {
install::use_version(&config, version)?;
install::use_version(&config, config.network.repo, version)?;
} else {
print_banner();
process::check_bins_in_use(&config)?;
Expand Down
38 changes: 37 additions & 1 deletion tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,41 @@ fn list_empty() {
.success();
}

#[test]
fn migrate_legacy_versions() {
let temp_dir = tempfile::Builder::new().tempdir().unwrap();
let foundry_dir = temp_dir.path().join(".foundry");
let versions_dir = foundry_dir.join("versions");

std::fs::create_dir_all(versions_dir.join("nightly")).unwrap();
std::fs::create_dir_all(versions_dir.join("stable")).unwrap();

for version in ["nightly", "stable"] {
for bin in BINS {
let bin_path = versions_dir.join(version).join(format!("{bin}{EXE_SUFFIX}"));
std::fs::write(&bin_path, "fake binary").unwrap();
}
}

assert!(versions_dir.join("nightly").exists());
assert!(versions_dir.join("stable").exists());

foundryup().env("FOUNDRY_DIR", &foundry_dir).arg("--list").assert().success().stderr_eq(str![
[r#"
...
foundryup: migrating legacy version [..]
...
foundryup: migrating legacy version [..]
...
"#]
]);

assert!(!versions_dir.join("nightly").exists());
assert!(!versions_dir.join("stable").exists());
assert!(versions_dir.join("foundry-rs/foundry/nightly").exists());
assert!(versions_dir.join("foundry-rs/foundry/stable").exists());
}

fn test_install(version: &str) {
let temp_dir = tempfile::Builder::new().tempdir().unwrap();
let foundry_dir = temp_dir.path().join(".foundry");
Expand All @@ -176,11 +211,12 @@ fn test_install(version: &str) {

foundryup().env("FOUNDRY_DIR", &foundry_dir).arg("--list").assert().success().stderr_eq(str![
[r#"
...
foundryup: foundry-rs/foundry [..]
foundryup: - forge [..]
foundryup: - cast [..]
foundryup: - anvil [..]
foundryup: - chisel [..]

...
"#]
]);
Expand Down