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
5import 'package:file/file.dart';
6import 'package:file/memory.dart';
7import 'package:file_testing/file_testing.dart';
8import 'package:flutter_tools/src/base/logger.dart';
9import 'package:flutter_tools/src/build_info.dart';
10import 'package:flutter_tools/src/dart/pub.dart';
11import 'package:flutter_tools/src/flutter_manifest.dart';
12import 'package:flutter_tools/src/macos/cocoapod_utils.dart';
13import 'package:flutter_tools/src/macos/cocoapods.dart';
14import 'package:flutter_tools/src/project.dart';
15import 'package:test/fake.dart';
16
17import '../../src/common.dart';
18import '../../src/context.dart';
19import '../../src/fake_pub_deps.dart';
20import '../../src/package_config.dart';
21
22void main() {
23 group('processPodsIfNeeded', () {
24 late MemoryFileSystem fs;
25 late FakeCocoaPods cocoaPods;
26 late BufferLogger logger;
27
28 // Adds basic properties to the flutterProject and its subprojects.
29 void setUpProject(
30 FakeFlutterProject flutterProject,
31 MemoryFileSystem fileSystem, {
32 List<String> pluginNames = const <String>[],
33 }) {
34 flutterProject
35 ..manifest = FakeFlutterManifest()
36 ..directory = fileSystem.systemTempDirectory.childDirectory('app')
37 ..flutterPluginsFile = flutterProject.directory.childFile('.flutter-plugins')
38 ..flutterPluginsDependenciesFile = flutterProject.directory.childFile(
39 '.flutter-plugins-dependencies',
40 )
41 ..ios = FakeIosProject(fileSystem: fileSystem, parent: flutterProject)
42 ..macos = FakeMacOSProject(fileSystem: fileSystem, parent: flutterProject)
43 ..android = FakeAndroidProject()
44 ..web = FakeWebProject()
45 ..windows = FakeWindowsProject()
46 ..linux = FakeLinuxProject()
47 ..packageConfig = flutterProject.directory
48 .childDirectory('.dart_tool')
49 .childFile('package_config.json');
50
51 const String pluginYamlTemplate = '''
52 flutter:
53 plugin:
54 platforms:
55 ios:
56 pluginClass: PLUGIN_CLASS
57 macos:
58 pluginClass: PLUGIN_CLASS
59 ''';
60
61 final Directory fakePubCache = fileSystem.systemTempDirectory.childDirectory('cache');
62
63 writePackageConfigFile(
64 directory: flutterProject.directory,
65 mainLibName: 'my_app',
66 packages: <String, String>{
67 for (final String plugin in pluginNames)
68 plugin: fakePubCache.childDirectory(plugin).uri.toString(),
69 },
70 );
71
72 for (final String name in pluginNames) {
73 flutterProject.manifest.dependencies.add(name);
74 final Directory pluginDirectory = fakePubCache.childDirectory(name);
75 pluginDirectory.childFile('pubspec.yaml')
76 ..createSync(recursive: true)
77 ..writeAsStringSync(pluginYamlTemplate.replaceAll('PLUGIN_CLASS', name));
78 }
79 }
80
81 setUp(() async {
82 fs = MemoryFileSystem.test();
83 cocoaPods = FakeCocoaPods();
84 logger = BufferLogger.test();
85 });
86
87 group('for iOS', () {
88 group('using CocoaPods only', () {
89 testUsingContext(
90 'processes when there are plugins',
91 () async {
92 final FakeFlutterProject flutterProject = FakeFlutterProject();
93 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
94
95 await processPodsIfNeeded(
96 flutterProject.ios,
97 fs.currentDirectory.childDirectory('build').path,
98 BuildMode.debug,
99 );
100 expect(cocoaPods.processedPods, isTrue);
101 },
102 overrides: <Type, Generator>{
103 FileSystem: () => fs,
104 ProcessManager: FakeProcessManager.empty,
105 Pub: FakePubWithPrimedDeps.new,
106 CocoaPods: () => cocoaPods,
107 },
108 );
109
110 testUsingContext(
111 'processes when no plugins but the project is a module and podfile exists',
112 () async {
113 final FakeFlutterProject flutterProject = FakeFlutterProject();
114 setUpProject(flutterProject, fs);
115 flutterProject.isModule = true;
116 flutterProject.ios.podfile.createSync(recursive: true);
117
118 await processPodsIfNeeded(
119 flutterProject.ios,
120 fs.currentDirectory.childDirectory('build').path,
121 BuildMode.debug,
122 );
123 expect(cocoaPods.processedPods, isTrue);
124 },
125 overrides: <Type, Generator>{
126 FileSystem: () => fs,
127 ProcessManager: FakeProcessManager.empty,
128 Pub: FakePubWithPrimedDeps.new,
129 CocoaPods: () => cocoaPods,
130 },
131 );
132
133 testUsingContext(
134 "skips when no plugins and the project is a module but podfile doesn't exist",
135 () async {
136 final FakeFlutterProject flutterProject = FakeFlutterProject();
137 setUpProject(flutterProject, fs);
138 flutterProject.isModule = true;
139
140 await processPodsIfNeeded(
141 flutterProject.ios,
142 fs.currentDirectory.childDirectory('build').path,
143 BuildMode.debug,
144 );
145 expect(cocoaPods.processedPods, isFalse);
146 },
147 overrides: <Type, Generator>{
148 FileSystem: () => fs,
149 ProcessManager: FakeProcessManager.empty,
150 Pub: FakePubWithPrimedDeps.new,
151 CocoaPods: () => cocoaPods,
152 },
153 );
154
155 testUsingContext(
156 'skips when no plugins and project is not a module',
157 () async {
158 final FakeFlutterProject flutterProject = FakeFlutterProject();
159 setUpProject(flutterProject, fs);
160
161 await processPodsIfNeeded(
162 flutterProject.ios,
163 fs.currentDirectory.childDirectory('build').path,
164 BuildMode.debug,
165 );
166 expect(cocoaPods.processedPods, isFalse);
167 },
168 overrides: <Type, Generator>{
169 FileSystem: () => fs,
170 ProcessManager: FakeProcessManager.empty,
171 Pub: FakePubWithPrimedDeps.new,
172 CocoaPods: () => cocoaPods,
173 },
174 );
175 });
176
177 group('using Swift Package Manager', () {
178 testUsingContext(
179 'processes if podfile exists',
180 () async {
181 final FakeFlutterProject flutterProject = FakeFlutterProject();
182 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
183 flutterProject.ios.usesSwiftPackageManager = true;
184 flutterProject.ios.podfile.createSync(recursive: true);
185
186 await processPodsIfNeeded(
187 flutterProject.ios,
188 fs.currentDirectory.childDirectory('build').path,
189 BuildMode.debug,
190 );
191 expect(cocoaPods.processedPods, isTrue);
192 },
193 overrides: <Type, Generator>{
194 FileSystem: () => fs,
195 ProcessManager: FakeProcessManager.empty,
196 Pub: FakePubWithPrimedDeps.new,
197 CocoaPods: () => cocoaPods,
198 },
199 );
200
201 testUsingContext(
202 'skip if podfile does not exists',
203 () async {
204 final FakeFlutterProject flutterProject = FakeFlutterProject();
205 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
206 flutterProject.ios.usesSwiftPackageManager = true;
207
208 await processPodsIfNeeded(
209 flutterProject.ios,
210 fs.currentDirectory.childDirectory('build').path,
211 BuildMode.debug,
212 );
213 expect(cocoaPods.processedPods, isFalse);
214 },
215 overrides: <Type, Generator>{
216 FileSystem: () => fs,
217 ProcessManager: FakeProcessManager.empty,
218 CocoaPods: () => cocoaPods,
219 },
220 );
221
222 testUsingContext(
223 'process if podfile does not exists but forceCocoaPodsOnly is true',
224 () async {
225 final FakeFlutterProject flutterProject = FakeFlutterProject();
226 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
227 flutterProject.ios.usesSwiftPackageManager = true;
228 final File generatedManifestFile = flutterProject.ios.flutterPluginSwiftPackageManifest;
229 generatedManifestFile.createSync(recursive: true);
230
231 await processPodsIfNeeded(
232 flutterProject.ios,
233 fs.currentDirectory.childDirectory('build').path,
234 BuildMode.debug,
235 forceCocoaPodsOnly: true,
236 );
237 expect(cocoaPods.processedPods, isTrue);
238 expect(cocoaPods.podfileSetup, isTrue);
239 expect(
240 logger.warningText,
241 'Swift Package Manager does not yet support this command. '
242 'CocoaPods will be used instead.\n',
243 );
244 expect(generatedManifestFile, exists);
245 const String emptyDependencies = 'dependencies: [\n \n ],\n';
246 expect(generatedManifestFile.readAsStringSync(), contains(emptyDependencies));
247 },
248 overrides: <Type, Generator>{
249 FileSystem: () => fs,
250 ProcessManager: FakeProcessManager.empty,
251 Pub: FakePubWithPrimedDeps.new,
252 CocoaPods: () => cocoaPods,
253 Logger: () => logger,
254 },
255 );
256 });
257 });
258
259 group('for macOS', () {
260 group('using CocoaPods only', () {
261 testUsingContext(
262 'processes when there are plugins',
263 () async {
264 final FakeFlutterProject flutterProject = FakeFlutterProject();
265 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
266
267 await processPodsIfNeeded(
268 flutterProject.macos,
269 fs.currentDirectory.childDirectory('build').path,
270 BuildMode.debug,
271 );
272 expect(cocoaPods.processedPods, isTrue);
273 },
274 overrides: <Type, Generator>{
275 FileSystem: () => fs,
276 ProcessManager: FakeProcessManager.empty,
277 Pub: FakePubWithPrimedDeps.new,
278 CocoaPods: () => cocoaPods,
279 },
280 );
281
282 testUsingContext(
283 'processes when no plugins but the project is a module and podfile exists',
284 () async {
285 final FakeFlutterProject flutterProject = FakeFlutterProject();
286 setUpProject(flutterProject, fs);
287 flutterProject.isModule = true;
288 flutterProject.macos.podfile.createSync(recursive: true);
289
290 await processPodsIfNeeded(
291 flutterProject.macos,
292 fs.currentDirectory.childDirectory('build').path,
293 BuildMode.debug,
294 );
295 expect(cocoaPods.processedPods, isTrue);
296 },
297 overrides: <Type, Generator>{
298 FileSystem: () => fs,
299 ProcessManager: FakeProcessManager.empty,
300 Pub: FakePubWithPrimedDeps.new,
301 CocoaPods: () => cocoaPods,
302 },
303 );
304
305 testUsingContext(
306 "skips when no plugins and the project is a module but podfile doesn't exist",
307 () async {
308 final FakeFlutterProject flutterProject = FakeFlutterProject();
309 setUpProject(flutterProject, fs);
310 flutterProject.isModule = true;
311
312 await processPodsIfNeeded(
313 flutterProject.macos,
314 fs.currentDirectory.childDirectory('build').path,
315 BuildMode.debug,
316 );
317 expect(cocoaPods.processedPods, isFalse);
318 },
319 overrides: <Type, Generator>{
320 FileSystem: () => fs,
321 ProcessManager: FakeProcessManager.empty,
322 Pub: FakePubWithPrimedDeps.new,
323 CocoaPods: () => cocoaPods,
324 },
325 );
326
327 testUsingContext(
328 'skips when no plugins and project is not a module',
329 () async {
330 final FakeFlutterProject flutterProject = FakeFlutterProject();
331 setUpProject(flutterProject, fs);
332
333 await processPodsIfNeeded(
334 flutterProject.macos,
335 fs.currentDirectory.childDirectory('build').path,
336 BuildMode.debug,
337 );
338 expect(cocoaPods.processedPods, isFalse);
339 },
340 overrides: <Type, Generator>{
341 FileSystem: () => fs,
342 ProcessManager: FakeProcessManager.empty,
343 Pub: FakePubWithPrimedDeps.new,
344 CocoaPods: () => cocoaPods,
345 },
346 );
347 });
348
349 group('using Swift Package Manager', () {
350 testUsingContext(
351 'processes if podfile exists',
352 () async {
353 final FakeFlutterProject flutterProject = FakeFlutterProject();
354 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
355 flutterProject.macos.usesSwiftPackageManager = true;
356 flutterProject.macos.podfile.createSync(recursive: true);
357
358 await processPodsIfNeeded(
359 flutterProject.macos,
360 fs.currentDirectory.childDirectory('build').path,
361 BuildMode.debug,
362 );
363 expect(cocoaPods.processedPods, isTrue);
364 },
365 overrides: <Type, Generator>{
366 FileSystem: () => fs,
367 ProcessManager: FakeProcessManager.empty,
368 Pub: FakePubWithPrimedDeps.new,
369 CocoaPods: () => cocoaPods,
370 },
371 );
372
373 testUsingContext(
374 'skip if podfile does not exists',
375 () async {
376 final FakeFlutterProject flutterProject = FakeFlutterProject();
377 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
378 flutterProject.macos.usesSwiftPackageManager = true;
379
380 await processPodsIfNeeded(
381 flutterProject.macos,
382 fs.currentDirectory.childDirectory('build').path,
383 BuildMode.debug,
384 );
385 expect(cocoaPods.processedPods, isFalse);
386 },
387 overrides: <Type, Generator>{
388 FileSystem: () => fs,
389 ProcessManager: FakeProcessManager.empty,
390 CocoaPods: () => cocoaPods,
391 },
392 );
393
394 testUsingContext(
395 'process if podfile does not exists but forceCocoaPodsOnly is true',
396 () async {
397 final FakeFlutterProject flutterProject = FakeFlutterProject();
398 setUpProject(flutterProject, fs, pluginNames: <String>['plugin_one', 'plugin_two']);
399 flutterProject.macos.usesSwiftPackageManager = true;
400 final File generatedManifestFile =
401 flutterProject.macos.flutterPluginSwiftPackageManifest;
402 generatedManifestFile.createSync(recursive: true);
403
404 await processPodsIfNeeded(
405 flutterProject.macos,
406 fs.currentDirectory.childDirectory('build').path,
407 BuildMode.debug,
408 forceCocoaPodsOnly: true,
409 );
410 expect(cocoaPods.processedPods, isTrue);
411 expect(cocoaPods.podfileSetup, isTrue);
412 expect(
413 logger.warningText,
414 'Swift Package Manager does not yet support this command. '
415 'CocoaPods will be used instead.\n',
416 );
417
418 expect(generatedManifestFile, exists);
419 const String emptyDependencies = 'dependencies: [\n \n ],\n';
420 expect(generatedManifestFile.readAsStringSync(), contains(emptyDependencies));
421 },
422 overrides: <Type, Generator>{
423 FileSystem: () => fs,
424 ProcessManager: FakeProcessManager.empty,
425 Pub: FakePubWithPrimedDeps.new,
426 CocoaPods: () => cocoaPods,
427 Logger: () => logger,
428 },
429 );
430 });
431 });
432 });
433}
434
435class FakeFlutterManifest extends Fake implements FlutterManifest {
436 @override
437 Set<String> get dependencies => <String>{};
438}
439
440class FakeFlutterProject extends Fake implements FlutterProject {
441 @override
442 bool isModule = false;
443
444 @override
445 late FlutterManifest manifest;
446
447 @override
448 late Directory directory;
449
450 @override
451 late File flutterPluginsFile;
452
453 @override
454 late File flutterPluginsDependenciesFile;
455
456 @override
457 late FakeIosProject ios;
458
459 @override
460 late FakeMacOSProject macos;
461
462 @override
463 late AndroidProject android;
464
465 @override
466 late WebProject web;
467
468 @override
469 late LinuxProject linux;
470
471 @override
472 late WindowsProject windows;
473
474 @override
475 late File packageConfig;
476}
477
478class FakeMacOSProject extends Fake implements MacOSProject {
479 FakeMacOSProject({required MemoryFileSystem fileSystem, required this.parent})
480 : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
481
482 @override
483 String pluginConfigKey = 'macos';
484
485 @override
486 final FlutterProject parent;
487
488 @override
489 Directory hostAppRoot;
490
491 bool exists = true;
492
493 @override
494 bool existsSync() => exists;
495
496 @override
497 File get podfile => hostAppRoot.childFile('Podfile');
498
499 @override
500 File get xcodeProjectInfoFile =>
501 hostAppRoot.childDirectory('Runner.xcodeproj').childFile('project.pbxproj');
502
503 @override
504 File get flutterPluginSwiftPackageManifest => hostAppRoot
505 .childDirectory('Flutter')
506 .childDirectory('ephemeral')
507 .childDirectory('Packages')
508 .childDirectory('FlutterGeneratedPluginSwiftPackage')
509 .childFile('Package.swift');
510
511 @override
512 bool usesSwiftPackageManager = false;
513
514 @override
515 bool get flutterPluginSwiftPackageInProjectSettings => usesSwiftPackageManager;
516}
517
518class FakeIosProject extends Fake implements IosProject {
519 FakeIosProject({required MemoryFileSystem fileSystem, required this.parent})
520 : hostAppRoot = fileSystem.directory('app_name').childDirectory('ios');
521
522 @override
523 String pluginConfigKey = 'ios';
524
525 @override
526 final FlutterProject parent;
527
528 @override
529 Directory hostAppRoot;
530
531 @override
532 bool exists = true;
533
534 @override
535 bool existsSync() => exists;
536
537 @override
538 File get podfile => hostAppRoot.childFile('Podfile');
539
540 @override
541 File get xcodeProjectInfoFile =>
542 hostAppRoot.childDirectory('Runner.xcodeproj').childFile('project.pbxproj');
543
544 @override
545 File get flutterPluginSwiftPackageManifest => hostAppRoot
546 .childDirectory('Flutter')
547 .childDirectory('ephemeral')
548 .childDirectory('Packages')
549 .childDirectory('FlutterGeneratedPluginSwiftPackage')
550 .childFile('Package.swift');
551
552 @override
553 bool usesSwiftPackageManager = false;
554
555 @override
556 bool get flutterPluginSwiftPackageInProjectSettings => usesSwiftPackageManager;
557}
558
559class FakeAndroidProject extends Fake implements AndroidProject {
560 @override
561 String pluginConfigKey = 'android';
562
563 @override
564 bool existsSync() => false;
565}
566
567class FakeWebProject extends Fake implements WebProject {
568 @override
569 String pluginConfigKey = 'web';
570
571 @override
572 bool existsSync() => false;
573}
574
575class FakeWindowsProject extends Fake implements WindowsProject {
576 @override
577 String pluginConfigKey = 'windows';
578
579 @override
580 bool existsSync() => false;
581}
582
583class FakeLinuxProject extends Fake implements LinuxProject {
584 @override
585 String pluginConfigKey = 'linux';
586
587 @override
588 bool existsSync() => false;
589}
590
591class FakeCocoaPods extends Fake implements CocoaPods {
592 bool podfileSetup = false;
593 bool processedPods = false;
594
595 @override
596 Future<bool> processPods({
597 required XcodeBasedProject xcodeProject,
598 required BuildMode buildMode,
599 bool dependenciesChanged = true,
600 }) async {
601 processedPods = true;
602 return true;
603 }
604
605 @override
606 Future<void> setupPodfile(XcodeBasedProject xcodeProject) async {
607 podfileSetup = true;
608 }
609
610 @override
611 void invalidatePodInstallOutput(XcodeBasedProject xcodeProject) {}
612}
613

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com