Unverified Commit 7f959d8b authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add Xcode build script for macOS target (#31329)

parent 2e2eeb79
#!/usr/bin/env bash
# Copyright 2018 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.
# TODO(jonahwilliams): refactor this and xcode_backend.sh into one script
# once macOS supports the same configuration as iOS.
RunCommand() {
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@"
return $?
}
EchoError() {
echo "$@" 1>&2
}
# Set the working directory to the project root
project_path="${SOURCE_ROOT}/.."
RunCommand pushd "${project_path}" > /dev/null
# Set the verbose flag.
verbose_flag=""
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
verbose_flag="--verbose"
fi
# Set the target file.
target_path="lib/main.dart"
if [[ -n "$FLUTTER_TARGET" ]]; then
target_path="${FLUTTER_TARGET}"
fi
# Set the track widget creation flag.
track_widget_creation_flag=""
if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
track_widget_creation_flag="--track-widget-creation"
fi
# Set the build mode
build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
${verbose_flag} \
build bundle \
--target-platform=darwin-x64 \
--target="${target_path}" \
--${build_mode} \
${track_widget_creation_flag}
......@@ -18,6 +18,7 @@ enum Artifact {
flutterTester,
snapshotDart,
flutterFramework,
flutterMacOSFramework,
vmSnapshotData,
isolateSnapshotData,
platformKernelDill,
......@@ -44,6 +45,8 @@ String _artifactToFileName(Artifact artifact, [ TargetPlatform platform, BuildMo
return 'snapshot.dart';
case Artifact.flutterFramework:
return 'Flutter.framework';
case Artifact.flutterMacOSFramework:
return 'FlutterMacOS.framework';
case Artifact.vmSnapshotData:
// Flutter 'debug' and 'dynamic profile' modes for all target platforms use Dart
// RELEASE VM snapshot that comes from host debug build and has the metadata
......@@ -211,6 +214,10 @@ class CachedArtifacts extends Artifacts {
return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
case Artifact.kernelWorkerSnapshot:
return fs.path.join(dartSdkPath, 'bin', 'snapshots', _artifactToFileName(artifact));
case Artifact.flutterMacOSFramework:
final String engineArtifactsPath = cache.getArtifactDirectory('engine').path;
final String platformDirName = getNameForTargetPlatform(platform);
return fs.path.join(engineArtifactsPath, platformDirName, _artifactToFileName(artifact, platform, mode));
default:
assert(false, 'Artifact $artifact not available for platform $platform.');
return null;
......@@ -279,6 +286,8 @@ class LocalEngineArtifacts extends Artifacts {
return fs.path.join(_getFlutterPatchedSdkPath(mode), 'lib', _artifactToFileName(artifact));
case Artifact.flutterFramework:
return fs.path.join(engineOutPath, _artifactToFileName(artifact));
case Artifact.flutterMacOSFramework:
return fs.path.join(engineOutPath, _artifactToFileName(artifact));
case Artifact.flutterPatchedSdkPath:
// When using local engine always use [BuildMode.debug] regardless of
// what was specified in [mode] argument because local engine will
......
......@@ -382,6 +382,11 @@ String getIosBuildDirectory() {
return fs.path.join(getBuildDirectory(), 'ios');
}
/// Returns the macOS build output directory.
String getMacOSBuildDirectory() {
return fs.path.join(getBuildDirectory(), 'macos');
}
/// Returns the web build output directory.
String getWebBuildDirectory() {
return fs.path.join(getBuildDirectory(), 'web');
......
......@@ -8,6 +8,7 @@ import '../base/common.dart';
import '../build_info.dart';
import '../bundle.dart';
import '../runner/flutter_command.dart' show FlutterOptions, FlutterCommandResult;
import '../version.dart';
import 'build.dart';
class BuildBundleCommand extends BuildSubCommand {
......@@ -27,7 +28,16 @@ class BuildBundleCommand extends BuildSubCommand {
..addOption('depfile', defaultsTo: defaultDepfilePath)
..addOption('target-platform',
defaultsTo: 'android-arm',
allowed: <String>['android-arm', 'android-arm64', 'android-x86', 'android-x64', 'ios'],
allowed: const <String>[
'android-arm',
'android-arm64',
'android-x86',
'android-x64',
'ios',
'darwin-x64',
'linux-x64',
'windows-x64',
],
)
..addFlag('track-widget-creation',
hide: !verboseHelp,
......@@ -64,8 +74,21 @@ class BuildBundleCommand extends BuildSubCommand {
Future<FlutterCommandResult> runCommand() async {
final String targetPlatform = argResults['target-platform'];
final TargetPlatform platform = getTargetPlatformForName(targetPlatform);
if (platform == null)
if (platform == null) {
throwToolExit('Unknown platform: $targetPlatform');
}
// Check for target platforms that are only allowed on unstable Flutter.
switch (platform) {
case TargetPlatform.darwin_x64:
case TargetPlatform.windows_x64:
case TargetPlatform.linux_x64:
if (FlutterVersion.instance.isStable) {
throwToolExit('$targetPlatform is not supported on stable Flutter.');
}
break;
default:
break;
}
final BuildMode buildMode = getBuildMode();
......
......@@ -27,14 +27,23 @@ String flutterFrameworkDir(BuildMode mode) {
Artifact.flutterFramework, platform: TargetPlatform.ios, mode: mode)));
}
String flutterMacOSFrameworkDir(BuildMode mode) {
return fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(
Artifact.flutterMacOSFramework, platform: TargetPlatform.darwin_x64, mode: mode)));
}
/// Writes or rewrites Xcode property files with the specified information.
///
/// useMacOSConfig: Optional parameter that controls whether we use the macOS
/// project file instead. Defaults to false.
///
/// targetOverride: Optional parameter, if null or unspecified the default value
/// from xcode_backend.sh is used 'lib/main.dart'.
Future<void> updateGeneratedXcodeProperties({
@required FlutterProject project,
@required BuildInfo buildInfo,
String targetOverride,
bool useMacOSConfig = false,
}) async {
final StringBuffer localsBuffer = StringBuffer();
......@@ -53,14 +62,20 @@ Future<void> updateGeneratedXcodeProperties({
// The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
localsBuffer.writeln('FLUTTER_BUILD_DIR=${getBuildDirectory()}');
localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
final String buildDirectory = useMacOSConfig
? getMacOSBuildDirectory()
: getIosBuildDirectory();
localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../$buildDirectory');
if (!project.isModule) {
// For module projects we do not want to write the FLUTTER_FRAMEWORK_DIR
// explicitly. Rather we rely on the xcode backend script and the Podfile
// logic to derive it from FLUTTER_ROOT and FLUTTER_BUILD_MODE.
// However, this is necessary for regular projects using Cocoapods.
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}');
final String frameworkDir = useMacOSConfig
? flutterMacOSFrameworkDir(buildInfo.mode)
: flutterFrameworkDir(buildInfo.mode);
localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=$frameworkDir');
}
final String buildName = validatedBuildNameForPlatform(TargetPlatform.ios, buildInfo?.buildName ?? project.manifest.buildName);
......@@ -85,15 +100,21 @@ Future<void> updateGeneratedXcodeProperties({
// NOTE: this assumes that local engine binary paths are consistent with
// the conventions uses in the engine: 32-bit iOS engines are built to
// paths ending in _arm, 64-bit builds are not.
final String arch = engineOutPath.endsWith('_arm') ? 'armv7' : 'arm64';
localsBuffer.writeln('ARCHS=$arch');
//
// Skip this step for macOS builds.
if (!useMacOSConfig) {
final String arch = engineOutPath.endsWith('_arm') ? 'armv7' : 'arm64';
localsBuffer.writeln('ARCHS=$arch');
}
}
if (buildInfo.trackWidgetCreation) {
localsBuffer.writeln('TRACK_WIDGET_CREATION=true');
}
final File generatedXcodePropertiesFile = project.ios.generatedXcodePropertiesFile;
final File generatedXcodePropertiesFile = useMacOSConfig
? project.macos.generatedXcodePropertiesFile
: project.ios.generatedXcodePropertiesFile;
generatedXcodePropertiesFile.createSync(recursive: true);
generatedXcodePropertiesFile.writeAsStringSync(localsBuffer.toString());
}
......
......@@ -3,22 +3,46 @@
// found in the LICENSE file.
import '../base/common.dart';
import '../base/file_system.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 '../ios/xcodeproj.dart';
import '../project.dart';
/// Builds the macOS project through the project shell script.
/// Builds the macOS project through xcode build.
// TODO(jonahwilliams): support target option.
// TODO(jonahwilliams): refactor to share code with the existing iOS code.
Future<void> buildMacOS(FlutterProject flutterProject, BuildInfo buildInfo) async {
// Write configuration to an xconfig file in a standard location.
await updateGeneratedXcodeProperties(
project: flutterProject,
buildInfo: buildInfo,
useMacOSConfig: true,
);
// Set debug or release mode.
String config = 'Debug';
if (buildInfo.isRelease) {
config = 'Release';
}
final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
if (!flutterBuildDir.existsSync()) {
flutterBuildDir.createSync(recursive: true);
}
// Run build script provided by application.
final Process process = await processManager.start(<String>[
flutterProject.macos.buildScript.path,
Cache.flutterRoot,
buildInfo?.isDebug == true ? 'debug' : 'release',
buildInfo?.trackWidgetCreation == true ? 'track-widget-creation' : 'no-track-widget-creation',
'/usr/bin/env',
'xcrun',
'xcodebuild',
'-project', flutterProject.macos.xcodeProjectFile.path,
'-configuration', '$config',
'-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')}',
], runInShell: true);
final Status status = logger.startProgress(
'Building macOS application...',
......
......@@ -536,8 +536,14 @@ class MacOSProject {
bool existsSync() => project.directory.childDirectory('macos').existsSync();
// Note: The build script file exists as a temporary shim.
File get buildScript => project.directory.childDirectory('macos').childFile('build.sh');
Directory get _editableDirectory => project.directory.childDirectory('macos');
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
/// the Xcode build.
File get generatedXcodePropertiesFile => _editableDirectory.childDirectory('Flutter').childFile('Generated.xcconfig');
/// The Xcode project file.
Directory get xcodeProjectFile => _editableDirectory.childDirectory('Runner.xcodeproj');
// Note: The name script file exists as a temporary shim.
File get nameScript => project.directory.childDirectory('macos').childFile('name_output.sh');
......
......@@ -7,8 +7,10 @@ import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
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/project.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
......@@ -49,7 +51,6 @@ void main() {
testUsingContext('macOS build fails on non-macOS platform', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file('macos/build.sh').createSync(recursive: true);
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
......@@ -64,14 +65,22 @@ void main() {
testUsingContext('macOS build invokes build script', () async {
final BuildCommand command = BuildCommand();
applyMocksToCommand(command);
fs.file('macos/build.sh').createSync(recursive: true);
fs.directory('macos').createSync();
fs.file('pubspec.yaml').createSync();
fs.file('.packages').createSync();
final FlutterProject flutterProject = await FlutterProject.fromDirectory(fs.currentDirectory);
final Directory flutterBuildDir = fs.directory(getMacOSBuildDirectory());
when(mockProcessManager.start(<String>[
'/macos/build.sh',
'/',
'release',
'no-track-widget-creation',
'/usr/bin/env',
'xcrun',
'xcodebuild',
'-project', flutterProject.macos.xcodeProjectFile.path,
'-configuration', 'Debug',
'-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')}',
], runInShell: true)).thenAnswer((Invocation invocation) async {
return mockProcess;
});
......
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