blob: f5f7f8a5c470ba284c071718864d13a1476c135c [file] [log] [blame]
// Copyright 2017 The Chromium 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:async';
import 'dart:convert' show JSON;
import 'package:crypto/crypto.dart' show md5;
import 'package:meta/meta.dart';
import '../artifacts.dart';
import '../build_info.dart';
import '../globals.dart';
import 'context.dart';
import 'file_system.dart';
import 'process.dart';
GenSnapshot get genSnapshot => context.putIfAbsent(GenSnapshot, () => const GenSnapshot());
class GenSnapshot {
const GenSnapshot();
Future<int> run({
@required TargetPlatform targetPlatform,
@required BuildMode buildMode,
@required String packagesPath,
@required String depfilePath,
Iterable<String> additionalArgs: const <String>[],
}) {
final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData);
final List<String> args = <String>[
'--assert_initializer',
'--await_is_keyword',
'--causal_async_stacks',
'--vm_snapshot_data=$vmSnapshotData',
'--isolate_snapshot_data=$isolateSnapshotData',
'--packages=$packagesPath',
'--dependencies=$depfilePath',
'--print_snapshot_sizes',
]..addAll(additionalArgs);
final String snapshotterPath = artifacts.getArtifactPath(Artifact.genSnapshot, targetPlatform, buildMode);
return runCommandAndStreamOutput(<String>[snapshotterPath]..addAll(args));
}
}
/// A collection of checksums for a set of input files.
///
/// This class can be used during build actions to compute a checksum of the
/// build action inputs, and if unchanged from the previous build, skip the
/// build step. This assumes that build outputs are strictly a product of the
/// input files.
class Checksum {
Checksum.fromFiles(Set<String> inputPaths) : _checksums = <String, String>{} {
final Iterable<File> files = inputPaths.map(fs.file);
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
if (missingInputs.isNotEmpty)
throw new ArgumentError('Missing input files:\n' + missingInputs.join('\n'));
for (File file in files) {
final List<int> bytes = file.readAsBytesSync();
_checksums[file.path] = md5.convert(bytes).toString();
}
}
Checksum.fromJson(String json) : _checksums = JSON.decode(json);
final Map<String, String> _checksums;
String toJson() => JSON.encode(_checksums);
@override
bool operator==(dynamic other) {
return other is Checksum &&
_checksums.length == other._checksums.length &&
_checksums.keys.every((String key) => _checksums[key] == other._checksums[key]);
}
@override
int get hashCode => _checksums.hashCode;
}
final RegExp _separatorExpr = new RegExp(r'([^\\]) ');
final RegExp _escapeExpr = new RegExp(r'\\(.)');
/// Parses a VM snapshot dependency file.
///
/// Snapshot dependency files are a single line mapping the output snapshot to a
/// space-separated list of input files used to generate that output. Spaces and
/// backslashes are escaped with a backslash. e.g,
///
/// outfile : file1.dart fil\\e2.dart fil\ e3.dart
///
/// will return a set containing: 'file1.dart', 'fil\e2.dart', 'fil e3.dart'.
Future<Set<String>> readDepfile(String depfilePath) async {
// Depfile format:
// outfile1 outfile2 : file1.dart file2.dart file3.dart
final String contents = await fs.file(depfilePath).readAsString();
final String dependencies = contents.split(': ')[1];
return dependencies
.replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
.split('\n')
.map((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
.where((String path) => path.isNotEmpty)
.toSet();
}
/// Dart snapshot builder.
///
/// Builds Dart snapshots in one of three modes:
/// * Script snapshot: architecture-independent snapshot of a Dart script core
/// libraries.
/// * AOT snapshot: architecture-specific ahead-of-time compiled snapshot
/// suitable for loading with `mmap`.
/// * Assembly AOT snapshot: architecture-specific ahead-of-time compile to
/// assembly suitable for compilation as a static or dynamic library.
class Snapshotter {
/// Builds an architecture-independent snapshot of the specified script.
Future<int> buildScriptSnapshot({
@required String mainPath,
@required String snapshotPath,
@required String depfilePath,
@required String packagesPath
}) async {
final List<String> args = <String>[
'--snapshot_kind=script',
'--script_snapshot=$snapshotPath',
mainPath,
];
final String checksumsPath = '$depfilePath.checksums';
final int exitCode = await _build(
outputSnapshotPath: snapshotPath,
packagesPath: packagesPath,
snapshotArgs: args,
depfilePath: depfilePath,
mainPath: mainPath,
checksumsPath: checksumsPath,
);
if (exitCode != 0)
return exitCode;
await _writeChecksum(snapshotPath, depfilePath, mainPath, checksumsPath);
return exitCode;
}
/// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script.
Future<Null> buildAotSnapshot() async {
throw new UnimplementedError('AOT snapshotting not yet implemented');
}
Future<int> _build({
@required List<String> snapshotArgs,
@required String outputSnapshotPath,
@required String packagesPath,
@required String depfilePath,
@required String mainPath,
@required String checksumsPath,
}) async {
if (!await _isBuildRequired(outputSnapshotPath, depfilePath, mainPath, checksumsPath)) {
printTrace('Skipping snapshot build. Checksums match.');
return 0;
}
// Build the snapshot.
final int exitCode = await genSnapshot.run(
targetPlatform: null,
buildMode: BuildMode.debug,
packagesPath: packagesPath,
depfilePath: depfilePath,
additionalArgs: snapshotArgs,
);
if (exitCode != 0)
return exitCode;
_writeChecksum(outputSnapshotPath, depfilePath, mainPath, checksumsPath);
return 0;
}
Future<bool> _isBuildRequired(String outputSnapshotPath, String depfilePath, String mainPath, String checksumsPath) async {
final File checksumFile = fs.file(checksumsPath);
final File outputSnapshotFile = fs.file(outputSnapshotPath);
final File depfile = fs.file(depfilePath);
if (!outputSnapshotFile.existsSync() || !depfile.existsSync() || !checksumFile.existsSync())
return true;
try {
if (checksumFile.existsSync()) {
final Checksum oldChecksum = new Checksum.fromJson(await checksumFile.readAsString());
final Set<String> checksumPaths = await readDepfile(depfilePath)
..addAll(<String>[outputSnapshotPath, mainPath]);
final Checksum newChecksum = new Checksum.fromFiles(checksumPaths);
return oldChecksum != newChecksum;
}
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
printTrace('Error during snapshot checksum output: $e\n$s');
}
return true;
}
Future<Null> _writeChecksum(String outputSnapshotPath, String depfilePath, String mainPath, String checksumsPath) async {
try {
final Set<String> checksumPaths = await readDepfile(depfilePath)
..addAll(<String>[outputSnapshotPath, mainPath]);
final Checksum checksum = new Checksum.fromFiles(checksumPaths);
await fs.file(checksumsPath).writeAsString(checksum.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
print('Error during snapshot checksum output: $e\n$s');
}
}
}