はじめに
この記事はFlutter #2 Advent Calendar 2018 3日目の記事です。
毎日flutter doctor
を叩いてしまう、そんなエンジニアって多いと思います。
……flutter doctor
を叩かずとも、flutter run
、flutter build apk
やflutter build ios
は叩いてますよね。
そんなわけでflutter build
コマンドを追ってみたいと思います。
確認環境
確認日:2018.12.1
env | ver |
---|---|
Mac OS | 10.14.1 |
flutter | v0.11.13-beta |
Dart | 2.1.0 |
コマンドファイルの場所を探そう
flutter SDKを展開したら、下図のcommand
ディレクトリを探します。
packages/flutter_tools/lib/src/commands
ですね。
Githubで見たい場合はこちらからどうぞ。
├── bin ├── dev ├── examples ├── flutter └── packages ├── flutter ├── flutter_driver ├── flutter_goldens ├── flutter_goldens_client ├── flutter_localizations ├── flutter_test ├── flutter_tools │ ├── bin │ ├── doc │ ├── gradle │ ├── ide_templates │ ├── lib │ │ └── src │ │ ├── android │ │ ├── base │ │ ├── commands <= │ │ ├── dart │ │ ├── fuchsia │ │ ├── intellij │ │ ├── ios │ │ ├── runner │ │ ├── test │ │ ├── tester │ │ └── vscode │ ├── schema │ ├── templates │ ├── test │ └── tool └── fuchsia_remote_debug_protocol
commands
ディレクトリ内を見てみると、見慣れたコマンドが並んでいます。
├── analyze.dart ├── analyze_base.dart ├── analyze_continuously.dart ├── analyze_once.dart ├── attach.dart ├── build.dart ├── build_aot.dart ├── build_apk.dart ├── build_bundle.dart ├── build_flx.dart ├── build_ios.dart ├── channel.dart ├── clean.dart ├── config.dart ├── create.dart ├── daemon.dart ├── devices.dart ├── doctor.dart ├── drive.dart ├── emulators.dart ├── format.dart ├── ide_config.dart ├── inject_plugins.dart ├── install.dart ├── logs.dart ├── make_host_app_editable.dart ├── packages.dart ├── precache.dart ├── run.dart ├── screenshot.dart ├── shell_completion.dart ├── stop.dart ├── test.dart ├── trace.dart ├── update_packages.dart └── upgrade.dart
buildコマンドを見てみる
今回はbuild
系のコマンドを追ってみたいので、まずベースとなるbuild.dartを読んでみます。
// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'package:meta/meta.dart'; import '../base/file_system.dart'; import '../base/utils.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; import 'build_aot.dart'; import 'build_apk.dart'; import 'build_bundle.dart'; import 'build_flx.dart'; import 'build_ios.dart'; class BuildCommand extends FlutterCommand { BuildCommand({bool verboseHelp = false}) { addSubcommand(BuildApkCommand(verboseHelp: verboseHelp)); addSubcommand(BuildAotCommand()); addSubcommand(BuildIOSCommand()); addSubcommand(BuildFlxCommand()); addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp)); } @override final String name = 'build'; @override final String description = 'Flutter build commands.'; @override Future<FlutterCommandResult> runCommand() async => null; } abstract class BuildSubCommand extends FlutterCommand { BuildSubCommand() { requiresPubspecYaml(); } @override @mustCallSuper Future<FlutterCommandResult> runCommand() async { if (isRunningOnBot) { final File dotPackages = fs.file('.packages'); printStatus('Contents of .packages:'); if (dotPackages.existsSync()) printStatus(dotPackages.readAsStringSync()); else printError('File not found: ${dotPackages.absolute.path}'); final File pubspecLock = fs.file('pubspec.lock'); printStatus('Contents of pubspec.lock:'); if (pubspecLock.existsSync()) printStatus(pubspecLock.readAsStringSync()); else printError('File not found: ${pubspecLock.absolute.path}'); } return null; } }
build.dart
ファイルには BuildCommand
がクラスとして、BuildSubCommand
がアブストラクトクラスとして定義されています。どちらもFlutterCommand
クラスを、FlutterCommand
クラスがCommand
クラスを継承しています。
Command
クラスはpackages/flutter_tools/lib/src/runner/command_runner.dart
に記述されています。コマンドの説明や結果で表示される文章もCommand
クラスが生成しているので、flutter build
コマンドの例を取ってみるとおおよその関係が見えてきます。
Flutter build commands. // description反映 Usage: flutter build <subcommand> [arguments] // name + SubCommand反映 -h, --help Print this usage information. Available subcommands: // SubCommand反映 aot Build an ahead-of-time compiled snapshot of your app's Dart code. apk Build an Android APK file from your app. bundle Build the Flutter assets directory from your app. flx Deprecated ios Build an iOS application bundle (Mac OS X host only).
BuildSubCommand
は下記2つの確認を行うだけでのアブストラクトクラスになっています。
pubspec.yaml
がコマンドを叩いたカレントディレクトリに存在するか- コマンドが実行された環境がCI環境か、そうではないか
BuildCommand
クラスにてaddSubcommand
されているので、BuildSubCommand
はSubCommandとして扱われているだけなので、基本的にBuildCommand
とBuildSubCommand
に差がなさそうです。
build apkコマンドをみてみる
筆者が得意なOSがAndroidのため、flutter build apk
コマンドを追ってみることにします。
コマンドのコードはbuild_apk.dartになります。
// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import '../android/apk.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; class BuildApkCommand extends BuildSubCommand { BuildApkCommand({bool verboseHelp = false}) { usesTargetOption(); // ビルド対象のpath指定 addBuildModeFlags(); // releset/debug指定 usesFlavorOption(); // flavor指定 usesPubOption(); // flutter packages getをビルドコマンドの前に走らせるか usesBuildNumberOption(); // VersionCodeを指定 usesBuildNameOption(); // VersionNameを指定 argParser ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) ..addFlag('build-shared-library', negatable: false, help: 'Whether to prefer compiling to a *.so file (android only).', ) ..addOption('target-platform', defaultsTo: 'android-arm', allowed: <String>['android-arm', 'android-arm64']); } @override final String name = 'apk'; @override final String description = 'Build an Android APK file from your app.\n\n' 'This command can build debug and release versions of your application. \'debug\' builds support ' 'debugging and a quick development cycle. \'release\' builds don\'t support debugging and are ' 'suitable for deploying to app stores.'; @override Future<FlutterCommandResult> runCommand() async { await super.runCommand(); await buildApk( project: await FlutterProject.current(), target: targetFile, buildInfo: getBuildInfo(), ); return null; } }
BuildApkCommand
のオプション処理はコメントに書いた通りの内容になります。実装はpackages/flutter_tools/lib/src//android/flutter_command.dart
です。ビルドのModeなど、
Androidのビルドオプションに親しんでいる人ならば、コメントからおおよそ対応している項目は類推できる印象ですね。
runCommand
メソッドはbuildApk
コマンドを利用しています。packages/flutter_tools/lib/src//android/apk.dart
を開くとわかるのですが、ほぼGradleによるbuildへの橋渡しをしているだけです。ただ、Android関連のbuildはGradleで完結するため、過不足ない記述になっています。
感想
FlutterがどうやってAndroid(Gradle)に処理を渡しているのか気になっていたので、簡単ではありますがコードベースで処理を追ってみました。
一番感動したのは、Flutterのビルド処理が全てDartで完結していたことです。Flutterを書く時にもDart、Flutterのビルドコマンドを書く時にもDartを利用することで、Dartが読めれば上から下まで理解できる仕組みになっているなと感じました。
ビルド中にログ出力をしたいや、CI上でより細かな処理をしたい場合などに、今回の記事が参考になれば幸いです。
最後までお付き合いいただき、ありがとうございました。