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

Add rust_analyzer rule to generate a rust-project.json #505

Merged
merged 57 commits into from
Feb 16, 2021

Conversation

djmarcin
Copy link
Contributor

@djmarcin djmarcin commented Nov 26, 2020

This PR builds on the work in #384 to add a rust_analyzer rule which generates a rust-project.json. This file allows rust-analyzer to function without cargo by describing the code structure, layout, and dependency graph.

I think this PR is ready for review, but looking to get feedback on the following:

  • The rule requires the user to manually enter their bazel info execution_root in order to locate remote crates and out_dirs. Is there a better way to retrieve this from inside starlark without resorting to genrules? I'd like to leave a genrule rewrite to a later date to unblock the relatively large win this PR brings.
  • The rule assumes that the generated rust-project.json will be used directly from bazel-bin and generates relative paths for the source code under the workspace root. If there is a way to get the absolute path to the workspace root, this would not be necessary.
  • How should this be tested?

Other than that, I think this is good to go.

@google-cla google-cla bot added the cla: yes label Nov 26, 2020
Copy link
Collaborator

@UebelAndre UebelAndre left a comment

Choose a reason for hiding this comment

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

A few comments from a non maintainer 😀

You should also wire all this up to stardoc and run docs/update_docs.sh.

This is looking super awesome!!

for flag in ctx.rule.attr.rustc_flags:
# --cfg flags should be passed as well but as an atomic
# config, not key/value
if flag.startswith("--cfg"):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this not be --cfg=?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The = is optional, and could be a space. Updated to be explicit about both possibilities.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah cool 👍

The [6:] was throwing me off and seemed like an oversight. Thanks for updating that 🙏

rust/private/rust_analyzer.bzl Show resolved Hide resolved
idx += 1
output["crates"].append(crate)

ctx.actions.write(output = ctx.outputs.filename, content = struct(**output).to_json())
Copy link
Collaborator

Choose a reason for hiding this comment

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

You might want to add a todo or something to replace the to_json with https://docs.bazel.build/versions/master/skylark/lib/json.html just to let future authors know about it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a TODO, it's not in any released version yet

aspects = [rust_analyzer_aspect],
doc = "List of all targets to be included in the index",
),
"exec_root": attr.string(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Very interested in avoiding this. Is there no way to get this infer this information?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The rule requires the user to manually enter their bazel info execution_root in order to locate remote crates and out_dirs. Is there a better way to retrieve this from inside starlark without resorting to genrules? I'd like to leave a genrule rewrite to a later date to unblock the relatively large win this PR brings.

Can you explain more on why needing execution_root is necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree, this is the biggest issue with this PR. I believe the one real viable alternative is setting a custom --workspace_status_command https://docs.bazel.build/versions/master/user-manual.html#flag--workspace_status_command to provide this information via an environment variable, but very interested to know if I missed another way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe @damienmg or @mfarrugi might know?

The thing I want from this PR is to be able to push a change to my repo and have enabled rust-analyzer support for anyone with no additional work. Having someone need to explicitly set the output of bazel info execution_root sounds like it wouldn't support that. I think --workspace_status_command might be something that works? Since I could commit a .bazelrc file to the repo to enable these rules. But definitely interested in finding something more contained. I'm trying to find such a solution but am having no luck 😞

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason why it's necessary to get this information is that we must provide a path to various external source files, e.g. the rust sysroot, any third party crates you depend on, etc. All of the paths generated during the action analysis phase are relative to the execution root, and are not available via the bazel-* symlinks, so it's not possible to generate a relative path from the user's source directory to the external sources.

I'm toying with the idea of a simple genrule that can get execution_root and uses sed as a template expander, but it's not portable (e.g. to windows). There is a built-in action ctx.actions.expand_template, but unfortunately in our case the substitution would be in an output and that rule requires it to be available as a starlark variable, so I don't think it will work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should also mention that I'm really not sure how --workspace_status_command or even the genfiles solution would work, because you can't recursively run bazel info in order to find the workspace at runtime. So, passing the exec_root is still the best solution I have that works at the moment, unsatisfying as it is.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If there's a path forward with genrule, you could use cmd_ps or cmd_bat to handle the windows specific behavior, right?

Copy link
Contributor Author

@djmarcin djmarcin Nov 27, 2020

Choose a reason for hiding this comment

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

Ok a few updates here:

  1. The bazel-{workspace} symlink does point at the execution root, I don't know how I missed that before. I'm not sure this actually helps because I'm not sure I can get the workspace name in a normal (non-repository) rule either, and also because of (2)

  2. Due to how symlinks work, bazel-bin/.. is not your current directory, it is actually a path in the execution root. For example, if bazel-bin -> /home/vagrant/.cache/bazel/_bazel_vagrant/71ed060a30795ab0736b520a305b19cd/execroot/__main__/bazel-out/k8-fastbuild/bin then bazel-bin/.. is actually .../execroot/__main__/bazel-out/k8-fastbuild
    This has a couple of consequences. First, the way this PR resolves local source code is wrong, but for whatever reason it doesn't look like this is breaking anything. Second, we could drop the exec_root parameter if we were willing to make the assumption that the execroot is at bazel-bin/../../../, which I think is valid and fairly safe. This is assuming that the generated rust-project.json will be used directly from bazel-bin, which is the current assumption made in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason I don't fully understand yet, switching to entirely relative paths based on this assumption breaks rust-analyzer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is fixed by adding the gazelle-like utility run target.

rust/repositories.bzl Show resolved Hide resolved
rust/private/rust_analyzer.bzl Show resolved Hide resolved
rust/private/rust_analyzer.bzl Show resolved Hide resolved
]

RustAnalyzerInfo = provider(
"RustAnalyzerInfo holds rust crate metadata for targets",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not, can you use the doc keyword here?

@UebelAndre
Copy link
Collaborator

@djmarcin Hey, can you take a moment to fix the conflicts?

@illicitonion @hlopko Can you guys take a look at this? I think it'd be best to get this merged and allow more people to use it and make incremental improvements to it.

@djmarcin
Copy link
Contributor Author

djmarcin commented Feb 14, 2021

I merged up to latest HEAD, but it seems that something in the intervening changes (or maybe a bad merge?) busted this PR so that now it gets stuck in an infinite loop inside bazel and eventually OOMs. This happens after the aspect evaluation but before the rust_analyzer rule executes, so I'm not entirely sure how to debug it.

Nevermind, it seems that my local bazel server was in a bad state. Restarting it seems to have fixed it.

Copy link
Collaborator

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

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

Looks great - thanks!

@illicitonion illicitonion merged commit 8a56582 into bazelbuild:main Feb 16, 2021
@djmarcin
Copy link
Contributor Author

Thanks for merging, here are some quick notes on the latest "how to use" and future work.

How to use:

  1. Add the following to your WORKSPACE
load("@rules_rust//tools/rust_analyzer/raze:crates.bzl", "rules_rust_tools_rust_analyzer_fetch_remote_crates")
rules_rust_tools_rust_analyzer_fetch_remote_crates()
  1. Add a rule to your root BUILD file like the following. You can generate the interior targets list with something like: bazel query 'kind("rust_library|rust_binary", //...:all)' | grep -v build_script | grep "//" | awk " { print \"'\" \$0 \"',\" }". Note that visibility rules apply.
rust_analyzer(
    name = "rust_analyzer",
    targets = [ /* all the binary/library targets you want in the rust-project.json */ ],
)
  1. Run bazel run @rules_rust//tools/rust_analyzer:gen_rust_project whenever your dependencies change to regenerate rust-project.json. We .gitignore this file, because it's large and effectively just a build artifact. We use a VSCode task to run gen_rust_project automatically whenever VSCode starts, or manually whenever things get wonky. Once the rust-project.json file exists at your workspace root, rust-analyzer should automatically use it. We sometimes need to force a rust-analyzer restart to pick up changes.

Future work:

  1. Since we're already running a separate binary for this, we should be able to autodetect the targets in the workspace without needing the root rust_analyzer rule. Eliminate the need for this rule.
  2. Potentially, automatically include rules_rust_tools_rust_analyzer_fetch_remote_crates() when the user adds rust_repositories
  3. Improve the documentation once 1 and maybe 2 are done.

Known issues:

  • The --remote_download family of flags breaks proc_macro support because the proc_macro .so files are not downloaded. rust_analyzer needs these files present so it can load and run them. Solution: remove the --remote_download flags.
  • rust-analyzer uses the source code that is downloaded and extracted during a build. If you run bazel clean, rust-analyzer will be unable to find the symbols. Solution: rerun bazel build

@djmarcin djmarcin deleted the rust-analyzer branch February 16, 2021 19:31
Copy link
Member

@hlopko hlopko left a comment

Choose a reason for hiding this comment

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

I'm sorry, I didn't have time to do the thorough review :/

Rust analyzer is updated very often, do you plan to keep updating our checkout with every release? If not, maybe we should add a mechanism to choose an in-workspace version instead of the one bundled with the rules..

Could you check in the docs you mentioned in the last comment? That's already better than hunting for the PR to know how to use rust-analyzer.

I'll try to look into it more thoroughly soon. Thank you so much for working on this!

to Cargo.toml files.
"""

load("//rust/platform:triple_mappings.bzl", "system_to_dylib_ext", "triple_to_system")
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this file doesn't need anything special from the //rust/private and could potentially be moved out and only rely on the stable (non-private) APIs (BuildInfo and find_toolchain are certainly things that should be accessible through rust_common). By moving it out imho we:

  • dogfood our rust_common APIs
  • simplify the private package
  • impose stricter layering

Would you agree that moving it out is a reasonable thing to do?

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, I don't see any reason that would be a problem.

build_info = build_info,
)]

def find_proc_macro_dylib_path(toolchain, target):
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Why not leading underscore?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can update these to leading underscore in a follow-up.

@djmarcin
Copy link
Contributor Author

Rust analyzer is updated very often, do you plan to keep updating our checkout with every release? If not, maybe we should add a mechanism to choose an in-workspace version instead of the one bundled with the rules..

This PR does not bundle rust-analyzer, it merely generates a rust-project.json file, which appears to be a stable format.

yagehu added a commit to yagehu/rules_rust that referenced this pull request Mar 27, 2021
This commit adds the instructions for setting up rust-analyzer support
to the main documentation landing page. The instructions were originally
given in bazelbuild#505.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants