#![allow(clippy::redundant_pub_crate)]
mod analysis;
mod cfg_dump;
mod database;
mod types;
use std::path::Path;
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::Database;
const NAME: &str = "codehelion-backend-clang";
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(),
};
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,
}
impl Backend for ClangBackend<'_> {
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 = Database::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 Some(database) = Database::nearest(Path::new(&request.unit.file)) else {
return Answer::Unavailable(Unavailability::NoBuildInformation);
};
match analysis::analyze(
self.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();
Answer::Analyzed(ir)
}
Outcome::Unavailable(reason) => Answer::Unavailable(reason),
}
}
}