I’m building my first Rust project—a homebrew shell called mican—and set up CircleCI for it. Here’s what I did.

lint

rust-clippy is the well-known linter. It’s great at telling you things like “this would be nicer as if let,” which is very helpful (and humbling) for beginners who don’t yet know idiomatic Rust. The help URLs explain why a pattern is wrong.

warning: writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used with non-Vec-based slices.
   --> src/readline/reader.rs:154:29
    |
154 |     fn find_bind(&self, ch: &Vec<u8>) -> Option<Keybind> {
    |                             ^^^^^^^^ help: change this to: `&[u8]`
    |
    = note: #[warn(ptr_arg)] on by default
    = help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.200/index.html#ptr_arg

warning: `if _ { .. } else { .. }` is an expression
  --> src/readline/editor.rs:76:13
   |
76 | /             let index: usize;
77 | |             if self.completer_index > self.completions.len() {
78 | |                 self.completer_index = 1;
79 | |                 index = 1;
80 | |             } else {
81 | |                 index = self.completer_index;
82 | |             }
   | |_____________^ help: it is more idiomatic to write: `let index = if self.completer_index > self.completions.len() { ..; 1 } else { self.completer _index };`
   |
   = note: #[warn(useless_let_if_seq)] on by default
   = help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/v0.0.200/index.html#useless_let_if_seq

Installing clippy requires a nightly toolchain. Some nightly versions break clippy; I hit that and even sent a PR. So in CI, pin the Rust version. Install via cargo and choose the strictness level; clippy_pedantic is the strictest.

- run:
  name: lint
  command: |
    export PATH=~/.cargo/bin:$PATH
    rustup install nightly-2018-05-16
    cargo +nightly-2018-05-16 install clippy --vers 0.0.200 --force
    cargo +nightly-2018-05-16 clippy --release -- -Dclippy_pedantic

Test coverage

I used kcov plus Codecov. kcov currently supports only line coverage, not branch coverage (see discussion).

Codecov is easy to swap for other services; kcov has many examples. If you know a better approach to coverage, please tell me.

For mican it looks like this.

Install kcov:

- run:
  name: kcov
  command: |
      wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz
      tar xzf master.tar.gz
      cd kcov-master
      mkdir build
      cd build
      cmake ..
      make
      make install DESTDIR=../../kcov-build
      cd ../..
      rm -rf kcov-master

Run coverage:

- run:
  name: coverage
  command: |
    for file in target/debug/mican-*[^\.d]; do 
        mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; 
    done
    bash <(curl -s https://codecov.io/bash)
    echo "Uploaded code coverage"

Full config

Here’s the full config.yaml for mican. It adds Rust installation and the needed tools.

version: 2

jobs:
  build:
    docker:
      - image: ubuntu:16.04

    working_directory: /opt/utam0k

    steps:
      - checkout
      - run:
          name: set up
          command: |
            set -eux
            apt-get update
            apt-get install -y wget build-essential libcurl4-openssl-dev libelf-dev libdw-dev binutils-dev cmake libiberty-dev pkg-config zlib1g-dev python curl
      - run:
          name: preparation 
          command: |
            wget "https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init"
            chmod +x rustup-init
            ./rustup-init -y --no-modify-path --default-toolchain nightly
            RUSTUP=~/.cargo/bin/rustup
            CARGO=~/.cargo/bin/cargo
            chmod -R a+w $RUSTUP $CARGO
            rm rustup-init
            source ~/.cargo/env
      - run:
          name: lint
          command: |
            export PATH=~/.cargo/bin:$PATH
            rustup install nightly-2018-05-16
            cargo +nightly-2018-05-16 install clippy --vers 0.0.200 --force
            cargo +nightly-2018-05-16 clippy --release -- -Dclippy_pedantic
      - run:
          name: kcov
          command: |
              wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz
              tar xzf master.tar.gz
              cd kcov-master
              mkdir build
              cd build
              cmake ..
              make
              make install DESTDIR=../../kcov-build
              cd ../..
              rm -rf kcov-master
      - run:
          name: test
          command: |
            export PATH=~/.cargo/bin:$PATH
            cargo test
      - run:
          name: coverage
          command: |
            for file in target/debug/mican-*[^\.d]; do 
                mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; 
            done
            bash <(curl -s https://codecov.io/bash)
            echo "Uploaded code coverage"