Grinderを使って、ターミナルで実行するタスクをDartで書く
Dartで書いたタスクをターミナルやCIで実行できるGrinderパッケージの紹介です。
Grinderを導入するとgrind {タスク名}でターミナルから色々なタスクを行えるようになります。自分は以下のようなタスクをgrinderで実行してます。- build_runnerの実行(コマンド毎回忘れるやつ)
- リリース時のpubspec.yamlのバージョン更新
- リリースPull Requestの作成
導入方法
以下のコマンドを実行
dart pub global activate grinder
パスが通ってないとエラーが出たら.zshrcや.bashrcに以下を追加してください。
export PATH="$PATH":"$HOME/.pub-cache/bin"
使い方
- pubspec.yamlのdev_dependenciesにgrinderを追加します。
dev_dependencies:
grinder: ^0.9.0
(バージョンは執筆時の最新です)
- プロジェクトのルートにtoolディレクトリを作り、そこにgrind.dartというファイルを作ります。
import 'package:grinder/grinder.dart';
main(List<String> args) => grind(args);
- Taskアノテーションをつけてタスクを作る
('Generate docs.')
void doc() {
log("Generating docs...");
}
- 実行する
grind -hコマンドで実行可能なタスクが確認できます
docがタスク名です
$ grind -h
~~~
Available tasks:
doc Generate docs.
grind docのようにgrindの後にタスク名を指定すると、Dartで書いたタスクを実行できます。
$ grind doc
具体例
最近書いたタスクを紹介します。気に入ったらコピペして使ってみてください。
よく使うzshのコマンドのショートカット
よく使うけど覚えられないコマンドをタスク化してます。エイリアスでもいいですが、grinderのタスクにするとFlutterプロジェクトとセットで扱えるので、チームメンバーと気軽に共有できるのが良いです。
runはgrinderパッケージのメソッドでioパッケージのProcess.runメソッドをラップしたものです。
('build_runnerでファイル生成')
Future<void> generate() async {
run(
'flutter',
arguments: ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'],
);
}
('CocoaPodsのアップデート(パッケージアプデ時に使う)')
void updatePods() {
run(
'rm',
arguments: ['-rf', 'Pods/'],
workingDirectory: 'ios',
);
run(
'rm',
arguments: ['-rf', 'Podfile.lock'],
workingDirectory: 'ios',
);
run(
'flutter',
arguments: ['clean'],
);
run(
'flutter',
arguments: ['pub', 'get'],
);
run(
'pod',
arguments: ['install', '--repo-update'],
workingDirectory: 'ios',
);
}
pubspec.yamlのバージョンアップ
ファイル内の特定の文字列の置換みたいな操作もDartなので気軽にできます。
- context.invocation.argumentsで引数を受け取ってます
- failメソッドを使って引数がないとき処理を止めてます
('バージョン更新')
String incrementVersion() {
final args = context.invocation.arguments;
final newVersionName = args.getOption('version-name');
if (newVersionName == null) {
fail('--version-name=X.X.Xで新しいバージョン名を指定してください');
}
final pubspecFile = File('./pubspec.yaml');
final pubspecString = pubspecFile.readAsStringSync();
final pubspec = loadYaml(pubspecString);
final version = pubspec['version'] as String;
final splits = version.split('+');
final versionCode = int.parse(splits[1]);
final newVersionCode = versionCode + 1;
final updatedPubspecString = pubspecString.replaceFirst(
'version: $version',
'version: $newVersionName+$newVersionCode',
);
pubspecFile.writeAsStringSync(updatedPubspecString);
return '$newVersionName+$newVersionCode';
}
上記のタスクにはyamlパッケージが必要です。
実行時は以下のようにします。
grind increment-version --version-name=1.0.1
補足
おすすめの使い方
Grinderのタスクが増えてるとgrind.dartが読みづらくなるので、自分はpartでタスクごとにファイルを分けました。
tool
├── grind.dart
└── grinder_task
├── generate.dart
├── increment_version.dart
└── update_pods.dart
import 'package:grinder/grinder.dart';
part 'grinder_task/generate.dart';
part 'grinder_task/increment_version.dart';
part 'grinder_task/update_pods.dart';
main(List<String> args) => grind(args);
part of '../grind.dart';
('build_runnerでファイル生成')
Future<void> generate() async {
run(
'flutter',
arguments: ['pub', 'run', 'build_runner', 'build', '--delete-conflicting-outputs'],
);
}
プロセスの経過を表示する
grinderパッケージのrunを使ってスクリプトを実行すると完了までログが出ません。
以下のようなメソッドを使えば経過が表示できます。
Future<void> runCommand({
required String command,
}) async {
final splittedCommand = command.split(' ');
log(command);
final process = await Process.start(
splittedCommand.first,
splittedCommand.sublist(1),
);
stdout.addStream(process.stdout);
stderr.addStream(process.stderr);
}
利用側
part of '../grind.dart';
('build_runnerでファイル生成')
Future<void> generate() async {
runCommand(
command: 'flutter pub run build_runner build --delete-conflicting-outputs',
);
}
その他のGrinderタスクのアノテーション
具体例で使いませんでしたが、@DefaultTaskと@Dependsというアノテーションがあります。詳しくは公式のReadmeを見ていただきたいのですが、それぞれ
- @DefaultTask: grindとだけ打ったとき実行するタスクにつける
- @Depends: タスク実行前に他のタスクを実行する
ものです。
Github Actionsで利用する
GrinderのタスクをGithub Actionsのworkflowに組み込むこともできます。
flutter pub getに時間がかかるので、利用はFlutter関連の処理にとどめた方がいいかもです。
こんな感じです
name: on release pr merged
on:
pull_request:
branches:
- master
types: [closed]
jobs:
on-release-pr-merged:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- uses: actions/checkout@v2.3.4
- name: "Install Flutter"
run: ./.github/workflows/scripts/install-flutter.sh stable
- run: flutter pub get
- name: grinder task
run: dart tool/grind.dart hoge
Derryパッケージとの比較
Flutterプロジェクトによく使うコマンドを設定する方法としてDerryパッケージを使う方法もあります。
手軽にコマンドを共有するだけならDerry、より複雑なタスクを共有したいならGrinderが良いと思います。個人的には両方使い分けるのはややこしいのでGrinderを使うことにしました。まとめ
Flutterエンジニアにとって使い慣れたDartでスクリプトが書けるのはとても便利ですね。
追記
最近はこんな感じの設定を使っています。よかったら参考にしてみてください。
気に入ったらリポジトリにスターしてもらえるとモチベが上がりますw
Discussion