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 {
@override
String executable(BuildMode buildMode) {
final String binaryName = makefileExecutableName(project);
if (buildMode == BuildMode.debug) {
return fs.path.join(getLinuxBuildDirectory(), 'debug', binaryName);
} else {
return fs.path.join(getLinuxBuildDirectory(), 'release', binaryName);
}
return fs.path.join(getLinuxBuildDirectory(), getNameForBuildMode(buildMode), binaryName);
}
@override
......
......@@ -6,6 +6,7 @@ import 'package:meta/meta.dart';
import '../application_package.dart';
import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../globals.dart';
import '../ios/plist_parser.dart';
......@@ -131,7 +132,7 @@ class BuildableMacOSApp extends MacOSApp {
getMacOSBuildDirectory(),
'Build',
'Products',
buildMode == BuildMode.debug ? 'Debug' : 'Release',
toTitleCase(getNameForBuildMode(buildMode)),
appBundleNameFile.readAsStringSync().trim());
}
......
......@@ -44,19 +44,26 @@ Future<void> buildMacOS({
flutterProject.macos.outputFileList.createSync(recursive: true);
}
// Set debug or release mode.
String config = 'Debug';
if (buildInfo.isRelease) {
config = 'Release';
final Directory xcodeWorkspace = flutterProject.macos.xcodeWorkspace;
final XcodeProjectInfo projectInfo = await xcodeProjectInterpreter.getInfo(xcodeWorkspace.parent.path);
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 Process process = await processManager.start(<String>[
'/usr/bin/env',
'xcrun',
'xcodebuild',
'-workspace', flutterProject.macos.xcodeWorkspace.path,
'-configuration', '$config',
'-workspace', xcodeWorkspace.path,
'-configuration', '$configuration',
'-scheme', 'Runner',
'-derivedDataPath', flutterBuildDir.absolute.path,
'OBJROOT=${fs.path.join(flutterBuildDir.absolute.path, 'Build', 'Intermediates.noindex')}',
......
......@@ -98,6 +98,7 @@ class MacOSDevice extends Device {
}
// Make sure to call stop app after we've built.
_lastBuiltMode = debuggingOptions?.buildInfo?.mode;
await stopApp(package);
final Process process = await processManager.start(<String>[
executable
......@@ -126,7 +127,7 @@ class MacOSDevice extends Device {
// currently we rely on killing the isolate taking down the application.
@override
Future<bool> stopApp(covariant MacOSApp app) async {
return killProcess(app.executable(BuildMode.debug));
return killProcess(app.executable(_lastBuiltMode));
}
@override
......@@ -141,6 +142,9 @@ class MacOSDevice extends Device {
bool isSupportedForProject(FlutterProject flutterProject) {
return flutterProject.macos.existsSync();
}
// Track the last built mode from startApp.
BuildMode _lastBuiltMode;
}
class MacOSDevices extends PollingDeviceDiscovery {
......
......@@ -7,6 +7,7 @@ import 'package:meta/meta.dart';
import '../application_package.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../project.dart';
......@@ -66,7 +67,7 @@ class BuildableWindowsApp extends WindowsApp {
return fs.path.join(
getWindowsBuildDirectory(),
'x64',
buildMode == BuildMode.debug ? 'Debug' : 'Release',
toTitleCase(getNameForBuildMode(buildMode)),
'Runner',
exeNameFile.readAsStringSync().trim());
}
......
......@@ -12,6 +12,7 @@ import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build.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:mockito/mockito.dart';
import 'package:process/process.dart';
......@@ -21,6 +22,17 @@ import '../../src/context.dart';
import '../../src/mocks.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() {
MockProcessManager mockProcessManager;
MemoryFileSystem memoryFilesystem;
......@@ -51,6 +63,38 @@ void main() {
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 {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
......@@ -78,32 +122,44 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
testUsingContext('macOS build invokes xcode build', () async {
testUsingContext('macOS build invokes xcode build (debug)', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.directory('macos').createSync();
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
fs.file(fs.path.join('lib', 'main.dart')).createSync(recursive: true);
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', 'Release',
'-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;
});
createMinimalMockProjectFiles();
setUpMockXcodeBuildHandler('Debug');
await createTestCommandRunner(command).run(
const <String>['build', 'macos', '--debug']
);
}, overrides: <Type, Generator>{
FileSystem: () => memoryFilesystem,
ProcessManager: () => mockProcessManager,
Platform: () => macosPlatform,
FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
});
testUsingContext('macOS build invokes xcode build (profile)', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
createMinimalMockProjectFiles();
setUpMockXcodeBuildHandler('Profile');
await createTestCommandRunner(command).run(
const <String>['build', 'macos', '--profile']
);
}, 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(
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