| // Copyright 2014 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:async'; |
| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as path; |
| |
| import '../framework/devices.dart'; |
| import '../framework/framework.dart'; |
| import '../framework/task_result.dart'; |
| import '../framework/utils.dart'; |
| |
| const String _packageName = 'package_with_native_assets'; |
| |
| const List<String> _buildModes = <String>[ |
| 'debug', |
| 'profile', |
| 'release', |
| ]; |
| |
| TaskFunction createNativeAssetsTest({ |
| String? deviceIdOverride, |
| bool checkAppRunningOnLocalDevice = true, |
| bool isIosSimulator = false, |
| }) { |
| return () async { |
| if (deviceIdOverride == null) { |
| final Device device = await devices.workingDevice; |
| await device.unlock(); |
| deviceIdOverride = device.deviceId; |
| } |
| |
| await enableNativeAssets(); |
| |
| for (final String buildMode in _buildModes) { |
| if (buildMode != 'debug' && isIosSimulator) { |
| continue; |
| } |
| final TaskResult buildModeResult = await inTempDir((Directory tempDirectory) async { |
| final Directory packageDirectory = await createTestProject(_packageName, tempDirectory); |
| final Directory exampleDirectory = dir(packageDirectory.uri.resolve('example/').toFilePath()); |
| |
| final List<String> options = <String>[ |
| '-d', |
| deviceIdOverride!, |
| '--no-android-gradle-daemon', |
| '--no-publish-port', |
| '--verbose', |
| '--uninstall-first', |
| '--$buildMode', |
| ]; |
| int transitionCount = 0; |
| bool done = false; |
| |
| await inDirectory<void>(exampleDirectory, () async { |
| final int runFlutterResult = await runFlutter( |
| options: options, |
| onLine: (String line, Process process) { |
| if (done) { |
| return; |
| } |
| switch (transitionCount) { |
| case 0: |
| if (!line.contains('Flutter run key commands.')) { |
| return; |
| } |
| if (buildMode == 'debug') { |
| // Do a hot reload diff on the initial dill file. |
| process.stdin.writeln('r'); |
| } else { |
| done = true; |
| process.stdin.writeln('q'); |
| } |
| case 1: |
| if (!line.contains('Reloaded')) { |
| return; |
| } |
| process.stdin.writeln('R'); |
| case 2: |
| // Do a hot restart, pushing a new complete dill file. |
| if (!line.contains('Restarted application')) { |
| return; |
| } |
| // Do another hot reload, pushing a diff to the second dill file. |
| process.stdin.writeln('r'); |
| case 3: |
| if (!line.contains('Reloaded')) { |
| return; |
| } |
| done = true; |
| process.stdin.writeln('q'); |
| } |
| transitionCount += 1; |
| }, |
| ); |
| if (runFlutterResult != 0) { |
| print('Flutter run returned non-zero exit code: $runFlutterResult.'); |
| } |
| }); |
| |
| final int expectedNumberOfTransitions = buildMode == 'debug' ? 4 : 1; |
| if (transitionCount != expectedNumberOfTransitions) { |
| return TaskResult.failure( |
| 'Did not get expected number of transitions: $transitionCount ' |
| '(expected $expectedNumberOfTransitions)', |
| ); |
| } |
| return TaskResult.success(null); |
| }); |
| if (buildModeResult.failed) { |
| return buildModeResult; |
| } |
| } |
| return TaskResult.success(null); |
| }; |
| } |
| |
| Future<int> runFlutter({ |
| required List<String> options, |
| required void Function(String, Process) onLine, |
| }) async { |
| final Process process = await startFlutter( |
| 'run', |
| options: options, |
| ); |
| |
| final Completer<void> stdoutDone = Completer<void>(); |
| final Completer<void> stderrDone = Completer<void>(); |
| process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen((String line) { |
| onLine(line, process); |
| print('stdout: $line'); |
| }, onDone: stdoutDone.complete); |
| |
| process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen( |
| (String line) => print('stderr: $line'), |
| onDone: stderrDone.complete, |
| ); |
| |
| await Future.wait<void>(<Future<void>>[stdoutDone.future, stderrDone.future]); |
| final int exitCode = await process.exitCode; |
| return exitCode; |
| } |
| |
| final String _flutterBin = path.join(flutterDirectory.path, 'bin', 'flutter'); |
| |
| Future<void> enableNativeAssets() async { |
| print('Enabling configs for native assets...'); |
| final int configResult = await exec( |
| _flutterBin, |
| <String>[ |
| 'config', |
| '-v', |
| '--enable-native-assets', |
| ], |
| canFail: true); |
| if (configResult != 0) { |
| print('Failed to enable configuration, tasks may not run.'); |
| } |
| } |
| |
| Future<Directory> createTestProject( |
| String packageName, |
| Directory tempDirectory, |
| ) async { |
| await exec( |
| _flutterBin, |
| <String>[ |
| 'create', |
| '--no-pub', |
| '--template=package_ffi', |
| packageName, |
| ], |
| workingDirectory: tempDirectory.path, |
| ); |
| |
| final Directory packageDirectory = Directory( |
| path.join(tempDirectory.path, packageName), |
| ); |
| await _pinDependencies( |
| File(path.join(packageDirectory.path, 'pubspec.yaml')), |
| ); |
| await _pinDependencies( |
| File(path.join(packageDirectory.path, 'example', 'pubspec.yaml')), |
| ); |
| |
| await exec( |
| _flutterBin, |
| <String>[ |
| 'pub', |
| 'get', |
| ], |
| workingDirectory: packageDirectory.path, |
| ); |
| |
| return packageDirectory; |
| } |
| |
| Future<void> _pinDependencies(File pubspecFile) async { |
| final String oldPubspec = await pubspecFile.readAsString(); |
| final String newPubspec = oldPubspec.replaceAll(': ^', ': '); |
| await pubspecFile.writeAsString(newPubspec); |
| } |
| |
| |
| Future<T> inTempDir<T>(Future<T> Function(Directory tempDirectory) fun) async { |
| final Directory tempDirectory = dir(Directory.systemTemp.createTempSync().resolveSymbolicLinksSync()); |
| try { |
| return await fun(tempDirectory); |
| } finally { |
| tempDirectory.deleteSync(recursive: true); |
| } |
| } |