// 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 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/build_info.dart'; import '../src/common.dart'; import '../src/darwin_common.dart'; import 'test_utils.dart'; void main() { for (final BuildMode buildMode in <BuildMode>[BuildMode.debug, BuildMode.release]) { group(buildMode.name, () { String flutterRoot; String projectRoot; String flutterBin; Directory tempDir; Directory buildPath; Directory outputApp; Directory outputFlutterFramework; File outputFlutterFrameworkBinary; Directory outputAppFramework; File outputAppFrameworkBinary; setUpAll(() { flutterRoot = getFlutterRoot(); tempDir = createResolvedTempDirectorySync('ios_content_validation.'); flutterBin = fileSystem.path.join( flutterRoot, 'bin', 'flutter', ); processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'create', '--platforms=ios', '-i', 'objc', 'hello', ], workingDirectory: tempDir.path); projectRoot = tempDir.childDirectory('hello').path; processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'build', 'ios', '--verbose', '--no-codesign', '--${buildMode.name}', '--obfuscate', '--split-debug-info=foo/', ], workingDirectory: projectRoot); buildPath = fileSystem.directory(fileSystem.path.join( projectRoot, 'build', 'ios', 'iphoneos', )); outputApp = buildPath.childDirectory('Runner.app'); outputFlutterFramework = fileSystem.directory( fileSystem.path.join( outputApp.path, 'Frameworks', 'Flutter.framework', ), ); outputFlutterFrameworkBinary = outputFlutterFramework.childFile('Flutter'); outputAppFramework = fileSystem.directory(fileSystem.path.join( outputApp.path, 'Frameworks', 'App.framework', )); outputAppFrameworkBinary = outputAppFramework.childFile('App'); }); tearDownAll(() { tryToDelete(tempDir); }); testWithoutContext('flutter build ios builds a valid app', () { expect(outputAppFramework.childFile('App'), exists); final File vmSnapshot = fileSystem.file(fileSystem.path.join( outputAppFramework.path, 'flutter_assets', 'vm_snapshot_data', )); expect(vmSnapshot.existsSync(), buildMode == BuildMode.debug); expect(outputFlutterFramework.childDirectory('Headers'), isNot(exists)); expect(outputFlutterFramework.childDirectory('Modules'), isNot(exists)); // Archiving should contain a bitcode blob, but not building. // This mimics Xcode behavior and prevents a developer from having to install a // 300+MB app. expect(containsBitcode(outputFlutterFrameworkBinary.path, processManager), isFalse); }); testWithoutContext('Info.plist dart observatory Bonjour service', () { final String infoPlistPath = fileSystem.path.join( outputApp.path, 'Info.plist', ); final ProcessResult bonjourServices = processManager.runSync( <String>[ 'plutil', '-extract', 'NSBonjourServices', 'xml1', '-o', '-', infoPlistPath, ], ); final bool bonjourServicesFound = (bonjourServices.stdout as String).contains('_dartobservatory._tcp'); expect(bonjourServicesFound, buildMode == BuildMode.debug); final ProcessResult localNetworkUsage = processManager.runSync( <String>[ 'plutil', '-extract', 'NSLocalNetworkUsageDescription', 'xml1', '-o', '-', infoPlistPath, ], ); final bool localNetworkUsageFound = localNetworkUsage.exitCode == 0; expect(localNetworkUsageFound, buildMode == BuildMode.debug); }); testWithoutContext('check symbols', () { final ProcessResult symbols = processManager.runSync( <String>[ 'nm', '-g', outputAppFrameworkBinary.path, '-arch', 'arm64', ], ); final bool aotSymbolsFound = (symbols.stdout as String).contains('_kDartVmSnapshot'); expect(aotSymbolsFound, buildMode != BuildMode.debug); }); testWithoutContext('xcode_backend embed_and_thin', () { outputFlutterFramework.deleteSync(recursive: true); outputAppFramework.deleteSync(recursive: true); expect(outputFlutterFrameworkBinary.existsSync(), isFalse); expect(outputAppFrameworkBinary.existsSync(), isFalse); final String xcodeBackendPath = fileSystem.path.join( flutterRoot, 'packages', 'flutter_tools', 'bin', 'xcode_backend.sh', ); // Simulate a common Xcode build setting misconfiguration // where FLUTTER_APPLICATION_PATH is missing final ProcessResult xcodeBackendResult = processManager.runSync( <String>[ xcodeBackendPath, 'embed_and_thin', ], environment: <String, String>{ 'SOURCE_ROOT': fileSystem.path.join(projectRoot, 'ios'), 'BUILT_PRODUCTS_DIR': fileSystem.path.join( projectRoot, 'build', 'ios', 'Release-iphoneos', ), 'TARGET_BUILD_DIR': buildPath.path, 'FRAMEWORKS_FOLDER_PATH': 'Runner.app/Frameworks', 'VERBOSE_SCRIPT_LOGGING': '1', 'FLUTTER_BUILD_MODE': 'release', 'ACTION': 'install', 'ARCHS': 'arm64 armv7', 'FLUTTER_ROOT': flutterRoot, // Skip bitcode stripping since we just checked that above. }, ); expect(xcodeBackendResult.exitCode, 0); expect(outputFlutterFrameworkBinary.existsSync(), isTrue); expect(outputAppFrameworkBinary.existsSync(), isTrue); }, skip: !platform.isMacOS || buildMode != BuildMode.release); testWithoutContext('validate obfuscation', () { final ProcessResult grepResult = processManager.runSync(<String>[ 'grep', '-i', 'hello', outputAppFrameworkBinary.path, ]); expect(grepResult.stdout, isNot(contains('matches'))); }); }, skip: !platform.isMacOS, timeout: const Timeout(Duration(minutes: 5)), ); } }