* Copyright (c) 2024 Huawei Technologies Co., Ltd.
* upatch-build is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
use std::{
ffi::{OsStr, OsString},
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
};
use anyhow::{bail, Context, Result};
use indexmap::IndexSet;
use which::which;
use syscare_common::{concat_os, ffi::OsStrExt as _, fs, process::Command};
use crate::dwarf::{Producer, ProducerParser, ProducerType};
#[derive(Debug, Clone)]
pub struct Compiler {
pub prefix: Option<OsString>,
pub name: OsString,
pub kind: ProducerType,
pub version: OsString,
pub path: PathBuf,
pub linker: PathBuf,
}
impl Compiler {
pub fn parse<P, Q>(path: P, output_dir: Q) -> Result<Compiler>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let path = path.as_ref().to_path_buf();
let name = path
.file_name()
.context("Failed to parse compiler name")?
.to_os_string();
let output_path = Self::build_test_object(&path, &name, output_dir.as_ref())
.context("Failed to build test object")?;
let prefix = Self::parse_compiler_prefix(&name).map(OsStr::to_os_string);
let producer = Self::parse_compiler_producer(&output_path)
.context("Failed to parse compiler producer")?;
let linker = Self::get_compiler_linker(&path, &prefix, &producer)
.context("Failed to get compiler linker")?;
Ok(Self {
prefix,
name,
kind: producer.kind,
version: producer.version,
path,
linker,
})
}
fn build_test_object(path: &Path, name: &OsStr, output_dir: &Path) -> Result<PathBuf> {
let source_file = output_dir.join("test.c");
let output_file = output_dir.join(concat_os!(name, "-test"));
if !source_file.exists() {
fs::write(&source_file, "int main() { return 0; }")?;
}
Command::new(path)
.arg("-g")
.arg(&source_file)
.arg("-o")
.arg(&output_file)
.run()?
.exit_ok()?;
Ok(output_file)
}
fn parse_compiler_prefix(compiler: &OsStr) -> Option<&OsStr> {
* Matches compiler prefix of compiler name
* eg. x86_64-linux-gnu-gcc -> x86_64-linux-gnu-
* eg. aarch64-target-linux-clang -> aarch64-target-linux-
*/
let slice = compiler.as_bytes();
let spliter_indices = slice.iter().enumerate().rev().filter_map(|(index, &b)| {
if b == b'-' {
return Some(index);
}
None
});
for pos in spliter_indices {
let (prefix, name) = slice.split_at(pos + 1);
if name.iter().any(|&b| !b.is_ascii_digit()) {
return Some(OsStr::from_bytes(prefix));
}
}
None
}
fn parse_compiler_producer(path: &Path) -> Result<Producer> {
let producer_parser = ProducerParser::open(path)
.with_context(|| format!("Failed to open {}", path.display()))?;
let producer_iter = producer_parser
.parse()
.with_context(|| format!("Failed to parse {}", path.display()))?;
let mut producer_map = IndexSet::new();
for parse_result in producer_iter {
let producer = parse_result.context("Failed to parse object producer")?;
producer_map.insert(producer);
}
producer_map.sort();
producer_map.pop().context("No object producer")
}
fn get_component(
path: &Path,
prefix: &Option<OsString>,
name: &str,
) -> Result<Option<PathBuf>> {
let get_component_path = |name: &OsStr| -> Result<Option<PathBuf>> {
let output = Command::new(path)
.arg(concat_os!("-print-prog-name=", name))
.run_with_output()?;
output.exit_ok()?;
Ok(which(output.stdout.trim()).ok())
};
if let Some(prefixed_name) = prefix.as_ref().map(|pfx| concat_os!(pfx, name)) {
let component = get_component_path(&prefixed_name)?;
if component.is_some() {
return Ok(component);
}
}
get_component_path(OsStr::new(name))
}
fn get_compiler_linker(
path: &Path,
prefix: &Option<OsString>,
producer: &Producer,
) -> Result<PathBuf> {
const CLANG_LINKER_NAMES: &[&str] = &["ld.lld", "ld"];
const GNU_LINKER_NAMES: &[&str] = &["ld"];
let linkers = if matches!(producer.kind, ProducerType::ClangC | ProducerType::ClangCxx) {
CLANG_LINKER_NAMES
} else {
GNU_LINKER_NAMES
};
for name in linkers {
if let Some(path) = Self::get_component(path, prefix, name)? {
return Ok(path);
}
}
bail!("No suitable linker")
}
}
impl std::fmt::Display for Compiler {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{}{}",
self.prefix.as_deref().unwrap_or_default().to_string_lossy(),
self.name.to_string_lossy()
))
}
}