revive_llvm_builder/
utils.rs1use std::fs::File;
4use std::path::Path;
5use std::path::PathBuf;
6use std::process::Command;
7use std::process::Stdio;
8use std::time::Duration;
9
10use anyhow::Context;
11use path_slash::PathBufExt;
12
13pub const LLVM_HOST_SOURCE_URL: &str = "https://github.com/llvm/llvm-project";
15
16pub const LLVM_HOST_SOURCE_TAG: &str = "llvmorg-18.1.8";
18
19pub const XCODE_MIN_VERSION: u32 = 11;
21
22pub const XCODE_VERSION_15: u32 = 15;
24
25pub const DOWNLOAD_RETRIES: u16 = 16;
27
28pub const DOWNLOAD_PARALLEL_REQUESTS: u16 = 1;
30
31pub const DOWNLOAD_TIMEOUT_SECONDS: u64 = 300;
33
34pub const MUSL_SNAPSHOTS_URL: &str = "https://git.musl-libc.org/cgit/musl/snapshot";
36
37pub const EMSDK_SOURCE_URL: &str = "https://github.com/emscripten-core/emsdk.git";
39
40pub const EMSDK_VERSION: &str = "4.0.9";
42
43pub fn command(command: &mut Command, description: &str) -> anyhow::Result<()> {
47 log::debug!("executing '{command:?}' ({description})");
48
49 if std::env::var("DRY_RUN").is_ok() {
50 log::warn!("Only a dry run; not executing the command.");
51 return Ok(());
52 }
53
54 let status = command
55 .status()
56 .map_err(|error| anyhow::anyhow!("{} process: {}", description, error))?;
57
58 if !status.success() {
59 log::error!("the command '{command:?}' failed!");
60 anyhow::bail!("{} failed", description);
61 }
62
63 Ok(())
64}
65
66pub fn download(url: &str, path: &str) -> anyhow::Result<()> {
68 log::trace!("downloading '{url}' into '{path}'");
69
70 let mut downloader = downloader::Downloader::builder()
71 .download_folder(Path::new(path))
72 .parallel_requests(DOWNLOAD_PARALLEL_REQUESTS)
73 .retries(DOWNLOAD_RETRIES)
74 .timeout(Duration::from_secs(DOWNLOAD_TIMEOUT_SECONDS))
75 .build()?;
76 while let Err(error) = downloader.download(&[downloader::Download::new(url)]) {
77 log::error!("MUSL download from `{url}` failed: {error}");
78 }
79 Ok(())
80}
81
82pub fn unpack_tar(filename: PathBuf, path: &str) -> anyhow::Result<()> {
84 let tar_gz = File::open(filename)?;
85 let tar = flate2::read::GzDecoder::new(tar_gz);
86 let mut archive = tar::Archive::new(tar);
87 archive.unpack(path)?;
88 Ok(())
89}
90
91pub fn download_musl(name: &str) -> anyhow::Result<()> {
93 log::info!("downloading musl {name}");
94 let tar_file_name = format!("{name}.tar.gz");
95 let url = format!("{MUSL_SNAPSHOTS_URL}/{tar_file_name}");
96 let target_path = crate::llvm_path::DIRECTORY_LLVM_TARGET
97 .get()
98 .unwrap()
99 .to_string_lossy();
100 download(url.as_str(), &target_path)?;
101 let musl_tarball = crate::LLVMPath::musl_source(tar_file_name.as_str())?;
102 unpack_tar(musl_tarball, &target_path)?;
103 Ok(())
104}
105
106pub fn ninja(build_dir: &Path) -> anyhow::Result<()> {
108 let mut ninja = Command::new("ninja");
109 ninja.args(["-C", build_dir.to_string_lossy().as_ref()]);
110 if std::env::var("DRY_RUN").is_ok() {
111 ninja.arg("-n");
112 }
113 command(ninja.arg("install"), "Running ninja install")?;
114 Ok(())
115}
116
117pub fn absolute_path<P: AsRef<Path>>(path: P) -> anyhow::Result<PathBuf> {
119 let mut full_path = std::env::current_dir()?;
120 full_path.push(path);
121 Ok(full_path)
122}
123
124pub fn path_windows_to_unix<P: AsRef<Path> + PathBufExt>(path: P) -> anyhow::Result<PathBuf> {
128 path.to_slash()
129 .map(|pathbuf| PathBuf::from(pathbuf.to_string()))
130 .ok_or_else(|| anyhow::anyhow!("Windows-to-Unix path conversion error"))
131}
132
133pub fn check_presence(name: &str) -> anyhow::Result<()> {
135 which::which(name).with_context(|| format!("Tool `{name}` is missing. Please install"))?;
136 Ok(())
137}
138
139pub fn get_xcode_version() -> anyhow::Result<u32> {
141 let pkgutil = Command::new("pkgutil")
142 .args(["--pkg-info", "com.apple.pkg.CLTools_Executables"])
143 .stdout(Stdio::piped())
144 .spawn()
145 .map_err(|error| anyhow::anyhow!("`pkgutil` process: {}", error))?;
146 let grep_version = Command::new("grep")
147 .arg("version")
148 .stdin(Stdio::from(pkgutil.stdout.expect(
149 "Failed to identify XCode version - XCode or CLI tools are not installed",
150 )))
151 .output()
152 .map_err(|error| anyhow::anyhow!("`grep` process: {}", error))?;
153 let version_string = String::from_utf8(grep_version.stdout)?;
154 let version_regex = regex::Regex::new(r"version: (\d+)\..*")?;
155 let captures = version_regex
156 .captures(version_string.as_str())
157 .ok_or(anyhow::anyhow!(
158 "Failed to parse XCode version: {version_string}"
159 ))?;
160 let xcode_version: u32 = captures
161 .get(1)
162 .expect("Always has a major version")
163 .as_str()
164 .parse()
165 .map_err(|error| anyhow::anyhow!("Failed to parse XCode version: {error}"))?;
166 Ok(xcode_version)
167}
168
169pub fn install_emsdk() -> anyhow::Result<()> {
171 log::info!("installing emsdk v{EMSDK_VERSION}");
172
173 let emsdk_source_path = PathBuf::from(crate::LLVMPath::DIRECTORY_EMSDK_SOURCE);
174
175 if emsdk_source_path.exists() {
176 log::warn!(
177 "emsdk source path {emsdk_source_path:?} already exists.
178 Skipping the emsdk installation, delete the source path for re-installation"
179 );
180 return Ok(());
181 }
182
183 crate::utils::command(
184 Command::new("git")
185 .arg("clone")
186 .arg(crate::utils::EMSDK_SOURCE_URL)
187 .arg(emsdk_source_path.to_string_lossy().as_ref()),
188 "Emscripten SDK repository cloning",
189 )?;
190
191 crate::utils::command(
192 Command::new("git")
193 .arg("checkout")
194 .arg(format!("tags/{}", crate::utils::EMSDK_VERSION))
195 .current_dir(&emsdk_source_path),
196 "Emscripten SDK repository version checkout",
197 )?;
198
199 crate::utils::command(
200 Command::new("./emsdk")
201 .arg("install")
202 .arg(EMSDK_VERSION)
203 .current_dir(&emsdk_source_path),
204 "Emscripten SDK installation",
205 )?;
206
207 crate::utils::command(
208 Command::new("./emsdk")
209 .arg("activate")
210 .arg(EMSDK_VERSION)
211 .current_dir(&emsdk_source_path),
212 "Emscripten SDK activation",
213 )?;
214
215 log::warn!(
216 "run 'source {}emsdk_env.sh' to finish the emsdk installation",
217 emsdk_source_path.display()
218 );
219
220 Ok(())
221}
222
223pub fn directory_target_llvm(target_env: crate::target_env::TargetEnv) -> PathBuf {
225 crate::llvm_path::DIRECTORY_LLVM_TARGET
226 .get_or_init(|| PathBuf::from(format!("./target-llvm/{target_env}/")))
227 .clone()
228}