Specifying dev-dependencies through Cargo feature flags - rust

The Background
I am working on a Rust crate. It has several [dependencies] and several [dev-dependencies]. Some dev-dependencies are new crates, but some are just enabling features of crates that are already specified as dependencies.
For example, I use serde in my crate, but don't need to derive any serialization impls. However, for my tests I do need to derive them, so my Cargo.toml file looked like this:
[package]
name = "example"
version = "73015087.0.0"
edition = "2021"
[lib]
path = "/dev/null"
[dependencies]
serde = "1.0.139"
[dev-dependencies]
serde = { version = "1.0.139", features = ["derive"] }
I didn't like needing to duplicate the version number where I (or tools) might forget to update it in sync with the other one, so I changed the dev-dependency to use an unbounded version:
[dev-dependencies]
serde = { version = ">=0", features = ["derive"] }
This felt kind-of gross (particularly since we need to work around crates.io forbidding truly unbounded versions with *). I don't want to be specifying a second dependency on serde, I just want to enable a feature. I ended up with the following alternative.
The Idea
Instead of specifying my dev-dependencies directly under [dev-dependencies], I specified a dependency back on the crate itself, but with a feature flag named dev-dependencies enabled. I used the this flag to enable serde's derive feature without needing to duplicate the version number or use an unbounded range:
[package]
name = "example"
version = "73015087.0.0"
edition = "2021"
[lib]
path = "/dev/null"
[dependencies]
serde = "1.0.139"
[dev-dependencies]
example = { path = ".", default-features = false, features = ["dev-dependencies"] }
[features]
dev-dependencies = [
"serde/derive",
]
After playing with this, I realized that I could use this to list all the versions of all types of dependencies under [dependencies], by adding them as optional and enabling them through the new flag. For example, here is how I've added expect-test, instead of putting it under dev-dependencies.
[package]
name = "example"
version = "73015087.0.0"
edition = "2021"
[lib]
path = "/dev/null"
[dependencies]
expect-test = { version = "1.3.0", optional = true }
serde = "1.0.139"
[dev-dependencies]
example = { path = ".", default-features = false, features = ["dev-dependencies"] }
[features]
dev-dependencies = [
"serde/derive",
"expect-test",
]
It's a little weird, but I might prefer to have all of the versions in one place and avoid the potential duplication. I'm deciding whether to stick with it.
The Question
Does this produce any significant differences in behaviour from expressing dev-dependencies the conventional way, or is it equivalent? Am I going to end up with more confusing resolver issues down the line?
Here are the resolutions with and without dev dependencies enabled, according to cargo-tree:
$ cargo tree -e normal
example v73015087.0.0 (/workspaces/example)
└── serde v1.0.139
$ cargo tree -e normal,dev
example v73015087.0.0 (/workspaces/example)
├── expect-test v1.3.0
│ ├── dissimilar v1.0.4
│ └── once_cell v1.13.0
└── serde v1.0.139
└── serde_derive v1.0.139 (proc-macro)
├── proc-macro2 v1.0.40
│ └── unicode-ident v1.0.2
├── quote v1.0.20
│ └── proc-macro2 v1.0.40 (*)
└── syn v1.0.98
├── proc-macro2 v1.0.40 (*)
├── quote v1.0.20 (*)
└── unicode-ident v1.0.2
[dev-dependencies]
└── example v73015087.0.0 (/workspaces/example) (*)

Related

Rust Polars feature select in Cargo.toml not working

I have a strange behavior. Apparently I must have messed up something:
My toml file:
[package]
name = "test"
version = "0.1.0"
edition = "2021"
[dependencies]
# Version 0.22.7 ==> works
polars = {version = "0.22.8", features = ["lazy"]}
# Version 0.23.0 ==> Does Not Work ... and it will load the 0.23.2 version?!
#polars = {version = "0.23.0", features = ["lazy"]}
Main main.cs:
use polars::prelude::*;
pub fn main() {
let path = "C:\\temp\\rusty.csv";
let days = LazyCsvReader::new(path.into())
.has_header(false)
.finish()
.unwrap()
.collect();
}
Error:
error[E0433]: failed to resolve: use of undeclared type `LazyCsvReader`
--> src\main.rs:25:16
|
25 | let days = LazyCsvReader::new(path.into())
| ^^^^^^^^^^^^^ use of undeclared type `LazyCsvReader`
Any ideas ...?
Digging further I can see that part of the feature-tree is missing in version 0.23.2 of polars:
│ ├── polars feature "csv-file"
│ │ ├── polars v0.22.8 (*)
│ │ ├── polars feature "polars-io"
│ │ │ └── polars v0.22.8 (*)
│ │ ├── polars feature "polars-lazy"
│ │ │ └── polars v0.22.8 (*)
│ │ ├── polars-io feature "csv-file" (*)
│ │ └── polars-lazy feature "csv-file"
│ │ ├── polars-lazy v0.22.7 (*)
│ │ └── polars-io feature "csv-file" (*)
==> a BUG?
Version 0.23.1 of polars is feature complete ... does not have this problem
now my workaround question is: How do I force a specific version to be part of my project?
This:
polars = {version = "0.23.1", features = ["lazy"]}
did not work ...
Thanks to #isaactfa we have this workaround/solution:
polars = {version = "0.23.2", features = ["lazy", "csv-file"]}
My understanding is that the "csv-file" feature is a dependency feature of "lazy" and thus should have been loaded with just the "lazy" flag.
The other workaround is to really force polars' version to "<= 0.23.1"
polars = {version = "<= 0.23.1", features = ["lazy"]}

Is it possible to have dependencies in a Rust project for specific binaries? [duplicate]

I have a crate with both a binary and a library. The library is extremely light on dependencies, while the binary requires quite a bit more to, e.g., load files or do scoped parallel things.
Currently, I have my Cargo.toml set up like this:
[dependencies.kdtree]
path = "../kdtree"
[dependencies]
rand="0.3.0"
rustc-serialize = "0.3"
csv = {git = "https://github.com/BurntSushi/rust-csv.git"}
crossbeam = "0.2"
num_cpus = "0.2"
[lib]
name = "conformal"
path = "src/lib.rs"
[[bin]]
name = "ucitest"
path = "src/bin/main.rs"
The only dependencies the library needs are the kdtree and rand. However, it seems like even if you only build the library, it goes and builds the binary-only dependencies anyway. I've tried using features and other tricks like [[bin].dependencies] or [ucitest-dependencies] (or adding a dependencies= [] line under [[bin]]) that I thought might make them only build for the binary, but I can't find a way.
These aren't enough dependencies to make this a problem, but it's bothering me. Is there a way to narrow down dependencies so they only build for specific binaries?
There are several ways to simulate what you want:
1) Turn the binaries to examples
Examples and tests are built with dev-dependencies, so you could move those dependencies into this section. The library won't depend on them.
# File structure
conformal/
Cargo.toml
src/
lib.rs
examples/ # <-- the `ucitest` is
ucitest.rs # <-- moved to here
# Cargo.toml
[dependencies]
kdtree = { path = "../kdtree" }
rand = "0.3"
[dev-dependencies] # <-- move the examples-only dependencies here
serde = "1"
csv = "0.15"
crossbeam = "0.3"
num_cpus = "1"
[lib]
name = "conformal"
[[example]] # <--- declare the executable
name = "ucitest" # <--- as an example
To run the binary, use:
cargo run --example ucitest
2) Optional dependencies with required features
Dependencies can be made optional, so other crates that depend on your conformal library won't need to download them.
Starting from Rust 1.17, binaries can declare they require certain optional features to be turned on, effectively making those libraries "needed only for binaries".
# Cargo.toml
[dependencies]
kdtree = { path = "../kdtree" }
rand = "0.3"
serde = { version = "1", optional = true } # <-- make
csv = { version = "0.15", optional = true } # <-- all of
crossbeam = { version = "0.3", optional = true } # <-- them
num_cpus = { version = "1", optional = true } # <-- optional
[lib]
name = "conformal"
[features]
build-binary = ["serde", "csv", "crossbeam", "num_cpus"]
[[bin]]
name = "ucitest"
required-features = ["build-binary"] # <--
Note that you need to manually pass --features build-binary when building the binaries:
cargo run --features build-binary --bin ucitest
3) Make the binaries as its own package
You could do whatever dependency management you like when the library and the binary are separate packages.
# File structure
conformal/
Cargo.toml
src/
lib.rs
ucitest/ # <-- move ucitest
Cargo.toml # <-- into its own
src/ # <-- package.
main.rs
# ucitest/Cargo.toml
[dependencies]
conformal = { version = "0.1", path = "../" } # <-- explicitly depend on the library
serde = "1"
csv = "0.15"
crossbeam = "0.3"
num_cpus = "1"
These days this is probably best solved with workspaces [1, 2].
The directory structure is as follows:
project-root
├── Cargo.lock
├── Cargo.toml
├── yourlibary
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
├── src
│   └── main.rs
└── target
The top-level Cargo.toml file:
[package]
name = "yourprogram"
version = "0.1.0"
authors = ["You <you#example.com>"]
[workspace]
[dependencies]
yourlibrary = { path = "yourlibrary" }
yourlibrary Cargo.toml file:
[package]
name = "yourlibrary"
version = "0.1.0"
authors = ["You <you#example.com>"]
[dependencies]
The Cargo.lock file as well as the target directory is at the project root directory and is shared by all the components in the workspace. Workspace components are inferred automatically from dependencies with locak path, but can be specified manually as well.
Each component with its Cargo.toml file can still be published separately on crates.io
This is not implemented yet in Cargo.

Why can't Rust find the "js_sys" crate?

I'm new to rust. I'm trying to use the crate js_sys which contains a Math::log. I have included js_sys = 0.3.48 as the crate website tells me, and then use js_sys::Math::log; in main.rs. I get an error that rust cannot find the crate.
Steps to replicate:
In Cargo.toml
[package]
name = "sim"
version = "0.1.0"
authors = ["Excluded for privacy"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
js_sys = "0.3.48"
rand = "0.8.3"
In the top of my main.rs
// Luke Anglin and Tobi Solarin
use js_sys::Math::log;
use rand::prelude::*; // For the rng
const n: i32 = 1_000; // The number of trials
Error
error: no matching package named `js_sys` found
location searched: registry `https://github.com/rust-lang/crates.io-index`
perhaps you meant: js-sys
required by package `sim v0.1.0 (/Users/lukeanglin/Desktop/Probability/Project2/simulator/sim)`
Change js_sys to js-sys in your Cargo.toml and it should work. (As stated in the error you posted but easily overlooked)

How can I specify binary-only dependencies?

I have a crate with both a binary and a library. The library is extremely light on dependencies, while the binary requires quite a bit more to, e.g., load files or do scoped parallel things.
Currently, I have my Cargo.toml set up like this:
[dependencies.kdtree]
path = "../kdtree"
[dependencies]
rand="0.3.0"
rustc-serialize = "0.3"
csv = {git = "https://github.com/BurntSushi/rust-csv.git"}
crossbeam = "0.2"
num_cpus = "0.2"
[lib]
name = "conformal"
path = "src/lib.rs"
[[bin]]
name = "ucitest"
path = "src/bin/main.rs"
The only dependencies the library needs are the kdtree and rand. However, it seems like even if you only build the library, it goes and builds the binary-only dependencies anyway. I've tried using features and other tricks like [[bin].dependencies] or [ucitest-dependencies] (or adding a dependencies= [] line under [[bin]]) that I thought might make them only build for the binary, but I can't find a way.
These aren't enough dependencies to make this a problem, but it's bothering me. Is there a way to narrow down dependencies so they only build for specific binaries?
There are several ways to simulate what you want:
1) Turn the binaries to examples
Examples and tests are built with dev-dependencies, so you could move those dependencies into this section. The library won't depend on them.
# File structure
conformal/
Cargo.toml
src/
lib.rs
examples/ # <-- the `ucitest` is
ucitest.rs # <-- moved to here
# Cargo.toml
[dependencies]
kdtree = { path = "../kdtree" }
rand = "0.3"
[dev-dependencies] # <-- move the examples-only dependencies here
serde = "1"
csv = "0.15"
crossbeam = "0.3"
num_cpus = "1"
[lib]
name = "conformal"
[[example]] # <--- declare the executable
name = "ucitest" # <--- as an example
To run the binary, use:
cargo run --example ucitest
2) Optional dependencies with required features
Dependencies can be made optional, so other crates that depend on your conformal library won't need to download them.
Starting from Rust 1.17, binaries can declare they require certain optional features to be turned on, effectively making those libraries "needed only for binaries".
# Cargo.toml
[dependencies]
kdtree = { path = "../kdtree" }
rand = "0.3"
serde = { version = "1", optional = true } # <-- make
csv = { version = "0.15", optional = true } # <-- all of
crossbeam = { version = "0.3", optional = true } # <-- them
num_cpus = { version = "1", optional = true } # <-- optional
[lib]
name = "conformal"
[features]
build-binary = ["serde", "csv", "crossbeam", "num_cpus"]
[[bin]]
name = "ucitest"
required-features = ["build-binary"] # <--
Note that you need to manually pass --features build-binary when building the binaries:
cargo run --features build-binary --bin ucitest
3) Make the binaries as its own package
You could do whatever dependency management you like when the library and the binary are separate packages.
# File structure
conformal/
Cargo.toml
src/
lib.rs
ucitest/ # <-- move ucitest
Cargo.toml # <-- into its own
src/ # <-- package.
main.rs
# ucitest/Cargo.toml
[dependencies]
conformal = { version = "0.1", path = "../" } # <-- explicitly depend on the library
serde = "1"
csv = "0.15"
crossbeam = "0.3"
num_cpus = "1"
These days this is probably best solved with workspaces [1, 2].
The directory structure is as follows:
project-root
├── Cargo.lock
├── Cargo.toml
├── yourlibary
│   ├── Cargo.toml
│   └── src
│   └── lib.rs
├── src
│   └── main.rs
└── target
The top-level Cargo.toml file:
[package]
name = "yourprogram"
version = "0.1.0"
authors = ["You <you#example.com>"]
[workspace]
[dependencies]
yourlibrary = { path = "yourlibrary" }
yourlibrary Cargo.toml file:
[package]
name = "yourlibrary"
version = "0.1.0"
authors = ["You <you#example.com>"]
[dependencies]
The Cargo.lock file as well as the target directory is at the project root directory and is shared by all the components in the workspace. Workspace components are inferred automatically from dependencies with locak path, but can be specified manually as well.
Each component with its Cargo.toml file can still be published separately on crates.io
This is not implemented yet in Cargo.

How can I edit Cargo.toml in order to include resource files in my Cargo package?

I want to bundle a word list in plain text format with my Cargo package. Can I edit Cargo.toml in order to do this?
If I used npm, I would add this to my package.json:
"files": ["data/my_dictionary.txt"]
I tried include but it doesn't seem to work.
Here is my Cargo.toml
[package]
name = "chamkho"
version = "0.0.2"
authors = ["Vee Satayamas <vsatayamas#gmail.com>"]
test = true
description = "Thai word segmentation/breaking library and command line"
documentation = "https://github.com/veer66/chamkho/blob/master/README.md"
homepage = "https://github.com/veer66/chamkho/"
repository = "https://github.com/veer66/chamkho.git"
readme = "README.md"
keywords = ["text", "nlp", "thai", "library"]
license = "BSD-2-Clause"
include = ["**/*.txt", "**/*.rs","Cargo.toml"]
[[bin]]
name = "wordcut"
path = "src/cli.rs"
This is the output of cargo package -l
Cargo.toml
src/acc.rs
src/cli.rs
src/dict.rs
src/edge.rs
src/graph.rs
src/graph_builder.rs
src/lib.rs
src/space_acc.rs
src/wordcut.rs
tests/wordcut.rs
I believe include = ["data/my_dictionary.txt"] should work.
(I opened a PR adding this to the documentation.)

Resources