Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend TargetSpec functionality to rust-project.json #16135

Conversation

davidbarsky
Copy link
Contributor

Resolves #15892.

This PR, as structured, adds runnable support to rust-project.json-based workspaces, and that works pretty nicely. It's not as polished as I'd like it to be (namely: interpolation of test_id feels a bit janky and undocumented, the), but I'm opening this PR as a draft for general direction/feedback.

I think that this approach can also make #15476 unnecessary by adding a flycheck_command to rust-project.json while also extending similar functionality to Cargo only build the impacted crate.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 15, 2023
@davidbarsky davidbarsky force-pushed the davidbarsky/extend-cargotargetspec-to-rust-project branch 4 times, most recently from 20733f6 to 8903c89 Compare December 19, 2023 16:57
@bors
Copy link
Collaborator

bors commented Dec 20, 2023

☔ The latest upstream changes (presumably #16066) made this pull request unmergeable. Please resolve the merge conflicts.

@davidbarsky davidbarsky force-pushed the davidbarsky/extend-cargotargetspec-to-rust-project branch from 8903c89 to d91586f Compare December 24, 2023 00:17
Copy link
Member

@Veykril Veykril left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall implementation wise it looks good to me, al though I am still a bit on the fence wrt to having flycheck be in here. I am personally still of the opinion that flycheck should not be a direct part of r-a (but a separate extension altogether). Adding it to this file makes that less clear of a separation.

Comment on lines +281 to +288
pub enum TargetKindData {
Bin,
/// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
Lib,
Example,
Test,
Bench,
BuildScript,
Other,
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ties the cargo workspace architecture into project-json, I'm not sure that this was something @matklad had in mind when giving this alternative format for project descriptions.

Is there a need for things like BuildScript and Example? Bin, Lib and Test (and Other ofc) seem fine as they are rather general.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think #16135 (comment) answers this, but, yeah good catch of the specific smell here!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're not really needed, no—they were meant to try and make project-json more Cargo-like. I only really need Bin, Lib and Test if this PR doesn't take the approach that matklad suggested in his comment.

Comment on lines +96 to +99
/// [`project_manifest`] corresponds to the file defining a crate. With Cargo,
/// this will be a Cargo.toml, but with a non-Cargo build system, this might be
/// a `TARGETS` or `BUCK` file. Multiple, distinct crates can share a
/// single `project_manifest`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment confuses me given where its placed, this is the field of a CargoTargetSpec, so this should always be a Cargo.toml.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah—the intention behind this comment is to communicate that a non-Cargo project can also have a TargetSpec (née CargoTargetSpec), with the intention of trying to make non-Cargo build systems be more Cargo-like in order to reduce the maintenance burden on rust-analyzer, but as mentioned elsewhere, that might be a design bug.

Copy link
Member

@Veykril Veykril Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get that, it's just that the location for this command seems ill placed (as it talks about this struct, but also the copy, yet we are in the docs on the field), though i don't have a better spot for it either aside from somewhere in the module documentation.

@@ -372,7 +372,7 @@ interface Runnable {
}
```

rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look like this:
rust-analyzer supports two `kind`s of runnables, `"cargo"` and `"rustproject"`. The `args` for `"cargo"` look like this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

projectjson, not rustproject right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sorry! I get the two switched up in my head.

@@ -385,6 +385,16 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
}
```

The args for `"jsonproject"` look like:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

projectjson

args: args.slice(1),
cwd: projectJsonRunnableArgs.workspaceRoot,
env: prepareEnv(runnable, config.runnablesExtraEnv),
overrideCargo: "buck2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean only buck2 is supported for projectjson runnables?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ideally we'd fetch buck2 string from projectJsonRunnableArgs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, sorry—I meant to pull this string from configuration. Didn’t realize I left it here.

@Veykril Veykril added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 10, 2024
@@ -202,6 +243,8 @@ struct CrateData {
is_proc_macro: bool,
#[serde(default)]
repository: Option<String>,
#[serde(default)]
target_spec: Option<TargetSpecData>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some notes on JSON side of things here:

I'd maybe call the top-level thing build_info, as thre might be multiple targets (in buck2 sense) concerning a single crate. And, well, target is already overloaded, so it's better to avoid pilling more meanings onto that poor word.

I think the core feature here should be an inclusion of opaque blob of data, which is transmitted over LSP so that the custom client may interpret this. So, I'd say the basis should look like

build_info: Option<BuildInfo>

struct BuildInfo {
    data: serde_json::Value
}

Eg, one can imagine a situation where all builds are remote, and the way you "run" software is by issueing an HTTP request to a remote execution server, and opening a page in the web-browser with results. In this situation, the data would be an URL to call, and the client-side extension would take care of HTTPing.

But we also should support "standard" workflows where you just run some command line.

So, something like (pardon my TypeScript)

struct BuildInfo {
    data: serde_json::Value
    runnables: Vec<Runnable>
}

struct Runnable {
    kind: "build" | "check" | "test" | "run" | string,
    label: string,
    argv: string[],
    cwd: string
}

This shouldn't use a json map, but rather an array, to be more flexible. Eg, in the future we might want to add --release and non---release versions of tests, and with a list it should be simpler. More generally, I've recently read some JSON API guidlines somewhere (don't remember whether though), and one guideline that stuck with me is that you should represent maps in JSON as arrays (assoc lists). I think that's a good guideline and something we should be using in gerneral.

Then, the kind should be a semi-open enum. rust-analyzer should treat build, run, check and test specially, but it should also be able to show other tasks in GUI. Eg, you can imagine a "flush" task for an embedded build system, or some such.

We might want to split check into two --- one check that is run for the user and displays errors in the terminal, and another check that runs with message-format=json and is used for flycheck. I guess we could call those check and flycheck?

For tests, we need to think how to enable filtering. The core feature is that you can run a single test using cargo test -- $TEST_NAME, and all tests using cargo test. Note that this might be very different in non-cargo build systems. Eg, it could be builder test and builder test --test-filter=$TEST_NAME, where builder test --test-filter='' would run zero tests, rather than all tests. So I think for test, we should also split it into two: "testall" and "testone". And, for "testone", I think we want to make argv a template. That is, argv could look like

["builder", "test", "--test-filter=$$TEST_NAME$$", "./buildfile.b"]

We shouldn't assume that filter is always last, and that its always a separate argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback here!

I'd maybe call the top-level thing build_info, as thre might be multiple targets (in buck2 sense) concerning a single crate. And, well, target is already overloaded, so it's better to avoid pilling more meanings onto that poor word.

That's a fair point. I didn't intend for the naming to be Buck/Bazel-esque "targets", I mean to invoke the target concept in Cargo, of all things. In any case, target is too overloaded and I'll move over to build_info.

I think the core feature here should be an inclusion of opaque blob of data, which is transmitted over LSP so that the custom client may interpret this.

Is the idea that a companion extension to rust-lang.rust-analyzer (e.g., something for Buck or Bazel) would be responsible for assembling that opaque blob of data into a some sort of meaningful runnable command or is more that since I'm making this change now, I should make it more futureproof ("[B]ut we also should support "standard" workflows where you just run some command line.")? I ask since I'm working on a variable interpreter/template thingy, which—I think—should make expressing arbitrary build/test commands a lot easier in rust-analyzer itself, which reduces the pressure/need for end-users to install other extensions for runnables.

This shouldn't use a json map, but rather an array, to be more flexible. Eg, in the future we might want to add --release and non---release versions of tests, and with a list it should be simpler. More generally, I've recently read some JSON API guidlines somewhere (don't remember whether though), and one guideline that stuck with me is that you should represent maps in JSON as arrays (assoc lists). I think that's a good guideline and something we should be using in gerneral.

That seems reasonable to me! I was considering how to tackle this for Buck1 and this seems like a good approach.

Then, the kind should be a semi-open enum. rust-analyzer should treat build, run, check and test specially, but it should also be able to show other tasks in GUI. Eg, you can imagine a "flush" task for an embedded build system, or some such.

Yeah, that seems smart.

We might want to split check into two --- one check that is run for the user and displays errors in the terminal, and another check that runs with message-format=json and is used for flycheck. I guess we could call those check and flycheck?

I think now's a good time to make this change, especially if the new runnable API is a breaking change.

For tests, we need to think how to enable filtering. The core feature is that you can run a single test using cargo test -- $TEST_NAME, and all tests using cargo test. Note that this might be very different in non-cargo build systems. Eg, it could be builder test and builder test --test-filter=$TEST_NAME, where builder test --test-filter='' would run zero tests, rather than all tests. So I think for test, we should also split it into two: "testall" and "testone". And, for "testone", I think we want to make argv a template. That is, argv could look like: [redacted for quoting ease]

I don't think I've fully gotten this into my head yet (sorry, lots of travel!) but I think I see the utility of a change like this.

I think I'd like like a debug variant as well, but I haven't really through this specific aspect through.

Footnotes

  1. For context, buck2 is moving to a modifier-style approach. A user can write buck2 build repo//foo:bar?linux,release, which translate to "build repo//foo:bar for Linux using release". there's a more verbose --modifier-style API, but I suspect that both options can work when called from rust-analyzer or I suspect the Buck team will be flexible in implementing this.

@Veykril
Copy link
Member

Veykril commented Jan 12, 2024

Overall implementation wise it looks good to me, al though I am still a bit on the fence wrt to having flycheck be in here. I am personally still of the opinion that flycheck should not be a direct part of r-a (but a separate extension altogether). Adding it to this file makes that less clear of a separation.

I guess whether the format has that or not makes little difference, the command itself is heavily tied into the rest of the specification here whether it is supported by a separate extension or r-a.

@davidbarsky
Copy link
Contributor Author

Overall implementation wise it looks good to me, al though I am still a bit on the fence wrt to having flycheck be in here. I am personally still of the opinion that flycheck should not be a direct part of r-a (but a separate extension altogether). Adding it to this file makes that less clear of a separation.

I guess whether the format has that or not makes little difference, the command itself is heavily tied into the rest of the specification here whether it is supported by a separate extension or r-a.

While I agree with you that flycheck is a hack, I've found myself of the opinion that rust-analyzer (and specifically, its users who are not interested/aware of the need to install a build system-specific extension) can likely get a ridiculous amount of mileage through a templates commands and a promise from build systems to place rustc diagnostics in a deterministic location (like, say, stdout).

And yeah, I think the command's specification is largely immaterial to where it runs, or at the very least, it's not a binding commitment in one direction or another.

@bors
Copy link
Collaborator

bors commented Jan 19, 2024

☔ The latest upstream changes (presumably #16398) made this pull request unmergeable. Please resolve the merge conflicts.

@bors
Copy link
Collaborator

bors commented Feb 20, 2024

☔ The latest upstream changes (presumably #16612) made this pull request unmergeable. Please resolve the merge conflicts.

@bors
Copy link
Collaborator

bors commented Mar 15, 2024

☔ The latest upstream changes (presumably #16845) made this pull request unmergeable. Please resolve the merge conflicts.

bors added a commit that referenced this pull request Mar 18, 2024
Refactor extension to support arbitrary shell command runnables

Currently, the extension assumes that all runnables invoke cargo. Arguments are sometimes full CLI arguments, and sometimes arguments passed to a cargo subcommand.

Refactor the extension so that tasks are just a `program` and a list of strings `args`, and rename `CargoTask` to `RustTask` to make it generic.

(This was factored out of #16135 and tidied.)
@Wilfred Wilfred force-pushed the davidbarsky/extend-cargotargetspec-to-rust-project branch 2 times, most recently from 089a35d to 843626e Compare March 28, 2024 00:26
@bors
Copy link
Collaborator

bors commented Mar 29, 2024

☔ The latest upstream changes (presumably #16971) made this pull request unmergeable. Please resolve the merge conflicts.

@Wilfred Wilfred force-pushed the davidbarsky/extend-cargotargetspec-to-rust-project branch from 843626e to 4c8759c Compare March 29, 2024 21:41
@bors
Copy link
Collaborator

bors commented Apr 1, 2024

☔ The latest upstream changes (presumably #16970) made this pull request unmergeable. Please resolve the merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add an Optional CargoTargetSpec-like Data to rust-project.json
5 participants