mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Improve codesign script (#71244)
This commit is contained in:
parent
b29a6bc56b
commit
b7f5aef11a
@ -2,184 +2,24 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io' as io;
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
String get repoRoot => path.normalize(path.join(path.dirname(Platform.script.toFilePath()), '..', '..'));
|
// TODO(fujino): delete this script once PR #71244 lands on stable.
|
||||||
String get cacheDirectory => path.normalize(path.join(repoRoot, 'bin', 'cache'));
|
void main(List<String> args) {
|
||||||
|
final String scriptPath = io.Platform.script.toFilePath();
|
||||||
/// Check mime-type of file at [filePath] to determine if it is binary
|
final String scriptDir = path.dirname(scriptPath);
|
||||||
bool isBinary(String filePath) {
|
final String repoRoot = path.normalize(path.join(scriptDir, '..', '..'));
|
||||||
final ProcessResult result = Process.runSync(
|
final io.ProcessResult result = io.Process.runSync(
|
||||||
'file',
|
path.join(repoRoot, 'dev', 'tools', 'bin', 'conductor'),
|
||||||
<String>[
|
<String>['codesign', '--verify'],
|
||||||
'--mime-type',
|
|
||||||
'-b', // is binary
|
|
||||||
filePath,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
return (result.stdout as String).contains('application/x-mach-binary');
|
if (result.exitCode != 0) {
|
||||||
}
|
print('codesign script exited with code $result.exitCode');
|
||||||
|
print('stdout:\n${result.stdout}\n');
|
||||||
/// Find every binary file in the given [rootDirectory]
|
print('stderr:\n${result.stderr}\n');
|
||||||
List<String> findBinaryPaths([String rootDirectory]) {
|
io.exit(1);
|
||||||
rootDirectory ??= cacheDirectory;
|
}
|
||||||
final ProcessResult result = Process.runSync(
|
print('codesign script succeeded.');
|
||||||
'find',
|
print('stdout:\n${result.stdout}');
|
||||||
<String>[
|
|
||||||
rootDirectory,
|
|
||||||
'-type',
|
|
||||||
'f',
|
|
||||||
'-perm',
|
|
||||||
'+111', // is executable
|
|
||||||
],
|
|
||||||
);
|
|
||||||
final List<String> allFiles = (result.stdout as String).split('\n').where((String s) => s.isNotEmpty).toList();
|
|
||||||
return allFiles.where(isBinary).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given the path to a stamp file, read the contents.
|
|
||||||
///
|
|
||||||
/// Will throw if the file doesn't exist.
|
|
||||||
String readStamp(String filePath) {
|
|
||||||
final File file = File(filePath);
|
|
||||||
if (!file.existsSync()) {
|
|
||||||
throw 'Error! Stamp file $filePath does not exist!';
|
|
||||||
}
|
|
||||||
return file.readAsStringSync().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return whether or not the flutter cache is up to date.
|
|
||||||
bool checkCacheIsCurrent() {
|
|
||||||
try {
|
|
||||||
final String dartSdkStamp = readStamp(path.join(cacheDirectory, 'engine-dart-sdk.stamp'));
|
|
||||||
final String engineVersion = readStamp(path.join(repoRoot, 'bin', 'internal', 'engine.version'));
|
|
||||||
return dartSdkStamp == engineVersion;
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> get binariesWithEntitlements => List<String>.unmodifiable(<String>[
|
|
||||||
'ideviceinfo',
|
|
||||||
'idevicename',
|
|
||||||
'idevicescreenshot',
|
|
||||||
'idevicesyslog',
|
|
||||||
'libimobiledevice.6.dylib',
|
|
||||||
'libplist.3.dylib',
|
|
||||||
'iproxy',
|
|
||||||
'libusbmuxd.4.dylib',
|
|
||||||
'libssl.1.0.0.dylib',
|
|
||||||
'libcrypto.1.0.0.dylib',
|
|
||||||
'libzip.5.0.dylib',
|
|
||||||
'libzip.5.dylib',
|
|
||||||
'gen_snapshot',
|
|
||||||
'dart',
|
|
||||||
'flutter_tester',
|
|
||||||
'gen_snapshot_arm64',
|
|
||||||
'gen_snapshot_armv7',
|
|
||||||
]);
|
|
||||||
|
|
||||||
List<String> get expectedEntitlements => List<String>.unmodifiable(<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',
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
/// Check if the binary has the expected entitlements.
|
|
||||||
bool hasExpectedEntitlements(String binaryPath) {
|
|
||||||
try {
|
|
||||||
final ProcessResult entitlementResult = Process.runSync(
|
|
||||||
'codesign',
|
|
||||||
<String>[
|
|
||||||
'--display',
|
|
||||||
'--entitlements',
|
|
||||||
':-',
|
|
||||||
binaryPath,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (entitlementResult.exitCode != 0) {
|
|
||||||
print('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 = binariesWithEntitlements.contains(path.basename(binaryPath));
|
|
||||||
if (output.contains(entitlement) != entitlementExpected) {
|
|
||||||
print('File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} entitlement $entitlement.');
|
|
||||||
passes = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return passes;
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (!Platform.isMacOS) {
|
|
||||||
print('Error! Expected operating system "macos", actual operating system '
|
|
||||||
'is: "${Platform.operatingSystem}"');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkCacheIsCurrent()) {
|
|
||||||
print(
|
|
||||||
'Warning! Your cache is either not present or not matching your flutter\n'
|
|
||||||
'version. Run a `flutter` command to update your cache, and re-try this\n'
|
|
||||||
'test.');
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<String> unsignedBinaries = <String>[];
|
|
||||||
final List<String> wrongEntitlementBinaries = <String>[];
|
|
||||||
for (final String binaryPath in findBinaryPaths(cacheDirectory)) {
|
|
||||||
print('Verifying the code signature of $binaryPath');
|
|
||||||
final ProcessResult codeSignResult = Process.runSync(
|
|
||||||
'codesign',
|
|
||||||
<String>[
|
|
||||||
'-vvv',
|
|
||||||
binaryPath,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (codeSignResult.exitCode != 0) {
|
|
||||||
unsignedBinaries.add(binaryPath);
|
|
||||||
print('File "$binaryPath" does not appear to be codesigned.\n'
|
|
||||||
'The `codesign` command failed with exit code ${codeSignResult.exitCode}:\n'
|
|
||||||
'${codeSignResult.stderr}\n');
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
print('Verifying entitlements of $binaryPath');
|
|
||||||
if (!hasExpectedEntitlements(binaryPath)) {
|
|
||||||
wrongEntitlementBinaries.add(binaryPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unsignedBinaries.isNotEmpty) {
|
|
||||||
print('Found ${unsignedBinaries.length} unsigned binaries:');
|
|
||||||
unsignedBinaries.forEach(print);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wrongEntitlementBinaries.isNotEmpty) {
|
|
||||||
print('Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
|
|
||||||
wrongEntitlementBinaries.forEach(print);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unsignedBinaries.isNotEmpty) {
|
|
||||||
// TODO(jmagman): Also exit if `wrongEntitlementBinaries.isNotEmpty` after https://github.com/flutter/flutter/issues/46704 is done.
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
print('Verified that binaries are codesigned and have expected entitlements.');
|
|
||||||
}
|
}
|
||||||
|
@ -694,7 +694,7 @@ Future<void> _runFrameworkTests() async {
|
|||||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata);
|
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'), tableData: bigqueryApi?.tabledata);
|
||||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), tableData: bigqueryApi?.tabledata);
|
await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'), tableData: bigqueryApi?.tabledata);
|
||||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), tableData: bigqueryApi?.tabledata);
|
await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'), tableData: bigqueryApi?.tabledata);
|
||||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'tools'), tableData: bigqueryApi?.tabledata);
|
await _pubRunTest(path.join(flutterRoot, 'dev', 'tools'), tableData: bigqueryApi?.tabledata, forceSingleCore: true);
|
||||||
await _pubRunTest(path.join(flutterRoot, 'dev', 'benchmarks', 'metrics_center'), tableData: bigqueryApi?.tabledata);
|
await _pubRunTest(path.join(flutterRoot, 'dev', 'benchmarks', 'metrics_center'), tableData: bigqueryApi?.tabledata);
|
||||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), tableData: bigqueryApi?.tabledata);
|
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), tableData: bigqueryApi?.tabledata);
|
||||||
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
|
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'), tableData: bigqueryApi?.tabledata);
|
||||||
|
@ -10,15 +10,17 @@
|
|||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:dev_tools/codesign.dart';
|
||||||
|
import 'package:dev_tools/globals.dart';
|
||||||
|
import 'package:dev_tools/roll_dev.dart';
|
||||||
|
import 'package:dev_tools/repository.dart';
|
||||||
|
import 'package:dev_tools/stdio.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
import 'package:dev_tools/repository.dart';
|
|
||||||
import 'package:dev_tools/roll_dev.dart';
|
|
||||||
import 'package:dev_tools/stdio.dart';
|
|
||||||
|
|
||||||
void main(List<String> args) {
|
Future<void> main(List<String> args) async {
|
||||||
const FileSystem fileSystem = LocalFileSystem();
|
const FileSystem fileSystem = LocalFileSystem();
|
||||||
const ProcessManager processManager = LocalProcessManager();
|
const ProcessManager processManager = LocalProcessManager();
|
||||||
const Platform platform = LocalPlatform();
|
const Platform platform = LocalPlatform();
|
||||||
@ -29,9 +31,12 @@ void main(List<String> args) {
|
|||||||
);
|
);
|
||||||
final Checkouts checkouts = Checkouts(
|
final Checkouts checkouts = Checkouts(
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: localFlutterRoot.parent,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
processManager: processManager,
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
);
|
);
|
||||||
|
|
||||||
final CommandRunner<void> runner = CommandRunner<void>(
|
final CommandRunner<void> runner = CommandRunner<void>(
|
||||||
'conductor',
|
'conductor',
|
||||||
'A tool for coordinating Flutter releases.',
|
'A tool for coordinating Flutter releases.',
|
||||||
@ -39,17 +44,16 @@ void main(List<String> args) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
<Command<void>>[
|
<Command<void>>[
|
||||||
RollDev(
|
RollDevCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
repository: checkouts.addRepo(
|
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repoType: RepositoryType.framework,
|
|
||||||
stdio: stdio,
|
|
||||||
),
|
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
),
|
),
|
||||||
|
CodesignCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
|
flutterRoot: localFlutterRoot,
|
||||||
|
),
|
||||||
].forEach(runner.addCommand);
|
].forEach(runner.addCommand);
|
||||||
|
|
||||||
if (!assertsEnabled()) {
|
if (!assertsEnabled()) {
|
||||||
@ -58,20 +62,9 @@ void main(List<String> args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runner.run(args);
|
await runner.run(args);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
stdio.printError(e.toString());
|
stdio.printError(e.toString());
|
||||||
io.exit(1);
|
io.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool assertsEnabled() {
|
|
||||||
// Verify asserts enabled
|
|
||||||
bool assertsEnabled = false;
|
|
||||||
|
|
||||||
assert(() {
|
|
||||||
assertsEnabled = true;
|
|
||||||
return true;
|
|
||||||
}());
|
|
||||||
return assertsEnabled;
|
|
||||||
}
|
|
||||||
|
369
dev/tools/lib/codesign.dart
Normal file
369
dev/tools/lib/codesign.dart
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
// 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';
|
||||||
|
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,
|
||||||
|
}) : assert(flutterRoot != null),
|
||||||
|
fileSystem = checkouts.fileSystem,
|
||||||
|
platform = checkouts.platform,
|
||||||
|
stdio = checkouts.stdio,
|
||||||
|
processManager = checkouts.processManager {
|
||||||
|
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 => _framework ??= FrameworkRepository.localRepoAsUpstream(
|
||||||
|
checkouts,
|
||||||
|
upstreamPath: flutterRoot.path,
|
||||||
|
);
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
set framework(FrameworkRepository framework) => _framework = framework;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'codesign';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description =>
|
||||||
|
'For codesigning and verifying the signatures of engine binaries.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void run() {
|
||||||
|
if (!platform.isMacOS) {
|
||||||
|
throw ConductorException(
|
||||||
|
'Error! Expected operating system "macos", actual operating system is: '
|
||||||
|
'"${platform.operatingSystem}"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argResults['verify'] as bool != true) {
|
||||||
|
throw ConductorException(
|
||||||
|
'Sorry, but codesigning is not implemented yet. Please pass the '
|
||||||
|
'--$kVerify flag to verify signatures.');
|
||||||
|
}
|
||||||
|
|
||||||
|
String revision;
|
||||||
|
if (argResults.wasParsed(kRevision)) {
|
||||||
|
stdio.printError('Warning! When providing an arbitrary revision, the contents of the cache may not');
|
||||||
|
stdio.printError('match the expected binaries in the conductor tool. It is preferred to check out');
|
||||||
|
stdio.printError('the desired revision and run that version of the conductor.\n');
|
||||||
|
revision = argResults[kRevision] as String;
|
||||||
|
} else {
|
||||||
|
revision = (processManager.runSync(
|
||||||
|
<String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
workingDirectory: framework.checkoutDirectory.path,
|
||||||
|
).stdout as String).trim();
|
||||||
|
assert(revision.isNotEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
framework.checkout(revision);
|
||||||
|
|
||||||
|
// Ensure artifacts present
|
||||||
|
framework.runFlutter(<String>['precache', '--ios', '--macos']);
|
||||||
|
|
||||||
|
verifyExist();
|
||||||
|
if (argResults[kSignatures] as bool) {
|
||||||
|
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.
|
||||||
|
List<String> get binariesWithEntitlements {
|
||||||
|
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-release/gen_snapshot',
|
||||||
|
'artifacts/engine/darwin-x64/flutter_tester',
|
||||||
|
'artifacts/engine/darwin-x64/gen_snapshot',
|
||||||
|
'artifacts/engine/ios-profile/gen_snapshot_arm64',
|
||||||
|
'artifacts/engine/ios-profile/gen_snapshot_armv7',
|
||||||
|
'artifacts/engine/ios-release/gen_snapshot_arm64',
|
||||||
|
'artifacts/engine/ios-release/gen_snapshot_armv7',
|
||||||
|
'artifacts/engine/ios/gen_snapshot_arm64',
|
||||||
|
'artifacts/engine/ios/gen_snapshot_armv7',
|
||||||
|
'artifacts/ios-deploy/ios-deploy',
|
||||||
|
'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',
|
||||||
|
].map((String relativePath) => fileSystem.path.join(framework.cacheDirectory, 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.
|
||||||
|
List<String> get binariesWithoutEntitlements {
|
||||||
|
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/ios-profile/Flutter.xcframework/ios-armv7_arm64/Flutter.framework/Flutter',
|
||||||
|
'artifacts/engine/ios-profile/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework/Flutter',
|
||||||
|
'artifacts/engine/ios-release/Flutter.xcframework/ios-armv7_arm64/Flutter.framework/Flutter',
|
||||||
|
'artifacts/engine/ios-release/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework/Flutter',
|
||||||
|
'artifacts/engine/ios/Flutter.xcframework/ios-armv7_arm64/Flutter.framework/Flutter',
|
||||||
|
'artifacts/engine/ios/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework/Flutter',
|
||||||
|
].map((String relativePath) => fileSystem.path.join(framework.cacheDirectory, 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
|
||||||
|
void verifyExist() {
|
||||||
|
final Set<String> foundFiles = <String>{};
|
||||||
|
for (final String binaryPath in findBinaryPaths(framework.cacheDirectory)) {
|
||||||
|
if (binariesWithEntitlements.contains(binaryPath)) {
|
||||||
|
foundFiles.add(binaryPath);
|
||||||
|
} else if (binariesWithoutEntitlements.contains(binaryPath)) {
|
||||||
|
foundFiles.add(binaryPath);
|
||||||
|
} else {
|
||||||
|
throw ConductorException('Found unexpected binary in cache: $binaryPath');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> allExpectedFiles = binariesWithEntitlements + 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');
|
||||||
|
stdio.printError('If this commit is removing binaries from the cache, this test should be fixed by');
|
||||||
|
stdio.printError('removing the relevant entry from either the `binariesWithEntitlements` or');
|
||||||
|
stdio.printError('`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
|
||||||
|
void verifySignatures() {
|
||||||
|
final List<String> unsignedBinaries = <String>[];
|
||||||
|
final List<String> wrongEntitlementBinaries = <String>[];
|
||||||
|
final List<String> unexpectedBinaries = <String>[];
|
||||||
|
|
||||||
|
for (final String binaryPath in findBinaryPaths(framework.cacheDirectory)) {
|
||||||
|
bool verifySignature = false;
|
||||||
|
bool verifyEntitlements = false;
|
||||||
|
if (binariesWithEntitlements.contains(binaryPath)) {
|
||||||
|
verifySignature = true;
|
||||||
|
verifyEntitlements = true;
|
||||||
|
}
|
||||||
|
if (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 = processManager.runSync(
|
||||||
|
<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 (!hasExpectedEntitlements(binaryPath)) {
|
||||||
|
wrongEntitlementBinaries.add(binaryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First print all deviations from expectations
|
||||||
|
if (unsignedBinaries.isNotEmpty) {
|
||||||
|
stdio.printError('Found ${unsignedBinaries.length} unsigned binaries:');
|
||||||
|
unsignedBinaries.forEach(print);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrongEntitlementBinaries.isNotEmpty) {
|
||||||
|
stdio.printError(
|
||||||
|
'Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
|
||||||
|
wrongEntitlementBinaries.forEach(print);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
stdio.printStatus(
|
||||||
|
'Verified that binaries for commit ${argResults[kRevision] as String} are codesigned and have '
|
||||||
|
'expected entitlements.');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _allBinaryPaths;
|
||||||
|
/// Find every binary file in the given [rootDirectory].
|
||||||
|
List<String> findBinaryPaths(String rootDirectory) {
|
||||||
|
if (_allBinaryPaths != null) {
|
||||||
|
return _allBinaryPaths;
|
||||||
|
}
|
||||||
|
final io.ProcessResult result = processManager.runSync(
|
||||||
|
<String>[
|
||||||
|
'find',
|
||||||
|
rootDirectory,
|
||||||
|
'-type',
|
||||||
|
'f',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final List<String> allFiles = (result.stdout as String)
|
||||||
|
.split('\n')
|
||||||
|
.where((String s) => s.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
_allBinaryPaths = allFiles.where(isBinary).toList();
|
||||||
|
return _allBinaryPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check mime-type of file at [filePath] to determine if it is binary.
|
||||||
|
bool isBinary(String filePath) {
|
||||||
|
final io.ProcessResult result = processManager.runSync(
|
||||||
|
<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.
|
||||||
|
bool hasExpectedEntitlements(String binaryPath) {
|
||||||
|
final io.ProcessResult entitlementResult = processManager.runSync(
|
||||||
|
<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 = 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;
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,7 @@ class Git {
|
|||||||
return processManager.runSync(
|
return processManager.runSync(
|
||||||
<String>['git', ...args],
|
<String>['git', ...args],
|
||||||
workingDirectory: workingDirectory,
|
workingDirectory: workingDirectory,
|
||||||
|
environment: <String, String>{'GIT_TRACE': '1'},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/local.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
const String kIncrement = 'increment';
|
const String kIncrement = 'increment';
|
||||||
const String kCommit = 'commit';
|
const String kCommit = 'commit';
|
||||||
const String kRemoteName = 'remote';
|
const String kRemoteName = 'remote';
|
||||||
@ -24,3 +28,61 @@ String stdoutToString(dynamic input) {
|
|||||||
final String str = input as String;
|
final String str = input as String;
|
||||||
return str.trim();
|
return str.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ConductorException implements Exception {
|
||||||
|
ConductorException(this.message);
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Exception: $message';
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory _flutterRoot;
|
||||||
|
Directory get localFlutterRoot {
|
||||||
|
if (_flutterRoot != null) {
|
||||||
|
return _flutterRoot;
|
||||||
|
}
|
||||||
|
String filePath;
|
||||||
|
const FileSystem fileSystem = LocalFileSystem();
|
||||||
|
const Platform platform = LocalPlatform();
|
||||||
|
|
||||||
|
// If a test
|
||||||
|
if (platform.script.scheme == 'data') {
|
||||||
|
final RegExp pattern = RegExp(
|
||||||
|
r'(file:\/\/[^"]*[/\\]dev\/tools[/\\][^"]+\.dart)',
|
||||||
|
multiLine: true,
|
||||||
|
);
|
||||||
|
final Match match =
|
||||||
|
pattern.firstMatch(Uri.decodeFull(platform.script.path));
|
||||||
|
if (match == null) {
|
||||||
|
throw Exception(
|
||||||
|
'Cannot determine path of script!\n${platform.script.path}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
filePath = Uri.parse(match.group(1)).path.replaceAll(r'%20', ' ');
|
||||||
|
} else {
|
||||||
|
filePath = platform.script.toFilePath();
|
||||||
|
}
|
||||||
|
final String checkoutsDirname = fileSystem.path.normalize(
|
||||||
|
fileSystem.path.join(
|
||||||
|
fileSystem.path.dirname(filePath),
|
||||||
|
'..', // flutter/dev/tools
|
||||||
|
'..', // flutter/dev
|
||||||
|
'..', // flutter
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_flutterRoot = fileSystem.directory(checkoutsDirname);
|
||||||
|
return _flutterRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool assertsEnabled() {
|
||||||
|
// Verify asserts enabled
|
||||||
|
bool assertsEnabled = false;
|
||||||
|
|
||||||
|
assert(() {
|
||||||
|
assertsEnabled = true;
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
return assertsEnabled;
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@ import './stdio.dart';
|
|||||||
import './version.dart';
|
import './version.dart';
|
||||||
|
|
||||||
/// A source code repository.
|
/// A source code repository.
|
||||||
class Repository {
|
abstract class Repository {
|
||||||
Repository({
|
Repository({
|
||||||
@required this.name,
|
@required this.name,
|
||||||
@required this.upstream,
|
@required this.upstream,
|
||||||
@ -46,22 +46,35 @@ class Repository {
|
|||||||
|
|
||||||
Directory _checkoutDirectory;
|
Directory _checkoutDirectory;
|
||||||
|
|
||||||
/// Lazily-loaded directory for the repository checkout.
|
/// Directory for the repository checkout.
|
||||||
///
|
///
|
||||||
/// Cloning a repository is time-consuming, thus the repository is not cloned
|
/// Since cloning a repository takes a long time, we do not ensure it is
|
||||||
/// until this getter is called.
|
/// cloned on the filesystem until this getter is accessed.
|
||||||
Directory get checkoutDirectory {
|
Directory get checkoutDirectory {
|
||||||
if (_checkoutDirectory != null) {
|
if (_checkoutDirectory != null) {
|
||||||
return _checkoutDirectory;
|
return _checkoutDirectory;
|
||||||
}
|
}
|
||||||
_checkoutDirectory = parentDirectory.childDirectory(name);
|
_checkoutDirectory = parentDirectory.childDirectory(name);
|
||||||
if (checkoutDirectory.existsSync() && !useExistingCheckout) {
|
if (!useExistingCheckout && _checkoutDirectory.existsSync()) {
|
||||||
deleteDirectory();
|
stdio.printTrace('Deleting $name from ${_checkoutDirectory.path}...');
|
||||||
}
|
_checkoutDirectory.deleteSync(recursive: true);
|
||||||
if (!checkoutDirectory.existsSync()) {
|
} else if (useExistingCheckout && _checkoutDirectory.existsSync()) {
|
||||||
stdio.printTrace('Cloning $name to ${checkoutDirectory.path}...');
|
|
||||||
git.run(
|
git.run(
|
||||||
<String>['clone', '--', upstream, checkoutDirectory.path],
|
<String>['checkout', 'master'],
|
||||||
|
'Checkout to master branch',
|
||||||
|
workingDirectory: _checkoutDirectory.path,
|
||||||
|
);
|
||||||
|
git.run(
|
||||||
|
<String>['pull', '--ff-only'],
|
||||||
|
'Updating $name repo',
|
||||||
|
workingDirectory: _checkoutDirectory.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!_checkoutDirectory.existsSync()) {
|
||||||
|
stdio.printTrace(
|
||||||
|
'Cloning $name from $upstream to ${_checkoutDirectory.path}...');
|
||||||
|
git.run(
|
||||||
|
<String>['clone', '--', upstream, _checkoutDirectory.path],
|
||||||
'Cloning $name repo',
|
'Cloning $name repo',
|
||||||
workingDirectory: parentDirectory.path,
|
workingDirectory: parentDirectory.path,
|
||||||
);
|
);
|
||||||
@ -72,27 +85,16 @@ class Repository {
|
|||||||
git.run(
|
git.run(
|
||||||
<String>['checkout', channel, '--'],
|
<String>['checkout', channel, '--'],
|
||||||
'check out branch $channel locally',
|
'check out branch $channel locally',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: _checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
stdio.printTrace(
|
|
||||||
'Using existing $name repo at ${checkoutDirectory.path}...',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return _checkoutDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteDirectory() {
|
final String revision = reverseParse('HEAD');
|
||||||
if (!checkoutDirectory.existsSync()) {
|
stdio
|
||||||
stdio.printTrace(
|
.printTrace('Repository $name is checked out at revision "$revision".');
|
||||||
'Tried to delete ${checkoutDirectory.path} but it does not exist.',
|
return _checkoutDirectory;
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
stdio.printTrace('Deleting $name from ${checkoutDirectory.path}...');
|
|
||||||
checkoutDirectory.deleteSync(recursive: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The URL of the remote named [remoteName].
|
/// The URL of the remote named [remoteName].
|
||||||
@ -124,6 +126,14 @@ class Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkout(String revision) {
|
||||||
|
git.run(
|
||||||
|
<String>['checkout', revision],
|
||||||
|
'checkout $revision',
|
||||||
|
workingDirectory: checkoutDirectory.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Obtain the version tag of the previous dev release.
|
/// Obtain the version tag of the previous dev release.
|
||||||
String getFullTag(String remoteName) {
|
String getFullTag(String remoteName) {
|
||||||
const String glob = '*.*.*-*.*.pre';
|
const String glob = '*.*.*-*.*.pre';
|
||||||
@ -142,7 +152,7 @@ class Repository {
|
|||||||
<String>['rev-parse', ref],
|
<String>['rev-parse', ref],
|
||||||
'look up the commit for the ref $ref',
|
'look up the commit for the ref $ref',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
).trim();
|
||||||
assert(revisionHash.isNotEmpty);
|
assert(revisionHash.isNotEmpty);
|
||||||
return revisionHash;
|
return revisionHash;
|
||||||
}
|
}
|
||||||
@ -216,24 +226,6 @@ class Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Version flutterVersion() {
|
|
||||||
// Build tool
|
|
||||||
processManager.runSync(<String>[
|
|
||||||
fileSystem.path.join(checkoutDirectory.path, 'bin', 'flutter'),
|
|
||||||
'help',
|
|
||||||
]);
|
|
||||||
// Check version
|
|
||||||
final io.ProcessResult result = processManager.runSync(<String>[
|
|
||||||
fileSystem.path.join(checkoutDirectory.path, 'bin', 'flutter'),
|
|
||||||
'--version',
|
|
||||||
'--machine',
|
|
||||||
]);
|
|
||||||
final Map<String, dynamic> versionJson = jsonDecode(
|
|
||||||
globals.stdoutToString(result.stdout),
|
|
||||||
) as Map<String, dynamic>;
|
|
||||||
return Version.fromString(versionJson['frameworkVersion'] as String);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an empty commit and return the revision.
|
/// Create an empty commit and return the revision.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
String authorEmptyCommit([String message = 'An empty commit']) {
|
String authorEmptyCommit([String message = 'An empty commit']) {
|
||||||
@ -261,20 +253,120 @@ class Repository {
|
|||||||
///
|
///
|
||||||
/// This method is for testing purposes.
|
/// This method is for testing purposes.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
Repository cloneRepository(String cloneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FrameworkRepository extends Repository {
|
||||||
|
FrameworkRepository(
|
||||||
|
this.checkouts, {
|
||||||
|
String name = 'framework',
|
||||||
|
String upstream = FrameworkRepository.defaultUpstream,
|
||||||
|
bool localUpstream = false,
|
||||||
|
bool useExistingCheckout = false,
|
||||||
|
}) : super(
|
||||||
|
name: name,
|
||||||
|
upstream: upstream,
|
||||||
|
fileSystem: checkouts.fileSystem,
|
||||||
|
localUpstream: localUpstream,
|
||||||
|
parentDirectory: checkouts.directory,
|
||||||
|
platform: checkouts.platform,
|
||||||
|
processManager: checkouts.processManager,
|
||||||
|
stdio: checkouts.stdio,
|
||||||
|
useExistingCheckout: useExistingCheckout,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A [FrameworkRepository] with the host conductor's repo set as upstream.
|
||||||
|
///
|
||||||
|
/// This is useful when testing a commit that has not been merged upstream
|
||||||
|
/// yet.
|
||||||
|
factory FrameworkRepository.localRepoAsUpstream(
|
||||||
|
Checkouts checkouts, {
|
||||||
|
String name = 'framework',
|
||||||
|
bool useExistingCheckout = false,
|
||||||
|
@required String upstreamPath,
|
||||||
|
}) {
|
||||||
|
return FrameworkRepository(
|
||||||
|
checkouts,
|
||||||
|
name: name,
|
||||||
|
upstream: 'file://$upstreamPath/',
|
||||||
|
localUpstream: false,
|
||||||
|
useExistingCheckout: useExistingCheckout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Checkouts checkouts;
|
||||||
|
static const String defaultUpstream =
|
||||||
|
'https://github.com/flutter/flutter.git';
|
||||||
|
|
||||||
|
String get cacheDirectory => fileSystem.path.join(
|
||||||
|
checkoutDirectory.path,
|
||||||
|
'bin',
|
||||||
|
'cache',
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
Repository cloneRepository(String cloneName) {
|
Repository cloneRepository(String cloneName) {
|
||||||
assert(localUpstream);
|
assert(localUpstream);
|
||||||
cloneName ??= 'clone-of-$name';
|
cloneName ??= 'clone-of-$name';
|
||||||
return Repository(
|
return FrameworkRepository(
|
||||||
fileSystem: fileSystem,
|
checkouts,
|
||||||
name: cloneName,
|
name: cloneName,
|
||||||
parentDirectory: parentDirectory,
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
stdio: stdio,
|
|
||||||
upstream: 'file://${checkoutDirectory.path}/',
|
upstream: 'file://${checkoutDirectory.path}/',
|
||||||
useExistingCheckout: useExistingCheckout,
|
useExistingCheckout: useExistingCheckout,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _ensureToolReady() {
|
||||||
|
final File toolsStamp =
|
||||||
|
fileSystem.directory(cacheDirectory).childFile('flutter_tools.stamp');
|
||||||
|
if (toolsStamp.existsSync()) {
|
||||||
|
final String toolsStampHash = toolsStamp.readAsStringSync().trim();
|
||||||
|
final String repoHeadHash = reverseParse('HEAD');
|
||||||
|
if (toolsStampHash == repoHeadHash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdio.printTrace('Building tool...');
|
||||||
|
// Build tool
|
||||||
|
processManager.runSync(<String>[
|
||||||
|
fileSystem.path.join(checkoutDirectory.path, 'bin', 'flutter'),
|
||||||
|
'help',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.ProcessResult runFlutter(List<String> args) {
|
||||||
|
_ensureToolReady();
|
||||||
|
|
||||||
|
return processManager.runSync(<String>[
|
||||||
|
fileSystem.path.join(checkoutDirectory.path, 'bin', 'flutter'),
|
||||||
|
...args,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void checkout(String revision) {
|
||||||
|
super.checkout(revision);
|
||||||
|
// The tool will overwrite old cached artifacts, but not delete unused
|
||||||
|
// artifacts from a previous version. Thus, delete the entire cache and
|
||||||
|
// re-populate.
|
||||||
|
final Directory cache = fileSystem.directory(cacheDirectory);
|
||||||
|
if (cache.existsSync()) {
|
||||||
|
stdio.printTrace('Deleting cache...');
|
||||||
|
cache.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
_ensureToolReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
Version flutterVersion() {
|
||||||
|
// Check version
|
||||||
|
final io.ProcessResult result =
|
||||||
|
runFlutter(<String>['--version', '--machine']);
|
||||||
|
final Map<String, dynamic> versionJson = jsonDecode(
|
||||||
|
globals.stdoutToString(result.stdout),
|
||||||
|
) as Map<String, dynamic>;
|
||||||
|
return Version.fromString(versionJson['frameworkVersion'] as String);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An enum of all the repositories that the Conductor supports.
|
/// An enum of all the repositories that the Conductor supports.
|
||||||
@ -285,81 +377,22 @@ enum RepositoryType {
|
|||||||
|
|
||||||
class Checkouts {
|
class Checkouts {
|
||||||
Checkouts({
|
Checkouts({
|
||||||
@required Platform platform,
|
|
||||||
@required this.fileSystem,
|
@required this.fileSystem,
|
||||||
|
@required this.platform,
|
||||||
@required this.processManager,
|
@required this.processManager,
|
||||||
Directory parentDirectory,
|
@required this.stdio,
|
||||||
String directoryName = 'checkouts',
|
@required Directory parentDirectory,
|
||||||
}) {
|
String directoryName = 'flutter_conductor_checkouts',
|
||||||
if (parentDirectory != null) {
|
}) : assert(parentDirectory != null),
|
||||||
directory = parentDirectory.childDirectory(directoryName);
|
directory = parentDirectory.childDirectory(directoryName) {
|
||||||
} else {
|
|
||||||
String filePath;
|
|
||||||
// If a test
|
|
||||||
if (platform.script.scheme == 'data') {
|
|
||||||
final RegExp pattern = RegExp(
|
|
||||||
r'(file:\/\/[^"]*[/\\]dev\/tools[/\\][^"]+\.dart)',
|
|
||||||
multiLine: true,
|
|
||||||
);
|
|
||||||
final Match match =
|
|
||||||
pattern.firstMatch(Uri.decodeFull(platform.script.path));
|
|
||||||
if (match == null) {
|
|
||||||
throw Exception(
|
|
||||||
'Cannot determine path of script!\n${platform.script.path}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
filePath = Uri.parse(match.group(1)).path.replaceAll(r'%20', ' ');
|
|
||||||
} else {
|
|
||||||
filePath = platform.script.toFilePath();
|
|
||||||
}
|
|
||||||
final String checkoutsDirname = fileSystem.path.normalize(
|
|
||||||
fileSystem.path.join(
|
|
||||||
fileSystem.path.dirname(filePath),
|
|
||||||
'..',
|
|
||||||
'checkouts',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
directory = fileSystem.directory(checkoutsDirname);
|
|
||||||
}
|
|
||||||
if (!directory.existsSync()) {
|
if (!directory.existsSync()) {
|
||||||
directory.createSync(recursive: true);
|
directory.createSync(recursive: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory directory;
|
final Directory directory;
|
||||||
final FileSystem fileSystem;
|
final FileSystem fileSystem;
|
||||||
|
final Platform platform;
|
||||||
final ProcessManager processManager;
|
final ProcessManager processManager;
|
||||||
|
final Stdio stdio;
|
||||||
Repository addRepo({
|
|
||||||
@required RepositoryType repoType,
|
|
||||||
@required Stdio stdio,
|
|
||||||
@required Platform platform,
|
|
||||||
FileSystem fileSystem,
|
|
||||||
String upstream,
|
|
||||||
String name,
|
|
||||||
bool localUpstream = false,
|
|
||||||
bool useExistingCheckout = false,
|
|
||||||
}) {
|
|
||||||
switch (repoType) {
|
|
||||||
case RepositoryType.framework:
|
|
||||||
name ??= 'framework';
|
|
||||||
upstream ??= 'https://github.com/flutter/flutter.git';
|
|
||||||
break;
|
|
||||||
case RepositoryType.engine:
|
|
||||||
name ??= 'engine';
|
|
||||||
upstream ??= 'https://github.com/flutter/engine.git';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return Repository(
|
|
||||||
name: name,
|
|
||||||
upstream: upstream,
|
|
||||||
stdio: stdio,
|
|
||||||
platform: platform,
|
|
||||||
fileSystem: fileSystem,
|
|
||||||
parentDirectory: directory,
|
|
||||||
processManager: processManager,
|
|
||||||
localUpstream: localUpstream,
|
|
||||||
useExistingCheckout: useExistingCheckout,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,12 @@ import './stdio.dart';
|
|||||||
import './version.dart';
|
import './version.dart';
|
||||||
|
|
||||||
/// Create a new dev release without cherry picks.
|
/// Create a new dev release without cherry picks.
|
||||||
class RollDev extends Command<void> {
|
class RollDevCommand extends Command<void> {
|
||||||
RollDev({
|
RollDevCommand({
|
||||||
this.fileSystem,
|
@required this.checkouts,
|
||||||
this.platform,
|
@required this.fileSystem,
|
||||||
this.repository,
|
@required this.platform,
|
||||||
this.stdio,
|
@required this.stdio,
|
||||||
}) {
|
}) {
|
||||||
argParser.addOption(
|
argParser.addOption(
|
||||||
kIncrement,
|
kIncrement,
|
||||||
@ -60,10 +60,10 @@ class RollDev extends Command<void> {
|
|||||||
argParser.addFlag(kYes, negatable: false, abbr: 'y', help: 'Skip the confirmation prompt.');
|
argParser.addFlag(kYes, negatable: false, abbr: 'y', help: 'Skip the confirmation prompt.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Checkouts checkouts;
|
||||||
final FileSystem fileSystem;
|
final FileSystem fileSystem;
|
||||||
final Platform platform;
|
final Platform platform;
|
||||||
final Stdio stdio;
|
final Stdio stdio;
|
||||||
final Repository repository;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => 'roll-dev';
|
String get name => 'roll-dev';
|
||||||
@ -76,9 +76,7 @@ class RollDev extends Command<void> {
|
|||||||
void run() {
|
void run() {
|
||||||
rollDev(
|
rollDev(
|
||||||
argResults: argResults,
|
argResults: argResults,
|
||||||
fileSystem: fileSystem,
|
repository: FrameworkRepository(checkouts),
|
||||||
platform: platform,
|
|
||||||
repository: repository,
|
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: argParser.usage,
|
usage: argParser.usage,
|
||||||
);
|
);
|
||||||
@ -93,9 +91,7 @@ bool rollDev({
|
|||||||
@required String usage,
|
@required String usage,
|
||||||
@required ArgResults argResults,
|
@required ArgResults argResults,
|
||||||
@required Stdio stdio,
|
@required Stdio stdio,
|
||||||
@required Platform platform,
|
@required FrameworkRepository repository,
|
||||||
@required FileSystem fileSystem,
|
|
||||||
@required Repository repository,
|
|
||||||
String remoteName = 'origin',
|
String remoteName = 'origin',
|
||||||
}) {
|
}) {
|
||||||
final String level = argResults[kIncrement] as String;
|
final String level = argResults[kIncrement] as String;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io' as io;
|
||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
@ -31,9 +31,15 @@ class VerboseStdio extends Stdio {
|
|||||||
@required this.stdin,
|
@required this.stdin,
|
||||||
}) : assert(stdout != null), assert(stderr != null), assert(stdin != null);
|
}) : assert(stdout != null), assert(stderr != null), assert(stdin != null);
|
||||||
|
|
||||||
final Stdout stdout;
|
factory VerboseStdio.local() => VerboseStdio(
|
||||||
final Stdout stderr;
|
stdout: io.stdout,
|
||||||
final Stdin stdin;
|
stderr: io.stderr,
|
||||||
|
stdin: io.stdin,
|
||||||
|
);
|
||||||
|
|
||||||
|
final io.Stdout stdout;
|
||||||
|
final io.Stdout stderr;
|
||||||
|
final io.Stdin stdin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void printError(String message) {
|
void printError(String message) {
|
||||||
|
71
dev/tools/test/codesign_integration_test.dart
Normal file
71
dev/tools/test/codesign_integration_test.dart
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// 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:file/file.dart';
|
||||||
|
import 'package:file/local.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import 'package:dev_tools/codesign.dart' show CodesignCommand;
|
||||||
|
import 'package:dev_tools/globals.dart';
|
||||||
|
import 'package:dev_tools/repository.dart' show Checkouts;
|
||||||
|
|
||||||
|
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();
|
||||||
|
const ProcessManager processManager = LocalProcessManager();
|
||||||
|
final TestStdio stdio = TestStdio(verbose: true);
|
||||||
|
final Checkouts checkouts = Checkouts(
|
||||||
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: localFlutterRoot.parent,
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
|
);
|
||||||
|
|
||||||
|
final CommandRunner<void> runner = CommandRunner<void>('codesign-test', '')
|
||||||
|
..addCommand(
|
||||||
|
CodesignCommand(checkouts: checkouts, flutterRoot: localFlutterRoot));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runner.run(<String>[
|
||||||
|
'codesign',
|
||||||
|
'--verify',
|
||||||
|
// Only verify if the correct binaries are in the cache
|
||||||
|
'--no-signatures',
|
||||||
|
]);
|
||||||
|
} on ConductorException catch (e) {
|
||||||
|
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'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
''';
|
412
dev/tools/test/codesign_test.dart
Normal file
412
dev/tools/test/codesign_test.dart
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
// 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:dev_tools/codesign.dart';
|
||||||
|
import 'package:dev_tools/globals.dart';
|
||||||
|
import 'package:dev_tools/repository.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
||||||
|
import './common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('codesign command', () {
|
||||||
|
const String flutterRoot = '/flutter';
|
||||||
|
const String checkoutsParentDirectory = '$flutterRoot/dev/tools/';
|
||||||
|
const String flutterCache =
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache';
|
||||||
|
const String flutterBin =
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/flutter';
|
||||||
|
const String revision = 'abcd1234';
|
||||||
|
CommandRunner<void> runner;
|
||||||
|
Checkouts checkouts;
|
||||||
|
MemoryFileSystem fileSystem;
|
||||||
|
FakePlatform platform;
|
||||||
|
TestStdio stdio;
|
||||||
|
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: binariesWithEntitlements,
|
||||||
|
binariesWithoutEntitlements: 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 => await runner.run(<String>['codesign']),
|
||||||
|
throwsExceptionWith('Error! Expected operating system "macos"'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws exception if verify flag is not provided', () async {
|
||||||
|
createRunner();
|
||||||
|
expect(
|
||||||
|
() async => await runner.run(<String>['codesign']),
|
||||||
|
throwsExceptionWith(
|
||||||
|
'Sorry, but codesigning is not implemented yet. Please pass the --$kVerify flag to verify signatures'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
'--',
|
||||||
|
'file://$flutterRoot/',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
]),
|
||||||
|
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',
|
||||||
|
'--ios',
|
||||||
|
'--macos',
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: const <String>[
|
||||||
|
'find',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||||
|
'-type',
|
||||||
|
'f',
|
||||||
|
],
|
||||||
|
stdout: allBinaries.join('\n'),
|
||||||
|
),
|
||||||
|
for (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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
'--',
|
||||||
|
'file://$flutterRoot/',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
]),
|
||||||
|
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',
|
||||||
|
'--ios',
|
||||||
|
'--macos',
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: const <String>[
|
||||||
|
'find',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||||
|
'-type',
|
||||||
|
'f',
|
||||||
|
],
|
||||||
|
stdout: allBinaries.join('\n'),
|
||||||
|
),
|
||||||
|
for (String bin in allBinaries)
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['file', '--mime-type', '-b', bin],
|
||||||
|
stdout: 'application/x-mach-binary',
|
||||||
|
),
|
||||||
|
...codesignCheckCommands,
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
() async => await 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',
|
||||||
|
'--',
|
||||||
|
'file://$flutterRoot/',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
]),
|
||||||
|
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',
|
||||||
|
'--ios',
|
||||||
|
'--macos',
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: const <String>[
|
||||||
|
'find',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||||
|
'-type',
|
||||||
|
'f',
|
||||||
|
],
|
||||||
|
stdout: allBinaries.join('\n'),
|
||||||
|
),
|
||||||
|
for (String bin in allBinaries)
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['file', '--mime-type', '-b', bin],
|
||||||
|
stdout: 'application/x-mach-binary',
|
||||||
|
),
|
||||||
|
...codesignCheckCommands,
|
||||||
|
]);
|
||||||
|
expect(
|
||||||
|
() async => await 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',
|
||||||
|
'--',
|
||||||
|
'file://$flutterRoot/',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
]),
|
||||||
|
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',
|
||||||
|
'--ios',
|
||||||
|
'--macos',
|
||||||
|
]),
|
||||||
|
FakeCommand(
|
||||||
|
command: const <String>[
|
||||||
|
'find',
|
||||||
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache',
|
||||||
|
'-type',
|
||||||
|
'f',
|
||||||
|
],
|
||||||
|
stdout: allBinaries.join('\n'),
|
||||||
|
),
|
||||||
|
for (String bin in allBinaries)
|
||||||
|
FakeCommand(
|
||||||
|
command: <String>['file', '--mime-type', '-b', bin],
|
||||||
|
stdout: 'application/x-mach-binary',
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
try {
|
||||||
|
await runner.run(<String>[
|
||||||
|
'codesign',
|
||||||
|
'--$kVerify',
|
||||||
|
'--no-$kSignatures',
|
||||||
|
'--$kRevision',
|
||||||
|
revision,
|
||||||
|
]);
|
||||||
|
} on ConductorException {
|
||||||
|
//print(stdio.error);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
processManager.hasRemainingExpectations,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeCodesignCommand extends CodesignCommand {
|
||||||
|
FakeCodesignCommand({
|
||||||
|
@required Checkouts checkouts,
|
||||||
|
@required this.binariesWithEntitlements,
|
||||||
|
@required this.binariesWithoutEntitlements,
|
||||||
|
@required Directory flutterRoot,
|
||||||
|
}) : super(checkouts: checkouts, flutterRoot: flutterRoot);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final List<String> binariesWithEntitlements;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final List<String> binariesWithoutEntitlements;
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||||
import 'package:test/test.dart' as test_package show TypeMatcher;
|
import 'package:test/test.dart' as test_package show TypeMatcher;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import 'package:file/local.dart';
|
|||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import 'package:dev_tools/globals.dart';
|
||||||
import 'package:dev_tools/roll_dev.dart' show rollDev;
|
import 'package:dev_tools/roll_dev.dart' show rollDev;
|
||||||
import 'package:dev_tools/repository.dart';
|
import 'package:dev_tools/repository.dart';
|
||||||
import 'package:dev_tools/version.dart';
|
import 'package:dev_tools/version.dart';
|
||||||
@ -22,8 +23,8 @@ void main() {
|
|||||||
const String usageString = 'Usage: flutter conductor.';
|
const String usageString = 'Usage: flutter conductor.';
|
||||||
|
|
||||||
Checkouts checkouts;
|
Checkouts checkouts;
|
||||||
Repository frameworkUpstream;
|
FrameworkRepository frameworkUpstream;
|
||||||
Repository framework;
|
FrameworkRepository framework;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
platform = const LocalPlatform();
|
platform = const LocalPlatform();
|
||||||
@ -32,22 +33,20 @@ void main() {
|
|||||||
stdio = TestStdio(verbose: true);
|
stdio = TestStdio(verbose: true);
|
||||||
checkouts = Checkouts(
|
checkouts = Checkouts(
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
|
parentDirectory: localFlutterRoot.parent,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
processManager: processManager,
|
processManager: processManager,
|
||||||
|
stdio: stdio,
|
||||||
);
|
);
|
||||||
|
|
||||||
frameworkUpstream = checkouts.addRepo(
|
frameworkUpstream = FrameworkRepository(checkouts, localUpstream: true);
|
||||||
repoType: RepositoryType.framework,
|
|
||||||
name: 'framework-upstream',
|
|
||||||
stdio: stdio,
|
|
||||||
platform: platform,
|
|
||||||
localUpstream: true,
|
|
||||||
fileSystem: fileSystem,
|
|
||||||
useExistingCheckout: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
// This repository has [frameworkUpstream] set as its push/pull remote.
|
// This repository has [frameworkUpstream] set as its push/pull remote.
|
||||||
framework = frameworkUpstream.cloneRepository('test-framework');
|
framework = FrameworkRepository(
|
||||||
|
checkouts,
|
||||||
|
name: 'test-framework',
|
||||||
|
upstream: 'file://${frameworkUpstream.checkoutDirectory.path}/',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('increment m', () {
|
test('increment m', () {
|
||||||
@ -68,8 +67,6 @@ void main() {
|
|||||||
usage: usageString,
|
usage: usageString,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: framework,
|
repository: framework,
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
@ -107,8 +104,6 @@ void main() {
|
|||||||
usage: usageString,
|
usage: usageString,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: framework,
|
repository: framework,
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
|
@ -24,7 +24,7 @@ void main() {
|
|||||||
FakeArgResults fakeArgResults;
|
FakeArgResults fakeArgResults;
|
||||||
MemoryFileSystem fileSystem;
|
MemoryFileSystem fileSystem;
|
||||||
TestStdio stdio;
|
TestStdio stdio;
|
||||||
Repository repo;
|
FrameworkRepository repo;
|
||||||
Checkouts checkouts;
|
Checkouts checkouts;
|
||||||
FakePlatform platform;
|
FakePlatform platform;
|
||||||
FakeProcessManager processManager;
|
FakeProcessManager processManager;
|
||||||
@ -39,12 +39,9 @@ void main() {
|
|||||||
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
|
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
|
||||||
platform: platform,
|
platform: platform,
|
||||||
processManager: processManager,
|
processManager: processManager,
|
||||||
);
|
|
||||||
repo = checkouts.addRepo(
|
|
||||||
platform: platform,
|
|
||||||
repoType: RepositoryType.framework,
|
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
);
|
);
|
||||||
|
repo = FrameworkRepository(checkouts);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns false if level not provided', () {
|
test('returns false if level not provided', () {
|
||||||
@ -56,8 +53,6 @@ void main() {
|
|||||||
expect(
|
expect(
|
||||||
rollDev(
|
rollDev(
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
@ -75,8 +70,6 @@ void main() {
|
|||||||
expect(
|
expect(
|
||||||
rollDev(
|
rollDev(
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
@ -92,8 +85,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -115,8 +113,6 @@ void main() {
|
|||||||
try {
|
try {
|
||||||
rollDev(
|
rollDev(
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
@ -137,8 +133,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -187,8 +188,6 @@ void main() {
|
|||||||
rollDev(
|
rollDev(
|
||||||
usage: usage,
|
usage: usage,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
),
|
),
|
||||||
@ -206,8 +205,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -267,8 +271,6 @@ void main() {
|
|||||||
() => rollDev(
|
() => rollDev(
|
||||||
usage: usage,
|
usage: usage,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
),
|
),
|
||||||
@ -283,8 +285,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -333,8 +340,6 @@ void main() {
|
|||||||
() => rollDev(
|
() => rollDev(
|
||||||
usage: usage,
|
usage: usage,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
),
|
),
|
||||||
@ -353,8 +358,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -410,8 +420,6 @@ void main() {
|
|||||||
expect(
|
expect(
|
||||||
() => rollDev(
|
() => rollDev(
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
@ -427,8 +435,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -501,8 +514,6 @@ void main() {
|
|||||||
rollDev(
|
rollDev(
|
||||||
usage: usage,
|
usage: usage,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
),
|
),
|
||||||
@ -517,8 +528,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -595,8 +611,6 @@ void main() {
|
|||||||
rollDev(
|
rollDev(
|
||||||
usage: usage,
|
usage: usage,
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
),
|
),
|
||||||
@ -611,8 +625,13 @@ void main() {
|
|||||||
'clone',
|
'clone',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
]),
|
]),
|
||||||
|
const FakeCommand(command: <String>[
|
||||||
|
'git',
|
||||||
|
'rev-parse',
|
||||||
|
'HEAD',
|
||||||
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'remote',
|
'remote',
|
||||||
@ -684,8 +703,6 @@ void main() {
|
|||||||
expect(
|
expect(
|
||||||
rollDev(
|
rollDev(
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
fileSystem: fileSystem,
|
|
||||||
platform: platform,
|
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
|
Loading…
Reference in New Issue
Block a user