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 |
|