Skip to content

[bug] tauri-bundler stage-2 src/bin/ disk scan ignores [[bin]] required-features #15325

@guoliu

Description

@guoliu

Describe the bug

tauri-cli's get_binaries() runs in two stages when enumerating binaries to bundle. Stage 1 reads [[bin]] entries from Cargo.toml and correctly skips entries whose required-features aren't in the build's enabled feature set. Stage 2 walks src-tauri/src/bin/ from disk and adds back every name not already in the list — with no required-features filter. The result: a [[bin]] correctly gated on required-features = ["dev-only-feature"] is still picked up by the bundler if its source happens to live under src/bin/, and the bundler then fails trying to copy a binary cargo never built.

This breaks the standard Cargo convention of putting developer-tooling bins under src/bin/ and gating them on a feature so they don't ship in release. The release-mode failure surfaces as a confusing "file not found" copy error in the bundler that doesn't point to the disk-scan logic.

Reproduction

A Tauri 2 app with two bins, the second gated on a dev-only feature:

# src-tauri/Cargo.toml
[[bin]]
name = "my-app"
path = "src/main.rs"

[[bin]]
name = "generate-bindings"
path = "src/bin/generate-bindings.rs"
required-features = ["dev-tools"]

[features]
dev-tools = []

src/bin/generate-bindings.rs:

fn main() { println!("dev tool"); }

Run cargo tauri build (release, no --features dev-tools):

Bundling my-app.app (...)
   Error failed to bundle project Failed to copy binary from
   ".../release/generate-bindings": `".../release/generate-bindings" does not exist`

Cargo correctly didn't build generate-bindings because dev-tools wasn't enabled. The bundler tries to copy it anyway because Stage 2's disk scan in crates/tauri-cli/src/interface/rust.rs get_binaries() (around lines 963–1002) found the source file and added the bin back to the list.

Workaround that works: move the source out of src/bin/ to e.g. dev-bin/ and update [[bin]] path =. Stage 2 only walks src/bin/, so it finds nothing; Stage 1's required-features check is the only gate that runs. Fragile (relies on the disk-scan path not getting broadened later) and breaks the Cargo convention.

Expected behavior

Stage 2's disk scan should consult the manifest before re-adding a discovered name. If a [[bin]] entry exists for that name with required-features not satisfied by the current build, skip it — same gate as Stage 1.

Pseudocode for the fix:

// In Stage 2, after detecting a bin name from src/bin/<name>.rs:
let manifest_entry = manifest_bins.iter().find(|b| b.name == name);
if let Some(entry) = manifest_entry {
    if !entry.required_features.iter().all(|f| options.features.contains(f)) {
        continue;
    }
}

This preserves auto-discovery for bins without a Cargo.toml entry but stops re-enabling explicitly gated ones.

Alternative (less invasive): expose a bundle.bin allowlist in tauri.conf.json so users can declare "only bundle these names" without depending on auto-discovery to do the right thing. Tracked at #9180.

Full tauri info output

[⚠] Environment
    - OS: Mac OS 26.3.1 arm64 (X64)
    ✔ Xcode Command Line Tools: installed
    ✔ rustc: 1.93.0 (254b59607 2026-01-19) (Homebrew)
    ✔ cargo: 1.93.0 (Homebrew)
    - node: 23.7.0
    - pnpm: 10.29.3
    - npm: 10.9.2
    - bun: 1.3.9

[-] Packages
    - tauri 🦀: 2.10.2, (outdated, latest: 2.11.0)
    - tauri-build 🦀: 2.5.5, (outdated, latest: 2.6.0)
    - wry 🦀: 0.54.2, (outdated, latest: 0.55.0)
    - tao 🦀: 0.34.5, (outdated, latest: 0.35.0)
    - @tauri-apps/api : 2.10.1 (outdated, latest: 2.11.0)
    - @tauri-apps/cli : 2.8.0 (outdated, latest: 2.11.0)

[-] Plugins
    - tauri-plugin-shell 🦀: 2.3.5
    - tauri-plugin-dialog 🦀: 2.6.0
    - tauri-plugin-updater 🦀: 2.10.0
    - tauri-plugin-fs 🦀: 2.5.0
    - tauri-plugin-single-instance 🦀: 2.4.0
    - tauri-plugin-opener 🦀: 2.5.3
    - tauri-plugin-deep-link 🦀: 2.4.7
    - tauri-plugin-global-shortcut 🦀: 2.3.1
    - tauri-plugin-log 🦀: 2.8.0
    - tauri-plugin-process 🦀: 2.3.1

[-] App
    - build-type: bundle
    - CSP: unset
    - frontendDist: ../dist
    - devUrl: http://localhost:1420/
    - bundler: Rollup

(Disk-scan logic is platform-independent — verified against current tauri-cli source on dev as of 2026-05-02.)

Stack trace

   Compiling moss v0.6.4 (.../src-tauri)
    Finished `release-optimized` profile [optimized] target(s) in 8m 14s
       Built application at: .../target/universal-apple-darwin/release-optimized/moss
    Bundling moss.app (.../target/universal-apple-darwin/release-optimized/bundle/macos/moss.app)
failed to bundle project Failed to copy binary from
".../target/universal-apple-darwin/release-optimized/generate-bindings":
`".../target/universal-apple-darwin/release-optimized/generate-bindings" does not exist`
       Error failed to bundle project Failed to copy binary from
".../target/universal-apple-darwin/release-optimized/generate-bindings":
`".../target/universal-apple-darwin/release-optimized/generate-bindings" does not exist`
##[error]Process completed with exit code 1.

Additional context

Hit on three consecutive release attempts of an open-source Tauri 2 app before tracing it to tauri-cli. Two prior attempted hotfixes failed because they assumed required-features alone was the gate:

  1. Gating main() behind cfg(debug_assertions) — bin compiled in release but produced no useful binary; bundler still tried to copy it (or rather, it was built so the bundler "worked" but was including a useless artifact).
  2. Adding required-features = ["dev-tools"] to the [[bin]] — Stage 1 honored it, Stage 2 didn't; bundle still failed.

The eventually working workaround was to move the file out of src/bin/ entirely. That goes against the standard Cargo idiom for developer-tooling bins.

Related: #9180 (open feature request for an explicit bin allowlist; would also solve this if implemented).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions