1use anyhow::bail;
5use reqwest::StatusCode;
6use std::io::Cursor;
7use std::path::{Path, PathBuf};
8use std::process::Command;
9use std::{env, fs};
10
11pub fn protoc(version: &str, out_dir: &Path) -> anyhow::Result<PathBuf> {
50 let protoc_path = ensure_protoc_installed(version, out_dir)?;
51
52 Ok(protoc_path)
53}
54
55fn ensure_protoc_installed(version: &str, install_dir: &Path) -> anyhow::Result<PathBuf> {
59 let release_name = get_protoc_release_name(version);
60
61 let protoc_dir = install_dir.join(format!("protoc-fetcher/{release_name}"));
62 let protoc_path = protoc_dir.join("bin/protoc");
63 if protoc_path.exists() {
64 println!("protoc with correct version is already installed.");
65 } else {
66 println!("protoc v{version} not found, downloading...");
67 download_protoc(&protoc_dir, &release_name, version)?;
68 }
69 println!(
70 "`protoc --version`: {}",
71 get_protoc_version(&protoc_path).unwrap()
72 );
73
74 Ok(protoc_path)
75}
76
77fn download_protoc(protoc_dir: &Path, release_name: &str, version: &str) -> anyhow::Result<()> {
78 let archive_url = protoc_release_archive_url(release_name, version);
79 let response = reqwest::blocking::get(archive_url)?;
80 if response.status() != StatusCode::OK {
81 bail!(
82 "Error downloading release archive: {} {}",
83 response.status(),
84 response.text().unwrap_or_default()
85 );
86 }
87 println!("Download successful.");
88
89 fs::create_dir_all(protoc_dir)?;
90 let cursor = Cursor::new(response.bytes()?);
91 zip_extract::extract(cursor, protoc_dir, false)?;
92 println!("Extracted archive.");
93
94 let protoc_path = protoc_dir.join("bin/protoc");
95 if !protoc_path.exists() {
96 bail!("Extracted protoc archive, but could not find bin/protoc!");
97 }
98
99 println!("protoc installed successfully: {:?}", &protoc_path);
100 Ok(())
101}
102
103fn protoc_release_archive_url(release_name: &str, version: &str) -> String {
104 let archive_url =
105 format!("https://github.com/protocolbuffers/protobuf/releases/download/v{version}/{release_name}.zip");
106 println!("Release URL: {archive_url}");
107
108 archive_url
109}
110
111fn get_protoc_release_name(version: &str) -> String {
112 let mut platform = env::consts::OS;
113 let mut arch = env::consts::ARCH;
114 println!("Detected: {}, {}", platform, arch);
115
116 if platform == "macos" {
120 platform = "osx"; }
122 if arch == "aarch64" {
123 arch = "aarch_64";
124 }
125
126 format!("protoc-{version}-{platform}-{arch}")
127}
128
129fn get_protoc_version(protoc_path: &Path) -> anyhow::Result<String> {
130 let version = String::from_utf8(Command::new(&protoc_path).arg("--version").output()?.stdout)?;
131 Ok(version)
132}