1use anyhow::bail;
5use reqwest::StatusCode;
6use std::fs;
7use std::io::Cursor;
8use std::path::{Path, PathBuf};
9use std::process::Command;
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 #[cfg(unix)]
95 let protoc_path = protoc_dir.join("bin/protoc");
96
97 #[cfg(windows)]
98 let protoc_path = protoc_dir.join("bin/protoc.exe");
99
100 if !protoc_path.exists() {
101 bail!("Extracted protoc archive, but could not find bin/protoc!");
102 }
103
104 println!("protoc installed successfully: {:?}", &protoc_path);
105 Ok(())
106}
107
108fn protoc_release_archive_url(release_name: &str, version: &str) -> String {
109 let archive_url =
110 format!("https://github.com/protocolbuffers/protobuf/releases/download/v{version}/{release_name}.zip");
111 println!("Release URL: {archive_url}");
112
113 archive_url
114}
115
116fn get_protoc_release_name(version: &str) -> String {
117 #[allow(unused)]
123 let name = "";
124
125 #[cfg(all(target_os = "linux", target_arch = "aarch64"))]
126 let name = "linux-aarch_64";
127
128 #[cfg(all(target_os = "linux", target_arch = "x86"))]
129 let name = "linux-x86_32";
130
131 #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
132 let name = "linux-x86_64";
133
134 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
135 let name = "osx-aarch_64";
136
137 #[cfg(all(target_os = "macos", target_arch = "x86_64"))]
138 let name = "osx-x86_64";
139
140 #[cfg(all(
141 target_os = "macos",
142 not(target_arch = "aarch64"),
143 not(target_arch = "x86_64")
144 ))]
145 let name = "osx-universal_binary";
146
147 #[cfg(all(windows, target_pointer_width = "32"))]
148 let name = "win32";
149
150 #[cfg(all(windows, target_pointer_width = "64"))]
151 let name = "win64";
152
153 if name.is_empty() {
154 panic!("`protoc` unsupported platform");
155 }
156
157 println!("Detected: {}", name);
158
159 format!("protoc-{version}-{name}")
160}
161
162fn get_protoc_version(protoc_path: &Path) -> anyhow::Result<String> {
163 let version = String::from_utf8(Command::new(protoc_path).arg("--version").output()?.stdout)?;
164 Ok(version)
165}