cargo_metadata icon indicating copy to clipboard operation
cargo_metadata copied to clipboard

Make it easier to build an artifact and record its path

Open jyn514 opened this issue 3 years ago • 3 comments

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 way MetadataCommand 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.

jyn514 avatar Feb 26 '21 19:02 jyn514

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.

jyn514 avatar Feb 26 '21 19:02 jyn514

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`");

jyn514 avatar Feb 26 '21 19:02 jyn514

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.

oli-obk avatar Mar 01 '21 10:03 oli-obk