#![allow(clippy::redundant_pub_crate)]
mod analysis;
mod cfg_dump;
mod database;
mod types;
use std::path::Path;
use std::sync::Mutex;
use codehelion_helper::PROTOCOL_VERSION;
use codehelion_helper::ir::{COMPILER_IR_SCHEMA_VERSION, Unavailability};
use codehelion_helper::protocol::{
Analyze, BuildDescription, Capability, DescribeBuild, HelperIdentity,
};
use codehelion_helper::server::{Answer, Backend, Description, serve};
use crate::analysis::Outcome;
use crate::database::Databases;
const NAME: &str = "codehelion-backend-clang";
static REFUSALS: Mutex<Vec<String>> = Mutex::new(Vec::new());
pub(crate) fn refused(why: &str) {
let line = format!("{NAME}: {why}");
REFUSALS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(line.clone());
eprintln!("{line}");
}
fn taken_refusals() -> Vec<String> {
std::mem::take(
&mut *REFUSALS
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
)
}
fn main() -> std::process::ExitCode {
if let Err(error) = codehelion_helper::enforce_current_process_limit_from_environment() {
eprintln!("{NAME}: {error}");
return std::process::ExitCode::FAILURE;
}
let clang = match clang::Clang::new() {
Ok(clang) => clang,
Err(why) => {
eprintln!(
"{NAME}: no libclang to analyse with: {why}. Install Clang's library and, \
if it is somewhere unusual, point LIBCLANG_PATH at it."
);
return std::process::ExitCode::FAILURE;
}
};
let mut backend = ClangBackend {
clang: &clang,
cfg_available: cfg_dump::available(),
databases: Databases::default(),
};
let mut input = std::io::stdin().lock();
let mut output = std::io::stdout().lock();
match serve(&mut backend, &mut input, &mut output) {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(error) => {
eprintln!("{NAME}: {error}");
std::process::ExitCode::FAILURE
}
}
}
struct ClangBackend<'c> {
clang: &'c clang::Clang,
cfg_available: bool,
databases: Databases,
}
impl Backend for ClangBackend<'_> {
fn diagnostics(&mut self) -> Vec<String> {
taken_refusals()
}
fn identity(&self) -> HelperIdentity {
HelperIdentity {
name: NAME.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
protocol: PROTOCOL_VERSION,
toolchains: vec![clang::get_version()],
capabilities: {
let mut capabilities = vec![
Capability::Types,
Capability::NameResolution,
Capability::CallTargets,
Capability::MacroExpansion,
Capability::TemplateInstantiation,
];
if self.cfg_available {
capabilities.push(Capability::MirCfg);
}
capabilities
},
executes: Vec::new(),
}
}
fn describe(&mut self, request: &DescribeBuild) -> Description {
let described = self
.databases
.nearest(Path::new(&request.root))
.map_or_else(BuildDescription::default, |database| BuildDescription {
features: Vec::new(),
cfgs: database.definitions(),
});
Description::Build(described)
}
fn analyze(&mut self, request: &Analyze) -> Answer {
let clang = self.clang;
let Some(database) = self.databases.nearest(Path::new(&request.unit.file)) else {
refused(&format!(
"{}: no compilation database above this file says how it is compiled",
request.unit.unit
));
return Answer::Unavailable(Unavailability::NoBuildInformation);
};
match analysis::analyze(
clang,
&request.unit,
database,
request.compile_command.as_ref(),
request.read_boundary.as_deref().map(Path::new),
&request.want,
) {
Outcome::Analyzed(mut ir) => {
ir.schema_version = COMPILER_IR_SCHEMA_VERSION.to_string();
ir.keep_only(&request.want);
Answer::Analyzed(ir)
}
Outcome::Unavailable(reason) => Answer::Unavailable(reason),
}
}
}