blob: d66b158cd7457050cdcaccc6d25961613e8c8b32 [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 'package:build/build.dart';
import 'package:build_config/build_config.dart';
import 'package:glob/glob.dart';
import '../generate/input_matcher.dart';
import 'package_graph.dart';
/// Like a [PackageGraph] but packages are further broken down into modules
/// based on build config.
class TargetGraph {
/// All [TargetNode]s indexed by `"$packageName:$targetName"`.
final Map<String, TargetNode> allModules;
/// All [TargetNode]s by package name.
final Map<String, List<TargetNode>> modulesByPackage;
/// The [BuildConfig] of the root package.
final BuildConfig rootPackageConfig;
TargetGraph._(this.allModules, this.modulesByPackage, this.rootPackageConfig);
static Future<TargetGraph> forPackageGraph(PackageGraph packageGraph,
{Map<String, BuildConfig> overrideBuildConfig,
List<String> defaultRootPackageWhitelist}) async {
overrideBuildConfig ??= const {};
final modulesByKey = <String, TargetNode>{};
final modulesByPackage = <String, List<TargetNode>>{};
BuildConfig rootPackageConfig;
for (final package in packageGraph.allPackages.values) {
final config = overrideBuildConfig[package.name] ??
await _packageBuildConfig(package);
List<String> defaultInclude;
if (package.isRoot) {
defaultInclude = defaultRootPackageWhitelist;
rootPackageConfig = config;
} else if (package.name == r'$sdk') {
defaultInclude = const [
'lib/dev_compiler/**.js',
'lib/_internal/**.sum',
];
} else {
defaultInclude = const ['lib/**'];
}
final nodes = config.buildTargets.values.map((target) =>
TargetNode(target, package, defaultInclude: defaultInclude));
for (final node in nodes) {
modulesByKey[node.target.key] = node;
modulesByPackage.putIfAbsent(node.target.package, () => []).add(node);
}
}
return TargetGraph._(modulesByKey, modulesByPackage, rootPackageConfig);
}
/// Whether or not [id] is included in the sources of any target in the graph.
bool anyMatchesAsset(AssetId id) =>
modulesByPackage[id.package]?.any((t) => t.matchesSource(id)) ?? false;
}
class TargetNode {
final BuildTarget target;
final PackageNode package;
List<Glob> get sourceIncludes => _sourcesMatcher.includeGlobs;
final InputMatcher _sourcesMatcher;
TargetNode(this.target, this.package, {List<String> defaultInclude})
: _sourcesMatcher =
InputMatcher(target.sources, defaultInclude: defaultInclude);
bool excludesSource(AssetId id) => _sourcesMatcher.excludes(id);
bool matchesSource(AssetId id) => _sourcesMatcher.matches(id);
@override
String toString() => target.key;
}
Future<BuildConfig> _packageBuildConfig(PackageNode package) async {
final dependencyNames = package.dependencies.map((n) => n.name);
if (package.path == null) {
return BuildConfig.useDefault(package.name, dependencyNames);
}
try {
return await BuildConfig.fromBuildConfigDir(
package.name, dependencyNames, package.path);
} on ArgumentError catch (e) {
throw BuildConfigParseException(package.name, e);
}
}
class BuildConfigParseException implements Exception {
final String packageName;
final dynamic exception;
BuildConfigParseException(this.packageName, this.exception);
}