diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index ff4b3c30e1f..6f85e894a0b 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart @@ -50,9 +50,9 @@ class ApplicationPackageFactory { case TargetPlatform.tester: return FlutterTesterApp.fromCurrentDirectory(); case TargetPlatform.darwin_x64: - return applicationBinary != null - ? MacOSApp.fromPrebuiltApp(applicationBinary) - : null; + return applicationBinary == null + ? MacOSApp.fromMacOSProject((await FlutterProject.current()).macos) + : MacOSApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.web: return WebApplicationPackage(await FlutterProject.current()); case TargetPlatform.linux_x64: diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart index 46b58d8ddff..3eeb3cb59ee 100644 --- a/packages/flutter_tools/lib/src/commands/build_macos.dart +++ b/packages/flutter_tools/lib/src/commands/build_macos.dart @@ -5,13 +5,10 @@ import 'dart:async'; import '../base/common.dart'; -import '../base/io.dart'; import '../base/platform.dart'; -import '../base/process_manager.dart'; import '../build_info.dart'; import '../cache.dart'; -import '../convert.dart'; -import '../globals.dart'; +import '../macos/build_macos.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; @@ -62,23 +59,7 @@ class BuildMacosCommand extends BuildSubCommand { if (!flutterProject.macos.existsSync()) { throwToolExit('No macOS desktop project configured.'); } - final Process process = await processManager.start([ - flutterProject.macos.buildScript.path, - Cache.flutterRoot, - buildInfo.isDebug ? 'debug' : 'release', - ], runInShell: true); - process.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printError); - process.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen(printStatus); - final int result = await process.exitCode; - if (result != 0) { - throwToolExit('Build process failed'); - } + await buildMacOS(flutterProject, buildInfo); return null; } } diff --git a/packages/flutter_tools/lib/src/desktop.dart b/packages/flutter_tools/lib/src/desktop.dart index e1af6698e73..791d33c83fd 100644 --- a/packages/flutter_tools/lib/src/desktop.dart +++ b/packages/flutter_tools/lib/src/desktop.dart @@ -6,9 +6,9 @@ import 'base/platform.dart'; import 'version.dart'; // Only launch or display desktop embedding devices if -// `FLUTTER_DESKTOP_EMBEDDING` environment variable is set to true. +// `ENABLE_FLUTTER_DESKTOP` environment variable is set to true. bool get flutterDesktopEnabled { - _flutterDesktopEnabled ??= platform.environment['FLUTTER_DESKTOP_EMBEDDING']?.toLowerCase() == 'true'; + _flutterDesktopEnabled ??= platform.environment['ENABLE_FLUTTER_DESKTOP']?.toLowerCase() == 'true'; return _flutterDesktopEnabled && !FlutterVersion.instance.isStable; } bool _flutterDesktopEnabled; diff --git a/packages/flutter_tools/lib/src/macos/application_package.dart b/packages/flutter_tools/lib/src/macos/application_package.dart index 28d8ade1716..90220a35473 100644 --- a/packages/flutter_tools/lib/src/macos/application_package.dart +++ b/packages/flutter_tools/lib/src/macos/application_package.dart @@ -6,8 +6,12 @@ import 'package:meta/meta.dart'; import '../application_package.dart'; import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; import '../globals.dart'; import '../ios/plist_utils.dart' as plist; +import '../project.dart'; /// Tests whether a [FileSystemEntity] is an macOS bundle directory bool _isBundleDirectory(FileSystemEntity entity) => @@ -16,6 +20,11 @@ bool _isBundleDirectory(FileSystemEntity entity) => abstract class MacOSApp extends ApplicationPackage { MacOSApp({@required String projectBundleId}) : super(id: projectBundleId); + /// Creates a new [MacOSApp] from a macOS project directory. + factory MacOSApp.fromMacOSProject(MacOSProject project) { + return BuildableMacOSApp(project); + } + /// Creates a new [MacOSApp] from an existing app bundle. /// /// `applicationBinary` is the path to the framework directory created by an @@ -24,21 +33,33 @@ abstract class MacOSApp extends ApplicationPackage { /// which is expected to start the application and send the observatory /// port over stdout. factory MacOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) { - final FileSystemEntityType entityType = fs.typeSync(applicationBinary.path); + final _ExecutableAndId executableAndId = _executableFromBundle(applicationBinary); + final Directory applicationBundle = fs.directory(applicationBinary); + return PrebuiltMacOSApp( + bundleDir: applicationBundle, + bundleName: applicationBundle.path, + projectBundleId: executableAndId.id, + executable: executableAndId.executable, + ); + } + + /// Look up the executable name for a macOS application bundle. + static _ExecutableAndId _executableFromBundle(Directory applicationBundle) { + final FileSystemEntityType entityType = fs.typeSync(applicationBundle.path); if (entityType == FileSystemEntityType.notFound) { - printError('File "${applicationBinary.path}" does not exist.'); + printError('File "${applicationBundle.path}" does not exist.'); return null; } Directory bundleDir; if (entityType == FileSystemEntityType.directory) { - final Directory directory = fs.directory(applicationBinary); + final Directory directory = fs.directory(applicationBundle); if (!_isBundleDirectory(directory)) { - printError('Folder "${applicationBinary.path}" is not an app bundle.'); + printError('Folder "${applicationBundle.path}" is not an app bundle.'); return null; } - bundleDir = fs.directory(applicationBinary); + bundleDir = fs.directory(applicationBundle); } else { - printError('Folder "${applicationBinary.path}" is not an app bundle.'); + printError('Folder "${applicationBundle.path}" is not an app bundle.'); return null; } final String plistPath = fs.path.join(bundleDir.path, 'Contents', 'Info.plist'); @@ -55,20 +76,16 @@ abstract class MacOSApp extends ApplicationPackage { final String executable = fs.path.join(bundleDir.path, 'Contents', 'MacOS', executableName); if (!fs.file(executable).existsSync()) { printError('Could not find macOS binary at $executable'); - return null; } - return PrebuiltMacOSApp( - bundleDir: bundleDir, - bundleName: fs.path.basename(bundleDir.path), - projectBundleId: id, - executable: executable, - ); + return _ExecutableAndId(executable, id); } @override String get displayName => id; - String get executable; + String applicationBundle(BuildMode buildMode); + + String executable(BuildMode buildMode); } class PrebuiltMacOSApp extends MacOSApp { @@ -76,16 +93,59 @@ class PrebuiltMacOSApp extends MacOSApp { @required this.bundleDir, @required this.bundleName, @required this.projectBundleId, - @required this.executable, - }) : super(projectBundleId: projectBundleId); + @required String executable, + }) : _executable = executable, + super(projectBundleId: projectBundleId); final Directory bundleDir; final String bundleName; final String projectBundleId; - @override - final String executable; + final String _executable; @override String get name => bundleName; + + @override + String applicationBundle(BuildMode buildMode) => bundleDir.path; + + @override + String executable(BuildMode buildMode) => _executable; +} + +class BuildableMacOSApp extends MacOSApp { + BuildableMacOSApp(this.project); + + final MacOSProject project; + + @override + String get name => 'macOS'; + + @override + String applicationBundle(BuildMode buildMode) { + final ProcessResult result = processManager.runSync([ + project.nameScript.path, + buildMode == BuildMode.debug ? 'debug' : 'release' + ], runInShell: true); + final String directory = result.stdout.toString().trim(); + return directory; + } + + @override + String executable(BuildMode buildMode) { + final ProcessResult result = processManager.runSync([ + project.nameScript.path, + buildMode == BuildMode.debug ? 'debug' : 'release' + ], runInShell: true); + final String directory = result.stdout.toString().trim(); + final _ExecutableAndId executableAndId = MacOSApp._executableFromBundle(fs.directory(directory)); + return executableAndId.executable; + } +} + +class _ExecutableAndId { + _ExecutableAndId(this.executable, this.id); + + final String executable; + final String id; } diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart new file mode 100644 index 00000000000..8ec7a61d327 --- /dev/null +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -0,0 +1,43 @@ +// Copyright 2019 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 '../base/common.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process_manager.dart'; +import '../build_info.dart'; +import '../cache.dart'; +import '../convert.dart'; +import '../globals.dart'; +import '../project.dart'; + +/// Builds the macOS project through the project shell script. +Future buildMacOS(FlutterProject flutterProject, BuildInfo buildInfo) async { + final Process process = await processManager.start([ + flutterProject.macos.buildScript.path, + Cache.flutterRoot, + buildInfo?.isDebug == true ? 'debug' : 'release', + ], runInShell: true); + final Status status = logger.startProgress( + 'Building macOS application...', + timeout: null, + ); + int result; + try { + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printError); + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(printTrace); + result = await process.exitCode; + } finally { + status.cancel(); + } + if (result != 0) { + throwToolExit('Build process failed'); + } +} diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index 74c11f8fadb..ccfd40eec85 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -8,11 +8,14 @@ import '../base/os.dart'; import '../base/platform.dart'; import '../base/process_manager.dart'; import '../build_info.dart'; +import '../cache.dart'; import '../convert.dart'; import '../device.dart'; import '../globals.dart'; import '../macos/application_package.dart'; +import '../project.dart'; import '../protocol_discovery.dart'; +import 'build_macos.dart'; import 'macos_workflow.dart'; /// A device that represents a desktop MacOS target. @@ -66,16 +69,27 @@ class MacOSDevice extends Device { bool usesTerminalUi = true, bool ipv6 = false, }) async { - if (!prebuiltApplication) { - return LaunchResult.failed(); - } // Stop any running applications with the same executable. + if (!prebuiltApplication) { + Cache.releaseLockEarly(); + await buildMacOS(await FlutterProject.current(), debuggingOptions?.buildInfo); + } + // Make sure to call stop app after we've built. await stopApp(package); - final Process process = await processManager.start([package.executable]); + final Process process = await processManager.start([ + package.executable(debuggingOptions?.buildInfo?.mode) + ]); + if (debuggingOptions?.buildInfo?.isRelease == true) { + return LaunchResult.succeeded(); + } final MacOSLogReader logReader = MacOSLogReader(package, process); final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(logReader); try { final Uri observatoryUri = await observatoryDiscovery.uri; + // Bring app to foreground. + await processManager.run([ + 'open', package.applicationBundle(debuggingOptions?.buildInfo?.mode), + ]); return LaunchResult.succeeded(observatoryUri: observatoryUri); } catch (error) { printError('Error waiting for a debug connection: $error'); @@ -91,6 +105,8 @@ class MacOSDevice extends Device { Future stopApp(covariant MacOSApp app) async { final RegExp whitespace = RegExp(r'\s+'); bool succeeded = true; + // assume debug for now. + final String executable = app.executable(BuildMode.debug); try { final ProcessResult result = await processManager.run([ 'ps', 'aux', @@ -100,7 +116,7 @@ class MacOSDevice extends Device { } final List lines = result.stdout.split('\n'); for (String line in lines) { - if (!line.contains(app.executable)) { + if (!line.contains(executable)) { continue; } final List values = line.split(whitespace); diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 56e16599ee3..ae80a3d00b8 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -538,6 +538,9 @@ class MacOSProject { // Note: The build script file exists as a temporary shim. File get buildScript => project.directory.childDirectory('macos').childFile('build.sh'); + + // Note: The name script file exists as a temporary shim. + File get nameScript => project.directory.childDirectory('macos').childFile('name_output.sh'); } /// The Windows sub project @@ -550,6 +553,9 @@ class WindowsProject { // Note: The build script file exists as a temporary shim. File get buildScript => project.directory.childDirectory('windows').childFile('build.bat'); + + // Note: The name script file exists as a temporary shim. + File get nameScript => project.directory.childDirectory('windows').childFile('name_output.sh'); } /// The Linux sub project. @@ -562,4 +568,7 @@ class LinuxProject { // Note: The build script file exists as a temporary shim. File get buildScript => project.directory.childDirectory('linux').childFile('build.sh'); + + // Note: The name script file exists as a temporary shim. + File get nameScript => project.directory.childDirectory('linux').childFile('name_output.sh'); } \ No newline at end of file diff --git a/packages/flutter_tools/test/commands/build_linux_test.dart b/packages/flutter_tools/test/commands/build_linux_test.dart index b9397ce1edc..c8131a9414f 100644 --- a/packages/flutter_tools/test/commands/build_linux_test.dart +++ b/packages/flutter_tools/test/commands/build_linux_test.dart @@ -44,6 +44,7 @@ void main() { ), throwsA(isInstanceOf())); }, overrides: { Platform: () => linuxPlatform, + FileSystem: () => memoryFilesystem, }); testUsingContext('Linux build fails on non-linux platform', () async { diff --git a/packages/flutter_tools/test/linux/linux_workflow_test.dart b/packages/flutter_tools/test/linux/linux_workflow_test.dart index 2cd86f16d41..d111ffced83 100644 --- a/packages/flutter_tools/test/linux/linux_workflow_test.dart +++ b/packages/flutter_tools/test/linux/linux_workflow_test.dart @@ -13,7 +13,7 @@ void main() { group(LinuxWorkflow, () { final MockPlatform linux = MockPlatform(); final MockPlatform linuxWithFde = MockPlatform() - ..environment['FLUTTER_DESKTOP_EMBEDDING'] = 'true'; + ..environment['ENABLE_FLUTTER_DESKTOP'] = 'true'; final MockPlatform notLinux = MockPlatform(); when(linux.isLinux).thenReturn(true); when(linuxWithFde.isLinux).thenReturn(true); diff --git a/packages/flutter_tools/test/macos/macos_device_test.dart b/packages/flutter_tools/test/macos/macos_device_test.dart index e379eddfdb9..715350b8cf6 100644 --- a/packages/flutter_tools/test/macos/macos_device_test.dart +++ b/packages/flutter_tools/test/macos/macos_device_test.dart @@ -32,7 +32,7 @@ void main() { testUsingContext('defaults', () async { final MockMacOSApp mockMacOSApp = MockMacOSApp(); - when(mockMacOSApp.executable).thenReturn('foo'); + when(mockMacOSApp.executable(any)).thenReturn('foo'); expect(await device.targetPlatform, TargetPlatform.darwin_x64); expect(device.name, 'macOS'); expect(await device.installApp(mockMacOSApp), true); @@ -49,7 +49,7 @@ void main() { tester 17193 0.0 0.2 4791128 37820 ?? S 2:27PM 0:00.09 /Applications/foo '''; final MockMacOSApp mockMacOSApp = MockMacOSApp(); - when(mockMacOSApp.executable).thenReturn('/Applications/foo'); + when(mockMacOSApp.executable(any)).thenReturn('/Applications/foo'); when(mockProcessManager.run(['ps', 'aux'])).thenAnswer((Invocation invocation) async { return ProcessResult(1, 0, psOut, ''); }); @@ -68,7 +68,7 @@ tester 17193 0.0 0.2 4791128 37820 ?? S 2:27PM 0:00.09 /Applica final MockProcessManager mockProcessManager = MockProcessManager(); final MockFile mockFile = MockFile(); final MockProcess mockProcess = MockProcess(); - when(macOSApp.executable).thenReturn('test'); + when(macOSApp.executable(any)).thenReturn('test'); when(mockFileSystem.file('test')).thenReturn(mockFile); when(mockFile.existsSync()).thenReturn(true); when(mockProcessManager.start(['test'])).thenAnswer((Invocation invocation) async { @@ -83,11 +83,6 @@ tester 17193 0.0 0.2 4791128 37820 ?? S 2:27PM 0:00.09 /Applica ]); }); - test('fails without a prebuilt application', () async { - final LaunchResult result = await device.startApp(macOSApp, prebuiltApplication: false); - expect(result.started, false); - }); - testUsingContext('Can run from prebuilt application', () async { final LaunchResult result = await device.startApp(macOSApp, prebuiltApplication: true); expect(result.started, true); diff --git a/packages/flutter_tools/test/macos/macos_workflow_test.dart b/packages/flutter_tools/test/macos/macos_workflow_test.dart index 0cc0ba37452..532ed724db4 100644 --- a/packages/flutter_tools/test/macos/macos_workflow_test.dart +++ b/packages/flutter_tools/test/macos/macos_workflow_test.dart @@ -16,7 +16,7 @@ void main() { group(MacOSWorkflow, () { final MockPlatform mac = MockPlatform(); final MockPlatform macWithFde = MockPlatform() - ..environment['FLUTTER_DESKTOP_EMBEDDING'] = 'true'; + ..environment['ENABLE_FLUTTER_DESKTOP'] = 'true'; final MockPlatform notMac = MockPlatform(); when(mac.isMacOS).thenReturn(true); when(macWithFde.isMacOS).thenReturn(true); diff --git a/packages/flutter_tools/test/windows/windows_workflow_test.dart b/packages/flutter_tools/test/windows/windows_workflow_test.dart index c8e31e2175d..5530a5c5ccb 100644 --- a/packages/flutter_tools/test/windows/windows_workflow_test.dart +++ b/packages/flutter_tools/test/windows/windows_workflow_test.dart @@ -14,7 +14,7 @@ void main() { group(WindowsWorkflow, () { final MockPlatform windows = MockPlatform(); final MockPlatform windowsWithFde = MockPlatform() - ..environment['FLUTTER_DESKTOP_EMBEDDING'] = 'true'; + ..environment['ENABLE_FLUTTER_DESKTOP'] = 'true'; final MockPlatform notWindows = MockPlatform(); when(windows.isWindows).thenReturn(true); when(windowsWithFde.isWindows).thenReturn(true);