Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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 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() {
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