@@ -10,7 +10,7 @@ import 'package:flutter_devicelab/framework/task_result.dart';
10
10
import 'package:flutter_devicelab/framework/utils.dart' ;
11
11
import 'package:path/path.dart' as path;
12
12
13
- /// Tests that iOS .xcframeworks can be built.
13
+ /// Tests that iOS and macOS .xcframeworks can be built.
14
14
Future <void > main () async {
15
15
await task (() async {
16
16
@@ -19,7 +19,7 @@ Future<void> main() async {
19
19
final Directory tempDir = Directory .systemTemp.createTempSync ('flutter_module_test.' );
20
20
try {
21
21
await inDirectory (tempDir, () async {
22
- section ('Test module template' );
22
+ section ('Test iOS module template' );
23
23
24
24
final Directory moduleProjectDir =
25
25
Directory (path.join (tempDir.path, 'hello_module' ));
@@ -34,6 +34,7 @@ Future<void> main() async {
34
34
],
35
35
);
36
36
37
+ await _addPlugin (moduleProjectDir);
37
38
await _testBuildIosFramework (moduleProjectDir, isModule: true );
38
39
39
40
section ('Test app template' );
@@ -45,7 +46,9 @@ Future<void> main() async {
45
46
options: < String > ['--org' , 'io.flutter.devicelab' , 'hello_project' ],
46
47
);
47
48
49
+ await _addPlugin (projectDir);
48
50
await _testBuildIosFramework (projectDir);
51
+ await _testBuildMacOSFramework (projectDir);
49
52
});
50
53
51
54
return TaskResult .success (null );
@@ -59,7 +62,7 @@ Future<void> main() async {
59
62
});
60
63
}
61
64
62
- Future <void > _testBuildIosFramework (Directory projectDir, { bool isModule = false } ) async {
65
+ Future <void > _addPlugin (Directory projectDir) async {
63
66
section ('Add plugins' );
64
67
65
68
final File pubspec = File (path.join (projectDir.path, 'pubspec.yaml' ));
@@ -75,24 +78,11 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
75
78
options: < String > ['get' ],
76
79
);
77
80
});
81
+ }
78
82
79
- // First, build the module in Debug to copy the debug version of Flutter.xcframework.
80
- // This proves "flutter build ios-framework" re-copies the relevant Flutter.xcframework,
81
- // otherwise building plugins with bitcode will fail linking because the debug version
82
- // of Flutter.xcframework does not contain bitcode.
83
- await inDirectory (projectDir, () async {
84
- await flutter (
85
- 'build' ,
86
- options: < String > [
87
- 'ios' ,
88
- '--debug' ,
89
- '--no-codesign' ,
90
- ],
91
- );
92
- });
93
-
83
+ Future <void > _testBuildIosFramework (Directory projectDir, { bool isModule = false }) async {
94
84
// This builds all build modes' frameworks by default
95
- section ('Build frameworks ' );
85
+ section ('Build iOS app ' );
96
86
97
87
const String outputDirectoryName = 'flutter-frameworks' ;
98
88
@@ -488,6 +478,293 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
488
478
}
489
479
}
490
480
481
+
482
+ Future <void > _testBuildMacOSFramework (Directory projectDir) async {
483
+ // This builds all build modes' frameworks by default
484
+ section ('Build macOS frameworks' );
485
+
486
+ const String outputDirectoryName = 'flutter-frameworks' ;
487
+
488
+ await inDirectory (projectDir, () async {
489
+ await flutter (
490
+ 'build' ,
491
+ options: < String > [
492
+ 'macos-framework' ,
493
+ '--verbose' ,
494
+ '--output=$outputDirectoryName ' ,
495
+ '--obfuscate' ,
496
+ '--split-debug-info=symbols' ,
497
+ ],
498
+ );
499
+ });
500
+
501
+ final String outputPath = path.join (projectDir.path, outputDirectoryName);
502
+ final String flutterFramework = path.join (
503
+ outputPath,
504
+ 'Debug' ,
505
+ 'FlutterMacOS.xcframework' ,
506
+ 'macos-arm64_x86_64' ,
507
+ 'FlutterMacOS.framework' ,
508
+ );
509
+ checkDirectoryExists (flutterFramework);
510
+
511
+ final String debugAppFrameworkPath = path.join (
512
+ outputPath,
513
+ 'Debug' ,
514
+ 'App.xcframework' ,
515
+ 'macos-arm64_x86_64' ,
516
+ 'App.framework' ,
517
+ 'App' ,
518
+ );
519
+ checkSymlinkExists (debugAppFrameworkPath);
520
+
521
+ checkFileExists (path.join (
522
+ outputPath,
523
+ 'Debug' ,
524
+ 'App.xcframework' ,
525
+ 'macos-arm64_x86_64' ,
526
+ 'App.framework' ,
527
+ 'Resources' ,
528
+ 'Info.plist' ,
529
+ ));
530
+
531
+ section ('Check debug build has Dart snapshot as asset' );
532
+
533
+ checkFileExists (path.join (
534
+ outputPath,
535
+ 'Debug' ,
536
+ 'App.xcframework' ,
537
+ 'macos-arm64_x86_64' ,
538
+ 'App.framework' ,
539
+ 'Resources' ,
540
+ 'flutter_assets' ,
541
+ 'vm_snapshot_data' ,
542
+ ));
543
+
544
+ section ('Check obfuscation symbols' );
545
+
546
+ checkFileExists (path.join (
547
+ projectDir.path,
548
+ 'symbols' ,
549
+ 'app.darwin-arm64.symbols' ,
550
+ ));
551
+
552
+ checkFileExists (path.join (
553
+ projectDir.path,
554
+ 'symbols' ,
555
+ 'app.darwin-x86_64.symbols' ,
556
+ ));
557
+
558
+ section ('Check debug build has no Dart AOT' );
559
+
560
+ final String aotSymbols = await _dylibSymbols (debugAppFrameworkPath);
561
+
562
+ if (aotSymbols.contains ('architecture' ) ||
563
+ aotSymbols.contains ('_kDartVmSnapshot' )) {
564
+ throw TaskResult .failure ('Debug App.framework contains AOT' );
565
+ }
566
+
567
+ section ('Check profile, release builds has Dart AOT dylib' );
568
+
569
+ for (final String mode in < String > ['Profile' , 'Release' ]) {
570
+ final String appFrameworkPath = path.join (
571
+ outputPath,
572
+ mode,
573
+ 'App.xcframework' ,
574
+ 'macos-arm64_x86_64' ,
575
+ 'App.framework' ,
576
+ 'App' ,
577
+ );
578
+
579
+ await _checkDylib (appFrameworkPath);
580
+
581
+ final String aotSymbols = await _dylibSymbols (appFrameworkPath);
582
+
583
+ if (! aotSymbols.contains ('_kDartVmSnapshot' )) {
584
+ throw TaskResult .failure ('$mode App.framework missing Dart AOT' );
585
+ }
586
+
587
+ checkFileNotExists (path.join (
588
+ outputPath,
589
+ mode,
590
+ 'App.xcframework' ,
591
+ 'macos-arm64_x86_64' ,
592
+ 'App.framework' ,
593
+ 'Resources' ,
594
+ 'flutter_assets' ,
595
+ 'vm_snapshot_data' ,
596
+ ));
597
+
598
+ checkFileExists (path.join (
599
+ outputPath,
600
+ mode,
601
+ 'App.xcframework' ,
602
+ 'macos-arm64_x86_64' ,
603
+ 'App.framework' ,
604
+ 'Resources' ,
605
+ 'Info.plist' ,
606
+ ));
607
+ }
608
+
609
+ section ("Check all modes' engine dylib" );
610
+
611
+ for (final String mode in < String > ['Debug' , 'Profile' , 'Release' ]) {
612
+ final String engineBinary = path.join (
613
+ outputPath,
614
+ mode,
615
+ 'FlutterMacOS.xcframework' ,
616
+ 'macos-arm64_x86_64' ,
617
+ 'FlutterMacOS.framework' ,
618
+ 'FlutterMacOS' ,
619
+ );
620
+ checkSymlinkExists (engineBinary);
621
+
622
+ checkFileExists (path.join (
623
+ outputPath,
624
+ mode,
625
+ 'FlutterMacOS.xcframework' ,
626
+ 'macos-arm64_x86_64' ,
627
+ 'FlutterMacOS.framework' ,
628
+ 'Headers' ,
629
+ 'FlutterMacOS.h' ,
630
+ ));
631
+ }
632
+
633
+ section ('Check all modes have plugins' );
634
+
635
+ for (final String mode in < String > ['Debug' , 'Profile' , 'Release' ]) {
636
+ final String pluginFrameworkPath = path.join (
637
+ outputPath,
638
+ mode,
639
+ 'connectivity_macos.xcframework' ,
640
+ 'macos-arm64_x86_64' ,
641
+ 'connectivity_macos.framework' ,
642
+ 'connectivity_macos' ,
643
+ );
644
+
645
+ await _checkDylib (pluginFrameworkPath);
646
+ if (! await _linksOnFlutterMacOS (pluginFrameworkPath)) {
647
+ throw TaskResult .failure ('$pluginFrameworkPath does not link on Flutter' );
648
+ }
649
+
650
+ final String transitiveDependencyFrameworkPath = path.join (
651
+ outputPath,
652
+ mode,
653
+ 'Reachability.xcframework' ,
654
+ 'macos-arm64_x86_64' ,
655
+ 'Reachability.framework' ,
656
+ 'Reachability' ,
657
+ );
658
+ if (await _linksOnFlutterMacOS (transitiveDependencyFrameworkPath)) {
659
+ throw TaskResult .failure ('Transitive dependency $transitiveDependencyFrameworkPath unexpectedly links on Flutter' );
660
+ }
661
+
662
+ checkFileExists (path.join (
663
+ outputPath,
664
+ mode,
665
+ 'connectivity_macos.xcframework' ,
666
+ 'macos-arm64_x86_64' ,
667
+ 'connectivity_macos.framework' ,
668
+ 'Headers' ,
669
+ 'connectivity_macos-Swift.h' ,
670
+ ));
671
+
672
+ checkDirectoryExists (path.join (
673
+ outputPath,
674
+ mode,
675
+ 'connectivity_macos.xcframework' ,
676
+ 'macos-arm64_x86_64' ,
677
+ 'connectivity_macos.framework' ,
678
+ 'Modules' ,
679
+ 'connectivity_macos.swiftmodule' ,
680
+ ));
681
+
682
+ if (mode != 'Debug' ) {
683
+ checkDirectoryExists (path.join (
684
+ outputPath,
685
+ mode,
686
+ 'connectivity_macos.xcframework' ,
687
+ 'macos-arm64_x86_64' ,
688
+ 'dSYMs' ,
689
+ 'connectivity_macos.framework.dSYM' ,
690
+ ));
691
+ }
692
+
693
+ checkSymlinkExists (path.join (
694
+ outputPath,
695
+ mode,
696
+ 'connectivity_macos.xcframework' ,
697
+ 'macos-arm64_x86_64' ,
698
+ 'connectivity_macos.framework' ,
699
+ 'connectivity_macos' ,
700
+ ));
701
+ }
702
+
703
+ // This builds all build modes' frameworks by default
704
+ section ('Build podspec and static plugins' );
705
+
706
+ const String cocoapodsOutputDirectoryName = 'flutter-frameworks-cocoapods' ;
707
+
708
+ await inDirectory (projectDir, () async {
709
+ await flutter (
710
+ 'build' ,
711
+ options: < String > [
712
+ 'macos-framework' ,
713
+ '--cocoapods' ,
714
+ '--force' , // Allow podspec creation on master.
715
+ '--output=$cocoapodsOutputDirectoryName ' ,
716
+ '--static' ,
717
+ ],
718
+ );
719
+ });
720
+
721
+ final String cocoapodsOutputPath = path.join (projectDir.path, cocoapodsOutputDirectoryName);
722
+ for (final String mode in < String > ['Debug' , 'Profile' , 'Release' ]) {
723
+ checkFileExists (path.join (
724
+ cocoapodsOutputPath,
725
+ mode,
726
+ 'FlutterMacOS.podspec' ,
727
+ ));
728
+ await _checkDylib (path.join (
729
+ cocoapodsOutputPath,
730
+ mode,
731
+ 'App.xcframework' ,
732
+ 'macos-arm64_x86_64' ,
733
+ 'App.framework' ,
734
+ 'App' ,
735
+ ));
736
+
737
+ await _checkStatic (path.join (
738
+ cocoapodsOutputPath,
739
+ mode,
740
+ 'package_info.xcframework' ,
741
+ 'macos-arm64_x86_64' ,
742
+ 'package_info.framework' ,
743
+ 'package_info' ,
744
+ ));
745
+
746
+ await _checkStatic (path.join (
747
+ cocoapodsOutputPath,
748
+ mode,
749
+ 'connectivity_macos.xcframework' ,
750
+ 'macos-arm64_x86_64' ,
751
+ 'connectivity_macos.framework' ,
752
+ 'connectivity_macos' ,
753
+ ));
754
+
755
+ checkDirectoryExists (path.join (
756
+ cocoapodsOutputPath,
757
+ mode,
758
+ 'Reachability.xcframework' ,
759
+ ));
760
+ }
761
+
762
+ checkFileExists (path.join (
763
+ outputPath,
764
+ 'GeneratedPluginRegistrant.swift' ,
765
+ ));
766
+ }
767
+
491
768
Future <void > _checkDylib (String pathToLibrary) async {
492
769
final String binaryFileType = await fileType (pathToLibrary);
493
770
if (! binaryFileType.contains ('dynamically linked' )) {
@@ -529,3 +806,13 @@ Future<bool> _linksOnFlutter(String pathToBinary) async {
529
806
]);
530
807
return loadCommands.contains ('Flutter.framework' );
531
808
}
809
+
810
+ Future <bool > _linksOnFlutterMacOS (String pathToBinary) async {
811
+ final String loadCommands = await eval ('otool' , < String > [
812
+ '-l' ,
813
+ '-arch' ,
814
+ 'arm64' ,
815
+ pathToBinary,
816
+ ]);
817
+ return loadCommands.contains ('FlutterMacOS.framework' );
818
+ }
0 commit comments