diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements index dddb8a30c85..9f56413f381 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/DebugProfile.entitlements @@ -3,7 +3,7 @@ com.apple.security.app-sandbox - + com.apple.security.cs.allow-jit com.apple.security.network.server diff --git a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements index 852fa1a4728..e89b7f323cf 100644 --- a/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements +++ b/packages/pigeon/platform_tests/test_plugin/example/macos/Runner/Release.entitlements @@ -3,6 +3,6 @@ com.apple.security.app-sandbox - + diff --git a/script/tool/lib/src/common/xcode.dart b/script/tool/lib/src/common/xcode.dart index e26735df122..4a4bf03ba54 100644 --- a/script/tool/lib/src/common/xcode.dart +++ b/script/tool/lib/src/common/xcode.dart @@ -32,13 +32,21 @@ class Xcode { /// Runs an `xcodebuild` in [directory] with the given parameters. Future runXcodeBuild( - Directory directory, { + Directory exampleDirectory, + String platform, { List actions = const ['build'], required String workspace, required String scheme, String? configuration, List extraFlags = const [], }) { + File? disabledSandboxEntitlementFile; + if (actions.contains('test') && platform.toLowerCase() == 'macos') { + disabledSandboxEntitlementFile = _createDisabledSandboxEntitlementFile( + exampleDirectory.childDirectory(platform.toLowerCase()), + configuration ?? 'Debug', + ); + } final List args = [ _xcodeBuildCommand, ...actions, @@ -46,13 +54,15 @@ class Xcode { ...['-scheme', scheme], if (configuration != null) ...['-configuration', configuration], ...extraFlags, + if (disabledSandboxEntitlementFile != null) + 'CODE_SIGN_ENTITLEMENTS=${disabledSandboxEntitlementFile.path}', ]; final String completeTestCommand = '$_xcRunCommand ${args.join(' ')}'; if (log) { print(completeTestCommand); } return processRunner.runAndStream(_xcRunCommand, args, - workingDir: directory); + workingDir: exampleDirectory); } /// Returns true if [project], which should be an .xcodeproj directory, @@ -156,4 +166,48 @@ class Xcode { } return null; } + + /// Finds and copies macOS entitlements file. In the copy, disables sandboxing. + /// If entitlements file is not found, returns null. + /// + /// As of macOS 14, testing a macOS sandbox app may prompt the user to grant + /// access to the app. To workaround this in CI, we create and use a entitlements + /// file with sandboxing disabled. See + /// https://developer.apple.com/documentation/security/app_sandbox/accessing_files_from_the_macos_app_sandbox. + File? _createDisabledSandboxEntitlementFile( + Directory macOSDirectory, + String configuration, + ) { + final String entitlementDefaultFileName = + configuration == 'Release' ? 'Release' : 'DebugProfile'; + + final File entitlementFile = macOSDirectory + .childDirectory('Runner') + .childFile('$entitlementDefaultFileName.entitlements'); + + if (!entitlementFile.existsSync()) { + print('Unable to find entitlements file at ${entitlementFile.path}'); + return null; + } + + final String originalEntitlementFileContents = + entitlementFile.readAsStringSync(); + final File disabledSandboxEntitlementFile = macOSDirectory + .fileSystem.systemTempDirectory + .createTempSync('flutter_disable_sandbox_entitlement.') + .childFile( + '${entitlementDefaultFileName}WithDisabledSandboxing.entitlements'); + disabledSandboxEntitlementFile.createSync(recursive: true); + disabledSandboxEntitlementFile.writeAsStringSync( + originalEntitlementFileContents.replaceAll( + RegExp( + r'com\.apple\.security\.app-sandbox<\/key>[\S\s]*?'), + ''' +com.apple.security.app-sandbox + ''', + ), + ); + + return disabledSandboxEntitlementFile; + } } diff --git a/script/tool/lib/src/native_test_command.dart b/script/tool/lib/src/native_test_command.dart index 20eef521de2..53bf41298d8 100644 --- a/script/tool/lib/src/native_test_command.dart +++ b/script/tool/lib/src/native_test_command.dart @@ -465,6 +465,7 @@ this command. _printRunningExampleTestsMessage(example, platform); final int exitCode = await _xcode.runXcodeBuild( example.directory, + platform, // Clean before testing to remove cached swiftmodules from previous // runs, which can cause conflicts. actions: ['clean', 'test'], diff --git a/script/tool/lib/src/xcode_analyze_command.dart b/script/tool/lib/src/xcode_analyze_command.dart index 6760eeeff8e..707a3f08b6d 100644 --- a/script/tool/lib/src/xcode_analyze_command.dart +++ b/script/tool/lib/src/xcode_analyze_command.dart @@ -111,6 +111,7 @@ class XcodeAnalyzeCommand extends PackageLoopingCommand { print('Running $platform tests and analyzer for $examplePath...'); final int exitCode = await _xcode.runXcodeBuild( example.directory, + platform, // Clean before analyzing to remove cached swiftmodules from previous // runs, which can cause conflicts. actions: ['clean', 'analyze'], diff --git a/script/tool/test/common/xcode_test.dart b/script/tool/test/common/xcode_test.dart index b401287e15a..8fd41516001 100644 --- a/script/tool/test/common/xcode_test.dart +++ b/script/tool/test/common/xcode_test.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/local.dart'; +import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/xcode.dart'; import 'package:test/test.dart'; @@ -161,6 +162,7 @@ void main() { final int exitCode = await xcode.runXcodeBuild( directory, + 'ios', workspace: 'A.xcworkspace', scheme: 'AScheme', ); @@ -186,7 +188,7 @@ void main() { test('handles all arguments', () async { final Directory directory = const LocalFileSystem().currentDirectory; - final int exitCode = await xcode.runXcodeBuild(directory, + final int exitCode = await xcode.runXcodeBuild(directory, 'ios', actions: ['action1', 'action2'], workspace: 'A.xcworkspace', scheme: 'AScheme', @@ -225,6 +227,7 @@ void main() { final int exitCode = await xcode.runXcodeBuild( directory, + 'ios', workspace: 'A.xcworkspace', scheme: 'AScheme', ); @@ -246,6 +249,42 @@ void main() { directory.path), ])); }); + + test('sets CODE_SIGN_ENTITLEMENTS for macos tests', () async { + final FileSystem fileSystem = MemoryFileSystem(); + final Directory directory = fileSystem.currentDirectory; + directory + .childDirectory('macos') + .childDirectory('Runner') + .childFile('DebugProfile.entitlements') + .createSync(recursive: true); + + final int exitCode = await xcode.runXcodeBuild( + directory, + 'macos', + workspace: 'A.xcworkspace', + scheme: 'AScheme', + actions: ['test'], + ); + + expect(exitCode, 0); + expect( + processRunner.recordedCalls, + orderedEquals([ + ProcessCall( + 'xcrun', + const [ + 'xcodebuild', + 'test', + '-workspace', + 'A.xcworkspace', + '-scheme', + 'AScheme', + 'CODE_SIGN_ENTITLEMENTS=/.tmp_rand0/flutter_disable_sandbox_entitlement.rand0/DebugProfileWithDisabledSandboxing.entitlements' + ], + directory.path), + ])); + }); }); group('projectHasTarget', () {