use anyhow::bail;
use reqwest::StatusCode;
use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::Command;
pub fn protoc(version: &str, out_dir: &Path) -> anyhow::Result<PathBuf> {
let protoc_path = ensure_protoc_installed(version, out_dir)?;
Ok(protoc_path)
}
fn ensure_protoc_installed(version: &str, install_dir: &Path) -> anyhow::Result<PathBuf> {
let release_name = get_protoc_release_name(version);
let protoc_dir = install_dir.join(format!("protoc-fetcher/{release_name}"));
let protoc_path = protoc_dir.join("bin/protoc");
if protoc_path.exists() {
println!("protoc with correct version is already installed.");
} else {
println!("protoc v{version} not found, downloading...");
download_protoc(&protoc_dir, &release_name, version)?;
}
println!(
"`protoc --version`: {}",
get_protoc_version(&protoc_path).unwrap()
);
Ok(protoc_path)
}
fn download_protoc(protoc_dir: &Path, release_name: &str, version: &str) -> anyhow::Result<()> {
let archive_url = protoc_release_archive_url(release_name, version);
let response = reqwest::blocking::get(archive_url)?;
if response.status() != StatusCode::OK {
bail!(
"Error downloading release archive: {} {}",
response.status(),
response.text().unwrap_or_default()
);
}
println!("Download successful.");
fs::create_dir_all(protoc_dir)?;
let cursor = Cursor::new(response.bytes()?);
zip_extract::extract(cursor, protoc_dir, false)?;
println!("Extracted archive.");
#[cfg(unix)]
let protoc_path = protoc_dir.join("bin/protoc");
#[cfg(windows)]
let protoc_path = protoc_dir.join("bin/protoc.exe");
if !protoc_path.exists() {
bail!("Extracted protoc archive, but could not find bin/protoc!");
}
println!("protoc installed successfully: {:?}", &protoc_path);
Ok(())
}
fn protoc_release_archive_url(release_name: &str, version: &str) -> String {
let archive_url =
format!("https://github.com/protocolbuffers/protobuf/releases/download/v{version}/{release_name}.zip");
println!("Release URL: {archive_url}");
archive_url
}
fn get_protoc_release_name(version: &str) -> String {
#[allow(unused)]
let name = "";
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
let name = "linux-aarch_64";
#[cfg(all(target_os = "linux", target_arch = "x86"))]
let name = "linux-x86_32";
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
let name = "linux-x86_64";
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
let name = "osx-aarch_64";
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
let name = "osx-x86_64";
#[cfg(all(
target_os = "macos",
not(target_arch = "aarch64"),
not(target_arch = "x86_64")
))]
let name = "osx-universal_binary";
#[cfg(all(windows, target_pointer_width = "32"))]
let name = "win32";
#[cfg(all(windows, target_pointer_width = "64"))]
let name = "win64";
if name.is_empty() {
panic!("`protoc` unsupported platform");
}
println!("Detected: {}", name);
format!("protoc-{version}-{name}")
}
fn get_protoc_version(protoc_path: &Path) -> anyhow::Result<String> {
let version = String::from_utf8(Command::new(protoc_path).arg("--version").output()?.stdout)?;
Ok(version)
}