Unverified Commit 353add83 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Re-land codesign test improvement (#73997)

parent 35000147
......@@ -2,184 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:io';
import 'dart:io' as io;
import 'package:path/path.dart' as path;
String get repoRoot => path.normalize(path.join(path.dirname(Platform.script.toFilePath()), '..', '..'));
String get cacheDirectory => path.normalize(path.join(repoRoot, 'bin', 'cache'));
/// Check mime-type of file at [filePath] to determine if it is binary
bool isBinary(String filePath) {
final ProcessResult result = Process.runSync(
'file',
<String>[
'--mime-type',
'-b', // is binary
filePath,
],
);
return (result.stdout as String).contains('application/x-mach-binary');
}
/// Find every binary file in the given [rootDirectory]
List<String> findBinaryPaths([String rootDirectory]) {
rootDirectory ??= cacheDirectory;
final ProcessResult result = Process.runSync(
'find',
<String>[
rootDirectory,
'-type',
'f',
'-perm',
'+111', // is executable
],
// TODO(fujino): delete this script once PR #71244 lands on stable.
void main(List<String> args) {
final String scriptPath = io.Platform.script.toFilePath();
final String scriptDir = path.dirname(scriptPath);
final String repoRoot = path.normalize(path.join(scriptDir, '..', '..'));
final io.ProcessResult result = io.Process.runSync(
path.join(repoRoot, 'dev', 'tools', 'bin', 'conductor'),
<String>['codesign', '--verify'],
);
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.');
if (result.exitCode != 0) {
print('codesign script exited with code $result.exitCode');
print('stdout:\n${result.stdout}\n');
print('stderr:\n${result.stderr}\n');
io.exit(1);
}
print('codesign script succeeded.');
print('stdout:\n${result.stdout}');
}
......@@ -662,7 +662,7 @@ Future<void> _runFrameworkTests() async {
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'));
await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'));
await _pubRunTest(path.join(flutterRoot, 'dev', 'snippets'));
await _pubRunTest(path.join(flutterRoot, 'dev', 'tools'));
await _pubRunTest(path.join(flutterRoot, 'dev', 'tools'), forceSingleCore: true);
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'));
......
......@@ -10,15 +10,17 @@
import 'dart:io' as io;
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/local.dart';
import 'package:platform/platform.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 ProcessManager processManager = LocalProcessManager();
const Platform platform = LocalPlatform();
......@@ -29,9 +31,12 @@ void main(List<String> args) {
);
final Checkouts checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: localFlutterRoot.parent,
platform: platform,
processManager: processManager,
stdio: stdio,
);
final CommandRunner<void> runner = CommandRunner<void>(
'conductor',
'A tool for coordinating Flutter releases.',
......@@ -39,17 +44,16 @@ void main(List<String> args) {
);
<Command<void>>[
RollDev(
RollDevCommand(
checkouts: checkouts,
fileSystem: fileSystem,
platform: platform,
repository: checkouts.addRepo(
fileSystem: fileSystem,
platform: platform,
repoType: RepositoryType.framework,
stdio: stdio,
),
stdio: stdio,
),
CodesignCommand(
checkouts: checkouts,
flutterRoot: localFlutterRoot,
),
].forEach(runner.addCommand);
if (!assertsEnabled()) {
......@@ -58,20 +62,9 @@ void main(List<String> args) {
}
try {
runner.run(args);
await runner.run(args);
} on Exception catch (e) {
stdio.printError(e.toString());
io.exit(1);
}
}
bool assertsEnabled() {
// Verify asserts enabled
bool assertsEnabled = false;
assert(() {
assertsEnabled = true;
return true;
}());
return assertsEnabled;
}
This diff is collapsed.
......@@ -45,6 +45,7 @@ class Git {
return processManager.runSync(
<String>['git', ...args],
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
// 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 kCommit = 'commit';
const String kRemoteName = 'remote';
......@@ -24,3 +28,61 @@ String stdoutToString(dynamic input) {
final String str = input as String;
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;
}
This diff is collapsed.
......@@ -14,12 +14,12 @@ import './stdio.dart';
import './version.dart';
/// Create a new dev release without cherry picks.
class RollDev extends Command<void> {
RollDev({
this.fileSystem,
this.platform,
this.repository,
this.stdio,
class RollDevCommand extends Command<void> {
RollDevCommand({
@required this.checkouts,
@required this.fileSystem,
@required this.platform,
@required this.stdio,
}) {
argParser.addOption(
kIncrement,
......@@ -60,10 +60,10 @@ class RollDev extends Command<void> {
argParser.addFlag(kYes, negatable: false, abbr: 'y', help: 'Skip the confirmation prompt.');
}
final Checkouts checkouts;
final FileSystem fileSystem;
final Platform platform;
final Stdio stdio;
final Repository repository;
@override
String get name => 'roll-dev';
......@@ -76,9 +76,7 @@ class RollDev extends Command<void> {
void run() {
rollDev(
argResults: argResults,
fileSystem: fileSystem,
platform: platform,
repository: repository,
repository: FrameworkRepository(checkouts),
stdio: stdio,
usage: argParser.usage,
);
......@@ -93,9 +91,7 @@ bool rollDev({
@required String usage,
@required ArgResults argResults,
@required Stdio stdio,
@required Platform platform,
@required FileSystem fileSystem,
@required Repository repository,
@required FrameworkRepository repository,
String remoteName = 'origin',
}) {
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
// found in the LICENSE file.
import 'dart:io';
import 'dart:io' as io;
import 'package:meta/meta.dart';
......@@ -31,9 +31,15 @@ class VerboseStdio extends Stdio {
@required this.stdin,
}) : assert(stdout != null), assert(stderr != null), assert(stdin != null);
final Stdout stdout;
final Stdout stderr;
final Stdin stdin;
factory VerboseStdio.local() => VerboseStdio(
stdout: io.stdout,
stderr: io.stderr,
stdin: io.stdin,
);
final io.Stdout stdout;
final io.Stdout stderr;
final io.Stdin stdin;
@override
void printError(String message) {
......
// 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(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'),
});
}
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.
''';
This diff is collapsed.
......@@ -4,6 +4,7 @@
import 'dart:io';
import 'package:file/file.dart';
import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
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:process/process.dart';
import 'package:dev_tools/globals.dart';
import 'package:dev_tools/roll_dev.dart' show rollDev;
import 'package:dev_tools/repository.dart';
import 'package:dev_tools/version.dart';
......@@ -22,8 +23,8 @@ void main() {
const String usageString = 'Usage: flutter conductor.';
Checkouts checkouts;
Repository frameworkUpstream;
Repository framework;
FrameworkRepository frameworkUpstream;
FrameworkRepository framework;
setUp(() {
platform = const LocalPlatform();
......@@ -32,22 +33,20 @@ void main() {
stdio = TestStdio(verbose: true);
checkouts = Checkouts(
fileSystem: fileSystem,
parentDirectory: localFlutterRoot.parent,
platform: platform,
processManager: processManager,
);
frameworkUpstream = checkouts.addRepo(
repoType: RepositoryType.framework,
name: 'framework-upstream',
stdio: stdio,
platform: platform,
localUpstream: true,
fileSystem: fileSystem,
useExistingCheckout: false,
);
frameworkUpstream = FrameworkRepository(checkouts, localUpstream: true);
// 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', () {
......@@ -68,8 +67,6 @@ void main() {
usage: usageString,
argResults: fakeArgResults,
stdio: stdio,
fileSystem: fileSystem,
platform: platform,
repository: framework,
),
true,
......@@ -107,8 +104,6 @@ void main() {
usage: usageString,
argResults: fakeArgResults,
stdio: stdio,
fileSystem: fileSystem,
platform: platform,
repository: framework,
),
true,
......
......@@ -24,7 +24,7 @@ void main() {
FakeArgResults fakeArgResults;
MemoryFileSystem fileSystem;
TestStdio stdio;
Repository repo;
FrameworkRepository repo;
Checkouts checkouts;
FakePlatform platform;
FakeProcessManager processManager;
......@@ -39,12 +39,9 @@ void main() {
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
platform: platform,
processManager: processManager,
);
repo = checkouts.addRepo(
platform: platform,
repoType: RepositoryType.framework,
stdio: stdio,
);
repo = FrameworkRepository(checkouts);
});
test('returns false if level not provided', () {
......@@ -56,8 +53,6 @@ void main() {
expect(
rollDev(
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
usage: usage,
......@@ -75,8 +70,6 @@ void main() {
expect(
rollDev(
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
usage: usage,
......@@ -92,8 +85,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -115,8 +113,6 @@ void main() {
try {
rollDev(
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
usage: usage,
......@@ -137,8 +133,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -187,8 +188,6 @@ void main() {
rollDev(
usage: usage,
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
),
......@@ -206,8 +205,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -267,8 +271,6 @@ void main() {
() => rollDev(
usage: usage,
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
),
......@@ -283,8 +285,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -333,8 +340,6 @@ void main() {
() => rollDev(
usage: usage,
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
),
......@@ -353,8 +358,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -410,8 +420,6 @@ void main() {
expect(
() => rollDev(
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
usage: usage,
......@@ -427,8 +435,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -501,8 +514,6 @@ void main() {
rollDev(
usage: usage,
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
),
......@@ -517,8 +528,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -595,8 +611,6 @@ void main() {
rollDev(
usage: usage,
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
),
......@@ -611,8 +625,13 @@ void main() {
'clone',
'--',
kUpstreamRemote,
'${checkoutsParentDirectory}checkouts/framework',
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
]),
const FakeCommand(command: <String>[
'git',
'rev-parse',
'HEAD',
], stdout: commit),
const FakeCommand(command: <String>[
'git',
'remote',
......@@ -684,8 +703,6 @@ void main() {
expect(
rollDev(
argResults: fakeArgResults,
fileSystem: fileSystem,
platform: platform,
repository: repo,
stdio: stdio,
usage: usage,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment