mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
334 Commits
v2.0.0-beta.6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bcac60df8 | |||
| b34108fa24 | |||
| 5f9e9063f8 | |||
| a3e11149f7 | |||
| d22c785610 | |||
| 5035f8ec36 | |||
| b8f401b0ba | |||
| 282783c081 | |||
| f5ae76e64d | |||
| dbabdbff11 | |||
| 2fdba8842e | |||
| 0fe4fadbc3 | |||
| 04be189e51 | |||
| 4d0a273d65 | |||
| 73917c12a8 | |||
| 2cd1e2e0ce | |||
| a6ddc506f8 | |||
| b8d7b3bc1a | |||
| 02daae417c | |||
| 8b1b4a9a79 | |||
| 13f1267eac | |||
| 3cebee6766 | |||
| ab0538e9a4 | |||
| b643265809 | |||
| 7a65e07e9b | |||
| 3c4092b965 | |||
| 0f599bea2b | |||
| 9927fc35b6 | |||
| 16fb6a491f | |||
| 2541aef71a | |||
| 7ad1f95bf7 | |||
| 66c879a95d | |||
| d53c358d2f | |||
| f24c017545 | |||
| 3b8603ab22 | |||
| 86b7d6b229 | |||
| 3f82781808 | |||
| 926dd06ce9 | |||
| de70b6fca0 | |||
| 79542a4725 | |||
| 0ebd2ae459 | |||
| 08ebea8bd2 | |||
| 46b8890159 | |||
| 8356fe670d | |||
| 7834185223 | |||
| 216dd70d5b | |||
| 976e629f08 | |||
| 67b28b7da1 | |||
| c0b9bfae21 | |||
| f3f4b0482a | |||
| 278683f103 | |||
| 6ad60de32a | |||
| 90c93164ab | |||
| c02eb31dbb | |||
| 86d92866aa | |||
| 51c3af2bbe | |||
| d0b8b449d9 | |||
| 32f5f202f7 | |||
| 481dbc185a | |||
| 26a4cc79cf | |||
| 5c96c2ee51 | |||
| d162fe847e | |||
| 2a24eb5aec | |||
| f7c291bd55 | |||
| 03a578c1bb | |||
| d09ef8e207 | |||
| 63552a58ae | |||
| 827f4a42bd | |||
| b2dcecc5d8 | |||
| 67b237eab6 | |||
| 66da80ff69 | |||
| 0a85f540f2 | |||
| d79b5b1233 | |||
| 8f5519cb6a | |||
| 19ec08be9a | |||
| 2ad0898efa | |||
| 03f2bcb8b1 | |||
| 781071761a | |||
| 572afa8551 | |||
| 90e81fad7e | |||
| 7a8efb4c88 | |||
| 56dac46280 | |||
| 1866158092 | |||
| fe8e7029f6 | |||
| e2c70342c9 | |||
| e6035b00df | |||
| 97bcfe23d4 | |||
| d684b622b5 | |||
| c698750068 | |||
| a06d3455ae | |||
| fbdaa89cc0 | |||
| f7cb494a62 | |||
| 7cc6ed2b43 | |||
| 532b684682 | |||
| fb1d434bbc | |||
| 23009bf9a3 | |||
| 6fb4bb8855 | |||
| a138dfa907 | |||
| 0b95613768 | |||
| 5d4b33a500 | |||
| f2a591356e | |||
| 8d24ec6373 | |||
| 58430d947b | |||
| 1533125f9d | |||
| 5654060dc8 | |||
| 84079c3934 | |||
| 48804dc2e3 | |||
| 8cfa8b7dab | |||
| 93a4d7e55d | |||
| 7c424a7966 | |||
| 678210d58a | |||
| 4302821615 | |||
| c4b4244b59 | |||
| 52c138bf06 | |||
| 813c8aa539 | |||
| 0f0aaab795 | |||
| b21892be31 | |||
| 247d6da94b | |||
| bd95faa9c3 | |||
| 2c57e4960f | |||
| cff4be2979 | |||
| e1da90943c | |||
| b5713db333 | |||
| 4c3f5e9836 | |||
| e9ce02feb0 | |||
| 9a378d85ed | |||
| c8ff45f2c8 | |||
| 34e4513c69 | |||
| a015971c20 | |||
| e67d5998b3 | |||
| 91bc23edfc | |||
| c9c3b32376 | |||
| 0dc123b064 | |||
| 1e62d4664c | |||
| 1205e8ceb4 | |||
| c917cad5f0 | |||
| dd653329f5 | |||
| f5d3d5f10a | |||
| c327ed3ea8 | |||
| 85fb18a21a | |||
| 00ad0d8094 | |||
| 8fac63c42c | |||
| 0fb7f3901c | |||
| 3385f58341 | |||
| 60b227f45e | |||
| 127ae5ae44 | |||
| 5f48e69775 | |||
| 8756eee07b | |||
| bd3ed0d5ad | |||
| e638d0b17a | |||
| f58616b6dd | |||
| e9762e24c2 | |||
| dab79d96a1 | |||
| a57e5db983 | |||
| d0afd3b83e | |||
| a367af612b | |||
| 22052ea10b | |||
| f7c3501eae | |||
| 07ef93f16a | |||
| 8e8ab6bef8 | |||
| e865f3d598 | |||
| 2b13e9886a | |||
| 1750af736a | |||
| 731b604c24 | |||
| 2d643eb071 | |||
| 0c48d711c7 | |||
| 34220a8e26 | |||
| 0c9e5526d4 | |||
| 3db0727469 | |||
| 8b5bf21f38 | |||
| b77df77000 | |||
| 7e08f9715b | |||
| 3c05852d00 | |||
| a51ff44be1 | |||
| d225cac205 | |||
| 737b3782db | |||
| 1d782243e0 | |||
| a1499f475d | |||
| f263e490e3 | |||
| d0e6c5c057 | |||
| e1c51ac297 | |||
| 39b1b49985 | |||
| 6c7160ab7e | |||
| 644d4762f0 | |||
| b40fae5140 | |||
| fbf85632ab | |||
| 73a89d2768 | |||
| a162c2f840 | |||
| a474b27d55 | |||
| 3d7f2b70dc | |||
| fe886f862d | |||
| 2bcbc34850 | |||
| 9b557e4c8e | |||
| b9ba5796ed | |||
| e101610416 | |||
| 196c003a92 | |||
| 7b00a9e9f2 | |||
| 311de887ec | |||
| 485b259c32 | |||
| d8fdfaa2c0 | |||
| 2612cda1fb | |||
| bc46e17824 | |||
| e735adbd3d | |||
| 6768df9d80 | |||
| eaba2391e0 | |||
| bd8f5ede23 | |||
| acf46c6b54 | |||
| 9358d8ec60 | |||
| 809e2ffddc | |||
| 87fa29e8b0 | |||
| 42d4c38079 | |||
| fa26200ed7 | |||
| a0e7f9bc37 | |||
| 5898d7aebf | |||
| ffb38d1bb0 | |||
| d56dda72f0 | |||
| c6971f3f2d | |||
| 3965a035fa | |||
| f1fc29f77e | |||
| 7c4f1c5d13 | |||
| fa4a6cadbb | |||
| 799971d54e | |||
| 8eef37e8df | |||
| 5f36916087 | |||
| ee667a2dde | |||
| cf5fc54cfa | |||
| 1cdfa1e857 | |||
| 3f157f33a5 | |||
| a1ea2919f8 | |||
| 9c31c82a37 | |||
| 4f34dfa194 | |||
| ae6d37a10b | |||
| 06e54f3456 | |||
| 6ed07bfaf1 | |||
| 80e939653a | |||
| 48305e0380 | |||
| 2eafbb218b | |||
| a1f2a535e5 | |||
| 8461b35cd7 | |||
| 95868f1d19 | |||
| 506c251d68 | |||
| f3c157ff06 | |||
| 6d3c63ccd8 | |||
| bbd8d9714f | |||
| 3c66ac3d54 | |||
| 561a9107e2 | |||
| e8de35b78e | |||
| d938988d43 | |||
| 3e6efb7736 | |||
| 674c942d7d | |||
| 6b7dcabbed | |||
| e202c3ef95 | |||
| b7730b3d00 | |||
| a4fdb61f04 | |||
| 2876be37a3 | |||
| 11171763eb | |||
| 6037a79ba2 | |||
| f7efe5fdff | |||
| 0692deac59 | |||
| c3e3d175c5 | |||
| c45f4bbc99 | |||
| b0c5431ac5 | |||
| 9ab246367b | |||
| dcafe51eda | |||
| c65e87c382 | |||
| 1756b9f6c5 | |||
| 303f2938a2 | |||
| 526e031251 | |||
| 10b2a9c129 | |||
| abe68ef2f2 | |||
| 304df96411 | |||
| 7aa878b48e | |||
| a119d9a6dd | |||
| ebf653816a | |||
| 424434edd6 | |||
| 7f14b684bf | |||
| c5da7f7dd5 | |||
| 2fd655850a | |||
| 79bd7317c1 | |||
| 21f8f2407c | |||
| d2b7a9ef25 | |||
| 2cf9cf24d6 | |||
| 5ef3416457 | |||
| 6ff8d002f7 | |||
| 9ca157d717 | |||
| 67b63311fc | |||
| 72ea1c8911 | |||
| d4a540857d | |||
| 676488433f | |||
| 83de98b5ee | |||
| c1ba4e91d1 | |||
| 575900024d | |||
| cbe299e859 | |||
| 741d93e211 | |||
| 603dbd6882 | |||
| 6fb0a63de2 | |||
| ab2e84a2c6 | |||
| 9596051cb4 | |||
| a5d9d8282e | |||
| 3287a0f65c | |||
| fab9c62dfb | |||
| 08cd768260 | |||
| 8acaaf528c | |||
| 6e881a74e1 | |||
| cc1bc44e69 | |||
| c7b85518ab | |||
| bb039a1445 | |||
| 8fc142d316 | |||
| b0123b3f83 | |||
| 2ec17aee9b | |||
| ec9731e1e5 | |||
| a06382c27e | |||
| e013638c5a | |||
| 70ab82f1f7 | |||
| c5896689cf | |||
| 67719dd93e | |||
| 258e141017 | |||
| dbdda55065 | |||
| a43320af1f | |||
| 35bbd40f5d | |||
| c1cb4b0b19 | |||
| 2379853faa | |||
| 5e1aff180f | |||
| 3846a7d315 | |||
| dcf209aac5 | |||
| c7e6394628 | |||
| 235dc7f517 | |||
| 199c07e975 | |||
| 56a5a61825 | |||
| 3d2236de82 | |||
| bcc5871cd8 | |||
| 7d0d7df54c | |||
| 0221a2d54d | |||
| bc687173c0 |
@@ -0,0 +1,4 @@
|
|||||||
|
# statically link the C runtime so the executable does not depend on
|
||||||
|
# that shared/dynamic library.
|
||||||
|
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
+172
-67
@@ -4,13 +4,17 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '*.md'
|
- "*.md"
|
||||||
- 'LICENSE*'
|
- "LICENSE*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# For npm publish provenance
|
||||||
|
id-token: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BUILD_PROFILE: release-lto
|
BUILD_PROFILE: release-lto
|
||||||
CARGO_TARGET_DIR: target
|
CARGO_INCREMENTAL: 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
@@ -24,38 +28,17 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install libgtk-3-dev
|
sudo apt-get -y install libgtk-3-dev
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Check git tag against Cargo version
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -eou pipefail
|
|
||||||
tag='${{github.ref}}'
|
|
||||||
tag="${tag#refs/tags/}"
|
|
||||||
for file in */Cargo.toml; do
|
|
||||||
version=$(grep '^version' $file | head -1 | awk -F' = ' '{print $2}' | tr -d '"')
|
|
||||||
version="v$version"
|
|
||||||
if [ "$tag" != "$version" ]; then
|
|
||||||
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Setup sccache
|
- name: Cache Rust workspace
|
||||||
uses: mozilla-actions/sccache-action@v0.0.4
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
env:
|
run: cargo check --all-targets --all-features --workspace
|
||||||
RUSTC_WRAPPER: sccache
|
|
||||||
SCCACHE_GHA_ENABLED: "true"
|
|
||||||
run: cargo check
|
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
env:
|
run: cargo clippy --all-targets --all-features --workspace
|
||||||
RUSTC_WRAPPER: sccache
|
|
||||||
SCCACHE_GHA_ENABLED: "true"
|
|
||||||
run: cargo clippy
|
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
@@ -64,7 +47,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
# We use nightly options in rustfmt.toml
|
# We use nightly options in rustfmt.toml
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
@@ -79,19 +62,18 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
checks:
|
checks:
|
||||||
- advisories
|
# - advisories
|
||||||
- bans licenses sources
|
- bans licenses sources
|
||||||
# Prevent new advisories from failing CI
|
# Prevent new advisories from failing CI
|
||||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
with:
|
with:
|
||||||
command: check ${{ matrix.checks }}
|
command: check ${{ matrix.checks }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
if: 'false' # No tests yet
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
@@ -104,16 +86,13 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install libgtk-3-dev
|
sudo apt-get -y install libgtk-3-dev
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Setup sccache
|
- name: Cache Rust workspace
|
||||||
uses: mozilla-actions/sccache-action@v0.0.4
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
env:
|
run: cargo test --release --all-features --workspace
|
||||||
RUSTC_WRAPPER: sccache
|
|
||||||
SCCACHE_GHA_ENABLED: "true"
|
|
||||||
run: cargo test --release
|
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build objdiff-cli
|
name: Build objdiff-cli
|
||||||
@@ -137,10 +116,10 @@ jobs:
|
|||||||
name: linux-aarch64
|
name: linux-aarch64
|
||||||
build: zigbuild
|
build: zigbuild
|
||||||
features: default
|
features: default
|
||||||
- platform: ubuntu-latest
|
- platform: windows-latest
|
||||||
target: armv7-unknown-linux-musleabi
|
target: i686-pc-windows-msvc
|
||||||
name: linux-armv7l
|
name: windows-x86
|
||||||
build: zigbuild
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
@@ -166,25 +145,34 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
- name: Install uv
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
run: |
|
||||||
|
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||||
|
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target }}
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
build-gui:
|
build-gui:
|
||||||
@@ -195,21 +183,26 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- platform: ubuntu-latest
|
- platform: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu.2.31
|
||||||
|
target_base: x86_64-unknown-linux-gnu
|
||||||
name: linux-x86_64
|
name: linux-x86_64
|
||||||
packages: libgtk-3-dev
|
packages: libgtk-3-dev
|
||||||
|
build: zigbuild
|
||||||
features: default
|
features: default
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
name: windows-x86_64
|
name: windows-x86_64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
name: macos-x86_64
|
name: macos-x86_64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
name: macos-arm64
|
name: macos-arm64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
@@ -220,40 +213,101 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install ${{ matrix.packages }}
|
sudo apt-get -y install ${{ matrix.packages }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
- name: Install uv
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
|
- name: Install cargo-zigbuild
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
run: |
|
||||||
|
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||||
|
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target_base || matrix.target }}
|
||||||
- name: Setup sccache
|
- name: Cache Rust workspace
|
||||||
uses: mozilla-actions/sccache-action@v0.0.4
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
env:
|
|
||||||
RUSTC_WRAPPER: sccache
|
|
||||||
SCCACHE_GHA_ENABLED: "true"
|
|
||||||
run: >
|
run: >
|
||||||
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
release:
|
build-wasm:
|
||||||
name: Release
|
name: Build objdiff-wasm
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: rust-src
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm -C objdiff-wasm ci
|
||||||
|
- name: Build
|
||||||
|
run: npm -C objdiff-wasm run build
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: wasm
|
||||||
|
path: objdiff-wasm/dist/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
check-version:
|
||||||
|
name: Check package versions
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ check, build-cli, build-gui ]
|
needs: [ build-cli, build-gui, build-wasm ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Check git tag against package versions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -eou pipefail
|
||||||
|
tag='${{github.ref}}'
|
||||||
|
tag="${tag#refs/tags/}"
|
||||||
|
version=$(grep '^version' Cargo.toml | head -1 | awk -F' = ' '{print $2}' | tr -d '"')
|
||||||
|
version="v$version"
|
||||||
|
if [ "$tag" != "$version" ]; then
|
||||||
|
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
version="v$(jq -r .version objdiff-wasm/package.json)"
|
||||||
|
if [ "$tag" != "$version" ]; then
|
||||||
|
echo "::error::Git tag doesn't match the npm version! ($tag != $version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
release-github:
|
||||||
|
name: Release (GitHub)
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
pattern: objdiff-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: Rename artifacts
|
- name: Rename artifacts
|
||||||
working-directory: artifacts
|
working-directory: artifacts
|
||||||
@@ -278,6 +332,57 @@ jobs:
|
|||||||
done
|
done
|
||||||
ls -R ../out
|
ls -R ../out
|
||||||
- name: Release
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
files: out/*
|
files: out/*
|
||||||
|
draft: true
|
||||||
|
generate_release_notes: true
|
||||||
|
|
||||||
|
release-cargo:
|
||||||
|
name: Release (Cargo)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Publish
|
||||||
|
env:
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
run: cargo publish -p objdiff-core
|
||||||
|
|
||||||
|
release-npm:
|
||||||
|
name: Release (npm)
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wasm
|
||||||
|
path: objdiff-wasm/dist
|
||||||
|
- name: Publish
|
||||||
|
working-directory: objdiff-wasm
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
version=$(jq -r '.version' package.json)
|
||||||
|
tag="latest"
|
||||||
|
# Check for prerelease by looking for a dash
|
||||||
|
case "$version" in
|
||||||
|
*-*)
|
||||||
|
tag=$(echo "$version" | sed -e 's/^[^-]*-//' -e 's/\..*$//')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "Publishing version $version with tag '$tag'..."
|
||||||
|
npm publish --provenance --access public --tag "$tag"
|
||||||
|
|||||||
+1
-5
@@ -3,10 +3,6 @@ target/
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
generated/
|
generated/
|
||||||
|
|
||||||
# cargo-mobile
|
|
||||||
.cargo/
|
|
||||||
/gen
|
|
||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
@@ -22,4 +18,4 @@ android.keystore
|
|||||||
*.frag
|
*.frag
|
||||||
*.vert
|
*.vert
|
||||||
*.metal
|
*.metal
|
||||||
.vscode/launch.json
|
.vscode/
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v5.0.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
args: [--markdown-linebreak-ext=md]
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: fix-byte-order-marker
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: cargo-fmt
|
||||||
|
name: cargo fmt
|
||||||
|
description: Run cargo fmt on all project files.
|
||||||
|
language: system
|
||||||
|
entry: cargo
|
||||||
|
args: ["+nightly", "fmt", "--all"]
|
||||||
|
pass_filenames: false
|
||||||
|
- id: cargo clippy
|
||||||
|
name: cargo clippy
|
||||||
|
description: Run cargo clippy on all project files.
|
||||||
|
language: system
|
||||||
|
entry: cargo
|
||||||
|
args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
|
||||||
|
pass_filenames: false
|
||||||
|
- id: cargo-deny
|
||||||
|
name: cargo deny
|
||||||
|
description: Run cargo deny on all project files.
|
||||||
|
language: system
|
||||||
|
entry: cargo
|
||||||
|
args: ["deny", "check"]
|
||||||
|
pass_filenames: false
|
||||||
|
always_run: true
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure & Module Organization
|
||||||
|
|
||||||
|
- `objdiff-core`: core library with diffing logic and shared components
|
||||||
|
- `objdiff-cli`: CLI/TUI with `ratatui`
|
||||||
|
- `objdiff-gui`: GUI with `eframe`/`egui`
|
||||||
|
- `objdiff-wasm`: WebAssembly bindings
|
||||||
|
|
||||||
|
objdiff has three main frontends: GUI, CLI/TUI, and [web](https://github.com/encounter/objdiff-web) (utilizing `objdiff-wasm`).
|
||||||
|
|
||||||
|
`objdiff-gui` is the most fully-featured and is the primary target for new features.
|
||||||
|
|
||||||
|
`objdiff-cli` has an interactive TUI `diff` mode (powered by `ratatui`) and a non-interactive `report` mode to generate detailed progress reports (e.g. for <https://decomp.dev>).
|
||||||
|
|
||||||
|
`objdiff-wasm` is compiled into a [WebAssembly Component](https://component-model.bytecodealliance.org/). The [API](objdiff-wasm/wit/objdiff.wit) is defined using [WIT](https://component-model.bytecodealliance.org/design/wit.html). The web interface is separate, but mirrors the GUI in functionality.
|
||||||
|
|
||||||
|
## Build, Test, and Development Commands
|
||||||
|
|
||||||
|
Run `cargo build` for a debug build and `cargo build --release` for an optimized build.
|
||||||
|
The CLI can be exercised with `cargo run --release --bin objdiff-cli -- --help`.
|
||||||
|
|
||||||
|
The WASM build is not included in the workspace by default due to differences in toolchain and target; build it with `npm -C objdiff-wasm run build`. (nightly required)
|
||||||
|
In general, do NOT use `--workspace`, as it will include the WASM crate and likely fail. Formatting and linting commands are exempt.
|
||||||
|
|
||||||
|
Pre-commit tasks:
|
||||||
|
|
||||||
|
- `cargo test` to run the test suite
|
||||||
|
- `cargo +nightly fmt --all` to format code (nightly required)
|
||||||
|
- `cargo +nightly clippy --all-targets --all-features --workspace -- -D warnings` to lint code (nightly required)
|
||||||
|
- `cargo deny check` (`cargo install --locked cargo-deny` if needed) if dependencies change
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
Favor focused unit tests adjacent to the code, and integration scenarios in `objdiff-core/tests`.
|
||||||
|
Integration tests utilize snapshots with the `insta` crate; run `curl -LsSf https://insta.rs/install.sh | sh` if needed.
|
||||||
|
To generate updated snapshots, use `cargo insta test`, review diffs manually, then accept changes with `cargo insta accept` (or simply `mv` to accept specific snapshots).
|
||||||
|
|
||||||
|
## Configuration Tips
|
||||||
|
|
||||||
|
- `config.schema.json`: JSON schema for the user-facing project config file (`objdiff.json`)
|
||||||
|
- `objdiff-core/config-schema.json`: Internal options schema to generate `DiffObjConfig` (see `objdiff-core/config_gen.rs`)
|
||||||
|
|
||||||
|
The project configuration (`objdiff.json`) is intended to be generated by the projects' build system. It includes paths to the target (expected) and base (current) object files, along with build commands and file watch patterns. See `README.md` for a summary of key options.
|
||||||
Generated
+4242
-1795
File diff suppressed because it is too large
Load Diff
+22
-2
@@ -3,10 +3,30 @@ members = [
|
|||||||
"objdiff-cli",
|
"objdiff-cli",
|
||||||
"objdiff-core",
|
"objdiff-core",
|
||||||
"objdiff-gui",
|
"objdiff-gui",
|
||||||
|
"objdiff-wasm",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
default-members = [
|
||||||
|
"objdiff-cli",
|
||||||
|
"objdiff-core",
|
||||||
|
"objdiff-gui",
|
||||||
|
# Exclude objdiff-wasm by default
|
||||||
|
]
|
||||||
|
resolver = "3"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "3.7.3"
|
||||||
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://github.com/encounter/objdiff"
|
||||||
|
rust-version = "1.88"
|
||||||
|
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = "thin"
|
lto = "fat"
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
|
[profile.release-min]
|
||||||
|
inherits = "release-lto"
|
||||||
|
opt-level = "z"
|
||||||
|
|||||||
@@ -6,53 +6,75 @@
|
|||||||
A local diffing tool for decompilation projects. Inspired by [decomp.me](https://decomp.me) and [asm-differ](https://github.com/simonlindholm/asm-differ).
|
A local diffing tool for decompilation projects. Inspired by [decomp.me](https://decomp.me) and [asm-differ](https://github.com/simonlindholm/asm-differ).
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
- Compare entire object files: functions and data.
|
|
||||||
- Built-in symbol demangling for C++. (CodeWarrior, Itanium & MSVC)
|
- Compare entire object files: functions and data
|
||||||
- Automatic rebuild on source file changes.
|
- Built-in C++ symbol demangling (GCC, MSVC, CodeWarrior, Itanium)
|
||||||
- Project integration via [configuration file](#configuration).
|
- Automatic rebuild on source file changes
|
||||||
- Search and filter all of a project's objects and quickly switch.
|
- Project integration via [configuration file](#configuration)
|
||||||
- Click to highlight all instances of values and registers.
|
- Search and filter objects with quick switching
|
||||||
|
- Click-to-highlight values and registers
|
||||||
|
- Detailed progress reporting (powers [decomp.dev](https://decomp.dev))
|
||||||
|
- WebAssembly API, [web interface](https://github.com/encounter/objdiff-web) and [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=decomp-dev.objdiff) (WIP)
|
||||||
|
|
||||||
Supports:
|
Supports:
|
||||||
- PowerPC 750CL (GameCube, Wii)
|
|
||||||
- MIPS (N64, PS1, PS2, PSP)
|
|
||||||
- x86 (COFF only at the moment)
|
|
||||||
- ARM (GBA, DS, 3DS)
|
- ARM (GBA, DS, 3DS)
|
||||||
|
- ARM64 (Switch)
|
||||||
|
- MIPS (N64, PS1, PS2, PSP)
|
||||||
|
- PowerPC (GameCube, Wii, PS3, Xbox 360)
|
||||||
|
- SuperH (Saturn, Dreamcast)
|
||||||
|
- x86, x86_64 (PC)
|
||||||
|
|
||||||
See [Usage](#usage) for more information.
|
See [Usage](#usage) for more information.
|
||||||
|
|
||||||
|
## Downloads
|
||||||
|
|
||||||
|
To build from source, see [Building](#building).
|
||||||
|
|
||||||
|
### GUI
|
||||||
|
|
||||||
|
- [Windows (x86_64)](https://github.com/encounter/objdiff/releases/latest/download/objdiff-windows-x86_64.exe)
|
||||||
|
- [Linux (x86_64)](https://github.com/encounter/objdiff/releases/latest/download/objdiff-linux-x86_64)
|
||||||
|
- [macOS (arm64)](https://github.com/encounter/objdiff/releases/latest/download/objdiff-macos-arm64)
|
||||||
|
- [macOS (x86_64)](https://github.com/encounter/objdiff/releases/latest/download/objdiff-macos-x86_64)
|
||||||
|
|
||||||
|
For Linux and macOS, run `chmod +x objdiff-*` to make the binary executable.
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
CLI binaries are available on the [releases page](https://github.com/encounter/objdiff/releases).
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path
|
objdiff compares two relocatable object files (`.o`). Here's how it works:
|
||||||
from the "target" and "base" directories.
|
|
||||||
|
|
||||||
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual")
|
1. **Create an `objdiff.json` configuration file** in your project root (or generate it with your build script).
|
||||||
object is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
|
This file lists **all objects in the project** with their target ("expected") and base ("current") paths.
|
||||||
|
|
||||||
- Target build directory: `build/asm`
|
2. **Load the project** in objdiff.
|
||||||
- Base build directory: `build/src`
|
|
||||||
- Object: `MetroTRK/mslsupp.o`
|
|
||||||
|
|
||||||
objdiff will then execute the build system from the project directory to build both objects:
|
3. **Select an object** from the sidebar to begin diffing.
|
||||||
|
|
||||||
```sh
|
4. **objdiff automatically:**
|
||||||
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
|
- Executes the build system to compile the base object (from current source code)
|
||||||
$ make build/src/MetroTRK/mslsupp.o
|
- Compares the two objects and displays the differences
|
||||||
```
|
- Watches for source file changes and rebuilds when detected
|
||||||
|
|
||||||
The objects will then be compared and the results will be displayed in the UI.
|
The configuration file allows complete flexibility in project structure - your build directories can have any layout as long as the paths are specified correctly.
|
||||||
|
|
||||||
See [Configuration](#configuration) for more information.
|
See [Configuration](#configuration) for setup details.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in
|
Projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in
|
||||||
the root project directory.
|
the root project directory.
|
||||||
|
|
||||||
If your project has a generator script (e.g. `configure.py`), it's recommended to generate the objdiff configuration
|
If your project has a generator script (e.g. `configure.py`), it's highly recommended to generate the objdiff configuration
|
||||||
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
|
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -67,22 +89,31 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
"build_base": true,
|
"build_base": true,
|
||||||
"watch_patterns": [
|
"watch_patterns": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
"*.cc",
|
||||||
"*.cp",
|
"*.cp",
|
||||||
"*.cpp",
|
"*.cpp",
|
||||||
"*.cxx",
|
"*.cxx",
|
||||||
|
"*.c++",
|
||||||
"*.h",
|
"*.h",
|
||||||
|
"*.hh",
|
||||||
"*.hp",
|
"*.hp",
|
||||||
"*.hpp",
|
"*.hpp",
|
||||||
"*.hxx",
|
"*.hxx",
|
||||||
|
"*.h++",
|
||||||
|
"*.pch",
|
||||||
|
"*.pch++",
|
||||||
|
"*.inc",
|
||||||
"*.s",
|
"*.s",
|
||||||
"*.S",
|
"*.S",
|
||||||
"*.asm",
|
"*.asm",
|
||||||
"*.inc",
|
|
||||||
"*.py",
|
"*.py",
|
||||||
"*.yml",
|
"*.yml",
|
||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
],
|
],
|
||||||
|
"ignore_patterns": [
|
||||||
|
"build/**/*"
|
||||||
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"name": "main/MetroTRK/mslsupp",
|
"name": "main/MetroTRK/mslsupp",
|
||||||
@@ -96,61 +127,77 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
|
|
||||||
### Schema
|
### Schema
|
||||||
|
|
||||||
View [config.schema.json](config.schema.json) for all available options. The below list is a summary of the most important options.
|
> [!NOTE]
|
||||||
|
> View [config.schema.json](config.schema.json) for all available options. Below is a summary of the most important options.
|
||||||
|
|
||||||
`custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
|
#### Build Configuration
|
||||||
If the project uses a different build system (e.g. `ninja`), specify it here.
|
|
||||||
The build command will be `[custom_make] [custom_args] path/to/object.o`.
|
|
||||||
|
|
||||||
`custom_args` _(optional)_: Additional arguments to pass to the build command prior to the object path.
|
**`custom_make`** _(optional, default: `"make"`)_
|
||||||
|
If the project uses a different build system (e.g. `ninja`), specify it here. The build command will be `[custom_make] [custom_args] path/to/object.o`.
|
||||||
|
|
||||||
`build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g.
|
**`custom_args`** _(optional)_
|
||||||
`make path/to/target.o`).
|
Additional arguments to pass to the build command prior to the object path.
|
||||||
This is useful if the target objects are not built by default or can change based on project configuration or edits
|
|
||||||
to assembly files.
|
|
||||||
Requires the build system to be configured properly.
|
|
||||||
|
|
||||||
`build_base`: If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).
|
**`build_target`** _(default: `false`)_
|
||||||
It's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.
|
If true, objdiff will build the target objects before diffing (e.g. `make path/to/target.o`). Useful if target objects are not built by default or can change based on project configuration. Requires proper build system configuration.
|
||||||
|
|
||||||
`watch_patterns` _(optional)_: A list of glob patterns to watch for changes.
|
**`build_base`** _(default: `true`)_
|
||||||
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
If true, objdiff will build the base objects before diffing (e.g. `make path/to/base.o`). It's unlikely you'll want to disable this unless using an external tool to rebuild the base object.
|
||||||
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
|
||||||
If not specified, objdiff will use the default patterns listed above.
|
|
||||||
|
|
||||||
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
#### File Watching
|
||||||
|
|
||||||
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
**`watch_patterns`** _(optional, default: listed above)_
|
||||||
>
|
A list of glob patterns to watch for changes ([supported syntax](https://docs.rs/globset/latest/globset/#syntax)). When these files change, objdiff automatically rebuilds and re-compares objects.
|
||||||
> `target_path`: Path to the "target" or "expected" object from the project root.
|
|
||||||
> This object is the **intended result** of the match.
|
**`ignore_patterns`** _(optional, default: listed above)_
|
||||||
>
|
A list of glob patterns to explicitly ignore when watching for changes ([supported syntax](https://docs.rs/globset/latest/globset/#syntax)).
|
||||||
> `base_path`: Path to the "base" or "actual" object from the project root.
|
|
||||||
> This object is built from the **current source code**.
|
#### Units (Objects)
|
||||||
>
|
|
||||||
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
**`units`** _(optional)_
|
||||||
>
|
If specified, objdiff displays a list of objects in the sidebar for easy navigation. Each unit contains:
|
||||||
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
|
|
||||||
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
|
- **`name`** _(optional)_ - The display name in the UI. Defaults to the object's `path`.
|
||||||
|
- **`target_path`** _(optional)_ - Path to the "target" or "expected" object (the **intended result**).
|
||||||
|
- **`base_path`** _(optional)_ - Path to the "base" or "current" object (built from **current source code**). Omit if there is no source object yet.
|
||||||
|
- **`metadata.auto_generated`** _(optional)_ - Hides the object from the sidebar but includes it in progress reports.
|
||||||
|
- **`metadata.complete`** _(optional)_ - Marks the object as "complete" (linked) when `true` or "incomplete" when `false`.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
Install Rust via [rustup](https://rustup.rs).
|
Install Rust via [rustup](https://rustup.rs).
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ git clone https://github.com/encounter/objdiff.git
|
git clone https://github.com/encounter/objdiff.git
|
||||||
$ cd objdiff
|
cd objdiff
|
||||||
$ cargo run --release
|
cargo run --release
|
||||||
# or, for wgpu backend (recommended on macOS)
|
```
|
||||||
$ cargo run --release --features wgpu
|
|
||||||
|
Or install directly with cargo:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-gui objdiff-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
Binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Install `pre-commit` to run linting and formatting automatically:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
rustup toolchain install nightly # Required for cargo fmt/clippy
|
||||||
|
cargo install --locked cargo-deny # https://github.com/EmbarkStudios/cargo-deny
|
||||||
|
uv tool install pre-commit # https://docs.astral.sh/uv, or use pipx or pip
|
||||||
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|
||||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
|
||||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||||
|
|
||||||
at your option.
|
at your option.
|
||||||
|
|
||||||
|
|||||||
+70
-10
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"custom_make": {
|
"custom_make": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "By default, objdiff will use make to build the project.\nIf the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.",
|
"description": "If the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.",
|
||||||
"examples": [
|
"examples": [
|
||||||
"make",
|
"make",
|
||||||
"ninja"
|
"ninja"
|
||||||
@@ -41,39 +41,55 @@
|
|||||||
},
|
},
|
||||||
"build_target": {
|
"build_target": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).\nThis is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.\nRequires the build system to be configured properly.",
|
"description": "If true, objdiff will build the target objects before diffing (e.g. `make path/to/target.o`).\nUseful if target objects are not built by default or can change based on project configuration.\nRequires proper build system configuration.",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"build_base": {
|
"build_base": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.",
|
"description": "If true, objdiff will build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this unless using an external tool to rebuild the base object.",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
"watch_patterns": {
|
"watch_patterns": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "List of glob patterns to watch for changes in the project.\nIf any of these files change, objdiff will automatically rebuild the objects and re-compare them.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
"description": "A list of glob patterns to watch for changes.\nWhen these files change, objdiff automatically rebuilds and re-compares objects.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"default": [
|
"default": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
"*.cc",
|
||||||
"*.cp",
|
"*.cp",
|
||||||
"*.cpp",
|
"*.cpp",
|
||||||
"*.cxx",
|
"*.cxx",
|
||||||
|
"*.c++",
|
||||||
"*.h",
|
"*.h",
|
||||||
|
"*.hh",
|
||||||
"*.hp",
|
"*.hp",
|
||||||
"*.hpp",
|
"*.hpp",
|
||||||
"*.hxx",
|
"*.hxx",
|
||||||
|
"*.h++",
|
||||||
|
"*.pch",
|
||||||
|
"*.pch++",
|
||||||
|
"*.inc",
|
||||||
"*.s",
|
"*.s",
|
||||||
"*.S",
|
"*.S",
|
||||||
"*.asm",
|
"*.asm",
|
||||||
"*.inc",
|
|
||||||
"*.py",
|
"*.py",
|
||||||
"*.yml",
|
"*.yml",
|
||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignore_patterns": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "A list of glob patterns to explicitly ignore when watching for changes.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"build/**/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Use units instead.",
|
"description": "Use units instead.",
|
||||||
@@ -95,6 +111,25 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/$defs/progress_category"
|
"$ref": "#/$defs/progress_category"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Diff configuration options that should be applied automatically when the project is loaded.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"demangler": "gnu_legacy"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$defs": {
|
"$defs": {
|
||||||
@@ -103,7 +138,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The name of the object in the UI. If not specified, the object's path will be used."
|
"description": "The display name in the UI. Defaults to the object's path."
|
||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -112,11 +147,11 @@
|
|||||||
},
|
},
|
||||||
"target_path": {
|
"target_path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Path to the target object from the project root.\nRequired if path is not specified."
|
"description": "Path to the \"target\" or \"expected\" object (the intended result)."
|
||||||
},
|
},
|
||||||
"base_path": {
|
"base_path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Path to the base object from the project root.\nRequired if path is not specified."
|
"description": "Path to the \"base\" or \"current\" object (built from current source code).\nOmit if there is no source object yet."
|
||||||
},
|
},
|
||||||
"reverse_fn_order": {
|
"reverse_fn_order": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -133,6 +168,27 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ref": "#/$defs/metadata"
|
"ref": "#/$defs/metadata"
|
||||||
|
},
|
||||||
|
"symbol_mappings": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Manual symbol mappings from target to base.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Diff configuration options that should be applied when this unit is active.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -168,6 +224,10 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"preset_id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -180,7 +240,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"complete": {
|
"complete": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Marks the object as \"complete\" (or \"linked\") in the object list.\nThis is useful for marking objects that are fully decompiled. A value of `false` will mark the object as \"incomplete\"."
|
"description": "Marks the object as \"complete\" (linked) when `true` or \"incomplete\" when `false`."
|
||||||
},
|
},
|
||||||
"reverse_fn_order": {
|
"reverse_fn_order": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -200,7 +260,7 @@
|
|||||||
},
|
},
|
||||||
"auto_generated": {
|
"auto_generated": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Hides the object from the object list by default, but still includes it in reports."
|
"description": "Hides the object from the sidebar but includes it in progress reports."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
# The values provided in this template are the default values that will be used
|
# The values provided in this template are the default values that will be used
|
||||||
# when any section or field is not specified in your own configuration
|
# when any section or field is not specified in your own configuration
|
||||||
|
|
||||||
|
# Root options
|
||||||
|
|
||||||
|
# The graph table configures how the dependency graph is constructed and thus
|
||||||
|
# which crates the checks are performed against
|
||||||
|
[graph]
|
||||||
# If 1 or more target triples (and optionally, target_features) are specified,
|
# If 1 or more target triples (and optionally, target_features) are specified,
|
||||||
# only the specified targets will be checked when running `cargo deny check`.
|
# only the specified targets will be checked when running `cargo deny check`.
|
||||||
# This means, if a particular package is only ever used as a target specific
|
# This means, if a particular package is only ever used as a target specific
|
||||||
@@ -20,51 +25,75 @@
|
|||||||
targets = [
|
targets = [
|
||||||
# The triple can be any string, but only the target triples built in to
|
# The triple can be any string, but only the target triples built in to
|
||||||
# rustc (as of 1.40) can be checked against actual config expressions
|
# rustc (as of 1.40) can be checked against actual config expressions
|
||||||
#{ triple = "x86_64-unknown-linux-musl" },
|
#"x86_64-unknown-linux-musl",
|
||||||
# You can also specify which target_features you promise are enabled for a
|
# You can also specify which target_features you promise are enabled for a
|
||||||
# particular target. target_features are currently not validated against
|
# particular target. target_features are currently not validated against
|
||||||
# the actual valid features supported by the target architecture.
|
# the actual valid features supported by the target architecture.
|
||||||
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||||
]
|
]
|
||||||
|
# When creating the dependency graph used as the source of truth when checks are
|
||||||
|
# executed, this field can be used to prune crates from the graph, removing them
|
||||||
|
# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate
|
||||||
|
# is pruned from the graph, all of its dependencies will also be pruned unless
|
||||||
|
# they are connected to another crate in the graph that hasn't been pruned,
|
||||||
|
# so it should be used with care. The identifiers are [Package ID Specifications]
|
||||||
|
# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html)
|
||||||
|
#exclude = []
|
||||||
|
# If true, metadata will be collected with `--all-features`. Note that this can't
|
||||||
|
# be toggled off if true, if you want to conditionally enable `--all-features` it
|
||||||
|
# is recommended to pass `--all-features` on the cmd line instead
|
||||||
|
all-features = false
|
||||||
|
# If true, metadata will be collected with `--no-default-features`. The same
|
||||||
|
# caveat with `all-features` applies
|
||||||
|
no-default-features = false
|
||||||
|
# If set, these feature will be enabled when collecting metadata. If `--features`
|
||||||
|
# is specified on the cmd line they will take precedence over this option.
|
||||||
|
#features = []
|
||||||
|
|
||||||
|
# The output table provides options for how/if diagnostics are outputted
|
||||||
|
[output]
|
||||||
|
# When outputting inclusion graphs in diagnostics that include features, this
|
||||||
|
# option can be used to specify the depth at which feature edges will be added.
|
||||||
|
# This option is included since the graphs can be quite large and the addition
|
||||||
|
# of features from the crate(s) to all of the graph roots can be far too verbose.
|
||||||
|
# This option can be overridden via `--feature-depth` on the cmd line
|
||||||
|
feature-depth = 1
|
||||||
|
|
||||||
# This section is considered when running `cargo deny check advisories`
|
# This section is considered when running `cargo deny check advisories`
|
||||||
# More documentation for the advisories section can be found here:
|
# More documentation for the advisories section can be found here:
|
||||||
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||||
[advisories]
|
[advisories]
|
||||||
# The path where the advisory database is cloned/fetched into
|
# The path where the advisory databases are cloned/fetched into
|
||||||
db-path = "~/.cargo/advisory-db"
|
#db-path = "$CARGO_HOME/advisory-dbs"
|
||||||
# The url(s) of the advisory databases to use
|
# The url(s) of the advisory databases to use
|
||||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
#db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||||
# The lint level for security vulnerabilities
|
|
||||||
vulnerability = "deny"
|
|
||||||
# The lint level for unmaintained crates
|
|
||||||
unmaintained = "warn"
|
|
||||||
# The lint level for crates that have been yanked from their source registry
|
|
||||||
yanked = "warn"
|
|
||||||
# The lint level for crates with security notices. Note that as of
|
|
||||||
# 2019-12-17 there are no security notice advisories in
|
|
||||||
# https://github.com/rustsec/advisory-db
|
|
||||||
notice = "warn"
|
|
||||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||||
# output a note when they are encountered.
|
# output a note when they are encountered.
|
||||||
ignore = []
|
ignore = [
|
||||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
# lower than the range specified will be ignored. Note that ignored advisories
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
# will still output a note when they are encountered.
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
# * None - CVSS Score 0.0
|
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
# * Low - CVSS Score 0.1 - 3.9
|
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
||||||
# * Medium - CVSS Score 4.0 - 6.9
|
{ id = "RUSTSEC-2025-0134", reason = "Unmaintained rustls-pemfile crate is an indirect dependency" },
|
||||||
# * High - CVSS Score 7.0 - 8.9
|
{ id = "RUSTSEC-2026-0098", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
|
||||||
# * Critical - CVSS Score 9.0 - 10.0
|
{ id = "RUSTSEC-2026-0099", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
|
||||||
#severity-threshold =
|
{ id = "RUSTSEC-2026-0104", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
|
||||||
|
{ id = "RUSTSEC-2026-0192", reason = "Unmaintained ttf-parser is an egui 0.33 indirect dependency; egui 0.35 requires raising MSRV from 1.88 to 1.92" },
|
||||||
|
{ id = "RUSTSEC-2026-0194", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
|
||||||
|
{ id = "RUSTSEC-2026-0195", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
|
||||||
|
]
|
||||||
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
|
# If this is false, then it uses a built-in git library.
|
||||||
|
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||||
|
# See Git Authentication for more information about setting up git authentication.
|
||||||
|
#git-fetch-with-cli = true
|
||||||
|
|
||||||
# This section is considered when running `cargo deny check licenses`
|
# This section is considered when running `cargo deny check licenses`
|
||||||
# More documentation for the licenses section can be found here:
|
# More documentation for the licenses section can be found here:
|
||||||
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||||
[licenses]
|
[licenses]
|
||||||
# The lint level for crates which do not have a detectable license
|
# List of explicitly allowed licenses
|
||||||
unlicensed = "deny"
|
|
||||||
# List of explictly allowed licenses
|
|
||||||
# See https://spdx.org/licenses/ for list of possible licenses
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||||
allow = [
|
allow = [
|
||||||
@@ -77,34 +106,13 @@ allow = [
|
|||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
"MPL-2.0",
|
"MPL-2.0",
|
||||||
"Unicode-DFS-2016",
|
"Unicode-3.0",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"0BSD",
|
"0BSD",
|
||||||
"OFL-1.1",
|
"OFL-1.1",
|
||||||
"LicenseRef-UFL-1.0",
|
"Ubuntu-font-1.0",
|
||||||
"OpenSSL",
|
"CDLA-Permissive-2.0",
|
||||||
"GPL-3.0",
|
|
||||||
]
|
]
|
||||||
# List of explictly disallowed licenses
|
|
||||||
# See https://spdx.org/licenses/ for list of possible licenses
|
|
||||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
|
||||||
deny = [
|
|
||||||
#"Nokia",
|
|
||||||
]
|
|
||||||
# Lint level for licenses considered copyleft
|
|
||||||
copyleft = "warn"
|
|
||||||
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
|
|
||||||
# * both - The license will be approved if it is both OSI-approved *AND* FSF
|
|
||||||
# * either - The license will be approved if it is either OSI-approved *OR* FSF
|
|
||||||
# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF
|
|
||||||
# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved
|
|
||||||
# * neither - This predicate is ignored and the default lint level is used
|
|
||||||
allow-osi-fsf-free = "neither"
|
|
||||||
# Lint level used when no other predicates are matched
|
|
||||||
# 1. License isn't in the allow or deny lists
|
|
||||||
# 2. License isn't copyleft
|
|
||||||
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
|
|
||||||
default = "deny"
|
|
||||||
# The confidence threshold for detecting a license from license text.
|
# The confidence threshold for detecting a license from license text.
|
||||||
# The higher the value, the more closely the license text must be to the
|
# The higher the value, the more closely the license text must be to the
|
||||||
# canonical license text of a valid SPDX license file.
|
# canonical license text of a valid SPDX license file.
|
||||||
@@ -115,17 +123,15 @@ confidence-threshold = 0.8
|
|||||||
exceptions = [
|
exceptions = [
|
||||||
# Each entry is the crate and version constraint, and its specific allow
|
# Each entry is the crate and version constraint, and its specific allow
|
||||||
# list
|
# list
|
||||||
#{ allow = ["Zlib"], name = "adler32", version = "*" },
|
#{ allow = ["Zlib"], crate = "adler32" },
|
||||||
]
|
]
|
||||||
|
|
||||||
# Some crates don't have (easily) machine readable licensing information,
|
# Some crates don't have (easily) machine readable licensing information,
|
||||||
# adding a clarification entry for it allows you to manually specify the
|
# adding a clarification entry for it allows you to manually specify the
|
||||||
# licensing information
|
# licensing information
|
||||||
[[licenses.clarify]]
|
[[licenses.clarify]]
|
||||||
# The name of the crate the clarification applies to
|
# The package spec the clarification applies to
|
||||||
name = "ring"
|
crate = "ring"
|
||||||
# The optional version constraint for the crate
|
|
||||||
version = "*"
|
|
||||||
# The SPDX expression for the license requirements of the crate
|
# The SPDX expression for the license requirements of the crate
|
||||||
expression = "MIT AND ISC AND OpenSSL"
|
expression = "MIT AND ISC AND OpenSSL"
|
||||||
# One or more files in the crate's source used as the "source of truth" for
|
# One or more files in the crate's source used as the "source of truth" for
|
||||||
@@ -140,7 +146,9 @@ license-files = [
|
|||||||
|
|
||||||
[licenses.private]
|
[licenses.private]
|
||||||
# If true, ignores workspace crates that aren't published, or are only
|
# If true, ignores workspace crates that aren't published, or are only
|
||||||
# published to private registries
|
# published to private registries.
|
||||||
|
# To see how to mark a crate as unpublished (to the official registry),
|
||||||
|
# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field.
|
||||||
ignore = false
|
ignore = false
|
||||||
# One or more private registries that you might publish crates to, if a crate
|
# One or more private registries that you might publish crates to, if a crate
|
||||||
# is only published to private registries, and ignore is true, the crate will
|
# is only published to private registries, and ignore is true, the crate will
|
||||||
@@ -163,30 +171,63 @@ wildcards = "allow"
|
|||||||
# * simplest-path - The path to the version with the fewest edges is highlighted
|
# * simplest-path - The path to the version with the fewest edges is highlighted
|
||||||
# * all - Both lowest-version and simplest-path are used
|
# * all - Both lowest-version and simplest-path are used
|
||||||
highlight = "all"
|
highlight = "all"
|
||||||
|
# The default lint level for `default` features for crates that are members of
|
||||||
|
# the workspace that is being checked. This can be overridden by allowing/denying
|
||||||
|
# `default` on a crate-by-crate basis if desired.
|
||||||
|
workspace-default-features = "allow"
|
||||||
|
# The default lint level for `default` features for external crates that are not
|
||||||
|
# members of the workspace. This can be overridden by allowing/denying `default`
|
||||||
|
# on a crate-by-crate basis if desired.
|
||||||
|
external-default-features = "allow"
|
||||||
# List of crates that are allowed. Use with care!
|
# List of crates that are allowed. Use with care!
|
||||||
allow = [
|
allow = [
|
||||||
#{ name = "ansi_term", version = "=0.11.0" },
|
#"ansi_term@0.11.0",
|
||||||
|
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||||
]
|
]
|
||||||
# List of crates to deny
|
# List of crates to deny
|
||||||
deny = [
|
deny = [
|
||||||
# Each entry the name of a crate and a version range. If version is
|
#"ansi_term@0.11.0",
|
||||||
# not specified, all versions will be matched.
|
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||||
#{ name = "ansi_term", version = "=0.11.0" },
|
|
||||||
#
|
|
||||||
# Wrapper crates can optionally be specified to allow the crate when it
|
# Wrapper crates can optionally be specified to allow the crate when it
|
||||||
# is a direct dependency of the otherwise banned crate
|
# is a direct dependency of the otherwise banned crate
|
||||||
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
|
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# List of features to allow/deny
|
||||||
|
# Each entry the name of a crate and a version range. If version is
|
||||||
|
# not specified, all versions will be matched.
|
||||||
|
#[[bans.features]]
|
||||||
|
#crate = "reqwest"
|
||||||
|
# Features to not allow
|
||||||
|
#deny = ["json"]
|
||||||
|
# Features to allow
|
||||||
|
#allow = [
|
||||||
|
# "rustls",
|
||||||
|
# "__rustls",
|
||||||
|
# "__tls",
|
||||||
|
# "hyper-rustls",
|
||||||
|
# "rustls",
|
||||||
|
# "rustls-pemfile",
|
||||||
|
# "rustls-tls-webpki-roots",
|
||||||
|
# "tokio-rustls",
|
||||||
|
# "webpki-roots",
|
||||||
|
#]
|
||||||
|
# If true, the allowed features must exactly match the enabled feature set. If
|
||||||
|
# this is set there is no point setting `deny`
|
||||||
|
#exact = true
|
||||||
|
|
||||||
# Certain crates/versions that will be skipped when doing duplicate detection.
|
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||||
skip = [
|
skip = [
|
||||||
#{ name = "ansi_term", version = "=0.11.0" },
|
#"ansi_term@0.11.0",
|
||||||
|
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||||
]
|
]
|
||||||
# Similarly to `skip` allows you to skip certain crates during duplicate
|
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||||
# detection. Unlike skip, it also includes the entire tree of transitive
|
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||||
# dependencies starting at the specified crate, up to a certain depth, which is
|
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||||
# by default infinite
|
# by default infinite.
|
||||||
skip-tree = [
|
skip-tree = [
|
||||||
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
|
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
|
||||||
|
#{ crate = "ansi_term@0.11.0", depth = 20 },
|
||||||
]
|
]
|
||||||
|
|
||||||
# This section is considered when running `cargo deny check sources`.
|
# This section is considered when running `cargo deny check sources`.
|
||||||
@@ -206,9 +247,10 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
|||||||
allow-git = []
|
allow-git = []
|
||||||
|
|
||||||
[sources.allow-org]
|
[sources.allow-org]
|
||||||
# 1 or more github.com organizations to allow git sources for
|
# github.com organizations to allow git sources for
|
||||||
github = ["encounter"]
|
github = [
|
||||||
# 1 or more gitlab.com organizations to allow git sources for
|
]
|
||||||
#gitlab = [""]
|
# gitlab.com organizations to allow git sources for
|
||||||
# 1 or more bitbucket.org organizations to allow git sources for
|
gitlab = []
|
||||||
#bitbucket = [""]
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
bitbucket = []
|
||||||
|
|||||||
+24
-21
@@ -1,31 +1,34 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "objdiff-cli"
|
name = "objdiff-cli"
|
||||||
version = "2.0.0-beta.6"
|
version.workspace = true
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.70"
|
rust-version.workspace = true
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors.workspace = true
|
||||||
license = "MIT OR Apache-2.0"
|
license.workspace = true
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository.workspace = true
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
description = """
|
description = """
|
||||||
A local diffing tool for decompilation projects.
|
A local diffing tool for decompilation projects.
|
||||||
"""
|
"""
|
||||||
publish = false
|
publish = false
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0"
|
||||||
argp = "0.3.0"
|
argp = "0.4"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.29"
|
||||||
enable-ansi-support = "0.2.1"
|
enable-ansi-support = "0.3"
|
||||||
memmap2 = "0.9.4"
|
memmap2 = "0.9"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
prost = "0.13.1"
|
prost = "0.14"
|
||||||
ratatui = "0.26.2"
|
ratatui = "0.30"
|
||||||
rayon = "1.10.0"
|
rayon = "1.11"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.116"
|
serde_json = "1.0"
|
||||||
supports-color = "3.0.0"
|
supports-color = "3.0"
|
||||||
time = { version = "0.3.36", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
typed-path = "0.12"
|
||||||
|
|
||||||
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
|
mimalloc = "0.1"
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
let output = std::process::Command::new("git")
|
|
||||||
.args(["rev-parse", "HEAD"])
|
|
||||||
.output()
|
|
||||||
.expect("Failed to execute git");
|
|
||||||
let rev = String::from_utf8(output.stdout).expect("Failed to parse git output");
|
|
||||||
println!("cargo:rustc-env=GIT_COMMIT_SHA={rev}");
|
|
||||||
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
//! For now, this only adds a --version/-V option which causes early-exit.
|
//! For now, this only adds a --version/-V option which causes early-exit.
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
use argp::{parser::ParseGlobalOptions, EarlyExit, FromArgs, TopLevelCommand};
|
use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
|
||||||
|
|
||||||
struct ArgsOrVersion<T>(T)
|
struct ArgsOrVersion<T>(T)
|
||||||
where T: FromArgs;
|
where T: FromArgs;
|
||||||
@@ -31,10 +31,9 @@ where T: FromArgs
|
|||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
if v.version {
|
if v.version {
|
||||||
println!(
|
println!(
|
||||||
"{} {} {}",
|
"{} {}",
|
||||||
command_name.first().unwrap_or(&""),
|
command_name.first().unwrap_or(&""),
|
||||||
env!("CARGO_PKG_VERSION"),
|
env!("CARGO_PKG_VERSION"),
|
||||||
env!("GIT_COMMIT_SHA"),
|
|
||||||
);
|
);
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+361
-812
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,33 @@
|
|||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use objdiff_core::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig};
|
||||||
|
|
||||||
|
pub fn apply_config_args(diff_config: &mut DiffObjConfig, args: &[String]) -> Result<()> {
|
||||||
|
for config in args {
|
||||||
|
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
|
||||||
|
let property_id = ConfigPropertyId::from_str(key)
|
||||||
|
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
|
||||||
|
diff_config.set_property_value_str(property_id, value).map_err(|()| {
|
||||||
|
let mut options = String::new();
|
||||||
|
match property_id.kind() {
|
||||||
|
ConfigPropertyKind::Boolean => {
|
||||||
|
options = "true, false".to_string();
|
||||||
|
}
|
||||||
|
ConfigPropertyKind::Choice(variants) => {
|
||||||
|
for (i, variant) in variants.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
options.push_str(", ");
|
||||||
|
}
|
||||||
|
options.push_str(variant.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
+162
-103
@@ -1,28 +1,25 @@
|
|||||||
use std::{
|
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
|
||||||
collections::HashSet,
|
|
||||||
fs::File,
|
|
||||||
io::Read,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
bindings::report::{
|
bindings::report::{
|
||||||
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
|
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
||||||
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
REPORT_VERSION,
|
|
||||||
},
|
},
|
||||||
config::ProjectObject,
|
config::{ProjectObject, ProjectOptions, apply_project_options, path::platform_path},
|
||||||
diff, obj,
|
diff,
|
||||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
obj::{self, SectionKind, SymbolFlag, SymbolKind},
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::util::output::{write_output, OutputFormat};
|
use crate::{
|
||||||
|
cmd::{apply_config_args, diff::ObjectConfig},
|
||||||
|
util::output::{OutputFormat, write_output},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
@@ -43,33 +40,36 @@ pub enum SubCommand {
|
|||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
#[argp(subcommand, name = "generate")]
|
#[argp(subcommand, name = "generate")]
|
||||||
pub struct GenerateArgs {
|
pub struct GenerateArgs {
|
||||||
#[argp(option, short = 'p')]
|
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||||
/// Project directory
|
/// Project directory
|
||||||
project: Option<PathBuf>,
|
project: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(switch, short = 'd')]
|
#[argp(switch, short = 'd')]
|
||||||
/// Deduplicate global and weak symbols (runs single-threaded)
|
/// Deduplicate global and weak symbols (runs single-threaded)
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
#[argp(option, short = 'f')]
|
#[argp(option, short = 'f')]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
|
#[argp(option, short = 'c')]
|
||||||
|
/// Configuration property (key=value)
|
||||||
|
config: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// List any changes from a previous report.
|
/// List any changes from a previous report.
|
||||||
#[argp(subcommand, name = "changes")]
|
#[argp(subcommand, name = "changes")]
|
||||||
pub struct ChangesArgs {
|
pub struct ChangesArgs {
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Previous report file
|
/// Previous report file
|
||||||
previous: PathBuf,
|
previous: Utf8PlatformPathBuf,
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Current report file
|
/// Current report file
|
||||||
current: PathBuf,
|
current: Utf8PlatformPathBuf,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'f')]
|
#[argp(option, short = 'f')]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
@@ -83,18 +83,46 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate(args: GenerateArgs) -> Result<()> {
|
fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let base_diff_config = diff::DiffObjConfig {
|
||||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
|
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||||
info!("Loading project {}", project_dir.display());
|
combine_data_sections: true,
|
||||||
|
combine_text_sections: true,
|
||||||
|
ppc_calculate_pool_relocations: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let mut project = match objdiff_core::config::try_project_config(project_dir) {
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
|
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||||
|
info!("Loading project {}", project_dir);
|
||||||
|
|
||||||
|
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
|
||||||
Some((Ok(config), _)) => config,
|
Some((Ok(config), _)) => config,
|
||||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||||
None => bail!("No project configuration found"),
|
None => bail!("No project configuration found"),
|
||||||
};
|
};
|
||||||
|
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 project_units = project.units.as_deref().unwrap_or_default();
|
||||||
|
let objects = project_units
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, o)| {
|
||||||
|
(
|
||||||
|
ObjectConfig::new(
|
||||||
|
o,
|
||||||
|
project_dir,
|
||||||
|
target_obj_dir.as_deref(),
|
||||||
|
base_obj_dir.as_deref(),
|
||||||
|
),
|
||||||
|
idx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
info!(
|
info!(
|
||||||
"Generating report for {} units (using {} threads)",
|
"Generating report for {} units (using {} threads)",
|
||||||
project.objects.len(),
|
objects.len(),
|
||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,36 +131,36 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||||
if args.deduplicate {
|
if args.deduplicate {
|
||||||
// If deduplicating, we need to run single-threaded
|
// If deduplicating, we need to run single-threaded
|
||||||
for object in &mut project.objects {
|
for (object, unit_idx) in &objects {
|
||||||
if let Some(unit) = report_object(
|
let diff_config = build_unit_diff_config(
|
||||||
object,
|
&base_diff_config,
|
||||||
project_dir,
|
project.options.as_ref(),
|
||||||
project.target_dir.as_deref(),
|
project_units.get(*unit_idx).and_then(ProjectObject::options),
|
||||||
project.base_dir.as_deref(),
|
&args.config,
|
||||||
Some(&mut existing_functions),
|
)?;
|
||||||
)? {
|
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
|
||||||
|
{
|
||||||
units.push(unit);
|
units.push(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = project
|
let vec = objects
|
||||||
.objects
|
.par_iter()
|
||||||
.par_iter_mut()
|
.map(|(object, unit_idx)| {
|
||||||
.map(|object| {
|
let diff_config = build_unit_diff_config(
|
||||||
report_object(
|
&base_diff_config,
|
||||||
object,
|
project.options.as_ref(),
|
||||||
project_dir,
|
project_units.get(*unit_idx).and_then(ProjectObject::options),
|
||||||
project.target_dir.as_deref(),
|
&args.config,
|
||||||
project.base_dir.as_deref(),
|
)?;
|
||||||
None,
|
report_object(object, &diff_config, None)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
|
||||||
let mut categories = Vec::new();
|
let mut categories = Vec::new();
|
||||||
for category in &project.progress_categories {
|
for category in project.progress_categories() {
|
||||||
categories.push(ReportCategory {
|
categories.push(ReportCategory {
|
||||||
id: category.id.clone(),
|
id: category.id.clone(),
|
||||||
name: category.name.clone(),
|
name: category.name.clone(),
|
||||||
@@ -148,72 +176,91 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_unit_diff_config(
|
||||||
|
base: &diff::DiffObjConfig,
|
||||||
|
project_options: Option<&ProjectOptions>,
|
||||||
|
unit_options: Option<&ProjectOptions>,
|
||||||
|
cli_args: &[String],
|
||||||
|
) -> Result<diff::DiffObjConfig> {
|
||||||
|
let mut diff_config = base.clone();
|
||||||
|
if let Some(options) = project_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
if let Some(options) = unit_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
// CLI args override project and unit options
|
||||||
|
apply_config_args(&mut diff_config, cli_args)?;
|
||||||
|
Ok(diff_config)
|
||||||
|
}
|
||||||
|
|
||||||
fn report_object(
|
fn report_object(
|
||||||
object: &mut ProjectObject,
|
object: &ObjectConfig,
|
||||||
project_dir: &Path,
|
diff_config: &diff::DiffObjConfig,
|
||||||
target_dir: Option<&Path>,
|
|
||||||
base_dir: Option<&Path>,
|
|
||||||
mut existing_functions: Option<&mut HashSet<String>>,
|
mut existing_functions: Option<&mut HashSet<String>>,
|
||||||
) -> Result<Option<ReportUnit>> {
|
) -> Result<Option<ReportUnit>> {
|
||||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
|
||||||
match (&object.target_path, &object.base_path) {
|
match (&object.target_path, &object.base_path) {
|
||||||
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
(None, Some(_)) if !object.complete.unwrap_or(false) => {
|
||||||
warn!("Skipping object without target: {}", object.name());
|
warn!("Skipping object without target: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
warn!("Skipping object without target or base: {}", object.name());
|
warn!("Skipping object without target or base: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
let mapping_config = diff::MappingConfig {
|
||||||
|
mappings: object.symbol_mappings.clone(),
|
||||||
|
selecting_left: None,
|
||||||
|
selecting_right: None,
|
||||||
|
};
|
||||||
let target = object
|
let target = object
|
||||||
.target_path
|
.target_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Target)
|
||||||
|
.with_context(|| format!("Failed to open {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = object
|
let base = object
|
||||||
.base_path
|
.base_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Base)
|
||||||
|
.with_context(|| format!("Failed to open {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
let result =
|
||||||
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
|
||||||
|
|
||||||
let metadata = ReportUnitMetadata {
|
let metadata = ReportUnitMetadata {
|
||||||
complete: object.complete(),
|
complete: object.metadata.complete,
|
||||||
module_name: target
|
module_name: target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|o| o.split_meta.as_ref())
|
.and_then(|o| o.split_meta.as_ref())
|
||||||
.and_then(|m| m.module_name.clone()),
|
.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),
|
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()),
|
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
|
||||||
progress_categories: object
|
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
|
||||||
.metadata
|
auto_generated: object.metadata.auto_generated,
|
||||||
.as_ref()
|
|
||||||
.and_then(|m| m.progress_categories.clone())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
|
||||||
};
|
};
|
||||||
let mut measures = Measures::default();
|
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
let mut functions = vec![];
|
let mut functions = vec![];
|
||||||
|
|
||||||
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
||||||
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
||||||
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) {
|
for ((section_idx, section), section_diff) in
|
||||||
|
obj.sections.iter().enumerate().zip(&obj_diff.sections)
|
||||||
|
{
|
||||||
|
if section.kind == SectionKind::Unknown {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
sections.push(ReportItem {
|
sections.push(ReportItem {
|
||||||
name: section.name.clone(),
|
name: section.name.clone(),
|
||||||
@@ -223,39 +270,40 @@ fn report_object(
|
|||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
virtual_address: section.virtual_address,
|
virtual_address: section.virtual_address,
|
||||||
}),
|
}),
|
||||||
|
address: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
match section.kind {
|
match section.kind {
|
||||||
ObjSectionKind::Data | ObjSectionKind::Bss => {
|
SectionKind::Data | SectionKind::Bss => {
|
||||||
measures.total_data += section.size;
|
measures.total_data += section.size;
|
||||||
if section_match_percent == 100.0 {
|
if section_match_percent == 100.0 {
|
||||||
measures.matched_data += section.size;
|
measures.matched_data += section.size;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ObjSectionKind::Code => (),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_diff.symbols) {
|
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
|
||||||
if symbol.size == 0 {
|
if symbol.section != Some(section_idx)
|
||||||
|
|| symbol.size == 0
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||||
|
|| symbol.kind == SymbolKind::Section
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(existing_functions) = &mut existing_functions {
|
if let Some(existing_functions) = &mut existing_functions
|
||||||
if (symbol.flags.0.contains(ObjSymbolFlags::Global)
|
&& (symbol.flags.contains(SymbolFlag::Global)
|
||||||
|| symbol.flags.0.contains(ObjSymbolFlags::Weak))
|
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||||
&& !existing_functions.insert(symbol.name.clone())
|
&& !existing_functions.insert(symbol.name.clone())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
|
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
|
||||||
measures.total_code += symbol.size;
|
measures.total_code += symbol.size;
|
||||||
@@ -270,6 +318,7 @@ fn report_object(
|
|||||||
demangled_name: symbol.demangled_name.clone(),
|
demangled_name: symbol.demangled_name.clone(),
|
||||||
virtual_address: symbol.virtual_address,
|
virtual_address: symbol.virtual_address,
|
||||||
}),
|
}),
|
||||||
|
address: symbol.address.checked_sub(section.address),
|
||||||
});
|
});
|
||||||
if match_percent == 100.0 {
|
if match_percent == 100.0 {
|
||||||
measures.matched_functions += 1;
|
measures.matched_functions += 1;
|
||||||
@@ -277,14 +326,25 @@ fn report_object(
|
|||||||
measures.total_functions += 1;
|
measures.total_functions += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sections.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
let reverse_fn_order = object.metadata.reverse_fn_order.unwrap_or(false);
|
||||||
|
functions.sort_by(|a, b| {
|
||||||
|
if reverse_fn_order {
|
||||||
|
b.address.unwrap_or(0).cmp(&a.address.unwrap_or(0))
|
||||||
|
} else {
|
||||||
|
a.address.unwrap_or(u64::MAX).cmp(&b.address.unwrap_or(u64::MAX))
|
||||||
|
}
|
||||||
|
.then_with(|| a.size.cmp(&b.size))
|
||||||
|
});
|
||||||
if metadata.complete.unwrap_or(false) {
|
if metadata.complete.unwrap_or(false) {
|
||||||
measures.complete_code = measures.total_code;
|
measures.complete_code = measures.total_code;
|
||||||
measures.complete_data = measures.total_data;
|
measures.complete_data = measures.total_data;
|
||||||
|
measures.complete_units = 1;
|
||||||
}
|
}
|
||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
Ok(Some(ReportUnit {
|
Ok(Some(ReportUnit {
|
||||||
name: object.name().to_string(),
|
name: object.name.clone(),
|
||||||
measures: Some(measures),
|
measures: Some(measures),
|
||||||
sections,
|
sections,
|
||||||
functions,
|
functions,
|
||||||
@@ -294,7 +354,7 @@ fn report_object(
|
|||||||
|
|
||||||
fn changes(args: ChangesArgs) -> Result<()> {
|
fn changes(args: ChangesArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
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
|
// Special case for comparing two reports from stdin
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
@@ -409,15 +469,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_report(path: &Path) -> Result<Report> {
|
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||||
if path == Path::new("-") {
|
if path == Utf8PlatformPath::new("-") {
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
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 file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
|
||||||
let mmap = unsafe { memmap2::Mmap::map(&file) }
|
let mmap =
|
||||||
.with_context(|| format!("Failed to map {}", path.display()))?;
|
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
|
||||||
Report::parse(mmap.as_ref())
|
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
|
||||||
.with_context(|| format!("Failed to load report {}", path.display()))
|
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -1,6 +1,15 @@
|
|||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
mod argp_version;
|
mod argp_version;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod util;
|
mod util;
|
||||||
|
mod views;
|
||||||
|
|
||||||
|
// musl's allocator is very slow, so use mimalloc when targeting musl.
|
||||||
|
// Otherwise, use the system allocator to avoid extra code size.
|
||||||
|
#[cfg(target_env = "musl")]
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
|
||||||
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, str::FromStr};
|
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
@@ -8,7 +17,7 @@ use anyhow::{Error, Result};
|
|||||||
use argp::{FromArgValue, FromArgs};
|
use argp::{FromArgValue, FromArgs};
|
||||||
use enable_ansi_support::enable_ansi_support;
|
use enable_ansi_support::enable_ansi_support;
|
||||||
use supports_color::Stream;
|
use supports_color::Stream;
|
||||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
@@ -34,9 +34,12 @@ impl OutputFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
|
pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
|
||||||
where T: serde::Serialize + prost::Message {
|
where
|
||||||
match output {
|
T: serde::Serialize + prost::Message,
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
match output.as_ref().map(|p| p.as_ref()) {
|
||||||
Some(output) if output != Path::new("-") => {
|
Some(output) if output != Path::new("-") => {
|
||||||
info!("Writing to {}", output.display());
|
info!("Writing to {}", output.display());
|
||||||
let file = File::options()
|
let file = File::options()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{io::stdout, panic};
|
|||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::Show,
|
cursor::Show,
|
||||||
event::DisableMouseCapture,
|
event::DisableMouseCapture,
|
||||||
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
terminal::{LeaveAlternateScreen, disable_raw_mode},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn crossterm_panic_handler() {
|
pub fn crossterm_panic_handler() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user