[flutter_tools] Port xcode backend to dart (#86753)
This commit is contained in:
parent
93de096e64
commit
2d07436dbd
466
packages/flutter_tools/bin/xcode_backend.dart
Normal file
466
packages/flutter_tools/bin/xcode_backend.dart
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
// Copyright 2014 The Flutter 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 'dart:io';
|
||||||
|
|
||||||
|
void main(List<String> arguments) {
|
||||||
|
File? scriptOutputStreamFile;
|
||||||
|
final String? scriptOutputStreamFileEnv = Platform.environment['SCRIPT_OUTPUT_STREAM_FILE'];
|
||||||
|
if (scriptOutputStreamFileEnv != null && scriptOutputStreamFileEnv.isNotEmpty) {
|
||||||
|
scriptOutputStreamFile = File(scriptOutputStreamFileEnv);
|
||||||
|
}
|
||||||
|
Context(
|
||||||
|
arguments: arguments,
|
||||||
|
environment: Platform.environment,
|
||||||
|
scriptOutputStreamFile: scriptOutputStreamFile,
|
||||||
|
).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Container for script arguments and environment variables.
|
||||||
|
///
|
||||||
|
/// All interactions with the platform are broken into individual methods that
|
||||||
|
/// can be overridden in tests.
|
||||||
|
class Context {
|
||||||
|
Context({
|
||||||
|
required this.arguments,
|
||||||
|
required this.environment,
|
||||||
|
File? scriptOutputStreamFile,
|
||||||
|
}) {
|
||||||
|
if (scriptOutputStreamFile != null) {
|
||||||
|
scriptOutputStream = scriptOutputStreamFile.openSync(mode: FileMode.write);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, String> environment;
|
||||||
|
final List<String> arguments;
|
||||||
|
RandomAccessFile? scriptOutputStream;
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
if (arguments.isEmpty) {
|
||||||
|
// Named entry points were introduced in Flutter v0.0.7.
|
||||||
|
stderr.write(
|
||||||
|
'error: Your Xcode project is incompatible with this version of Flutter. '
|
||||||
|
'Run "rm -rf ios/Runner.xcodeproj" and "flutter create ." to regenerate.\n');
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String subCommand = arguments.first;
|
||||||
|
switch (subCommand) {
|
||||||
|
case 'build':
|
||||||
|
buildApp();
|
||||||
|
break;
|
||||||
|
case 'thin':
|
||||||
|
// No-op, thinning is handled during the bundle asset assemble build target.
|
||||||
|
break;
|
||||||
|
case 'embed':
|
||||||
|
embedFlutterFrameworks();
|
||||||
|
break;
|
||||||
|
case 'embed_and_thin':
|
||||||
|
// Thinning is handled during the bundle asset assemble build target, so just embed.
|
||||||
|
embedFlutterFrameworks();
|
||||||
|
break;
|
||||||
|
case 'test_observatory_bonjour_service':
|
||||||
|
// Exposed for integration testing only.
|
||||||
|
addObservatoryBonjourService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool existsDir(String path) {
|
||||||
|
final Directory dir = Directory(path);
|
||||||
|
return dir.existsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool existsFile(String path) {
|
||||||
|
final File file = File(path);
|
||||||
|
return file.existsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run given command in a synchronous subprocess.
|
||||||
|
///
|
||||||
|
/// Will throw [Exception] if the exit code is not 0.
|
||||||
|
ProcessResult runSync(
|
||||||
|
String bin,
|
||||||
|
List<String> args, {
|
||||||
|
bool verbose = false,
|
||||||
|
bool allowFail = false,
|
||||||
|
String? workingDirectory,
|
||||||
|
}) {
|
||||||
|
if (verbose) {
|
||||||
|
print('♦ $bin ${args.join(' ')}');
|
||||||
|
}
|
||||||
|
final ProcessResult result = Process.runSync(
|
||||||
|
bin,
|
||||||
|
args,
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
);
|
||||||
|
if (verbose) {
|
||||||
|
print((result.stdout as String).trim());
|
||||||
|
}
|
||||||
|
if ((result.stderr as String).isNotEmpty) {
|
||||||
|
echoError((result.stderr as String).trim());
|
||||||
|
}
|
||||||
|
if (!allowFail && result.exitCode != 0) {
|
||||||
|
stderr.write('${result.stderr}\n');
|
||||||
|
throw Exception(
|
||||||
|
'Command "$bin ${args.join(' ')}" exited with code ${result.exitCode}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log message to stderr.
|
||||||
|
void echoError(String message) {
|
||||||
|
stderr.writeln(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log message to stdout.
|
||||||
|
void echo(String message) {
|
||||||
|
stdout.write(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit the application with the given exit code.
|
||||||
|
///
|
||||||
|
/// Exists to allow overriding in tests.
|
||||||
|
Never exitApp(int code) {
|
||||||
|
exit(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return value from environment if it exists, else throw [Exception].
|
||||||
|
String environmentEnsure(String key) {
|
||||||
|
final String? value = environment[key];
|
||||||
|
if (value == null) {
|
||||||
|
throw Exception(
|
||||||
|
'Expected the environment variable "$key" to exist, but it was not found',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When provided with a pipe by the host Flutter build process, output to the
|
||||||
|
// pipe goes to stdout of the Flutter build process directly.
|
||||||
|
void streamOutput(String output) {
|
||||||
|
scriptOutputStream?.writeStringSync('$output\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
String parseFlutterBuildMode() {
|
||||||
|
// Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
|
||||||
|
// This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
|
||||||
|
// they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
|
||||||
|
final String? buildMode = (environment['FLUTTER_BUILD_MODE'] ?? environment['CONFIGURATION'])?.toLowerCase();
|
||||||
|
|
||||||
|
if (buildMode != null) {
|
||||||
|
if (buildMode.contains('release')) {
|
||||||
|
return 'release';
|
||||||
|
}
|
||||||
|
if (buildMode.contains('profile')) {
|
||||||
|
return 'profile';
|
||||||
|
}
|
||||||
|
if (buildMode.contains('debug')) {
|
||||||
|
return 'debug';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echoError('========================================================================');
|
||||||
|
echoError('ERROR: Unknown FLUTTER_BUILD_MODE: $buildMode.');
|
||||||
|
echoError("Valid values are 'Debug', 'Profile', or 'Release' (case insensitive).");
|
||||||
|
echoError('This is controlled by the FLUTTER_BUILD_MODE environment variable.');
|
||||||
|
echoError('If that is not set, the CONFIGURATION environment variable is used.');
|
||||||
|
echoError('');
|
||||||
|
echoError('You can fix this by either adding an appropriately named build');
|
||||||
|
echoError('configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the');
|
||||||
|
echoError('.xcconfig file for the current build configuration (${environment['CONFIGURATION']}).');
|
||||||
|
echoError('========================================================================');
|
||||||
|
exitApp(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the App.framework as an embedded binary and the flutter_assets as
|
||||||
|
// resources.
|
||||||
|
void embedFlutterFrameworks() {
|
||||||
|
// Embed App.framework from Flutter into the app (after creating the Frameworks directory
|
||||||
|
// if it doesn't already exist).
|
||||||
|
final String xcodeFrameworksDir = '${environment['TARGET_BUILD_DIR']}/${environment['FRAMEWORKS_FOLDER_PATH']}';
|
||||||
|
runSync(
|
||||||
|
'mkdir',
|
||||||
|
<String>[
|
||||||
|
'-p',
|
||||||
|
'--',
|
||||||
|
xcodeFrameworksDir,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
runSync(
|
||||||
|
'rsync',
|
||||||
|
<String>[
|
||||||
|
'-av',
|
||||||
|
'--delete',
|
||||||
|
'--filter',
|
||||||
|
'- .DS_Store',
|
||||||
|
'${environment['BUILT_PRODUCTS_DIR']}/App.framework',
|
||||||
|
xcodeFrameworksDir,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Embed the actual Flutter.framework that the Flutter app expects to run against,
|
||||||
|
// which could be a local build or an arch/type specific build.
|
||||||
|
runSync(
|
||||||
|
'rsync',
|
||||||
|
<String>[
|
||||||
|
'-av',
|
||||||
|
'--delete',
|
||||||
|
'--filter',
|
||||||
|
'- .DS_Store',
|
||||||
|
'${environment['BUILT_PRODUCTS_DIR']}/Flutter.framework',
|
||||||
|
'$xcodeFrameworksDir/',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
addObservatoryBonjourService();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the observatory publisher Bonjour service to the produced app bundle Info.plist.
|
||||||
|
void addObservatoryBonjourService() {
|
||||||
|
final String buildMode = parseFlutterBuildMode();
|
||||||
|
|
||||||
|
// Debug and profile only.
|
||||||
|
if (buildMode == 'release') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String builtProductsPlist = '${environment['BUILT_PRODUCTS_DIR'] ?? ''}/${environment['INFOPLIST_PATH'] ?? ''}';
|
||||||
|
|
||||||
|
if (!existsFile(builtProductsPlist)) {
|
||||||
|
// Very occasionally Xcode hasn't created an Info.plist when this runs.
|
||||||
|
// The file will be present on re-run.
|
||||||
|
echo(
|
||||||
|
'${environment['INFOPLIST_PATH'] ?? ''} does not exist. Skipping '
|
||||||
|
'_dartobservatory._tcp NSBonjourServices insertion. Try re-building to '
|
||||||
|
'enable "flutter attach".');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are already NSBonjourServices specified by the app (uncommon),
|
||||||
|
// insert the observatory service name to the existing list.
|
||||||
|
ProcessResult result = runSync(
|
||||||
|
'plutil',
|
||||||
|
<String>[
|
||||||
|
'-extract',
|
||||||
|
'NSBonjourServices',
|
||||||
|
'xml1',
|
||||||
|
'-o',
|
||||||
|
'-',
|
||||||
|
builtProductsPlist,
|
||||||
|
],
|
||||||
|
allowFail: true,
|
||||||
|
);
|
||||||
|
if (result.exitCode == 0) {
|
||||||
|
runSync(
|
||||||
|
'plutil',
|
||||||
|
<String>[
|
||||||
|
'-insert',
|
||||||
|
'NSBonjourServices.0',
|
||||||
|
'-string',
|
||||||
|
'_dartobservatory._tcp',
|
||||||
|
builtProductsPlist
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Otherwise, add the NSBonjourServices key and observatory service name.
|
||||||
|
runSync(
|
||||||
|
'plutil',
|
||||||
|
<String>[
|
||||||
|
'-insert',
|
||||||
|
'NSBonjourServices',
|
||||||
|
'-json',
|
||||||
|
'["_dartobservatory._tcp"]',
|
||||||
|
builtProductsPlist,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
//fi
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't override the local network description the Flutter app developer
|
||||||
|
// specified (uncommon). This text will appear below the "Your app would
|
||||||
|
// like to find and connect to devices on your local network" permissions
|
||||||
|
// popup.
|
||||||
|
result = runSync(
|
||||||
|
'plutil',
|
||||||
|
<String>[
|
||||||
|
'-extract',
|
||||||
|
'NSLocalNetworkUsageDescription',
|
||||||
|
'xml1',
|
||||||
|
'-o',
|
||||||
|
'-',
|
||||||
|
builtProductsPlist,
|
||||||
|
],
|
||||||
|
allowFail: true,
|
||||||
|
);
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
runSync(
|
||||||
|
'plutil',
|
||||||
|
<String>[
|
||||||
|
'-insert',
|
||||||
|
'NSLocalNetworkUsageDescription',
|
||||||
|
'-string',
|
||||||
|
'Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds.',
|
||||||
|
builtProductsPlist,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildApp() {
|
||||||
|
final bool verbose = environment['VERBOSE_SCRIPT_LOGGING'] != null && environment['VERBOSE_SCRIPT_LOGGING'] != '';
|
||||||
|
final String sourceRoot = environment['SOURCE_ROOT'] ?? '';
|
||||||
|
String projectPath = '$sourceRoot/..';
|
||||||
|
if (environment['FLUTTER_APPLICATION_PATH'] != null) {
|
||||||
|
projectPath = environment['FLUTTER_APPLICATION_PATH']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
String targetPath = 'lib/main.dart';
|
||||||
|
if (environment['FLUTTER_TARGET'] != null) {
|
||||||
|
targetPath = environment['FLUTTER_TARGET']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
String derivedDir = '$sourceRoot/Flutter}';
|
||||||
|
if (existsDir('$projectPath/.ios')) {
|
||||||
|
derivedDir = '$projectPath/.ios/Flutter';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
|
||||||
|
// This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
|
||||||
|
// they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
|
||||||
|
|
||||||
|
final String buildMode = parseFlutterBuildMode();
|
||||||
|
String artifactVariant = 'unknown';
|
||||||
|
switch (buildMode) {
|
||||||
|
case 'release':
|
||||||
|
artifactVariant = 'ios-release';
|
||||||
|
break;
|
||||||
|
case 'profile':
|
||||||
|
artifactVariant = 'ios-profile';
|
||||||
|
break;
|
||||||
|
case 'debug':
|
||||||
|
artifactVariant = 'ios';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn the user if not archiving (ACTION=install) in release mode.
|
||||||
|
final String? action = environment['ACTION'];
|
||||||
|
if (action == 'install' && buildMode != 'release') {
|
||||||
|
echo(
|
||||||
|
'warning: Flutter archive not built in Release mode. Ensure '
|
||||||
|
'FLUTTER_BUILD_MODE is set to release or run "flutter build ios '
|
||||||
|
'--release", then re-run Archive from Xcode.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final String frameworkPath = '${environmentEnsure('FLUTTER_ROOT')}/bin/cache/artifacts/engine/$artifactVariant';
|
||||||
|
|
||||||
|
String flutterFramework = '$frameworkPath/Flutter.xcframework';
|
||||||
|
|
||||||
|
final String? localEngine = environment['LOCAL_ENGINE'];
|
||||||
|
if (localEngine != null) {
|
||||||
|
if (!localEngine.toLowerCase().contains(buildMode)) {
|
||||||
|
echoError('========================================================================');
|
||||||
|
echoError("ERROR: Requested build with Flutter local engine at '$localEngine'");
|
||||||
|
echoError("This engine is not compatible with FLUTTER_BUILD_MODE: '$buildMode'.");
|
||||||
|
echoError('You can fix this by updating the LOCAL_ENGINE environment variable, or');
|
||||||
|
echoError('by running:');
|
||||||
|
echoError(' flutter build ios --local-engine=ios_$buildMode');
|
||||||
|
echoError('or');
|
||||||
|
echoError(' flutter build ios --local-engine=ios_${buildMode}_unopt');
|
||||||
|
echoError('========================================================================');
|
||||||
|
exitApp(-1);
|
||||||
|
}
|
||||||
|
flutterFramework = '${environmentEnsure('FLUTTER_ENGINE')}/out/$localEngine/Flutter.xcframework';
|
||||||
|
}
|
||||||
|
String bitcodeFlag = '';
|
||||||
|
if (environment['ENABLE_BITCODE'] == 'YES' && environment['ACTION'] == 'install') {
|
||||||
|
bitcodeFlag = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jmagman): use assemble copied engine in add-to-app.
|
||||||
|
if (existsDir('$projectPath/.ios')) {
|
||||||
|
runSync(
|
||||||
|
'rsync',
|
||||||
|
<String>[
|
||||||
|
'-av',
|
||||||
|
'--delete',
|
||||||
|
'--filter',
|
||||||
|
'- .DS_Store',
|
||||||
|
flutterFramework,
|
||||||
|
'$derivedDir/engine',
|
||||||
|
],
|
||||||
|
verbose: verbose,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> flutterArgs = <String>[];
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
flutterArgs.add('--verbose');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment['FLUTTER_ENGINE'] != null && environment['FLUTTER_ENGINE']!.isNotEmpty) {
|
||||||
|
flutterArgs.add('--local-engine-src-path=${environment['FLUTTER_ENGINE']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment['LOCAL_ENGINE'] != null && environment['LOCAL_ENGINE']!.isNotEmpty) {
|
||||||
|
flutterArgs.add('--local-engine=${environment['LOCAL_ENGINE']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
flutterArgs.addAll(<String>[
|
||||||
|
'assemble',
|
||||||
|
'--no-version-check',
|
||||||
|
'--output=${environment['BUILT_PRODUCTS_DIR'] ?? ''}/',
|
||||||
|
'-dTargetPlatform=ios',
|
||||||
|
'-dTargetFile=$targetPath',
|
||||||
|
'-dBuildMode=$buildMode',
|
||||||
|
'-dIosArchs=${environment['ARCHS'] ?? ''}',
|
||||||
|
'-dSdkRoot=${environment['SDKROOT'] ?? ''}',
|
||||||
|
'-dSplitDebugInfo=${environment['SPLIT_DEBUG_INFO'] ?? ''}',
|
||||||
|
'-dTreeShakeIcons=${environment['TREE_SHAKE_ICONS'] ?? ''}',
|
||||||
|
'-dTrackWidgetCreation=${environment['TRACK_WIDGET_CREATION'] ?? ''}',
|
||||||
|
'-dDartObfuscation=${environment['DART_OBFUSCATION'] ?? ''}',
|
||||||
|
'-dEnableBitcode=$bitcodeFlag',
|
||||||
|
'--ExtraGenSnapshotOptions=${environment['EXTRA_GEN_SNAPSHOT_OPTIONS'] ?? ''}',
|
||||||
|
'--DartDefines=${environment['DART_DEFINES'] ?? ''}',
|
||||||
|
'--ExtraFrontEndOptions=${environment['EXTRA_FRONT_END_OPTIONS'] ?? ''}',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (environment['PERFORMANCE_MEASUREMENT_FILE'] != null && environment['PERFORMANCE_MEASUREMENT_FILE']!.isNotEmpty) {
|
||||||
|
flutterArgs.add('--performance-measurement-file=${environment['PERFORMANCE_MEASUREMENT_FILE']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? expandedCodeSignIdentity = environment['EXPANDED_CODE_SIGN_IDENTITY'];
|
||||||
|
if (expandedCodeSignIdentity != null && expandedCodeSignIdentity.isNotEmpty && environment['CODE_SIGNING_REQUIRED'] != 'NO') {
|
||||||
|
flutterArgs.add('-dCodesignIdentity=$expandedCodeSignIdentity');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment['BUNDLE_SKSL_PATH'] != null && environment['BUNDLE_SKSL_PATH']!.isNotEmpty) {
|
||||||
|
flutterArgs.add('-dBundleSkSLPath=${environment['BUNDLE_SKSL_PATH']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment['CODE_SIZE_DIRECTORY'] != null && environment['CODE_SIZE_DIRECTORY']!.isNotEmpty) {
|
||||||
|
flutterArgs.add('-dCodeSizeDirectory=${environment['CODE_SIZE_DIRECTORY']}');
|
||||||
|
}
|
||||||
|
|
||||||
|
flutterArgs.add('${buildMode}_ios_bundle_flutter_assets');
|
||||||
|
|
||||||
|
final ProcessResult result = runSync(
|
||||||
|
'${environmentEnsure('FLUTTER_ROOT')}/bin/flutter',
|
||||||
|
flutterArgs,
|
||||||
|
verbose: verbose,
|
||||||
|
allowFail: true,
|
||||||
|
workingDirectory: projectPath, // equivalent of RunCommand pushd "${project_path}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.exitCode != 0) {
|
||||||
|
echoError('Failed to package $projectPath.');
|
||||||
|
exitApp(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamOutput('done');
|
||||||
|
streamOutput(' └─Compiling, linking and signing...');
|
||||||
|
|
||||||
|
echo('Project $projectPath built and packaged successfully.');
|
||||||
|
}
|
||||||
|
}
|
@ -3,266 +3,27 @@
|
|||||||
# Use of this source code is governed by a BSD-style license that can be
|
# Use of this source code is governed by a BSD-style license that can be
|
||||||
# found in the LICENSE file.
|
# found in the LICENSE file.
|
||||||
|
|
||||||
# Exit on error
|
# exit on error, or usage of unset var
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
RunCommand() {
|
# Needed because if it is set, cd may print the path it changed to.
|
||||||
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
|
unset CDPATH
|
||||||
echo "♦ $*"
|
|
||||||
fi
|
|
||||||
"$@"
|
|
||||||
return $?
|
|
||||||
}
|
|
||||||
|
|
||||||
# When provided with a pipe by the host Flutter build process, output to the
|
function follow_links() (
|
||||||
# pipe goes to stdout of the Flutter build process directly.
|
cd -P "$(dirname -- "$1")"
|
||||||
StreamOutput() {
|
file="$PWD/$(basename -- "$1")"
|
||||||
if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
|
while [[ -h "$file" ]]; do
|
||||||
echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
|
cd -P "$(dirname -- "$file")"
|
||||||
fi
|
file="$(readlink -- "$file")"
|
||||||
}
|
cd -P "$(dirname -- "$file")"
|
||||||
|
file="$PWD/$(basename -- "$file")"
|
||||||
|
done
|
||||||
|
echo "$file"
|
||||||
|
)
|
||||||
|
|
||||||
EchoError() {
|
PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")"
|
||||||
echo "$@" 1>&2
|
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
|
||||||
}
|
FLUTTER_ROOT="$BIN_DIR/../../.."
|
||||||
|
DART="$FLUTTER_ROOT/bin/dart"
|
||||||
|
|
||||||
AssertExists() {
|
"$DART" "$BIN_DIR/xcode_backend.dart" "$@"
|
||||||
if [[ ! -e "$1" ]]; then
|
|
||||||
if [[ -h "$1" ]]; then
|
|
||||||
EchoError "The path $1 is a symlink to a path that does not exist"
|
|
||||||
else
|
|
||||||
EchoError "The path $1 does not exist"
|
|
||||||
fi
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ParseFlutterBuildMode() {
|
|
||||||
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
|
|
||||||
# This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
|
|
||||||
# they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
|
|
||||||
local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
|
|
||||||
|
|
||||||
case "$build_mode" in
|
|
||||||
*release*) build_mode="release";;
|
|
||||||
*profile*) build_mode="profile";;
|
|
||||||
*debug*) build_mode="debug";;
|
|
||||||
*)
|
|
||||||
EchoError "========================================================================"
|
|
||||||
EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: ${build_mode}."
|
|
||||||
EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
|
|
||||||
EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
|
|
||||||
EchoError "If that is not set, the CONFIGURATION environment variable is used."
|
|
||||||
EchoError ""
|
|
||||||
EchoError "You can fix this by either adding an appropriately named build"
|
|
||||||
EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
|
|
||||||
EchoError ".xcconfig file for the current build configuration (${CONFIGURATION})."
|
|
||||||
EchoError "========================================================================"
|
|
||||||
exit -1;;
|
|
||||||
esac
|
|
||||||
echo "${build_mode}"
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildApp() {
|
|
||||||
local project_path="${SOURCE_ROOT}/.."
|
|
||||||
if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
|
|
||||||
project_path="${FLUTTER_APPLICATION_PATH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local target_path="lib/main.dart"
|
|
||||||
if [[ -n "$FLUTTER_TARGET" ]]; then
|
|
||||||
target_path="${FLUTTER_TARGET}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local derived_dir="${SOURCE_ROOT}/Flutter"
|
|
||||||
if [[ -e "${project_path}/.ios" ]]; then
|
|
||||||
derived_dir="${project_path}/.ios/Flutter"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Default value of assets_path is flutter_assets
|
|
||||||
local assets_path="flutter_assets"
|
|
||||||
# The value of assets_path can set by add FLTAssetsPath to
|
|
||||||
# AppFrameworkInfo.plist.
|
|
||||||
if FLTAssetsPath=$(/usr/libexec/PlistBuddy -c "Print :FLTAssetsPath" "${derived_dir}/AppFrameworkInfo.plist" 2>/dev/null); then
|
|
||||||
if [[ -n "$FLTAssetsPath" ]]; then
|
|
||||||
assets_path="${FLTAssetsPath}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
|
|
||||||
# This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
|
|
||||||
# they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
|
|
||||||
local build_mode="$(ParseFlutterBuildMode)"
|
|
||||||
local artifact_variant="unknown"
|
|
||||||
case "$build_mode" in
|
|
||||||
release ) artifact_variant="ios-release";;
|
|
||||||
profile ) artifact_variant="ios-profile";;
|
|
||||||
debug ) artifact_variant="ios";;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Warn the user if not archiving (ACTION=install) in release mode.
|
|
||||||
if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
|
|
||||||
echo "warning: Flutter archive not built in Release mode. Ensure FLUTTER_BUILD_MODE \
|
|
||||||
is set to release or run \"flutter build ios --release\", then re-run Archive from Xcode."
|
|
||||||
fi
|
|
||||||
|
|
||||||
local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
|
|
||||||
local flutter_framework="${framework_path}/Flutter.xcframework"
|
|
||||||
|
|
||||||
if [[ -n "$LOCAL_ENGINE" ]]; then
|
|
||||||
if [[ $(echo "$LOCAL_ENGINE" | tr "[:upper:]" "[:lower:]") != *"$build_mode"* ]]; then
|
|
||||||
EchoError "========================================================================"
|
|
||||||
EchoError "ERROR: Requested build with Flutter local engine at '${LOCAL_ENGINE}'"
|
|
||||||
EchoError "This engine is not compatible with FLUTTER_BUILD_MODE: '${build_mode}'."
|
|
||||||
EchoError "You can fix this by updating the LOCAL_ENGINE environment variable, or"
|
|
||||||
EchoError "by running:"
|
|
||||||
EchoError " flutter build ios --local-engine=ios_${build_mode}"
|
|
||||||
EchoError "or"
|
|
||||||
EchoError " flutter build ios --local-engine=ios_${build_mode}_unopt"
|
|
||||||
EchoError "========================================================================"
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.xcframework"
|
|
||||||
fi
|
|
||||||
local bitcode_flag=""
|
|
||||||
if [[ "$ENABLE_BITCODE" == "YES" && "$ACTION" == "install" ]]; then
|
|
||||||
bitcode_flag="true"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# TODO(jmagman): use assemble copied engine in add-to-app.
|
|
||||||
if [[ -e "${project_path}/.ios" ]]; then
|
|
||||||
RunCommand rsync -av --delete --filter "- .DS_Store" "${flutter_framework}" "${derived_dir}/engine"
|
|
||||||
fi
|
|
||||||
|
|
||||||
RunCommand pushd "${project_path}" > /dev/null
|
|
||||||
|
|
||||||
# Construct the "flutter assemble" argument array. Arguments should be added
|
|
||||||
# as quoted string elements of the flutter_args array, otherwise an argument
|
|
||||||
# (like a path) with spaces in it might be interpreted as two separate
|
|
||||||
# arguments.
|
|
||||||
local flutter_args=("${FLUTTER_ROOT}/bin/flutter")
|
|
||||||
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
|
|
||||||
flutter_args+=('--verbose')
|
|
||||||
fi
|
|
||||||
if [[ -n "$FLUTTER_ENGINE" ]]; then
|
|
||||||
flutter_args+=("--local-engine-src-path=${FLUTTER_ENGINE}")
|
|
||||||
fi
|
|
||||||
if [[ -n "$LOCAL_ENGINE" ]]; then
|
|
||||||
flutter_args+=("--local-engine=${LOCAL_ENGINE}")
|
|
||||||
fi
|
|
||||||
flutter_args+=(
|
|
||||||
"assemble"
|
|
||||||
"--no-version-check"
|
|
||||||
"--output=${BUILT_PRODUCTS_DIR}/"
|
|
||||||
"-dTargetPlatform=ios"
|
|
||||||
"-dTargetFile=${target_path}"
|
|
||||||
"-dBuildMode=${build_mode}"
|
|
||||||
"-dIosArchs=${ARCHS}"
|
|
||||||
"-dSdkRoot=${SDKROOT}"
|
|
||||||
"-dSplitDebugInfo=${SPLIT_DEBUG_INFO}"
|
|
||||||
"-dTreeShakeIcons=${TREE_SHAKE_ICONS}"
|
|
||||||
"-dTrackWidgetCreation=${TRACK_WIDGET_CREATION}"
|
|
||||||
"-dDartObfuscation=${DART_OBFUSCATION}"
|
|
||||||
"-dEnableBitcode=${bitcode_flag}"
|
|
||||||
"--ExtraGenSnapshotOptions=${EXTRA_GEN_SNAPSHOT_OPTIONS}"
|
|
||||||
"--DartDefines=${DART_DEFINES}"
|
|
||||||
"--ExtraFrontEndOptions=${EXTRA_FRONT_END_OPTIONS}"
|
|
||||||
)
|
|
||||||
if [[ -n "$PERFORMANCE_MEASUREMENT_FILE" ]]; then
|
|
||||||
flutter_args+=("--performance-measurement-file=${PERFORMANCE_MEASUREMENT_FILE}")
|
|
||||||
fi
|
|
||||||
if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" && "${CODE_SIGNING_REQUIRED:-}" != "NO" ]]; then
|
|
||||||
flutter_args+=("-dCodesignIdentity=${EXPANDED_CODE_SIGN_IDENTITY}")
|
|
||||||
fi
|
|
||||||
if [[ -n "$BUNDLE_SKSL_PATH" ]]; then
|
|
||||||
flutter_args+=("-dBundleSkSLPath=${BUNDLE_SKSL_PATH}")
|
|
||||||
fi
|
|
||||||
if [[ -n "$CODE_SIZE_DIRECTORY" ]]; then
|
|
||||||
flutter_args+=("-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}")
|
|
||||||
fi
|
|
||||||
flutter_args+=("${build_mode}_ios_bundle_flutter_assets")
|
|
||||||
|
|
||||||
RunCommand "${flutter_args[@]}"
|
|
||||||
|
|
||||||
if [[ $? -ne 0 ]]; then
|
|
||||||
EchoError "Failed to package ${project_path}."
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
StreamOutput "done"
|
|
||||||
StreamOutput " └─Compiling, linking and signing..."
|
|
||||||
|
|
||||||
RunCommand popd > /dev/null
|
|
||||||
|
|
||||||
echo "Project ${project_path} built and packaged successfully."
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Adds the App.framework as an embedded binary and the flutter_assets as
|
|
||||||
# resources.
|
|
||||||
EmbedFlutterFrameworks() {
|
|
||||||
# Embed App.framework from Flutter into the app (after creating the Frameworks directory
|
|
||||||
# if it doesn't already exist).
|
|
||||||
local xcode_frameworks_dir="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
|
||||||
RunCommand mkdir -p -- "${xcode_frameworks_dir}"
|
|
||||||
RunCommand rsync -av --delete --filter "- .DS_Store" "${BUILT_PRODUCTS_DIR}/App.framework" "${xcode_frameworks_dir}"
|
|
||||||
|
|
||||||
# Embed the actual Flutter.framework that the Flutter app expects to run against,
|
|
||||||
# which could be a local build or an arch/type specific build.
|
|
||||||
RunCommand rsync -av --delete --filter "- .DS_Store" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/"
|
|
||||||
|
|
||||||
AddObservatoryBonjourService
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add the observatory publisher Bonjour service to the produced app bundle Info.plist.
|
|
||||||
AddObservatoryBonjourService() {
|
|
||||||
local build_mode="$(ParseFlutterBuildMode)"
|
|
||||||
# Debug and profile only.
|
|
||||||
if [[ "${build_mode}" == "release" ]]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
local built_products_plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
|
|
||||||
|
|
||||||
if [[ ! -f "${built_products_plist}" ]]; then
|
|
||||||
# Very occasionally Xcode hasn't created an Info.plist when this runs.
|
|
||||||
# The file will be present on re-run.
|
|
||||||
echo "${INFOPLIST_PATH} does not exist. Skipping _dartobservatory._tcp NSBonjourServices insertion. Try re-building to enable \"flutter attach\"."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
# If there are already NSBonjourServices specified by the app (uncommon), insert the observatory service name to the existing list.
|
|
||||||
if plutil -extract NSBonjourServices xml1 -o - "${built_products_plist}"; then
|
|
||||||
RunCommand plutil -insert NSBonjourServices.0 -string "_dartobservatory._tcp" "${built_products_plist}"
|
|
||||||
else
|
|
||||||
# Otherwise, add the NSBonjourServices key and observatory service name.
|
|
||||||
RunCommand plutil -insert NSBonjourServices -json "[\"_dartobservatory._tcp\"]" "${built_products_plist}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Don't override the local network description the Flutter app developer specified (uncommon).
|
|
||||||
# This text will appear below the "Your app would like to find and connect to devices on your local network" permissions popup.
|
|
||||||
if ! plutil -extract NSLocalNetworkUsageDescription xml1 -o - "${built_products_plist}"; then
|
|
||||||
RunCommand plutil -insert NSLocalNetworkUsageDescription -string "Allow Flutter tools on your computer to connect and debug your application. This prompt will not appear on release builds." "${built_products_plist}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main entry point.
|
|
||||||
if [[ $# == 0 ]]; then
|
|
||||||
# Named entry points were introduced in Flutter v0.0.7.
|
|
||||||
EchoError "error: Your Xcode project is incompatible with this version of Flutter. Run \"rm -rf ios/Runner.xcodeproj\" and \"flutter create .\" to regenerate."
|
|
||||||
exit -1
|
|
||||||
else
|
|
||||||
case $1 in
|
|
||||||
"build")
|
|
||||||
BuildApp ;;
|
|
||||||
"thin")
|
|
||||||
# No-op, thinning is handled during the bundle asset assemble build target.
|
|
||||||
;;
|
|
||||||
"embed")
|
|
||||||
EmbedFlutterFrameworks ;;
|
|
||||||
"embed_and_thin")
|
|
||||||
# Thinning is handled during the bundle asset assemble build target, so just embed.
|
|
||||||
EmbedFlutterFrameworks ;;
|
|
||||||
"test_observatory_bonjour_service")
|
|
||||||
# Exposed for integration testing only.
|
|
||||||
AddObservatoryBonjourService ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
@ -0,0 +1,280 @@
|
|||||||
|
// Copyright 2014 The Flutter 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 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
|
|
||||||
|
import '../../bin/xcode_backend.dart';
|
||||||
|
import '../src/common.dart';
|
||||||
|
import '../src/fake_process_manager.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late MemoryFileSystem fileSystem;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = MemoryFileSystem();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('build', () {
|
||||||
|
test('exits with useful error message when build mode not set', () {
|
||||||
|
final Directory buildDir = fileSystem.directory('/path/to/builds')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
final File pipe = fileSystem.file('/tmp/pipe')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
const String buildMode = 'Debug';
|
||||||
|
final TestContext context = TestContext(
|
||||||
|
<String>['build'],
|
||||||
|
<String, String>{
|
||||||
|
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||||
|
'ENABLE_BITCODE': 'YES',
|
||||||
|
'FLUTTER_ROOT': flutterRoot.path,
|
||||||
|
'INFOPLIST_PATH': 'Info.plist',
|
||||||
|
},
|
||||||
|
commands: <FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
'${flutterRoot.path}/bin/flutter',
|
||||||
|
'assemble',
|
||||||
|
'--no-version-check',
|
||||||
|
'--output=${buildDir.path}/',
|
||||||
|
'-dTargetPlatform=ios',
|
||||||
|
'-dTargetFile=lib/main.dart',
|
||||||
|
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||||
|
'-dIosArchs=',
|
||||||
|
'-dSdkRoot=',
|
||||||
|
'-dSplitDebugInfo=',
|
||||||
|
'-dTreeShakeIcons=',
|
||||||
|
'-dTrackWidgetCreation=',
|
||||||
|
'-dDartObfuscation=',
|
||||||
|
'-dEnableBitcode=',
|
||||||
|
'--ExtraGenSnapshotOptions=',
|
||||||
|
'--DartDefines=',
|
||||||
|
'--ExtraFrontEndOptions=',
|
||||||
|
'debug_ios_bundle_flutter_assets',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
scriptOutputStreamFile: pipe,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
() => context.run(),
|
||||||
|
throwsException,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
context.stderr,
|
||||||
|
contains('ERROR: Unknown FLUTTER_BUILD_MODE: null.\n'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('calls flutter assemble', () {
|
||||||
|
final Directory buildDir = fileSystem.directory('/path/to/builds')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
final File pipe = fileSystem.file('/tmp/pipe')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
const String buildMode = 'Debug';
|
||||||
|
final TestContext context = TestContext(
|
||||||
|
<String>['build'],
|
||||||
|
<String, String>{
|
||||||
|
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||||
|
'CONFIGURATION': buildMode,
|
||||||
|
'ENABLE_BITCODE': 'YES',
|
||||||
|
'FLUTTER_ROOT': flutterRoot.path,
|
||||||
|
'INFOPLIST_PATH': 'Info.plist',
|
||||||
|
},
|
||||||
|
commands: <FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
'${flutterRoot.path}/bin/flutter',
|
||||||
|
'assemble',
|
||||||
|
'--no-version-check',
|
||||||
|
'--output=${buildDir.path}/',
|
||||||
|
'-dTargetPlatform=ios',
|
||||||
|
'-dTargetFile=lib/main.dart',
|
||||||
|
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||||
|
'-dIosArchs=',
|
||||||
|
'-dSdkRoot=',
|
||||||
|
'-dSplitDebugInfo=',
|
||||||
|
'-dTreeShakeIcons=',
|
||||||
|
'-dTrackWidgetCreation=',
|
||||||
|
'-dDartObfuscation=',
|
||||||
|
'-dEnableBitcode=',
|
||||||
|
'--ExtraGenSnapshotOptions=',
|
||||||
|
'--DartDefines=',
|
||||||
|
'--ExtraFrontEndOptions=',
|
||||||
|
'debug_ios_bundle_flutter_assets',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
scriptOutputStreamFile: pipe,
|
||||||
|
)..run();
|
||||||
|
final List<String> streamedLines = pipe.readAsLinesSync();
|
||||||
|
// Ensure after line splitting, the exact string 'done' appears
|
||||||
|
expect(streamedLines, contains('done'));
|
||||||
|
expect(streamedLines, contains(' └─Compiling, linking and signing...'));
|
||||||
|
expect(
|
||||||
|
context.stdout,
|
||||||
|
contains('built and packaged successfully.'),
|
||||||
|
);
|
||||||
|
expect(context.stderr, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('forwards all env variables to flutter assemble', () {
|
||||||
|
final Directory buildDir = fileSystem.directory('/path/to/builds')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
final Directory flutterRoot = fileSystem.directory('/path/to/flutter')
|
||||||
|
..createSync(recursive: true);
|
||||||
|
const String archs = 'arm64 armv7';
|
||||||
|
const String buildMode = 'Release';
|
||||||
|
const String dartObfuscation = 'false';
|
||||||
|
const String dartDefines = 'flutter.inspector.structuredErrors%3Dtrue';
|
||||||
|
const String expandedCodeSignIdentity = 'F1326572E0B71C3C8442805230CB4B33B708A2E2';
|
||||||
|
const String extraFrontEndOptions = '--some-option';
|
||||||
|
const String extraGenSnapshotOptions = '--obfuscate';
|
||||||
|
const String sdkRoot = '/path/to/sdk';
|
||||||
|
const String splitDebugInfo = '/path/to/split/debug/info';
|
||||||
|
const String trackWidgetCreation = 'true';
|
||||||
|
const String treeShake = 'true';
|
||||||
|
final TestContext context = TestContext(
|
||||||
|
<String>['build'],
|
||||||
|
<String, String>{
|
||||||
|
'ACTION': 'install',
|
||||||
|
'ARCHS': archs,
|
||||||
|
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||||
|
'CODE_SIGNING_REQUIRED': 'YES',
|
||||||
|
'CONFIGURATION': buildMode,
|
||||||
|
'DART_DEFINES': dartDefines,
|
||||||
|
'DART_OBFUSCATION': dartObfuscation,
|
||||||
|
'ENABLE_BITCODE': 'YES',
|
||||||
|
'EXPANDED_CODE_SIGN_IDENTITY': expandedCodeSignIdentity,
|
||||||
|
'EXTRA_FRONT_END_OPTIONS': extraFrontEndOptions,
|
||||||
|
'EXTRA_GEN_SNAPSHOT_OPTIONS': extraGenSnapshotOptions,
|
||||||
|
'FLUTTER_ROOT': flutterRoot.path,
|
||||||
|
'INFOPLIST_PATH': 'Info.plist',
|
||||||
|
'SDKROOT': sdkRoot,
|
||||||
|
'SPLIT_DEBUG_INFO': splitDebugInfo,
|
||||||
|
'TRACK_WIDGET_CREATION': trackWidgetCreation,
|
||||||
|
'TREE_SHAKE_ICONS': treeShake,
|
||||||
|
},
|
||||||
|
commands: <FakeCommand>[
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
'${flutterRoot.path}/bin/flutter',
|
||||||
|
'assemble',
|
||||||
|
'--no-version-check',
|
||||||
|
'--output=${buildDir.path}/',
|
||||||
|
'-dTargetPlatform=ios',
|
||||||
|
'-dTargetFile=lib/main.dart',
|
||||||
|
'-dBuildMode=${buildMode.toLowerCase()}',
|
||||||
|
'-dIosArchs=$archs',
|
||||||
|
'-dSdkRoot=$sdkRoot',
|
||||||
|
'-dSplitDebugInfo=$splitDebugInfo',
|
||||||
|
'-dTreeShakeIcons=$treeShake',
|
||||||
|
'-dTrackWidgetCreation=$trackWidgetCreation',
|
||||||
|
'-dDartObfuscation=$dartObfuscation',
|
||||||
|
'-dEnableBitcode=true',
|
||||||
|
'--ExtraGenSnapshotOptions=$extraGenSnapshotOptions',
|
||||||
|
'--DartDefines=$dartDefines',
|
||||||
|
'--ExtraFrontEndOptions=$extraFrontEndOptions',
|
||||||
|
'-dCodesignIdentity=$expandedCodeSignIdentity',
|
||||||
|
'release_ios_bundle_flutter_assets',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
)..run();
|
||||||
|
expect(
|
||||||
|
context.stdout,
|
||||||
|
contains('built and packaged successfully.'),
|
||||||
|
);
|
||||||
|
expect(context.stderr, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('test_observatory_bonjour_service', () {
|
||||||
|
test('handles when the Info.plist is missing', () {
|
||||||
|
final Directory buildDir = fileSystem.directory('/path/to/builds');
|
||||||
|
buildDir.createSync(recursive: true);
|
||||||
|
final TestContext context = TestContext(
|
||||||
|
<String>['test_observatory_bonjour_service'],
|
||||||
|
<String, String>{
|
||||||
|
'CONFIGURATION': 'Debug',
|
||||||
|
'BUILT_PRODUCTS_DIR': buildDir.path,
|
||||||
|
'INFOPLIST_PATH': 'Info.plist',
|
||||||
|
},
|
||||||
|
commands: <FakeCommand>[],
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
)..run();
|
||||||
|
expect(
|
||||||
|
context.stdout,
|
||||||
|
contains(
|
||||||
|
'Info.plist does not exist. Skipping _dartobservatory._tcp NSBonjourServices insertion.'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestContext extends Context {
|
||||||
|
TestContext(
|
||||||
|
List<String> arguments,
|
||||||
|
Map<String, String> environment, {
|
||||||
|
required this.fileSystem,
|
||||||
|
required List<FakeCommand> commands,
|
||||||
|
File? scriptOutputStreamFile,
|
||||||
|
}) : processManager = FakeProcessManager.list(commands),
|
||||||
|
super(arguments: arguments, environment: environment, scriptOutputStreamFile: scriptOutputStreamFile);
|
||||||
|
|
||||||
|
final FileSystem fileSystem;
|
||||||
|
final FakeProcessManager processManager;
|
||||||
|
|
||||||
|
String stdout = '';
|
||||||
|
String stderr = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool existsDir(String path) {
|
||||||
|
return fileSystem.directory(path).existsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool existsFile(String path) {
|
||||||
|
return fileSystem.file(path).existsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProcessResult runSync(
|
||||||
|
String bin,
|
||||||
|
List<String> args, {
|
||||||
|
bool verbose = false,
|
||||||
|
bool allowFail = false,
|
||||||
|
String? workingDirectory,
|
||||||
|
}) {
|
||||||
|
return processManager.runSync(
|
||||||
|
<dynamic>[bin, ...args],
|
||||||
|
workingDirectory: workingDirectory,
|
||||||
|
environment: environment,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void echoError(String message) {
|
||||||
|
stderr += '$message\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void echo(String message) {
|
||||||
|
stdout += message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Never exitApp(int code) {
|
||||||
|
// This is an exception for the benefit of unit tests.
|
||||||
|
// The real implementation calls `exit(code)`.
|
||||||
|
throw Exception('App exited with code $code');
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,8 @@ void main() {
|
|||||||
|
|
||||||
test('no unauthorized imports of dart:io', () {
|
test('no unauthorized imports of dart:io', () {
|
||||||
final List<String> allowedPaths = <String>[
|
final List<String> allowedPaths = <String>[
|
||||||
|
// This is a standalone script invoked by xcode, not part of the tool
|
||||||
|
fileSystem.path.join(flutterTools, 'bin', 'xcode_backend.dart'),
|
||||||
fileSystem.path.join(flutterTools, 'lib', 'src', 'base', 'io.dart'),
|
fileSystem.path.join(flutterTools, 'lib', 'src', 'base', 'io.dart'),
|
||||||
fileSystem.path.join(flutterTools, 'lib', 'src', 'base', 'platform.dart'),
|
fileSystem.path.join(flutterTools, 'lib', 'src', 'base', 'platform.dart'),
|
||||||
fileSystem.path.join(flutterTools, 'lib', 'src', 'base', 'error_handling_io.dart'),
|
fileSystem.path.join(flutterTools, 'lib', 'src', 'base', 'error_handling_io.dart'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user