From 6b246624419d132e5d0174bf97515e2eba928b13 Mon Sep 17 00:00:00 2001 From: 0xsharkboy Date: Mon, 21 Sep 2026 23:37:32 +0200 Subject: [PATCH 1/3] build: add reproducible Nix packages --- flake.lock | 48 +++++++++++++ flake.nix | 122 ++++++++++++++++++++++++++++++++ nix/package.nix | 109 ++++++++++++++++++++++++++++ objdiff-gui/src/app.rs | 4 +- objdiff-gui/src/update.rs | 1 + objdiff-gui/src/views/config.rs | 18 +++-- 6 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/package.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..f28eb0e6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1789921291, + "narHash": "sha256-Ft/BRnIqw1MywFoXydKobjjWmDFgDdYtSpJliE8+yUw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "44a91898084f46797b5fac650c7e8c9ac38c43d4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1789977098, + "narHash": "sha256-fKY4BedarKx4aR0+UOHk65hhDW8pxFytFkaUi0VYDYk=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "2776e42828203ec89511699c8dcdedec69ab98e1", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..616fbda9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,122 @@ +{ + description = "A local diffing tool for decompilation projects"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + rust-overlay, + }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + ]; + forAllSystems = nixpkgs.lib.genAttrs systems; + version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.package.version; + mkPkgs = + system: + import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default ]; + }; + in + { + packages = forAllSystems ( + system: + let + pkgs = mkPkgs system; + rustToolchain = pkgs.rust-bin.stable."1.88.0".default; + rustPlatform = pkgs.makeRustPlatform { + cargo = rustToolchain; + rustc = rustToolchain; + }; + mkObjdiff = pkgs.callPackage ./nix/package.nix { + inherit rustPlatform version; + }; + in + rec { + objdiff = mkObjdiff { + crateName = "objdiff-gui"; + binaryName = "objdiff"; + withGui = true; + }; + objdiff-cli = mkObjdiff { + crateName = "objdiff-cli"; + binaryName = "objdiff-cli"; + }; + default = objdiff; + } + ); + + apps = forAllSystems (system: { + default = self.apps.${system}.objdiff; + objdiff = { + type = "app"; + program = "${self.packages.${system}.objdiff}/bin/objdiff"; + meta.description = "Run the objdiff graphical interface"; + }; + objdiff-cli = { + type = "app"; + program = "${self.packages.${system}.objdiff-cli}/bin/objdiff-cli"; + meta.description = "Run the objdiff command-line interface"; + }; + }); + + checks = forAllSystems (system: { + inherit (self.packages.${system}) objdiff objdiff-cli; + }); + + devShells = forAllSystems ( + system: + let + pkgs = mkPkgs system; + rustToolchain = pkgs.rust-bin.selectLatestNightlyWith ( + toolchain: + toolchain.default.override { + extensions = [ + "clippy" + "rust-src" + "rustfmt" + ]; + targets = [ "wasm32-wasip2" ]; + } + ); + cargoWrapper = pkgs.writeShellScriptBin "cargo" '' + if [[ "''${1-}" == "+nightly" ]]; then + shift + fi + exec ${rustToolchain}/bin/cargo "$@" + ''; + in + { + default = pkgs.mkShell { + inputsFrom = [ self.packages.${system}.objdiff ]; + packages = with pkgs; [ + cargoWrapper + rustToolchain + cargo-deny + cargo-insta + nodejs + pre-commit + protobuf + rust-analyzer + ]; + shellHook = '' + export PATH="${cargoWrapper}/bin:$PATH" + ''; + }; + } + ); + + formatter = forAllSystems (system: (mkPkgs system).nixfmt); + }; +} diff --git a/nix/package.nix b/nix/package.nix new file mode 100644 index 00000000..939ca1da --- /dev/null +++ b/nix/package.nix @@ -0,0 +1,109 @@ +{ + lib, + rustPlatform, + pkg-config, + makeWrapper, + makeDesktopItem, + fontconfig, + freetype, + libGL, + libx11, + libxcursor, + libxi, + libxrandr, + libxkbcommon, + vulkan-loader, + wayland, + xdg-utils, + version, +}: +{ + crateName, + binaryName, + withGui ? false, +}: +let + desktopItem = makeDesktopItem { + name = "objdiff"; + desktopName = "objdiff"; + comment = "Compare object files in decompilation projects"; + exec = "objdiff"; + icon = "objdiff"; + categories = [ "Development" ]; + startupWMClass = "objdiff"; + }; + runtimeLibraries = [ + libGL + libxkbcommon + vulkan-loader + wayland + libx11 + libxcursor + libxi + libxrandr + ]; +in +rustPlatform.buildRustPackage { + pname = crateName; + inherit version; + + src = lib.cleanSourceWith { + src = ../.; + filter = + path: type: + let + name = baseNameOf path; + in + !lib.elem name [ + ".git" + ".worktrees" + "result" + "target" + ]; + }; + + # Import registry dependencies directly from Cargo.lock so lock-file updates + # do not require refreshing a single vendor hash. + cargoLock.lockFile = ../Cargo.lock; + cargoBuildFlags = [ + "--package" + crateName + ]; + cargoTestFlags = [ + "--package" + crateName + ]; + + nativeBuildInputs = [ pkg-config ] ++ lib.optionals withGui [ makeWrapper ]; + buildInputs = lib.optionals withGui [ + fontconfig + freetype + wayland + libx11 + ]; + + env = lib.optionalAttrs withGui { + OBJDIFF_DISABLE_SELF_UPDATE = "1"; + }; + + postInstall = lib.optionalString withGui '' + wrapProgram "$out/bin/${binaryName}" \ + --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath runtimeLibraries} \ + --prefix PATH : ${lib.makeBinPath [ xdg-utils ]} + install -Dm644 objdiff-gui/assets/icon.png \ + "$out/share/icons/hicolor/512x512/apps/objdiff.png" + install -Dm644 ${desktopItem}/share/applications/objdiff.desktop \ + "$out/share/applications/objdiff.desktop" + ''; + + meta = { + description = "A local diffing tool for decompilation projects"; + homepage = "https://github.com/encounter/objdiff"; + license = with lib.licenses; [ + asl20 + mit + ]; + mainProgram = binaryName; + platforms = lib.platforms.linux; + }; +} diff --git a/objdiff-gui/src/app.rs b/objdiff-gui/src/app.rs index 0c2e061d..9d48c689 100644 --- a/objdiff-gui/src/app.rs +++ b/objdiff-gui/src/app.rs @@ -31,6 +31,7 @@ use crate::{ app_config::{AppConfigVersion, deserialize_config}, config::{ProjectObjectNode, load_project_config}, jobs::{create_objdiff_config, egui_waker, start_build}, + update::SELF_UPDATE_ENABLED, views::{ appearance::{Appearance, appearance_window}, config::{ @@ -507,7 +508,8 @@ impl App { if state.config.selected_obj.is_some() { state.queue_build = true; } - app.view_state.config_state.queue_check_update = state.config.auto_update_check; + app.view_state.config_state.queue_check_update = + SELF_UPDATE_ENABLED && state.config.auto_update_check; } app.appearance.init_fonts(&cc.egui_ctx); app.appearance.utc_offset = utc_offset; diff --git a/objdiff-gui/src/update.rs b/objdiff-gui/src/update.rs index 53defc26..edb53820 100644 --- a/objdiff-gui/src/update.rs +++ b/objdiff-gui/src/update.rs @@ -27,6 +27,7 @@ pub const BIN_NAME_NEW: &str = pub const BIN_NAME_OLD: &str = formatcp!("objdiff-{}-{}{}", OS, ARCH, std::env::consts::EXE_SUFFIX); pub const RELEASE_URL: &str = formatcp!("https://github.com/{}/{}/releases/latest", GITHUB_USER, GITHUB_REPO); +pub const SELF_UPDATE_ENABLED: bool = option_env!("OBJDIFF_DISABLE_SELF_UPDATE").is_none(); pub fn build_updater() -> Result> { Ok(Box::new( diff --git a/objdiff-gui/src/views/config.rs b/objdiff-gui/src/views/config.rs index 7e5b8a08..05079c53 100644 --- a/objdiff-gui/src/views/config.rs +++ b/objdiff-gui/src/views/config.rs @@ -24,7 +24,7 @@ use crate::{ config::ProjectObjectNode, hotkeys, jobs::{start_check_update, start_update}, - update::RELEASE_URL, + update::{RELEASE_URL, SELF_UPDATE_ENABLED}, views::{ appearance::Appearance, file::{FileDialogResult, FileDialogState}, @@ -175,15 +175,21 @@ pub fn config_ui( } = &mut *state_guard; ui.heading("Updates"); - ui.checkbox(auto_update_check, "Check for updates on startup"); - if ui.add_enabled(!config_state.check_update_running, egui::Button::new("Check now")).clicked() - { - config_state.queue_check_update = true; + if SELF_UPDATE_ENABLED { + ui.checkbox(auto_update_check, "Check for updates on startup"); + if ui + .add_enabled(!config_state.check_update_running, egui::Button::new("Check now")) + .clicked() + { + config_state.queue_check_update = true; + } + } else { + ui.label("Updates are managed by your package manager"); } ui.label(format!("Current version: {}", env!("CARGO_PKG_VERSION"))); if let Some(result) = &config_state.check_update { ui.label(format!("Latest version: {}", result.latest_release.version())); - if result.update_available { + if SELF_UPDATE_ENABLED && result.update_available { ui.colored_label(appearance.insert_color, "Update available"); ui.horizontal(|ui| { if let Some(bin_name) = &result.found_binary From cebc72ae79fdefe07ae598454d2995d983caa70c Mon Sep 17 00:00:00 2001 From: 0xsharkboy Date: Mon, 21 Sep 2026 23:37:46 +0200 Subject: [PATCH 2/3] ci: validate Nix packages on Linux --- .github/workflows/build.yaml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 25e4cae6..e8e418d8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -72,6 +72,37 @@ jobs: with: command: check ${{ matrix.checks }} + nix: + name: Nix (${{ matrix.system }}) + strategy: + matrix: + include: + - system: x86_64-linux + runner: ubuntu-24.04 + - system: aarch64-linux + runner: ubuntu-24.04-arm + fail-fast: false + runs-on: ${{ matrix.runner }} + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Free disk space for the Nix store + uses: wimpysworld/nothing-but-nix@v9 + - name: Install Nix + uses: cachix/install-nix-action@v31 + - name: Restore and save Nix store + uses: nix-community/cache-nix-action@v7 + with: + primary-key: nix-${{ matrix.system }}-${{ hashFiles('flake.lock', '**/*.nix', 'Cargo.toml', 'Cargo.lock') }} + restore-prefixes-first-match: nix-${{ matrix.system }}- + gc-max-store-size-linux: 5G + - name: Evaluate flake + run: nix flake check --no-build + - name: Build packages + run: nix build .#objdiff .#objdiff-cli --print-build-logs + - name: Check development shell + run: nix develop --command cargo +nightly --version + test: name: Test strategy: From ca9a99e15de3705c43a97ec4977df4ec9dca530e Mon Sep 17 00:00:00 2001 From: 0xsharkboy Date: Mon, 21 Sep 2026 23:38:01 +0200 Subject: [PATCH 3/3] docs: document Nix workflows --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 71786850..311e8278 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,37 @@ If specified, objdiff displays a list of objects in the sidebar for easy navigat ## Building +### Nix (Linux) + +The flake supports `x86_64-linux` and `aarch64-linux`. Run the GUI or CLI directly: + +```shell +nix run . +nix run .#objdiff-cli -- --help +``` + +Build or install either frontend: + +```shell +nix build .#objdiff +nix build .#objdiff-cli +nix profile install .#objdiff +nix profile install .#objdiff-cli +``` + +Enter the development shell for Rust, Node.js, the WASM toolchain, and the contribution tools: + +```shell +nix develop +cargo test +npm -C objdiff-wasm run build +``` + +The Nix GUI package disables the built-in self-updater because its executable is immutable. Update it +through Nix instead. + +### Cargo + Install Rust via [rustup](https://rustup.rs). ```shell