diff --git a/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart b/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart index a410eb2c47150..b2be48db88515 100644 --- a/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart +++ b/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart @@ -2,10 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:convert'; import 'dart:io'; -import 'package:flutter_devicelab/common.dart'; import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/ios.dart'; import 'package:flutter_devicelab/framework/task_result.dart'; @@ -16,8 +14,6 @@ Future main() async { await task(() async { section('Copy test Flutter App with watchOS Companion'); - String? watchDeviceID; - String? phoneDeviceID; final Directory tempDir = Directory.systemTemp .createTempSync('flutter_ios_app_with_extensions_test.'); final Directory projectDir = @@ -32,11 +28,23 @@ Future main() async { section('Create release build'); + // This only builds the iOS app, not the companion watchOS app. The watchOS app + // has been removed as a build dependency and is not embedded in the app to avoid + // requiring the watchOS being available in CI. + // Instead, validate the tool detects that there is a watch companion, and omits + // the "-sdk iphoneos" option, which fails to build the watchOS app. + // See https://github.com/flutter/flutter/pull/94190. await inDirectory(projectDir, () async { - await flutter( + final String buildOutput = await evalFlutter( 'build', options: ['ios', '--no-codesign', '--release', '--verbose'], ); + if (!buildOutput.contains('Watch companion app found')) { + throw TaskResult.failure('Did not detect watch companion'); + } + if (buildOutput.contains('-sdk iphoneos -destination')) { + throw TaskResult.failure('-sdk must be omitted for app with watch companion'); + } }); final String appBundle = Directory(path.join( @@ -64,228 +72,17 @@ Future main() async { await _checkFlutterFrameworkArchs(appFrameworkPath); await _checkFlutterFrameworkArchs(flutterFrameworkPath); - // Check the watch extension framework added in the Podfile - // is in place with the expected watch archs. - final String watchExtensionFrameworkPath = path.join( - appBundle, - 'Watch', - 'watch.app', - 'PlugIns', - 'watch Extension.appex', - 'Frameworks', - 'EFQRCode.framework', - 'EFQRCode', - ); - unawaited(_checkWatchExtensionFrameworkArchs(watchExtensionFrameworkPath)); - - section('Clean build'); - - await inDirectory(projectDir, () async { - await flutter('clean'); - }); - - section('Create debug build'); - - await inDirectory(projectDir, () async { - await flutter( - 'build', - options: ['ios', '--debug', '--no-codesign', '--verbose'], - ); - }); - - checkDirectoryExists(appBundle); - await _checkFlutterFrameworkArchs(appFrameworkPath); - await _checkFlutterFrameworkArchs(flutterFrameworkPath); - unawaited(_checkWatchExtensionFrameworkArchs(watchExtensionFrameworkPath)); - section('Clean build'); await inDirectory(projectDir, () async { await flutter('clean'); }); - section('Run app on simulator device'); - - // Xcode 11.4 simctl create makes the runtime argument optional, and defaults to latest. - // TODO(jmagman): Remove runtime parsing when devicelab upgrades to Xcode 11.4 https://github.com/flutter/flutter/issues/54889 - final String availableRuntimes = await eval( - 'xcrun', - [ - 'simctl', - 'list', - 'runtimes', - ], - workingDirectory: flutterDirectory.path, - ); - - // Example simctl list: - // == Runtimes == - // iOS 10.3 (10.3.1 - 14E8301) - com.apple.CoreSimulator.SimRuntime.iOS-10-3 - // iOS 13.4 (13.4 - 17E255) - com.apple.CoreSimulator.SimRuntime.iOS-13-4 - // tvOS 13.4 (13.4 - 17L255) - com.apple.CoreSimulator.SimRuntime.tvOS-13-4 - // watchOS 6.2 (6.2 - 17T256) - com.apple.CoreSimulator.SimRuntime.watchOS-6-2 - String? iOSSimRuntime; - String? watchSimRuntime; - - final RegExp iOSRuntimePattern = RegExp(r'iOS .*\) - (.*)'); - final RegExp watchOSRuntimePattern = RegExp(r'watchOS .*\) - (.*)'); - - for (final String runtime in LineSplitter.split(availableRuntimes)) { - // These seem to be in order, so allow matching multiple lines so it grabs - // the last (hopefully latest) one. - final RegExpMatch? iOSRuntimeMatch = iOSRuntimePattern.firstMatch(runtime); - if (iOSRuntimeMatch != null) { - iOSSimRuntime = iOSRuntimeMatch.group(1)!.trim(); - continue; - } - final RegExpMatch? watchOSRuntimeMatch = watchOSRuntimePattern.firstMatch(runtime); - if (watchOSRuntimeMatch != null) { - watchSimRuntime = watchOSRuntimeMatch.group(1)!.trim(); - } - } - if (iOSSimRuntime == null || watchSimRuntime == null) { - String message; - if (iOSSimRuntime != null) { - message = 'Found "$iOSSimRuntime", but no watchOS simulator runtime found.'; - } else if (watchSimRuntime != null) { - message = 'Found "$watchSimRuntime", but no iOS simulator runtime found.'; - } else { - message = 'watchOS and iOS simulator runtimes not found.'; - } - return TaskResult.failure('$message Available runtimes:\n$availableRuntimes'); - } - - // Create iOS simulator. - phoneDeviceID = await eval( - 'xcrun', - [ - 'simctl', - 'create', - 'TestFlutteriPhoneWithWatch', - 'com.apple.CoreSimulator.SimDeviceType.iPhone-11', - iOSSimRuntime, - ], - workingDirectory: flutterDirectory.path, - ); - - // Create watchOS simulator. - watchDeviceID = await eval( - 'xcrun', - [ - 'simctl', - 'create', - 'TestFlutterWatch', - 'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-44mm', - watchSimRuntime, - ], - workingDirectory: flutterDirectory.path, - ); - - // Pair watch with phone. - await eval( - 'xcrun', - ['simctl', 'pair', watchDeviceID, phoneDeviceID], - workingDirectory: flutterDirectory.path, - ); - - // Boot simulator devices. - await eval( - 'xcrun', - ['simctl', 'bootstatus', phoneDeviceID, '-b'], - workingDirectory: flutterDirectory.path, - ); - await eval( - 'xcrun', - ['simctl', 'bootstatus', watchDeviceID, '-b'], - workingDirectory: flutterDirectory.path, - ); - - // Start app on simulated device. - final Process process = await startProcess( - path.join(flutterDirectory.path, 'bin', 'flutter'), - ['run', '-d', phoneDeviceID], - workingDirectory: projectDir.path); - - process.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen((String line) { - print('stdout: $line'); - // Wait for app startup to complete and quit immediately afterwards. - if (line.startsWith('An Observatory debugger')) { - process.stdin.write('q'); - } - }); - process.stderr - .transform(utf8.decoder) - .transform(const LineSplitter()) - .listen((String line) { - print('stderr: $line'); - }); - - final int exitCode = await process.exitCode; - - if (exitCode != 0) { - return TaskResult.failure( - 'Failed to start flutter iOS app with WatchOS companion on simulated device.'); - } - - final String simulatorAppBundle = Directory(path.join( - projectDir.path, - 'build', - 'ios', - 'iphonesimulator', - 'Runner.app', - )).path; - - checkDirectoryExists(simulatorAppBundle); - checkFileExists(path.join( - simulatorAppBundle, - 'Frameworks', - 'App.framework', - 'App', - )); - checkFileExists(path.join( - simulatorAppBundle, - 'Frameworks', - 'Flutter.framework', - 'Flutter', - )); - return TaskResult.success(null); } catch (e) { return TaskResult.failure(e.toString()); } finally { rmTree(tempDir); - // Delete simulator devices - if (watchDeviceID != null && watchDeviceID != '') { - await eval( - 'xcrun', - ['simctl', 'shutdown', watchDeviceID], - canFail: true, - workingDirectory: flutterDirectory.path, - ); - await eval( - 'xcrun', - ['simctl', 'delete', watchDeviceID], - canFail: true, - workingDirectory: flutterDirectory.path, - ); - } - if (phoneDeviceID != null && phoneDeviceID != '') { - await eval( - 'xcrun', - ['simctl', 'shutdown', phoneDeviceID], - canFail: true, - workingDirectory: flutterDirectory.path, - ); - await eval( - 'xcrun', - ['simctl', 'delete', phoneDeviceID], - canFail: true, - workingDirectory: flutterDirectory.path, - ); - } } }); } @@ -302,15 +99,3 @@ Future _checkFlutterFrameworkArchs(String frameworkPath) async { throw TaskResult.failure('$frameworkPath x86_64 architecture unexpectedly present'); } } - -Future _checkWatchExtensionFrameworkArchs(String frameworkPath) async { - checkFileExists(frameworkPath); - final String archs = await fileType(frameworkPath); - if (!archs.contains('armv7k')) { - throw TaskResult.failure('$frameworkPath armv7k architecture missing'); - } - - if (!archs.contains('arm64_32')) { - throw TaskResult.failure('$frameworkPath arm64_32 architecture missing'); - } -} diff --git a/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/project.pbxproj index bedb0e89bf951..21c59118ddea3 100644 --- a/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/project.pbxproj +++ b/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/project.pbxproj @@ -18,7 +18,6 @@ 49C15B60243E340E0025F804 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C15B5F243E340E0025F804 /* ExtensionDelegate.swift */; }; 49C15B62243E340F0025F804 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49C15B61243E340F0025F804 /* Assets.xcassets */; }; 49C15B65243E340F0025F804 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49C15B64243E340F0025F804 /* Preview Assets.xcassets */; }; - 49C15B69243E340F0025F804 /* watch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 49C15B4A243E340B0025F804 /* watch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -34,13 +33,6 @@ remoteGlobalIDString = 49C15B55243E340E0025F804; remoteInfo = "watch Extension"; }; - 49C15B67243E340F0025F804 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 49C15B49243E340B0025F804; - remoteInfo = watch; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -55,17 +47,6 @@ name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; - 49C15B73243E340F0025F804 /* Embed Watch Content */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "$(CONTENTS_FOLDER_PATH)/Watch"; - dstSubfolderSpec = 16; - files = ( - 49C15B69243E340F0025F804 /* watch.app in Embed Watch Content */, - ); - name = "Embed Watch Content"; - runOnlyForDeploymentPostprocessing = 0; - }; 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -307,13 +288,11 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 49C15B73243E340F0025F804 /* Embed Watch Content */, DF3DAF4426EF33A40B49B448 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( - 49C15B68243E340F0025F804 /* PBXTargetDependency */, ); name = Runner; productName = Runner; @@ -531,11 +510,6 @@ target = 49C15B55243E340E0025F804 /* watch Extension */; targetProxy = 49C15B58243E340E0025F804 /* PBXContainerItemProxy */; }; - 49C15B68243E340F0025F804 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 49C15B49243E340B0025F804 /* watch */; - targetProxy = 49C15B67243E340F0025F804 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ diff --git a/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e1fb7f..c87d15a335208 100644 --- a/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/dev/integration_tests/ios_app_with_extensions/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + - -