mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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
|
||||||
+144
-24
@@ -4,13 +4,16 @@ 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
|
CARGO_INCREMENTAL: 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -30,10 +33,12 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --all-features --all-targets
|
run: cargo check --all-targets --all-features
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
run: cargo clippy --all-features --all-targets
|
run: cargo clippy --all-targets --all-features
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
@@ -63,13 +68,12 @@ jobs:
|
|||||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- 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 ]
|
||||||
@@ -85,8 +89,10 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
run: cargo test --release --all-features
|
run: cargo test --release --features all
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build objdiff-cli
|
name: Build objdiff-cli
|
||||||
@@ -142,22 +148,30 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- 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: |
|
||||||
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||||
- 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@v4
|
||||||
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:
|
||||||
@@ -168,21 +182,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 }}
|
||||||
@@ -194,34 +213,70 @@ jobs:
|
|||||||
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@v4
|
||||||
|
- name: Install cargo-zigbuild
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
run: |
|
||||||
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||||
- 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: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
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@v4
|
||||||
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
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ build-cli, build-gui ]
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Check git tag against Cargo version
|
- 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@v4
|
||||||
|
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@v4
|
||||||
|
with:
|
||||||
|
name: wasm
|
||||||
|
path: objdiff-wasm/dist/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
check-version:
|
||||||
|
name: Check package versions
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ build-cli, build-gui, build-wasm ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Check git tag against package versions
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -eou pipefail
|
set -eou pipefail
|
||||||
@@ -233,9 +288,24 @@ jobs:
|
|||||||
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
|
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
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:
|
||||||
|
contents: write
|
||||||
|
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
|
||||||
@@ -265,3 +335,53 @@ jobs:
|
|||||||
files: out/*
|
files: out/*
|
||||||
draft: true
|
draft: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
|
release-cargo:
|
||||||
|
name: Release (Cargo)
|
||||||
|
if: 'false' # TODO re-enable when all dependencies are published
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- 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@v4
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
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"]
|
||||||
|
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
|
||||||
Generated
+2760
-1310
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -3,8 +3,9 @@ members = [
|
|||||||
"objdiff-cli",
|
"objdiff-cli",
|
||||||
"objdiff-core",
|
"objdiff-core",
|
||||||
"objdiff-gui",
|
"objdiff-gui",
|
||||||
|
"objdiff-wasm",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "3"
|
||||||
|
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
@@ -13,9 +14,9 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.2.2"
|
version = "3.0.0"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository = "https://github.com/encounter/objdiff"
|
||||||
rust-version = "1.74"
|
rust-version = "1.88"
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ Features:
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -104,6 +106,9 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
],
|
],
|
||||||
|
"ignore_patterns": [
|
||||||
|
"build/**/*"
|
||||||
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"name": "main/MetroTRK/mslsupp",
|
"name": "main/MetroTRK/mslsupp",
|
||||||
@@ -139,18 +144,22 @@ It's unlikely you'll want to disable this, unless you're using an external tool
|
|||||||
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
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.
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
|
|
||||||
|
`ignore_patterns` _(optional)_: A list of glob patterns to explicitly ignore when watching for changes.
|
||||||
|
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||||
|
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.
|
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||||
|
|
||||||
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||||
>
|
>
|
||||||
> `target_path`: Path to the "target" or "expected" object from the project root.
|
> `target_path`: Path to the "target" or "expected" object from the project root.
|
||||||
> This object is the **intended result** of the match.
|
> This object is the **intended result** of the match.
|
||||||
>
|
>
|
||||||
> `base_path`: Path to the "base" or "actual" object from the project root.
|
> `base_path`: Path to the "base" or "actual" object from the project root.
|
||||||
> This object is built from the **current source code**.
|
> This object is built from the **current source code**.
|
||||||
>
|
>
|
||||||
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
||||||
>
|
>
|
||||||
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
|
> `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".
|
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
|
||||||
|
|
||||||
@@ -172,6 +181,21 @@ $ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-
|
|||||||
|
|
||||||
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
||||||
|
|
||||||
|
## Installing `pre-commit`
|
||||||
|
|
||||||
|
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit.
|
||||||
|
|
||||||
|
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
|
||||||
|
|
||||||
|
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cargo install --locked cargo-deny
|
||||||
|
$ rustup toolchain install nightly
|
||||||
|
$ uv tool install pre-commit
|
||||||
|
$ pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|||||||
@@ -74,6 +74,16 @@
|
|||||||
"*.json"
|
"*.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignore_patterns": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of glob patterns to explicitly ignore when watching for changes.\nFiles matching these patterns will not trigger a rebuild.\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.",
|
||||||
@@ -133,6 +143,13 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"ref": "#/$defs/metadata"
|
"ref": "#/$defs/metadata"
|
||||||
|
},
|
||||||
|
"symbol_mappings": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Manual symbol mappings from target to base.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -168,6 +185,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": [
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ feature-depth = 1
|
|||||||
# 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 = [
|
||||||
"RUSTSEC-2024-0370",
|
|
||||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
|
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# 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.
|
# If this is false, then it uses a built-in git library.
|
||||||
@@ -98,12 +98,12 @@ 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",
|
||||||
]
|
]
|
||||||
# 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
|
||||||
@@ -240,7 +240,10 @@ allow-git = []
|
|||||||
|
|
||||||
[sources.allow-org]
|
[sources.allow-org]
|
||||||
# github.com organizations to allow git sources for
|
# github.com organizations to allow git sources for
|
||||||
github = ["encounter"]
|
github = [
|
||||||
|
"enarx", # flagset
|
||||||
|
"encounter",
|
||||||
|
]
|
||||||
# gitlab.com organizations to allow git sources for
|
# gitlab.com organizations to allow git sources for
|
||||||
gitlab = []
|
gitlab = []
|
||||||
# bitbucket.org organizations to allow git sources for
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
argp = "0.3"
|
argp = "0.4"
|
||||||
crossterm = "0.28"
|
crossterm = "0.29"
|
||||||
enable-ansi-support = "0.2"
|
enable-ansi-support = "0.2"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
prost = "0.13"
|
prost = "0.14"
|
||||||
ratatui = "0.28"
|
ratatui = "0.29"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@@ -28,6 +28,7 @@ supports-color = "3.0"
|
|||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
typed-path = "0.11"
|
||||||
|
|
||||||
[target.'cfg(target_env = "musl")'.dependencies]
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
+305
-843
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(())
|
||||||
|
}
|
||||||
|
|||||||
+115
-102
@@ -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::path::platform_path,
|
||||||
diff, obj,
|
diff, obj,
|
||||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
obj::{SectionKind, SymbolFlag},
|
||||||
};
|
};
|
||||||
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,56 +83,64 @@ 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 mut 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()
|
||||||
|
};
|
||||||
|
apply_config_args(&mut diff_config, &args.config)?;
|
||||||
|
|
||||||
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"),
|
||||||
};
|
};
|
||||||
info!(
|
info!(
|
||||||
"Generating report for {} units (using {} threads)",
|
"Generating report for {} units (using {} threads)",
|
||||||
project.objects.len(),
|
project.units().len(),
|
||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let target_obj_dir =
|
||||||
|
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let base_obj_dir =
|
||||||
|
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let objects = project
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|o| {
|
||||||
|
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut units = vec![];
|
let mut units = vec![];
|
||||||
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 in &objects {
|
||||||
if let Some(unit) = report_object(
|
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
|
||||||
object,
|
{
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
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| report_object(object, &diff_config, None))
|
||||||
.map(|object| {
|
|
||||||
report_object(
|
|
||||||
object,
|
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
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(),
|
||||||
@@ -149,55 +157,49 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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::default();
|
||||||
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).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).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 { total_units: 1, ..Default::default() };
|
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
@@ -205,15 +207,16 @@ fn report_object(
|
|||||||
|
|
||||||
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 +226,39 @@ 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 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
if symbol.section != Some(section_idx)
|
||||||
|
|| symbol.size == 0
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||||
|
{
|
||||||
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 +273,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,6 +281,16 @@ 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;
|
||||||
@@ -285,7 +299,7 @@ fn report_object(
|
|||||||
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,
|
||||||
@@ -295,7 +309,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)?;
|
||||||
@@ -410,15 +424,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()))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
#![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.
|
// musl's allocator is very slow, so use mimalloc when targeting musl.
|
||||||
// Otherwise, use the system allocator to avoid extra code size.
|
// Otherwise, use the system allocator to avoid extra code size.
|
||||||
@@ -14,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() {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::event::Event;
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::cmd::diff::AppState;
|
||||||
|
|
||||||
|
pub mod function_diff;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EventResult {
|
||||||
|
pub redraw: bool,
|
||||||
|
pub click_xy: Option<(u16, u16)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EventControlFlow {
|
||||||
|
Break,
|
||||||
|
Continue(EventResult),
|
||||||
|
Reload,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UiView {
|
||||||
|
fn draw(&mut self, state: &AppState, f: &mut Frame, result: &mut EventResult);
|
||||||
|
fn handle_event(&mut self, state: &mut AppState, event: Event) -> EventControlFlow;
|
||||||
|
fn reload(&mut self, state: &AppState) -> Result<()>;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user