diff --git a/dev/devicelab/bin/tasks/ios_content_validation_test.dart b/dev/devicelab/bin/tasks/ios_content_validation_test.dart index 50595f693b..2f120975e5 100644 --- a/dev/devicelab/bin/tasks/ios_content_validation_test.dart +++ b/dev/devicelab/bin/tasks/ios_content_validation_test.dart @@ -14,6 +14,8 @@ import 'package:path/path.dart' as path; Future main() async { await task(() async { try { + bool foundProjectName = false; + bool bitcode = false; await runProjectTest((FlutterProject flutterProject) async { section('Build app with with --obfuscate'); await inDirectory(flutterProject.rootPath, () async { @@ -50,13 +52,6 @@ Future main() async { fail('Failed to produce expected output at ${outputAppFrameworkBinary.path}'); } - if (await dartObservatoryBonjourServiceFound(outputAppPath)) { - throw TaskResult.failure('Release bundle has unexpected NSBonjourServices'); - } - if (await localNetworkUsageFound(outputAppPath)) { - throw TaskResult.failure('Release bundle has unexpected NSLocalNetworkUsageDescription'); - } - section('Validate obfuscation'); // Verify that an identifier from the Dart project code is not present @@ -68,11 +63,11 @@ Future main() async { canFail: true, ); if (response.trim().contains('matches')) { - throw TaskResult.failure('Found project name in obfuscated dart library'); + foundProjectName = true; } }); - section('Validate release contents'); + section('Validate bitcode'); final Directory outputFlutterFramework = Directory(path.join( flutterProject.rootPath, @@ -88,13 +83,7 @@ Future main() async { if (!outputFlutterFrameworkBinary.existsSync()) { fail('Failed to produce expected output at ${outputFlutterFrameworkBinary.path}'); } - - // Archiving should contain a bitcode blob, but not building in release. - // This mimics Xcode behavior and present a developer from having to install a - // 300+MB app to test devices. - if (await containsBitcode(outputFlutterFrameworkBinary.path)) { - throw TaskResult.failure('Bitcode present in Flutter.framework'); - } + bitcode = await containsBitcode(outputFlutterFrameworkBinary.path); section('Xcode backend script'); @@ -112,7 +101,7 @@ Future main() async { 'xcode_backend.sh' ); - // Simulate a common Xcode build setting misconfiguration + // Simulate a commonly Xcode build setting misconfiguration // where FLUTTER_APPLICATION_PATH is missing final int result = await exec( xcodeBackendPath, @@ -122,7 +111,6 @@ Future main() async { 'TARGET_BUILD_DIR': buildPath, 'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks', 'VERBOSE_SCRIPT_LOGGING': '1', - 'FLUTTER_BUILD_MODE': 'release', 'ACTION': 'install', // Skip bitcode stripping since we just checked that above. }, ); @@ -138,36 +126,18 @@ Future main() async { if (!outputAppFrameworkBinary.existsSync()) { fail('Failed to re-embed ${outputAppFrameworkBinary.path}'); } - - section('Clean build'); - - await inDirectory(flutterProject.rootPath, () async { - await flutter('clean'); - }); - - section('Validate debug contents'); - - await inDirectory(flutterProject.rootPath, () async { - await flutter('build', options: [ - 'ios', - '--debug', - '--no-codesign', - ]); - }); - - // Debug should also not contain bitcode. - if (await containsBitcode(outputFlutterFrameworkBinary.path)) { - throw TaskResult.failure('Bitcode present in Flutter.framework'); - } - - if (!await dartObservatoryBonjourServiceFound(outputAppPath)) { - throw TaskResult.failure('Debug bundle is missing NSBonjourServices'); - } - if (!await localNetworkUsageFound(outputAppPath)) { - throw TaskResult.failure('Debug bundle is missing NSLocalNetworkUsageDescription'); - } }); + if (foundProjectName) { + return TaskResult.failure('Found project name in obfuscated dart library'); + } + // Archiving should contain a bitcode blob, but not building in release. + // This mimics Xcode behavior and present a developer from having to install a + // 300+MB app to test devices. + if (bitcode) { + return TaskResult.failure('Bitcode present in Flutter.framework'); + } + return TaskResult.success(null); } on TaskResult catch (taskResult) { return taskResult; diff --git a/dev/devicelab/lib/framework/ios.dart b/dev/devicelab/lib/framework/ios.dart index 38be6612ca..5ea618b334 100644 --- a/dev/devicelab/lib/framework/ios.dart +++ b/dev/devicelab/lib/framework/ios.dart @@ -5,8 +5,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:path/path.dart' as path; - import 'utils.dart'; typedef SimulatorFunction = Future Function(String deviceId); @@ -104,40 +102,6 @@ Future containsBitcode(String pathToBinary) async { return !emptyBitcodeMarkerFound; } -Future dartObservatoryBonjourServiceFound(String appBundlePath) async => - (await eval( - 'plutil', - [ - '-extract', - 'NSBonjourServices', - 'xml1', - '-o', - '-', - path.join( - appBundlePath, - 'Info.plist', - ), - ], - canFail: true, - )).contains('_dartobservatory._tcp'); - -Future localNetworkUsageFound(String appBundlePath) async => - await exec( - 'plutil', - [ - '-extract', - 'NSLocalNetworkUsageDescription', - 'xml1', - '-o', - '-', - path.join( - appBundlePath, - 'Info.plist', - ), - ], - canFail: true, - ) == 0; - /// Creates and boots a new simulator, passes the new simulator's identifier to /// `testFunction`. /// diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh index 717f3ad530..c548f8dc70 100755 --- a/packages/flutter_tools/bin/xcode_backend.sh +++ b/packages/flutter_tools/bin/xcode_backend.sh @@ -38,32 +38,6 @@ AssertExists() { 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 @@ -98,12 +72,24 @@ BuildApp() { # 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 build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")" local artifact_variant="unknown" case "$build_mode" in - release ) artifact_variant="ios-release";; - profile ) artifact_variant="ios-profile";; - debug ) artifact_variant="ios";; + *release*) build_mode="release"; artifact_variant="ios-release";; + *profile*) build_mode="profile"; artifact_variant="ios-profile";; + *debug*) build_mode="debug"; artifact_variant="ios";; + *) + 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 # Warn the user if not archiving (ACTION=install) in release mode. @@ -141,7 +127,7 @@ is set to release or run \"flutter build ios --release\", then re-run Archive fr fi local bitcode_flag="" - if [[ "$ENABLE_BITCODE" == "YES" ]]; then + if [[ $ENABLE_BITCODE == "YES" ]]; then bitcode_flag="true" fi @@ -320,32 +306,6 @@ EmbedFlutterFrameworks() { RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/App.framework/App" RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter" fi - - 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 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 } EmbedAndThinFrameworks() { @@ -368,8 +328,5 @@ else EmbedFlutterFrameworks ;; "embed_and_thin") EmbedAndThinFrameworks ;; - "test_observatory_bonjour_service") - # Exposed for integration testing only. - AddObservatoryBonjourService ;; esac fi diff --git a/packages/flutter_tools/test/general.shard/ios/xcode_backend_test.dart b/packages/flutter_tools/test/general.shard/ios/xcode_backend_test.dart new file mode 100644 index 0000000000..b350cde384 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/ios/xcode_backend_test.dart @@ -0,0 +1,64 @@ +// 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:flutter_tools/src/base/io.dart'; + +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 archive a non-release build. +const Map installWithoutRelease = { + 'CONFIGURATION': 'Debug', + 'ACTION': 'install', +}; + +// 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 for on unsupported configuration combinations', () async { + await expectXcodeBackendFails(unknownConfiguration); + await expectXcodeBackendFails(unknownFlutterBuildMode); + await expectXcodeBackendFails(installWithoutRelease); + await expectXcodeBackendFails(localEngineDebugBuildModeRelease); + await expectXcodeBackendFails(localEngineProfileBuildeModeRelease); + }, skip: true); // https://github.com/flutter/flutter/issues/35707 (non-hermetic test requires precache to have run) +} diff --git a/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart b/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart deleted file mode 100644 index fa272f290f..0000000000 --- a/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart +++ /dev/null @@ -1,181 +0,0 @@ -// 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' as io; - -import 'package:flutter_tools/src/base/io.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/globals.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 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'); - }); - - 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', - }, - ); - print(result.stderr); - - 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', - }, - ); - print(result.stderr); - - 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); -}