Remove codesign command from conductor. (#141044)
Codesigning is now automated and the codesigning tests have been migrated to shard tests.
This commit is contained in:
parent
4d97bd7799
commit
f18e61a5e8
@ -90,13 +90,6 @@ Once a PR is opened, the user must validate CI builds. If there are regressions
|
||||
output of the failing test), then the user must fix these tests in their local
|
||||
checkout and push their changes again.
|
||||
|
||||
### Codesign Engine Binaries
|
||||
|
||||
The user must validate post-submit CI builds for their merged engine PR have
|
||||
passed. A link to the web dashboard is available via `conductor status`. Once
|
||||
the post-submit CI builds have all passed, the user must codesign engine
|
||||
binaries for the **merged** engine commit.
|
||||
|
||||
### Apply Framework Cherrypicks
|
||||
|
||||
The tool will attempt to auto-apply all framework cherrypicks. However, any
|
||||
|
@ -46,10 +46,6 @@ Future<void> main(List<String> args) async {
|
||||
)).trim();
|
||||
|
||||
<Command<void>>[
|
||||
CodesignCommand(
|
||||
checkouts: checkouts,
|
||||
flutterRoot: _localFlutterRoot,
|
||||
),
|
||||
StatusCommand(
|
||||
checkouts: checkouts,
|
||||
),
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
export 'src/candidates.dart';
|
||||
export 'src/clean.dart';
|
||||
export 'src/codesign.dart';
|
||||
export 'src/git.dart';
|
||||
export 'src/globals.dart';
|
||||
export 'src/next.dart' hide kStateOption, kYesFlag;
|
||||
|
@ -1,431 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:meta/meta.dart' show visibleForTesting;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import './globals.dart';
|
||||
import './repository.dart';
|
||||
import './stdio.dart';
|
||||
|
||||
const List<String> expectedEntitlements = <String>[
|
||||
'com.apple.security.cs.allow-jit',
|
||||
'com.apple.security.cs.allow-unsigned-executable-memory',
|
||||
'com.apple.security.cs.allow-dyld-environment-variables',
|
||||
'com.apple.security.network.client',
|
||||
'com.apple.security.network.server',
|
||||
'com.apple.security.cs.disable-library-validation',
|
||||
];
|
||||
|
||||
const String kVerify = 'verify';
|
||||
const String kSignatures = 'signatures';
|
||||
const String kRevision = 'revision';
|
||||
const String kUpstream = 'upstream';
|
||||
|
||||
|
||||
/// Command to codesign and verify the signatures of cached binaries.
|
||||
class CodesignCommand extends Command<void> {
|
||||
CodesignCommand({
|
||||
required this.checkouts,
|
||||
required this.flutterRoot,
|
||||
FrameworkRepository? framework,
|
||||
}) : fileSystem = checkouts.fileSystem,
|
||||
platform = checkouts.platform,
|
||||
stdio = checkouts.stdio,
|
||||
processManager = checkouts.processManager {
|
||||
if (framework != null) {
|
||||
_framework = framework;
|
||||
}
|
||||
argParser.addFlag(
|
||||
kVerify,
|
||||
help: 'Only verify expected binaries exist and are codesigned with entitlements.',
|
||||
);
|
||||
argParser.addFlag(
|
||||
kSignatures,
|
||||
defaultsTo: true,
|
||||
help: 'When off, this command will only verify the existence of binaries, and not their\n'
|
||||
'signatures or entitlements. Must be used with --verify flag.',
|
||||
);
|
||||
argParser.addOption(
|
||||
kUpstream,
|
||||
defaultsTo: FrameworkRepository.defaultUpstream,
|
||||
help: "The git remote URL to use as the Flutter framework's upstream.",
|
||||
);
|
||||
argParser.addOption(
|
||||
kRevision,
|
||||
help: 'The Flutter framework revision to use.',
|
||||
);
|
||||
}
|
||||
|
||||
final Checkouts checkouts;
|
||||
final FileSystem fileSystem;
|
||||
final Platform platform;
|
||||
final ProcessManager processManager;
|
||||
final Stdio stdio;
|
||||
|
||||
/// Root directory of the Flutter repository.
|
||||
final Directory flutterRoot;
|
||||
|
||||
FrameworkRepository? _framework;
|
||||
FrameworkRepository get framework {
|
||||
return _framework ??= FrameworkRepository(
|
||||
checkouts,
|
||||
upstreamRemote: Remote(
|
||||
name: RemoteName.upstream,
|
||||
url: argResults![kUpstream] as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => 'codesign';
|
||||
|
||||
@override
|
||||
String get description =>
|
||||
'For codesigning and verifying the signatures of engine binaries.';
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
if (!platform.isMacOS) {
|
||||
throw ConductorException(
|
||||
'Error! Expected operating system "macos", actual operating system is: '
|
||||
'"${platform.operatingSystem}"',
|
||||
);
|
||||
}
|
||||
|
||||
if (!(argResults!['verify'] as bool)) {
|
||||
throw ConductorException(
|
||||
'Sorry, but codesigning is not implemented yet. Please pass the '
|
||||
'--$kVerify flag to verify signatures.',
|
||||
);
|
||||
}
|
||||
|
||||
String revision;
|
||||
if (argResults!.wasParsed(kRevision)) {
|
||||
stdio.printWarning(
|
||||
'Warning! When providing an arbitrary revision, the contents of the cache may not '
|
||||
'match the expected binaries in the conductor tool. It is preferred to check out '
|
||||
'the desired revision and run that version of the conductor.\n',
|
||||
);
|
||||
revision = argResults![kRevision] as String;
|
||||
} else {
|
||||
revision = ((await processManager.run(
|
||||
<String>['git', 'rev-parse', 'HEAD'],
|
||||
workingDirectory: flutterRoot.path,
|
||||
)).stdout as String).trim();
|
||||
assert(revision.isNotEmpty);
|
||||
}
|
||||
|
||||
await framework.checkout(revision);
|
||||
|
||||
// Ensure artifacts present
|
||||
final io.ProcessResult result = await framework.runFlutter(
|
||||
<String>['precache', '--android', '--ios', '--macos'],
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
stdio.printError(
|
||||
'flutter precache: exitCode: ${result.exitCode}\n'
|
||||
'stdout:\n${result.stdout}\nstderr:\n${result.stderr}',
|
||||
);
|
||||
}
|
||||
|
||||
await verifyExist();
|
||||
if (argResults![kSignatures] as bool) {
|
||||
await verifySignatures();
|
||||
}
|
||||
}
|
||||
|
||||
/// Binaries that are expected to be codesigned and have entitlements.
|
||||
///
|
||||
/// This list should be kept in sync with the actual contents of Flutter's
|
||||
/// cache.
|
||||
Future<List<String>> get binariesWithEntitlements async {
|
||||
final String frameworkCacheDirectory = await framework.cacheDirectory;
|
||||
return <String>[
|
||||
'artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/android-arm-release/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/android-arm64-profile/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/android-arm64-release/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/android-x64-profile/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/android-x64-release/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/darwin-x64-profile/gen_snapshot',
|
||||
'artifacts/engine/darwin-x64-profile/gen_snapshot_arm64',
|
||||
'artifacts/engine/darwin-x64-profile/gen_snapshot_x64',
|
||||
'artifacts/engine/darwin-x64-release/gen_snapshot',
|
||||
'artifacts/engine/darwin-x64-release/gen_snapshot_arm64',
|
||||
'artifacts/engine/darwin-x64-release/gen_snapshot_x64',
|
||||
'artifacts/engine/darwin-x64/flutter_tester',
|
||||
'artifacts/engine/darwin-x64/gen_snapshot',
|
||||
'artifacts/engine/darwin-x64/gen_snapshot_arm64',
|
||||
'artifacts/engine/darwin-x64/gen_snapshot_x64',
|
||||
'artifacts/engine/ios-profile/gen_snapshot_arm64',
|
||||
'artifacts/engine/ios-release/gen_snapshot_arm64',
|
||||
'artifacts/engine/ios/gen_snapshot_arm64',
|
||||
'artifacts/libimobiledevice/idevicescreenshot',
|
||||
'artifacts/libimobiledevice/idevicesyslog',
|
||||
'artifacts/libimobiledevice/libimobiledevice-1.0.6.dylib',
|
||||
'artifacts/libplist/libplist-2.0.3.dylib',
|
||||
'artifacts/openssl/libcrypto.1.1.dylib',
|
||||
'artifacts/openssl/libssl.1.1.dylib',
|
||||
'artifacts/usbmuxd/iproxy',
|
||||
'artifacts/usbmuxd/libusbmuxd-2.0.6.dylib',
|
||||
'dart-sdk/bin/dart',
|
||||
'dart-sdk/bin/dartaotruntime',
|
||||
'dart-sdk/bin/utils/gen_snapshot',
|
||||
'dart-sdk/bin/utils/wasm-opt',
|
||||
]
|
||||
.map((String relativePath) =>
|
||||
fileSystem.path.join(frameworkCacheDirectory, relativePath))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Binaries that are only expected to be codesigned.
|
||||
///
|
||||
/// This list should be kept in sync with the actual contents of Flutter's
|
||||
/// cache.
|
||||
Future<List<String>> get binariesWithoutEntitlements async {
|
||||
final String frameworkCacheDirectory = await framework.cacheDirectory;
|
||||
return <String>[
|
||||
'artifacts/engine/darwin-x64-profile/FlutterMacOS.framework/Versions/A/FlutterMacOS',
|
||||
'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS',
|
||||
'artifacts/engine/darwin-x64/FlutterMacOS.framework/Versions/A/FlutterMacOS',
|
||||
'artifacts/engine/darwin-x64/font-subset',
|
||||
'artifacts/engine/darwin-x64/impellerc',
|
||||
'artifacts/engine/darwin-x64/libpath_ops.dylib',
|
||||
'artifacts/engine/darwin-x64/libtessellator.dylib',
|
||||
'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-profile/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios-release/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios/extension_safe/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/ios-deploy/ios-deploy',
|
||||
]
|
||||
.map((String relativePath) =>
|
||||
fileSystem.path.join(frameworkCacheDirectory, relativePath))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Verify the existence of all expected binaries in cache.
|
||||
///
|
||||
/// This function ignores code signatures and entitlements, and is intended to
|
||||
/// be run on every commit. It should throw if either new binaries are added
|
||||
/// to the cache or expected binaries removed. In either case, this class'
|
||||
/// [binariesWithEntitlements] or [binariesWithoutEntitlements] lists should
|
||||
/// be updated accordingly.
|
||||
@visibleForTesting
|
||||
Future<void> verifyExist() async {
|
||||
final Set<String> foundFiles = <String>{};
|
||||
for (final String binaryPath
|
||||
in await findBinaryPaths(await framework.cacheDirectory)) {
|
||||
if ((await binariesWithEntitlements).contains(binaryPath)) {
|
||||
foundFiles.add(binaryPath);
|
||||
} else if ((await binariesWithoutEntitlements).contains(binaryPath)) {
|
||||
foundFiles.add(binaryPath);
|
||||
} else {
|
||||
throw ConductorException(
|
||||
'Found unexpected binary in cache: $binaryPath');
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> allExpectedFiles =
|
||||
(await binariesWithEntitlements) + (await binariesWithoutEntitlements);
|
||||
if (foundFiles.length < allExpectedFiles.length) {
|
||||
final List<String> unfoundFiles = allExpectedFiles
|
||||
.where(
|
||||
(String file) => !foundFiles.contains(file),
|
||||
)
|
||||
.toList();
|
||||
stdio.printError(
|
||||
'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n\n'
|
||||
'If this commit is removing binaries from the cache, this test should be fixed by\n'
|
||||
'removing the relevant entry from either the "binariesWithEntitlements" or\n'
|
||||
'"binariesWithoutEntitlements" getters in dev/tools/lib/codesign.dart.',
|
||||
);
|
||||
throw ConductorException('Did not find all expected binaries!');
|
||||
}
|
||||
|
||||
stdio.printStatus('All expected binaries present.');
|
||||
}
|
||||
|
||||
/// Verify code signatures and entitlements of all binaries in the cache.
|
||||
@visibleForTesting
|
||||
Future<void> verifySignatures() async {
|
||||
final List<String> unsignedBinaries = <String>[];
|
||||
final List<String> wrongEntitlementBinaries = <String>[];
|
||||
final List<String> unexpectedBinaries = <String>[];
|
||||
for (final String binaryPath
|
||||
in await findBinaryPaths(await framework.cacheDirectory)) {
|
||||
bool verifySignature = false;
|
||||
bool verifyEntitlements = false;
|
||||
if ((await binariesWithEntitlements).contains(binaryPath)) {
|
||||
verifySignature = true;
|
||||
verifyEntitlements = true;
|
||||
}
|
||||
if ((await binariesWithoutEntitlements).contains(binaryPath)) {
|
||||
verifySignature = true;
|
||||
}
|
||||
if (!verifySignature && !verifyEntitlements) {
|
||||
unexpectedBinaries.add(binaryPath);
|
||||
stdio.printError('Unexpected binary $binaryPath found in cache!');
|
||||
continue;
|
||||
}
|
||||
stdio.printTrace('Verifying the code signature of $binaryPath');
|
||||
final io.ProcessResult codeSignResult = await processManager.run(
|
||||
<String>[
|
||||
'codesign',
|
||||
'-vvv',
|
||||
binaryPath,
|
||||
],
|
||||
);
|
||||
if (codeSignResult.exitCode != 0) {
|
||||
unsignedBinaries.add(binaryPath);
|
||||
stdio.printError(
|
||||
'File "$binaryPath" does not appear to be codesigned.\n'
|
||||
'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n'
|
||||
'${codeSignResult.stderr}\n',
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (verifyEntitlements) {
|
||||
stdio.printTrace('Verifying entitlements of $binaryPath');
|
||||
if (!(await hasExpectedEntitlements(binaryPath))) {
|
||||
wrongEntitlementBinaries.add(binaryPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First print all deviations from expectations
|
||||
if (unsignedBinaries.isNotEmpty) {
|
||||
stdio.printError('Found ${unsignedBinaries.length} unsigned binaries:');
|
||||
unsignedBinaries.forEach(stdio.printError);
|
||||
}
|
||||
|
||||
if (wrongEntitlementBinaries.isNotEmpty) {
|
||||
stdio.printError('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
|
||||
wrongEntitlementBinaries.forEach(stdio.printError);
|
||||
}
|
||||
|
||||
if (unexpectedBinaries.isNotEmpty) {
|
||||
stdio.printError('Found ${unexpectedBinaries.length} unexpected binaries in the cache:');
|
||||
unexpectedBinaries.forEach(print);
|
||||
}
|
||||
|
||||
// Finally, exit on any invalid state
|
||||
if (unsignedBinaries.isNotEmpty) {
|
||||
throw ConductorException('Test failed because unsigned binaries detected.');
|
||||
}
|
||||
|
||||
if (wrongEntitlementBinaries.isNotEmpty) {
|
||||
throw ConductorException(
|
||||
'Test failed because files found with the wrong entitlements:\n'
|
||||
'${wrongEntitlementBinaries.join('\n')}',
|
||||
);
|
||||
}
|
||||
|
||||
if (unexpectedBinaries.isNotEmpty) {
|
||||
throw ConductorException('Test failed because unexpected binaries found in the cache.');
|
||||
}
|
||||
|
||||
final String? desiredRevision = argResults![kRevision] as String?;
|
||||
if (desiredRevision == null) {
|
||||
stdio.printStatus('Verified that binaries are codesigned and have expected entitlements.');
|
||||
} else {
|
||||
stdio.printStatus(
|
||||
'Verified that binaries for commit $desiredRevision are codesigned and have '
|
||||
'expected entitlements.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<String>? _allBinaryPaths;
|
||||
|
||||
/// Find every binary file in the given [rootDirectory].
|
||||
Future<List<String>> findBinaryPaths(String rootDirectory) async {
|
||||
if (_allBinaryPaths != null) {
|
||||
return _allBinaryPaths!;
|
||||
}
|
||||
final List<String> allBinaryPaths = <String>[];
|
||||
final io.ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
'find',
|
||||
rootDirectory,
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
);
|
||||
final List<String> allFiles = (result.stdout as String)
|
||||
.split('\n')
|
||||
.where((String s) => s.isNotEmpty)
|
||||
.toList();
|
||||
|
||||
await Future.forEach(allFiles, (String filePath) async {
|
||||
if (await isBinary(filePath)) {
|
||||
allBinaryPaths.add(filePath);
|
||||
}
|
||||
});
|
||||
_allBinaryPaths = allBinaryPaths;
|
||||
return _allBinaryPaths!;
|
||||
}
|
||||
|
||||
/// Check mime-type of file at [filePath] to determine if it is binary.
|
||||
Future<bool> isBinary(String filePath) async {
|
||||
final io.ProcessResult result = await processManager.run(
|
||||
<String>[
|
||||
'file',
|
||||
'--mime-type',
|
||||
'-b', // is binary
|
||||
filePath,
|
||||
],
|
||||
);
|
||||
return (result.stdout as String).contains('application/x-mach-binary');
|
||||
}
|
||||
|
||||
/// Check if the binary has the expected entitlements.
|
||||
Future<bool> hasExpectedEntitlements(String binaryPath) async {
|
||||
final io.ProcessResult entitlementResult = await processManager.run(
|
||||
<String>[
|
||||
'codesign',
|
||||
'--display',
|
||||
'--entitlements',
|
||||
':-',
|
||||
binaryPath,
|
||||
],
|
||||
);
|
||||
|
||||
if (entitlementResult.exitCode != 0) {
|
||||
stdio.printError(
|
||||
'The `codesign --entitlements` command failed with exit code ${entitlementResult.exitCode}:\n'
|
||||
'${entitlementResult.stderr}\n',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool passes = true;
|
||||
final String output = entitlementResult.stdout as String;
|
||||
for (final String entitlement in expectedEntitlements) {
|
||||
final bool entitlementExpected =
|
||||
(await binariesWithEntitlements).contains(binaryPath);
|
||||
if (output.contains(entitlement) != entitlementExpected) {
|
||||
stdio.printError(
|
||||
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
|
||||
'entitlement $entitlement.',
|
||||
);
|
||||
passes = false;
|
||||
}
|
||||
}
|
||||
return passes;
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This test clones the framework and downloads pre-built binaries; it sometimes
|
||||
// times out with the default 5 minutes: https://github.com/flutter/flutter/issues/100937
|
||||
@Timeout(Duration(minutes: 10))
|
||||
library;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:conductor_core/src/codesign.dart' show CodesignCommand;
|
||||
import 'package:conductor_core/src/globals.dart';
|
||||
import 'package:conductor_core/src/repository.dart' show Checkouts, FrameworkRepository;
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import './common.dart';
|
||||
|
||||
/// Verify all binaries in the Flutter cache are expected by Conductor.
|
||||
void main() {
|
||||
test(
|
||||
'validate the expected binaries from the conductor codesign command are present in the cache',
|
||||
() async {
|
||||
const Platform platform = LocalPlatform();
|
||||
const FileSystem fileSystem = LocalFileSystem();
|
||||
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_conductor_integration_test.');
|
||||
const ProcessManager processManager = LocalProcessManager();
|
||||
final TestStdio stdio = TestStdio(verbose: true);
|
||||
final Checkouts checkouts = Checkouts(
|
||||
fileSystem: fileSystem,
|
||||
parentDirectory: tempDir,
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
);
|
||||
|
||||
final Directory flutterRoot = _flutterRootFromDartBinary(
|
||||
fileSystem.file(platform.executable),
|
||||
);
|
||||
|
||||
final String currentHead = (processManager.runSync(
|
||||
<String>['git', 'rev-parse', 'HEAD'],
|
||||
workingDirectory: flutterRoot.path,
|
||||
).stdout as String).trim();
|
||||
|
||||
final FrameworkRepository framework = FrameworkRepository.localRepoAsUpstream(
|
||||
checkouts,
|
||||
upstreamPath: flutterRoot.path,
|
||||
initialRef: currentHead,
|
||||
);
|
||||
final CommandRunner<void> runner = CommandRunner<void>('codesign-test', '');
|
||||
runner.addCommand(
|
||||
CodesignCommand(
|
||||
checkouts: checkouts,
|
||||
framework: framework,
|
||||
flutterRoot: flutterRoot,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
await runner.run(<String>[
|
||||
'codesign',
|
||||
'--verify',
|
||||
// Only verify if the correct binaries are in the cache
|
||||
'--no-signatures',
|
||||
]);
|
||||
} on ConductorException catch (e) {
|
||||
print(stdio.error);
|
||||
print(_fixItInstructions);
|
||||
fail(e.message);
|
||||
} on Exception {
|
||||
print('stdout:\n${stdio.stdout}');
|
||||
print('stderr:\n${stdio.error}');
|
||||
rethrow;
|
||||
}
|
||||
}, onPlatform: <String, dynamic>{
|
||||
'windows': const Skip('codesign command is only supported on macos'),
|
||||
'linux': const Skip('codesign command is only supported on macos'),
|
||||
});
|
||||
}
|
||||
|
||||
Directory _flutterRootFromDartBinary(File dartBinary) {
|
||||
final Directory flutterDartSdkDir = dartBinary.parent.parent;
|
||||
final Directory flutterCache = flutterDartSdkDir.parent;
|
||||
final Directory flutterSdkDir = flutterCache.parent.parent;
|
||||
return flutterSdkDir;
|
||||
}
|
||||
|
||||
const String _fixItInstructions = '''
|
||||
Codesign integration test failed.
|
||||
|
||||
This means that the binary files found in the Flutter cache do not match those
|
||||
expected by the conductor tool (either an expected file was not found in the
|
||||
cache or an unexpected file was found in the cache).
|
||||
|
||||
This usually happens either during an engine roll or a change to the caching
|
||||
logic in flutter_tools. If this is a valid change, then the conductor source
|
||||
code should be updated, specifically either the [binariesWithEntitlements] or
|
||||
[binariesWithoutEntitlements] lists, depending on if the file should have macOS
|
||||
entitlements applied during codesigning.
|
||||
''';
|
@ -1,615 +0,0 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:conductor_core/src/codesign.dart';
|
||||
import 'package:conductor_core/src/repository.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import './common.dart';
|
||||
|
||||
void main() {
|
||||
group('codesign command', () {
|
||||
const String flutterRoot = '/flutter';
|
||||
const String checkoutsParentDirectory = '$flutterRoot/dev/conductor/';
|
||||
const String flutterCache =
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache';
|
||||
const String flutterBin =
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/flutter';
|
||||
const String revision = 'abcd1234';
|
||||
late CommandRunner<void> runner;
|
||||
late Checkouts checkouts;
|
||||
late MemoryFileSystem fileSystem;
|
||||
late FakePlatform platform;
|
||||
late TestStdio stdio;
|
||||
late FakeProcessManager processManager;
|
||||
const List<String> binariesWithEntitlements = <String>[
|
||||
'$flutterCache/dart-sdk/bin/dart',
|
||||
'$flutterCache/dart-sdk/bin/dartaotruntime',
|
||||
];
|
||||
const List<String> binariesWithoutEntitlements = <String>[
|
||||
'$flutterCache/engine/darwin-x64/font-subset',
|
||||
];
|
||||
const List<String> allBinaries = <String>[
|
||||
...binariesWithEntitlements,
|
||||
...binariesWithoutEntitlements,
|
||||
];
|
||||
|
||||
void createRunner({
|
||||
String operatingSystem = 'macos',
|
||||
List<FakeCommand>? commands,
|
||||
}) {
|
||||
stdio = TestStdio();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
platform = FakePlatform(operatingSystem: operatingSystem);
|
||||
processManager = FakeProcessManager.list(commands ?? <FakeCommand>[]);
|
||||
checkouts = Checkouts(
|
||||
fileSystem: fileSystem,
|
||||
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
);
|
||||
final FakeCodesignCommand command = FakeCodesignCommand(
|
||||
checkouts: checkouts,
|
||||
binariesWithEntitlements: Future<List<String>>.value(binariesWithEntitlements),
|
||||
binariesWithoutEntitlements: Future<List<String>>.value(binariesWithoutEntitlements),
|
||||
flutterRoot: fileSystem.directory(flutterRoot),
|
||||
);
|
||||
runner = CommandRunner<void>('codesign-test', '')
|
||||
..addCommand(command);
|
||||
}
|
||||
|
||||
test('throws exception if not run from macos', () async {
|
||||
createRunner(operatingSystem: 'linux');
|
||||
expect(
|
||||
() async => runner.run(<String>['codesign']),
|
||||
throwsExceptionWith('Error! Expected operating system "macos"'),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws exception if verify flag is not provided', () async {
|
||||
createRunner();
|
||||
expect(
|
||||
() async => runner.run(<String>['codesign']),
|
||||
throwsExceptionWith(
|
||||
'Sorry, but codesigning is not implemented yet. Please pass the --$kVerify flag to verify signatures'),
|
||||
);
|
||||
});
|
||||
|
||||
test('does not fail if --revision flag not provided', () async {
|
||||
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
|
||||
for (final String bin in binariesWithEntitlements) {
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '-vvv', bin],
|
||||
),
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '--display', '--entitlements', ':-', bin],
|
||||
stdout: expectedEntitlements.join('\n'),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (final String bin in binariesWithoutEntitlements) {
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '-vvv', bin],
|
||||
),
|
||||
);
|
||||
}
|
||||
createRunner(commands: <FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
FrameworkRepository.defaultUpstream,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
FrameworkRepository.defaultBranch,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
revision,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'precache',
|
||||
'--android',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]),
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'find',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
stdout: allBinaries.join('\n'),
|
||||
),
|
||||
for (final String bin in allBinaries)
|
||||
FakeCommand(
|
||||
command: <String>['file', '--mime-type', '-b', bin],
|
||||
stdout: 'application/x-mach-binary',
|
||||
),
|
||||
...codesignCheckCommands,
|
||||
]);
|
||||
await runner.run(<String>['codesign', '--$kVerify']);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
expect(stdio.stdout, contains('Verified that binaries are codesigned and have expected entitlements'));
|
||||
});
|
||||
|
||||
test('framework cloned from repo provided by --$kUpstream', () async {
|
||||
const String upstreamRepo = 'https://githost.org/org/project';
|
||||
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
|
||||
for (final String bin in binariesWithEntitlements) {
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '-vvv', bin],
|
||||
),
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '--display', '--entitlements', ':-', bin],
|
||||
stdout: expectedEntitlements.join('\n'),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (final String bin in binariesWithoutEntitlements) {
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '-vvv', bin],
|
||||
),
|
||||
);
|
||||
}
|
||||
createRunner(commands: <FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
upstreamRepo,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
FrameworkRepository.defaultBranch,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
revision,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'precache',
|
||||
'--android',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]),
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'find',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
stdout: allBinaries.join('\n'),
|
||||
),
|
||||
for (final String bin in allBinaries)
|
||||
FakeCommand(
|
||||
command: <String>['file', '--mime-type', '-b', bin],
|
||||
stdout: 'application/x-mach-binary',
|
||||
),
|
||||
...codesignCheckCommands,
|
||||
]);
|
||||
await runner.run(<String>[
|
||||
'codesign',
|
||||
'--$kVerify',
|
||||
'--$kRevision',
|
||||
revision,
|
||||
'--$kUpstream',
|
||||
upstreamRepo,
|
||||
]);
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
expect(stdio.stdout, contains('Verified that binaries for commit $revision are codesigned and have expected entitlements'));
|
||||
});
|
||||
|
||||
test('succeeds if every binary is codesigned and has correct entitlements', () async {
|
||||
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
|
||||
for (final String bin in binariesWithEntitlements) {
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '-vvv', bin],
|
||||
),
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '--display', '--entitlements', ':-', bin],
|
||||
stdout: expectedEntitlements.join('\n'),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (final String bin in binariesWithoutEntitlements) {
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: <String>['codesign', '-vvv', bin],
|
||||
),
|
||||
);
|
||||
}
|
||||
createRunner(commands: <FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
FrameworkRepository.defaultUpstream,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
FrameworkRepository.defaultBranch,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
revision,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'precache',
|
||||
'--android',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]),
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'find',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
stdout: allBinaries.join('\n'),
|
||||
),
|
||||
for (final String bin in allBinaries)
|
||||
FakeCommand(
|
||||
command: <String>['file', '--mime-type', '-b', bin],
|
||||
stdout: 'application/x-mach-binary',
|
||||
),
|
||||
...codesignCheckCommands,
|
||||
]);
|
||||
await runner.run(<String>['codesign', '--$kVerify', '--$kRevision', revision]);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
expect(stdio.stdout, contains('Verified that binaries for commit $revision are codesigned and have expected entitlements'));
|
||||
});
|
||||
|
||||
test('fails if a single binary is not codesigned', () async {
|
||||
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'],
|
||||
),
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'codesign',
|
||||
'--display',
|
||||
'--entitlements',
|
||||
':-',
|
||||
'$flutterCache/dart-sdk/bin/dart',
|
||||
],
|
||||
stdout: expectedEntitlements.join('\n'),
|
||||
)
|
||||
);
|
||||
// Not signed
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'],
|
||||
exitCode: 1,
|
||||
),
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'],
|
||||
),
|
||||
);
|
||||
|
||||
createRunner(commands: <FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
FrameworkRepository.defaultUpstream,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
FrameworkRepository.defaultBranch,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
revision,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'precache',
|
||||
'--android',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]),
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'find',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
stdout: allBinaries.join('\n'),
|
||||
),
|
||||
for (final String bin in allBinaries)
|
||||
FakeCommand(
|
||||
command: <String>['file', '--mime-type', '-b', bin],
|
||||
stdout: 'application/x-mach-binary',
|
||||
),
|
||||
...codesignCheckCommands,
|
||||
]);
|
||||
await expectLater(
|
||||
() => runner.run(<String>['codesign', '--$kVerify', '--$kRevision', revision]),
|
||||
throwsExceptionWith('Test failed because unsigned binaries detected.'),
|
||||
);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
test('fails if a single binary has the wrong entitlements', () async {
|
||||
final List<FakeCommand> codesignCheckCommands = <FakeCommand>[];
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dart'],
|
||||
),
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
FakeCommand(
|
||||
command: const <String>['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dart'],
|
||||
stdout: expectedEntitlements.join('\n'),
|
||||
)
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '-vvv', '$flutterCache/dart-sdk/bin/dartaotruntime'],
|
||||
),
|
||||
);
|
||||
// No entitlements
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '--display', '--entitlements', ':-', '$flutterCache/dart-sdk/bin/dartaotruntime'],
|
||||
)
|
||||
);
|
||||
codesignCheckCommands.add(
|
||||
const FakeCommand(
|
||||
command: <String>['codesign', '-vvv', '$flutterCache/engine/darwin-x64/font-subset'],
|
||||
),
|
||||
);
|
||||
createRunner(commands: <FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
FrameworkRepository.defaultUpstream,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
FrameworkRepository.defaultBranch,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
revision,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'precache',
|
||||
'--android',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]),
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'find',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
stdout: allBinaries.join('\n'),
|
||||
),
|
||||
for (final String bin in allBinaries)
|
||||
FakeCommand(
|
||||
command: <String>['file', '--mime-type', '-b', bin],
|
||||
stdout: 'application/x-mach-binary',
|
||||
),
|
||||
...codesignCheckCommands,
|
||||
]);
|
||||
await expectLater(
|
||||
() => runner.run(<String>['codesign', '--$kVerify', '--$kRevision', revision]),
|
||||
throwsExceptionWith('Test failed because files found with the wrong entitlements'),
|
||||
);
|
||||
expect(processManager.hasRemainingExpectations, false);
|
||||
});
|
||||
|
||||
test('does not check signatures or entitlements if --no-$kSignatures specified', () async {
|
||||
createRunner(commands: <FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
FrameworkRepository.defaultUpstream,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
FrameworkRepository.defaultBranch,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'rev-parse',
|
||||
'HEAD',
|
||||
], stdout: revision),
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
revision,
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'help',
|
||||
]),
|
||||
const FakeCommand(command: <String>[
|
||||
flutterBin,
|
||||
'precache',
|
||||
'--android',
|
||||
'--ios',
|
||||
'--macos',
|
||||
]),
|
||||
FakeCommand(
|
||||
command: const <String>[
|
||||
'find',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||
'-type',
|
||||
'f',
|
||||
],
|
||||
stdout: allBinaries.join('\n'),
|
||||
),
|
||||
for (final String bin in allBinaries)
|
||||
FakeCommand(
|
||||
command: <String>['file', '--mime-type', '-b', bin],
|
||||
stdout: 'application/x-mach-binary',
|
||||
),
|
||||
]);
|
||||
await runner.run(<String>[
|
||||
'codesign',
|
||||
'--$kVerify',
|
||||
'--no-$kSignatures',
|
||||
'--$kRevision',
|
||||
revision,
|
||||
]);
|
||||
expect(
|
||||
processManager.hasRemainingExpectations,
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FakeCodesignCommand extends CodesignCommand {
|
||||
FakeCodesignCommand({
|
||||
required super.checkouts,
|
||||
required this.binariesWithEntitlements,
|
||||
required this.binariesWithoutEntitlements,
|
||||
required super.flutterRoot,
|
||||
});
|
||||
|
||||
@override
|
||||
final Future<List<String>> binariesWithEntitlements;
|
||||
|
||||
@override
|
||||
final Future<List<String>> binariesWithoutEntitlements;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user