blob: ca6a3f857443f684092dd24f0d0659fece695294 [file] [log] [blame]
// Copyright 2019 The Flutter 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:io';
import 'package:args/args.dart';
import 'package:pedantic/pedantic.dart';
const String angularDartProjectDirectory = '../app';
const String flutterProjectDirectory = '../app_flutter';
const String gcloudProjectIdFlag = 'project';
const String gcloudProjectIdAbbrFlag = 'p';
const String gcloudProjectVersionFlag = 'version';
const String gcloudProjectVersionAbbrFlag = 'v';
const String flutterProfileModeFlag = 'profile';
const String ignoreVersionFlag = 'ignore-version-check';
const String helpFlag = 'help';
String _gcloudProjectId;
String _gcloudProjectVersion;
bool _flutterProfileMode;
bool _ignoreVersion;
/// Check if [gcloudProjectIdFlag] and [gcloudProjectVersionFlag]
/// were passed as arguments. If they were, also set [_gcloudProjectId]
/// and [_gcloudProjectVersion] accordingly.
bool _getArgs(ArgParser argParser, List<String> arguments) {
final ArgResults args = argParser.parse(arguments);
final bool printHelpMessage = args[helpFlag] as bool;
if (printHelpMessage) {
return false;
}
_gcloudProjectId = args[gcloudProjectIdFlag] as String;
_gcloudProjectVersion = args[gcloudProjectVersionFlag] as String;
_flutterProfileMode = args[flutterProfileModeFlag] as bool;
_ignoreVersion = args[ignoreVersionFlag] as bool;
if (_gcloudProjectId == null) {
stderr.write('--$gcloudProjectIdFlag must be defined\n');
return false;
}
if (_gcloudProjectVersion == null) {
stderr.write('--$gcloudProjectVersionFlag must be defined\n');
return false;
}
return true;
}
/// Check the Flutter version installed and make sure it is a recent version
/// from the past 21 days.
///
/// Flutter tools handles the rest of the checks (e.g. Dart version) when
/// building the project.
Future<bool> _checkDependencies() async {
if (_ignoreVersion) {
return true;
}
stdout.writeln('Checking Flutter version via flutter --version');
final ProcessResult result =
await Process.run('flutter', <String>['--version']);
final String flutterVersionOutput = result.stdout as String;
// This makes an assumption that only the framework will have its version
// printed out with the date in YYYY-MM-DD format.
final RegExp dateRegExp =
RegExp(r'([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))');
final String flutterVersionDateRaw =
dateRegExp.allMatches(flutterVersionOutput).first.group(0);
final DateTime flutterVersionDate = DateTime.parse(flutterVersionDateRaw);
final DateTime now = DateTime.now();
final Duration lastUpdateToFlutter = now.difference(flutterVersionDate);
return lastUpdateToFlutter.inDays < 21;
}
/// Build app Angular Dart project
Future<bool> _buildAngularDartApp() async {
/// Clean up previous build files to ensure this codebase is deployed.
await Process.run(
'rm',
<String>['-rf', 'build/'],
workingDirectory: angularDartProjectDirectory,
);
final Process pubProcess = await Process.start('pub', <String>['get'],
workingDirectory: angularDartProjectDirectory);
await stdout.addStream(pubProcess.stdout);
await stderr.addStream(pubProcess.stderr);
if (await pubProcess.exitCode != 0) {
return false;
}
final Process buildProcess = await Process.start(
'pub',
<String>[
'run',
'build_runner',
'build',
'--release',
'--output',
'build',
'--delete-conflicting-outputs'
],
workingDirectory: angularDartProjectDirectory,
);
await stdout.addStream(buildProcess.stdout);
await stderr.addStream(buildProcess.stderr);
// The Angular Dart build dashboard page has been replaced with a Flutter
// version. There are some administrative features missing in the Flutter
// version so we still offer the old build dashboard.
await Process.run(
'mv', <String>['build/web/build.html', 'build/web/old_build.html'],
workingDirectory: angularDartProjectDirectory);
return await buildProcess.exitCode == 0;
}
/// Build app_flutter for web.
Future<bool> _buildFlutterWebApp() async {
/// Clean up previous build files to ensure this codebase is deployed.
await Process.run('rm', <String>['-rf', 'build/'],
workingDirectory: flutterProjectDirectory);
final Process process = await Process.start(
'flutter',
<String>[
'build',
'web',
'--dart-define',
'FLUTTER_WEB_USE_SKIA=true',
_flutterProfileMode ? '--profile' : '--release'
],
workingDirectory: flutterProjectDirectory);
await stdout.addStream(process.stdout);
final bool successfulReturn = await process.exitCode == 0;
return successfulReturn;
}
/// Copy the built project from app to this app_dart project.
Future<bool> _copyAngularDartProject() async {
final ProcessResult result = await Process.run('cp',
<String>['-rn', '$angularDartProjectDirectory/build/web', 'build/']);
// On MacOS, this will return exit code 1 since this copy does
// have files that "fail" to overwrite due to `app_flutter`.
return result.exitCode == 0 || result.exitCode == 1;
}
/// Copy the built project from app_flutter to this app_dart project.
Future<bool> _copyFlutterApp() async {
final ProcessResult result = await Process.run(
'cp', <String>['-r', '$flutterProjectDirectory/build', 'build/']);
return result.exitCode == 0;
}
/// Run the Google Cloud CLI tool to deploy to [_gcloudProjectId] under
/// version [_gcloudProjectVersion].
Future<bool> _deployToAppEngine() async {
stdout.writeln('Deploying to AppEngine');
/// The Google Cloud deployment command is an interactive process. It will
/// print out what it is about to do, and ask for confirmation (Y/n).
final Process process = await Process.start(
'gcloud',
<String>[
'app',
'deploy',
'--project',
_gcloudProjectId,
'--version',
_gcloudProjectVersion,
'--no-promote',
'--no-stop-previous-version',
],
);
/// Let this user confirm the details before Google Cloud sends for deployment.
unawaited(stdin.pipe(process.stdin));
await process.stderr.pipe(stderr);
await process.stdout.pipe(stdout);
return await process.exitCode == 0;
}
Future<void> main(List<String> arguments) async {
final ArgParser argParser = ArgParser()
..addOption(gcloudProjectIdFlag, abbr: gcloudProjectIdAbbrFlag)
..addOption(gcloudProjectVersionFlag, abbr: gcloudProjectVersionAbbrFlag)
..addFlag(flutterProfileModeFlag)
..addFlag(ignoreVersionFlag)
..addFlag(helpFlag);
if (!_getArgs(argParser, arguments)) {
stdout.write('Required flags:\n'
'--$gcloudProjectIdFlag gcp-id\n'
'--$gcloudProjectVersionFlag version\n\n'
'Optional flags:\n'
'--$flutterProfileModeFlag\tBuild app_flutter in profile for debugging\n'
'--$ignoreVersionFlag\tForce deploy with current Flutter version\n');
exit(1);
}
if (!await _checkDependencies()) {
stderr.writeln(
'Update Flutter to a version on master from the past 3 weeks to deploy Cocoon');
exit(1);
}
if (!await _buildAngularDartApp()) {
stderr.writeln('Failed to build Angular Dart project');
exit(1);
}
if (!await _buildFlutterWebApp()) {
stderr.writeln('Failed to build Flutter app');
exit(1);
}
/// Clean up previous build files to ensure the latest files are deployed.
await Process.run('rm', <String>['-rf', 'build/']);
if (!await _copyFlutterApp()) {
stderr.writeln('Failed to copy Flutter app over');
exit(1);
}
if (!await _copyAngularDartProject()) {
stderr.writeln('Failed to copy Angular Dart project over');
exit(1);
}
if (!await _deployToAppEngine()) {
stderr.writeln('Failed to deploy to AppEngine');
exit(1);
}
}