diff --git a/script/tool/lib/src/create_all_plugins_app_command.dart b/script/tool/lib/src/create_all_plugins_app_command.dart index a23dc83d98f3..ea7d5a5c4388 100644 --- a/script/tool/lib/src/create_all_plugins_app_command.dart +++ b/script/tool/lib/src/create_all_plugins_app_command.dart @@ -6,22 +6,30 @@ import 'dart:io' as io; import 'package:file/file.dart'; import 'package:path/path.dart' as p; +import 'package:platform/platform.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'common/core.dart'; import 'common/package_command.dart'; +import 'common/process_runner.dart'; import 'common/repository_package.dart'; const String _outputDirectoryFlag = 'output-dir'; +const int _exitUpdateMacosPodfileFailed = 3; +const int _exitUpdateMacosPbxprojFailed = 4; +const int _exitGenNativeBuildFilesFailed = 5; + /// A command to create an application that builds all in a single application. class CreateAllPluginsAppCommand extends PackageCommand { /// Creates an instance of the builder command. CreateAllPluginsAppCommand( Directory packagesDir, { + ProcessRunner processRunner = const ProcessRunner(), Directory? pluginsRoot, - }) : super(packagesDir) { + Platform platform = const LocalPlatform(), + }) : super(packagesDir, processRunner: processRunner, platform: platform) { final Directory defaultDir = pluginsRoot ?? packagesDir.fileSystem.currentDirectory; argParser.addOption(_outputDirectoryFlag, @@ -61,10 +69,28 @@ class CreateAllPluginsAppCommand extends PackageCommand { print(''); } + await _genPubspecWithAllPlugins(); + + // Run `flutter pub get` to generate all native build files. + // TODO(stuartmorgan): This hangs on Windows for some reason. Since it's + // currently not needed on Windows, skip it there, but we should investigate + // further and/or implement https://github.com/flutter/flutter/issues/93407, + // and remove the need for this conditional. + if (!platform.isWindows) { + if (!await _genNativeBuildFiles()) { + printError( + "Failed to generate native build files via 'flutter pub get'"); + throw ToolExit(_exitGenNativeBuildFilesFailed); + } + } + await Future.wait(>[ - _genPubspecWithAllPlugins(), _updateAppGradle(), _updateManifest(), + _updateMacosPbxproj(), + // This step requires the native file generation triggered by + // flutter pub get above, so can't currently be run on Windows. + if (!platform.isWindows) _updateMacosPodfile(), ]); } @@ -259,4 +285,61 @@ dev_dependencies:${_pubspecMapString(pubspec.devDependencies)} return buffer.toString(); } + + Future _genNativeBuildFiles() async { + final int exitCode = await processRunner.runAndStream( + flutterCommand, + ['pub', 'get'], + workingDir: _appDirectory, + ); + return exitCode == 0; + } + + Future _updateMacosPodfile() async { + /// Only change the macOS deployment target if the host platform is macOS. + /// The Podfile is not generated on other platforms. + if (!platform.isMacOS) { + return; + } + + final File podfileFile = + app.platformDirectory(FlutterPlatform.macos).childFile('Podfile'); + if (!podfileFile.existsSync()) { + printError("Can't find Podfile for macOS"); + throw ToolExit(_exitUpdateMacosPodfileFailed); + } + + final StringBuffer newPodfile = StringBuffer(); + for (final String line in podfileFile.readAsLinesSync()) { + if (line.contains('platform :osx')) { + // macOS 10.15 is required by in_app_purchase. + newPodfile.writeln("platform :osx, '10.15'"); + } else { + newPodfile.writeln(line); + } + } + podfileFile.writeAsStringSync(newPodfile.toString()); + } + + Future _updateMacosPbxproj() async { + final File pbxprojFile = app + .platformDirectory(FlutterPlatform.macos) + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj'); + if (!pbxprojFile.existsSync()) { + printError("Can't find project.pbxproj for macOS"); + throw ToolExit(_exitUpdateMacosPbxprojFailed); + } + + final StringBuffer newPbxproj = StringBuffer(); + for (final String line in pbxprojFile.readAsLinesSync()) { + if (line.contains('MACOSX_DEPLOYMENT_TARGET')) { + // macOS 10.15 is required by in_app_purchase. + newPbxproj.writeln(' MACOSX_DEPLOYMENT_TARGET = 10.15;'); + } else { + newPbxproj.writeln(line); + } + } + pbxprojFile.writeAsStringSync(newPbxproj.toString()); + } } diff --git a/script/tool/test/create_all_plugins_app_command_test.dart b/script/tool/test/create_all_plugins_app_command_test.dart index 830dd59a8d42..cb2347fe9cc8 100644 --- a/script/tool/test/create_all_plugins_app_command_test.dart +++ b/script/tool/test/create_all_plugins_app_command_test.dart @@ -7,10 +7,12 @@ import 'dart:io' as io; import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/local.dart'; +import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/create_all_plugins_app_command.dart'; import 'package:platform/platform.dart'; import 'package:test/test.dart'; +import 'mocks.dart'; import 'util.dart'; void main() { @@ -20,6 +22,7 @@ void main() { late FileSystem fileSystem; late Directory testRoot; late Directory packagesDir; + late RecordingProcessRunner processRunner; setUp(() { // Since the core of this command is a call to 'flutter create', the test @@ -28,9 +31,11 @@ void main() { fileSystem = const LocalFileSystem(); testRoot = fileSystem.systemTempDirectory.createTempSync(); packagesDir = testRoot.childDirectory('packages'); + processRunner = RecordingProcessRunner(); command = CreateAllPluginsAppCommand( packagesDir, + processRunner: processRunner, pluginsRoot: testRoot, ); runner = CommandRunner( @@ -103,6 +108,91 @@ void main() { baselinePubspec.environment?[dartSdkKey]); }); + test('macOS deployment target is modified in Podfile', () async { + createFakePlugin('plugina', packagesDir); + + final File podfileFile = command.packagesDir.parent + .childDirectory('all_plugins') + .childDirectory('macos') + .childFile('Podfile'); + podfileFile.createSync(recursive: true); + podfileFile.writeAsStringSync(""" +platform :osx, '10.11' +# some other line +"""); + + await runCapturingPrint(runner, ['all-plugins-app']); + final List podfile = command.app + .platformDirectory(FlutterPlatform.macos) + .childFile('Podfile') + .readAsLinesSync(); + + expect( + podfile, + everyElement((String line) => + !line.contains('platform :osx') || line.contains("'10.15'"))); + }, + // Podfile is only generated (and thus only edited) on macOS. + skip: !io.Platform.isMacOS); + + test('macOS deployment target is modified in pbxproj', () async { + createFakePlugin('plugina', packagesDir); + + await runCapturingPrint(runner, ['all-plugins-app']); + final List pbxproj = command.app + .platformDirectory(FlutterPlatform.macos) + .childDirectory('Runner.xcodeproj') + .childFile('project.pbxproj') + .readAsLinesSync(); + + expect( + pbxproj, + everyElement((String line) => + !line.contains('MACOSX_DEPLOYMENT_TARGET') || + line.contains('10.15'))); + }); + + test('calls flutter pub get', () async { + createFakePlugin('plugina', packagesDir); + + await runCapturingPrint(runner, ['all-plugins-app']); + + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + getFlutterCommand(const LocalPlatform()), + const ['pub', 'get'], + testRoot.childDirectory('all_plugins').path), + ])); + }, + // See comment about Windows in create_all_plugins_app_command.dart + skip: io.Platform.isWindows); + + test('fails if flutter pub get fails', () async { + createFakePlugin('plugina', packagesDir); + + processRunner.mockProcessesForExecutable[ + getFlutterCommand(const LocalPlatform())] = [ + MockProcess(exitCode: 1) + ]; + Error? commandError; + final List output = await runCapturingPrint( + runner, ['all-plugins-app'], errorHandler: (Error e) { + commandError = e; + }); + + expect(commandError, isA()); + expect( + output, + containsAllInOrder([ + contains( + "Failed to generate native build files via 'flutter pub get'"), + ])); + }, + // See comment about Windows in create_all_plugins_app_command.dart + skip: io.Platform.isWindows); + test('handles --output-dir', () async { createFakePlugin('plugina', packagesDir);