[Android]gradle taskのビルド時間を抽出するスクリプト書いてみた

# はじめに

現在、Androidインターンでビルド時間の改善に向けて日々奮闘中です。

マルチモジュールへの分割であったり、app moduleとは別なapplication moduleを作成してデバッグ速度をあげようとしている中のですが、

ビルド時間の計測を手動でしていたため、効率も悪く、ビルドがいつ終わったかを常に意識して待つ必要がありした。

そこで、shellscriptを書くことにより、人力ビルドから開放されようという試みです。

shell scriptを書くのは初めてなのでもっと良い書き方等あれば教えてくださいmm

# ざっくり流れ

1. 何回ビルドを行うかを入力
2. ビルドキャッシュを使うか否かを入力
3. output先のファイルを生成
4. 2の入力をもとにgradleのタスクを実行
5. 正規表現でビルド時間のみを抽出し、ファイルに書き込む
6. 4~5を1で入力した回数繰り返す

実際のコードはこちら

```shell

    #!/bin/bash
    # ./build/build-time-2020-01-29-17-50-00.txt のようなファイルにビルド時間を書き込むスクリプト
    
    
    read -p "How many tasks do you try ? [1~99] => " -r n
    if [[ ! $n =~ [0-9]{1,2} ]] || [ $n == 0 ]; then
      echo "input number range of [1~99]"
      exit
    fi
    read -p "use build cache? [y/n] => " -r yn
    case "$yn" in
      "y") echo "start tasks with using build cache.";;
      "n") echo "start tasks with full build.";;
        *) echo "input y or n. "
           exit;;
    esac
    
    dirPath=./buildtime
    if [[ ! -e $dirPath ]]; then
       mkdir "buildtime"
    fi
    
    filePath=$dirPath/build-time-`date '+%Y-%m-%d-%H-%M-%S'`.txt
    touch "$filePath"
    
    # taskの実行とcleanを行い、ビルド実行時間をtextファイルに書き出す
    for count in `seq 1 $n`
    do
      echo "now caliculating... $count"
      noBuildCache=""
      if [ "$yn" == "n" ]; then
        ./gradlew clean > /dev/null
        noBuildCache="--no-build-cache"
      fi
      ./gradlew app:assembleDebug $noBuildCache | while read -r line
      do
        if [[ $line =~ ^'BUILD SUCCESSFUL in '([0-5]{0,1}[0-9]{1}.)$ ]]
       then
        echo "${BASH_REMATCH[1]}, " >> $filePath
       elif [[ $line =~ ^'BUILD SUCCESSFUL in '([0-9]{*}.[0-5]{0,1}[0-9]{1}.)$ ]]
       then
        echo "${BASH_REMATCH[1]}" >> $filePath
       fi
      done
    done
    
    echo "gradle tasks completed"
```

最初はzshで書いていたのですが、マッチした正規表現の抽出方法がいまいちわからずbashに変更しました。

# ざっくり解説

## 何回ビルドを行うか

```shell

    read -p "How many tasks do you try ? [1~99] => " -r n
    if [[ ! $n =~ [0-9]{1,2} ]] || [ $n == 0 ]; then
      echo "input number range of [1~99]"
      exit
    fi

```

`read -p` の後に、ユーザーに入力を促す文章を記述し、入力内容をnに入れています。

ここで、

- 数字のみを入力
- 2桁まで

というバリデーションを効かせる記述もしておきます。

bashの場合正規表現は、

`[[ ]]` で囲むこと、 `=~` の左辺に対象,右辺に正規表現のパターンを書くことで実装できます。

上記では、

- 数字のみ ⇒  `[0-9]`
- 2桁まで ⇒ `{1,2}`

と表現しています。

## ビルドキャッシュを使うか

```shell
    read -p "use build cache? [y/n] => " -r yn
    case "$yn" in
      "y") echo "start tasks with using build cache.";;
      "n") echo "start tasks with full build.";;
        *) echo "input y or n. "
           exit;;
    esac

```

上記と同じ要領で入力を受け取ります。

そしてここでは `case` 文を使って、yとn以外に入力された場合はスクリプトの実行を終わらせるようにしています。

shellの書き方の特徴として、case文の終わりやif文の終わりに、逆文字を記述するようになっています。

## 結果を出力するファイルの作成

```shell

    dirPath=./buildtime
    if [[ ! -e $dirPath ]]; then
       mkdir "buildtime"
    fi
    
    filePath=$dirPath/build-time-`date '+%Y-%m-%d-%H-%M-%S'`.txt
    touch "$filePath"
    
```

ここではディレクトリが存在しなければ作成し、

`buildtime` ディレクトリの子階層にファイルを作成しています。

    `date '+%Y-%m-%d-%H-%M-%S'`

のように `` で囲んだ箇所はコマンドとして実行できるため、

dateコマンドで現在の標準時刻を取得し、 `2020-01-28-23-11-15` のようにフォーマットしています。

## ループを開始

```shell

    for count in `seq 1 $n`
    do
      echo "now caliculating... $count"
      noBuildCache=""
      if [ "$yn" == "n" ]; then
        ./gradlew clean > /dev/null
        noBuildCache="--no-build-cache"
      fi
    #略
    done
```

入力で受け取った回数だけループを回します。

キャッシュを使用しない場合には `./gradlew clean` を実行すると同時に、変数に `--no-build-cache` を代入しておきます。（あとで使う）

このとき、ただgradleのタスクを実行するだけだと、内部的に `exit` が呼ばれるためタスク以降のスクリプトが実行されません。

そこで、タスクの出力を `/dev/null` に渡すことでこれを回避します。

> /dev/null（nullデバイスとも呼ばれる）は、UNIXやUnix系オペレーティングシステム (OS) におけるスペシャルファイルの1つで、そこに書き込まれたデータを全て捨て（writeシステムコールは成功する）、読み出してもどんなプロセスに対してもデータを返さない（EOFを返す）。

[wikipedia]([https://ja.wikipedia.org/wiki//dev/null](https://ja.wikipedia.org/wiki//dev/null))

## gradleのタスクを実行してファイルに書き込む

```shell

    ./gradlew app:assembleDebug $noBuildCache | while read -r line
      do
        if [[ $line =~ ^'BUILD SUCCESSFUL in '([0-5]{0,1}[0-9]{1}.)$ ]]
       then
        echo "${BASH_REMATCH[1]}, " >> $filePath
       elif [[ $line =~ ^'BUILD SUCCESSFUL in '([0-9]{*}.[0-5]{0,1}[0-9]{1}.)$ ]]
       then
        echo "${BASH_REMATCH[1]}" >> $filePath
       fi
      done

```

gradleのタスク実行結果を、パイプ `|` を利用して一行ずつ読み込み、

`BUILD SUCCESSFUL in **s` or `BUILD SUCCESSFUL in **m**s` を抽出するような正規表現を書きました。

# 参考文献

[正規表現サンプル集](https://www.megasoft.co.jp/mifes/seiki/meta.html)

[シェルの入出力制御あれこれ - Qiita](https://qiita.com/tag1216/items/7ce35b7c27d371165e56)

[初心者向けシェルスクリプトの基本コマンドの紹介 - Qiita](https://qiita.com/zayarwinttun/items/0dae4cb66d8f4bd2a337#%E5%85%A5%E5%8A%9B%E5%87%BA%E5%8A%9B)

[シェルスクリプトで [y/n] を入力させて処理を分岐する方法 | LFI](https://linuxfan.info/shell-script-y-n)

[ファイルやディレクトリが存在するかシェルスクリプトで確認する](https://hacknote.jp/archives/31390/)

[koalaman/shellcheck](https://github.com/koalaman/shellcheck/wiki/SC2164)

[EZ-NET: 標準出力や標準エラーの内容を捨てる - Linux シェルスクリプトプログラミング](http://program.station.ez-net.jp/special/handbook/sh/system/stderr.asp)

[](https://shellscript.sunone.me/date.html)