Olivier
Olivier

Reputation: 873

How to deal with multiple nested workspace roots?

How can you have multiple nested workspace with Cargo?

I have the following project structure:

myworkspace
├── project_a
│   └── Cargo.toml
├── project_b
│   └── Cargo.toml
│   └── project_b_dependency
|       └── Cargo.toml
└── Cargo.toml

Where project_b_dependency is a big library which is a git submodule which has a workspace by itself.

I get an error when I run cargo build because there is a workspace within a workspace.

$ cargo build
error: multiple workspace roots found in the same workspace:
  /myworkspace
  /myworkspace/project_b/project_b_dependency

Is there a simple work-around? I want to keep project_b_dependency in source control as a submodule.

This is not a duplicate of Refactoring to workspace structure causes extern crate imports to not work because I want to know how I can deal with nested workspaces.

Upvotes: 16

Views: 17043

Answers (4)

Andrew Lipscomb
Andrew Lipscomb

Reputation: 1058

I've figured out a way to do this one, but it requires cooperation for the submodules you are bringing in to follow a similar pattern.

My issue here was that I had a lib bigger_lib which is in the repo as a workspace, and then a common_lib which is a dependency for both my_app and bigger_lib (not specifically what the question was asking, but what I'm trying to solve). These are representative our current state of many little workspace repos, all on git.

With a layout like this

workspace root
├── Cargo.toml (workspace)
├── my_app (executable)
|   └── Cargo.toml
├── common_lib (lib, via submodule)
|   └── Cargo.toml
└── bigger_lib_repo (workspace, via submodule)
    ├── Cargo.toml  (workspace)
    ├── common_lib (lib, via submodule)
    |   └── Cargo.toml
    └── bigger_lib (lib, not a submodule)

With content looking like this

# ./Cargo.toml
[workspace]

members = ["app"]

# Important to avoid the multiple workspaces error
exclude = ["bigger_lib_repo"]

# Unsure if this is necessary
resolver = "2"

[patch.crates-io]
# The common dep gets overridden for everybody here
common_lib = { path = "./common_lib" }
# ./my_app/Cargo.toml
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"

[dependencies]
# Note this isn't specced using a path
common_lib = { version = "0.1.0" }
bigger_lib = { version = "0.1.0", path = "../bigger_lib_repo/bigger_lib" }
# ./bigger_lib_repo/Cargo.toml
[workspace]

members = [ "bigger_lib" ]

resolver = "2"

[patch.crates-io]
# Same as the root workspace
common_lib = { path = "./common_lib" }
# ./bigger_lib_repo/bigger_lib/Cargo.toml
[package]
name = "bigger_lib"
version = "0.1.0"
edition = "2021"

[dependencies]
# Note this isn't specced using a path
common_lib = { version = "0.1.0" }

All other Cargo.toml files are not particularly different from what you'd expect.

With this pattern I can

  • Build the workspace root without issues
  • Build the nested workspace root without issues
  • Use publicly exported package members from nested libraries (ie: bigger_lib::common_lib::MyStruct)

Upvotes: 1

jolestar
jolestar

Reputation: 1333

I find a method to keep git submodule.

  1. Add submodule project's workspace members to parent Cargo.toml
  2. Remove submodule project's Cargo.toml
  3. Use submodule_name/crate path to define dependency.

Upvotes: 1

attdona
attdona

Reputation: 18993

It seems that nested workspaces are quite difficult to manage, so one possibility is to change the layout of you project:

.
├── myworkspace
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── project_a
│   │   ├── Cargo.toml
│   │   └── src
│   │       └── lib.rs
│   ├── project_b
│   │   ├── Cargo.toml
│   │   └── src
│   │       └── lib.rs
│   └── src
│       └── main.rs
└── project_b_dependency
    ├── Cargo.toml
    └── src
        └── lib.rs

in myworkspace/Cargo.toml:

[workspace]
members= ["project_a", "project_b"]

In myworkspace/project_b/Cargo.toml

[dependencies]
project_b_dependency = {path = "../../project_b_dependency"}

I've tried to use workspace.exclude property with your layout but without success.

Upvotes: 7

ljedrz
ljedrz

Reputation: 22283

Workspaces can't be nested; as the docs state:

A crate may either specify package.workspace or specify [workspace]. That is, a crate cannot both be a root crate in a workspace (contain [workspace]) and also be a member crate of another workspace (contain package.workspace).

The Cargo workspace RFC also specified this:

A workspace is valid if these two properties hold:

  • A workspace has only one root crate (that with [workspace] in Cargo.toml).
  • All workspace crates defined in workspace.members point back to the workspace root with package.workspace.

Upvotes: 10

Related Questions