cargo_metadata
cargo_metadata copied to clipboard
Make it easier to build an artifact and record its path
Right now, there's an example on the front page, which is much appreciated:
let mut command = Command::new("cargo")
.args(&["build", "--message-format=json-render-diagnostics"])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let reader = std::io::BufReader::new(command.stdout.take().unwrap());
for message in cargo_metadata::Message::parse_stream(reader) {
// ...
}
let output = command.wait().expect("Couldn't get cargo's exit status");
This has a few issues though:
- It doesn't handle
CARGO
the wayMetadataCommand
does: https://docs.rs/cargo_metadata/0.12.3/src/cargo_metadata/lib.rs.html#557 - Because it's an example and it's long, it's easy for anyone using it to copy/paste it once and never update it. This isn't a giant deal, but it does make the code a little harder to read and misses improvements like https://github.com/oli-obk/cargo_metadata/pull/147.
It would be really useful for MetadataCommand
to work for things other than cargo metadata
itself. Maybe this is as simple as adding MetadataSubcommand(&mut self, cmd: String) -> Self
, and replacing metadata --format-version=1
with that command? Everything else should work the same since other_options
is public.
I guess this means exec()
returning a Metadata
no longer makes sense, it would have to remove Vec<Message>
or something like that. Maybe you could play around with typestate somehow so the return type depends on the subcommand? I haven't thought about it in detail.
If it helps, this is how I adapted the example:
// First, build `threenp1` and record the generated executable
// Adapted from the example at https://docs.rs/cargo_metadata/0.12.3/cargo_metadata/#examples.
// TODO: I opened https://github.com/oli-obk/cargo_metadata/issues/153 to hopefully require less copy/pasting in the future.
let mut command = Command::new(std::env::var("CARGO").unwrap_or("cargo"))
.args(&["build", "--example=threenp1", "--message-format=json-render-diagnostics"])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let reader = std::io::BufReader::new(command.stdout.take().unwrap());
let threenp1 = cargo_metadata::Message::parse_stream(reader).find_map(|message| {
// original jq script: `select(.reason == "compiler-artifact" and (.target.kind[] | contains("example"))) | .executable`
if let Message::CompilerArtifact(Artifact { executable, target, .. }) = message {
if target.kind.contains("example") {
return executable;
}
}
None
}).unwrap();
let output = command.wait().unwrap();
assert!(output.status.success(), "failed to run `cargo build`");
We could certainly start exposing more helpers (e.g. for getting a cargo command), or just use something like escargot
and reexport things, but I'm not sure how much sugar we should add on top of this crate. I can totally see adding a cargo_metadata::Message::from_cmd_stdout
that you can pass a Command
to and that will take care of setting stdout, running the thing and using bufreaders and all. Since cargo is such a common use case, just having a from_cargo
that takes a list of arguments would also be ok.
I'm less sold on the builder/typestate solution, but maybe I'm just not actually seeing what you are envisioning.