はじめに
現在、Androidインターンでビルド時間の改善に向けて日々奮闘中です。
マルチモジュールへの分割であったり、app moduleとは別なapplication moduleを作成してデバッグ速度をあげようとしている中のですが、
ビルド時間の計測を手動でしていたため、効率も悪く、ビルドがいつ終わったかを常に意識して待つ必要がありした。
そこで、shellscriptを書くことにより、人力ビルドから開放されようという試みです。
shell scriptを書くのは初めてなのでもっと良い書き方等あれば教えてくださいmm
ざっくり流れ
- 何回ビルドを行うかを入力
- ビルドキャッシュを使うか否かを入力
- output先のファイルを生成
- 2の入力をもとにgradleのタスクを実行
- 正規表現でビルド時間のみを抽出し、ファイルに書き込む
- 4~5を1で入力した回数繰り返す
実際のコードはこちら
#!/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に変更しました。
ざっくり解説
何回ビルドを行うか
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}
と表現しています。
ビルドキャッシュを使うか
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文の終わりに、逆文字を記述するようになっています。
結果を出力するファイルの作成
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
のようにフォーマットしています。
ループを開始
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を返す)。
gradleのタスクを実行してファイルに書き込む
./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
を抽出するような正規表現を書きました。
参考文献
初心者向けシェルスクリプトの基本コマンドの紹介 - Qiita
シェルスクリプトで [y/n] を入力させて処理を分岐する方法 | LFI
ファイルやディレクトリが存在するかシェルスクリプトで確認する