blob: 492f8a53372aab1941f23ce3f3069d0386b92335 [file] [log] [blame]
// Copyright (c) 2023, 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:convert';
import 'dart:io';
import 'package:graphs/graphs.dart' as graphs;
import 'package:logging/logging.dart';
import 'package:package_config/package_config.dart';
class NativeAssetsBuildPlanner {
final PackageGraph packageGraph;
final List<Package> packagesWithNativeAssets;
final Uri dartExecutable;
final Logger logger;
NativeAssetsBuildPlanner({
required this.packageGraph,
required this.packagesWithNativeAssets,
required this.dartExecutable,
required this.logger,
});
static Future<NativeAssetsBuildPlanner> fromRootPackageRoot({
required Uri rootPackageRoot,
required List<Package> packagesWithNativeAssets,
required Uri dartExecutable,
required Logger logger,
}) async {
final result = await Process.run(
dartExecutable.toFilePath(),
[
'pub',
'deps',
'--json',
],
workingDirectory: rootPackageRoot.toFilePath(),
);
final packageGraph =
PackageGraph.fromPubDepsJsonString(result.stdout as String);
return NativeAssetsBuildPlanner(
packageGraph: packageGraph,
packagesWithNativeAssets: packagesWithNativeAssets,
dartExecutable: dartExecutable,
logger: logger,
);
}
(List<Package> packages, bool success) plan({
String? runPackageName,
}) {
final PackageGraph packageGraph;
if (runPackageName != null) {
packageGraph = this.packageGraph.subGraph(runPackageName);
} else {
packageGraph = this.packageGraph;
}
final packageMap = {
for (final package in packagesWithNativeAssets) package.name: package
};
final packagesToBuild = packageMap.keys.toSet();
final stronglyConnectedComponents = packageGraph.computeStrongComponents();
final result = <Package>[];
var success = true;
for (final stronglyConnectedComponent in stronglyConnectedComponents) {
final stronglyConnectedComponentWithNativeAssets = [
for (final packageName in stronglyConnectedComponent)
if (packagesToBuild.contains(packageName)) packageName
];
if (stronglyConnectedComponentWithNativeAssets.length > 1) {
logger.severe(
'Cyclic dependency for native asset builds in the following '
'packages: $stronglyConnectedComponentWithNativeAssets.',
);
success = false;
} else if (stronglyConnectedComponentWithNativeAssets.length == 1) {
result.add(
packageMap[stronglyConnectedComponentWithNativeAssets.single]!);
}
}
return (result, success);
}
}
class PackageGraph {
final Map<String, List<String>> map;
PackageGraph(this.map);
/// Construct a graph from the JSON produced by `dart pub deps --json`.
factory PackageGraph.fromPubDepsJsonString(String json) =>
PackageGraph.fromPubDepsJson(jsonDecode(json) as Map<dynamic, dynamic>);
/// Construct a graph from the JSON produced by `dart pub deps --json`.
factory PackageGraph.fromPubDepsJson(Map<dynamic, dynamic> map) {
final result = <String, List<String>>{};
final packages = map['packages'] as List<dynamic>;
for (final package in packages) {
final package_ = package as Map<dynamic, dynamic>;
final name = package_['name'] as String;
final dependencies = (package_['dependencies'] as List<dynamic>)
.whereType<String>()
.toList();
result[name] = dependencies;
}
return PackageGraph(result);
}
Iterable<String> neighborsOf(String vertex) => map[vertex] ?? [];
Iterable<String> get vertices => map.keys;
List<List<String>> computeStrongComponents() =>
graphs.stronglyConnectedComponents(vertices, neighborsOf);
PackageGraph subGraph(String rootPackageName) {
if (!vertices.contains(rootPackageName)) {
// Some downstream tooling requested a package that doesn't exist.
// This will likely lead to an error, so avoid building native assets.
return PackageGraph({});
}
final subgraphVertices = [
...graphs.transitiveClosure(vertices, neighborsOf)[rootPackageName]!,
rootPackageName,
];
return PackageGraph({
for (final vertex in map.keys)
if (subgraphVertices.contains(vertex))
vertex: [
for (final neighbor in map[vertex]!)
if (subgraphVertices.contains(neighbor)) neighbor,
]
});
}
}