Files
objdiff/objdiff-core/src/config/mod.rs
T

268 lines
9.7 KiB
Rust
Raw Normal View History

2024-02-27 18:47:51 -07:00
use std::{
2024-10-09 21:44:18 -06:00
fs,
2024-02-27 18:47:51 -07:00
fs::File,
2024-10-09 21:44:18 -06:00
io::{BufReader, BufWriter, Read},
2024-02-27 18:47:51 -07:00
path::{Path, PathBuf},
};
use anyhow::{anyhow, Context, Result};
2024-10-09 21:44:18 -06:00
use bimap::BiBTreeMap;
2024-02-27 18:47:51 -07:00
use filetime::FileTime;
use globset::{Glob, GlobSet, GlobSetBuilder};
2024-10-09 21:44:18 -06:00
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
2024-02-27 18:47:51 -07:00
pub struct ProjectConfig {
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub min_version: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub custom_make: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-05-16 02:53:14 +02:00
pub custom_args: Option<Vec<String>>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub target_dir: Option<PathBuf>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub base_dir: Option<PathBuf>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_base: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_target: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub watch_patterns: Option<Vec<Glob>>,
2024-10-09 21:44:18 -06:00
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
pub units: Option<Vec<ProjectObject>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
2024-02-27 18:47:51 -07:00
}
2024-10-09 21:44:18 -06:00
impl ProjectConfig {
#[inline]
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
#[inline]
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
self.units.get_or_insert_with(Vec::new)
}
#[inline]
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
self.progress_categories.as_deref().unwrap_or_default()
}
#[inline]
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
self.progress_categories.get_or_insert_with(Vec::new)
}
}
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
2024-02-27 18:47:51 -07:00
pub struct ProjectObject {
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub name: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub path: Option<PathBuf>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub target_path: Option<PathBuf>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub base_path: Option<PathBuf>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use metadata.reverse_fn_order")]
2024-02-27 18:47:51 -07:00
pub reverse_fn_order: Option<bool>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use metadata.complete")]
2024-02-27 18:47:51 -07:00
pub complete: Option<bool>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub scratch: Option<ScratchConfig>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<ProjectObjectMetadata>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub symbol_mappings: Option<SymbolMappings>,
}
2024-10-09 21:44:18 -06:00
pub type SymbolMappings = BiBTreeMap<String, String>;
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectObjectMetadata {
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub complete: Option<bool>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reverse_fn_order: Option<bool>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_path: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub progress_categories: Option<Vec<String>>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_generated: Option<bool>,
}
2024-10-09 21:44:18 -06:00
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectProgressCategory {
#[serde(default)]
pub id: String,
#[serde(default)]
pub name: String,
2024-02-27 18:47:51 -07:00
}
impl ProjectObject {
pub fn name(&self) -> &str {
if let Some(name) = &self.name {
name
} else if let Some(path) = &self.path {
path.to_str().unwrap_or("[invalid path]")
} else {
"[unknown]"
}
}
pub fn resolve_paths(
&mut self,
project_dir: &Path,
target_obj_dir: Option<&Path>,
base_obj_dir: Option<&Path>,
) {
if let (Some(target_obj_dir), Some(path), None) =
(target_obj_dir, &self.path, &self.target_path)
{
self.target_path = Some(target_obj_dir.join(path));
} else if let Some(path) = &self.target_path {
self.target_path = Some(project_dir.join(path));
}
if let (Some(base_obj_dir), Some(path), None) = (base_obj_dir, &self.path, &self.base_path)
{
self.base_path = Some(base_obj_dir.join(path));
} else if let Some(path) = &self.base_path {
self.base_path = Some(project_dir.join(path));
}
}
pub fn complete(&self) -> Option<bool> {
2024-10-09 21:44:18 -06:00
#[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
}
pub fn reverse_fn_order(&self) -> Option<bool> {
2024-10-09 21:44:18 -06:00
#[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
}
2024-09-03 18:59:07 -06:00
pub fn hidden(&self) -> bool {
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
}
2024-09-28 10:55:25 -06:00
pub fn source_path(&self) -> Option<&String> {
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
}
2024-02-27 18:47:51 -07:00
}
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ScratchConfig {
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub platform: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub compiler: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub c_flags: Option<String>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
2024-02-27 18:47:51 -07:00
pub ctx_path: Option<PathBuf>,
2024-10-09 21:44:18 -06:00
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_ctx: Option<bool>,
2024-02-27 18:47:51 -07:00
}
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
2024-02-27 18:47:51 -07:00
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
];
#[derive(Clone, Eq, PartialEq)]
pub struct ProjectConfigInfo {
pub path: PathBuf,
2024-10-09 21:44:18 -06:00
pub timestamp: Option<FileTime>,
2024-02-27 18:47:51 -07:00
}
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
for filename in CONFIG_FILENAMES.iter() {
let config_path = dir.join(filename);
2024-09-28 10:54:54 -06:00
let Ok(file) = File::open(&config_path) else {
2024-02-27 18:47:51 -07:00
continue;
};
let metadata = file.metadata();
if let Ok(metadata) = metadata {
if !metadata.is_file() {
continue;
}
let ts = FileTime::from_last_modification_time(&metadata);
2024-09-28 10:54:54 -06:00
let mut reader = BufReader::new(file);
let mut result = match filename.contains("json") {
2024-09-28 10:54:54 -06:00
true => read_json_config(&mut reader),
false => read_yml_config(&mut reader),
2024-02-27 18:47:51 -07:00
};
if let Ok(config) = &result {
// Validate min_version if present
if let Err(e) = validate_min_version(config) {
result = Err(e);
}
}
2024-10-09 21:44:18 -06:00
return Some((result, ProjectConfigInfo { path: config_path, timestamp: Some(ts) }));
2024-02-27 18:47:51 -07:00
}
}
None
}
2024-10-09 21:44:18 -06:00
pub fn save_project_config(
config: &ProjectConfig,
info: &ProjectConfigInfo,
) -> Result<ProjectConfigInfo> {
if let Some(last_ts) = info.timestamp {
// Check if the file has changed since we last read it
if let Ok(metadata) = fs::metadata(&info.path) {
let ts = FileTime::from_last_modification_time(&metadata);
if ts != last_ts {
return Err(anyhow!("Config file has changed since last read"));
}
}
}
let mut writer =
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
match ext {
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
"yml" | "yaml" => {
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
}
_ => Err(anyhow!("Unknown config file extension: {ext}")),
}?;
let file = writer.into_inner().context("Failed to flush file")?;
let metadata = file.metadata().context("Failed to get file metadata")?;
let ts = FileTime::from_last_modification_time(&metadata);
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
}
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
let Some(min_version) = &config.min_version else { return Ok(()) };
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
.context("Failed to parse package version")?;
2024-05-21 09:50:27 -06:00
let min_version = semver::Version::parse(min_version).context("Failed to parse min_version")?;
if version >= min_version {
Ok(())
} else {
Err(anyhow!("Project requires objdiff version {min_version} or higher"))
}
}
2024-02-27 18:47:51 -07:00
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
Ok(serde_yaml::from_reader(reader)?)
}
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
Ok(serde_json::from_reader(reader)?)
}
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
let mut builder = GlobSetBuilder::new();
for glob in vec {
builder.add(glob.clone());
}
builder.build()
}