How can I specify binary-only dependencies? - rust

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.

Related

Specifying dev-dependencies through Cargo feature flags

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) (*)

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.

How to change the app name but not library name of a package?

In Rust, I created one package and now I want to change only output app name without changing package name.
Below is the content of Cargo.toml file:
[package]
authors = ["Rust exam"]
edition = "2021"
name = "rust-exam"
description = "Rebuilt for Scale"
version = "1.10.0"
license = "Apache-2.0"
[dependencies]
base64 = "0.12.3"
clap = "2.33.1"
serde = "1.0.132"
serde_json = "1.0.73"
serde_yaml = "0.8.23"
tempfile = "3.2.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
When I input cargo build, it makes rust-exam and librust-exam.
I want to only change rust-exam, not change package name. How can I do that?
You can configure the app name like so:
[[bin]]
name = "rust-output"
path = "src/main.rs"

include toml file from another toml

I am trying to include a second toml file from Cargo.toml file.
I don't known how to do that and i don't known do this is possible.
I am trying this:
Cargo.toml:
[package]
name = "toml"
authors = "TANDEX"
version = "0.0.0"
include = ["libs.toml"]
libs.toml:
[dependencies]
termion = "0.9.8"
and this:
Cargo.toml:
include = ["libs.toml"]
[package]
name = "toml"
authors = ["TANDEX"]
version = "0.0.0"
libs.toml:
[dependencies]
termion = "0.9.8"
Both of them don't works.
For information I want to automatically generate one file by script and the `Cargo.toml` leave normal.

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