Non-Cargo Projects

Purpose and Scope

rust-analyzer is commonly introduced through Cargo workspaces, but the tool is not limited to Cargo. The non-Cargo project workflow exists for teams that build Rust with another build system, such as a monorepo build graph, an internal wrapper, or a system that already knows every target and dependency. In that mode, the editor cannot infer the crate graph from manifests, so the user supplies the graph explicitly in a file named rust-project.json. This page explains the documented contract of that file and how to think about it when adapting a non-Cargo build to rust-analyzer.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

The key shift is that rust-analyzer needs semantic information rather than build commands. Cargo normally tells tools which crates exist, where each crate root is, which edition applies, what dependencies are named, and which conditional compilation flags are active. For a non-Cargo project, the build system must export equivalent information in a stable JSON description. Once rust-analyzer can see the sysroot, crates, dependency edges, and command templates for tests or binaries, it can provide editor features without embedding build-system-specific logic.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

Relevant Source Files

  • src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md — the rust-analyzer book page that states rust-analyzer does not require Cargo and documents the rust-project.json interfaces for project, crate, dependency, sysroot, cfg group, and runnable data.

Core Primitives

A ProjectJson is the top-level description consumed by rust-analyzer for this workflow. It can name a sysroot directory, point to sysroot source code, describe sysroot crates with another project object, define shared configuration groups, list the project crates, and provide runnable command templates. The mandatory center of the file is the crates array, because that is where the graph of Rust compilation units is described. The optional fields make the graph more complete, but the crate list is the part that replaces Cargo metadata for editor analysis.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

A Crate is one Rust compilation unit from rust-analyzer’s point of view. The documented crate fields include a display name, a root module path, the Rust edition, an optional version, dependencies, and an optional workspace-member flag. The distinction between display and dependency naming matters: a display name is for presentation, while dependency entries carry semantically significant crate names. This lets a build-system export choose readable labels without accidentally changing how paths and extern crate references resolve inside the analyzed program.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

The sysroot fields connect a custom project graph to Rust’s built-in crates. The sysroot value is the directory where the compiler looks for crates that ship with Rust, such as the standard library. The documentation shows that users can ask the compiler for this location with rustc --print sysroot. The sysroot_src value points at the source directory containing standard library crates such as std and core; when it is provided, rust-analyzer automatically adds dependencies on sysroot crates. If it is omitted, the project may describe those dependencies manually.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

rust-project.json Reference

The documented schema is best read as a bridge between an external build graph and rust-analyzer’s language model. cfg_groups allows common conditional-compilation values to be shared between crates instead of repeated. The crates list must include all transitive dependencies as well as sysroot crates such as the standard library and core library when they are not being discovered through the sysroot source path. This requirement is important because rust-analyzer cannot answer name-resolution, type, or completion questions accurately if any dependency edge is missing from the graph.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

{
  "sysroot": "/Users/yourname/.rustup/toolchains/stable-x86_64-apple-darwin",
  "sysroot_src": "/Users/yourname/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library",
  "crates": [
    {
      "display_name": "app",
      "root_module": "/home/user/repo/app/src/lib.rs",
      "edition": "2021",
      "deps": [],
      "is_workspace_member": true
    }
  ]
}

The edition field accepts the Rust editions documented by rust-analyzer for this format: 2015, 2018, 2021, or 2024. This is not just metadata; edition selection affects parsing and name resolution rules. The version field is optional and is used for calculating the correct docs.rs URL, which makes it useful for third-party crates when documentation navigation is expected to point at a published version. is_workspace_member lets the exporter classify crates that belong to the currently edited project separately from external dependencies, enabling rust-analyzer to treat non-member crates as less frequently changing.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

Execution Flow

A practical integration usually starts by asking the existing build system for the same data it already uses to compile. First, collect every Rust crate that can affect the opened project, including generated crates if the editor should analyze their sources. Second, assign each crate a root module and edition. Third, emit dependency entries using the names that source code expects when referring to other crates. Finally, add the sysroot location or explicitly model the standard library crates. The result is not a replacement build file; it is an editor-facing graph description.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

After the graph is available, runnables add a second layer: how editor actions should invoke the external build system. The documentation describes runnables as configuration for command-line actions used to run or debug binaries and tests without hard-coding build-system knowledge into rust-analyzer. The example uses a program named buck, arguments for a test invocation, a working directory, and a kind of testOne. Placeholders such as {label} and {test_id} are intended to be filled from runnable context, allowing the editor to offer test actions while the external tool remains in charge of execution.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

{
  "program": "buck",
  "args": [
    "test",
    "{label}",
    "--",
    "{test_id}",
    "--print-passing-details"
  ],
  "cwd": "/home/user/repo-root/",
  "kind": "testOne"
}

Cargo Contrast and Edge Cases

Cargo projects normally give rust-analyzer a manifest-centered discovery path: Cargo defines packages, crates, targets, features, editions, dependencies, and build outputs. The official Cargo documentation describes Cargo as the Rust package manager and build tool, with crates and manifests as central concepts. Non-Cargo integration should therefore preserve the same conceptual facts even when there is no manifest. If a proprietary build system uses different vocabulary, translate it into rust-analyzer’s crate graph rather than exposing the build system’s internal terms directly to the editor.

A common edge case is standard library modeling. If sysroot_src is available and points at the Rust library source tree, rust-analyzer can automatically add sysroot dependencies. If the project intentionally needs several different sysroot-like graphs, the documentation notes that omitting this path allows dependencies to be specified manually. Another edge case is third-party code: setting is_workspace_member to false for external crates and standard library crates tells rust-analyzer they are not expected to change with normal edits, which can improve behavior for large dependency graphs.

Sources: src/tools/rust-analyzer/docs/book/src/non_cargo_based_projects.md

Next Steps

For a working integration, generate the smallest complete rust-project.json first: sysroot information, all project crates, root modules, editions, and dependencies. Then add shared cfg groups if crates repeat the same conditional settings, and add runnables only after navigation, completion, and diagnostics work correctly. Treat the file as exported metadata from the real build system, not as a hand-maintained parallel project whenever possible. Readers using Cargo should start with the Cargo-oriented rust-analyzer setup instead; readers adapting monorepos should continue with configuration, diagnostics, and troubleshooting guidance.

Related pages: rust-analyzer-configuration, rust-analyzer-features-diagnostics-assists, rust-analyzer-troubleshooting-faq, cargo-getting-started