@@ -10,7 +10,7 @@ import 'package:flutter_devicelab/framework/task_result.dart';
1010import 'package:flutter_devicelab/framework/utils.dart' ;
1111import 'package:path/path.dart' as path;
1212
13- /// Tests that iOS .xcframeworks can be built.
13+ /// Tests that iOS and macOS .xcframeworks can be built.
1414Future <void > main () async {
1515 await task (() async {
1616
@@ -19,7 +19,7 @@ Future<void> main() async {
1919 final Directory tempDir = Directory .systemTemp.createTempSync ('flutter_module_test.' );
2020 try {
2121 await inDirectory (tempDir, () async {
22- section ('Test module template' );
22+ section ('Test iOS module template' );
2323
2424 final Directory moduleProjectDir =
2525 Directory (path.join (tempDir.path, 'hello_module' ));
@@ -34,6 +34,7 @@ Future<void> main() async {
3434 ],
3535 );
3636
37+ await _addPlugin (moduleProjectDir);
3738 await _testBuildIosFramework (moduleProjectDir, isModule: true );
3839
3940 section ('Test app template' );
@@ -45,7 +46,9 @@ Future<void> main() async {
4546 options: < String > ['--org' , 'io.flutter.devicelab' , 'hello_project' ],
4647 );
4748
49+ await _addPlugin (projectDir);
4850 await _testBuildIosFramework (projectDir);
51+ await _testBuildMacOSFramework (projectDir);
4952 });
5053
5154 return TaskResult .success (null );
@@ -59,7 +62,7 @@ Future<void> main() async {
5962 });
6063}
6164
62- Future <void > _testBuildIosFramework (Directory projectDir, { bool isModule = false } ) async {
65+ Future <void > _addPlugin (Directory projectDir) async {
6366 section ('Add plugins' );
6467
6568 final File pubspec = File (path.join (projectDir.path, 'pubspec.yaml' ));
@@ -75,24 +78,11 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
7578 options: < String > ['get' ],
7679 );
7780 });
81+ }
7882
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 {
9484 // This builds all build modes' frameworks by default
95- section ('Build frameworks ' );
85+ section ('Build iOS app ' );
9686
9787 const String outputDirectoryName = 'flutter-frameworks' ;
9888
@@ -488,6 +478,293 @@ Future<void> _testBuildIosFramework(Directory projectDir, { bool isModule = fals
488478 }
489479}
490480
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+
491768Future <void > _checkDylib (String pathToLibrary) async {
492769 final String binaryFileType = await fileType (pathToLibrary);
493770 if (! binaryFileType.contains ('dynamically linked' )) {
@@ -529,3 +806,13 @@ Future<bool> _linksOnFlutter(String pathToBinary) async {
529806 ]);
530807 return loadCommands.contains ('Flutter.framework' );
531808}
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