¿Paquete de óxido con una biblioteca y un binario?

190

Me gustaría hacer un paquete Rust que contenga tanto una biblioteca reutilizable (donde se implementa la mayor parte del programa), como también un ejecutable que lo use.

Suponiendo que no he confundido ninguna semántica en el sistema del módulo Rust, ¿cómo debería ser mi Cargo.tomlarchivo?

Andrew Wagner
fuente

Respuestas:

205
Tok:tmp doug$ du -a

8   ./Cargo.toml
8   ./src/bin.rs
8   ./src/lib.rs
16  ./src

Cargo.toml:

[package]
name = "mything"
version = "0.0.1"
authors = ["me <[email protected]>"]

[lib]
name = "mylib"
path = "src/lib.rs"

[[bin]]
name = "mybin"
path = "src/bin.rs"

src / lib.rs:

pub fn test() {
    println!("Test");
}

src / bin.rs:

extern crate mylib; // not needed since Rust edition 2018

use mylib::test;

pub fn main() {
    test();
}
Doug
fuente
2
Gracias Doug, lo intentaré! ¿Son opcionales las anotaciones #! [Crate_name =] y #! [Crate_type] entonces?
Andrew Wagner
44
Cuando usa Cargo, estas opciones son innecesarias porque Cargo las pasa como banderas de compilación. Si corres cargo build --verbose, los verás en la rustclínea de comandos.
Vladimir Matveev
33
¿Sabes por qué [[bin]]hay una gran variedad de tablas? ¿Por qué usar [[bin]]y no [bin]? No parece haber ninguna documentación al respecto.
CMCDragonkai
40
@CMCDragonkai Es la especificación del formato toml [[x]] es una matriz una vez deserializada; es decir. una sola caja puede producir múltiples archivos binarios, pero solo una biblioteca (por lo tanto, [lib], no [[lib]]). Puede tener múltiples secciones de bin. (Estoy de acuerdo, esto se ve raro, pero toml siempre fue una elección controvertida).
Doug
1
¿Hay alguna manera de evitar que compile el binario cuando todo lo que quiero es la lib? El binario tiene dependencias adicionales que agrego a través de una característica llamada "binario", cuando intento compilarlo sin esa característica, no se genera. Se queja de que no puede encontrar las cajas que bin.rs está tratando de importar.
Persona93
150

También puede simplemente colocar fuentes binarias src/biny el resto de sus fuentes src. Puedes ver un ejemplo en mi proyecto . No es necesario modificarlo Cargo.tomlen absoluto, y cada archivo fuente se compilará en un binario del mismo nombre.

La configuración de la otra respuesta se reemplaza por:

$ tree
.
├── Cargo.toml
└── src
    ├── bin
    │   └── mybin.rs
    └── lib.rs

Cargo.toml

[package]
name = "example"
version = "0.0.1"
authors = ["An Devloper <[email protected]>"]

src / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

src / bin / mybin.rs

extern crate example; // Optional in Rust 2018

fn main() {
    println!("I'm using the library: {:?}", example::really_complicated_code(1, 2));
}

Y ejecutarlo:

$ cargo run --bin mybin
I'm using the library: Ok(3)

Además, puede crear uno src/main.rsque se utilizará como ejecutable de facto. Desafortunadamente, esto entra en conflicto con el cargo doccomando:

No se puede documentar un paquete donde una biblioteca y un binario tienen el mismo nombre. Considere renombrar uno o marcar el objetivo comodoc = false

Shepmaster
fuente
13
¡encaja bien con el enfoque de convención sobre configuración de Rust! ambas respuestas juntas y tienes una gran comodidad y flexibilidad.
ovejas voladoras
9
extern crate example;no se requiere a partir de rust 2018, puede escribir directamente use example::really_complicated_code;y usar la función sin nombrar el alcance
sassman
47

Una solución alternativa es no tratar de agrupar ambas cosas en un solo paquete. Para proyectos un poco más grandes con un ejecutable amigable, me pareció muy agradable usar un espacio de trabajo

Creamos un proyecto binario que incluye una biblioteca dentro de él:

the-binary
├── Cargo.lock
├── Cargo.toml
├── mylibrary
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
└── src
    └── main.rs

Cargo.toml

Esto usa la [workspace]clave y depende de la biblioteca:

[package]
name = "the-binary"
version = "0.1.0"
authors = ["An Devloper <[email protected]>"]

[workspace]

[dependencies]
mylibrary = { path = "mylibrary" }

src / main.rs

extern crate mylibrary;

fn main() {
    println!("I'm using the library: {:?}", mylibrary::really_complicated_code(1, 2));
}

mylibrary / src / lib.rs

use std::error::Error;

pub fn really_complicated_code(a: u8, b: u8) -> Result<u8, Box<Error>> {
    Ok(a + b)
}

Y ejecutarlo:

$ cargo run
   Compiling mylibrary v0.1.0 (file:///private/tmp/the-binary/mylibrary)
   Compiling the-binary v0.1.0 (file:///private/tmp/the-binary)
    Finished dev [unoptimized + debuginfo] target(s) in 0.73 secs
     Running `target/debug/the-binary`
I'm using the library: Ok(3)

Hay dos grandes beneficios para este esquema:

  1. El binario ahora puede usar dependencias que solo se aplican a él. Por ejemplo, puede incluir muchas cajas para mejorar la experiencia del usuario, como analizadores de línea de comandos o formato de terminal. Ninguno de estos "infectará" la biblioteca.

  2. El espacio de trabajo evita compilaciones redundantes de cada componente. Si corremos cargo buildtanto en el mylibraryy el the-binarydirectorio, la biblioteca no se construirá en ambas ocasiones - es compartida entre ambos proyectos.

Shepmaster
fuente
Esto parece una forma mucho mejor de hacerlo. Obviamente han pasado años desde que se hizo la pregunta, pero la gente todavía tiene dificultades para organizar grandes proyectos. ¿Existe una desventaja al usar un espacio de trabajo en comparación con la respuesta seleccionada arriba?
Jspies
44
@Jspies, el mayor inconveniente que se me ocurre es que hay algunas herramientas que no saben completamente cómo lidiar con los espacios de trabajo. Se encuentran en un lugar extraño cuando interactúan con herramientas existentes que tienen algún tipo de concepto de "proyecto". Personalmente, tiendo a adoptar un enfoque continuo: empiezo con todo main.rs, luego lo divido en módulos a medida que crece, finalmente me separo src/bincuando es un poco más grande, y luego me muevo a un espacio de trabajo cuando empiezo a reutilizar en gran medida la lógica central.
Shepmaster
gracias le daré una vuelta. mi proyecto actual tiene un par de librerías que se desarrollan como parte del proyecto pero también se usan externamente.
Jspies
Se construye y funciona bien, pero cargo testparece ignorar las pruebas unitarias en lib.rs
Stein
3
@Stein creo que quierescargo test --all
Shepmaster
18

Puede poner lib.rsy main.rsen la carpeta de fuentes juntas. No hay conflicto y la carga construirá ambas cosas.

Para resolver conflictos de documentación, agregue a su Cargo.toml:

[[bin]]
name = "main"
doc = false
DenisKolodin
fuente
3
Eso estaría cubierto por " Además, solo puede crear un src / main.rs que se utilizará como el ejecutable de facto ". en la otra respuesta, no? Y el conflicto de documentación se resuelve con la respuesta aceptada, ¿verdad? Es posible que deba aclarar su respuesta para mostrar por qué esto es único. Está bien hacer referencia a las otras respuestas para construir sobre ellas.
Shepmaster