Skip to content

Commit 068e76c

Browse files
committed
add fallback to LLVM install script (for apt)
and conditionally use `sudo`
1 parent fadeb13 commit 068e76c

File tree

5 files changed

+145
-18
lines changed

5 files changed

+145
-18
lines changed

clang-installer/src/downloader/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,27 @@ async fn download(url: &Url, cache_path: &Path, timeout: u64) -> Result<(), Down
7272
Ok(())
7373
}
7474

75+
/// On Unix-like systems, this changes the permissions of the file at `path`.
76+
///
77+
/// If `mode` is `Some`, then the permissions will be set to the bitwise OR of
78+
/// the existing permissions and given `mode`.
79+
/// If `mode` is `None`, then the permissions will be set to `0o755`.
80+
#[cfg(unix)]
81+
fn chmod_file(path: &Path, mode: Option<u32>) -> std::io::Result<()> {
82+
// Make the extracted binary executable on Unix-like systems.
83+
use std::os::unix::fs::PermissionsExt;
84+
let out = fs::OpenOptions::new().write(true).open(path)?;
85+
let mut perms = out.metadata()?.permissions();
86+
match mode {
87+
Some(mode) => {
88+
let prev = perms.mode();
89+
perms.set_mode(prev | mode);
90+
}
91+
None => perms.set_mode(0o755),
92+
}
93+
out.set_permissions(perms)
94+
}
95+
7596
#[cfg(test)]
7697
mod tests {
7798
use crate::DownloadError;

clang-installer/src/downloader/native_packages/mod.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ pub enum PackageManagerError {
2020
package: String,
2121
stderr: String,
2222
},
23+
#[cfg(target_os = "linux")]
24+
#[error("Failed to add LLVM PPA repository (for `apt`): {0}")]
25+
LlvmPpaError(String),
26+
#[error("Failed parsing URL: {0}")]
27+
UrlParseError(#[from] url::ParseError),
28+
#[error(transparent)]
29+
DownloadError(#[from] crate::downloader::DownloadError),
2330
}
2431

2532
pub trait PackageManager {
@@ -46,7 +53,7 @@ pub trait PackageManager {
4653
&self,
4754
package_name: &str,
4855
version: Option<&Version>,
49-
) -> Result<(), PackageManagerError>;
56+
) -> impl Future<Output = Result<(), PackageManagerError>>;
5057
}
5158

5259
pub fn get_available_package_managers() -> Vec<impl PackageManager + Display> {
@@ -63,7 +70,7 @@ pub fn get_available_package_managers() -> Vec<impl PackageManager + Display> {
6370
managers
6471
}
6572

66-
pub fn try_install_package(
73+
pub async fn try_install_package(
6774
tool: &ClangTool,
6875
version_req: &VersionReq,
6976
min_version: &Version,
@@ -92,12 +99,13 @@ pub fn try_install_package(
9299
log::info!(
93100
"{mgr} package manager does not have a version of {tool} matching {version_req} installed."
94101
);
95-
match mgr.install_package(&pkg_name, Some(min_version)) {
102+
match mgr.install_package(&pkg_name, Some(min_version)).await {
96103
Ok(()) => {
97104
log::info!(
98105
"Successfully installed {tool} v{min_version} using {mgr} package manager."
99106
);
100-
let path = tool.get_exe_path(&RequestedVersion::SystemDefault)?;
107+
let path =
108+
tool.get_exe_path(&RequestedVersion::Requirement(version_req.clone()))?;
101109
let version = tool.capture_version(&path)?;
102110
if version_req.matches(&version) {
103111
log::info!(

clang-installer/src/downloader/native_packages/unix.rs

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ impl Display for UnixPackageManager {
3636
}
3737
}
3838

39+
#[cfg(target_os = "linux")]
40+
impl crate::downloader::caching::Cacher for UnixPackageManager {}
41+
3942
impl UnixPackageManager {
4043
fn as_str(&self) -> &'static str {
4144
match self {
@@ -49,6 +52,10 @@ impl UnixPackageManager {
4952
}
5053
}
5154

55+
fn has_sudo() -> bool {
56+
which::which("sudo").is_ok()
57+
}
58+
5259
fn pkg_name_with_version(&self, package_name: &str, version: Option<&Version>) -> String {
5360
match self {
5461
#[cfg(target_os = "linux")]
@@ -114,7 +121,7 @@ impl PackageManager for UnixPackageManager {
114121
}
115122
}
116123

117-
fn install_package(
124+
async fn install_package(
118125
&self,
119126
package_name: &str,
120127
version: Option<&Version>,
@@ -134,13 +141,37 @@ impl PackageManager for UnixPackageManager {
134141
args.push("install");
135142
}
136143
}
137-
let output = Command::new(self.as_str())
138-
.args(args)
139-
.arg(package_id.as_str())
140-
.output()?;
144+
let output = if Self::has_sudo() {
145+
Command::new("sudo")
146+
.arg(self.as_str())
147+
.args(args)
148+
.arg(package_id.as_str())
149+
.output()?
150+
} else {
151+
Command::new(self.as_str())
152+
.args(args)
153+
.arg(package_id.as_str())
154+
.output()?
155+
};
141156
if output.status.success() {
142157
Ok(())
143158
} else {
159+
#[cfg(target_os = "linux")]
160+
if matches!(self, UnixPackageManager::Apt)
161+
&& let Some(version) = version
162+
{
163+
use crate::downloader::caching::Cacher;
164+
165+
log::info!(
166+
"trying to install from official LLVM PPA repository (for Debian-based `apt` package manager)"
167+
);
168+
return llvm_apt_install::install_llvm_via_apt(
169+
Self::get_cache_dir().as_path(),
170+
version.major.to_string(),
171+
package_id.as_str(),
172+
)
173+
.await;
174+
}
144175
Err(PackageManagerError::InstallationError {
145176
manager: self.as_str().to_string(),
146177
package: package_id,
@@ -181,3 +212,75 @@ impl PackageManager for UnixPackageManager {
181212
}
182213
}
183214
}
215+
216+
#[cfg(target_os = "linux")]
217+
mod llvm_apt_install {
218+
use crate::downloader::{
219+
chmod_file, download,
220+
native_packages::{PackageManagerError, unix::UnixPackageManager},
221+
};
222+
use std::{path::Path, process::Command};
223+
use url::Url;
224+
225+
const LLVM_INSTALL_SCRIPT_URL: &str = "https://apt.llvm.org/llvm.sh";
226+
227+
/// Installs the official LLVM APT repository and its GPG key.
228+
///
229+
/// This is required to install specific versions of clang tools on Debian-based distributions using `apt`.}
230+
pub async fn install_llvm_via_apt(
231+
cache_path: &Path,
232+
ver_major: String,
233+
package_name: &str,
234+
) -> Result<(), PackageManagerError> {
235+
let download_path = cache_path.join("llvm_apt_install.sh");
236+
if !download_path.exists() {
237+
log::info!(
238+
"Downloading LLVM APT repository installation script from {LLVM_INSTALL_SCRIPT_URL}"
239+
);
240+
download(
241+
&Url::parse(LLVM_INSTALL_SCRIPT_URL)?,
242+
&download_path,
243+
60 * 2,
244+
)
245+
.await?;
246+
chmod_file(&download_path, Some(0o111))?;
247+
}
248+
let has_sudo = UnixPackageManager::has_sudo();
249+
250+
let output = if has_sudo {
251+
Command::new("sudo")
252+
.arg("bash")
253+
.arg(download_path.as_os_str())
254+
.arg(ver_major)
255+
.output()?
256+
} else {
257+
Command::new("bash")
258+
.arg(download_path.as_os_str())
259+
.arg(ver_major)
260+
.output()?
261+
};
262+
if !output.status.success() {
263+
return Err(PackageManagerError::LlvmPpaError(
264+
String::from_utf8_lossy(&output.stderr).to_string(),
265+
));
266+
}
267+
let output = if has_sudo {
268+
Command::new("sudo")
269+
.arg("apt")
270+
.args(["install", "-y", package_name])
271+
.output()
272+
} else {
273+
Command::new("apt")
274+
.args(["install", "-y", package_name])
275+
.output()
276+
}?;
277+
if !output.status.success() {
278+
return Err(PackageManagerError::InstallationError {
279+
manager: "apt (with LLVM PPA)".to_string(),
280+
package: package_name.to_string(),
281+
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
282+
});
283+
}
284+
Ok(())
285+
}
286+
}

clang-installer/src/downloader/static_dist.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,7 @@ impl StaticDistDownloader {
151151
log::info!("Downloading static binary for {tool} version {ver_str} from {url}");
152152
download(&url, &download_path, 60 * 2).await?;
153153
#[cfg(unix)]
154-
{
155-
// Make the extracted binary executable on Unix-like systems.
156-
use std::os::unix::fs::PermissionsExt;
157-
let out = fs::OpenOptions::new().write(true).open(&download_path)?;
158-
let mut perms = out.metadata()?.permissions();
159-
perms.set_mode(0o755);
160-
out.set_permissions(perms)?;
161-
}
154+
super::chmod_file(&download_path, None)?;
162155
}
163156
let sha512_cache_path = cache_path
164157
.join("static_dist")

clang-installer/src/version.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ impl RequestedVersion {
139139
Ok(bin) => bin,
140140
Err(e) => {
141141
log::error!("Failed to download {tool} {version_req} from PyPi: {e}");
142-
if let Some(result) = try_install_package(tool, version_req, &min_ver)? {
142+
if let Some(result) =
143+
try_install_package(tool, version_req, &min_ver).await?
144+
{
143145
return Ok(Some(result));
144146
}
145147
log::info!("Falling back to downloading {tool} static binaries.");

0 commit comments

Comments
 (0)