diff --git a/dev/integration_tests/flutter_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/flutter_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 239f9868126..0c15a507add 100644
--- a/dev/integration_tests/flutter_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/dev/integration_tests/flutter_gallery/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -36,8 +36,8 @@
ReferencedContainer = "container:Runner.xcodeproj">
-
-
+
+
-
-
['Debug', 'Release']) {
+ final String buildModeLower = buildMode.toLowerCase();
+ test('flutter build macos --$buildModeLower builds a valid app', () async {
+ final String workingDirectory = fileSystem.path.join(
+ getFlutterRoot(),
+ 'dev',
+ 'integration_tests',
+ 'flutter_gallery',
+ );
+ final String flutterBin = fileSystem.path.join(
+ getFlutterRoot(),
+ 'bin',
+ 'flutter',
+ );
+
+ await processManager.run([
+ flutterBin,
+ ...getLocalEngineArguments(),
+ 'clean',
+ ], workingDirectory: workingDirectory);
+
+ final ProcessResult result = await processManager.run([
+ flutterBin,
+ ...getLocalEngineArguments(),
+ 'build',
+ 'macos',
+ '--$buildModeLower',
+ ], workingDirectory: workingDirectory);
+
+ print(result.stdout);
+ print(result.stderr);
+
+ expect(result.exitCode, 0);
+
+ final Directory outputApp = fileSystem.directory(fileSystem.path.join(
+ workingDirectory,
+ 'build',
+ 'macos',
+ 'Build',
+ 'Products',
+ buildMode,
+ 'flutter_gallery.app',
+ ));
+
+ final Directory outputAppFramework =
+ fileSystem.directory(fileSystem.path.join(
+ outputApp.path,
+ 'Contents',
+ 'Frameworks',
+ 'App.framework',
+ ));
+
+ expect(outputAppFramework.childFile('App'), exists);
+ expect(outputAppFramework.childLink('Resources'), exists);
+
+ final File vmSnapshot = fileSystem.file(fileSystem.path.join(
+ outputApp.path,
+ 'Contents',
+ 'Frameworks',
+ 'App.framework',
+ 'Resources',
+ 'flutter_assets',
+ 'vm_snapshot_data',
+ ));
+
+ expect(vmSnapshot.existsSync(), buildMode == 'Debug');
+
+ final File outputFlutterFrameworkBinary =
+ fileSystem.file(fileSystem.path.join(
+ outputApp.path,
+ 'Contents',
+ 'Frameworks',
+ 'FlutterMacOS.framework',
+ 'FlutterMacOS',
+ ));
+ expect(outputFlutterFrameworkBinary, exists);
+
+ // Archiving should contain a bitcode blob, but not building.
+ // This mimics Xcode behavior and present a developer from having to install a
+ // 300+MB app.
+ expect(
+ await containsBitcode(outputFlutterFrameworkBinary.path),
+ isFalse,
+ );
+
+ await processManager.run([
+ flutterBin,
+ ...getLocalEngineArguments(),
+ 'clean',
+ ], workingDirectory: workingDirectory);
+ }, skip: !platform.isMacOS,
+ timeout: const Timeout(Duration(minutes: 5)),
+ );
+ }
+}
+
+Future containsBitcode(String pathToBinary) async {
+ // See: https://stackoverflow.com/questions/32755775/how-to-check-a-static-library-is-built-contain-bitcode
+ final ProcessResult result = await processManager.run([
+ 'otool',
+ '-l',
+ '-arch',
+ 'arm64',
+ pathToBinary,
+ ]);
+ final String loadCommands = result.stdout as String;
+ if (!loadCommands.contains('__LLVM')) {
+ return false;
+ }
+ // Presence of the section may mean a bitcode marker was embedded (size=1), but there is no content.
+ if (!loadCommands.contains('size 0x0000000000000001')) {
+ return true;
+ }
+ // Check the false positives: size=1 wasn't referencing the __LLVM section.
+
+ bool emptyBitcodeMarkerFound = false;
+ // Section
+ // sectname __bundle
+ // segname __LLVM
+ // addr 0x003c4000
+ // size 0x0042b633
+ // offset 3932160
+ // ...
+ final List lines = LineSplitter.split(loadCommands).toList();
+ lines.asMap().forEach((int index, String line) {
+ if (line.contains('segname __LLVM') && lines.length - index - 1 > 3) {
+ final String emptyBitcodeMarker =
+ lines.skip(index - 1).take(3).firstWhere(
+ (String line) => line.contains(' size 0x0000000000000001'),
+ orElse: () => null,
+ );
+ if (emptyBitcodeMarker != null) {
+ emptyBitcodeMarkerFound = true;
+ return;
+ }
+ }
+ });
+ return !emptyBitcodeMarkerFound;
+}