Idea for a crate/tool: `cargo-task`

Hi!

I find myself in a need of a simple cli tool when working on my Rust projects. However, I am on a pretty deep level of yak-shaving now, so I am afraid that I'll get a stackoverflow if try write to write this tool myself :slight_smile:

So, I'll just through in the idea here, and hope that somebody picks it up and runs with it =)

When developing Rust code, I often have to run repetetive tasks. The example of such tasks are

  • generate some files to src/ (build.rs is not applicable here, b/c I want to commit the results of codegen)
  • add a new test data file with a specified format: for example, add files 00dd_foo.rs and 00dd_foo.txt to test/test_data/parsing/ok
  • check that each file with .rs extension starts with a license preamble
  • build and package a rust application
  • automatically update changelog file

Currently, I use just (a simpler and more sane make) for this, but I don't quite like two things about this solution:

  • it relies on shell, and I am bad at shell scripting,
  • shells are bad at being cross-platform.

What I would really love is the ability to write this "scripts" in Rust...

Here is a short specification of the proposed solution

Specification

Conventions

By convention, all custom tasks are defined as a binary crate in the tasks subdirectory. For example, the layout for the frobnicator project might look like this:

frobnicator/
  Cargo.toml
  src/
    lib.rs
  tasks/
    Cargo.toml # this is **not** a member of workspace
    src/
      main.rs # this is the binary capable of executing all the tasks

The bare-bones way to run generate-test taks would look like this than:

$ cargo --manifest-path ./tasks/Cargo.toml run -- generate-test --name "foo"

Running Tasks

To run tasks in a convenient way, a special subcommand, cargo task is provided. So, the actual invocation looks like

$ cargo task generate-test --name "foo"

The job of cargo-task is:

  • find out the workspace root and cd in there
  • run cargo build --manifest-path ./tasks to build the tasks crate
  • run the resulting tasks binary, forwaring all the command-line arguments.

Bike shedding: should this be cargo do instead of cargo task? Or maybe even cargo x, to mirror ./x.py? Or maybe a fancy cargo swissknife?

Defining Tasks

In principle, it is possible to write arbitrary code in the tasks/src/main.rs. However a "shell-like scripting support library" would be most useful! Here are some tasks that this library might do:

  • reexport file crate API to make reading and wriing files easily
  • reexport other utilites to make it easy to read config files and env variables.
  • provide a way to conveniently launch subprocesses, with few lines of code and with automatic panicking on non-zero return code a-la set -e.
  • provide pure-rust implementations of common shell utilites as functions, like cd, ls, grep, find, sed.
  • provide automatic command rounting, with a strong convention over configuration API. That is, be simpler and less flexible than clap, the only way to define commands and options should be via serde.
  • provide a convenient way to shell out back to Cargo.

That's about it for specification I guess?

As I've said, I don't have enough time/desire to make this dream a reality, but, if someone else creates at least a bare bones version of it (basically, creates a repo with CI and implementation of cargo task), I promise I'll use this thing for my project and will contribute stuff, necessary for me personally :slight_smile:

7 Likes

I like this idea; and it's pretty similar to mine, but I think a bit better.

Could you expand on this a bit? Yours what? :slight_smile:

Sorry, heh. I've been tossing around a design for this feature in my head for a few months now; I haven't shared it with anyone yet because I didn't like some aspects of it, which I think you've solved here :slight_smile:

1 Like

This is a little off topic, but what is the motivation for this? It seems to be a lot of duplication and maintenance (updating the year) when a top level license file should have the same effect.

Sometimes folks from the legal department require copyright notice in each file. It certainly helps to detect copy-paste of portions of the software.

I don’t believe that one needs to annually bump a year though: it signifies the start of the copyright.

Fun fact: miniz has two different copyright notices, one at the start, and one at the end of the file: https://github.com/richgel999/miniz/blob/master/miniz.c

I have seen cargo-make tool provides some cross platform scripting capabilities and compose tasks

Like just, cargo-make focuses on defining tasks, while actually doing tasks is mostly left to the shell utilities.

I would prefer to define everything using just Rust. An eDSL for specifying tasks dependencies and such would be nice, but not necessary.

Something like this seems pretty handy. What you are describing is essentially what Rake does for Ruby programmers.

1 Like

with cargo-make you can write tasks entirely in rust, look at:
https://github.com/sagiegurari/cargo-make#usage-task-command-script-task-examplerust

[tasks.rust]
script_runner = "@rust"
script = [
'''
//! ```cargo
//! [dependencies]
//! time = "*"
//! ```
extern crate time;
fn main() {
    println!("{}", time::now().rfc822z());
}
'''
]
4 Likes

Yeah, cargo-make looks really interesting! However, for my use-cases, I personally would prefer something less declarative, less powerful and not tied to Cargo.

if less tied to cargo, I would recommand not calling it cargo-task :slight_smile:

I was also thinking about making cargo-make provide 2 executables, the first work via cargo and the other simple cli, same as rustfmt.
Not sure why haven't done it by now :slight_smile:

Turns out, Cargo already has all the features I need:

:slight_smile: