From 7f81a020eade4822aad8f221040c420b79d706c5 Mon Sep 17 00:00:00 2001 From: Lucas Date: Mon, 13 Oct 2025 20:17:50 -0300 Subject: [PATCH 1/2] Add --install-only command --- Cargo.lock | 1 + platform-tools-sdk/cargo-build-sbf/Cargo.toml | 3 +- .../cargo-build-sbf/src/main.rs | 33 ++- .../cargo-build-sbf/src/toolchain.rs | 217 +++++++++++++----- .../cargo-build-sbf/tests/crates.rs | 23 +- 5 files changed, 219 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62269687e2500a..20f1a930ef37d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7662,6 +7662,7 @@ dependencies = [ "regex", "reqwest 0.12.23", "semver 1.0.27", + "serde", "serial_test", "solana-file-download", "solana-keypair", diff --git a/platform-tools-sdk/cargo-build-sbf/Cargo.toml b/platform-tools-sdk/cargo-build-sbf/Cargo.toml index dfed8621f1265d..1d737b39a8dc85 100644 --- a/platform-tools-sdk/cargo-build-sbf/Cargo.toml +++ b/platform-tools-sdk/cargo-build-sbf/Cargo.toml @@ -23,8 +23,9 @@ clap = { version = "3.1.5", features = ["cargo", "env"] } itertools = { workspace = true } log = { workspace = true, features = ["std"] } regex = { workspace = true } -reqwest = { workspace = true, features = ["blocking", "rustls-tls", "rustls-tls-native-roots" ] } +reqwest = { workspace = true, features = ["blocking", "rustls-tls", "rustls-tls-native-roots", "json" ] } semver = { workspace = true } +serde = { workspace = true } solana-file-download = "=3.1.0" solana-keypair = "=3.0.1" solana-logger = "=3.0.0" diff --git a/platform-tools-sdk/cargo-build-sbf/src/main.rs b/platform-tools-sdk/cargo-build-sbf/src/main.rs index ae96a5d52cad77..803ab0af5d04a3 100644 --- a/platform-tools-sdk/cargo-build-sbf/src/main.rs +++ b/platform-tools-sdk/cargo-build-sbf/src/main.rs @@ -6,8 +6,9 @@ use { crate::{ post_processing::post_process, toolchain::{ - corrupted_toolchain, generate_toolchain_name, get_base_rust_version, install_tools, - rust_target_triple, DEFAULT_PLATFORM_TOOLS_VERSION, + corrupted_toolchain, generate_toolchain_name, get_base_rust_version, + install_and_link_tools, install_tools, rust_target_triple, + validate_platform_tools_version, DEFAULT_PLATFORM_TOOLS_VERSION, }, utils::spawn, }, @@ -48,6 +49,7 @@ pub struct Config<'a> { arch: &'a str, optimize_size: bool, lto: bool, + install_only: bool, } impl Default for Config<'_> { @@ -81,6 +83,7 @@ impl Default for Config<'_> { arch: "v0", optimize_size: false, lto: false, + install_only: false, } } } @@ -139,7 +142,7 @@ fn prepare_environment( exit(1); }); - install_tools(config, package, metadata) + install_and_link_tools(config, package, metadata) } fn invoke_cargo(config: &Config, validated_toolchain_version: String) { @@ -436,6 +439,17 @@ fn main() { .conflicts_with("skip_tools_install") .help("Download and install platform-tools even when existing tools are located"), ) + .arg( + Arg::new("install_only") + .long("install-only") + .takes_value(false) + .conflicts_with("skip_tools_install") + .help( + "Download and install platform-tools, without building a program. May be used \ + together with `--force-tools-install` to override an existing installation. \ + To choose a specific version, use `--install-only --tools-version v1.51`", + ), + ) .arg( Arg::new("skip_tools_install") .long("skip-tools-install") @@ -615,12 +629,25 @@ fn main() { arch: matches.value_of("arch").unwrap(), optimize_size: matches.is_present("optimize_size"), lto: matches.is_present("lto"), + install_only: matches.is_present("install_only"), }; let manifest_path: Option = matches.value_of_t("manifest_path").ok(); if config.verbose { debug!("{config:?}"); debug!("manifest_path: {manifest_path:?}"); } + + if config.install_only { + let platform_tools_version = validate_platform_tools_version( + config + .platform_tools_version + .unwrap_or(DEFAULT_PLATFORM_TOOLS_VERSION), + DEFAULT_PLATFORM_TOOLS_VERSION, + ); + install_tools(&config, &platform_tools_version, true); + return; + } + build_solana(config, manifest_path); } diff --git a/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs b/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs index 9135485bac8dcc..5db3723ef69c01 100644 --- a/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs +++ b/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs @@ -3,7 +3,8 @@ use { bzip2::bufread::BzDecoder, log::{debug, error, warn}, regex::Regex, - solana_file_download::download_file, + serde::{Deserialize, Serialize}, + solana_file_download::{download_file, download_file_with_headers}, std::{ env, fs::{self, File}, @@ -17,6 +18,24 @@ use { pub(crate) const DEFAULT_PLATFORM_TOOLS_VERSION: &str = "v1.51"; pub(crate) const DEFAULT_RUST_VERSION: &str = "1.84.1"; +// Common headers used for Github API. +const USER_AGENT_HEADER: (&str, &str) = ("User-Agent", "cargo-build-sbf"); +const GITHUB_API_VERSION_HEADER: (&str, &str) = ("X-GitHub-Api-Version", "2022-11-28"); + +// Headers necessary for querying Github and expecting a JSON response. +const GITHUB_API_JSON_RESPONSE_HEADERS: [(&str, &str); 3] = [ + USER_AGENT_HEADER, + GITHUB_API_VERSION_HEADER, + ("Accept", "application/vnd.github+json"), +]; + +// Headers necessary for downloading a file from Github API. +const GITHUB_API_BYTES_RESPONSE_HEADERS: [(&str, &str); 3] = [ + USER_AGENT_HEADER, + GITHUB_API_VERSION_HEADER, + ("Accept", "application/octet-stream"), +]; + fn find_installed_platform_tools() -> Vec { let solana = home_dir().join(".cache").join("solana"); let package = "platform-tools"; @@ -70,7 +89,10 @@ fn semver_version(version: &str) -> String { } } -fn validate_platform_tools_version(requested_version: &str, builtin_version: &str) -> String { +pub(crate) fn validate_platform_tools_version( + requested_version: &str, + builtin_version: &str, +) -> String { // Early return here in case it's the first time we're running `cargo build-sbf` // and we need to create the cache folders if requested_version == builtin_version { @@ -130,14 +152,98 @@ pub(crate) fn get_base_rust_version(platform_tools_version: &str) -> String { } } +fn retrieve_file_from_github_api( + download_file_name: &str, + platform_tools_version: &str, + download_file_path: &Path, +) -> Result<(), String> { + #[derive(Debug, Deserialize, Serialize)] + struct Items { + name: String, + url: String, + } + + #[derive(Debug, Deserialize, Serialize)] + struct GithubResponse { + assets: Vec, + } + + let client = reqwest::blocking::Client::new(); + let query_url = format!("https://api.github.com/repos/anza-xyz/platform-tools/releases/tags/{platform_tools_version}"); + + let mut query_headers = reqwest::header::HeaderMap::new(); + for item in GITHUB_API_JSON_RESPONSE_HEADERS { + query_headers.insert(item.0, item.1.parse().unwrap()); + } + let response = client + .get(query_url) + .headers(query_headers) + .send() + .map_err(|err| format!("Failed to retrieve Github releases: {err}"))?; + + let parsed_response: GithubResponse = response + .json() + .map_err(|err| format!("Failed to parse Github response: {err}"))?; + + let download_url = parsed_response + .assets + .iter() + .find(|item| item.name == download_file_name) + .map(|item| item.url.as_str()) + .ok_or(format!("File {download_file_name} not found for download").as_str())?; + + download_file_with_headers( + download_url, + download_file_path, + true, + &mut None, + &GITHUB_API_BYTES_RESPONSE_HEADERS, + ) +} + +fn retrieve_file_from_browser_url( + download_file_name: &str, + platform_tools_version: &str, + download_file_path: &Path, +) -> Result<(), String> { + let url = format!("https://github.com/anza-xyz/platform-tools/releases/download/{platform_tools_version}/{download_file_name}"); + download_file(url.as_str(), download_file_path, true, &mut None) +} + +fn download_platform_tools( + download_file_name: &str, + platform_tools_version: &str, + download_file_path: &Path, + use_rest_api: bool, +) -> Result<(), String> { + if use_rest_api { + retrieve_file_from_github_api( + download_file_name, + platform_tools_version, + download_file_path, + ) + } else { + retrieve_file_from_browser_url( + download_file_name, + platform_tools_version, + download_file_path, + ) + .map_err(|err| { + format!( + "{err}\n It looks like the download has failed. If this is a persistent issue, \ + try `cargo-build-sbf --install-only` to download form an alternative source." + ) + }) + } +} + // Check whether a package is installed and install it if missing. -fn install_if_missing( +pub(crate) fn install_if_missing( config: &Config, package: &str, - url: &str, - download_file_name: &str, platform_tools_version: &str, target_path: &Path, + use_rest_api: bool, ) -> Result<(), String> { if config.force_tools_install { if target_path.is_dir() { @@ -180,17 +286,32 @@ fn install_if_missing( debug!("Remove file {target_path:?}"); fs::remove_file(target_path).map_err(|err| err.to_string())?; } + fs::create_dir_all(target_path).map_err(|err| err.to_string())?; - let mut url = String::from(url); - url.push('/'); - url.push_str(platform_tools_version); - url.push('/'); - url.push_str(download_file_name); - let download_file_path = target_path.join(download_file_name); + let arch = if cfg!(target_arch = "aarch64") { + "aarch64" + } else { + "x86_64" + }; + let platform_tools_download_file_name = if cfg!(target_os = "windows") { + format!("platform-tools-windows-{arch}.tar.bz2") + } else if cfg!(target_os = "macos") { + format!("platform-tools-osx-{arch}.tar.bz2") + } else { + format!("platform-tools-linux-{arch}.tar.bz2") + }; + + let download_file_path = target_path.join(&platform_tools_download_file_name); if download_file_path.exists() { fs::remove_file(&download_file_path).map_err(|err| err.to_string())?; } - download_file(url.as_str(), &download_file_path, true, &mut None)?; + + download_platform_tools( + &platform_tools_download_file_name, + platform_tools_version, + &download_file_path, + use_rest_api, + )?; let zip = File::open(&download_file_path).map_err(|err| err.to_string())?; let tar = BzDecoder::new(BufReader::new(zip)); let mut archive = Archive::new(tar); @@ -325,7 +446,36 @@ fn link_solana_toolchain(config: &Config, requested_toolchain_version: &str) { } } -pub(crate) fn install_tools( +pub(crate) fn install_tools(config: &Config, platform_tools_version: &str, use_rest_api: bool) { + let package = "platform-tools"; + let target_path = make_platform_tools_path_for_version(package, platform_tools_version); + install_if_missing( + config, + package, + platform_tools_version, + &target_path, + use_rest_api, + ) + .unwrap_or_else(|err| { + // The package version directory doesn't contain a valid + // installation, and it should be removed. + let target_path_parent = target_path.parent().expect("Invalid package path"); + if target_path_parent.exists() { + fs::remove_dir_all(target_path_parent).unwrap_or_else(|err| { + error!( + "Failed to remove {} while recovering from installation failure: {}", + target_path_parent.to_string_lossy(), + err, + ); + exit(1); + }); + } + error!("Failed to install platform-tools: {err}"); + exit(1); + }); +} + +pub(crate) fn install_and_link_tools( config: &Config, package: Option<&cargo_metadata::Package>, metadata: &cargo_metadata::Metadata, @@ -364,46 +514,7 @@ pub(crate) fn install_tools( let platform_tools_version = validate_platform_tools_version(platform_tools_version, DEFAULT_PLATFORM_TOOLS_VERSION); if !config.skip_tools_install { - let arch = if cfg!(target_arch = "aarch64") { - "aarch64" - } else { - "x86_64" - }; - - let platform_tools_download_file_name = if cfg!(target_os = "windows") { - format!("platform-tools-windows-{arch}.tar.bz2") - } else if cfg!(target_os = "macos") { - format!("platform-tools-osx-{arch}.tar.bz2") - } else { - format!("platform-tools-linux-{arch}.tar.bz2") - }; - let package = "platform-tools"; - let target_path = make_platform_tools_path_for_version(package, &platform_tools_version); - install_if_missing( - config, - package, - "https://github.com/anza-xyz/platform-tools/releases/download", - platform_tools_download_file_name.as_str(), - &platform_tools_version, - &target_path, - ) - .unwrap_or_else(|err| { - // The package version directory doesn't contain a valid - // installation, and it should be removed. - let target_path_parent = target_path.parent().expect("Invalid package path"); - if target_path_parent.exists() { - fs::remove_dir_all(target_path_parent).unwrap_or_else(|err| { - error!( - "Failed to remove {} while recovering from installation failure: {}", - target_path_parent.to_string_lossy(), - err, - ); - exit(1); - }); - } - error!("Failed to install platform-tools: {err}"); - exit(1); - }); + install_tools(config, &platform_tools_version, false); } if config.no_rustup_override { diff --git a/platform-tools-sdk/cargo-build-sbf/tests/crates.rs b/platform-tools-sdk/cargo-build-sbf/tests/crates.rs index 46a0be6af790ef..46a8ed3da11da9 100644 --- a/platform-tools-sdk/cargo-build-sbf/tests/crates.rs +++ b/platform-tools-sdk/cargo-build-sbf/tests/crates.rs @@ -12,7 +12,7 @@ fn should_install_tools() -> bool { let toolchain_path = PathBuf::from(tools_path) .join(".cache") .join("solana") - .join("v1.50") + .join("v1.51") .join("platform-tools"); let rust_path = toolchain_path.join("rust"); @@ -344,3 +344,24 @@ fn test_corrupted_toolchain() { // Revert to the original name, so other tests can run correctly. fs::rename(wrong_rust_folder, right_rust_folder).expect("Failed to rename file"); } + +#[test] +#[serial] +fn test_alternate_download() { + let args = [ + "-v", + "--sbf-sdk", + "../sbf", + "--install-only", + "--force-tools-install", + ]; + let assert = assert_cmd::Command::cargo_bin("cargo-build-sbf") + .unwrap() + .env("RUST_LOG", "debug") + .args(args) + .assert(); + + assert.success(); + + build_noop_and_readelf("v0"); +} From 9b0eb1f8f91a4e79cff6e6924dab5be4a63fd350 Mon Sep 17 00:00:00 2001 From: Lucas Ste <38472950+LucasSte@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:35:57 -0300 Subject: [PATCH 2/2] Update platform-tools-sdk/cargo-build-sbf/src/toolchain.rs Co-authored-by: Jon C --- platform-tools-sdk/cargo-build-sbf/src/toolchain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs b/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs index 5db3723ef69c01..fd9f771c375770 100644 --- a/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs +++ b/platform-tools-sdk/cargo-build-sbf/src/toolchain.rs @@ -231,7 +231,7 @@ fn download_platform_tools( .map_err(|err| { format!( "{err}\n It looks like the download has failed. If this is a persistent issue, \ - try `cargo-build-sbf --install-only` to download form an alternative source." + try `cargo-build-sbf --install-only` to download from an alternative source." ) }) }