Can I have a Cargo workspace with two binaries relying on a library where only one binary enables a feature of the library? - rust

I have a Cargo workspaces project with the structure:
├── src
├── lib
│   └── Cargo.toml
├── tools
│   ├── src/bin/foo.rs
│   └── Cargo.toml
└── Cargo.toml
., lib and tools are my three workspace members. lib has a feature that enables tracing of the execution of a performance-sensitive function. As this feature adds performance overhead, the main binary should not enable it, but the foo binary is a testing tool which uses it.
However, Cargo workspaces take the union of the features of the two binaries, so the main binary ends up including the feature anyway.
How can I fix this?

Related

Where to place "common command line" tools in a standard library Rust package structure?

I'm developing a library. For internal development experiments I need a bunch of binary command line entry points that I can execute. I'm using a standard project layout like described in this answer under "Flexible".
What I'd like to achieve: Certain functionality in my command line tools is similar, and I'd like to move that out into its own module. Ideally I'd like to introduce a cli_common.rs module containing some helper functions here in the package structure:
.
├── Cargo.toml
└── src
├── bin
│   ├── cli_common.rs
│   ├── run_foo.rs (uses cli_common)
│   └── run_bar.rs (uses cli_common)
├── lib.rs
└── <various-lib-related-sub-modules>
This doesn't seem to be possible, because the compiler expects every module under bin to have a main function.
This suggest that I have to move the functionality of cli_common.rs into the library itself. This doesn't feel great, because it mixes "peripheral" logic with "core" logic, and I'd like to avoid having this functionality as part of the public interface of the library.
Is there a trick to have such a cli_commons.rs without having to move it into the library?
I found a solution based on this answer.
For simple cases, an extra crate can be avoided by moving the cli_common.rs somewhere, where the compiler doesn't expect a main. In my example I modified the structure for instance to:
.
├── Cargo.toml
└── src
├── bin
│   ├── cli_common.rs
│   │   └── cli_common.rs
│   ├── run_foo.rs
│   └── run_bar.rs
├── lib.rs
└── <various-lib-related-sub-modules>
Within run_foo.rs and run_bar.rs, I can now use mod combined with a #[path] attribute:
#[path="cli_common/cli_common.rs"]
mod cli_common;
For more complex scenarios, going for a separate crate may be preferred.

Workspace optional dependencies (std & no_std)

I am developing a code for my school's rocketry team and I have two programs, one meant to flash the on-board computer and another to run some data analysis on the flight data. The chip code uses no_std while the data analysis program uses std. The data-analysis code will run on my PC, and the chip code will run on the chip.
Here is my workspace root Cargo.toml and my project graph:
[workspace]
members = [
"chip",
"data-analysis",
]
.
├── Cargo.lock
├── Cargo.toml
├── chip
│   ├── Cargo.toml
│   ├── memory.x
│   ├── openocd.cfg
│   ├── openocd.gdb
│   └── src
│   └── main.rs
├── data-analysis
│   ├── Cargo.toml
│   └── src
│   └── main.rs
├── README.md
└── resources
├── 3m.mkd
├── data-stm32f103c8t6.pdf
├── links.txt
├── reference-stm32f103xx.pdf
├── schematic-stm32f103c8t6.png
└── todo.txt
I have decided to use a workspace to organize my code. When I attempt to build the workspace, I get the error:
error[E0463]: can't find crate for `std`
|
= note: the `thumbv7m-none-eabi` target may not support the standard library
= note: `std` is required by `data_analysis` because it does not declare `#![no_std]`
= help: consider building the standard library from source with `cargo build -Zbuild-std`
When I compile with cargo build -Zbuild-std I get the error:
error[E0463]: can't find crate for `panic_abort`
error[E0658]: use of unstable library feature 'restricted_std'
|
= help: add `#![feature(restricted_std)]` to the crate attributes to enable
However I need no_std and not restricted_std.
I understand that dependencies for all files are stored in Cargo.lock and presumably that's why it is producing this error. My question is, how do I express to the compiler that I need std for data_analysis but not for chip code? Should I even be using workspaces over just using one package with multiple binaries and using [features] in the Cargo.toml?
It seems like you're trying to complile data_analysis with the same target as chip. I recommend just getting rid of the root Cargo.toml and compiling the respective ones in the folders. You could make a Makefile to automate this easily

Why is cargo build cache invalidating?

I have a barebones workspace project:
.
├── build-debug.sh
├── Cargo.lock
├── Cargo.toml
├── common
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
├── rs-test.iml
├── server
│   ├── Cargo.toml
│   └── src
│   └── main.rs
└── wui
├── Cargo.toml
└── src
└── lib.rs
The rs files either empty or just an empty main function.
The server and the wui depends on common: common = { path = "../common" }.
The common project has one crates.io dependency with I suppose build script or proc macro dependency.
The build script:
cargo build -p wui --target wasm32-unknown-unknown
cargo build -p server
The problem:
When I rebuild the unchanged project, some wui dependencies are getting invalidated/rebuilt, then the same for server.
Either:
remove the wasm32 target flag
replace the dependency with a simple crate without build time compiled dependencies
It does not rebuild the subprojects anymore.
Is this a cargo bug? What can I do?
It's probably not a cargo bug. What is likely happening here is that your crates.io dependency (you don't mention what it is, which might have been useful) has different dependencies or features depending on the target architecture. Thus, as you alternate between building the WASM target and your host target, stuff is being rebuilt.
Perhaps it would be better in this case to stop using the Cargo workspace and build the server and wui separately; this way you'll have separate target directories for the server and wui, which takes some extra disk space and takes longer for non-incremental compilation, but will prevent you from having to rebuild that stuff all the time as you build both.

Can I swap out the library used by a binary with a wrapper when building a third-party crate?

Let's say there is a vendored third-party cargo project consisting of a library plem and a binary plem_main that I want to extend with some functionality of my own. Crucially, the functionality needs to go in the library plem, not the binary plem_main (which can stay the same). I could write a wrapper my_plem around the library that offers the same interface to the binary, but with the extra functionality included. The project would be set up like this:
.
├── Cargo.toml
├── my_plem
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
└── third-party
├── plem
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
└── plem_main
├── Cargo.toml
└── src
└── main.rs
my_plem/src/lib.rs would depend on things in third-party/plem/src/lib.rs and reexport or overwrite the functions exported by the latter. Is there a good way to get cargo to build the binary plem_main on top of my_plem instead of plem?
"Best" here means that the solution has no or minimal merge conflicts when updating plem in my project and doesn't duplicate the code of plem_main. Ideally it does not touch third-party at all.

What is the recommended directory structure for a Rust project?

Where should one put the sources, examples, documentation, unit tests, integration tests, license, benchmarks etc?
Cargo, the official package manager for Rust, defines some conventions regarding the layout of a Rust crate:
.
├── Cargo.lock
├── Cargo.toml
├── benches
│ └── large-input.rs
├── examples
│ └── simple.rs
├── src
│ ├── bin
│ │ └── another_executable.rs
│ ├── lib.rs
│ └── main.rs
└── tests
└── some-integration-tests.rs
Cargo.toml and Cargo.lock are stored in the root of your project.
Source code goes in the src directory.
The default library file is src/lib.rs.
The default executable file is src/main.rs.
Other executables can be placed in src/bin/*.rs.
Integration tests go in the tests directory (unit tests go in each file they're testing).
Example executable files go in the examples directory.
Benchmarks go in the benches directory.
These are explained in more detail in the manifest description.
By following this standard layout, you'll be able to use Cargo's commands to build, run and test your project easily. Run cargo new to set up a new executable project or cargo new --lib to set up a new library project.
Additionally, documentation for libraries is often written in documentation comments (comments that start with /// before any item, or //! to document the parent item). Also, the license is usually put at the root.
Unit tests, as mentioned above, are written in the same module as the functions they're testing. Usually, they're put in an inner module. It looks like this (this is what Cargo generates for a new library with cargo new --lib):
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
}
}

Resources