cargo: `cargo new` sometimes generates invalid Cargo.toml when new crate is in a subdirectory of a workspace with Cargo 1.71

Problem

A crate in a subdirectory of a workspace opting out of being in the workspace via an empty [workspace] table or specifying its path in workspace.exclude in the workspace root’s Cargo.toml fails on Windows with Cargo 1.71 whereas it worked with previous versions of Cargo.

Steps

Create a crate in a subdirectory of a workspace with the following Cargo.toml:

[package]
name = "required_libs"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
[lib]
crate-type=["staticlib"]
[workspace]

Running cargo commands fail:

 'C:/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe' '-E' 'env' 'CARGO_BUILD_RUSTC=C:/Users/runneradmin/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin/rustc.exe' 'C:/Users/runneradmin/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin/cargo.exe' 'rustc' '--verbose' '--color' 'never' '--target=x86_64-pc-windows-msvc' '--' '--print=native-static-libs'
error: failed to parse manifest at `C:\cxx-qt\build\corrosion\required_libs\Cargo.toml`
Caused by:
  error inheriting `version` from workspace root manifest's `workspace.package.version`
Caused by:
  `workspace.package.version` was not defined

Removing the empty [workspace] table from Cargo.toml fails with a different error. This error is also shown when specifying workspace.exclude = [ "path/to/subdir" ] in the workspace root’s Cargo.toml:

'C:/Program Files/Microsoft Visual Studio/2022/Enterprise/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin/cmake.exe' '-E' 'env' 'CARGO_BUILD_RUSTC=C:/Users/runneradmin/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin/rustc.exe' 'C:/Users/runneradmin/.rustup/toolchains/stable-x86_64-pc-windows-msvc/bin/cargo.exe' 'rustc' '--verbose' '--color' 'never' '--target=x86_64-pc-windows-msvc' '--' '--print=native-static-libs'
error: current package believes it's in a workspace when it's not:
current:   C:\cxx-qt\build\corrosion\required_libs\Cargo.toml
workspace: C:\cxx-qt\Cargo.toml
this may be fixable by adding `build\corrosion\required_libs` to the `workspace.members` array of the manifest located at: C:\cxx-qt\Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.

Possible Solution(s)

I encounter this when using Corrosion to invoke Cargo via CMake as part of building a C++ application. At CMake configure time, Corrosion autogenerates a dummy staticlib crate with the above Cargo.toml to run cargo rustc -- --print=native-static-libs, which is used to tell CMake what system libraries need to be linked whenever linking a staticlib into a C/C++ build. The generated dummy crate is put in the CMake build directory, which conventionally is inside the code repository, which also contains the workspace’s virtual Cargo.toml manifest.

A workaround for this particular scenario is to create the CMake build directory outside of the code repository.

If rustc had some way to get the list of native-static-libs needed for every staticlib crate without needing to generate a dummy crate, this situation could be avoided.

Notes

Downstream Corrosion bug: https://github.com/corrosion-rs/corrosion/issues/418

Somehow this bug is only occurring on Windows. Linux and macOS builds still work fine with Rust 1.71.

The root Cargo.toml for the workspace is:

[workspace]
members = [
    "crates/cxx-qt",
    "crates/cxx-qt-build",
    "crates/cxx-qt-gen",
    "crates/cxx-qt-lib",
    "crates/cxx-qt-lib-headers",
    "crates/qt-build-utils",

    "examples/cargo_without_cmake",
    "examples/demo_threading/rust",
    "examples/qml_extension_plugin/plugin/rust",
    "examples/qml_features/rust",
    "examples/qml_minimal/rust",

    "tests/basic_cxx_only/rust",
    "tests/basic_cxx_qt/rust",
    "tests/qt_types_standalone/rust",
]

[workspace.package]
edition = "2021"
license = "MIT OR Apache-2.0"
repository = "https://github.com/KDAB/cxx-qt/"
version = "0.5.3"

# Note a version needs to be specified on dependencies of packages
# we publish, otherwise crates.io complains as it doesn't know the version.
[workspace.dependencies]
cxx-qt = { path = "crates/cxx-qt" }
cxx-qt-macro = { path = "crates/cxx-qt-macro" }
cxx-qt-build = { path = "crates/cxx-qt-build" }
cxx-qt-gen = { path = "crates/cxx-qt-gen", version = "0.5.3" }
cxx-qt-lib = { path = "crates/cxx-qt-lib" }
cxx-qt-lib-headers = { path = "crates/cxx-qt-lib-headers", version = "0.5.3" }
qt-build-utils = { path = "crates/qt-build-utils", version = "0.5.3" }

# Ensure that the example comments are kept in sync
# examples/cargo_without_cmake/Cargo.toml
# examples/qml_minimal/rust/Cargo.toml
cxx = "1.0.95"
cxx-build = { version = "1.0.95", features = [ "parallel" ] }
cxx-gen = "0.7.95"
convert_case = "0.6"
proc-macro2 = "1.0"
syn = { version = "2.0", features = ["extra-traits", "full"] }
quote = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Version

1.71

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 22 (12 by maintainers)

Commits related to this issue

Most upvoted comments

I made a minimal example here: https://github.com/jschwe/cargo_regression_repro

What corrosion wants is:

  • Get a library package where cargo build (or more precisely cargo rustc -- --print=native-static-libs works. The content of the library is not important, we just want to know which libraries need to be linked against for std to work.
  • Anything outside (e.g. an outer workspace) should not be modified.

Previously, cargo new seemed like a good fit for this. The only limitation was, that when a parent directory contained a workspace, we needed to add an [workspace] to the Cargo.toml generated by cargo new.

We are considering adding the new package to the workspace members automatically

I guess if this is going to happen, we can’t use cargo new anymore and need to ship our own dummy package, which is also fine.

We discussed this in the cargo team meeting today.

While we didn’t formally nail it down, our primary concern for programmatic usage is in having processes (like a button in an IDE) that run cargo new to create a package. There was also interest in having the placement of files within what is created to be predictable so the IDE could then immediately open src/main.rs for editing.

For programmatic usage that expects content of the files to be generated in a specific way we felt was out of scope. For a case like in this issue, the files could be generated directly or with tools like cargo-generate or cargo-hatch.

That still leaves the question of what we consider the right UX. Our primary concern was not regressing in what works. Since cargo new && cargo check will will in this case, putting in workspace inheritance doesn’t regress that behavior. We would like to move away from any erroring and the general tone seemed like we were interested in automatically adding the package to the workspace members with a potential opt-out.

Since the use case (programmatic use) is out of scope and the proposed fix (don’t inherit) was decided against, I’m closing this and discussion on workspace interactions will be carried forward in #6378.

Thanks for the repro!

I guess I’ve figured out what’s going on. #12069 use find_root_manifest_for_wd to discover the possible workspace manifest, however, without checking workspace.members field. If the newly added package is not included in workspace.members, then Cargo shouldn’t try to make it inherit anything from the workspace.

For now, I consider this as a regression. We at least need to stop the invalid member from inheriting things. There are two solutions come up to my mind:

  1. Create the package first. And then use Workspace::new to check if the new package is a member. If yes, edit the manifest for the inheritance.
  2. Use WorkspaceRootConfig::members_paths or something to determine whether it is a member when creating the manifest.

Not sure which one is simpler. Probably the first one?

@hi-rustin are you willing to have a look at it?