Easy way to access the built binary

I often need to do something to or with the binary I've just build, and would find it useful if cargo provided an easy way to find and access the location of the built binary, regardless of whether it has actually been built.

Possible Solution

In a way, the command would be like cargo run without actually running the executable. It would just output the (by default absolute or relative, whichever wins the bikeshed) path for the binary in plaintext format. For example,

cargo binpath

->

/home/hellouser/projects/hello/target/debug/hello

The command would accept the arguments that affect the build location, similar to cargo run, such as:

  • --release or --profile ...
  • --target ...
  • --target-dir ...

No binary would be built, allowing the command to run blazingly fast quickly.

Rationale

Using the compiled binary directly has some advantages over cargo run.

Manual development tasks:

  • Profile or benchmark the executable without noise from cargo.
    • hyperfine --warmup 3 target/release/my_bin_name
    • flamegraph target/release-with-debuginfo/my_bin_name
  • Run the binary with a debugger.
    • rust-gdb target/debug/my_bin_name

It's inconvenient to type target/the_target_platform/the_profile_that_was_used/the_name_of_the_binary:

  • It's many keypresses, even when using autocompletion.
  • At worst, you have to remember or look up the profile, target triplet, and actual name of the binary.
  • You have to remember the structure that the path follows, i.e. the order of the above things.
  • The fact that cargo run already knows everything whereas you need to figure it out is frustrating.

Automation:

  • Handling or poking the binary in the CI, e.g.
    • Copying it somewhere else or packaging it into an archive
      • cp target/x86_64-unknown-linux-gnu/different-profile/my_bin_name my_bin_dir
      • zip myzip.zip target/x86_64-unknown-linux-gnu/different-profile/my_bin_name
    • Using various tools output debug information on the file
      • file target/x86_64-unknown-linux-gnu/different/try_bin_name
      • ldd target/x86_64-unknown-linux-gnu/different/try_bin_name
      • strings target/x86_64-unknown-linux-gnu/different/try_bin_name | grep company_secret
  • Local automation in a Makefile or similar

Problems:

  • Hardcoding the same binary name into many places leads to changes to many places later.
  • The path, especially when repeated often, is verbose. It's also in an "encoded" (although easy-to-understand) format that's more difficult to look up than command-line parameters to cargo.

Current Workarounds

In my opinion, this is already stretching it, but you can get to the root of the target directory with:

cargo metadata --format-version=1 --no-deps | jq '.target_directory'

cargo metadata doesn't support --release, --profile, or --target. If you want to use those, you need to add them manually. Either way, you also need to add the binary name.

Alternatively, you can tell cargo to build and provide the output as json with --message-format json. Then, use jq:

cargo build --profile different --target x86_64-unknown-linux-gnu --message-format json | jq -s '.[0].executable'

The downside here is that the command will do the build.

Instead of jq, others tools such as grep could be used.

It all feels brittle and doesn't scale well when more advanced cargo configuration, workspaces etc. are used.

There's also the unstable --artifact-dir option which could solve the problem at least partway.

Related

7 Likes

I feel there are two different but related problems brought up here:

  • Direct running of the binary
  • "Packaging" the binary

These are related but imo have different requirements. For packaging-like operation, there may be additional files you want, like debug files, sbom, or maybe custom files (Allow build scripts to stage final artifacts · Issue #13663 · rust-lang/cargo · GitHub). A single output for a binary would be insufficient for these. You mentioned --artifact-dir as a workaround but to me that seems like the solution to this problem.

For local use, I can understand wanting to know the binary. However, I feel like a cargo binpath or cargo which would likely be as bloated to use as writing out the path. And writing finding the file in target-dir may become easier. In the upcoming 1.91, we've split the concept of target-dir into target-dir (final artifacts) and build-dir (intermediate artifacts) and allow people to move the intermediate artifacts out of target-dir. We hope to eventually change the default for build-dir to be moved out of target-dir. In this scenario, there won't be as much irrelevant content to be interacting with. If we still don't consider that enough, we could consider having cargo build enumerate the final artifacts but that could get noisy in a workspace cargo doc lists one artifact and says "and N other files". Maybe that would be sufficient for people to find the others (except for --target A --target B cases)?

1 Like

Now that I think of your reasoning, I agree with you on --artifact-dir solving the packaging side of the problem. Having quick access to the binary name might still be convenient but difficult to justify as required for this.

Nice! Having fewer files cluttering this space would already help. Countless times I have stared at the autocompletion results, waiting for my brain to figure which of them is the file I'm looking for. :slight_smile:

2 Likes

Having slept on the thought of cargo binpath (or which, as you suggest, which would work perfectly well as well), an alternative comes to my mind: What if we just kept using cargo run, but added a --with option as in "run with", where you can specify the command to use. E.g., cargo run --with "ldd"?

I'm not sure how the built binary is currently executed – is it just exec'd, replacing the running cargo process? If so, this would also work with the more sensitive tools such as benchmarking and debugging software.

2 Likes

We have target runner today and so you could do cargo run --config 'target."cfg(true)".runner="ldd"'. Not the most ergonomic. Huh, in looking for issues related to our debugging story, I found Make it possible to run binaries produced by cargo directly. · Issue #3670 · rust-lang/cargo · GitHub. Still need to dig through it to see what was said before on this topic.

Oh, seems that the proposals in this thread so far are pretty much a duplicate of the GitHub issue you linked. Either way, that's a good find!

In the linked GitHub issue, matrach suggests the following command that builds on the target runner you brought up:

cargo --config "target.'cfg(unix)'.runner = 'ls'" run

If we add --quiet, it will only output the binary name. Edit: And let's use echo instead of ls, as it seems to be two orders of magnitude faster.

Now, we can get quite a versatile command by aliasing that:

alias cargo-which="cargo --quiet --config \"target.'cfg(unix)'.runner = 'echo'\" run"

For example:

$ cargo-which 
target/debug/binpath_example
$ cargo-which --release
target/release/binpath_example
$ cargo-which --profile different
target/different/binpath_example
$ cargo-which --profile different --target x86_64-unknown-linux-gnu
target/x86_64-unknown-linux-gnu/different/binpath_example

And consequently:

$ hyperfine --warmup 3 $(cargo-which --profile different --target x86_64-unknown-linux-gnu)
Benchmark 1: target/x86_64-unknown-linux-gnu/different/binpath_example
  Time (mean ± σ):     642.0 µs ±  36.7 µs    [User: 385.5 µs, System: 314.5 µs]
  Range (min … max):   581.1 µs … 907.2 µs    2217 runs
7 Likes

You could make a which Cargo alias too.

That seems quite powerful in fact. I had forgotten Cargo aliases exist... :smiley:

For posterity, the following alias in a project's .cargo/config.toml[1] file gives the same feature as the Bash alias in my comment above, with the addition that it can be easily version-controlled:

[alias]
which = ["run", "--quiet", "--config", "target.'cfg(true)'.runner='echo'"]

I think the major difference between ls and echo boils down to accessing the path even if – somehow – the binary disappears before its location is given to the command.


  1. Configuration - The Cargo Book ↩︎

4 Likes