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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion platform-tools-sdk/cargo-build-sbf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
33 changes: 30 additions & 3 deletions platform-tools-sdk/cargo-build-sbf/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -48,6 +49,7 @@ pub struct Config<'a> {
arch: &'a str,
optimize_size: bool,
lto: bool,
install_only: bool,
}

impl Default for Config<'_> {
Expand Down Expand Up @@ -81,6 +83,7 @@ impl Default for Config<'_> {
arch: "v0",
optimize_size: false,
lto: false,
install_only: false,
}
}
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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<PathBuf> = 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);
}

Expand Down
217 changes: 164 additions & 53 deletions platform-tools-sdk/cargo-build-sbf/src/toolchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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<String> {
let solana = home_dir().join(".cache").join("solana");
let package = "platform-tools";
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Items>,
}

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 from 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() {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
Loading
Loading