Unverified Commit 62dc238b authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Avoid using watchOS SDK in CI tests (#94190)

parent 7d3ca786
...@@ -2,10 +2,8 @@ ...@@ -2,10 +2,8 @@
// 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.
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter_devicelab/common.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/ios.dart'; import 'package:flutter_devicelab/framework/ios.dart';
import 'package:flutter_devicelab/framework/task_result.dart'; import 'package:flutter_devicelab/framework/task_result.dart';
...@@ -16,8 +14,6 @@ Future<void> main() async { ...@@ -16,8 +14,6 @@ Future<void> main() async {
await task(() async { await task(() async {
section('Copy test Flutter App with watchOS Companion'); section('Copy test Flutter App with watchOS Companion');
String? watchDeviceID;
String? phoneDeviceID;
final Directory tempDir = Directory.systemTemp final Directory tempDir = Directory.systemTemp
.createTempSync('flutter_ios_app_with_extensions_test.'); .createTempSync('flutter_ios_app_with_extensions_test.');
final Directory projectDir = final Directory projectDir =
...@@ -32,11 +28,23 @@ Future<void> main() async { ...@@ -32,11 +28,23 @@ Future<void> main() async {
section('Create release build'); 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 inDirectory(projectDir, () async {
await flutter( final String buildOutput = await evalFlutter(
'build', 'build',
options: <String>['ios', '--no-codesign', '--release', '--verbose'], options: <String>['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( final String appBundle = Directory(path.join(
...@@ -64,228 +72,17 @@ Future<void> main() async { ...@@ -64,228 +72,17 @@ Future<void> main() async {
await _checkFlutterFrameworkArchs(appFrameworkPath); await _checkFlutterFrameworkArchs(appFrameworkPath);
await _checkFlutterFrameworkArchs(flutterFrameworkPath); 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: <String>['ios', '--debug', '--no-codesign', '--verbose'],
);
});
checkDirectoryExists(appBundle);
await _checkFlutterFrameworkArchs(appFrameworkPath);
await _checkFlutterFrameworkArchs(flutterFrameworkPath);
unawaited(_checkWatchExtensionFrameworkArchs(watchExtensionFrameworkPath));
section('Clean build'); section('Clean build');
await inDirectory(projectDir, () async { await inDirectory(projectDir, () async {
await flutter('clean'); 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',
<String>[
'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',
<String>[
'simctl',
'create',
'TestFlutteriPhoneWithWatch',
'com.apple.CoreSimulator.SimDeviceType.iPhone-11',
iOSSimRuntime,
],
workingDirectory: flutterDirectory.path,
);
// Create watchOS simulator.
watchDeviceID = await eval(
'xcrun',
<String>[
'simctl',
'create',
'TestFlutterWatch',
'com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-5-44mm',
watchSimRuntime,
],
workingDirectory: flutterDirectory.path,
);
// Pair watch with phone.
await eval(
'xcrun',
<String>['simctl', 'pair', watchDeviceID, phoneDeviceID],
workingDirectory: flutterDirectory.path,
);
// Boot simulator devices.
await eval(
'xcrun',
<String>['simctl', 'bootstatus', phoneDeviceID, '-b'],
workingDirectory: flutterDirectory.path,
);
await eval(
'xcrun',
<String>['simctl', 'bootstatus', watchDeviceID, '-b'],
workingDirectory: flutterDirectory.path,
);
// Start app on simulated device.
final Process process = await startProcess(
path.join(flutterDirectory.path, 'bin', 'flutter'),
<String>['run', '-d', phoneDeviceID],
workingDirectory: projectDir.path);
process.stdout
.transform<String>(utf8.decoder)
.transform<String>(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<String>(utf8.decoder)
.transform<String>(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); return TaskResult.success(null);
} catch (e) { } catch (e) {
return TaskResult.failure(e.toString()); return TaskResult.failure(e.toString());
} finally { } finally {
rmTree(tempDir); rmTree(tempDir);
// Delete simulator devices
if (watchDeviceID != null && watchDeviceID != '') {
await eval(
'xcrun',
<String>['simctl', 'shutdown', watchDeviceID],
canFail: true,
workingDirectory: flutterDirectory.path,
);
await eval(
'xcrun',
<String>['simctl', 'delete', watchDeviceID],
canFail: true,
workingDirectory: flutterDirectory.path,
);
}
if (phoneDeviceID != null && phoneDeviceID != '') {
await eval(
'xcrun',
<String>['simctl', 'shutdown', phoneDeviceID],
canFail: true,
workingDirectory: flutterDirectory.path,
);
await eval(
'xcrun',
<String>['simctl', 'delete', phoneDeviceID],
canFail: true,
workingDirectory: flutterDirectory.path,
);
}
} }
}); });
} }
...@@ -302,15 +99,3 @@ Future<void> _checkFlutterFrameworkArchs(String frameworkPath) async { ...@@ -302,15 +99,3 @@ Future<void> _checkFlutterFrameworkArchs(String frameworkPath) async {
throw TaskResult.failure('$frameworkPath x86_64 architecture unexpectedly present'); throw TaskResult.failure('$frameworkPath x86_64 architecture unexpectedly present');
} }
} }
Future<void> _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');
}
}
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
49C15B60243E340E0025F804 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C15B5F243E340E0025F804 /* ExtensionDelegate.swift */; }; 49C15B60243E340E0025F804 /* ExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49C15B5F243E340E0025F804 /* ExtensionDelegate.swift */; };
49C15B62243E340F0025F804 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49C15B61243E340F0025F804 /* Assets.xcassets */; }; 49C15B62243E340F0025F804 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49C15B61243E340F0025F804 /* Assets.xcassets */; };
49C15B65243E340F0025F804 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49C15B64243E340F0025F804 /* Preview 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 */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
...@@ -34,13 +33,6 @@ ...@@ -34,13 +33,6 @@
remoteGlobalIDString = 49C15B55243E340E0025F804; remoteGlobalIDString = 49C15B55243E340E0025F804;
remoteInfo = "watch Extension"; remoteInfo = "watch Extension";
}; };
49C15B67243E340F0025F804 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 49C15B49243E340B0025F804;
remoteInfo = watch;
};
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
...@@ -55,17 +47,6 @@ ...@@ -55,17 +47,6 @@
name = "Embed App Extensions"; name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0; 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 */ = { 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
...@@ -307,13 +288,11 @@ ...@@ -307,13 +288,11 @@
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
49C15B73243E340F0025F804 /* Embed Watch Content */,
DF3DAF4426EF33A40B49B448 /* [CP] Embed Pods Frameworks */, DF3DAF4426EF33A40B49B448 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
49C15B68243E340F0025F804 /* PBXTargetDependency */,
); );
name = Runner; name = Runner;
productName = Runner; productName = Runner;
...@@ -531,11 +510,6 @@ ...@@ -531,11 +510,6 @@
target = 49C15B55243E340E0025F804 /* watch Extension */; target = 49C15B55243E340E0025F804 /* watch Extension */;
targetProxy = 49C15B58243E340E0025F804 /* PBXContainerItemProxy */; targetProxy = 49C15B58243E340E0025F804 /* PBXContainerItemProxy */;
}; };
49C15B68243E340F0025F804 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 49C15B49243E340B0025F804 /* watch */;
targetProxy = 49C15B67243E340F0025F804 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
......
...@@ -27,8 +27,6 @@ ...@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
...@@ -38,8 +36,8 @@ ...@@ -38,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
...@@ -61,8 +59,6 @@ ...@@ -61,8 +59,6 @@
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment