build_support/
lib.rs

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";