// 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. // @dart = 2.8 import 'dart:io' as io; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/globals_null_migrated.dart' as globals; import '../src/common.dart'; const String xcodeBackendPath = 'bin/xcode_backend.sh'; const String xcodeBackendErrorHeader = '========================================================================'; // Acceptable $CONFIGURATION/$FLUTTER_BUILD_MODE values should be debug, profile, or release const Map unknownConfiguration = { 'CONFIGURATION': 'Custom', }; // $FLUTTER_BUILD_MODE will override $CONFIGURATION const Map unknownFlutterBuildMode = { 'FLUTTER_BUILD_MODE': 'Custom', 'CONFIGURATION': 'Debug', }; // Can't use a debug engine build with a release build. const Map localEngineDebugBuildModeRelease = { 'SOURCE_ROOT': '../examples/hello_world', 'FLUTTER_ROOT': '../..', 'LOCAL_ENGINE': '/engine/src/out/ios_debug_unopt', 'CONFIGURATION': 'Release', }; // Can't use a debug build with a profile engine. const Map localEngineProfileBuildeModeRelease = { 'SOURCE_ROOT': '../examples/hello_world', 'FLUTTER_ROOT': '../..', 'LOCAL_ENGINE': '/engine/src/out/ios_profile', 'CONFIGURATION': 'Debug', 'FLUTTER_BUILD_MODE': 'Debug', }; void main() { Future expectXcodeBackendFails(Map environment) async { final ProcessResult result = await Process.run( xcodeBackendPath, ['build'], environment: environment, ); expect(result.stderr, startsWith(xcodeBackendErrorHeader)); expect(result.exitCode, isNot(0)); } test('Xcode backend fails with no arguments', () async { final ProcessResult result = await Process.run( xcodeBackendPath, [], environment: { 'SOURCE_ROOT': '../examples/hello_world', 'FLUTTER_ROOT': '../..', }, ); expect(result.stderr, startsWith('error: Your Xcode project is incompatible with this version of Flutter.')); expect(result.exitCode, isNot(0)); }, skip: !io.Platform.isMacOS); test('Xcode backend fails for on unsupported configuration combinations', () async { await expectXcodeBackendFails(unknownConfiguration); await expectXcodeBackendFails(unknownFlutterBuildMode); await expectXcodeBackendFails(localEngineDebugBuildModeRelease); await expectXcodeBackendFails(localEngineProfileBuildeModeRelease); }, skip: !io.Platform.isMacOS); test('Xcode backend warns archiving a non-release build.', () async { final ProcessResult result = await Process.run( xcodeBackendPath, ['build'], environment: { 'CONFIGURATION': 'Debug', 'ACTION': 'install', }, ); expect(result.stdout, contains('warning: Flutter archive not built in Release mode.')); expect(result.exitCode, isNot(0)); }, skip: !io.Platform.isMacOS); group('observatory Bonjour service keys', () { Directory buildDirectory; File infoPlist; setUp(() { buildDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_tools_xcode_backend_test.'); infoPlist = buildDirectory.childFile('Info.plist'); }); test('handles when the Info.plist is missing', () async { final ProcessResult result = await Process.run( xcodeBackendPath, ['test_observatory_bonjour_service'], environment: { 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); expect(result.stdout, contains('Info.plist does not exist.')); expect(result.exitCode, 0); }); const String emptyPlist = ''' '''; test('does not add keys in Release', () async { infoPlist.writeAsStringSync(emptyPlist); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_observatory_bonjour_service'], environment: { 'CONFIGURATION': 'Release', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); final String actualInfoPlist = infoPlist.readAsStringSync(); expect(actualInfoPlist, isNot(contains('NSBonjourServices'))); expect(actualInfoPlist, isNot(contains('dartobservatory'))); expect(actualInfoPlist, isNot(contains('NSLocalNetworkUsageDescription'))); expect(result.exitCode, 0); }); for (final String buildConfiguration in ['Debug', 'Profile']) { test('add keys in $buildConfiguration', () async { infoPlist.writeAsStringSync(emptyPlist); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_observatory_bonjour_service'], environment: { 'CONFIGURATION': buildConfiguration, 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); final String actualInfoPlist = infoPlist.readAsStringSync(); expect(actualInfoPlist, contains('NSBonjourServices')); expect(actualInfoPlist, contains('dartobservatory')); expect(actualInfoPlist, contains('NSLocalNetworkUsageDescription')); expect(result.exitCode, 0); }); } test('adds to existing Bonjour services, does not override network usage description', () async { infoPlist.writeAsStringSync(''' NSBonjourServices _bogus._tcp NSLocalNetworkUsageDescription Don't override this '''); final ProcessResult result = await Process.run( xcodeBackendPath, ['test_observatory_bonjour_service'], environment: { 'CONFIGURATION': 'Debug', 'BUILT_PRODUCTS_DIR': buildDirectory.path, 'INFOPLIST_PATH': 'Info.plist', }, ); expect(infoPlist.readAsStringSync(), ''' NSBonjourServices _dartobservatory._tcp _bogus._tcp NSLocalNetworkUsageDescription Don't override this '''); expect(result.exitCode, 0); }); }, skip: !io.Platform.isMacOS); }