1use std::env;
2use std::path::{Path, PathBuf};
3
4pub fn parse_bool_env(key: &str) -> bool {
5 match env::var(key) {
6 Ok(v) => matches!(
7 v.as_str(),
8 "1" | "true" | "yes" | "on" | "TRUE" | "YES" | "ON"
9 ),
10 Err(_) => false,
11 }
12}
13
14pub fn msvc_crt_suffix_from_env(target_env: Option<&str>) -> Option<&'static str> {
15 let is_msvc = match target_env {
16 Some(s) => s == "msvc",
17 None => matches!(
18 env::var("CARGO_CFG_TARGET_ENV").ok().as_deref(),
19 Some("msvc")
20 ),
21 };
22 if !is_msvc {
23 return None;
24 }
25 let tf = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
26 if tf.split(',').any(|f| f == "crt-static") {
27 Some("mt")
28 } else {
29 Some("md")
30 }
31}
32
33pub fn expected_lib_name(target_env: &str, base: &str) -> String {
34 if target_env == "msvc" {
35 format!("{}.lib", base)
36 } else {
37 format!("lib{}.a", base)
38 }
39}
40
41pub fn compose_archive_name(
42 crate_short: &str,
43 version: &str,
44 target: &str,
45 link_type: &str,
46 extra: Option<&str>,
47 crt: &str,
48) -> String {
49 let extra = extra.unwrap_or("");
50 if crt.is_empty() {
51 if extra.is_empty() {
52 format!(
53 "{}-prebuilt-{}-{}-{}.tar.gz",
54 crate_short, version, target, link_type
55 )
56 } else {
57 format!(
58 "{}-prebuilt-{}-{}-{}{}.tar.gz",
59 crate_short, version, target, link_type, extra
60 )
61 }
62 } else if extra.is_empty() {
63 format!(
64 "{}-prebuilt-{}-{}-{}-{}.tar.gz",
65 crate_short, version, target, link_type, crt
66 )
67 } else {
68 format!(
69 "{}-prebuilt-{}-{}-{}{}-{}.tar.gz",
70 crate_short, version, target, link_type, extra, crt
71 )
72 }
73}
74
75pub fn release_tags(crate_sys_name: &str, version: &str) -> [String; 2] {
76 [
77 format!("{}-v{}", crate_sys_name, version),
78 format!("v{}", version),
79 ]
80}
81
82pub fn compose_manifest_bytes(
83 crate_short: &str,
84 version: &str,
85 target: &str,
86 link_type: &str,
87 crt: &str,
88 features: Option<&str>,
89) -> Vec<u8> {
90 let mut buf = Vec::new();
91 use std::io::Write;
92 let _ = writeln!(
93 &mut buf,
94 "{} prebuilt\nversion={}\ntarget={}\nlink={}\ncrt={}",
95 crate_short, version, target, link_type, crt
96 );
97 if let Some(f) = features
98 && !f.is_empty()
99 {
100 let _ = writeln!(&mut buf, "features={}", f);
101 }
102 buf
103}
104
105pub fn release_candidate_urls(
106 owner: &str,
107 repo: &str,
108 tags: &[String],
109 names: &[String],
110) -> Vec<String> {
111 let mut out = Vec::with_capacity(tags.len() * names.len());
112 for tag in tags {
113 for name in names {
114 out.push(format!(
115 "https://github.com/{}/{}/releases/download/{}/{}",
116 owner, repo, tag, name
117 ));
118 }
119 }
120 out
121}
122
123pub fn release_candidate_urls_default(tags: &[String], names: &[String]) -> Vec<String> {
124 release_candidate_urls(DEFAULT_GITHUB_OWNER, DEFAULT_GITHUB_REPO, tags, names)
125}
126
127pub fn release_owner_repo() -> (String, String) {
128 let owner =
129 env::var("BUILD_SUPPORT_GH_OWNER").unwrap_or_else(|_| DEFAULT_GITHUB_OWNER.to_string());
130 let repo =
131 env::var("BUILD_SUPPORT_GH_REPO").unwrap_or_else(|_| DEFAULT_GITHUB_REPO.to_string());
132 (owner, repo)
133}
134
135pub fn release_candidate_urls_env(tags: &[String], names: &[String]) -> Vec<String> {
136 let (owner, repo) = release_owner_repo();
137 release_candidate_urls(&owner, &repo, tags, names)
138}
139
140pub fn is_offline() -> bool {
141 match env::var("CARGO_NET_OFFLINE") {
142 Ok(v) => matches!(
143 v.as_str(),
144 "1" | "true" | "yes" | "on" | "TRUE" | "YES" | "ON"
145 ),
146 Err(_) => false,
147 }
148}
149
150pub fn prebuilt_extract_dir_env(cache_root: &Path, target_env: &str) -> PathBuf {
151 let target = env::var("TARGET").unwrap_or_default();
152 let crt_suffix = if target_env == "msvc" {
153 let tf = env::var("CARGO_CFG_TARGET_FEATURE").unwrap_or_default();
154 if tf.split(',').any(|f| f == "crt-static") {
155 "-mt"
156 } else {
157 "-md"
158 }
159 } else {
160 ""
161 };
162 cache_root
163 .join(target)
164 .join(format!("static{}", crt_suffix))
165}
166
167pub fn extract_archive_to_cache(
168 archive_path: &Path,
169 cache_root: &Path,
170 lib_name: &str,
171) -> Result<PathBuf, String> {
172 let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
173 let extract_dir = prebuilt_extract_dir_env(cache_root, &target_env);
174 if extract_dir.exists() {
175 let lib_dir = extract_dir.join("lib");
176 if lib_dir.join(lib_name).exists() || extract_dir.join(lib_name).exists() {
177 return Ok(lib_dir);
178 }
179 let _ = std::fs::remove_dir_all(&extract_dir);
180 }
181 std::fs::create_dir_all(&extract_dir)
182 .map_err(|e| format!("create dir {}: {}", extract_dir.display(), e))?;
183 let file = std::fs::File::open(archive_path)
184 .map_err(|e| format!("open {}: {}", archive_path.display(), e))?;
185 let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(file));
186 archive
187 .unpack(&extract_dir)
188 .map_err(|e| format!("unpack {}: {}", archive_path.display(), e))?;
189 let lib_dir = extract_dir.join("lib");
190 if lib_dir.join(lib_name).exists() {
191 return Ok(lib_dir);
192 }
193 if extract_dir.join(lib_name).exists() {
194 return Ok(extract_dir);
195 }
196 Err("extracted archive did not contain expected library".into())
197}
198
199pub fn download_prebuilt(
200 cache_root: &Path,
201 url: &str,
202 lib_name: &str,
203 _target_env: &str,
204) -> Result<PathBuf, String> {
205 let dl_dir = cache_root.join("download");
206 let _ = std::fs::create_dir_all(&dl_dir);
207
208 if url.ends_with(".tar.gz") || url.ends_with(".tgz") {
209 let fname = url.split('/').next_back().unwrap_or("prebuilt.tar.gz");
210 let archive_path = dl_dir.join(fname);
211 if !archive_path.exists() {
212 let client = reqwest::blocking::Client::builder()
213 .timeout(std::time::Duration::from_secs(300))
214 .build()
215 .map_err(|e| format!("create http client: {}", e))?;
216 let resp = client
217 .get(url)
218 .send()
219 .map_err(|e| format!("http get: {}", e))?;
220 if !resp.status().is_success() {
221 return Err(format!("http status {}", resp.status()));
222 }
223 let bytes = resp.bytes().map_err(|e| format!("read body: {}", e))?;
224 std::fs::write(&archive_path, &bytes)
225 .map_err(|e| format!("write {}: {}", archive_path.display(), e))?;
226 }
227 return extract_archive_to_cache(&archive_path, cache_root, lib_name);
228 }
229
230 let dst = dl_dir.join(lib_name);
231 if dst.exists() {
232 return Ok(dl_dir);
233 }
234 let client = reqwest::blocking::Client::builder()
235 .timeout(std::time::Duration::from_secs(120))
236 .build()
237 .map_err(|e| format!("http client: {}", e))?;
238 let resp = client
239 .get(url)
240 .send()
241 .map_err(|e| format!("http get: {}", e))?;
242 if !resp.status().is_success() {
243 return Err(format!("http status {}", resp.status()));
244 }
245 let bytes = resp.bytes().map_err(|e| format!("read body: {}", e))?;
246 std::fs::write(&dst, &bytes).map_err(|e| format!("write {}: {}", dst.display(), e))?;
247 Ok(dl_dir)
248}
249
250pub fn prebuilt_cache_root_from_env_or_target(
251 manifest_dir: &Path,
252 cache_env_var: &str,
253 folder: &str,
254) -> PathBuf {
255 if let Ok(dir) = env::var(cache_env_var) {
256 return PathBuf::from(dir);
257 }
258 let target_dir = env::var("CARGO_TARGET_DIR")
259 .map(PathBuf::from)
260 .unwrap_or_else(|_| manifest_dir.parent().unwrap().join("target"));
261 target_dir.join(folder)
262}
263pub const DEFAULT_GITHUB_OWNER: &str = "Latias94";
264pub const DEFAULT_GITHUB_REPO: &str = "dear-imgui";