Skip to content

[hooks_runner] Add TimelineEvents #2327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkgs/hooks_runner/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.20.2

* Add `dart:developer` `TimelineEvent`s to enable performance tracing for
hook invocations.

## 0.20.1

* Bump the SDK constraint to at least the one from `package:hooks` to fix
Expand Down
97 changes: 61 additions & 36 deletions pkgs/hooks_runner/lib/src/build_runner/build_planner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:convert';
import 'dart:developer';

import 'package:file/file.dart';
import 'package:graphs/graphs.dart' as graphs;
Expand All @@ -19,13 +20,16 @@ typedef BuildPlan = List<Package>;

@internal
class NativeAssetsBuildPlanner {
final TimelineTask task;

final PackageGraph packageGraph;
final Uri dartExecutable;
final Logger logger;
final PackageLayout packageLayout;
final FileSystem fileSystem;

NativeAssetsBuildPlanner._({
required this.task,
required this.packageGraph,
required this.dartExecutable,
required this.logger,
Expand All @@ -39,11 +43,12 @@ class NativeAssetsBuildPlanner {
required Logger logger,
required PackageLayout packageLayout,
required FileSystem fileSystem,
TimelineTask? task,
}) async {
final packageGraphJsonFile = fileSystem.file(
packageConfigUri.resolve('package_graph.json'),
);
assert(packageGraphJsonFile.existsSync());
assert(await packageGraphJsonFile.exists());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this actually work? I was under the impression that code in asserts has to be synchronous? A quick experiment in dartpad with assert(await Future.value(false)); also seems to indicate that it wouldn't fire as expected...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, never mind. This works. Dartpad doesn't have asserts enabled it seems.

final packageGraphJson = await packageGraphJsonFile.readAsString();
final packageGraph = PackageGraph.fromPackageGraphJsonString(
packageGraphJson,
Expand All @@ -57,17 +62,22 @@ class NativeAssetsBuildPlanner {
logger: logger,
packageLayout: packageLayout,
fileSystem: fileSystem,
task: task ?? TimelineTask(),
);
}

/// All packages in [PackageLayout.packageConfig] with native assets.
///
/// Whether a package has native assets is defined by whether it contains
/// a `hook/build.dart` or `hook/link.dart`.
Future<List<Package>> packagesWithHook(Hook hook) async => switch (hook) {
Hook.build => _packagesWithBuildHook ??= await _runPackagesWithHook(hook),
Hook.link => _packagesWithLinkHook ??= await _runPackagesWithHook(hook),
};
Future<List<Package>> packagesWithHook(Hook hook) async => _timeAsync(
'BuildPlanner.packagesWithHook',
arguments: {'hook': hook.toString()},
() async => switch (hook) {
Hook.build => _packagesWithBuildHook ??= await _runPackagesWithHook(hook),
Hook.link => _packagesWithLinkHook ??= await _runPackagesWithHook(hook),
},
);

List<Package>? _packagesWithBuildHook;
List<Package>? _packagesWithLinkHook;
Expand Down Expand Up @@ -113,38 +123,53 @@ class NativeAssetsBuildPlanner {
/// a cyclic dependency is detected among packages with native asset build
/// hooks, the [Result] is a [Failure] containing a
/// [HooksRunnerFailure.projectConfig].
Future<Result<BuildPlan, HooksRunnerFailure>> makeBuildHookPlan() async {
if (_buildHookPlan != null) return Success(_buildHookPlan!);
final packagesWithNativeAssets = await packagesWithHook(Hook.build);
if (packagesWithNativeAssets.isEmpty) {
// Avoid calculating the package graph if there are no hooks.
return const Success([]);
}
final packageMap = {
for (final package in packagesWithNativeAssets) package.name: package,
};
final packagesToBuild = packageMap.keys.toSet();
final stronglyConnectedComponents = packageGraph.computeStrongComponents();
final result = <Package>[];
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.',
);
return const Failure(HooksRunnerFailure.projectConfig);
} else if (stronglyConnectedComponentWithNativeAssets.length == 1) {
result.add(
packageMap[stronglyConnectedComponentWithNativeAssets.single]!,
);
}
Future<Result<BuildPlan, HooksRunnerFailure>> makeBuildHookPlan() async =>
_timeAsync('BuildPlanner.makeBuildHookPlan', () async {
if (_buildHookPlan != null) return Success(_buildHookPlan!);
final packagesWithNativeAssets = await packagesWithHook(Hook.build);
if (packagesWithNativeAssets.isEmpty) {
// Avoid calculating the package graph if there are no hooks.
return const Success([]);
}
final packageMap = {
for (final package in packagesWithNativeAssets) package.name: package,
};
final packagesToBuild = packageMap.keys.toSet();
final stronglyConnectedComponents = packageGraph
.computeStrongComponents();
final result = <Package>[];
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.',
);
return const Failure(HooksRunnerFailure.projectConfig);
} else if (stronglyConnectedComponentWithNativeAssets.length == 1) {
result.add(
packageMap[stronglyConnectedComponentWithNativeAssets.single]!,
);
}
}
_buildHookPlan = result;
return Success(result);
});

Future<T> _timeAsync<T>(
String name,
Future<T> Function() function, {
Map<String, Object>? arguments,
}) async {
task.start(name, arguments: arguments);
try {
return await function();
} finally {
task.finish();
}
_buildHookPlan = result;
return Success(result);
}
}

Expand Down
Loading
Loading