mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Make objdiff-core no_std + huge WASM rework
This commit is contained in:
Generated
+204
-116
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ supports-color = "3.0"
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
typed-path = "0.10"
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
+127
-103
@@ -1,8 +1,6 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::stdout,
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -27,7 +25,11 @@ use objdiff_core::{
|
||||
watcher::{create_watcher, Watcher},
|
||||
BuildConfig,
|
||||
},
|
||||
config::{build_globset, ProjectConfig, ProjectObject},
|
||||
config::{
|
||||
build_globset,
|
||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||
ProjectConfig, ProjectObject, ProjectObjectMetadata,
|
||||
},
|
||||
diff,
|
||||
diff::{
|
||||
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
||||
@@ -40,6 +42,7 @@ use objdiff_core::{
|
||||
obj::ObjInfo,
|
||||
};
|
||||
use ratatui::prelude::*;
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
util::{
|
||||
@@ -53,21 +56,21 @@ use crate::{
|
||||
/// Diff two object files. (Interactive or one-shot mode)
|
||||
#[argp(subcommand, name = "diff")]
|
||||
pub struct Args {
|
||||
#[argp(option, short = '1')]
|
||||
#[argp(option, short = '1', from_str_fn(platform_path))]
|
||||
/// Target object file
|
||||
target: Option<PathBuf>,
|
||||
#[argp(option, short = '2')]
|
||||
target: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = '2', from_str_fn(platform_path))]
|
||||
/// Base object file
|
||||
base: Option<PathBuf>,
|
||||
#[argp(option, short = 'p')]
|
||||
base: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||
/// Project directory
|
||||
project: Option<PathBuf>,
|
||||
project: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'u')]
|
||||
/// Unit name within project
|
||||
unit: Option<String>,
|
||||
#[argp(option, short = 'o')]
|
||||
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||
/// Output file (one-shot mode) ("-" for stdout)
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option)]
|
||||
/// Output format (json, json-pretty, proto) (default: json)
|
||||
format: Option<String>,
|
||||
@@ -89,86 +92,61 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
let (target_path, base_path, project_config) = match (
|
||||
&args.target,
|
||||
&args.base,
|
||||
&args.project,
|
||||
&args.unit,
|
||||
) {
|
||||
(Some(_), Some(_), None, None)
|
||||
| (Some(_), None, None, None)
|
||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||
(None, None, p, u) => {
|
||||
let project = match p {
|
||||
Some(project) => project.clone(),
|
||||
_ => std::env::current_dir().context("Failed to get the current directory")?,
|
||||
};
|
||||
let Some((project_config, project_config_info)) =
|
||||
objdiff_core::config::try_project_config(&project)
|
||||
else {
|
||||
bail!("Project config not found in {}", &project.display())
|
||||
};
|
||||
let mut project_config = project_config.with_context(|| {
|
||||
format!("Reading project config {}", project_config_info.path.display())
|
||||
})?;
|
||||
let object = {
|
||||
let resolve_paths = |o: &mut ProjectObject| {
|
||||
o.resolve_paths(
|
||||
&project,
|
||||
project_config.target_dir.as_deref(),
|
||||
project_config.base_dir.as_deref(),
|
||||
let (target_path, base_path, project_config) =
|
||||
match (&args.target, &args.base, &args.project, &args.unit) {
|
||||
(Some(_), Some(_), None, None)
|
||||
| (Some(_), None, None, None)
|
||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||
(None, None, p, u) => {
|
||||
let project = match p {
|
||||
Some(project) => project.clone(),
|
||||
_ => check_path_buf(
|
||||
std::env::current_dir().context("Failed to get the current directory")?,
|
||||
)
|
||||
.context("Current directory is not valid UTF-8")?,
|
||||
};
|
||||
if let Some(u) = u {
|
||||
let unit_path =
|
||||
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
||||
|
||||
let Some(object) = project_config
|
||||
.units
|
||||
.as_deref_mut()
|
||||
.unwrap_or_default()
|
||||
.iter_mut()
|
||||
.find_map(|obj| {
|
||||
if obj.name.as_deref() == Some(u) {
|
||||
resolve_paths(obj);
|
||||
return Some(obj);
|
||||
}
|
||||
|
||||
let up = unit_path.as_deref()?;
|
||||
|
||||
resolve_paths(obj);
|
||||
|
||||
if [&obj.base_path, &obj.target_path]
|
||||
.into_iter()
|
||||
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
|
||||
.any(|p| p == up)
|
||||
{
|
||||
return Some(obj);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
else {
|
||||
bail!("Unit not found: {}", u)
|
||||
};
|
||||
|
||||
object
|
||||
let Some((project_config, project_config_info)) =
|
||||
objdiff_core::config::try_project_config(project.as_ref())
|
||||
else {
|
||||
bail!("Project config not found in {}", &project)
|
||||
};
|
||||
let project_config = project_config.with_context(|| {
|
||||
format!("Reading project config {}", project_config_info.path.display())
|
||||
})?;
|
||||
let target_obj_dir = project_config
|
||||
.target_dir
|
||||
.as_ref()
|
||||
.map(|p| project.join(p.with_platform_encoding()));
|
||||
let base_obj_dir = project_config
|
||||
.base_dir
|
||||
.as_ref()
|
||||
.map(|p| project.join(p.with_platform_encoding()));
|
||||
let objects = project_config
|
||||
.units
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|o| {
|
||||
ObjectConfig::new(
|
||||
o,
|
||||
&project,
|
||||
target_obj_dir.as_deref(),
|
||||
base_obj_dir.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let object = if let Some(u) = u {
|
||||
objects
|
||||
.iter()
|
||||
.find(|obj| obj.name == *u)
|
||||
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
||||
} else if let Some(symbol_name) = &args.symbol {
|
||||
let mut idx = None;
|
||||
let mut count = 0usize;
|
||||
for (i, obj) in project_config
|
||||
.units
|
||||
.as_deref_mut()
|
||||
.unwrap_or_default()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
{
|
||||
resolve_paths(obj);
|
||||
|
||||
for (i, obj) in objects.iter().enumerate() {
|
||||
if obj
|
||||
.target_path
|
||||
.as_deref()
|
||||
.map(|o| obj::read::has_function(o, symbol_name))
|
||||
.map(|o| obj::read::has_function(o.as_ref(), symbol_name))
|
||||
.transpose()?
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@@ -181,7 +159,7 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
match (count, idx) {
|
||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||
(1, Some(i)) => &mut project_config.units_mut()[i],
|
||||
(1, Some(i)) => &objects[i],
|
||||
(2.., Some(_)) => bail!(
|
||||
"Multiple instances of {} were found, try specifying a unit",
|
||||
symbol_name
|
||||
@@ -190,14 +168,13 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
} else {
|
||||
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
||||
}
|
||||
};
|
||||
let target_path = object.target_path.clone();
|
||||
let base_path = object.base_path.clone();
|
||||
(target_path, base_path, Some(project_config))
|
||||
}
|
||||
_ => bail!("Either target and base or project and unit must be specified"),
|
||||
};
|
||||
};
|
||||
let target_path = object.target_path.clone();
|
||||
let base_path = object.base_path.clone();
|
||||
(target_path, base_path, Some(project_config))
|
||||
}
|
||||
_ => bail!("Either target and base or project and unit must be specified"),
|
||||
};
|
||||
|
||||
if let Some(output) = &args.output {
|
||||
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref())
|
||||
@@ -245,20 +222,20 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
|
||||
|
||||
fn run_oneshot(
|
||||
args: &Args,
|
||||
output: &Path,
|
||||
target_path: Option<&Path>,
|
||||
base_path: Option<&Path>,
|
||||
output: &Utf8PlatformPath,
|
||||
target_path: Option<&Utf8PlatformPath>,
|
||||
base_path: Option<&Utf8PlatformPath>,
|
||||
) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||
let target = target_path
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = base_path
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let result =
|
||||
@@ -272,10 +249,10 @@ fn run_oneshot(
|
||||
pub struct AppState {
|
||||
pub jobs: JobQueue,
|
||||
pub waker: Arc<TermWaker>,
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||
pub project_config: Option<ProjectConfig>,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub left_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub right_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
@@ -315,6 +292,53 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration for a single object file.
|
||||
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfig {
|
||||
pub name: String,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub metadata: ProjectObjectMetadata,
|
||||
pub complete: Option<bool>,
|
||||
}
|
||||
|
||||
impl ObjectConfig {
|
||||
pub fn new(
|
||||
object: &ProjectObject,
|
||||
project_dir: &Utf8PlatformPath,
|
||||
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||
) -> Self {
|
||||
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||
(target_obj_dir, &object.path, &object.target_path)
|
||||
{
|
||||
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||
} else if let Some(path) = &object.target_path {
|
||||
Some(project_dir.join(path.with_platform_encoding()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||
(base_obj_dir, &object.path, &object.base_path)
|
||||
{
|
||||
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||
} else if let Some(path) = &object.base_path {
|
||||
Some(project_dir.join(path.with_platform_encoding()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Self {
|
||||
name: object.name().to_string(),
|
||||
target_path,
|
||||
base_path,
|
||||
metadata: object.metadata.clone().unwrap_or_default(),
|
||||
complete: object.complete(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn reload(&mut self) -> Result<()> {
|
||||
let config = create_objdiff_config(self);
|
||||
@@ -355,8 +379,8 @@ impl Wake for TermWaker {
|
||||
|
||||
fn run_interactive(
|
||||
args: Args,
|
||||
target_path: Option<PathBuf>,
|
||||
base_path: Option<PathBuf>,
|
||||
target_path: Option<Utf8PlatformPathBuf>,
|
||||
base_path: Option<Utf8PlatformPathBuf>,
|
||||
project_config: Option<ProjectConfig>,
|
||||
) -> Result<()> {
|
||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||
@@ -384,7 +408,7 @@ fn run_interactive(
|
||||
let watch_patterns = project_config.build_watch_patterns()?;
|
||||
state.watcher = Some(create_watcher(
|
||||
state.modified.clone(),
|
||||
project_dir,
|
||||
project_dir.as_ref(),
|
||||
build_globset(&watch_patterns)?,
|
||||
Waker::from(state.waker.clone()),
|
||||
)?);
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
time::Instant,
|
||||
};
|
||||
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use argp::FromArgs;
|
||||
@@ -14,15 +8,19 @@ use objdiff_core::{
|
||||
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||
REPORT_VERSION,
|
||||
},
|
||||
config::ProjectObject,
|
||||
config::path::platform_path,
|
||||
diff, obj,
|
||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
||||
};
|
||||
use prost::Message;
|
||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use tracing::{info, warn};
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::util::output::{write_output, OutputFormat};
|
||||
use crate::{
|
||||
cmd::diff::ObjectConfig,
|
||||
util::output::{write_output, OutputFormat},
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Generate a progress report for a project.
|
||||
@@ -43,12 +41,12 @@ pub enum SubCommand {
|
||||
/// Generate a progress report for a project.
|
||||
#[argp(subcommand, name = "generate")]
|
||||
pub struct GenerateArgs {
|
||||
#[argp(option, short = 'p')]
|
||||
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||
/// Project directory
|
||||
project: Option<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
project: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||
/// Output file
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(switch, short = 'd')]
|
||||
/// Deduplicate global and weak symbols (runs single-threaded)
|
||||
deduplicate: bool,
|
||||
@@ -61,15 +59,15 @@ pub struct GenerateArgs {
|
||||
/// List any changes from a previous report.
|
||||
#[argp(subcommand, name = "changes")]
|
||||
pub struct ChangesArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(platform_path))]
|
||||
/// Previous report file
|
||||
previous: PathBuf,
|
||||
#[argp(positional)]
|
||||
previous: Utf8PlatformPathBuf,
|
||||
#[argp(positional, from_str_fn(platform_path))]
|
||||
/// Current report file
|
||||
current: PathBuf,
|
||||
#[argp(option, short = 'o')]
|
||||
current: Utf8PlatformPathBuf,
|
||||
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||
/// Output file
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'f')]
|
||||
/// Output format (json, json-pretty, proto) (default: json)
|
||||
format: Option<String>,
|
||||
@@ -84,10 +82,10 @@ pub fn run(args: Args) -> Result<()> {
|
||||
|
||||
fn generate(args: GenerateArgs) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
|
||||
info!("Loading project {}", project_dir.display());
|
||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||
info!("Loading project {}", project_dir);
|
||||
|
||||
let mut project = match objdiff_core::config::try_project_config(project_dir) {
|
||||
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
|
||||
Some((Ok(config), _)) => config,
|
||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||
None => bail!("No project configuration found"),
|
||||
@@ -98,37 +96,33 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||
);
|
||||
|
||||
let target_obj_dir =
|
||||
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
let base_obj_dir =
|
||||
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
let objects = project
|
||||
.units
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|o| {
|
||||
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let start = Instant::now();
|
||||
let mut units = vec![];
|
||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||
if args.deduplicate {
|
||||
// If deduplicating, we need to run single-threaded
|
||||
for object in project.units.as_deref_mut().unwrap_or_default() {
|
||||
if let Some(unit) = report_object(
|
||||
object,
|
||||
project_dir,
|
||||
project.target_dir.as_deref(),
|
||||
project.base_dir.as_deref(),
|
||||
Some(&mut existing_functions),
|
||||
)? {
|
||||
for object in &objects {
|
||||
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
|
||||
units.push(unit);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let vec = project
|
||||
.units
|
||||
.as_deref_mut()
|
||||
.unwrap_or_default()
|
||||
.par_iter_mut()
|
||||
.map(|object| {
|
||||
report_object(
|
||||
object,
|
||||
project_dir,
|
||||
project.target_dir.as_deref(),
|
||||
project.base_dir.as_deref(),
|
||||
None,
|
||||
)
|
||||
})
|
||||
let vec = objects
|
||||
.par_iter()
|
||||
.map(|object| report_object(object, None))
|
||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||
units = vec.into_iter().flatten().collect();
|
||||
}
|
||||
@@ -151,20 +145,16 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
||||
}
|
||||
|
||||
fn report_object(
|
||||
object: &mut ProjectObject,
|
||||
project_dir: &Path,
|
||||
target_dir: Option<&Path>,
|
||||
base_dir: Option<&Path>,
|
||||
object: &ObjectConfig,
|
||||
mut existing_functions: Option<&mut HashSet<String>>,
|
||||
) -> Result<Option<ReportUnit>> {
|
||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
||||
match (&object.target_path, &object.base_path) {
|
||||
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
||||
warn!("Skipping object without target: {}", object.name());
|
||||
(None, Some(_)) if !object.complete.unwrap_or(false) => {
|
||||
warn!("Skipping object without target: {}", object.name);
|
||||
return Ok(None);
|
||||
}
|
||||
(None, None) => {
|
||||
warn!("Skipping object without target or base: {}", object.name());
|
||||
warn!("Skipping object without target or base: {}", object.name);
|
||||
return Ok(None);
|
||||
}
|
||||
_ => {}
|
||||
@@ -178,35 +168,31 @@ fn report_object(
|
||||
.target_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = object
|
||||
.base_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
|
||||
let metadata = ReportUnitMetadata {
|
||||
complete: object.complete(),
|
||||
complete: object.metadata.complete,
|
||||
module_name: target
|
||||
.as_ref()
|
||||
.and_then(|o| o.split_meta.as_ref())
|
||||
.and_then(|m| m.module_name.clone()),
|
||||
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
||||
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
|
||||
progress_categories: object
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|m| m.progress_categories.clone())
|
||||
.unwrap_or_default(),
|
||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
||||
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
|
||||
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
|
||||
auto_generated: object.metadata.auto_generated,
|
||||
};
|
||||
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||
let mut sections = vec![];
|
||||
@@ -218,7 +204,7 @@ fn report_object(
|
||||
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
||||
// Support cases where we don't have a target object,
|
||||
// assume complete means 100% match
|
||||
if object.complete().unwrap_or(false) {
|
||||
if object.complete.unwrap_or(false) {
|
||||
100.0
|
||||
} else {
|
||||
0.0
|
||||
@@ -260,7 +246,7 @@ fn report_object(
|
||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||
// Support cases where we don't have a target object,
|
||||
// assume complete means 100% match
|
||||
if object.complete().unwrap_or(false) {
|
||||
if object.complete.unwrap_or(false) {
|
||||
100.0
|
||||
} else {
|
||||
0.0
|
||||
@@ -294,7 +280,7 @@ fn report_object(
|
||||
measures.calc_fuzzy_match_percent();
|
||||
measures.calc_matched_percent();
|
||||
Ok(Some(ReportUnit {
|
||||
name: object.name().to_string(),
|
||||
name: object.name.clone(),
|
||||
measures: Some(measures),
|
||||
sections,
|
||||
functions,
|
||||
@@ -304,7 +290,7 @@ fn report_object(
|
||||
|
||||
fn changes(args: ChangesArgs) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
|
||||
let (previous, current) = if args.previous == "-" && args.current == "-" {
|
||||
// Special case for comparing two reports from stdin
|
||||
let mut data = vec![];
|
||||
std::io::stdin().read_to_end(&mut data)?;
|
||||
@@ -419,15 +405,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_report(path: &Path) -> Result<Report> {
|
||||
if path == Path::new("-") {
|
||||
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||
if path == Utf8PlatformPath::new("-") {
|
||||
let mut data = vec![];
|
||||
std::io::stdin().read_to_end(&mut data)?;
|
||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
||||
}
|
||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path.display()))?;
|
||||
let mmap = unsafe { memmap2::Mmap::map(&file) }
|
||||
.with_context(|| format!("Failed to map {}", path.display()))?;
|
||||
Report::parse(mmap.as_ref())
|
||||
.with_context(|| format!("Failed to load report {}", path.display()))
|
||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
|
||||
let mmap =
|
||||
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
|
||||
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
|
||||
}
|
||||
|
||||
@@ -34,9 +34,12 @@ impl OutputFormat {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
|
||||
where T: serde::Serialize + prost::Message {
|
||||
match output {
|
||||
pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
|
||||
where
|
||||
T: serde::Serialize + prost::Message,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match output.as_ref().map(|p| p.as_ref()) {
|
||||
Some(output) if output != Path::new("-") => {
|
||||
info!("Writing to {}", output.display());
|
||||
let file = File::options()
|
||||
|
||||
+59
-49
@@ -16,12 +16,14 @@ documentation = "https://docs.rs/objdiff-core"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
all = [
|
||||
# Features
|
||||
"bindings",
|
||||
"build",
|
||||
"config",
|
||||
"dwarf",
|
||||
"serde",
|
||||
# Architectures
|
||||
"mips",
|
||||
"ppc",
|
||||
@@ -31,30 +33,21 @@ all = [
|
||||
]
|
||||
# Implicit, used to check if any arch is enabled
|
||||
any-arch = [
|
||||
"config",
|
||||
"dep:bimap",
|
||||
"dep:byteorder",
|
||||
"dep:flagset",
|
||||
"dep:heck",
|
||||
"dep:log",
|
||||
"dep:memmap2",
|
||||
"dep:num-traits",
|
||||
"dep:prettyplease",
|
||||
"dep:proc-macro2",
|
||||
"dep:quote",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:similar",
|
||||
"dep:strum",
|
||||
"dep:syn",
|
||||
]
|
||||
bindings = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
"dep:prost",
|
||||
"dep:prost-build",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
]
|
||||
build = [
|
||||
"dep:notify",
|
||||
@@ -68,15 +61,31 @@ build = [
|
||||
"dep:winapi",
|
||||
]
|
||||
config = [
|
||||
"dep:bimap",
|
||||
"dep:filetime",
|
||||
"dep:globset",
|
||||
"dep:semver",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:serde_yaml",
|
||||
"dep:typed-path",
|
||||
]
|
||||
dwarf = ["dep:gimli"]
|
||||
serde = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
]
|
||||
std = [
|
||||
"anyhow/std",
|
||||
"byteorder?/std",
|
||||
"flagset?/std",
|
||||
"log?/std",
|
||||
"num-traits?/std",
|
||||
"object/std",
|
||||
"prost?/std",
|
||||
"serde?/std",
|
||||
"strum?/std",
|
||||
"typed-path?/std",
|
||||
"dep:filetime",
|
||||
"dep:memmap2",
|
||||
]
|
||||
mips = [
|
||||
"any-arch",
|
||||
"dep:rabbitizer",
|
||||
@@ -108,65 +117,66 @@ arm64 = [
|
||||
wasm = [
|
||||
"any-arch",
|
||||
"bindings",
|
||||
"dep:console_error_panic_hook",
|
||||
"dep:console_log",
|
||||
"dep:log",
|
||||
"dep:tsify-next",
|
||||
"dep:wasm-bindgen",
|
||||
"dep:talc",
|
||||
"dep:spin",
|
||||
"dep:wit-bindgen",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["all"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bimap = { version = "0.6", features = ["serde"], optional = true }
|
||||
byteorder = { version = "1.5", optional = true }
|
||||
anyhow = { version = "1.0", default-features = false }
|
||||
byteorder = { version = "1.5", default-features = false, optional = true }
|
||||
filetime = { version = "0.2", optional = true }
|
||||
flagset = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
flagset = { version = "0.4", default-features = false, optional = true }
|
||||
log = { version = "0.4", default-features = false, optional = true }
|
||||
memmap2 = { version = "0.9", optional = true }
|
||||
num-traits = { version = "0.2", optional = true }
|
||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
||||
pbjson = { version = "0.7", optional = true }
|
||||
prost = { version = "0.13", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
similar = { version = "2.6", default-features = false, optional = true }
|
||||
strum = { version = "0.26", features = ["derive"], optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
||||
console_log = { version = "1.0", optional = true }
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
|
||||
pbjson = { version = "0.7", default-features = false, optional = true }
|
||||
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
|
||||
strum = { version = "0.26", default-features = false, features = ["derive"], optional = true }
|
||||
typed-path = { version = "0.10", default-features = false, optional = true }
|
||||
|
||||
# config
|
||||
globset = { version = "0.4", features = ["serde1"], optional = true }
|
||||
semver = { version = "1.0", optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde_yaml = { version = "0.9", optional = true }
|
||||
globset = { version = "0.4", default-features = false, optional = true }
|
||||
semver = { version = "1.0", default-features = false, optional = true }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||
|
||||
# dwarf
|
||||
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true }
|
||||
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
cwextab = { version = "1.0", optional = true }
|
||||
cwextab = { version = "1.0", optional = true, git = "https://github.com/encounter/cwextab.git" }
|
||||
ppc750cl = { version = "0.3", optional = true }
|
||||
|
||||
# mips
|
||||
rabbitizer = { version = "1.12", optional = true }
|
||||
|
||||
# x86
|
||||
cpp_demangle = { version = "0.4", optional = true }
|
||||
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
|
||||
msvc-demangler = { version = "0.10", optional = true }
|
||||
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||
msvc-demangler = { version = "0.11", optional = true }
|
||||
|
||||
# arm
|
||||
unarm = { version = "1.6", optional = true }
|
||||
arm-attr = { version = "0.1", optional = true }
|
||||
unarm = { version = "1.7", optional = true }
|
||||
arm-attr = { version = "0.2", optional = true }
|
||||
|
||||
# arm64
|
||||
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||
|
||||
# wasm
|
||||
#console_error_panic_hook = { version = "0.1", optional = true }
|
||||
#console_log = { version = "1.0", optional = true }
|
||||
talc = { version = "4.4", optional = true }
|
||||
spin = { version = "0.9", optional = true }
|
||||
wit-bindgen = { version = "0.38", default-features = false, features = ["macros"], optional = true }
|
||||
|
||||
# build
|
||||
notify = { version = "8.0.0", optional = true }
|
||||
@@ -196,6 +206,6 @@ prettyplease = { version = "0.2", optional = true }
|
||||
proc-macro2 = { version = "1.0", optional = true }
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
quote = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
syn = { version = "2.0", optional = true }
|
||||
|
||||
+10
-7
@@ -54,11 +54,14 @@ fn compile_protos() {
|
||||
}
|
||||
}
|
||||
|
||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||
pbjson_build::Builder::new()
|
||||
.register_descriptors(&descriptor_set)
|
||||
.expect("Failed to register descriptors")
|
||||
.preserve_proto_field_names()
|
||||
.build(&[".objdiff"])
|
||||
.expect("Failed to build pbjson");
|
||||
#[cfg(feature = "serde")]
|
||||
{
|
||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||
pbjson_build::Builder::new()
|
||||
.register_descriptors(&descriptor_set)
|
||||
.expect("Failed to register descriptors")
|
||||
.preserve_proto_field_names()
|
||||
.build(&[".objdiff"])
|
||||
.expect("Failed to build pbjson");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
let value = &item.value;
|
||||
variants.extend(quote! {
|
||||
#[serde(rename = #value, alias = #variant_name)]
|
||||
#[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
|
||||
#variant_ident,
|
||||
});
|
||||
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||
@@ -134,8 +134,8 @@ pub fn generate_diff_config() {
|
||||
});
|
||||
}
|
||||
enums.extend(quote! {
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum #enum_ident {
|
||||
#variants
|
||||
}
|
||||
@@ -168,7 +168,7 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for #enum_ident {
|
||||
impl core::str::FromStr for #enum_ident {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#variant_from_str
|
||||
@@ -244,7 +244,7 @@ pub fn generate_diff_config() {
|
||||
let default = b.default;
|
||||
if default {
|
||||
property_fields.extend(quote! {
|
||||
#[serde(default = "default_true")]
|
||||
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
|
||||
});
|
||||
}
|
||||
property_fields.extend(quote! {
|
||||
@@ -412,7 +412,7 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for ConfigPropertyId {
|
||||
impl core::str::FromStr for ConfigPropertyId {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#config_property_id_from_str
|
||||
@@ -433,6 +433,7 @@ pub fn generate_diff_config() {
|
||||
Choice(&'static str),
|
||||
}
|
||||
impl ConfigPropertyValue {
|
||||
#[cfg(feature = "serde")]
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||
@@ -440,17 +441,25 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl core::fmt::Display for ConfigPropertyValue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
|
||||
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConfigPropertyKind {
|
||||
Boolean,
|
||||
Choice(&'static [ConfigEnumVariantInfo]),
|
||||
}
|
||||
#enums
|
||||
#[cfg(feature = "serde")]
|
||||
#[inline(always)]
|
||||
fn default_true() -> bool { true }
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||
pub struct DiffObjConfig {
|
||||
#property_fields
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
use std::{
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::BTreeMap,
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
|
||||
use object::{
|
||||
elf::{self, SHT_ARM_ATTRIBUTES},
|
||||
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
|
||||
SectionKind, Symbol, SymbolKind,
|
||||
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionKind,
|
||||
Symbol, SymbolKind,
|
||||
};
|
||||
use unarm::{
|
||||
args::{Argument, OffsetImm, OffsetReg, Register},
|
||||
@@ -24,7 +28,7 @@ use crate::{
|
||||
|
||||
pub struct ObjArchArm {
|
||||
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
||||
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
|
||||
disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
|
||||
detected_version: Option<ArmVersion>,
|
||||
endianness: object::Endianness,
|
||||
}
|
||||
@@ -78,7 +82,7 @@ impl ObjArchArm {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
|
||||
fn elf_get_mapping_symbols(file: &File) -> BTreeMap<usize, Vec<DisasmMode>> {
|
||||
file.sections()
|
||||
.filter(|s| s.kind() == SectionKind::Text)
|
||||
.map(|s| {
|
||||
@@ -89,7 +93,7 @@ impl ObjArchArm {
|
||||
.filter_map(|s| DisasmMode::from_symbol(&s))
|
||||
.collect();
|
||||
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
||||
(s.index(), mapping_symbols)
|
||||
(s.index().0, mapping_symbols)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -121,7 +125,7 @@ impl ObjArch for ObjArchArm {
|
||||
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
|
||||
let mapping_symbols = self
|
||||
.disasm_modes
|
||||
.get(&SectionIndex(section_index))
|
||||
.get(§ion_index)
|
||||
.map(|x| x.as_slice())
|
||||
.unwrap_or(&fallback_mappings);
|
||||
let first_mapping_idx = mapping_symbols
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap};
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
collections::BTreeMap,
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
use core::cmp::Ordering;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use object::{elf, File, Relocation, RelocationFlags};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
|
||||
use alloc::{borrow::Cow, collections::BTreeMap, format, vec::Vec};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use object::{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
|
||||
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, format, string::String, vec::Vec};
|
||||
use core::ffi::CStr;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use byteorder::ByteOrder;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use std::{
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
@@ -479,7 +483,7 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
||||
|
||||
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
|
||||
// that register to hold some other value, unrelated to pool relocation addresses.
|
||||
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut HashMap<u8, ObjReloc>) {
|
||||
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut BTreeMap<u8, ObjReloc>) {
|
||||
let mut def_args = Arguments::default();
|
||||
ins.parse_defs(&mut def_args);
|
||||
for arg in def_args {
|
||||
@@ -576,11 +580,11 @@ fn generate_fake_pool_reloc_for_addr_mapping(
|
||||
func_address: u64,
|
||||
code: &[u8],
|
||||
relocations: &[ObjReloc],
|
||||
) -> HashMap<u32, ObjReloc> {
|
||||
let mut visited_ins_addrs = HashSet::new();
|
||||
let mut pool_reloc_for_addr = HashMap::new();
|
||||
) -> BTreeMap<u32, ObjReloc> {
|
||||
let mut visited_ins_addrs = BTreeSet::new();
|
||||
let mut pool_reloc_for_addr = BTreeMap::new();
|
||||
let mut ins_iters_with_gpr_state =
|
||||
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
|
||||
vec![(InsIter::new(code, func_address as u32), BTreeMap::new())];
|
||||
let mut gpr_state_at_bctr = BTreeMap::new();
|
||||
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
|
||||
for (cur_addr, ins) in ins_iter {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
use alloc::{
|
||||
borrow::Cow,
|
||||
boxed::Box,
|
||||
collections::BTreeMap,
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use iced_x86::{
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||
|
||||
use alloc::string::ToString;
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
||||
@@ -13,6 +16,7 @@ use crate::{
|
||||
|
||||
// Protobuf diff types
|
||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
||||
#[cfg(feature = "serde")]
|
||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
||||
|
||||
impl DiffResult {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#[cfg(feature = "any-arch")]
|
||||
pub mod diff;
|
||||
pub mod report;
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod wasm;
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||
use std::ops::AddAssign;
|
||||
|
||||
use alloc::{
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
use core::ops::AddAssign;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use prost::Message;
|
||||
use serde_json::error::Category;
|
||||
|
||||
// Protobuf report types
|
||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||
#[cfg(feature = "serde")]
|
||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||
|
||||
pub const REPORT_VERSION: u32 = 2;
|
||||
@@ -15,23 +21,30 @@ impl Report {
|
||||
/// Attempts to parse the report as binary protobuf or JSON.
|
||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||
if data.is_empty() {
|
||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
||||
bail!("Empty data");
|
||||
}
|
||||
let report = if data[0] == b'{' {
|
||||
// Load as JSON
|
||||
Self::from_json(data)?
|
||||
#[cfg(feature = "serde")]
|
||||
{
|
||||
Self::from_json(data)?
|
||||
}
|
||||
#[cfg(not(feature = "serde"))]
|
||||
bail!("JSON report parsing requires the `serde` feature")
|
||||
} else {
|
||||
// Load as binary protobuf
|
||||
Self::decode(data)?
|
||||
Self::decode(data).map_err(|e| anyhow::Error::msg(e.to_string()))?
|
||||
};
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||
match serde_json::from_slice::<Self>(bytes) {
|
||||
Ok(report) => Ok(report),
|
||||
Err(e) => {
|
||||
use serde_json::error::Category;
|
||||
match e.classify() {
|
||||
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
||||
Category::Data => {
|
||||
@@ -304,7 +317,8 @@ impl FromIterator<Measures> for Measures {
|
||||
}
|
||||
|
||||
// Older JSON report types
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct LegacyReport {
|
||||
fuzzy_match_percent: f32,
|
||||
total_code: u64,
|
||||
@@ -341,7 +355,8 @@ impl From<LegacyReport> for Report {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct LegacyReportUnit {
|
||||
name: String,
|
||||
fuzzy_match_percent: f32,
|
||||
@@ -351,11 +366,11 @@ struct LegacyReportUnit {
|
||||
matched_data: u64,
|
||||
total_functions: u32,
|
||||
matched_functions: u32,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
complete: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
module_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
module_id: Option<u32>,
|
||||
sections: Vec<LegacyReportItem>,
|
||||
functions: Vec<LegacyReportItem>,
|
||||
@@ -389,16 +404,20 @@ impl From<LegacyReportUnit> for ReportUnit {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct LegacyReportItem {
|
||||
name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
demangled_name: Option<String>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
serialize_with = "serialize_hex",
|
||||
deserialize_with = "deserialize_hex"
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
serialize_with = "serialize_hex",
|
||||
deserialize_with = "deserialize_hex"
|
||||
)
|
||||
)]
|
||||
address: Option<u64>,
|
||||
size: u64,
|
||||
@@ -419,6 +438,7 @@ impl From<LegacyReportItem> for ReportItem {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
||||
where S: serde::Serializer {
|
||||
if let Some(x) = x {
|
||||
@@ -428,6 +448,7 @@ where S: serde::Serializer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
||||
where D: serde::Deserializer<'de> {
|
||||
use serde::Deserialize;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
use prost::Message;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{bindings::diff::DiffResult, diff, obj};
|
||||
|
||||
fn parse_object(
|
||||
data: Option<Box<[u8]>>,
|
||||
config: &diff::DiffObjConfig,
|
||||
) -> Result<Option<obj::ObjInfo>, JsError> {
|
||||
data.as_ref().map(|data| obj::read::parse(data, config)).transpose().to_js()
|
||||
}
|
||||
|
||||
fn parse_and_run_diff(
|
||||
left: Option<Box<[u8]>>,
|
||||
right: Option<Box<[u8]>>,
|
||||
diff_config: diff::DiffObjConfig,
|
||||
mapping_config: diff::MappingConfig,
|
||||
) -> Result<DiffResult, JsError> {
|
||||
let target = parse_object(left, &diff_config)?;
|
||||
let base = parse_object(right, &diff_config)?;
|
||||
run_diff(target.as_ref(), base.as_ref(), diff_config, mapping_config)
|
||||
}
|
||||
|
||||
fn run_diff(
|
||||
left: Option<&obj::ObjInfo>,
|
||||
right: Option<&obj::ObjInfo>,
|
||||
diff_config: diff::DiffObjConfig,
|
||||
mapping_config: diff::MappingConfig,
|
||||
) -> Result<DiffResult, JsError> {
|
||||
log::debug!("Running diff with config: {:?}", diff_config);
|
||||
let result = diff::diff_objs(&diff_config, &mapping_config, left, right, None).to_js()?;
|
||||
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
||||
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
||||
Ok(DiffResult::new(left, right))
|
||||
}
|
||||
|
||||
// #[wasm_bindgen]
|
||||
// pub fn run_diff_json(
|
||||
// left: Option<Box<[u8]>>,
|
||||
// right: Option<Box<[u8]>>,
|
||||
// config: diff::DiffObjConfig,
|
||||
// ) -> Result<String, JsError> {
|
||||
// let out = run_diff_opt_box(left, right, config)?;
|
||||
// serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
|
||||
// }
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn run_diff_proto(
|
||||
left: Option<Box<[u8]>>,
|
||||
right: Option<Box<[u8]>>,
|
||||
diff_config: diff::DiffObjConfig,
|
||||
mapping_config: diff::MappingConfig,
|
||||
) -> Result<Box<[u8]>, JsError> {
|
||||
let out = parse_and_run_diff(left, right, diff_config, mapping_config)?;
|
||||
Ok(out.encode_to_vec().into_boxed_slice())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
fn start() -> Result<(), JsError> {
|
||||
console_error_panic_hook::set_once();
|
||||
#[cfg(debug_assertions)]
|
||||
console_log::init_with_level(log::Level::Debug).to_js()?;
|
||||
#[cfg(not(debug_assertions))]
|
||||
console_log::init_with_level(log::Level::Info).to_js()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn to_js_error(e: impl std::fmt::Display) -> JsError { JsError::new(&e.to_string()) }
|
||||
|
||||
trait ToJsResult {
|
||||
type Output;
|
||||
|
||||
fn to_js(self) -> Result<Self::Output, JsError>;
|
||||
}
|
||||
|
||||
impl<T, E: std::fmt::Display> ToJsResult for Result<T, E> {
|
||||
type Output = T;
|
||||
|
||||
fn to_js(self) -> Result<T, JsError> { self.map_err(to_js_error) }
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
pub mod watcher;
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use std::process::Command;
|
||||
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
pub struct BuildStatus {
|
||||
pub success: bool,
|
||||
@@ -25,14 +24,14 @@ impl Default for BuildStatus {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BuildConfig {
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||
pub custom_make: Option<String>,
|
||||
pub custom_args: Option<Vec<String>>,
|
||||
#[allow(unused)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
||||
pub fn run_make(config: &BuildConfig, arg: &str) -> BuildStatus {
|
||||
let Some(cwd) = &config.project_dir else {
|
||||
return BuildStatus {
|
||||
success: false,
|
||||
|
||||
+114
-112
@@ -1,37 +1,47 @@
|
||||
use std::{
|
||||
pub mod path;
|
||||
|
||||
use alloc::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter, Read},
|
||||
path::{Path, PathBuf},
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use filetime::FileTime;
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use path::unix_path_serde_option;
|
||||
use typed_path::Utf8UnixPathBuf;
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Default, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||
pub struct ProjectConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub min_version: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub custom_make: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub custom_args: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub target_dir: Option<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub base_dir: Option<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub target_dir: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub base_dir: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub build_base: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub build_target: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub watch_patterns: Option<Vec<String>>,
|
||||
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(alias = "objects", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub units: Option<Vec<ProjectObject>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||
}
|
||||
|
||||
@@ -39,11 +49,6 @@ 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()
|
||||
@@ -66,55 +71,62 @@ impl ProjectConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Default, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||
pub struct ProjectObject {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub path: Option<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub target_path: Option<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub base_path: Option<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub path: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub target_path: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub base_path: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
#[deprecated(note = "Use metadata.complete")]
|
||||
pub complete: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub metadata: Option<ProjectObjectMetadata>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub symbol_mappings: Option<SymbolMappings>,
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub symbol_mappings: Option<BTreeMap<String, String>>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", tsify_next::declare)]
|
||||
pub type SymbolMappings = BTreeMap<String, String>;
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Default, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||
pub struct ProjectObjectMetadata {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub complete: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub source_path: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub source_path: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub progress_categories: Option<Vec<String>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub auto_generated: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Default, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||
pub struct ProjectProgressCategory {
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
@@ -123,33 +135,12 @@ impl ProjectObject {
|
||||
if let Some(name) = &self.name {
|
||||
name
|
||||
} else if let Some(path) = &self.path {
|
||||
path.to_str().unwrap_or("[invalid path]")
|
||||
path.as_str()
|
||||
} 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> {
|
||||
#[expect(deprecated)]
|
||||
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
||||
@@ -164,25 +155,36 @@ impl ProjectObject {
|
||||
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn source_path(&self) -> Option<&String> {
|
||||
pub fn source_path(&self) -> Option<&Utf8UnixPathBuf> {
|
||||
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
||||
}
|
||||
|
||||
pub fn progress_categories(&self) -> &[String] {
|
||||
self.metadata.as_ref().and_then(|m| m.progress_categories.as_deref()).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn auto_generated(&self) -> Option<bool> {
|
||||
self.metadata.as_ref().and_then(|m| m.auto_generated)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Default, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||
pub struct ScratchConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub platform: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub compiler: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||
)]
|
||||
pub ctx_path: Option<Utf8UnixPathBuf>,
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub build_ctx: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||
pub preset_id: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -197,16 +199,20 @@ pub fn default_watch_patterns() -> Vec<Glob> {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub struct ProjectConfigInfo {
|
||||
pub path: PathBuf,
|
||||
pub timestamp: Option<FileTime>,
|
||||
pub path: std::path::PathBuf,
|
||||
pub timestamp: Option<filetime::FileTime>,
|
||||
}
|
||||
|
||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||
#[cfg(feature = "std")]
|
||||
pub fn try_project_config(
|
||||
dir: &std::path::Path,
|
||||
) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||
for filename in CONFIG_FILENAMES.iter() {
|
||||
let config_path = dir.join(filename);
|
||||
let Ok(file) = File::open(&config_path) else {
|
||||
let Ok(file) = std::fs::File::open(&config_path) else {
|
||||
continue;
|
||||
};
|
||||
let metadata = file.metadata();
|
||||
@@ -214,12 +220,9 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
||||
if !metadata.is_file() {
|
||||
continue;
|
||||
}
|
||||
let ts = FileTime::from_last_modification_time(&metadata);
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut result = match filename.contains("json") {
|
||||
true => read_json_config(&mut reader),
|
||||
false => read_yml_config(&mut reader),
|
||||
};
|
||||
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||
let mut reader = std::io::BufReader::new(file);
|
||||
let mut result = read_json_config(&mut reader);
|
||||
if let Ok(config) = &result {
|
||||
// Validate min_version if present
|
||||
if let Err(e) = validate_min_version(config) {
|
||||
@@ -232,40 +235,42 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
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 let Ok(metadata) = std::fs::metadata(&info.path) {
|
||||
let ts = filetime::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 mut writer = std::io::BufWriter::new(
|
||||
std::fs::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);
|
||||
let ts = filetime::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"))
|
||||
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||
.context("Failed to parse package version")?;
|
||||
let min_version = semver::Version::parse(min_version).context("Failed to parse min_version")?;
|
||||
let min_version = semver::Version::parse(min_version)
|
||||
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||
.context("Failed to parse min_version")?;
|
||||
if version >= min_version {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -273,15 +278,12 @@ fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
#[cfg(feature = "std")]
|
||||
fn read_json_config<R: std::io::Read>(reader: &mut R) -> Result<ProjectConfig> {
|
||||
Ok(serde_json::from_reader(reader)?)
|
||||
}
|
||||
|
||||
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
||||
pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
|
||||
let mut builder = GlobSetBuilder::new();
|
||||
for glob in vec {
|
||||
builder.add(glob.clone());
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user