| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | import 'dart:convert'; |
| 6 | |
| 7 | import 'package:flutter_tools/src/base/file_system.dart'; |
| 8 | import 'package:flutter_tools/src/base/io.dart'; |
| 9 | |
| 10 | import '../src/common.dart'; |
| 11 | import 'test_utils.dart'; |
| 12 | |
| 13 | class SwiftPackageManagerUtils { |
| 14 | static Future<void> enableSwiftPackageManager(String flutterBin, String workingDirectory) async { |
| 15 | final ProcessResult result = await processManager.run(<String>[ |
| 16 | flutterBin, |
| 17 | ...getLocalEngineArguments(), |
| 18 | 'config' , |
| 19 | '--enable-swift-package-manager' , |
| 20 | '-v' , |
| 21 | ], workingDirectory: workingDirectory); |
| 22 | expect( |
| 23 | result.exitCode, |
| 24 | 0, |
| 25 | reason: |
| 26 | 'Failed to enable Swift Package Manager: \n' |
| 27 | 'stdout: \n ${result.stdout}\n' |
| 28 | 'stderr: \n ${result.stderr}\n' , |
| 29 | ); |
| 30 | } |
| 31 | |
| 32 | static Future<void> disableSwiftPackageManager(String flutterBin, String workingDirectory) async { |
| 33 | final ProcessResult result = await processManager.run(<String>[ |
| 34 | flutterBin, |
| 35 | ...getLocalEngineArguments(), |
| 36 | 'config' , |
| 37 | '--no-enable-swift-package-manager' , |
| 38 | '-v' , |
| 39 | ], workingDirectory: workingDirectory); |
| 40 | expect( |
| 41 | result.exitCode, |
| 42 | 0, |
| 43 | reason: |
| 44 | 'Failed to disable Swift Package Manager: \n' |
| 45 | 'stdout: \n ${result.stdout}\n' |
| 46 | 'stderr: \n ${result.stderr}\n' , |
| 47 | ); |
| 48 | } |
| 49 | |
| 50 | static Future<String> createApp( |
| 51 | String flutterBin, |
| 52 | String workingDirectory, { |
| 53 | required String platform, |
| 54 | required List<String> options, |
| 55 | bool usesSwiftPackageManager = false, |
| 56 | }) async { |
| 57 | final appTemplateType = usesSwiftPackageManager ? 'spm' : 'default' ; |
| 58 | |
| 59 | final appName = ' ${platform}_ ${appTemplateType}_app' ; |
| 60 | final ProcessResult result = await processManager.run(<String>[ |
| 61 | flutterBin, |
| 62 | ...getLocalEngineArguments(), |
| 63 | 'create' , |
| 64 | '--org' , |
| 65 | 'io.flutter.devicelab' , |
| 66 | ...options, |
| 67 | appName, |
| 68 | ], workingDirectory: workingDirectory); |
| 69 | |
| 70 | expect( |
| 71 | result.exitCode, |
| 72 | 0, |
| 73 | reason: |
| 74 | 'Failed to create app: \n' |
| 75 | 'stdout: \n ${result.stdout}\n' |
| 76 | 'stderr: \n ${result.stderr}\n' , |
| 77 | ); |
| 78 | |
| 79 | return fileSystem.path.join(workingDirectory, appName); |
| 80 | } |
| 81 | |
| 82 | static Future<void> buildApp( |
| 83 | String flutterBin, |
| 84 | String workingDirectory, { |
| 85 | required List<String> options, |
| 86 | List<Pattern>? expectedLines, |
| 87 | List<String>? unexpectedLines, |
| 88 | }) async { |
| 89 | final List<Pattern> remainingExpectedLines = expectedLines ?? <Pattern>[]; |
| 90 | final unexpectedLinesFound = <String>[]; |
| 91 | final command = <String>[flutterBin, ...getLocalEngineArguments(), 'build' , ...options]; |
| 92 | |
| 93 | final ProcessResult result = await processManager.run( |
| 94 | command, |
| 95 | workingDirectory: workingDirectory, |
| 96 | ); |
| 97 | |
| 98 | final List<String> stdout = LineSplitter.split(result.stdout.toString()).toList(); |
| 99 | final List<String> stderr = LineSplitter.split(result.stderr.toString()).toList(); |
| 100 | final List<String> output = stdout + stderr; |
| 101 | for (final line in output) { |
| 102 | // Remove "[ +3 ms] " prefix |
| 103 | String trimmedLine = line.trim(); |
| 104 | if (trimmedLine.startsWith('[' )) { |
| 105 | final int prefixEndIndex = trimmedLine.indexOf(']' ); |
| 106 | if (prefixEndIndex > 0) { |
| 107 | trimmedLine = trimmedLine.substring(prefixEndIndex + 1, trimmedLine.length).trim(); |
| 108 | } |
| 109 | } |
| 110 | remainingExpectedLines.remove(trimmedLine); |
| 111 | remainingExpectedLines.removeWhere( |
| 112 | (Pattern expectedLine) => trimmedLine.contains(expectedLine), |
| 113 | ); |
| 114 | if (unexpectedLines != null) { |
| 115 | if (unexpectedLines |
| 116 | .where((String unexpectedLine) => trimmedLine.contains(unexpectedLine)) |
| 117 | .firstOrNull != |
| 118 | null) { |
| 119 | unexpectedLinesFound.add(trimmedLine); |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | expect( |
| 124 | result.exitCode, |
| 125 | 0, |
| 126 | reason: |
| 127 | 'Failed to build app for " ${command.join(' ' )}":\n' |
| 128 | 'stdout: \n ${result.stdout}\n' |
| 129 | 'stderr: \n ${result.stderr}\n' , |
| 130 | ); |
| 131 | expect( |
| 132 | remainingExpectedLines, |
| 133 | isEmpty, |
| 134 | reason: |
| 135 | 'Did not find expected lines for " ${command.join(' ' )}":\n' |
| 136 | 'stdout: \n ${result.stdout}\n' |
| 137 | 'stderr: \n ${result.stderr}\n' , |
| 138 | ); |
| 139 | expect( |
| 140 | unexpectedLinesFound, |
| 141 | isEmpty, |
| 142 | reason: |
| 143 | 'Found unexpected lines for " ${command.join(' ' )}":\n' |
| 144 | 'stdout: \n ${result.stdout}\n' |
| 145 | 'stderr: \n ${result.stderr}\n' , |
| 146 | ); |
| 147 | } |
| 148 | |
| 149 | static Future<void> cleanApp(String flutterBin, String workingDirectory) async { |
| 150 | final ProcessResult result = await processManager.run(<String>[ |
| 151 | flutterBin, |
| 152 | ...getLocalEngineArguments(), |
| 153 | 'clean' , |
| 154 | ], workingDirectory: workingDirectory); |
| 155 | expect( |
| 156 | result.exitCode, |
| 157 | 0, |
| 158 | reason: |
| 159 | 'Failed to clean app: \n' |
| 160 | 'stdout: \n ${result.stdout}\n' |
| 161 | 'stderr: \n ${result.stderr}\n' , |
| 162 | ); |
| 163 | } |
| 164 | |
| 165 | static Future<SwiftPackageManagerPlugin> createPlugin( |
| 166 | String flutterBin, |
| 167 | String workingDirectory, { |
| 168 | required String platform, |
| 169 | required String iosLanguage, |
| 170 | bool usesSwiftPackageManager = false, |
| 171 | }) async { |
| 172 | final dependencyManager = usesSwiftPackageManager ? 'spm' : 'cocoapods' ; |
| 173 | |
| 174 | // Create plugin |
| 175 | final pluginName = ' ${platform}_ ${iosLanguage}_ ${dependencyManager}_plugin' ; |
| 176 | final ProcessResult result = await processManager.run(<String>[ |
| 177 | flutterBin, |
| 178 | ...getLocalEngineArguments(), |
| 179 | 'create' , |
| 180 | '--org' , |
| 181 | 'io.flutter.devicelab' , |
| 182 | '--template=plugin' , |
| 183 | '--platforms= $platform' , |
| 184 | '-i' , |
| 185 | iosLanguage, |
| 186 | pluginName, |
| 187 | ], workingDirectory: workingDirectory); |
| 188 | |
| 189 | expect( |
| 190 | result.exitCode, |
| 191 | 0, |
| 192 | reason: |
| 193 | 'Failed to create plugin: \n' |
| 194 | 'stdout: \n ${result.stdout}\n' |
| 195 | 'stderr: \n ${result.stderr}\n' , |
| 196 | ); |
| 197 | |
| 198 | final Directory pluginDirectory = fileSystem.directory( |
| 199 | fileSystem.path.join(workingDirectory, pluginName), |
| 200 | ); |
| 201 | |
| 202 | return SwiftPackageManagerPlugin( |
| 203 | pluginName: pluginName, |
| 204 | pluginPath: pluginDirectory.path, |
| 205 | platform: platform, |
| 206 | className: |
| 207 | ' ${_capitalize(platform)}${_capitalize(iosLanguage)}${_capitalize(dependencyManager)}Plugin' , |
| 208 | );
|
| 209 | }
|
| 210 |
|
| 211 | static String _capitalize(String str) {
|
| 212 | return str[0].toUpperCase() + str.substring(1);
|
| 213 | }
|
| 214 |
|
| 215 | static void addDependency({
|
| 216 | required SwiftPackageManagerPlugin plugin,
|
| 217 | required String appDirectoryPath,
|
| 218 | }) {
|
| 219 | final File pubspec = fileSystem.file(fileSystem.path.join(appDirectoryPath, 'pubspec.yaml' ));
|
| 220 | final String pubspecContent = pubspec.readAsStringSync();
|
| 221 | pubspec.writeAsStringSync(
|
| 222 | pubspecContent.replaceFirst(
|
| 223 | '\ndependencies:\n' ,
|
| 224 | '\ndependencies:\n ${plugin.pluginName}:\n path: ${plugin.pluginPath}\n' ,
|
| 225 | ),
|
| 226 | );
|
| 227 | }
|
| 228 |
|
| 229 | static void removeDependency({
|
| 230 | required SwiftPackageManagerPlugin plugin,
|
| 231 | required String appDirectoryPath,
|
| 232 | }) {
|
| 233 | final File pubspec = fileSystem.file(fileSystem.path.join(appDirectoryPath, 'pubspec.yaml' ));
|
| 234 | final String pubspecContent = pubspec.readAsStringSync();
|
| 235 | final String updatedPubspecContent = pubspecContent.replaceFirst(
|
| 236 | '\n ${plugin.pluginName}:\n path: ${plugin.pluginPath}\n' ,
|
| 237 | '\n' ,
|
| 238 | );
|
| 239 |
|
| 240 | expect(updatedPubspecContent, isNot(pubspecContent));
|
| 241 |
|
| 242 | pubspec.writeAsStringSync(updatedPubspecContent);
|
| 243 | }
|
| 244 |
|
| 245 | static void disableSwiftPackageManagerByPubspec({required String appDirectoryPath}) {
|
| 246 | final File pubspec = fileSystem.file(fileSystem.path.join(appDirectoryPath, 'pubspec.yaml' ));
|
| 247 | final String pubspecContent = pubspec.readAsStringSync();
|
| 248 | pubspec.writeAsStringSync(
|
| 249 | pubspecContent.replaceFirst(
|
| 250 | '\n# The following section is specific to Flutter packages.\nflutter:\n' ,
|
| 251 | '\n# The following section is specific to Flutter packages.\nflutter:\n config: \n enable-swift-package-manager: false\n' ,
|
| 252 | ),
|
| 253 | );
|
| 254 | }
|
| 255 |
|
| 256 | static SwiftPackageManagerPlugin integrationTestPlugin(String platform) {
|
| 257 | final String flutterRoot = getFlutterRoot();
|
| 258 | return SwiftPackageManagerPlugin(
|
| 259 | platform: platform,
|
| 260 | pluginName: (platform == 'ios' ) ? 'integration_test' : 'integration_test_macos' ,
|
| 261 | pluginPath: (platform == 'ios' )
|
| 262 | ? fileSystem.path.join(flutterRoot, 'packages' , 'integration_test' )
|
| 263 | : fileSystem.path.join(
|
| 264 | flutterRoot,
|
| 265 | 'packages' ,
|
| 266 | 'integration_test' ,
|
| 267 | 'integration_test_macos' ,
|
| 268 | ),
|
| 269 | className: 'IntegrationTestPlugin' ,
|
| 270 | );
|
| 271 | }
|
| 272 |
|
| 273 | static List<Pattern> expectedLines({
|
| 274 | required String platform,
|
| 275 | required String appDirectoryPath,
|
| 276 | SwiftPackageManagerPlugin? cocoaPodsPlugin,
|
| 277 | SwiftPackageManagerPlugin? swiftPackagePlugin,
|
| 278 | bool swiftPackageMangerEnabled = false,
|
| 279 | bool migrated = false,
|
| 280 | }) {
|
| 281 | final frameworkName = platform == 'ios' ? 'Flutter' : 'FlutterMacOS' ;
|
| 282 | final String appPlatformDirectoryPath = fileSystem.path.join(appDirectoryPath, platform);
|
| 283 |
|
| 284 | final expectedLines = <Pattern>[];
|
| 285 | if (swiftPackageMangerEnabled) {
|
| 286 | expectedLines.addAll(<String>[
|
| 287 | 'FlutterGeneratedPluginSwiftPackage: $appPlatformDirectoryPath/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage' ,
|
| 288 | "➜ Explicit dependency on target 'FlutterGeneratedPluginSwiftPackage' in project 'FlutterGeneratedPluginSwiftPackage'" ,
|
| 289 | ]);
|
| 290 | }
|
| 291 | if (swiftPackagePlugin != null) {
|
| 292 | // If using a Swift Package plugin, but Swift Package Manager is not enabled, it falls back to being used as a CocoaPods plugin.
|
| 293 | if (swiftPackageMangerEnabled) {
|
| 294 | expectedLines.addAll(<Pattern>[
|
| 295 | RegExp(
|
| 296 | ' ${swiftPackagePlugin.pluginName}: [/private]* $appPlatformDirectoryPath/Flutter/ephemeral/Packages/.packages/ ${swiftPackagePlugin.pluginName} @ local' ,
|
| 297 | ),
|
| 298 | "➜ Explicit dependency on target ' ${swiftPackagePlugin.pluginName}' in project ' ${swiftPackagePlugin.pluginName}'" ,
|
| 299 | ]);
|
| 300 | } else {
|
| 301 | expectedLines.addAll(<String>[
|
| 302 | '-> Installing ${swiftPackagePlugin.pluginName} (0.0.1)' ,
|
| 303 | "➜ Explicit dependency on target ' ${swiftPackagePlugin.pluginName}' in project 'Pods'" ,
|
| 304 | ]);
|
| 305 | }
|
| 306 | }
|
| 307 | if (cocoaPodsPlugin != null) {
|
| 308 | expectedLines.addAll(<String>[
|
| 309 | 'Running pod install...' ,
|
| 310 | '-> Installing $frameworkName (1.0.0)' ,
|
| 311 | '-> Installing ${cocoaPodsPlugin.pluginName} (0.0.1)' ,
|
| 312 | "Target 'Pods-Runner' in project 'Pods'" ,
|
| 313 | "➜ Explicit dependency on target ' $frameworkName' in project 'Pods'" ,
|
| 314 | "➜ Explicit dependency on target ' ${cocoaPodsPlugin.pluginName}' in project 'Pods'" ,
|
| 315 | ]);
|
| 316 | }
|
| 317 | if (migrated) {
|
| 318 | expectedLines.addAll(<String>[
|
| 319 | 'Adding Swift Package Manager integration...' ,
|
| 320 | 'Running pod install...' ,
|
| 321 | "Target 'Pods-Runner' in project 'Pods'" ,
|
| 322 | ]);
|
| 323 | }
|
| 324 | return expectedLines;
|
| 325 | }
|
| 326 |
|
| 327 | static List<String> unexpectedLines({
|
| 328 | required String platform,
|
| 329 | required String appDirectoryPath,
|
| 330 | SwiftPackageManagerPlugin? cocoaPodsPlugin,
|
| 331 | SwiftPackageManagerPlugin? swiftPackagePlugin,
|
| 332 | bool swiftPackageMangerEnabled = false,
|
| 333 | bool migrated = false,
|
| 334 | }) {
|
| 335 | final frameworkName = platform == 'ios' ? 'Flutter' : 'FlutterMacOS' ;
|
| 336 | final String appPlatformDirectoryPath = fileSystem.path.join(appDirectoryPath, platform);
|
| 337 |
|
| 338 | final unexpectedLines = <String>[];
|
| 339 | if (cocoaPodsPlugin == null && !migrated) {
|
| 340 | unexpectedLines.addAll(<String>[
|
| 341 | 'Running pod install...' ,
|
| 342 | '-> Installing $frameworkName (1.0.0)' ,
|
| 343 | "Target 'Pods-Runner' in project 'Pods'" ,
|
| 344 | ]);
|
| 345 | }
|
| 346 | if (swiftPackagePlugin != null) {
|
| 347 | if (swiftPackageMangerEnabled) {
|
| 348 | unexpectedLines.addAll(<String>[
|
| 349 | '-> Installing ${swiftPackagePlugin.pluginName} (0.0.1)' ,
|
| 350 | "➜ Explicit dependency on target ' ${swiftPackagePlugin.pluginName}' in project 'Pods'" ,
|
| 351 | ]);
|
| 352 | } else {
|
| 353 | unexpectedLines.addAll(<String>[
|
| 354 | ' ${swiftPackagePlugin.pluginName}: $appPlatformDirectoryPath/Flutter/ephemeral/Packages/.packages/ ${swiftPackagePlugin.pluginName} @ local' ,
|
| 355 | "➜ Explicit dependency on target ' ${swiftPackagePlugin.pluginName}' in project ' ${swiftPackagePlugin.pluginName}'" ,
|
| 356 | ]);
|
| 357 | }
|
| 358 | }
|
| 359 | if (!migrated) {
|
| 360 | unexpectedLines.addAll(<String>['Adding Swift Package Manager integration...' ]);
|
| 361 | }
|
| 362 | return unexpectedLines;
|
| 363 | }
|
| 364 | }
|
| 365 |
|
| 366 | class SwiftPackageManagerPlugin {
|
| 367 | SwiftPackageManagerPlugin({
|
| 368 | required this.pluginName,
|
| 369 | required this.pluginPath,
|
| 370 | required this.platform,
|
| 371 | required this.className,
|
| 372 | });
|
| 373 |
|
| 374 | final String pluginName;
|
| 375 | final String pluginPath;
|
| 376 | final String platform;
|
| 377 | final String className;
|
| 378 | String get exampleAppPath => fileSystem.path.join(pluginPath, 'example' );
|
| 379 | String get exampleAppPlatformPath => fileSystem.path.join(exampleAppPath, platform);
|
| 380 | String get swiftPackagePlatformPath => fileSystem.path.join(pluginPath, platform, pluginName);
|
| 381 | }
|
| 382 |
|