Skip to content

Commit da92fc1

Browse files
Add run capability for macOS target (flutter#31218)
1 parent 9fd8caa commit da92fc1

12 files changed

+164
-59
lines changed

packages/flutter_tools/lib/src/application_package.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ class ApplicationPackageFactory {
5050
case TargetPlatform.tester:
5151
return FlutterTesterApp.fromCurrentDirectory();
5252
case TargetPlatform.darwin_x64:
53-
return applicationBinary != null
54-
? MacOSApp.fromPrebuiltApp(applicationBinary)
55-
: null;
53+
return applicationBinary == null
54+
? MacOSApp.fromMacOSProject((await FlutterProject.current()).macos)
55+
: MacOSApp.fromPrebuiltApp(applicationBinary);
5656
case TargetPlatform.web:
5757
return WebApplicationPackage(await FlutterProject.current());
5858
case TargetPlatform.linux_x64:

packages/flutter_tools/lib/src/commands/build_macos.dart

+2-21
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@
55
import 'dart:async';
66

77
import '../base/common.dart';
8-
import '../base/io.dart';
98
import '../base/platform.dart';
10-
import '../base/process_manager.dart';
119
import '../build_info.dart';
1210
import '../cache.dart';
13-
import '../convert.dart';
14-
import '../globals.dart';
11+
import '../macos/build_macos.dart';
1512
import '../project.dart';
1613
import '../runner/flutter_command.dart' show FlutterCommandResult;
1714
import 'build.dart';
@@ -62,23 +59,7 @@ class BuildMacosCommand extends BuildSubCommand {
6259
if (!flutterProject.macos.existsSync()) {
6360
throwToolExit('No macOS desktop project configured.');
6461
}
65-
final Process process = await processManager.start(<String>[
66-
flutterProject.macos.buildScript.path,
67-
Cache.flutterRoot,
68-
buildInfo.isDebug ? 'debug' : 'release',
69-
], runInShell: true);
70-
process.stderr
71-
.transform(utf8.decoder)
72-
.transform(const LineSplitter())
73-
.listen(printError);
74-
process.stdout
75-
.transform(utf8.decoder)
76-
.transform(const LineSplitter())
77-
.listen(printStatus);
78-
final int result = await process.exitCode;
79-
if (result != 0) {
80-
throwToolExit('Build process failed');
81-
}
62+
await buildMacOS(flutterProject, buildInfo);
8263
return null;
8364
}
8465
}

packages/flutter_tools/lib/src/desktop.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import 'base/platform.dart';
66
import 'version.dart';
77

88
// Only launch or display desktop embedding devices if
9-
// `FLUTTER_DESKTOP_EMBEDDING` environment variable is set to true.
9+
// `ENABLE_FLUTTER_DESKTOP` environment variable is set to true.
1010
bool get flutterDesktopEnabled {
11-
_flutterDesktopEnabled ??= platform.environment['FLUTTER_DESKTOP_EMBEDDING']?.toLowerCase() == 'true';
11+
_flutterDesktopEnabled ??= platform.environment['ENABLE_FLUTTER_DESKTOP']?.toLowerCase() == 'true';
1212
return _flutterDesktopEnabled && !FlutterVersion.instance.isStable;
1313
}
1414
bool _flutterDesktopEnabled;

packages/flutter_tools/lib/src/macos/application_package.dart

+78-18
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import 'package:meta/meta.dart';
66

77
import '../application_package.dart';
88
import '../base/file_system.dart';
9+
import '../base/io.dart';
10+
import '../base/process_manager.dart';
11+
import '../build_info.dart';
912
import '../globals.dart';
1013
import '../ios/plist_utils.dart' as plist;
14+
import '../project.dart';
1115

1216
/// Tests whether a [FileSystemEntity] is an macOS bundle directory
1317
bool _isBundleDirectory(FileSystemEntity entity) =>
@@ -16,6 +20,11 @@ bool _isBundleDirectory(FileSystemEntity entity) =>
1620
abstract class MacOSApp extends ApplicationPackage {
1721
MacOSApp({@required String projectBundleId}) : super(id: projectBundleId);
1822

23+
/// Creates a new [MacOSApp] from a macOS project directory.
24+
factory MacOSApp.fromMacOSProject(MacOSProject project) {
25+
return BuildableMacOSApp(project);
26+
}
27+
1928
/// Creates a new [MacOSApp] from an existing app bundle.
2029
///
2130
/// `applicationBinary` is the path to the framework directory created by an
@@ -24,21 +33,33 @@ abstract class MacOSApp extends ApplicationPackage {
2433
/// which is expected to start the application and send the observatory
2534
/// port over stdout.
2635
factory MacOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) {
27-
final FileSystemEntityType entityType = fs.typeSync(applicationBinary.path);
36+
final _ExecutableAndId executableAndId = _executableFromBundle(applicationBinary);
37+
final Directory applicationBundle = fs.directory(applicationBinary);
38+
return PrebuiltMacOSApp(
39+
bundleDir: applicationBundle,
40+
bundleName: applicationBundle.path,
41+
projectBundleId: executableAndId.id,
42+
executable: executableAndId.executable,
43+
);
44+
}
45+
46+
/// Look up the executable name for a macOS application bundle.
47+
static _ExecutableAndId _executableFromBundle(Directory applicationBundle) {
48+
final FileSystemEntityType entityType = fs.typeSync(applicationBundle.path);
2849
if (entityType == FileSystemEntityType.notFound) {
29-
printError('File "${applicationBinary.path}" does not exist.');
50+
printError('File "${applicationBundle.path}" does not exist.');
3051
return null;
3152
}
3253
Directory bundleDir;
3354
if (entityType == FileSystemEntityType.directory) {
34-
final Directory directory = fs.directory(applicationBinary);
55+
final Directory directory = fs.directory(applicationBundle);
3556
if (!_isBundleDirectory(directory)) {
36-
printError('Folder "${applicationBinary.path}" is not an app bundle.');
57+
printError('Folder "${applicationBundle.path}" is not an app bundle.');
3758
return null;
3859
}
39-
bundleDir = fs.directory(applicationBinary);
60+
bundleDir = fs.directory(applicationBundle);
4061
} else {
41-
printError('Folder "${applicationBinary.path}" is not an app bundle.');
62+
printError('Folder "${applicationBundle.path}" is not an app bundle.');
4263
return null;
4364
}
4465
final String plistPath = fs.path.join(bundleDir.path, 'Contents', 'Info.plist');
@@ -55,37 +76,76 @@ abstract class MacOSApp extends ApplicationPackage {
5576
final String executable = fs.path.join(bundleDir.path, 'Contents', 'MacOS', executableName);
5677
if (!fs.file(executable).existsSync()) {
5778
printError('Could not find macOS binary at $executable');
58-
return null;
5979
}
60-
return PrebuiltMacOSApp(
61-
bundleDir: bundleDir,
62-
bundleName: fs.path.basename(bundleDir.path),
63-
projectBundleId: id,
64-
executable: executable,
65-
);
80+
return _ExecutableAndId(executable, id);
6681
}
6782

6883
@override
6984
String get displayName => id;
7085

71-
String get executable;
86+
String applicationBundle(BuildMode buildMode);
87+
88+
String executable(BuildMode buildMode);
7289
}
7390

7491
class PrebuiltMacOSApp extends MacOSApp {
7592
PrebuiltMacOSApp({
7693
@required this.bundleDir,
7794
@required this.bundleName,
7895
@required this.projectBundleId,
79-
@required this.executable,
80-
}) : super(projectBundleId: projectBundleId);
96+
@required String executable,
97+
}) : _executable = executable,
98+
super(projectBundleId: projectBundleId);
8199

82100
final Directory bundleDir;
83101
final String bundleName;
84102
final String projectBundleId;
85103

86-
@override
87-
final String executable;
104+
final String _executable;
88105

89106
@override
90107
String get name => bundleName;
108+
109+
@override
110+
String applicationBundle(BuildMode buildMode) => bundleDir.path;
111+
112+
@override
113+
String executable(BuildMode buildMode) => _executable;
114+
}
115+
116+
class BuildableMacOSApp extends MacOSApp {
117+
BuildableMacOSApp(this.project);
118+
119+
final MacOSProject project;
120+
121+
@override
122+
String get name => 'macOS';
123+
124+
@override
125+
String applicationBundle(BuildMode buildMode) {
126+
final ProcessResult result = processManager.runSync(<String>[
127+
project.nameScript.path,
128+
buildMode == BuildMode.debug ? 'debug' : 'release'
129+
], runInShell: true);
130+
final String directory = result.stdout.toString().trim();
131+
return directory;
132+
}
133+
134+
@override
135+
String executable(BuildMode buildMode) {
136+
final ProcessResult result = processManager.runSync(<String>[
137+
project.nameScript.path,
138+
buildMode == BuildMode.debug ? 'debug' : 'release'
139+
], runInShell: true);
140+
final String directory = result.stdout.toString().trim();
141+
final _ExecutableAndId executableAndId = MacOSApp._executableFromBundle(fs.directory(directory));
142+
return executableAndId.executable;
143+
}
144+
}
145+
146+
class _ExecutableAndId {
147+
_ExecutableAndId(this.executable, this.id);
148+
149+
final String executable;
150+
final String id;
91151
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2019 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import '../base/common.dart';
6+
import '../base/io.dart';
7+
import '../base/logger.dart';
8+
import '../base/process_manager.dart';
9+
import '../build_info.dart';
10+
import '../cache.dart';
11+
import '../convert.dart';
12+
import '../globals.dart';
13+
import '../project.dart';
14+
15+
/// Builds the macOS project through the project shell script.
16+
Future<void> buildMacOS(FlutterProject flutterProject, BuildInfo buildInfo) async {
17+
final Process process = await processManager.start(<String>[
18+
flutterProject.macos.buildScript.path,
19+
Cache.flutterRoot,
20+
buildInfo?.isDebug == true ? 'debug' : 'release',
21+
], runInShell: true);
22+
final Status status = logger.startProgress(
23+
'Building macOS application...',
24+
timeout: null,
25+
);
26+
int result;
27+
try {
28+
process.stderr
29+
.transform(utf8.decoder)
30+
.transform(const LineSplitter())
31+
.listen(printError);
32+
process.stdout
33+
.transform(utf8.decoder)
34+
.transform(const LineSplitter())
35+
.listen(printTrace);
36+
result = await process.exitCode;
37+
} finally {
38+
status.cancel();
39+
}
40+
if (result != 0) {
41+
throwToolExit('Build process failed');
42+
}
43+
}

packages/flutter_tools/lib/src/macos/macos_device.dart

+20-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import '../base/os.dart';
88
import '../base/platform.dart';
99
import '../base/process_manager.dart';
1010
import '../build_info.dart';
11+
import '../cache.dart';
1112
import '../convert.dart';
1213
import '../device.dart';
1314
import '../globals.dart';
1415
import '../macos/application_package.dart';
16+
import '../project.dart';
1517
import '../protocol_discovery.dart';
18+
import 'build_macos.dart';
1619
import 'macos_workflow.dart';
1720

1821
/// A device that represents a desktop MacOS target.
@@ -66,16 +69,27 @@ class MacOSDevice extends Device {
6669
bool usesTerminalUi = true,
6770
bool ipv6 = false,
6871
}) async {
72+
// Stop any running applications with the same executable.
6973
if (!prebuiltApplication) {
70-
return LaunchResult.failed();
74+
Cache.releaseLockEarly();
75+
await buildMacOS(await FlutterProject.current(), debuggingOptions?.buildInfo);
7176
}
72-
// Stop any running applications with the same executable.
77+
// Make sure to call stop app after we've built.
7378
await stopApp(package);
74-
final Process process = await processManager.start(<String>[package.executable]);
79+
final Process process = await processManager.start(<String>[
80+
package.executable(debuggingOptions?.buildInfo?.mode)
81+
]);
82+
if (debuggingOptions?.buildInfo?.isRelease == true) {
83+
return LaunchResult.succeeded();
84+
}
7585
final MacOSLogReader logReader = MacOSLogReader(package, process);
7686
final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(logReader);
7787
try {
7888
final Uri observatoryUri = await observatoryDiscovery.uri;
89+
// Bring app to foreground.
90+
await processManager.run(<String>[
91+
'open', package.applicationBundle(debuggingOptions?.buildInfo?.mode),
92+
]);
7993
return LaunchResult.succeeded(observatoryUri: observatoryUri);
8094
} catch (error) {
8195
printError('Error waiting for a debug connection: $error');
@@ -91,6 +105,8 @@ class MacOSDevice extends Device {
91105
Future<bool> stopApp(covariant MacOSApp app) async {
92106
final RegExp whitespace = RegExp(r'\s+');
93107
bool succeeded = true;
108+
// assume debug for now.
109+
final String executable = app.executable(BuildMode.debug);
94110
try {
95111
final ProcessResult result = await processManager.run(<String>[
96112
'ps', 'aux',
@@ -100,7 +116,7 @@ class MacOSDevice extends Device {
100116
}
101117
final List<String> lines = result.stdout.split('\n');
102118
for (String line in lines) {
103-
if (!line.contains(app.executable)) {
119+
if (!line.contains(executable)) {
104120
continue;
105121
}
106122
final List<String> values = line.split(whitespace);

packages/flutter_tools/lib/src/project.dart

+9
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,9 @@ class MacOSProject {
538538

539539
// Note: The build script file exists as a temporary shim.
540540
File get buildScript => project.directory.childDirectory('macos').childFile('build.sh');
541+
542+
// Note: The name script file exists as a temporary shim.
543+
File get nameScript => project.directory.childDirectory('macos').childFile('name_output.sh');
541544
}
542545

543546
/// The Windows sub project
@@ -550,6 +553,9 @@ class WindowsProject {
550553

551554
// Note: The build script file exists as a temporary shim.
552555
File get buildScript => project.directory.childDirectory('windows').childFile('build.bat');
556+
557+
// Note: The name script file exists as a temporary shim.
558+
File get nameScript => project.directory.childDirectory('windows').childFile('name_output.sh');
553559
}
554560

555561
/// The Linux sub project.
@@ -562,4 +568,7 @@ class LinuxProject {
562568

563569
// Note: The build script file exists as a temporary shim.
564570
File get buildScript => project.directory.childDirectory('linux').childFile('build.sh');
571+
572+
// Note: The name script file exists as a temporary shim.
573+
File get nameScript => project.directory.childDirectory('linux').childFile('name_output.sh');
565574
}

packages/flutter_tools/test/commands/build_linux_test.dart

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void main() {
4444
), throwsA(isInstanceOf<ToolExit>()));
4545
}, overrides: <Type, Generator>{
4646
Platform: () => linuxPlatform,
47+
FileSystem: () => memoryFilesystem,
4748
});
4849

4950
testUsingContext('Linux build fails on non-linux platform', () async {

packages/flutter_tools/test/linux/linux_workflow_test.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ void main() {
1313
group(LinuxWorkflow, () {
1414
final MockPlatform linux = MockPlatform();
1515
final MockPlatform linuxWithFde = MockPlatform()
16-
..environment['FLUTTER_DESKTOP_EMBEDDING'] = 'true';
16+
..environment['ENABLE_FLUTTER_DESKTOP'] = 'true';
1717
final MockPlatform notLinux = MockPlatform();
1818
when(linux.isLinux).thenReturn(true);
1919
when(linuxWithFde.isLinux).thenReturn(true);

0 commit comments

Comments
 (0)