Skip to content

Commit

Permalink
Initial version of autocc code
Browse files Browse the repository at this point in the history
We can use a fairly trivial mechanism to detect from the process environment
which toolchain we need to call out to. If that fails we can inspect the filesystem
using the `PATH` variable.

We'll try to keep things as cheap as possible, and then we `execvpe` to replace the
running process with the real underlying compiler, while forcing the `argv[0]` name
to `/usr/bin/cc`.

Signed-off-by: Ikey Doherty <[email protected]>
  • Loading branch information
ikeycode committed Jul 15, 2024
1 parent 611a976 commit 2f27b48
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "autocc"
version = "0.1.0"
edition = "2021"

[dependencies]
128 changes: 128 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// SPDX-FileCopyrightText: Copyright © 2020-2024 Serpent OS Developers
//
// SPDX-License-Identifier: MPL-2.0

//! autocc
//!
//! Simple helper to "do the right thing" and be a sensible `/usr/bin/cc` helper,
//! calling out to the right compiler (i.e. `/usr/bin/clang`) without needing mangling
//! of the filesystem

use std::{env, ffi::OsStr, io, os::unix::process::CommandExt, path::PathBuf, process};

/// Right now we only support GNU (gcc) and LLVM (clang)
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
enum Toolchain {
// GNU (GCC)
GNU(String),

// LLVM (clang)
LLVM(String),
}

impl AsRef<str> for Toolchain {
fn as_ref(&self) -> &str {
match self {
Toolchain::GNU(s) => s,
Toolchain::LLVM(s) => s,
}
}
}

/// Correctly demangle an environment variable into just the binary *name*
fn env_var_without_args(name: impl AsRef<OsStr>) -> Option<String> {
let var = env::var(name.as_ref()).ok()?;

let result = var.split('/').last()?.split(' ').next()?;
Some(result.to_owned())
}

/// Attempt to find the tool relative to the path given (same dir)
fn tool_relative_to_path(path: impl AsRef<OsStr>, tool: &'static str) -> Option<String> {
let path = PathBuf::from(path.as_ref());
let input_path = path.parent()?;
let tool_path = input_path.join(tool);
if tool_path.exists() {
Some(tool_path.to_str()?.to_owned())
} else {
None
}
}

/// Try to return the correct toolchain based on the environment
fn toolchain_from_environment() -> Option<Toolchain> {
// Query CC var
if let Some(cc) = env_var_without_args("CC") {
match cc.as_str() {
"clang" => return Some(Toolchain::LLVM(env::var("CC").ok()?.to_owned())),
"gcc" => return Some(Toolchain::GNU(env::var("CC").ok()?.to_owned())),
x if x.contains("-gcc-") || x.ends_with("-gcc") => {
return Some(Toolchain::GNU(env::var("CC").ok()?.to_owned()))
}
_ => {}
}
}

// Query LD var
if let Some(ld) = env_var_without_args("LD") {
match ld.as_str() {
"lld" => return Some(Toolchain::LLVM(tool_relative_to_path(&ld, "clang")?)),
"ld" => return Some(Toolchain::GNU(tool_relative_to_path(&ld, "gcc")?)),
x if x.starts_with("ld.") => {
return Some(Toolchain::GNU(tool_relative_to_path(&ld, "gcc")?))
}
_ => {}
}
}

None
}

fn find_in_path(name: impl AsRef<OsStr>) -> Option<String> {
let path = env::var("PATH").unwrap_or_else(|_| "/usr/local/bin:/usr/bin:/bin".into());
let name = name.as_ref();
env::split_paths(&path)
.filter_map(|p| {
let tool_path = p.join(name);
if tool_path.exists() {
return Some(tool_path.to_string_lossy().to_string());
} else {
None
}
})
.next()
}

/// Check well known filesystesm path
fn toolchain_from_filesystem() -> Option<Toolchain> {
if let Some(clang) = find_in_path("clang") {
Some(Toolchain::LLVM(clang))
} else {
find_in_path("gcc").map(Toolchain::GNU)
}
}

/// Reexecute process as `cc` from whence we live, calling required toolchain
fn reexecute_with_args(compiler: &str) -> Result<(), io::Error> {
let mut cmd = process::Command::new(compiler);
cmd.arg0("/usr/bin/cc");
cmd.args(env::args().skip(1));
cmd.exec();

eprintln!("cmd = {cmd:?}");

Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
let toolchain = if let Some(toolchain) = toolchain_from_environment() {
Some(toolchain)
} else {
toolchain_from_filesystem()
}
.expect("failed to find compiler");

reexecute_with_args(toolchain.as_ref())?;
Ok(())
}

0 comments on commit 2f27b48

Please sign in to comment.