これは株式会社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の設定
GitHub Actionの導入にはレポジトリのタブにあるActionタブを押すとレコメンドが表示されます。
Set up this workflowをクリックすると自動でworkflowが作成されます。GitHub Acitonは無料で使えますが、いろいろな制限があります。入門だけなら特に細かい設定はいらないと思いますが、この設定ではUbuntuでビルドとテストが走るので、注意する必要があるかもしれません。
コードカバレッジ
Rustのコードカバレッジを測定するツールとしてcargo-kcov、tarpaulin、grcovがあります。cargo-kcovはメンテナンスがされていないようなので、今回はtarpaulinとおまけとしてgrcovを使います。
Codecov
Codecovを使ってGitHub Actionと連携するためにGitHubアカウトでログインし、Repository Upload Tokenを取得する必要があります。右にあるnot set yetボタンを押すとまだ連携していないレポジトリが一覧で出ます。連携が必要なレポジトリをクリックすると以下のようにtokenが発行されるので、コピーします。
コピーした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のバッジ
最後にREADMEにかっこよくバッジをつけましょう。現状は以下のようにバッジを付けています。
GitHub Actionのものは右上の三点リーダーにCreate status badgeボタンがあるので、READMEにコピペします。Codecovも同様に連携したレポジトリのSetting > Badgeにあるコードをコピペします。
また、shieds.ioでオリジナルのバッジを作ることもできます。
おわりに
とても簡単ですがRustとCI/CDに入門できました(気がします)。
Rustはまだまだ発展が著しいので、上記の設定やコードで不都合が起きる場合がありますが、ドキュメントが整備されているので、1次情報に当たるといいと思います。
12/17の記事はまーさんです。よろしくお願いします。