Unverified Commit 74076958 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Add profile support on macOS (#39264)

Fix some places where Debug/Release was treated as a binary switch.

Makes similar changes to Windows and Linux to simplify adding profile
support to those platforms in the future. This means `--profile` builds
will fail on Linux and Windows for now, but that's fine since they
aren't actually supported, and unlike `--release` don't provide useful
functionality at the native code level.

Also fixes 'stopApp' always using Debug on macOS, to avoid showing an
error when running Profile (or Release).

Fixes #33203
parent 6204eb64
...@@ -58,11 +58,7 @@ class BuildableLinuxApp extends LinuxApp { ...@@ -58,11 +58,7 @@ class BuildableLinuxApp extends LinuxApp {
@override @override
String executable(BuildMode buildMode) { String executable(BuildMode buildMode) {
final String binaryName = makefileExecutableName(project); final String binaryName = makefileExecutableName(project);
if (buildMode == BuildMode.debug) { return fs.path.join(getLinuxBuildDirectory(), getNameForBuildMode(buildMode), binaryName);
return fs.path.join(getLinuxBuildDirectory(), 'debug', binaryName);
} else {
return fs.path.join(getLinuxBuildDirectory(), 'release', binaryName);
}
} }
@override @override
......
...@@ -6,6 +6,7 @@ import 'package:meta/meta.dart'; ...@@ -6,6 +6,7 @@ import 'package:meta/meta.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/plist_parser.dart'; import '../ios/plist_parser.dart';
...@@ -131,7 +132,7 @@ class BuildableMacOSApp extends MacOSApp { ...@@ -131,7 +132,7 @@ class BuildableMacOSApp extends MacOSApp {
getMacOSBuildDirectory(), getMacOSBuildDirectory(),
'Build', 'Build',
'Products', 'Products',
buildMode == BuildMode.debug ? 'Debug' : 'Release', toTitleCase(getNameForBuildMode(buildMode)),
appBundleNameFile.readAsStringSync().trim()); appBundleNameFile.readAsStringSync().trim());
} }
......
...@@ -44,19 +44,26 @@ Future<void> buildMacOS({ ...@@ -44,19 +44,26 @@ Future<void> buildMacOS({
flutterProject.macos.outputFileList.createSync(recursive: true); flutterProject.macos.outputFileList.createSync(recursive: true);
} }
// Set debug or release mode. final Directory xcodeWorkspace = flutterProject.macos.xcodeWorkspace;
String config = 'Debug';
if (buildInfo.isRelease) { final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(xcodeWorkspace.parent.path);
config = 'Release'; final String scheme = projectInfo.schemeFor(buildInfo);
if (scheme == null) {
throwToolExit('Unable to find expected scheme in Xcode project.');
}
final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
if (configuration == null) {
throwToolExit('Unable to find expected configuration in Xcode project.');
} }
// Run build script provided by application.
// Run the Xcode build.
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
final Process process = await processManager.start(<String>[ final Process process = await processManager.start(<String>[
'/usr/bin/env', '/usr/bin/env',
'xcrun', 'xcrun',
'xcodebuild', 'xcodebuild',
'-workspace', flutterProject.macos.xcodeWorkspace.path, '-workspace', xcodeWorkspace.path,
'-configuration', '$config', '-configuration', '$configuration',
'-scheme', 'Runner', '-scheme', 'Runner',
'-derivedDataPath', flutterBuildDir.absolute.path, '-derivedDataPath', flutterBuildDir.absolute.path,
'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}', 'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
......
...@@ -98,6 +98,7 @@ class MacOSDevice extends Device { ...@@ -98,6 +98,7 @@ class MacOSDevice extends Device {
} }
// Make sure to call stop app after we've built. // Make sure to call stop app after we've built.
_lastBuiltMode = debuggingOptions?.buildInfo?.mode;
await stopApp(package); await stopApp(package);
final Process process = await processManager.start(<String>[ final Process process = await processManager.start(<String>[
executable executable
...@@ -126,7 +127,7 @@ class MacOSDevice extends Device { ...@@ -126,7 +127,7 @@ class MacOSDevice extends Device {
// currently we rely on killing the isolate taking down the application. // currently we rely on killing the isolate taking down the application.
@override @override
Future<bool> stopApp(covariant MacOSApp app) async { Future<bool> stopApp(covariant MacOSApp app) async {
return killProcess(app.executable(BuildMode.debug)); return killProcess(app.executable(_lastBuiltMode));
} }
@override @override
...@@ -141,6 +142,9 @@ class MacOSDevice extends Device { ...@@ -141,6 +142,9 @@ class MacOSDevice extends Device {
bool isSupportedForProject(FlutterProject flutterProject) { bool isSupportedForProject(FlutterProject flutterProject) {
return flutterProject.macos.existsSync(); return flutterProject.macos.existsSync();
} }
// Track the last built mode from startApp.
BuildMode _lastBuiltMode;
} }
class MacOSDevices extends PollingDeviceDiscovery { class MacOSDevices extends PollingDeviceDiscovery {
......
...@@ -7,6 +7,7 @@ import 'package:meta/meta.dart'; ...@@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../project.dart'; import '../project.dart';
...@@ -66,7 +67,7 @@ class BuildableWindowsApp extends WindowsApp { ...@@ -66,7 +67,7 @@ class BuildableWindowsApp extends WindowsApp {
return fs.path.join( return fs.path.join(
getWindowsBuildDirectory(), getWindowsBuildDirectory(),
'x64', 'x64',
buildMode == BuildMode.debug ? 'Debug' : 'Release', toTitleCase(getNameForBuildMode(buildMode)),
'Runner', 'Runner',
exeNameFile.readAsStringSync().trim()); exeNameFile.readAsStringSync().trim());
} }
......
...@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/build_info.dart'; ...@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -21,6 +22,17 @@ import '../../src/context.dart'; ...@@ -21,6 +22,17 @@ import '../../src/context.dart';
import '../../src/mocks.dart'; import '../../src/mocks.dart';
import '../../src/testbed.dart'; import '../../src/testbed.dart';
class FakeXcodeProjectInterpreterWithProfile extends FakeXcodeProjectInterpreter {
@override
Future<XcodeProjectInfo> getInfo(String projectPath) async {
return XcodeProjectInfo(
<String>['Runner'],
<String>['Debug', 'Profile', 'Release'],
<String>['Runner'],
);
}
}
void main() { void main() {
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
MemoryFileSystem memoryFilesystem; MemoryFileSystem memoryFilesystem;
...@@ -51,6 +63,38 @@ void main() { ...@@ -51,6 +63,38 @@ void main() {
when(notMacosPlatform.isMacOS).thenReturn(false); when(notMacosPlatform.isMacOS).thenReturn(false);
}); });
// Sets up the minimal mock project files necessary for macOS builds to succeed.
void createMinimalMockProjectFiles() {
fs.directory('macos').createSync();
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
}
// Mocks the process manager to handle an xcodebuild call to build the app
// in the given configuration.
void setUpMockXcodeBuildHandler(String configuration) {
final FlutterProject flutterProject = FlutterProject.fromDirectory(fs.currentDirectory);
final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
when(mockProcessManager.start(<String>[
'/usr/bin/env',
'xcrun',
'xcodebuild',
'-workspace', flutterProject.macos.xcodeWorkspace.path,
'-configuration', configuration,
'-scheme', 'Runner',
'-derivedDataPath', flutterBuildDir.absolute.path,
'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}',
'COMPILER_INDEX_STORE_ENABLE=NO',
])).thenAnswer((Invocation invocation) async {
fs.file(fs.path.join('macos', 'Flutter', 'ephemeral', '.app_filename'))
..createSync(recursive: true)
..writeAsStringSync('example.app');
return mockProcess;
});
}
testUsingContext('macOS build fails when there is no macos project', () async { testUsingContext('macOS build fails when there is no macos project', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
...@@ -78,32 +122,44 @@ void main() { ...@@ -78,32 +122,44 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
}); });
testUsingContext('macOS build invokes xcode build', () async { testUsingContext('macOS build invokes xcode build (debug)', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
fs.directory('macos').createSync(); createMinimalMockProjectFiles();
fs.file('pubspec.yaml').createSync(); setUpMockXcodeBuildHandler('Debug');
fs.file('.packages').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true); await createTestCommandRunner(command).run(
final FlutterProject flutterProject = FlutterProject.fromDirectory(fs.currentDirectory); const <String>['build', 'macos', '--debug']
final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory()); );
when(mockProcessManager.start(<String>[ }, overrides: <Type, Generator>{
'/usr/bin/env', FileSystem: () => memoryFilesystem,
'xcrun', ProcessManager: () => mockProcessManager,
'xcodebuild', Platform: () => macosPlatform,
'-workspace', flutterProject.macos.xcodeWorkspace.path, FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
'-configuration', 'Release', });
'-scheme', 'Runner',
'-derivedDataPath', flutterBuildDir.absolute.path, testUsingContext('macOS build invokes xcode build (profile)', () async {
'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}', final BuildCommand command = BuildCommand();
'SYMROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Products')}', applyMocksToCommand(command);
'COMPILER_INDEX_STORE_ENABLE=NO', createMinimalMockProjectFiles();
])).thenAnswer((Invocation invocation) async { setUpMockXcodeBuildHandler('Profile');
fs.file(fs.path.join('macos', 'Flutter', 'ephemeral', '.app_filename'))
..createSync(recursive: true) await createTestCommandRunner(command).run(
..writeAsStringSync('example.app'); const <String>['build', 'macos', '--profile']
return mockProcess; );
}); }, overrides: <Type, Generator>{
FileSystem: () => memoryFilesystem,
ProcessManager: () => mockProcessManager,
Platform: () => macosPlatform,
XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithProfile(),
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
testUsingContext('macOS build invokes xcode build (release)', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
createMinimalMockProjectFiles();
setUpMockXcodeBuildHandler('Release');
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>['build', 'macos', '--release'] const <String>['build', 'macos', '--release']
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment