• Stars
    star
    843
  • Rank 54,052 (Top 2 %)
  • Language
  • Created about 5 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

cargo xtask

cargo-xtask is way to add free-form automation to a Rust project, a-la make, npm run or bespoke bash scripts.

The two distinguishing features of xtask are:

  • It doesn't require any other binaries besides cargo and rustc, it fully bootstraps from them
  • Unlike bash, it can more easily be cross platform, as it doesn't use the shell.

Status

cargo-xtask is neither an officially recommended workflow, nor a de-facto standard (yet?). It might or might not work for your use case.

How Does It Work?

cargo-xtask is a polyfill for cargo workflows feature. It is a way to extend stock, stable cargo with custom commands (xtasks), written in Rust.

This polyfill doesn't need any code, just a particular configuration of a cargo project. This repository serves as a specification of such configuration.

Defining xtasks

The best way to create an xtask is to do so inside of a Cargo workspace. If you don't have a workspace already, you can create one inside your package by moving the contents into a new directory. Let's say that our package is named "testing." We first move everything into a sub-directory:

$ mkdir testing

# then move all of the stuff except your .git directory into the new testing directory:
$ mv src testing
$ mv Cargo.toml testing
$ mv .gitignore testing
$ mv README.md testing

# Don't forget anything else your package may have.

Then, add a new package named xtask:

$ cargo new --bin xtask

Then, we need to create a Cargo.toml for our workspace:

[workspace]
members = [
    "testing",
    "xtask",
]

If you had a workspace previously, you'd add xtask to your existing workspace Cargo.toml.

Then, the alias. This is where the magic happens. Create a .cargo:

$ mkdir .cargo

and create a file in it named config.toml with these contents:

[alias]
xtask = "run --package xtask --"

Example directory layout:

/testing
  .git
  .cargo/
    config
  Cargo.toml
  testing/
    Cargo.toml
    .gitignore
    src/
      lib.rs
  xtask/
    Cargo.toml
    src/
      main.rs

Both the xtask directory and the .cargo/config should be committed to the version control system.

If you don't want to use a workspace, you can use run --manifest-path ./xtask/Cargo.toml -- for the alias, but this is not recommended.

The xtask binary should expect at least one positional argument, which is a name of the task to be executed. Tasks are implemented in Rust, and can use arbitrary crates from crates.io. Tasks can execute cargo (it is advisable to use CARGO environmental variable to get the right cargo).

The xtask crate may or may not be a part of the main workspace. Usually, but not always, the workspace setup is better. If xtask is a part of the workspace, you can share dependencies between xtask and main crates, and dependencies update process is easier. Additionally, you will be able to use xtask = "run --package xtask --" as an alias, which works regardless of Cargo's working directory. If xtask is not a part of the workspace, you can use different feature sets for shared dependencies, and you can cache xtask/target more easily on CI. It is advisable to commit xtask lockfile to the repository.

It is advisable to minimize the compile time of xtasks.

You can find some examples of xtasks in the ./examples directory in this repository.

The current recommendation is to define various task as subcommands of the single xtask binary. An alternative is to use a separate binary and a separate entry in .cargo/config for each task.

External examples

And many more examples can be found via e.g. Github Code search.

Limitations

xtasks do not integrate with Cargo lifecycle. If you need to do custom post-processing after cargo build, you'll need to define and call cargo xtask build task, which calls cargo build internally. There's no way to intercept stock cargo build command.

It's impossible to use xtasks from dependencies, xtasks are project-local. However, it is possible to share logic for implementing common xtasks as crates.io packages.

If xtask is not a workspace member, cargo xtask will work only from the project's root directory.

Using xtasks

Use cargo xtask task-name command to run the task.

Example:

cargo xtask deploy

Note that this doesn't require any additional setup besides cloning the repository, and will automatically build the xtask binary on the first run.

Not Using xtasks

xtasks are entirely optional, and you don't have to use them! In particular, if, for your purposes, cargo build and cargo test are enough, don't use xtasks. If you prefer to write a short bash script, and don't need to support windows, there's no need to use xtasks either.

Standard xtasks

The following specifies the names and behaviors of some common xtasks, to help establish common conventions. If you want to tweak behavior of a standard task for your project, you can add custom flags to it. If you feel an important common task is missing, feel free to submit a PR!

cargo xtask, cargo xtask --help

When run without argument or with the --help argument, xtask should print a help message which lists available tasks.

cargo xtask dist

This should package the software and produce a set of distributable artifacts. Artifacts should be placed into ./target/dist directory. The precise meaning of artifacts is not defined, but, for a CLI tool, you can expect the binary itself (build in release mode and stripped), man pages and shell completion files. The dist command should clean the ./target/dist directory before populating it with artifacts. It is expected that the dist command calls cargo build --release internally.

See #3 for additional discussion.

cargo xtask codegen

This command should run code generation, which happens outside of build.rs. For example, if you are writing a gPRC server, and would like to commit the generated code into the repository (so that the clients don't have to have protoc installed), you can implement code generation as cargo xtask codegen.

cargo xtask ci

This task should run cargo test and any additional checks that are required on CI, like checking formatting, running miri test, checking links in the documentation. The CI configuration should generally look like this:

script:
  - cargo xtask ci

The expectation is that, if cargo xtask ci passes locally, the CI will be green as well.

You don't need this task if cargo test is enough for your purposes. Moreover, there are certain tradeoffs associated with using xtasks instead of CI provider's built-in ways to specify CI process. So, we do not recommend to blindly use xtask ci over .travis.yml, but, if you want to use xtasks for CI, use ci as the name of the task.

See #1 for discussion.

Tooling

Libraries:

  • devx: collection of useful utilities (spawning processes, git pre-commit hooks, etc.)
  • xshell: ergonomic "bash" scripting in Rust
  • duct: a library for running child processes with support for pipelines and IO redirection

If you write tools or libraries for xtasks, send a PR to this document. Some possible ideas:

  • cargo subcomand to generate xtask template
  • implementations of common xtasks, like "check that code is formatted with rustfmt" or "build completions for a clap app", as libraries.

Background

To my knowledge, the idea of xtasks was first introduced in this post. In some sense, the present document just specifies some conventions around original idea.

The name xtask is chosen so as not to conflict with potential future built-in cargo feature for tasks.

More Repositories

1

once_cell

Rust library for single assignment cells and lazy statics without macros
Rust
1,502
star
2

xshell

Rust
692
star
3

rust-course

CSS
220
star
4

minipratt

Rust
144
star
5

fall

Rust
121
star
6

crt

Rust
96
star
7

xflags

Rust
79
star
8

pale-fire

Port of Emacs Zenburn theme to VS Code
Rust
72
star
9

arbtest

A minimalist property-based testing library
Rust
64
star
10

proc-caesar

A proc-macro to break Rust IDEs
Rust
63
star
11

matklad.github.io

My coding related blog
TypeScript
58
star
12

cov-mark

Rust
55
star
13

config

configuration.nix is better than dot files
Rust
55
star
14

lock-bench

Rust
42
star
15

tom

tom: a format-preserving TOML parser in Rust
Rust
38
star
16

djot-rs

Rust
35
star
17

macro-dep-test

32
star
18

dlx

Rust
27
star
19

typed_key

A Rust library for strongly-typed string keys for configuration.
Rust
26
star
20

uncover

More maintainable Rust test
Rust
26
star
21

rustraytracer

A ray tracer written in Rust
Rust
26
star
22

always-assert

Rust
20
star
23

paxosnotes

TLA
19
star
24

spin-of-death

Rust
19
star
25

miniml

Rust
19
star
26

elapsed

Measure execution time of a block of Rust code
Rust
16
star
27

jthread-rs

Rust
15
star
28

drop_bomb

A Rust library for runtime-checked linearish types
Rust
14
star
29

jod-thread

std::thread which joins on drop by default.
Rust
14
star
30

bounds-check-cost

Rust
14
star
31

10k_linux_threads

Rust
13
star
32

backtrace-on-stack-overflow

Rust
12
star
33

abont

Finally, a shell for all seasons!
Rust
11
star
34

webassembly-test

Rust
9
star
35

write-json

Rust
9
star
36

tracing-span-tree

Rust
9
star
37

aoc2022

Zig
8
star
38

learnOpenGL

Rust
8
star
39

benchmarks

Rust
7
star
40

versuch

An experiment in try-fns
Rust
6
star
41

devils

Rust
5
star
42

rust-idea-plugin

Java
5
star
43

rust-inline

5
star
44

properly-concurrent

Rust
5
star
45

my-desire

Rust
5
star
46

repros

Rust
4
star
47

hashbench

Rust
4
star
48

m_lexer

Rust
4
star
49

raytracer

A ray tracer written in Haskell
Haskell
4
star
50

aoc2019

Rust
4
star
51

cargo-xtask-sqlx-example

Rust
3
star
52

glass-pipes

Rust
3
star
53

quadheap

Rust
3
star
54

join_to_string

Rust
3
star
55

auchat

Rust
3
star
56

varint

Rust
3
star
57

wsmeta

Shell
3
star
58

xaction

Rust
3
star
59

bunny

An exercise with glium
Rust
3
star
60

ftl

Rust
3
star
61

hello-getzig

Shell
3
star
62

open-notes

TypeScript
2
star
63

mu

meta circular interpreter
mupad
2
star
64

t-cmd

Rust
2
star
65

timeit

Rust
2
star
66

RobinHood

Robin Hood hashing unbenchmark
Rust
2
star
67

parse_tree

Rust
2
star
68

multithread

Rust
2
star
69

s

Rust
2
star
70

iwp

Rust
2
star
71

vec-vs-list

Compare vector vs linked list in terms of performance
Rust
2
star
72

calc

Rust
2
star
73

kotlin-rust-dp

Rust
2
star
74

workspace-vs-feaures

Rust
2
star
75

mandelbrot

Rust
2
star
76

tree_bench

Rust
1
star
77

with

Rust
1
star
78

noyaml

Rust
1
star
79

scope

Rust
1
star
80

diploma

TeX
1
star
81

nested-mod

Rust
1
star
82

mom

Rust
1
star
83

group-by-challenge

Rust
1
star
84

tigerbeetle-filtered

Zig
1
star
85

compilers

Rust
1
star
86

thrench

Rust
1
star
87

rustlexspec

Rust
1
star
88

dylib-rs

Rust
1
star
89

typed_index_derive

Rust
1
star
90

aoc2020

Rust
1
star
91

asyncbench

Clojure
1
star
92

resilient-ll-parsing

Rust
1
star
93

ocamlraytracer

OCaml
1
star
94

.emacs.d

Emacs Lisp
1
star
95

rust-ws

Nix
1
star
96

hashset-vs-vec-bench

Rust
1
star
97

get-example

Rust
1
star
98

flat

Python
1
star
99

cradle-of-tar

Rust
1
star
100

feeds.json

JavaScript
1
star