What is the best way to get the name of the binary that cargo will produce?

I had a method in place:

cargo pkgid | cut -d '#' -f 2 | cut -d ':' -f 1

but I think that this is behaving differently as of 1.26, as I'm getting a different output based on the current directory name. If the directory name matches the package name, the package name isn't separated from the current directory, e.g ${cwd}#${version}. If they mismatch, then there is an additional separator, e.g. ${cwd}#${package}:${version}.

I filed an issue with Cargo here but I have a feeling that it may have always worked this way but the recommendation of the above was inaccurate.

What is the current best way to get the binary name that Cargo will produce from src/main.rs?

It's probably easier, or at least more accurate, to use cargo metadata and a json parser. You want "packages" -> "targets" selected where "kind" is "bin".

2 Likes

This is what I've got. It was a little painful to divine this:

cargo metadata --format-version 1 | jq -r '.packages[].targets[] | select( .kind | map(. == "bin") | any ) | select ( .src_path | contains(".cargo/registry") | . != true ) | .name'

Let's see if I can break that down visually:

cargo metadata --format-version 1 | \
jq -r '.packages[].targets[] | 
  select( 
    .kind | map(. == "bin") | any 
  ) | 
  select ( 
    .src_path | contains(".cargo/registry") | . != true 
  ) | 
  .name'

Or, for all packages, for all targets, for any target having a kind array containing an element bin, for any of those having a src_path that does not contain .cargo/registry, get me the name.

That's convoluted…

There must be some more direct way.

I think cargo metadata --no-deps will at least filter the stuff from the registry.

cargo metadata --no-deps --format-version 1 | jq -r '.packages[].targets[] | select( .kind | map(. == "bin") | any ) | .name'

Much simpler!

I still wish there was a simple command to get this. It seems like needing that would be more common than it is…?

Now, here's how I'm getting the name and version of the package – not the binary – instead of relying on pkgid:

cargo metadata --no-deps --format-version 1 | jq -r '.packages[0] | [ .name, .version ] | join("-")'

@colindean could you expand a little bit why you are looking for package or binary name?
"Name of the binary" is actually a pretty complex business, and the question itself is under specified.

If you have a particular crate whose name is important to you, I suggest just hard-coding it.

If you want to write a generic automation which handles any crate, here are some bullet points to consider:

  • In general, Cargo project is a workspace of many packages, and each package can have its own binary. To learn the set of packages inside a worksapce, look at the members field of cargo metadata

  • The name of the binary, even for src/main.rs, is not necessary derived from the name of the package. To learn the name, take a look at name field of the corresponding target

  • A package could have several binaries, and neither of them is inherently "primary"

  • The name of the binary depends on the platform: foo vs foo.exe.

  • A binary is not necessary a single file. On windows and macs, Cargo also produces debug information in a separate file.

  • Besides cargo metadata, there's also cargo build --message-format=json, which will print full paths to artifacts as they are produced

  • There's also (unstable for now) cargo build --output-dir=out option which instructs Cargo to put final artifacts to the specified dir.

2 Likes

There is another way: just use the variables that Cargo makes available in build.rs, specifically CARGO_PKG_NAME. You can read more about that here.

3 Likes

Excellent question. I need the binary name because I want to create some Makefile targets that look like this:

ARTIFACT := $(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[].targets[] | select( .kind | map(. == "bin") | any ) | .name' )

DEBUG_BINARY := target/debug/$(ARTIFACT)
RELEASE_BINARY := target/release/$(ARTIFACT)

release: $(RELEASE_BINARY) ## creates a release build
	strip $<
	upx $<

debug: $(DEBUG_BINARY) ## creates a debug build

$(DEBUG_BINARY):
	cargo build

$(RELEASE_BINARY):
	cargo build --release

I want the package name and version because I want to have a single source for the name and version of the package: the Cargo.toml. I use that name and version in the documentation generation tasks:

QUALIFIED_PACKAGE = $(shell cargo metadata --no-deps --format-version 1 | jq -r '.packages[0] | [ .name, .version ] | join("-")' )

doc: $(DOC_DIR) ## builds documentation

zip: $(DOC_DIR) ## builds a ZIP file containing documentation
	cd $(DOC_DIR) && zip -r ../$(QUALIFIED_PACKAGE)-doc.zip .

$(DOC_DIR):
	cargo doc --no-deps

It's entirely possible that these aren't really necessary anymore. We built this Makefile when we were just starting into our… "oxidization."

1 Like

Hm, interesting!

If I were you, I'd just grepped Cargo.toml for name/version: it should be simpler than wrestling with cargo metadata and should cover this isolated use-case. Doing this "properly" is going to be hard, because Cargo's project model is pretty involved. RELEASE_BINARY := target/release/$(ARTIFACT) already places a lot of assumptions about project structure.

Yeah, in hindsight the perception of the brittleness of that method was inaccurate, compared to the complexity of the end solution.

If I could parse that out of cargo metadata, I would! I don't remember seeing that.

It looks like if the crate is part of a workspace, the solution:

cargo metadata --no-deps --format-version 1 | jq -r '.packages[].targets[] | select( .kind | map(. == "bin") | any ) | .name'

does not work: it extracts all binary names in the workspace, instead of the single binary name of the crate of the current folder.

Any other solution?

Please don't bump old threads. It's best to create a new one and link back as appropriate.

1 Like