これは株式会社POL テックカレンダー 2021 16日目の記事です。前日の記事はこちらです。

はじめに

弊社での新サービスにRustを採用することが決まり、勉強のために詳解Rustプログラミングや実践Rustプログラミング入門を読んでいます。初めての言語を学ぶ上でなにか形のあるものを作りたいと思い、ここを参考にすごく簡単なCLIアプリを作りました。

ただ作るだけだと味気ないので、CI/CDの勉強とかっこよくREADMEにバッジをつけるためにコードカバレッジの設定をしました。ついでにパッケージを配布できるようにもしました。
この記事でRustaceanへの一歩を踏み出していただけると嬉しいです。

この記事のゴール

この記事では、Rustを使ってすごく簡単なCLIアプリを作成し、GitHub Actionを使ってテストを自動化し、Codecovを使ってコードカバレッジを測定します。作成したアプリはここで配布されています。
作成したCLIアプリはGitHubにあります。

CLIアプリ

この記事ではmacOSを使います。

環境構築

コマンドを順に実行することで環境構築ができるようになっています。

Rust

Rustの導入にはrustupというコマンドラインツールを使用します。
公式では以下のコマンドを推奨していますが、私はHomeBrewで入れました。

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
brew install rustup

NeovimやVScodeの補完や型情報の表示のためにRust Language Server (rls)を導入することをおすすめします。

rustup component add rls rust-analysis rust-src

Cargo

RustにはCargoというビルドシステム兼、パッケージマネージャがあります。この記事でもCargoを使ってプロジェクトを作ります。srgatは今回のCLIアプリの名前です。

cargo new srgat --bin
cd srgat

そのままのCargoでは他のパッケージ(Rustではクレートと呼びます。)を入れるために、プロジェクト内のCargo.tomlにクレートの情報を記述する必要があります。これでは面倒なので、便利にするためにcargo-editを導入します。

cargo install cargo-edit

cargo-editによりCargoのサブコマンドとして、add、rm, upgrade等が使えるようになります。
今回のプロジェクで使うクレートを以下のようにインストールします。

// CLIアプリを作るためのクレート (ver. 0.3.25)
cargo add structopt
// 正規表現のクレート (ver. 1.5.4)
cargo add regex

RustでCLIアプリを作る場合はClapというクレートが有名ですが、これにならいstructoptを使います。
他にもfncmdやargoptという魅力的なクレートもあります。

CLIアプリの作成

今回は作成するCLIアプリはソースコード内にあるTODO:、NOTE:などをタグ情報とし、それらタグ情報を画面上に出力するアプリです。
小さなアプリなので、ソースコードとテストコードはすべてsrc/main.rsに記述します。

use regex::Regex;
use std::{fs::read_to_string, path::PathBuf};
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
#[structopt(name = "srgat", about = "tag search for source code")]
pub struct Cli {
    /// Pring tags in the files
    #[structopt(short, long, parse(from_os_str))]
    files: Option<Vec<PathBuf>>,
}

fn find_tags(num: usize, line: &str) {
    let tags = [
        "TODO: ",
        "INFO: ",
        "FIX: ",
        "WARNING: ",
        "NOTE: ",
        "HACK: ",
        "PERF: ",
    ];
    for tag in tags {
        if find_tag(line, tag) {
            print_line(num, line)
        }
    }
}

fn find_tag(line: &str, tag: &str) -> bool {
    let re = Regex::new(tag).unwrap();
    let contains_tag = re.find(line);
    match contains_tag {
        Some(_) => true,
        None => false,
    }
}

#[test]
fn test_find_tag() {
    let result = find_tag("TODO: This is a todo.", "TODO: ");
    assert_eq!(result, true);
    let result = find_tag("NOTE: This is a NOTE.", "NOTE: ");
    assert_eq!(result, true);
}

fn run_files(v: Vec<PathBuf>) {
    read_files(v)
}

fn read_files(vp: Vec<PathBuf>) {
    for v in vp {
        read_file(v);
        // NOTE: 改行
        println!("");
    }
}

fn read_file(p: PathBuf) {
    println!("{:?}", p);
    let content = read_to_string(&p).expect("could not read file");
    read_lines(content)
}

fn read_lines(content: String) {
    for (i, line) in content.lines().enumerate() {
        let num = i + 1;
        find_tags(num, line)
    }
}

fn print_line(num: usize, line: &str) {
    println!("{}: {}", num, line);
}

// TODO: showをerror表示から変える
fn run_show() {
    println!("error")
}

// TODO: すっきりさせる
fn run(cli: Cli) {
    match cli.files {
        Some(v) => run_files(v),
        None => run_show(),
    }
}

fn main() {
    let cli = Cli::from_args();
    run(cli)
}

structoptは以下のようにパラメータを簡単に追加できます。shortは-f、longは–filesを設定します。他にも色々な設定の仕方があります。

#[derive(StructOpt, Debug)]
#[structopt(name = "srgat", about = "tag search for source code")]
pub struct Cli {
    /// Pring tags in the files
    #[structopt(short, long, parse(from_os_str))]
    files: Option<Vec<PathBuf>>,
}

使い方は以下を想定しています。不完全なアプリですが、目的とするタグ情報は表示されました。

srgat -f src/main.rs
"src/main.rs"
15:         "TODO: ",
16:         "INFO: ",
17:         "FIX: ",
18:         "WARNING: ",
19:         "NOTE: ",
20:         "HACK: ",
21:         "PERF: ",
41:     let result = find_tag("TODO: This is a todo.", "TODO: ");
43:     let result = find_tag("NOTE: This is a NOTE.", "NOTE: ");
54:         // NOTE: 改行
76: // TODO: showをerror表示から変える
81: // TODO: すっきりさせる

CI/CD入門

今回はCI/CDツールとしてGitHub Action、コードカバレッジの管理にCodecovを使います。

GitHub Actionの設定

スクリーンショット 2021-12-15 22.11.31.png

コードカバレッジ

Rustのコードカバレッジを測定するツールとしてcargo-kcov、tarpaulin、grcovがあります。cargo-kcovはメンテナンスがされていないようなので、今回はtarpaulinとおまけとしてgrcovを使います。

Codecov

スクリーンショット 2021-12-15 22.35.32.png

コピーしたtokenはCodecovと連携したレポジトリのSetting > Secrets > New repository > secretでペーストします。こうすることで、workflow中で環境変数としてtokenが使えます。

tarpaulin

tarpaulinを使ってコードカバレッジを測定し、Codecovで管理するために、上で作ったworkflowにコードカバレッジを測定するコードを書きます。全体として以下のようになります。

name: Rust

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Build
      run: cargo build --verbose
    - name: Run tests
      run: cargo test --verbose
    - name: Push to codecov.io
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
      run: |
        cargo install cargo-tarpaulin
        cargo tarpaulin --out Xml
        bash <(curl -s https://codecov.io/bash) -X gcov -t $CODECOV_TOKEN

注意する点として、tarpaulinはx86_64プロセッサのlinux環境でしか動作しないようです。今回はUbuntu上で走らせることになるので、大丈夫です。

grcov

grcovを使ってコードカバレッジを測定するためにはこちらで記述されているコードを実行します(grcovはGitHub Action上で未確認です)。macOS上で動作させるために、lcovとgrcovをインストールします。

brew install lcov
cargo add grcov

クレートの配布

作成したアプリはcargoを使うことで、crates.ioで簡単に配布できます。
https://crates.io/me でアカウントを作成し、表示されたAPI tokenをつけてプロジェク内で以下のコマンドを叩くとビルドとアップロードが始まります。

cargo login xxxxxxxxxxxxxxxxxxxxxx

READMEのバッジ

スクリーンショット 2021-12-15 23.09.50.png

おわりに

とても簡単ですがRustとCI/CDに入門できました(気がします)。
Rustはまだまだ発展が著しいので、上記の設定やコードで不都合が起きる場合がありますが、ドキュメントが整備されているので、1次情報に当たるといいと思います。

12/17の記事はまーさんです。よろしくお願いします。

广告
将在 10 秒后关闭
bannerAds