mirror of
https://github.com/encounter/objdiff.git
synced 2025-06-07 07:03:39 +00:00
Make objdiff-core no_std + huge WASM rework
This commit is contained in:
parent
d938988d43
commit
e8de35b78e
320
Cargo.lock
generated
320
Cargo.lock
generated
@ -152,11 +152,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "arm-attr"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d0cabd3a7d2dfa96ab3faa7b532a83c5e090061bf6d83197ca2bc91f5afac6c"
|
||||
checksum = "5790583a9ebcc63c55f142a85d85fe63ec0e033c941fcf00a3605f81dada2e32"
|
||||
dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -395,15 +395,6 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bimap"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
@ -688,16 +679,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console_log"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.34"
|
||||
@ -862,11 +843,10 @@ checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285"
|
||||
|
||||
[[package]]
|
||||
name = "cwextab"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "003567b96ff9d8ac3275831650385891bca370092937be625157778b1e58f755"
|
||||
version = "1.0.4"
|
||||
source = "git+https://github.com/encounter/cwextab.git#15c344ac3302c32adbb8777c70f5ce739f432a6b"
|
||||
dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1336,12 +1316,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@ -1632,10 +1606,6 @@ name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
@ -2112,6 +2082,12 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@ -2159,6 +2135,7 @@ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2342,6 +2319,15 @@ name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
@ -2539,11 +2525,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "msvc-demangler"
|
||||
version = "0.10.1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc"
|
||||
checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3000,6 +2987,7 @@ dependencies = [
|
||||
"time",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"typed-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3008,10 +2996,7 @@ version = "2.7.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
"bimap",
|
||||
"byteorder",
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
"cpp_demangle",
|
||||
"cwdemangle",
|
||||
"cwextab",
|
||||
@ -3043,17 +3028,18 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"shell-escape",
|
||||
"similar",
|
||||
"spin",
|
||||
"strum",
|
||||
"syn 2.0.96",
|
||||
"talc",
|
||||
"tempfile",
|
||||
"time",
|
||||
"tsify-next",
|
||||
"typed-path",
|
||||
"unarm",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
"wit-bindgen",
|
||||
"yaxpeax-arch",
|
||||
"yaxpeax-arm",
|
||||
]
|
||||
@ -3095,6 +3081,7 @@ dependencies = [
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
"tracing-wasm",
|
||||
"typed-path",
|
||||
"wgpu",
|
||||
"winapi",
|
||||
]
|
||||
@ -4097,17 +4084,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
@ -4119,17 +4095,6 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.135"
|
||||
@ -4174,19 +4139,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
@ -4246,9 +4198,11 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
|
||||
version = "2.7.0"
|
||||
source = "git+https://github.com/encounter/similar.git?branch=no_std#20f3537abfefa4cfb8c4b981c8aab99c7b53130d"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@ -4329,11 +4283,23 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spdx"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "58b69356da67e2fc1f542c71ea7e654a361a79c938e4424392ecf4fa065d2193"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spirv"
|
||||
@ -4441,6 +4407,15 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "talc"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fcad3be1cfe36eb7d716a04791eba36a197da9d9b6ea1e28e64ac569da3701d"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
@ -4786,30 +4761,6 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tsify-next"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"tsify-next-macros",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tsify-next-macros"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@ -4825,6 +4776,12 @@ dependencies = [
|
||||
"rustc-hash 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41713888c5ccfd99979fcd1afd47b71652e331b3d4a0e19d30769e80fec76cce"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.1.0"
|
||||
@ -4838,9 +4795,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unarm"
|
||||
version = "1.6.7"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a34c5159ddf1e715c3144fd31571bd2d9b63ac70ce1df51d7145ade4532d0c78"
|
||||
checksum = "dff0b9c752e29548c4bf614faa49a5f2d222c6333f9f70e3eb5cd84387f6a333"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
@ -4889,12 +4846,6 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@ -5065,6 +5016,45 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.224.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7249cf8cb0c6b9cb42bce90c0a5feb276fbf963fa385ff3d818ab3d90818ed6"
|
||||
dependencies = [
|
||||
"leb128",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.224.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79d13d93febc749413cb6f327e4fdba8c84e4d03bd69fcc4a220c66f113c8de1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"spdx",
|
||||
"url",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.224.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65881a664fdd43646b647bb27bf186ab09c05bf56779d40aed4c6dce47d423f5"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.7"
|
||||
@ -5750,6 +5740,104 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b550e454e4cce8984398539a94a0226511e1f295b14afdc8f08b4e2e2ff9de3a"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e2f98d49960a416074c5d72889f810ed3032a32ffef5e4760094426fefbfe8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6f8d372a2d4a1227f2556e051cc24b2a5f15768d53451c84ff91e2527139e3"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cc49091f84e4f2ace078bbc86082b57e667b9e789baece4b1184e0963382b6e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn 2.0.96",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3545a699dc9d72298b2064ce71b771fc10fc6b757d29306b1e54a4283a75abba"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.224.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad555ab4f4e676474df746d937823c7279c2d6dd36c3e97a61db893d4ef64ee5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.8.0",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.224.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e2925a7365d2c6709ae17bdbb5777ffd8154fd70906b413fc01b75f0dba59e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
@ -5855,9 +5943,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yaxpeax-arm"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1c6a2af41f88546a08df3bc77aadf7263884d6dffdac5b32dea7dc2df23f241"
|
||||
checksum = "1db82aac85bc577d19b6255bf54ad97241c436eeb997ba159f399adacc5fb69e"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"yaxpeax-arch",
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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 }
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
65
objdiff-core/src/config/path.rs
Normal file
65
objdiff-core/src/config/path.rs
Normal file
@ -0,0 +1,65 @@
|
||||
// For argp::FromArgs
|
||||
#[cfg(feature = "std")]
|
||||
pub fn platform_path(value: &str) -> Result<typed_path::Utf8PlatformPathBuf, String> {
|
||||
Ok(typed_path::Utf8PlatformPathBuf::from(value))
|
||||
}
|
||||
|
||||
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8PlatformPath`].
|
||||
#[cfg(feature = "std")]
|
||||
pub fn check_path(
|
||||
path: &std::path::Path,
|
||||
) -> Result<&typed_path::Utf8PlatformPath, core::str::Utf8Error> {
|
||||
typed_path::Utf8PlatformPath::from_bytes_path(typed_path::PlatformPath::new(
|
||||
path.as_os_str().as_encoded_bytes(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePathBuf`].
|
||||
#[cfg(feature = "std")]
|
||||
pub fn check_path_buf(
|
||||
path: std::path::PathBuf,
|
||||
) -> Result<typed_path::Utf8PlatformPathBuf, alloc::string::FromUtf8Error> {
|
||||
typed_path::Utf8PlatformPathBuf::from_bytes_path_buf(typed_path::PlatformPathBuf::from(
|
||||
path.into_os_string().into_encoded_bytes(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub mod unix_path_serde_option {
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
use typed_path::Utf8UnixPathBuf;
|
||||
|
||||
pub fn serialize<S>(path: &Option<Utf8UnixPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
if let Some(path) = path {
|
||||
s.serialize_some(path.as_str())
|
||||
} else {
|
||||
s.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8UnixPathBuf>, D::Error>
|
||||
where D: Deserializer<'de> {
|
||||
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8UnixPathBuf::from))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "serde", feature = "std"))]
|
||||
pub mod platform_path_serde_option {
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
pub fn serialize<S>(path: &Option<Utf8PlatformPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer {
|
||||
if let Some(path) = path {
|
||||
s.serialize_some(path.as_str())
|
||||
} else {
|
||||
s.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8PlatformPathBuf>, D::Error>
|
||||
where D: Deserializer<'de> {
|
||||
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8PlatformPathBuf::from))
|
||||
}
|
||||
}
|
@ -1,7 +1,12 @@
|
||||
use std::{cmp::max, collections::BTreeMap};
|
||||
use alloc::{
|
||||
collections::BTreeMap,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
||||
use similar::{capture_diff_slices, Algorithm};
|
||||
|
||||
use super::FunctionRelocDiffs;
|
||||
use crate::{
|
||||
@ -118,8 +123,7 @@ fn diff_instructions(
|
||||
left_code: &ProcessCodeResult,
|
||||
right_code: &ProcessCodeResult,
|
||||
) -> Result<()> {
|
||||
let ops =
|
||||
capture_diff_slices_deadline(Algorithm::Patience, &left_code.ops, &right_code.ops, None);
|
||||
let ops = capture_diff_slices(Algorithm::Patience, &left_code.ops, &right_code.ops);
|
||||
if ops.is_empty() {
|
||||
left_diff.extend(
|
||||
left_code
|
||||
@ -138,7 +142,7 @@ fn diff_instructions(
|
||||
|
||||
for op in ops {
|
||||
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
||||
let len = max(left_range.len(), right_range.len());
|
||||
let len = left_range.len().max(right_range.len());
|
||||
left_diff.extend(
|
||||
left_code.insts[left_range.clone()]
|
||||
.iter()
|
||||
|
@ -1,10 +1,8 @@
|
||||
use std::{
|
||||
cmp::{max, min, Ordering},
|
||||
ops::Range,
|
||||
};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::{cmp::Ordering, ops::Range};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm};
|
||||
use similar::{capture_diff_slices, get_diff_ratio, Algorithm};
|
||||
|
||||
use super::code::{address_eq, section_name_eq};
|
||||
use crate::{
|
||||
@ -142,7 +140,7 @@ pub fn diff_data_section(
|
||||
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
|
||||
let left_data = &left.data[..left_max as usize];
|
||||
let right_data = &right.data[..right_max as usize];
|
||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
||||
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
||||
|
||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
||||
@ -151,27 +149,27 @@ pub fn diff_data_section(
|
||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||
let left_len = left_range.len();
|
||||
let right_len = right_range.len();
|
||||
let mut len = max(left_len, right_len);
|
||||
let mut len = left_len.max(right_len);
|
||||
let kind = match tag {
|
||||
similar::DiffTag::Equal => ObjDataDiffKind::None,
|
||||
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
|
||||
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
|
||||
similar::DiffTag::Replace => {
|
||||
// Ensure replacements are equal length
|
||||
len = min(left_len, right_len);
|
||||
len = left_len.min(right_len);
|
||||
ObjDataDiffKind::Replace
|
||||
}
|
||||
};
|
||||
let left_data = &left.data[left_range];
|
||||
let right_data = &right.data[right_range];
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data[..min(len, left_data.len())].to_vec(),
|
||||
data: left_data[..len.min(left_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data[..min(len, right_data.len())].to_vec(),
|
||||
data: right_data[..len.min(right_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
@ -283,7 +281,7 @@ pub fn diff_data_symbol(
|
||||
right_range,
|
||||
);
|
||||
|
||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
||||
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||
|
||||
let mut match_ratio = bytes_match_ratio;
|
||||
@ -375,7 +373,7 @@ pub fn diff_bss_section(
|
||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
||||
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
||||
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, &left_sizes, &right_sizes, None);
|
||||
let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
|
||||
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
||||
|
||||
// Use the highest match percent between two options:
|
||||
|
@ -1,3 +1,5 @@
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
use crate::{
|
||||
diff::{ObjInsArgDiff, ObjInsDiff},
|
||||
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol},
|
||||
|
@ -1,9 +1,14 @@
|
||||
use std::{collections::HashSet, ops::Range};
|
||||
use alloc::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
string::String,
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
use core::ops::Range;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
config::SymbolMappings,
|
||||
diff::{
|
||||
code::{diff_code, no_diff_code, process_code_symbol},
|
||||
data::{
|
||||
@ -473,12 +478,11 @@ struct SectionMatch {
|
||||
section_kind: ObjSectionKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||
pub struct MappingConfig {
|
||||
/// Manual symbol mappings
|
||||
pub mappings: SymbolMappings,
|
||||
pub mappings: BTreeMap<String, String>,
|
||||
/// The right object symbol name that we're selecting a left symbol for
|
||||
pub selecting_left: Option<String>,
|
||||
/// The left object symbol name that we're selecting a right symbol for
|
||||
@ -500,8 +504,8 @@ fn apply_symbol_mappings(
|
||||
left: &ObjInfo,
|
||||
right: &ObjInfo,
|
||||
mapping_config: &MappingConfig,
|
||||
left_used: &mut HashSet<SymbolRef>,
|
||||
right_used: &mut HashSet<SymbolRef>,
|
||||
left_used: &mut BTreeSet<SymbolRef>,
|
||||
right_used: &mut BTreeSet<SymbolRef>,
|
||||
matches: &mut Vec<SymbolMatch>,
|
||||
) -> Result<()> {
|
||||
// If we're selecting a symbol to use as a comparison, mark it as used
|
||||
@ -563,8 +567,8 @@ fn matching_symbols(
|
||||
mappings: &MappingConfig,
|
||||
) -> Result<Vec<SymbolMatch>> {
|
||||
let mut matches = Vec::new();
|
||||
let mut left_used = HashSet::new();
|
||||
let mut right_used = HashSet::new();
|
||||
let mut left_used = BTreeSet::new();
|
||||
let mut right_used = BTreeSet::new();
|
||||
if let Some(left) = left {
|
||||
if let Some(right) = right {
|
||||
apply_symbol_mappings(
|
||||
@ -645,7 +649,7 @@ fn matching_symbols(
|
||||
fn unmatched_symbols<'section, 'used>(
|
||||
section: &'section ObjSection,
|
||||
section_idx: usize,
|
||||
used: Option<&'used HashSet<SymbolRef>>,
|
||||
used: Option<&'used BTreeSet<SymbolRef>>,
|
||||
) -> impl Iterator<Item = (usize, &'section ObjSymbol)> + 'used
|
||||
where
|
||||
'section: 'used,
|
||||
@ -660,7 +664,7 @@ fn find_symbol(
|
||||
obj: Option<&ObjInfo>,
|
||||
in_symbol: &ObjSymbol,
|
||||
in_section: &ObjSection,
|
||||
used: Option<&HashSet<SymbolRef>>,
|
||||
used: Option<&BTreeSet<SymbolRef>>,
|
||||
) -> Option<SymbolRef> {
|
||||
let obj = obj?;
|
||||
// Try to find an exact name match
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::{fs, path::PathBuf, sync::mpsc::Receiver, task::Waker};
|
||||
use std::{fs, sync::mpsc::Receiver, task::Waker};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||
|
||||
use crate::{
|
||||
build::{run_make, BuildConfig, BuildStatus},
|
||||
@ -10,7 +11,7 @@ use crate::{
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CreateScratchConfig {
|
||||
pub build_config: BuildConfig,
|
||||
pub context_path: Option<PathBuf>,
|
||||
pub context_path: Option<Utf8UnixPathBuf>,
|
||||
pub build_context: bool,
|
||||
|
||||
// Scratch fields
|
||||
@ -18,7 +19,7 @@ pub struct CreateScratchConfig {
|
||||
pub platform: String,
|
||||
pub compiler_flags: String,
|
||||
pub function_name: String,
|
||||
pub target_obj: PathBuf,
|
||||
pub target_obj: Utf8PlatformPathBuf,
|
||||
pub preset_id: Option<u32>,
|
||||
}
|
||||
|
||||
@ -47,26 +48,25 @@ fn run_create_scratch(
|
||||
if let Some(context_path) = &config.context_path {
|
||||
if config.build_context {
|
||||
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
||||
match run_make(&config.build_config, context_path) {
|
||||
match run_make(&config.build_config, context_path.as_ref()) {
|
||||
BuildStatus { success: true, .. } => {}
|
||||
BuildStatus { success: false, stdout, stderr, .. } => {
|
||||
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
||||
}
|
||||
}
|
||||
}
|
||||
let context_path = project_dir.join(context_path);
|
||||
let context_path = project_dir.join(context_path.with_platform_encoding());
|
||||
context = Some(
|
||||
fs::read_to_string(&context_path)
|
||||
.map_err(|e| anyhow!("Failed to read {}: {}", context_path.display(), e))?,
|
||||
.map_err(|e| anyhow!("Failed to read {}: {}", context_path, e))?,
|
||||
);
|
||||
}
|
||||
|
||||
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
||||
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
||||
let diff_flags = serde_json::to_string(&diff_flags)?;
|
||||
let obj_path = project_dir.join(&config.target_obj);
|
||||
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
||||
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
||||
let file = reqwest::blocking::multipart::Part::file(&config.target_obj)
|
||||
.with_context(|| format!("Failed to open {}", config.target_obj))?;
|
||||
let mut form = reqwest::blocking::multipart::Form::new()
|
||||
.text("compiler", config.compiler.clone())
|
||||
.text("platform", config.platform.clone())
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::{path::PathBuf, sync::mpsc::Receiver, task::Waker};
|
||||
use std::{sync::mpsc::Receiver, task::Waker};
|
||||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use time::OffsetDateTime;
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
use crate::{
|
||||
build::{run_make, BuildConfig, BuildStatus},
|
||||
@ -14,8 +15,8 @@ pub struct ObjDiffConfig {
|
||||
pub build_config: BuildConfig,
|
||||
pub build_base: bool,
|
||||
pub build_target: bool,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
pub mapping_config: MappingConfig,
|
||||
}
|
||||
@ -43,20 +44,12 @@ fn run_build(
|
||||
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||
if let Some(target_path) = &config.target_path {
|
||||
target_path_rel = Some(target_path.strip_prefix(project_dir).map_err(|_| {
|
||||
anyhow!(
|
||||
"Target path '{}' doesn't begin with '{}'",
|
||||
target_path.display(),
|
||||
project_dir.display()
|
||||
)
|
||||
anyhow!("Target path '{}' doesn't begin with '{}'", target_path, project_dir)
|
||||
})?);
|
||||
}
|
||||
if let Some(base_path) = &config.base_path {
|
||||
base_path_rel = Some(base_path.strip_prefix(project_dir).map_err(|_| {
|
||||
anyhow!(
|
||||
"Base path '{}' doesn't begin with '{}'",
|
||||
base_path.display(),
|
||||
project_dir.display()
|
||||
)
|
||||
anyhow!("Base path '{}' doesn't begin with '{}'", base_path, project_dir)
|
||||
})?);
|
||||
};
|
||||
}
|
||||
@ -80,13 +73,13 @@ fn run_build(
|
||||
Some(target_path_rel) if config.build_target => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Building target {}", target_path_rel.display()),
|
||||
format!("Building target {}", target_path_rel),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
run_make(&config.build_config, target_path_rel)
|
||||
run_make(&config.build_config, target_path_rel.as_ref())
|
||||
}
|
||||
_ => BuildStatus::default(),
|
||||
};
|
||||
@ -95,13 +88,13 @@ fn run_build(
|
||||
Some(base_path_rel) if config.build_base => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Building base {}", base_path_rel.display()),
|
||||
format!("Building base {}", base_path_rel),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
run_make(&config.build_config, base_path_rel)
|
||||
run_make(&config.build_config, base_path_rel.as_ref())
|
||||
}
|
||||
_ => BuildStatus::default(),
|
||||
};
|
||||
@ -112,18 +105,18 @@ fn run_build(
|
||||
Some(target_path) if first_status.success => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Loading target {}", target_path.display()),
|
||||
format!("Loading target {}", target_path),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
match read::read(target_path, &config.diff_obj_config) {
|
||||
match read::read(target_path.as_ref(), &config.diff_obj_config) {
|
||||
Ok(obj) => Some(obj),
|
||||
Err(e) => {
|
||||
first_status = BuildStatus {
|
||||
success: false,
|
||||
stdout: format!("Loading object '{}'", target_path.display()),
|
||||
stdout: format!("Loading object '{}'", target_path),
|
||||
stderr: format!("{:#}", e),
|
||||
..Default::default()
|
||||
};
|
||||
@ -142,18 +135,18 @@ fn run_build(
|
||||
Some(base_path) if second_status.success => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Loading base {}", base_path.display()),
|
||||
format!("Loading base {}", base_path),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
match read::read(base_path, &config.diff_obj_config) {
|
||||
match read::read(base_path.as_ref(), &config.diff_obj_config) {
|
||||
Ok(obj) => Some(obj),
|
||||
Err(e) => {
|
||||
second_status = BuildStatus {
|
||||
success: false,
|
||||
stdout: format!("Loading object '{}'", base_path.display()),
|
||||
stdout: format!("Loading object '{}'", base_path),
|
||||
stderr: format!("{:#}", e),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -1,3 +1,6 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "any-arch")]
|
||||
pub mod arch;
|
||||
#[cfg(feature = "bindings")]
|
||||
@ -14,3 +17,5 @@ pub mod jobs;
|
||||
pub mod obj;
|
||||
#[cfg(feature = "any-arch")]
|
||||
pub mod util;
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod wasm;
|
||||
|
@ -1,9 +1,9 @@
|
||||
pub mod read;
|
||||
pub mod split_meta;
|
||||
|
||||
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
|
||||
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, vec::Vec};
|
||||
use core::fmt;
|
||||
|
||||
use filetime::FileTime;
|
||||
use flagset::{flags, FlagSet};
|
||||
use object::RelocationFlags;
|
||||
use split_meta::SplitMeta;
|
||||
@ -152,8 +152,9 @@ pub struct ObjSymbol {
|
||||
|
||||
pub struct ObjInfo {
|
||||
pub arch: Box<dyn ObjArch>,
|
||||
pub path: Option<PathBuf>,
|
||||
pub timestamp: Option<FileTime>,
|
||||
pub path: Option<String>,
|
||||
#[cfg(feature = "std")]
|
||||
pub timestamp: Option<filetime::FileTime>,
|
||||
pub sections: Vec<ObjSection>,
|
||||
/// Common BSS symbols
|
||||
pub common: Vec<ObjSymbol>,
|
||||
|
@ -1,13 +1,12 @@
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
io::Cursor,
|
||||
mem::size_of,
|
||||
path::Path,
|
||||
use alloc::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
format,
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use filetime::FileTime;
|
||||
use flagset::Flags;
|
||||
use object::{
|
||||
endian::LittleEndian as LE,
|
||||
@ -160,7 +159,7 @@ fn symbols_by_section(
|
||||
section: &ObjSection,
|
||||
section_symbols: &[Symbol<'_, '_>],
|
||||
split_meta: Option<&SplitMeta>,
|
||||
name_counts: &mut HashMap<String, u32>,
|
||||
name_counts: &mut BTreeMap<String, u32>,
|
||||
) -> Result<Vec<ObjSymbol>> {
|
||||
let mut result = Vec::<ObjSymbol>::new();
|
||||
for symbol in section_symbols {
|
||||
@ -377,33 +376,37 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
||||
// DWARF 1.1
|
||||
if let Some(section) = obj_file.section_by_name(".line") {
|
||||
let data = section.uncompressed_data()?;
|
||||
let mut reader = Cursor::new(data.as_ref());
|
||||
let mut reader: &[u8] = data.as_ref();
|
||||
|
||||
let mut text_sections = obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
||||
while reader.position() < data.len() as u64 {
|
||||
while !reader.is_empty() {
|
||||
let text_section_index = text_sections
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
|
||||
.index()
|
||||
.0;
|
||||
let start = reader.position();
|
||||
let size = read_u32(obj_file, &mut reader)?;
|
||||
let base_address = read_u32(obj_file, &mut reader)? as u64;
|
||||
|
||||
let mut section_data = &reader[..];
|
||||
let size = read_u32(obj_file, &mut section_data)? as usize;
|
||||
if size > reader.len() {
|
||||
bail!("Line info size {size} exceeds remaining size {}", reader.len());
|
||||
}
|
||||
(section_data, reader) = reader.split_at(size);
|
||||
|
||||
let base_address = read_u32(obj_file, &mut section_data)? as u64;
|
||||
let Some(out_section) =
|
||||
sections.iter_mut().find(|s| s.orig_index == text_section_index)
|
||||
else {
|
||||
// Skip line info for sections we filtered out
|
||||
reader.set_position(start + size as u64);
|
||||
continue;
|
||||
};
|
||||
let end = start + size as u64;
|
||||
while reader.position() < end {
|
||||
let line_number = read_u32(obj_file, &mut reader)?;
|
||||
let statement_pos = read_u16(obj_file, &mut reader)?;
|
||||
while !section_data.is_empty() {
|
||||
let line_number = read_u32(obj_file, &mut section_data)?;
|
||||
let statement_pos = read_u16(obj_file, &mut section_data)?;
|
||||
if statement_pos != 0xFFFF {
|
||||
log::warn!("Unhandled statement pos {}", statement_pos);
|
||||
}
|
||||
let address_delta = read_u32(obj_file, &mut reader)? as u64;
|
||||
let address_delta = read_u32(obj_file, &mut section_data)? as u64;
|
||||
out_section.line_info.insert(base_address + address_delta, line_number);
|
||||
log::debug!("Line: {:#x} -> {}", base_address + address_delta, line_number);
|
||||
}
|
||||
@ -413,22 +416,24 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
||||
// DWARF 2+
|
||||
#[cfg(feature = "dwarf")]
|
||||
{
|
||||
fn gimli_error(e: gimli::Error) -> anyhow::Error { anyhow::anyhow!("DWARF error: {e:?}") }
|
||||
let dwarf_cow = gimli::DwarfSections::load(|id| {
|
||||
Ok::<_, gimli::Error>(
|
||||
obj_file
|
||||
.section_by_name(id.name())
|
||||
.and_then(|section| section.uncompressed_data().ok())
|
||||
.unwrap_or(std::borrow::Cow::Borrowed(&[][..])),
|
||||
.unwrap_or(alloc::borrow::Cow::Borrowed(&[][..])),
|
||||
)
|
||||
})?;
|
||||
})
|
||||
.map_err(gimli_error)?;
|
||||
let endian = match obj_file.endianness() {
|
||||
object::Endianness::Little => gimli::RunTimeEndian::Little,
|
||||
object::Endianness::Big => gimli::RunTimeEndian::Big,
|
||||
};
|
||||
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
|
||||
let mut iter = dwarf.units();
|
||||
if let Some(header) = iter.next()? {
|
||||
let unit = dwarf.unit(header)?;
|
||||
if let Some(header) = iter.next().map_err(gimli_error)? {
|
||||
let unit = dwarf.unit(header).map_err(gimli_error)?;
|
||||
if let Some(program) = unit.line_program.clone() {
|
||||
let mut text_sections =
|
||||
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
|
||||
@ -438,7 +443,7 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
||||
.map(|s| &mut s.line_info);
|
||||
|
||||
let mut rows = program.rows();
|
||||
while let Some((_header, row)) = rows.next_row()? {
|
||||
while let Some((_header, row)) = rows.next_row().map_err(gimli_error)? {
|
||||
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
|
||||
lines.insert(row.address(), line.get() as u32);
|
||||
}
|
||||
@ -453,7 +458,7 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
|
||||
}
|
||||
}
|
||||
}
|
||||
if iter.next()?.is_some() {
|
||||
if iter.next().map_err(gimli_error)?.is_some() {
|
||||
log::warn!("Multiple units found in DWARF data, only processing the first");
|
||||
}
|
||||
}
|
||||
@ -638,7 +643,7 @@ fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSecti
|
||||
}
|
||||
|
||||
fn combine_data_sections(sections: &mut Vec<ObjSection>) -> Result<()> {
|
||||
let names_to_combine: HashSet<_> = sections
|
||||
let names_to_combine: BTreeSet<_> = sections
|
||||
.iter()
|
||||
.filter(|s| s.kind == ObjSectionKind::Data)
|
||||
.map(|s| s.name.clone())
|
||||
@ -677,14 +682,15 @@ fn combine_data_sections(sections: &mut Vec<ObjSection>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read(obj_path: &Path, config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||
#[cfg(feature = "std")]
|
||||
pub fn read(obj_path: &std::path::Path, config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||
let (data, timestamp) = {
|
||||
let file = fs::File::open(obj_path)?;
|
||||
let timestamp = FileTime::from_last_modification_time(&file.metadata()?);
|
||||
let file = std::fs::File::open(obj_path)?;
|
||||
let timestamp = filetime::FileTime::from_last_modification_time(&file.metadata()?);
|
||||
(unsafe { memmap2::Mmap::map(&file) }?, timestamp)
|
||||
};
|
||||
let mut obj = parse(&data, config)?;
|
||||
obj.path = Some(obj_path.to_owned());
|
||||
obj.path = Some(obj_path.to_string_lossy().into_owned());
|
||||
obj.timestamp = Some(timestamp);
|
||||
Ok(obj)
|
||||
}
|
||||
@ -710,7 +716,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||
}
|
||||
|
||||
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
|
||||
let mut section_name_counts: HashMap<String, u32> = HashMap::new();
|
||||
let mut section_name_counts: BTreeMap<String, u32> = BTreeMap::new();
|
||||
for section in &mut sections {
|
||||
section.symbols = symbols_by_section(
|
||||
arch.as_ref(),
|
||||
@ -733,12 +739,21 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
|
||||
}
|
||||
line_info(&obj_file, &mut sections, data)?;
|
||||
let common = common_symbols(arch.as_ref(), &obj_file, split_meta.as_ref())?;
|
||||
Ok(ObjInfo { arch, path: None, timestamp: None, sections, common, split_meta })
|
||||
Ok(ObjInfo {
|
||||
arch,
|
||||
path: None,
|
||||
#[cfg(feature = "std")]
|
||||
timestamp: None,
|
||||
sections,
|
||||
common,
|
||||
split_meta,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_function(obj_path: &Path, symbol_name: &str) -> Result<bool> {
|
||||
#[cfg(feature = "std")]
|
||||
pub fn has_function(obj_path: &std::path::Path, symbol_name: &str) -> Result<bool> {
|
||||
let data = {
|
||||
let file = fs::File::open(obj_path)?;
|
||||
let file = std::fs::File::open(obj_path)?;
|
||||
unsafe { memmap2::Mmap::map(&file) }?
|
||||
};
|
||||
Ok(File::parse(&*data)?
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{io, io::Write};
|
||||
use alloc::{string::String, vec, vec::Vec};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use object::{elf::SHT_NOTE, Endian, ObjectSection};
|
||||
|
||||
pub const SPLITMETA_SECTION: &str = ".note.split";
|
||||
@ -27,10 +28,10 @@ const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI");
|
||||
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
||||
|
||||
impl SplitMeta {
|
||||
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> io::Result<Self>
|
||||
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> Result<Self>
|
||||
where E: Endian {
|
||||
let mut result = SplitMeta::default();
|
||||
let data = section.uncompressed_data().map_err(object_io_error)?;
|
||||
let data = section.uncompressed_data().map_err(object_error)?;
|
||||
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
||||
while let Some(note) = iter.next(e)? {
|
||||
if note.name != ELF_NOTE_SPLIT {
|
||||
@ -39,19 +40,18 @@ impl SplitMeta {
|
||||
match note.n_type {
|
||||
NT_SPLIT_GENERATOR => {
|
||||
let string = String::from_utf8(note.desc.to_vec())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
.map_err(|e| anyhow::Error::from(e))?;
|
||||
result.generator = Some(string);
|
||||
}
|
||||
NT_SPLIT_MODULE_NAME => {
|
||||
let string = String::from_utf8(note.desc.to_vec())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
.map_err(|e| anyhow::Error::from(e))?;
|
||||
result.module_name = Some(string);
|
||||
}
|
||||
NT_SPLIT_MODULE_ID => {
|
||||
result.module_id =
|
||||
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size")
|
||||
})?));
|
||||
result.module_id = Some(e.read_u32_bytes(
|
||||
note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?,
|
||||
));
|
||||
}
|
||||
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
||||
let vec = if is_64 {
|
||||
@ -79,10 +79,11 @@ impl SplitMeta {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> io::Result<()>
|
||||
#[cfg(feature = "std")]
|
||||
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> std::io::Result<()>
|
||||
where
|
||||
E: Endian,
|
||||
W: Write + ?Sized,
|
||||
W: std::io::Write + ?Sized,
|
||||
{
|
||||
if let Some(generator) = &self.generator {
|
||||
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
||||
@ -137,10 +138,9 @@ impl SplitMeta {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an object::read::Error to an io::Error.
|
||||
fn object_io_error(err: object::read::Error) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidData, err)
|
||||
}
|
||||
/// Convert an object::read::Error to a String.
|
||||
#[inline]
|
||||
fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) }
|
||||
|
||||
/// An ELF note entry.
|
||||
struct Note<'data> {
|
||||
@ -161,27 +161,27 @@ where E: Endian
|
||||
impl<'data, E> NoteIterator<'data, E>
|
||||
where E: Endian
|
||||
{
|
||||
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> io::Result<Self> {
|
||||
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> Result<Self> {
|
||||
Ok(if is_64 {
|
||||
NoteIterator::B64(
|
||||
object::read::elf::NoteIterator::new(e, align, data).map_err(object_io_error)?,
|
||||
object::read::elf::NoteIterator::new(e, align, data).map_err(object_error)?,
|
||||
)
|
||||
} else {
|
||||
NoteIterator::B32(
|
||||
object::read::elf::NoteIterator::new(e, align as u32, data)
|
||||
.map_err(object_io_error)?,
|
||||
.map_err(object_error)?,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn next(&mut self, e: E) -> io::Result<Option<Note<'data>>> {
|
||||
fn next(&mut self, e: E) -> Result<Option<Note<'data>>> {
|
||||
match self {
|
||||
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
||||
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||
n_type: note.n_type(e),
|
||||
name: note.name(),
|
||||
desc: note.desc(),
|
||||
})),
|
||||
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
||||
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||
n_type: note.n_type(e),
|
||||
name: note.name(),
|
||||
desc: note.desc(),
|
||||
@ -192,7 +192,8 @@ where E: Endian
|
||||
|
||||
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
||||
|
||||
fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<()> {
|
||||
#[cfg(feature = "std")]
|
||||
fn align_data_to_4<W: std::io::Write + ?Sized>(writer: &mut W, len: usize) -> std::io::Result<()> {
|
||||
const ALIGN_BYTES: &[u8] = &[0; 4];
|
||||
if len % 4 != 0 {
|
||||
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
||||
@ -208,10 +209,11 @@ fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<
|
||||
// Desc | variable size, padded to a 4 byte boundary
|
||||
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
||||
|
||||
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> io::Result<()>
|
||||
#[cfg(feature = "std")]
|
||||
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> std::io::Result<()>
|
||||
where
|
||||
E: Endian,
|
||||
W: Write + ?Sized,
|
||||
W: std::io::Write + ?Sized,
|
||||
{
|
||||
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
||||
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
||||
|
@ -1,18 +1,15 @@
|
||||
use std::{
|
||||
fmt::{LowerHex, UpperHex},
|
||||
io::Read,
|
||||
};
|
||||
use alloc::format;
|
||||
use core::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use byteorder::{NativeEndian, ReadBytesExt};
|
||||
use num_traits::PrimInt;
|
||||
use object::{Endian, Object};
|
||||
|
||||
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
||||
pub struct ReallySigned<N: PrimInt>(pub(crate) N);
|
||||
|
||||
impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl<N: PrimInt> fmt::LowerHex for ReallySigned<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let num = self.0.to_i64().unwrap();
|
||||
let prefix = if f.alternate() { "0x" } else { "" };
|
||||
let bare_hex = format!("{:x}", num.abs());
|
||||
@ -20,8 +17,8 @@ impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let num = self.0.to_i64().unwrap();
|
||||
let prefix = if f.alternate() { "0x" } else { "" };
|
||||
let bare_hex = format!("{:X}", num.abs());
|
||||
@ -29,10 +26,18 @@ impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_u32<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u32> {
|
||||
Ok(obj_file.endianness().read_u32(reader.read_u32::<NativeEndian>()?))
|
||||
pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
|
||||
if reader.len() < 4 {
|
||||
return Err(anyhow::anyhow!("Not enough bytes to read u32"));
|
||||
}
|
||||
let value = u32::from_ne_bytes(reader[..4].try_into()?);
|
||||
Ok(obj_file.endianness().read_u32(value))
|
||||
}
|
||||
|
||||
pub fn read_u16<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u16> {
|
||||
Ok(obj_file.endianness().read_u16(reader.read_u16::<NativeEndian>()?))
|
||||
pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
|
||||
if reader.len() < 2 {
|
||||
return Err(anyhow::anyhow!("Not enough bytes to read u16"));
|
||||
}
|
||||
let value = u16::from_ne_bytes(reader[..2].try_into()?);
|
||||
Ok(obj_file.endianness().read_u16(value))
|
||||
}
|
||||
|
99
objdiff-core/src/wasm/api.rs
Normal file
99
objdiff-core/src/wasm/api.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use alloc::{
|
||||
format,
|
||||
str::FromStr,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::cell::RefCell;
|
||||
|
||||
use prost::Message;
|
||||
|
||||
use crate::{bindings::diff::DiffResult, diff, obj};
|
||||
|
||||
wit_bindgen::generate!({
|
||||
world: "api",
|
||||
});
|
||||
|
||||
use exports::objdiff::core::diff::{
|
||||
DiffConfigBorrow, Guest as GuestTypes, GuestDiffConfig, GuestObject, Object, ObjectBorrow,
|
||||
};
|
||||
|
||||
struct Component;
|
||||
|
||||
impl Guest for Component {
|
||||
fn init() -> Result<(), String> {
|
||||
// console_error_panic_hook::set_once();
|
||||
// #[cfg(debug_assertions)]
|
||||
// console_log::init_with_level(log::Level::Debug).map_err(|e| e.to_string())?;
|
||||
// #[cfg(not(debug_assertions))]
|
||||
// console_log::init_with_level(log::Level::Info).map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct ResourceDiffConfig(RefCell<diff::DiffObjConfig>);
|
||||
|
||||
impl GuestTypes for Component {
|
||||
type DiffConfig = ResourceDiffConfig;
|
||||
type Object = obj::ObjInfo;
|
||||
|
||||
fn run_diff(
|
||||
left: Option<ObjectBorrow>,
|
||||
right: Option<ObjectBorrow>,
|
||||
diff_config: DiffConfigBorrow,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
let result = run_diff_internal(
|
||||
left.as_ref().map(|o| o.get()),
|
||||
right.as_ref().map(|o| o.get()),
|
||||
&diff_config,
|
||||
&diff::MappingConfig::default(),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(result.encode_to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestDiffConfig for ResourceDiffConfig {
|
||||
fn new() -> Self { Self(RefCell::new(diff::DiffObjConfig::default())) }
|
||||
|
||||
fn set_property(&self, key: String, value: String) -> Result<(), String> {
|
||||
let id = diff::ConfigPropertyId::from_str(&key)
|
||||
.map_err(|_| format!("Invalid property key {:?}", key))?;
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.set_property_value_str(id, &value)
|
||||
.map_err(|_| format!("Invalid property value {:?}", value))
|
||||
}
|
||||
|
||||
fn get_property(&self, key: String) -> Result<String, String> {
|
||||
let id = diff::ConfigPropertyId::from_str(&key)
|
||||
.map_err(|_| format!("Invalid property key {:?}", key))?;
|
||||
Ok(self.0.borrow().get_property_value(id).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestObject for obj::ObjInfo {
|
||||
fn parse(data: Vec<u8>, diff_config: DiffConfigBorrow) -> Result<Object, String> {
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
obj::read::parse(&data, &diff_config).map(|o| Object::new(o)).map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn run_diff_internal(
|
||||
left: Option<&obj::ObjInfo>,
|
||||
right: Option<&obj::ObjInfo>,
|
||||
diff_config: &diff::DiffObjConfig,
|
||||
mapping_config: &diff::MappingConfig,
|
||||
) -> anyhow::Result<DiffResult> {
|
||||
log::debug!("Running diff with config: {:?}", diff_config);
|
||||
let result = diff::diff_objs(diff_config, mapping_config, left, right, None)?;
|
||||
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))
|
||||
}
|
||||
|
||||
export!(Component);
|
64
objdiff-core/src/wasm/cabi_realloc.rs
Normal file
64
objdiff-core/src/wasm/cabi_realloc.rs
Normal file
@ -0,0 +1,64 @@
|
||||
//! This module contains a canonical definition of the `cabi_realloc` function
|
||||
//! for the component model.
|
||||
//!
|
||||
//! The component model's canonical ABI for representing datatypes in memory
|
||||
//! makes use of this function when transferring lists and strings, for example.
|
||||
//! This function behaves like C's `realloc` but also takes alignment into
|
||||
//! account.
|
||||
//!
|
||||
//! Components are notably not required to export this function, but nearly
|
||||
//! all components end up doing so currently. This definition in the standard
|
||||
//! library removes the need for all compilations to define this themselves.
|
||||
//!
|
||||
//! More information about the canonical ABI can be found at
|
||||
//! <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
|
||||
//!
|
||||
//! Note that the name of this function is not standardized in the canonical ABI
|
||||
//! at this time. Instead it's a convention of the "componentization process"
|
||||
//! where a core wasm module is converted to a component to use this name.
|
||||
//! Additionally this is not the only possible definition of this function, so
|
||||
//! this is defined as a "weak" symbol. This means that other definitions are
|
||||
//! allowed to overwrite it if they are present in a compilation.
|
||||
|
||||
use alloc::{alloc, Layout};
|
||||
use core::ptr;
|
||||
|
||||
#[used]
|
||||
static FORCE_CODEGEN_OF_CABI_REALLOC: unsafe extern "C" fn(
|
||||
*mut u8,
|
||||
usize,
|
||||
usize,
|
||||
usize,
|
||||
) -> *mut u8 = cabi_realloc;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cabi_realloc(
|
||||
old_ptr: *mut u8,
|
||||
old_len: usize,
|
||||
align: usize,
|
||||
new_len: usize,
|
||||
) -> *mut u8 {
|
||||
let layout;
|
||||
let ptr = if old_len == 0 {
|
||||
if new_len == 0 {
|
||||
return ptr::without_provenance_mut(align);
|
||||
}
|
||||
layout = Layout::from_size_align_unchecked(new_len, align);
|
||||
alloc::alloc(layout)
|
||||
} else {
|
||||
debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!");
|
||||
layout = Layout::from_size_align_unchecked(old_len, align);
|
||||
alloc::realloc(old_ptr, layout, new_len)
|
||||
};
|
||||
if ptr.is_null() {
|
||||
// Print a nice message in debug mode, but in release mode don't
|
||||
// pull in so many dependencies related to printing so just emit an
|
||||
// `unreachable` instruction.
|
||||
if cfg!(debug_assertions) {
|
||||
alloc::handle_alloc_error(layout);
|
||||
} else {
|
||||
core::unreachable!("allocation failed")
|
||||
}
|
||||
}
|
||||
ptr
|
||||
}
|
18
objdiff-core/src/wasm/mod.rs
Normal file
18
objdiff-core/src/wasm/mod.rs
Normal file
@ -0,0 +1,18 @@
|
||||
mod api;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod cabi_realloc;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
static mut ARENA: [u8; 10000] = [0; 10000];
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: talc::Talck<spin::Mutex<()>, talc::ClaimOnOom> = talc::Talc::new(unsafe {
|
||||
talc::ClaimOnOom::new(talc::Span::from_array(core::ptr::addr_of!(ARENA) as *mut [u8; 10000]))
|
||||
})
|
||||
.lock();
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[panic_handler]
|
||||
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
|
29
objdiff-core/wit/objdiff.wit
Normal file
29
objdiff-core/wit/objdiff.wit
Normal file
@ -0,0 +1,29 @@
|
||||
package objdiff:core;
|
||||
|
||||
interface diff {
|
||||
resource diff-config {
|
||||
constructor();
|
||||
set-property: func(id: string, value: string) -> result<_, string>;
|
||||
get-property: func(id: string) -> result<string, string>;
|
||||
}
|
||||
|
||||
resource object {
|
||||
parse: static func(
|
||||
data: list<u8>,
|
||||
config: borrow<diff-config>,
|
||||
) -> result<object, string>;
|
||||
}
|
||||
|
||||
run-diff: func(
|
||||
left: option<borrow<object>>,
|
||||
right: option<borrow<object>>,
|
||||
config: borrow<diff-config>,
|
||||
) -> result<list<u8>, string>;
|
||||
}
|
||||
|
||||
world api {
|
||||
export diff;
|
||||
|
||||
export init: func() -> result<_, string>;
|
||||
export version: func() -> string;
|
||||
}
|
@ -29,7 +29,7 @@ bytes = "1.9"
|
||||
cfg-if = "1.0"
|
||||
const_format = "0.2"
|
||||
cwdemangle = "1.0"
|
||||
cwextab = "1.0"
|
||||
cwextab = { version = "1.0", git = "https://github.com/encounter/cwextab.git" }
|
||||
dirs = "5.0"
|
||||
egui = "0.30"
|
||||
egui_extras = "0.30"
|
||||
@ -51,6 +51,7 @@ serde_json = "1.0"
|
||||
shell-escape = "0.1"
|
||||
strum = { version = "0.26", features = ["derive"] }
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
typed-path = "0.10"
|
||||
|
||||
# Keep version in sync with egui
|
||||
[dependencies.eframe]
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
default::Default,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
@ -15,13 +16,15 @@ use globset::Glob;
|
||||
use objdiff_core::{
|
||||
build::watcher::{create_watcher, Watcher},
|
||||
config::{
|
||||
build_globset, default_watch_patterns, save_project_config, ProjectConfig,
|
||||
ProjectConfigInfo, ProjectObject, ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
||||
build_globset, default_watch_patterns, path::platform_path_serde_option,
|
||||
save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
|
||||
DEFAULT_WATCH_PATTERNS,
|
||||
},
|
||||
diff::DiffObjConfig,
|
||||
jobs::{Job, JobQueue, JobResult},
|
||||
};
|
||||
use time::UtcOffset;
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
app_config::{deserialize_config, AppConfigVersion},
|
||||
@ -90,26 +93,57 @@ impl Default for ViewState {
|
||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfig {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
#[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 reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
pub source_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: SymbolMappings,
|
||||
pub hidden: bool,
|
||||
pub scratch: Option<ScratchConfig>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub source_path: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl From<&ProjectObject> for ObjectConfig {
|
||||
fn from(object: &ProjectObject) -> Self {
|
||||
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
|
||||
};
|
||||
let source_path =
|
||||
object.source_path().map(|s| project_dir.join(s.with_platform_encoding()));
|
||||
Self {
|
||||
name: object.name().to_string(),
|
||||
target_path: object.target_path.clone(),
|
||||
base_path: object.base_path.clone(),
|
||||
target_path,
|
||||
base_path,
|
||||
reverse_fn_order: object.reverse_fn_order(),
|
||||
complete: object.complete(),
|
||||
hidden: object.hidden(),
|
||||
scratch: object.scratch.clone(),
|
||||
source_path: object.source_path().cloned(),
|
||||
source_path,
|
||||
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
@ -120,7 +154,7 @@ fn bool_true() -> bool { true }
|
||||
|
||||
pub struct AppState {
|
||||
pub config: AppConfig,
|
||||
pub objects: Vec<ProjectObject>,
|
||||
pub objects: Vec<ObjectConfig>,
|
||||
pub object_nodes: Vec<ProjectObjectNode>,
|
||||
pub watcher_change: bool,
|
||||
pub config_change: bool,
|
||||
@ -170,12 +204,12 @@ pub struct AppConfig {
|
||||
pub custom_args: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub target_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub base_obj_dir: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfig>,
|
||||
#[serde(default = "bool_true")]
|
||||
@ -189,7 +223,7 @@ pub struct AppConfig {
|
||||
#[serde(default = "default_watch_patterns")]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
pub recent_projects: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
}
|
||||
@ -217,12 +251,12 @@ impl Default for AppConfig {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn set_project_dir(&mut self, path: PathBuf) {
|
||||
pub fn set_project_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||
self.config.recent_projects.retain(|p| p != &path);
|
||||
if self.config.recent_projects.len() > 9 {
|
||||
self.config.recent_projects.truncate(9);
|
||||
}
|
||||
self.config.recent_projects.insert(0, path.clone());
|
||||
self.config.recent_projects.insert(0, path.to_string());
|
||||
self.config.project_dir = Some(path);
|
||||
self.config.target_obj_dir = None;
|
||||
self.config.base_obj_dir = None;
|
||||
@ -240,7 +274,7 @@ impl AppState {
|
||||
self.selecting_right = None;
|
||||
}
|
||||
|
||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
||||
pub fn set_target_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||
self.config.target_obj_dir = Some(path);
|
||||
self.config.selected_obj = None;
|
||||
self.obj_change = true;
|
||||
@ -249,7 +283,7 @@ impl AppState {
|
||||
self.selecting_right = None;
|
||||
}
|
||||
|
||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
||||
pub fn set_base_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
|
||||
self.config.base_obj_dir = Some(path);
|
||||
self.config.selected_obj = None;
|
||||
self.obj_change = true;
|
||||
@ -360,14 +394,8 @@ impl AppState {
|
||||
Some(object.symbol_mappings.clone())
|
||||
};
|
||||
}
|
||||
if let Some(existing) =
|
||||
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
||||
{
|
||||
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(object.symbol_mappings.clone())
|
||||
};
|
||||
if let Some(existing) = self.objects.iter_mut().find(|u| u.name == object.name) {
|
||||
existing.symbol_mappings = object.symbol_mappings.clone();
|
||||
}
|
||||
}
|
||||
// Save the updated project config
|
||||
@ -530,8 +558,13 @@ impl App {
|
||||
match build_globset(&state.config.watch_patterns)
|
||||
.map_err(anyhow::Error::new)
|
||||
.and_then(|globset| {
|
||||
create_watcher(self.modified.clone(), project_dir, globset, egui_waker(ctx))
|
||||
.map_err(anyhow::Error::new)
|
||||
create_watcher(
|
||||
self.modified.clone(),
|
||||
project_dir.as_ref(),
|
||||
globset,
|
||||
egui_waker(ctx),
|
||||
)
|
||||
.map_err(anyhow::Error::new)
|
||||
}) {
|
||||
Ok(watcher) => self.watcher = Some(watcher),
|
||||
Err(e) => log::error!("Failed to create watcher: {e}"),
|
||||
@ -672,8 +705,11 @@ impl eframe::App for App {
|
||||
};
|
||||
ui.separator();
|
||||
for path in recent_projects {
|
||||
if ui.button(format!("{}", path.display())).clicked() {
|
||||
state.write().unwrap().set_project_dir(path);
|
||||
if ui.button(&path).clicked() {
|
||||
state
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_project_dir(Utf8PlatformPathBuf::from(path));
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
@ -776,8 +812,8 @@ impl eframe::App for App {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
|
||||
if let Ok(metadata) = fs::metadata(path) {
|
||||
fn file_modified<P: AsRef<Path>>(path: P, last_ts: FileTime) -> bool {
|
||||
if let Ok(metadata) = fs::metadata(path.as_ref()) {
|
||||
FileTime::from_last_modification_time(&metadata) != last_ts
|
||||
} else {
|
||||
false
|
||||
|
@ -1,14 +1,15 @@
|
||||
use std::path::PathBuf;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use eframe::Storage;
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::{ScratchConfig, SymbolMappings},
|
||||
config::ScratchConfig,
|
||||
diff::{
|
||||
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
|
||||
X86Formatter,
|
||||
},
|
||||
};
|
||||
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||
|
||||
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
||||
|
||||
@ -62,7 +63,7 @@ pub struct ScratchConfigV2 {
|
||||
#[serde(default)]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
pub ctx_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub build_ctx: Option<bool>,
|
||||
#[serde(default)]
|
||||
@ -75,7 +76,7 @@ impl ScratchConfigV2 {
|
||||
platform: self.platform,
|
||||
compiler: self.compiler,
|
||||
c_flags: self.c_flags,
|
||||
ctx_path: self.ctx_path,
|
||||
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||
build_ctx: self.build_ctx,
|
||||
preset_id: self.preset_id,
|
||||
}
|
||||
@ -85,26 +86,27 @@ impl ScratchConfigV2 {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV2 {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub target_path: Option<String>,
|
||||
pub base_path: Option<String>,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfigV2>,
|
||||
pub source_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: SymbolMappings,
|
||||
pub symbol_mappings: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl ObjectConfigV2 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: self.target_path,
|
||||
base_path: self.base_path,
|
||||
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
complete: self.complete,
|
||||
hidden: false,
|
||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||
source_path: self.source_path,
|
||||
source_path: None,
|
||||
symbol_mappings: self.symbol_mappings,
|
||||
}
|
||||
}
|
||||
@ -120,11 +122,11 @@ pub struct AppConfigV2 {
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfigV2>,
|
||||
#[serde(default = "bool_true")]
|
||||
@ -138,7 +140,7 @@ pub struct AppConfigV2 {
|
||||
#[serde(default)]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
pub recent_projects: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfigV1,
|
||||
}
|
||||
@ -150,9 +152,9 @@ impl AppConfigV2 {
|
||||
custom_make: self.custom_make,
|
||||
custom_args: self.custom_args,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_base: self.build_base,
|
||||
build_target: self.build_target,
|
||||
@ -175,7 +177,7 @@ pub struct ScratchConfigV1 {
|
||||
#[serde(default)]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
pub ctx_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub build_ctx: bool,
|
||||
}
|
||||
@ -186,7 +188,7 @@ impl ScratchConfigV1 {
|
||||
platform: self.platform,
|
||||
compiler: self.compiler,
|
||||
c_flags: self.c_flags,
|
||||
ctx_path: self.ctx_path,
|
||||
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
|
||||
build_ctx: self.build_ctx.then_some(true),
|
||||
preset_id: None,
|
||||
}
|
||||
@ -196,8 +198,8 @@ impl ScratchConfigV1 {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV1 {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub target_path: Option<String>,
|
||||
pub base_path: Option<String>,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfigV1>,
|
||||
@ -208,12 +210,12 @@ impl ObjectConfigV1 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: self.target_path,
|
||||
base_path: self.base_path,
|
||||
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
|
||||
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
complete: self.complete,
|
||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||
source_path: self.source_path,
|
||||
source_path: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@ -298,11 +300,11 @@ pub struct AppConfigV1 {
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<String>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfigV1>,
|
||||
#[serde(default = "bool_true")]
|
||||
@ -316,7 +318,7 @@ pub struct AppConfigV1 {
|
||||
#[serde(default)]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
pub recent_projects: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfigV1,
|
||||
}
|
||||
@ -328,9 +330,9 @@ impl AppConfigV1 {
|
||||
custom_make: self.custom_make,
|
||||
custom_args: self.custom_args,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_base: self.build_base,
|
||||
build_target: self.build_target,
|
||||
@ -347,8 +349,8 @@ impl AppConfigV1 {
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV0 {
|
||||
pub name: String,
|
||||
pub target_path: PathBuf,
|
||||
pub base_path: PathBuf,
|
||||
pub target_path: String,
|
||||
pub base_path: String,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
}
|
||||
|
||||
@ -356,8 +358,8 @@ impl ObjectConfigV0 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: Some(self.target_path),
|
||||
base_path: Some(self.base_path),
|
||||
target_path: Some(Utf8PlatformPathBuf::from(self.target_path)),
|
||||
base_path: Some(Utf8PlatformPathBuf::from(self.base_path)),
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
..Default::default()
|
||||
}
|
||||
@ -368,9 +370,9 @@ impl ObjectConfigV0 {
|
||||
pub struct AppConfigV0 {
|
||||
pub custom_make: Option<String>,
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<String>,
|
||||
pub target_obj_dir: Option<String>,
|
||||
pub base_obj_dir: Option<String>,
|
||||
pub selected_obj: Option<ObjectConfigV0>,
|
||||
pub build_target: bool,
|
||||
pub auto_update_check: bool,
|
||||
@ -383,9 +385,9 @@ impl AppConfigV0 {
|
||||
AppConfig {
|
||||
custom_make: self.custom_make,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
|
||||
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_target: self.build_target,
|
||||
auto_update_check: self.auto_update_check,
|
||||
|
@ -1,8 +1,7 @@
|
||||
use std::path::{Component, Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use globset::Glob;
|
||||
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
|
||||
use objdiff_core::config::{try_project_config, DEFAULT_WATCH_PATTERNS};
|
||||
use typed_path::{Utf8UnixComponent, Utf8UnixPath};
|
||||
|
||||
use crate::app::{AppState, ObjectConfig};
|
||||
|
||||
@ -47,32 +46,19 @@ fn find_dir<'a>(
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn build_nodes(
|
||||
units: &mut [ProjectObject],
|
||||
project_dir: &Path,
|
||||
target_obj_dir: Option<&Path>,
|
||||
base_obj_dir: Option<&Path>,
|
||||
) -> Vec<ProjectObjectNode> {
|
||||
fn build_nodes(units: &mut [ObjectConfig]) -> Vec<ProjectObjectNode> {
|
||||
let mut nodes = vec![];
|
||||
for (idx, unit) in units.iter_mut().enumerate() {
|
||||
unit.resolve_paths(project_dir, target_obj_dir, base_obj_dir);
|
||||
let mut out_nodes = &mut nodes;
|
||||
let path = if let Some(name) = &unit.name {
|
||||
Path::new(name)
|
||||
} else if let Some(path) = &unit.path {
|
||||
path
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let path = Utf8UnixPath::new(&unit.name);
|
||||
if let Some(parent) = path.parent() {
|
||||
for component in parent.components() {
|
||||
if let Component::Normal(name) = component {
|
||||
let name = name.to_str().unwrap();
|
||||
if let Utf8UnixComponent::Normal(name) = component {
|
||||
out_nodes = find_dir(name, out_nodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
||||
let filename = path.file_name().unwrap().to_string();
|
||||
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
|
||||
}
|
||||
// Within the top-level module directories, join paths. Leave the
|
||||
@ -90,13 +76,18 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
||||
let Some(project_dir) = &state.config.project_dir else {
|
||||
return Ok(());
|
||||
};
|
||||
if let Some((result, info)) = try_project_config(project_dir) {
|
||||
if let Some((result, info)) = try_project_config(project_dir.as_ref()) {
|
||||
let project_config = result?;
|
||||
state.config.custom_make = project_config.custom_make.clone();
|
||||
state.config.custom_args = project_config.custom_args.clone();
|
||||
state.config.target_obj_dir =
|
||||
project_config.target_dir.as_deref().map(|p| project_dir.join(p));
|
||||
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
|
||||
state.config.target_obj_dir = project_config
|
||||
.target_dir
|
||||
.as_deref()
|
||||
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
state.config.base_obj_dir = project_config
|
||||
.base_dir
|
||||
.as_deref()
|
||||
.map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
state.config.build_base = project_config.build_base.unwrap_or(true);
|
||||
state.config.build_target = project_config.build_target.unwrap_or(false);
|
||||
if let Some(watch_patterns) = &project_config.watch_patterns {
|
||||
@ -109,21 +100,28 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
||||
}
|
||||
state.watcher_change = true;
|
||||
state.objects = project_config.units.clone().unwrap_or_default();
|
||||
state.object_nodes = build_nodes(
|
||||
&mut state.objects,
|
||||
project_dir,
|
||||
state.config.target_obj_dir.as_deref(),
|
||||
state.config.base_obj_dir.as_deref(),
|
||||
);
|
||||
state.objects = project_config
|
||||
.units
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|o| {
|
||||
ObjectConfig::new(
|
||||
o,
|
||||
project_dir,
|
||||
state.config.target_obj_dir.as_deref(),
|
||||
state.config.base_obj_dir.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
state.object_nodes = build_nodes(&mut state.objects);
|
||||
state.current_project_config = Some(project_config);
|
||||
state.project_config_info = Some(info);
|
||||
|
||||
// Reload selected object
|
||||
if let Some(selected_obj) = &state.config.selected_obj {
|
||||
if let Some(obj) = state.objects.iter().find(|o| o.name() == selected_obj.name) {
|
||||
let config = ObjectConfig::from(obj);
|
||||
state.set_selected_obj(config);
|
||||
if let Some(obj) = state.objects.iter().find(|o| o.name == selected_obj.name) {
|
||||
state.set_selected_obj(obj.clone());
|
||||
} else {
|
||||
state.clear_selected_obj();
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ fn create_scratch_config(
|
||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||
function_name,
|
||||
target_obj: target_path.to_path_buf(),
|
||||
target_obj: target_path.clone(),
|
||||
preset_id: scratch_config.preset_id,
|
||||
})
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
#[cfg(all(windows, feature = "wsl"))]
|
||||
use std::string::FromUtf16Error;
|
||||
use std::{
|
||||
mem::take,
|
||||
path::{Path, PathBuf, MAIN_SEPARATOR},
|
||||
};
|
||||
use std::{mem::take, path::MAIN_SEPARATOR};
|
||||
|
||||
#[cfg(all(windows, feature = "wsl"))]
|
||||
use anyhow::{Context, Result};
|
||||
@ -13,13 +10,14 @@ use egui::{
|
||||
};
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
||||
config::{path::check_path_buf, DEFAULT_WATCH_PATTERNS},
|
||||
diff::{
|
||||
ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
||||
ConfigPropertyValue, CONFIG_GROUPS,
|
||||
},
|
||||
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
||||
};
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||
@ -89,7 +87,7 @@ impl ConfigViewState {
|
||||
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
||||
let target_path = target_dir.join(obj_path);
|
||||
guard.set_selected_obj(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
name: obj_path.to_string(),
|
||||
target_path: Some(target_path),
|
||||
base_path: Some(path),
|
||||
..Default::default()
|
||||
@ -97,7 +95,7 @@ impl ConfigViewState {
|
||||
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
||||
let base_path = base_dir.join(obj_path);
|
||||
guard.set_selected_obj(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
name: obj_path.to_string(),
|
||||
target_path: Some(path),
|
||||
base_path: Some(base_path),
|
||||
..Default::default()
|
||||
@ -169,10 +167,7 @@ pub fn config_ui(
|
||||
) {
|
||||
let mut state_guard = state.write().unwrap();
|
||||
let AppState {
|
||||
config:
|
||||
AppConfig {
|
||||
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
|
||||
},
|
||||
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
|
||||
objects,
|
||||
object_nodes,
|
||||
..
|
||||
@ -223,9 +218,9 @@ pub fn config_ui(
|
||||
}
|
||||
});
|
||||
|
||||
let selected_index = selected_obj.as_ref().and_then(|selected_obj| {
|
||||
objects.iter().position(|obj| obj.name.as_ref() == Some(&selected_obj.name))
|
||||
});
|
||||
let selected_index = selected_obj
|
||||
.as_ref()
|
||||
.and_then(|selected_obj| objects.iter().position(|obj| obj.name == selected_obj.name));
|
||||
let mut new_selected_index = selected_index;
|
||||
if objects.is_empty() {
|
||||
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||
@ -324,22 +319,14 @@ pub fn config_ui(
|
||||
config_state.show_hidden,
|
||||
)
|
||||
}) {
|
||||
display_node(
|
||||
ui,
|
||||
&mut new_selected_index,
|
||||
project_dir.as_deref(),
|
||||
objects,
|
||||
&node,
|
||||
appearance,
|
||||
node_open,
|
||||
);
|
||||
display_node(ui, &mut new_selected_index, objects, &node, appearance, node_open);
|
||||
}
|
||||
});
|
||||
}
|
||||
if new_selected_index != selected_index {
|
||||
if let Some(idx) = new_selected_index {
|
||||
// Will set obj_changed, which will trigger a rebuild
|
||||
let config = ObjectConfig::from(&objects[idx]);
|
||||
let config = objects[idx].clone();
|
||||
state_guard.set_selected_obj(config);
|
||||
}
|
||||
}
|
||||
@ -353,9 +340,8 @@ pub fn config_ui(
|
||||
fn display_unit(
|
||||
ui: &mut egui::Ui,
|
||||
selected_obj: &mut Option<usize>,
|
||||
project_dir: Option<&Path>,
|
||||
name: &str,
|
||||
units: &[ProjectObject],
|
||||
units: &[ObjectConfig],
|
||||
index: usize,
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
@ -363,7 +349,7 @@ fn display_unit(
|
||||
let selected = *selected_obj == Some(index);
|
||||
let color = if selected {
|
||||
appearance.emphasized_text_color
|
||||
} else if let Some(complete) = object.complete() {
|
||||
} else if let Some(complete) = object.complete {
|
||||
if complete {
|
||||
appearance.insert_color
|
||||
} else {
|
||||
@ -382,26 +368,22 @@ fn display_unit(
|
||||
.color(color),
|
||||
)
|
||||
.ui(ui);
|
||||
if get_source_path(project_dir, object).is_some() {
|
||||
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
|
||||
if object.source_path.is_some() {
|
||||
response.context_menu(|ui| object_context_ui(ui, object));
|
||||
}
|
||||
if response.clicked() {
|
||||
*selected_obj = Some(index);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_source_path(project_dir: Option<&Path>, object: &ProjectObject) -> Option<PathBuf> {
|
||||
project_dir.and_then(|dir| object.source_path().map(|path| dir.join(path)))
|
||||
}
|
||||
|
||||
fn object_context_ui(ui: &mut egui::Ui, object: &ProjectObject, project_dir: Option<&Path>) {
|
||||
if let Some(source_path) = get_source_path(project_dir, object) {
|
||||
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
|
||||
if let Some(source_path) = &object.source_path {
|
||||
if ui
|
||||
.button("Open source file")
|
||||
.on_hover_text("Open the source file in the default editor")
|
||||
.clicked()
|
||||
{
|
||||
log::info!("Opening file {}", source_path.display());
|
||||
log::info!("Opening file {}", source_path);
|
||||
if let Err(e) = open::that_detached(&source_path) {
|
||||
log::error!("Failed to open source file: {e}");
|
||||
}
|
||||
@ -422,15 +404,14 @@ enum NodeOpen {
|
||||
fn display_node(
|
||||
ui: &mut egui::Ui,
|
||||
selected_obj: &mut Option<usize>,
|
||||
project_dir: Option<&Path>,
|
||||
units: &[ProjectObject],
|
||||
units: &[ObjectConfig],
|
||||
node: &ProjectObjectNode,
|
||||
appearance: &Appearance,
|
||||
node_open: NodeOpen,
|
||||
) {
|
||||
match node {
|
||||
ProjectObjectNode::Unit(name, idx) => {
|
||||
display_unit(ui, selected_obj, project_dir, name, units, *idx, appearance);
|
||||
display_unit(ui, selected_obj, name, units, *idx, appearance);
|
||||
}
|
||||
ProjectObjectNode::Dir(name, children) => {
|
||||
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
|
||||
@ -456,7 +437,7 @@ fn display_node(
|
||||
.open(open)
|
||||
.show(ui, |ui| {
|
||||
for node in children {
|
||||
display_node(ui, selected_obj, project_dir, units, node, appearance, node_open);
|
||||
display_node(ui, selected_obj, units, node, appearance, node_open);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -473,7 +454,7 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: usize) -> bool {
|
||||
}
|
||||
|
||||
fn filter_node(
|
||||
units: &[ProjectObject],
|
||||
units: &[ObjectConfig],
|
||||
node: &ProjectObjectNode,
|
||||
search: &str,
|
||||
filter_diffable: bool,
|
||||
@ -485,8 +466,8 @@ fn filter_node(
|
||||
let unit = &units[*idx];
|
||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
|
||||
&& (!filter_incomplete || matches!(unit.complete(), None | Some(false)))
|
||||
&& (show_hidden || !unit.hidden())
|
||||
&& (!filter_incomplete || matches!(unit.complete, None | Some(false)))
|
||||
&& (show_hidden || !unit.hidden)
|
||||
{
|
||||
Some(node.clone())
|
||||
} else {
|
||||
@ -524,13 +505,16 @@ fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) {
|
||||
);
|
||||
}
|
||||
|
||||
fn format_path(path: &Option<PathBuf>, appearance: &Appearance) -> RichText {
|
||||
fn format_path(path: &Option<Utf8PlatformPathBuf>, appearance: &Appearance) -> RichText {
|
||||
let mut color = appearance.replace_color;
|
||||
let text = if let Some(dir) = path {
|
||||
if let Some(rel) = dirs::home_dir().and_then(|home| dir.strip_prefix(&home).ok()) {
|
||||
format!("~{}{}", MAIN_SEPARATOR, rel.display())
|
||||
if let Some(rel) = dirs::home_dir()
|
||||
.and_then(|home| check_path_buf(home).ok())
|
||||
.and_then(|home| dir.strip_prefix(&home).ok())
|
||||
{
|
||||
format!("~{}{}", MAIN_SEPARATOR, rel)
|
||||
} else {
|
||||
format!("{}", dir.display())
|
||||
dir.to_string()
|
||||
}
|
||||
} else {
|
||||
color = appearance.delete_color;
|
||||
@ -544,7 +528,7 @@ pub const CONFIG_DISABLED_TEXT: &str =
|
||||
|
||||
fn pick_folder_ui(
|
||||
ui: &mut egui::Ui,
|
||||
dir: &Option<PathBuf>,
|
||||
dir: &Option<Utf8PlatformPathBuf>,
|
||||
label: &str,
|
||||
tooltip: impl FnOnce(&mut egui::Ui),
|
||||
appearance: &Appearance,
|
||||
|
@ -1,16 +1,18 @@
|
||||
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
||||
|
||||
use objdiff_core::config::path::check_path_buf;
|
||||
use pollster::FutureExt;
|
||||
use rfd::FileHandle;
|
||||
use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum FileDialogResult {
|
||||
#[default]
|
||||
None,
|
||||
ProjectDir(PathBuf),
|
||||
TargetDir(PathBuf),
|
||||
BaseDir(PathBuf),
|
||||
Object(PathBuf),
|
||||
ProjectDir(Utf8PlatformPathBuf),
|
||||
TargetDir(Utf8PlatformPathBuf),
|
||||
BaseDir(Utf8PlatformPathBuf),
|
||||
Object(Utf8PlatformPathBuf),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -22,7 +24,7 @@ impl FileDialogState {
|
||||
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
||||
where
|
||||
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
||||
ResultCb: FnOnce(PathBuf) -> FileDialogResult + Send + 'static,
|
||||
ResultCb: FnOnce(Utf8PlatformPathBuf) -> FileDialogResult + Send + 'static,
|
||||
{
|
||||
if self.thread.is_some() {
|
||||
return;
|
||||
@ -30,7 +32,8 @@ impl FileDialogState {
|
||||
let future = init();
|
||||
self.thread = Some(std::thread::spawn(move || {
|
||||
if let Some(handle) = future.block_on() {
|
||||
result_cb(PathBuf::from(handle))
|
||||
let path = PathBuf::from(handle);
|
||||
check_path_buf(path).map(result_cb).unwrap_or(FileDialogResult::None)
|
||||
} else {
|
||||
FileDialogResult::None
|
||||
}
|
||||
|
@ -280,12 +280,10 @@ impl DiffViewState {
|
||||
let Ok(state) = state.read() else {
|
||||
return;
|
||||
};
|
||||
if let (Some(project_dir), Some(source_path)) = (
|
||||
&state.config.project_dir,
|
||||
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref()),
|
||||
) {
|
||||
let source_path = project_dir.join(source_path);
|
||||
log::info!("Opening file {}", source_path.display());
|
||||
if let Some(source_path) =
|
||||
state.config.selected_obj.as_ref().and_then(|obj| obj.source_path.as_ref())
|
||||
{
|
||||
log::info!("Opening file {}", source_path);
|
||||
open::that_detached(source_path).unwrap_or_else(|err| {
|
||||
log::error!("Failed to open source file: {err}");
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "2.6.0",
|
||||
"version": "2.7.1",
|
||||
"description": "A local diffing tool for decompilation projects.",
|
||||
"author": {
|
||||
"name": "Luke Street",
|
||||
|
107
objdiff-wasm/src/display.ts
Normal file
107
objdiff-wasm/src/display.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import {ArgumentValue, InstructionDiff, RelocationTarget} from "../gen/diff_pb";
|
||||
|
||||
export type DiffText =
|
||||
DiffTextBasic
|
||||
| DiffTextBasicColor
|
||||
| DiffTextAddress
|
||||
| DiffTextLine
|
||||
| DiffTextOpcode
|
||||
| DiffTextArgument
|
||||
| DiffTextSymbol
|
||||
| DiffTextBranchDest
|
||||
| DiffTextSpacing;
|
||||
|
||||
type DiffTextBase = {
|
||||
diff_index?: number,
|
||||
};
|
||||
export type DiffTextBasic = DiffTextBase & {
|
||||
type: 'basic',
|
||||
text: string,
|
||||
};
|
||||
export type DiffTextBasicColor = DiffTextBase & {
|
||||
type: 'basic_color',
|
||||
text: string,
|
||||
index: number,
|
||||
};
|
||||
export type DiffTextAddress = DiffTextBase & {
|
||||
type: 'address',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextLine = DiffTextBase & {
|
||||
type: 'line',
|
||||
line_number: number,
|
||||
};
|
||||
export type DiffTextOpcode = DiffTextBase & {
|
||||
type: 'opcode',
|
||||
mnemonic: string,
|
||||
opcode: number,
|
||||
};
|
||||
export type DiffTextArgument = DiffTextBase & {
|
||||
type: 'argument',
|
||||
value: ArgumentValue,
|
||||
};
|
||||
export type DiffTextSymbol = DiffTextBase & {
|
||||
type: 'symbol',
|
||||
target: RelocationTarget,
|
||||
};
|
||||
export type DiffTextBranchDest = DiffTextBase & {
|
||||
type: 'branch_dest',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextSpacing = DiffTextBase & {
|
||||
type: 'spacing',
|
||||
count: number,
|
||||
};
|
||||
|
||||
// Native JavaScript implementation of objdiff_core::diff::display::display_diff
|
||||
export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) {
|
||||
const ins = diff.instruction;
|
||||
if (!ins) {
|
||||
return;
|
||||
}
|
||||
if (ins.line_number != null) {
|
||||
cb({type: 'line', line_number: ins.line_number});
|
||||
}
|
||||
cb({type: 'address', address: ins.address - baseAddr});
|
||||
if (diff.branch_from) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_from.branch_index});
|
||||
} else {
|
||||
cb({type: 'spacing', count: 4});
|
||||
}
|
||||
cb({type: 'opcode', mnemonic: ins.mnemonic, opcode: ins.opcode});
|
||||
let arg_diff_idx = 0; // non-PlainText argument index
|
||||
for (let i = 0; i < ins.arguments.length; i++) {
|
||||
if (i === 0) {
|
||||
cb({type: 'spacing', count: 1});
|
||||
}
|
||||
const arg = ins.arguments[i].value;
|
||||
let diff_index: number | undefined;
|
||||
if (arg.oneofKind !== 'plain_text') {
|
||||
diff_index = diff.arg_diff[arg_diff_idx]?.diff_index;
|
||||
arg_diff_idx++;
|
||||
}
|
||||
switch (arg.oneofKind) {
|
||||
case "plain_text":
|
||||
cb({type: 'basic', text: arg.plain_text, diff_index});
|
||||
break;
|
||||
case "argument":
|
||||
cb({type: 'argument', value: arg.argument, diff_index});
|
||||
break;
|
||||
case "relocation": {
|
||||
const reloc = ins.relocation!;
|
||||
cb({type: 'symbol', target: reloc.target!, diff_index});
|
||||
break;
|
||||
}
|
||||
case "branch_dest":
|
||||
if (arg.branch_dest < baseAddr) {
|
||||
cb({type: 'basic', text: '<unknown>', diff_index});
|
||||
} else {
|
||||
cb({type: 'branch_dest', address: arg.branch_dest - baseAddr, diff_index});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diff.branch_to) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_to.branch_index});
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
import {ArgumentValue, DiffResult, InstructionDiff, RelocationTarget} from "../gen/diff_pb";
|
||||
import {DiffResult} from "../gen/diff_pb";
|
||||
import type {
|
||||
ArmArchVersion,
|
||||
ArmR9Usage,
|
||||
DiffObjConfig,
|
||||
MipsAbi,
|
||||
MipsInstrCategory,
|
||||
X86Formatter
|
||||
ConfigProperty,
|
||||
MappingConfig,
|
||||
SymbolMappings,
|
||||
} from '../pkg';
|
||||
import {AnyHandlerData, InMessage, OutMessage} from './worker';
|
||||
|
||||
// Export wasm types
|
||||
export {ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter, DiffObjConfig};
|
||||
export {ConfigProperty, MappingConfig, SymbolMappings};
|
||||
|
||||
// Export protobuf types
|
||||
export * from '../gen/diff_pb';
|
||||
|
||||
// Export display types
|
||||
export * from './display';
|
||||
|
||||
interface PromiseCallbacks<T> {
|
||||
start: number;
|
||||
resolve: (value: T | PromiseLike<T>) => void;
|
||||
@ -111,12 +111,18 @@ async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
|
||||
return promise;
|
||||
}
|
||||
|
||||
export async function runDiff(left: Uint8Array | undefined, right: Uint8Array | undefined, diff_config?: DiffObjConfig): Promise<DiffResult> {
|
||||
export async function runDiff(
|
||||
left: Uint8Array | null | undefined,
|
||||
right: Uint8Array | null | undefined,
|
||||
properties?: ConfigProperty[],
|
||||
mappingConfig?: MappingConfig,
|
||||
): Promise<DiffResult> {
|
||||
const data = await defer<Uint8Array>({
|
||||
type: 'run_diff_proto',
|
||||
left,
|
||||
right,
|
||||
diff_config
|
||||
properties,
|
||||
mappingConfig,
|
||||
});
|
||||
const parseStart = performance.now();
|
||||
const result = DiffResult.fromBinary(data, {readUnknownField: false});
|
||||
@ -124,109 +130,3 @@ export async function runDiff(left: Uint8Array | undefined, right: Uint8Array |
|
||||
console.debug(`Parsing message took ${end - parseStart}ms`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export type DiffText =
|
||||
DiffTextBasic
|
||||
| DiffTextBasicColor
|
||||
| DiffTextAddress
|
||||
| DiffTextLine
|
||||
| DiffTextOpcode
|
||||
| DiffTextArgument
|
||||
| DiffTextSymbol
|
||||
| DiffTextBranchDest
|
||||
| DiffTextSpacing;
|
||||
|
||||
type DiffTextBase = {
|
||||
diff_index?: number,
|
||||
};
|
||||
export type DiffTextBasic = DiffTextBase & {
|
||||
type: 'basic',
|
||||
text: string,
|
||||
};
|
||||
export type DiffTextBasicColor = DiffTextBase & {
|
||||
type: 'basic_color',
|
||||
text: string,
|
||||
index: number,
|
||||
};
|
||||
export type DiffTextAddress = DiffTextBase & {
|
||||
type: 'address',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextLine = DiffTextBase & {
|
||||
type: 'line',
|
||||
line_number: number,
|
||||
};
|
||||
export type DiffTextOpcode = DiffTextBase & {
|
||||
type: 'opcode',
|
||||
mnemonic: string,
|
||||
opcode: number,
|
||||
};
|
||||
export type DiffTextArgument = DiffTextBase & {
|
||||
type: 'argument',
|
||||
value: ArgumentValue,
|
||||
};
|
||||
export type DiffTextSymbol = DiffTextBase & {
|
||||
type: 'symbol',
|
||||
target: RelocationTarget,
|
||||
};
|
||||
export type DiffTextBranchDest = DiffTextBase & {
|
||||
type: 'branch_dest',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextSpacing = DiffTextBase & {
|
||||
type: 'spacing',
|
||||
count: number,
|
||||
};
|
||||
|
||||
// Native JavaScript implementation of objdiff_core::diff::display::display_diff
|
||||
export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) {
|
||||
const ins = diff.instruction;
|
||||
if (!ins) {
|
||||
return;
|
||||
}
|
||||
if (ins.line_number != null) {
|
||||
cb({type: 'line', line_number: ins.line_number});
|
||||
}
|
||||
cb({type: 'address', address: ins.address - baseAddr});
|
||||
if (diff.branch_from) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_from.branch_index});
|
||||
} else {
|
||||
cb({type: 'spacing', count: 4});
|
||||
}
|
||||
cb({type: 'opcode', mnemonic: ins.mnemonic, opcode: ins.opcode});
|
||||
let arg_diff_idx = 0; // non-PlainText argument index
|
||||
for (let i = 0; i < ins.arguments.length; i++) {
|
||||
if (i === 0) {
|
||||
cb({type: 'spacing', count: 1});
|
||||
}
|
||||
const arg = ins.arguments[i].value;
|
||||
let diff_index: number | undefined;
|
||||
if (arg.oneofKind !== 'plain_text') {
|
||||
diff_index = diff.arg_diff[arg_diff_idx]?.diff_index;
|
||||
arg_diff_idx++;
|
||||
}
|
||||
switch (arg.oneofKind) {
|
||||
case "plain_text":
|
||||
cb({type: 'basic', text: arg.plain_text, diff_index});
|
||||
break;
|
||||
case "argument":
|
||||
cb({type: 'argument', value: arg.argument, diff_index});
|
||||
break;
|
||||
case "relocation": {
|
||||
const reloc = ins.relocation!;
|
||||
cb({type: 'symbol', target: reloc.target!, diff_index});
|
||||
break;
|
||||
}
|
||||
case "branch_dest":
|
||||
if (arg.branch_dest < baseAddr) {
|
||||
cb({type: 'basic', text: '<unknown>', diff_index});
|
||||
} else {
|
||||
cb({type: 'branch_dest', address: arg.branch_dest - baseAddr, diff_index});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diff.branch_to) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_to.branch_index});
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import wasmInit, * as exports from '../pkg';
|
||||
|
||||
const handlers = {
|
||||
init: init,
|
||||
// run_diff_json: run_diff_json,
|
||||
run_diff_proto: run_diff_proto,
|
||||
} as const;
|
||||
type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
|
||||
@ -29,24 +28,16 @@ async function initIfNeeded() {
|
||||
return wasmReady;
|
||||
}
|
||||
|
||||
// async function run_diff_json({left, right, config}: {
|
||||
// left: Uint8Array | undefined,
|
||||
// right: Uint8Array | undefined,
|
||||
// config?: exports.DiffObjConfig,
|
||||
// }): Promise<string> {
|
||||
// config = config || exports.default_diff_obj_config();
|
||||
// return exports.run_diff_json(left, right, cfg);
|
||||
// }
|
||||
|
||||
async function run_diff_proto({left, right, diff_config, mapping_config}: {
|
||||
left: Uint8Array | undefined,
|
||||
right: Uint8Array | undefined,
|
||||
diff_config?: exports.DiffObjConfig,
|
||||
mapping_config?: exports.MappingConfig,
|
||||
async function run_diff_proto({left, right, properties, mappingConfig}: {
|
||||
left: Uint8Array | null | undefined,
|
||||
right: Uint8Array | null | undefined,
|
||||
properties?: exports.ConfigProperty[],
|
||||
mappingConfig?: exports.MappingConfig,
|
||||
}): Promise<Uint8Array> {
|
||||
diff_config = diff_config || {};
|
||||
mapping_config = mapping_config || {};
|
||||
return exports.run_diff_proto(left, right, diff_config, mapping_config);
|
||||
const diffConfig = exports.config_from_properties(properties || []);
|
||||
const leftObj = left ? exports.parse_object(left, diffConfig) : null;
|
||||
const rightObj = right ? exports.parse_object(right, diffConfig) : null;
|
||||
return exports.run_diff(leftObj, rightObj, diffConfig, mappingConfig || {});
|
||||
}
|
||||
|
||||
export type AnyHandlerData = HandlerData[keyof HandlerData];
|
||||
|
@ -28,6 +28,7 @@ export default defineConfig([
|
||||
// https://github.com/egoist/tsup/issues/278
|
||||
async onSuccess() {
|
||||
await fs.copyFile('pkg/objdiff_core_bg.wasm', 'dist/objdiff_core_bg.wasm');
|
||||
await fs.copyFile('../objdiff-core/config-schema.json', 'dist/config-schema.json');
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user