blob: d885452b8b696944854357bab7a5fe3d67ef2d41 [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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';
import 'dart:io';
import 'package:bazel_worker/bazel_worker.dart';
import 'package:build/build.dart';
import 'package:meta/meta.dart';
import 'package:path/path.dart' as p;
import 'package:scratch_space/scratch_space.dart';
import 'common.dart';
import 'errors.dart';
import 'module_builder.dart';
import 'modules.dart';
import 'platform.dart';
import 'scratch_space.dart';
import 'workers.dart';
const multiRootScheme = 'org-dartlang-app';
/// A builder which can output kernel files for a given sdk.
///
/// This creates kernel files based on [moduleExtension] files, which are what
/// determine the module structure of an application.
class KernelBuilder implements Builder {
@override
final Map<String, List<String>> buildExtensions;
final String outputExtension;
final DartPlatform platform;
/// Whether this should create summary kernel files or full kernel files.
///
/// Summary files only contain the "outline" of the module - you can think of
/// this as everything but the method bodies.
final bool summaryOnly;
/// The sdk kernel file for the current platform.
final String sdkKernelPath;
/// The root directory of the platform's dart SDK.
///
/// If not provided, defaults to the directory of
/// [Platform.resolvedExecutable].
///
/// On flutter this is the path to the root of the flutter_patched_sdk
/// directory, which contains the platform kernel files.
final String platformSdk;
KernelBuilder(
{@required this.platform,
@required this.summaryOnly,
@required this.sdkKernelPath,
@required this.outputExtension,
String platformSdk})
: platformSdk = platformSdk ?? sdkDir,
buildExtensions = {
moduleExtension(platform): [outputExtension]
};
@override
Future build(BuildStep buildStep) async {
var module = Module.fromJson(
json.decode(await buildStep.readAsString(buildStep.inputId))
as Map<String, dynamic>);
try {
await _createKernel(
module: module,
buildStep: buildStep,
summaryOnly: summaryOnly,
outputExtension: outputExtension,
dartSdkDir: platformSdk,
sdkKernelPath: sdkKernelPath);
} on KernelException catch (e, s) {
log.severe(
'Error creating '
'${module.primarySource.changeExtension(outputExtension)}',
e,
s);
}
}
}
/// Creates a kernel file for [module].
Future<void> _createKernel(
{@required Module module,
@required BuildStep buildStep,
@required bool summaryOnly,
@required String outputExtension,
@required String dartSdkDir,
@required String sdkKernelPath}) async {
var request = WorkRequest();
var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);
var outputId = module.primarySource.changeExtension(outputExtension);
var outputFile = scratchSpace.fileFor(outputId);
File packagesFile;
{
var transitiveDeps = await module.computeTransitiveDependencies(buildStep);
var transitiveKernelDeps = <AssetId>[];
var transitiveSourceDeps = <AssetId>[];
await Future.wait(transitiveDeps.map((dep) => _addModuleDeps(
dep,
module,
transitiveKernelDeps,
transitiveSourceDeps,
buildStep,
outputExtension)));
var allAssetIds = Set<AssetId>()
..addAll(module.sources)
..addAll(transitiveKernelDeps)
..addAll(transitiveSourceDeps);
await scratchSpace.ensureAssets(allAssetIds, buildStep);
packagesFile = await createPackagesFile(allAssetIds);
_addRequestArguments(request, module, transitiveKernelDeps, dartSdkDir,
sdkKernelPath, outputFile, packagesFile, summaryOnly);
}
// We need to make sure and clean up the temp dir, even if we fail to compile.
try {
var analyzer = await buildStep.fetchResource(frontendDriverResource);
var response = await analyzer.doWork(request,
trackWork: (response) => buildStep
.trackStage('Kernel Generate', () => response, isExternal: true));
if (response.exitCode != EXIT_CODE_OK || !await outputFile.exists()) {
throw KernelException(
outputId, '${request.arguments.join(' ')}\n${response.output}');
}
if (response.output?.isEmpty == false) {
log.info(response.output);
}
// Copy the output back using the buildStep.
await scratchSpace.copyOutput(outputId, buildStep);
} finally {
await packagesFile.parent.delete(recursive: true);
}
}
/// Adds the source or kernel dependencies for [dependency] to
/// [transitiveKernelDeps] or [transitiveSourceDeps].
Future<void> _addModuleDeps(
Module dependency,
Module root,
List<AssetId> transitiveKernelDeps,
List<AssetId> transitiveSourceDeps,
BuildStep buildStep,
String outputExtension) async {
var kernelId = dependency.primarySource.changeExtension(outputExtension);
if (await buildStep.canRead(kernelId)) {
// If we can read the kernel file, but it depends on any module in this
// package, then we need to only provide sources for that file since its
// dependencies in this package will only be providing sources as well.
if ((await dependency.computeTransitiveDependencies(buildStep))
.any((m) => m.primarySource.package == root.primarySource.package)) {
transitiveSourceDeps.addAll(dependency.sources);
} else {
transitiveKernelDeps.add(kernelId);
}
} else {
transitiveSourceDeps.addAll(dependency.sources);
}
}
/// Fills in all the required arguments for [request] in order to compile the
/// kernel file for [module].
void _addRequestArguments(
WorkRequest request,
Module module,
Iterable<AssetId> transitiveKernelDeps,
String sdkDir,
String sdkKernelPath,
File outputFile,
File packagesFile,
bool summaryOnly) {
request.arguments.addAll([
'--dart-sdk-summary',
Uri.file(p.join(sdkDir, sdkKernelPath)).toString(),
'--output',
outputFile.path,
'--packages-file',
packagesFile.uri.toString(),
'--multi-root-scheme',
multiRootScheme,
'--exclude-non-sources',
summaryOnly ? '--summary-only' : '--no-summary-only',
]);
// Add all summaries as summary inputs.
request.arguments.addAll(transitiveKernelDeps.map((id) {
var relativePath = p.url.relative(scratchSpace.fileFor(id).uri.path,
from: scratchSpace.tempDir.uri.path);
if (summaryOnly) {
return '--input-summary=$multiRootScheme:///$relativePath';
} else {
return '--input-linked=$multiRootScheme:///$relativePath';
}
}));
request.arguments.addAll(module.sources.map((id) {
var uri = id.path.startsWith('lib')
? canonicalUriFor(id)
: '$multiRootScheme:///${id.path}';
return '--source=$uri';
}));
}