Skip to content

Commit 58b6279

Browse files
committed
[native_assets_builder] Use XXH3 hashing
1 parent 4069de3 commit 58b6279

File tree

7 files changed

+389
-65
lines changed

7 files changed

+389
-65
lines changed

pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ import 'package:logging/logging.dart';
1010
import 'package:native_assets_cli/native_assets_cli_internal.dart';
1111
import 'package:package_config/package_config.dart';
1212

13+
import '../file_system_cache/file_system_cache.dart';
1314
import '../locking/locking.dart';
1415
import '../model/build_dry_run_result.dart';
1516
import '../model/build_result.dart';
1617
import '../model/hook_result.dart';
1718
import '../model/link_result.dart';
1819
import '../package_layout/package_layout.dart';
19-
import '../utils/file.dart';
2020
import '../utils/run_process.dart';
21-
import '../utils/uri.dart';
2221
import 'build_planner.dart';
2322

2423
typedef DependencyMetadata = Map<String, Metadata>;
@@ -451,6 +450,8 @@ class NativeAssetsBuildRunner {
451450
return hookResult;
452451
}
453452

453+
// TODO(https://github.com/dart-lang/native/issues/32): Rerun hook if
454+
// environment variables change.
454455
Future<HookOutput?> _runHookForPackageCached(
455456
Hook hook,
456457
HookConfig config,
@@ -473,7 +474,7 @@ class NativeAssetsBuildRunner {
473474
final (
474475
compileSuccess,
475476
hookKernelFile,
476-
hookLastSourceChange,
477+
hookCacheFile,
477478
) = await _compileHookForPackageCached(
478479
config.packageName,
479480
config.outputDirectory,
@@ -488,7 +489,13 @@ class NativeAssetsBuildRunner {
488489

489490
final buildOutputFile =
490491
File.fromUri(config.outputDirectory.resolve(hook.outputName));
491-
if (buildOutputFile.existsSync()) {
492+
final cacheFile = File.fromUri(
493+
config.outputDirectory
494+
.resolve('../dependencies.file_system_cache.json'),
495+
);
496+
final cache = FileSystemCache(cacheFile: cacheFile);
497+
final cacheCutoffTime = DateTime.now();
498+
if (buildOutputFile.existsSync() && cacheFile.existsSync()) {
492499
late final HookOutput output;
493500
try {
494501
output = _readHookOutputFromUri(hook, buildOutputFile);
@@ -503,17 +510,13 @@ ${e.message}
503510
return null;
504511
}
505512

506-
final lastBuilt = output.timestamp.roundDownToSeconds();
507-
final dependenciesLastChange =
508-
await Dependencies(output.dependencies).lastModified();
509-
if (lastBuilt.isAfter(dependenciesLastChange) &&
510-
lastBuilt.isAfter(hookLastSourceChange)) {
513+
await cache.readCacheFile();
514+
final changedFile = await cache.findOutdatedFileSystemEntity();
515+
if (changedFile == null) {
511516
logger.info(
512517
[
513518
'Skipping ${hook.name} for ${config.packageName} in $outDir.',
514-
'Last build on $lastBuilt.',
515-
'Last dependencies change on $dependenciesLastChange.',
516-
'Last hook change on $hookLastSourceChange.',
519+
'Last build on ${output.timestamp}.',
517520
].join(' '),
518521
);
519522
// All build flags go into [outDir]. Therefore we do not have to
@@ -522,7 +525,7 @@ ${e.message}
522525
}
523526
}
524527

525-
return await _runHookForPackage(
528+
final result = await _runHookForPackage(
526529
hook,
527530
config,
528531
validator,
@@ -533,6 +536,26 @@ ${e.message}
533536
hookKernelFile,
534537
packageLayout,
535538
);
539+
if (result == null) {
540+
if (await cacheFile.exists()) {
541+
await cacheFile.delete();
542+
}
543+
} else {
544+
cache.reset();
545+
final modifiedDuringBuild = await cache.hashFiles(
546+
[
547+
...result.dependencies,
548+
// Also depend on the hook source code.
549+
hookCacheFile.uri,
550+
],
551+
validBeforeLastModified: cacheCutoffTime,
552+
);
553+
await cache.persist();
554+
if (modifiedDuringBuild != null) {
555+
logger.severe('File modified during build. Build must be rerun.');
556+
}
557+
}
558+
return result;
536559
},
537560
);
538561
}
@@ -644,7 +667,10 @@ ${e.message}
644667
/// It does not reuse the cached kernel for different configs due to
645668
/// reentrancy requirements. For more info see:
646669
/// https://github.com/dart-lang/native/issues/1319
647-
Future<(bool success, File kernelFile, DateTime lastSourceChange)>
670+
///
671+
/// TODO(https://github.com/dart-lang/native/issues/1578): Compile only once
672+
/// instead of per config. This requires more locking.
673+
Future<(bool success, File kernelFile, File cacheFile)>
648674
_compileHookForPackageCached(
649675
String packageName,
650676
Uri outputDirectory,
@@ -659,29 +685,18 @@ ${e.message}
659685
final depFile = File.fromUri(
660686
outputDirectory.resolve('../hook.dill.d'),
661687
);
688+
final cacheFile = File.fromUri(
689+
outputDirectory.resolve('../hook.file_system_cache.json'),
690+
);
691+
final cache = FileSystemCache(cacheFile: cacheFile);
692+
final cacheCutoffTime = DateTime.now();
662693
final bool mustCompile;
663-
final DateTime sourceLastChange;
664-
if (!await depFile.exists()) {
694+
if (!await cacheFile.exists()) {
665695
mustCompile = true;
666-
sourceLastChange = DateTime.now();
667696
} else {
668-
// Format: `path/to/my.dill: path/to/my.dart, path/to/more.dart`
669-
final depFileContents = await depFile.readAsString();
670-
final dartSourceFiles = depFileContents
671-
.trim()
672-
.split(' ')
673-
.skip(1) // '<kernel file>:'
674-
.map((u) => Uri.file(u).fileSystemEntity)
675-
.toList();
676-
final dartFilesLastChange = await dartSourceFiles.lastModified();
677-
final packageConfigLastChange =
678-
await packageConfigUri.fileSystemEntity.lastModified();
679-
sourceLastChange = packageConfigLastChange.isAfter(dartFilesLastChange)
680-
? packageConfigLastChange
681-
: dartFilesLastChange;
682-
final kernelLastChange = await kernelFile.lastModified();
683-
mustCompile = sourceLastChange == kernelLastChange ||
684-
sourceLastChange.isAfter(kernelLastChange);
697+
await cache.readCacheFile();
698+
final changedFile = await cache.findOutdatedFileSystemEntity();
699+
mustCompile = changedFile != null;
685700
}
686701
final bool success;
687702
if (!mustCompile) {
@@ -696,8 +711,30 @@ ${e.message}
696711
kernelFile,
697712
depFile,
698713
);
714+
715+
if (success) {
716+
// Format: `path/to/my.dill: path/to/my.dart, path/to/more.dart`
717+
final depFileContents = await depFile.readAsString();
718+
final dartSources = depFileContents
719+
.trim()
720+
.split(' ')
721+
.skip(1) // '<kernel file>:'
722+
.map(Uri.file)
723+
.toList();
724+
cache.reset();
725+
final modifiedDuringBuild = await cache.hashFiles(
726+
dartSources,
727+
validBeforeLastModified: cacheCutoffTime,
728+
);
729+
await cache.persist();
730+
if (modifiedDuringBuild != null) {
731+
logger.severe('File modified during build. Build must be rerun.');
732+
}
733+
} else {
734+
await cacheFile.delete();
735+
}
699736
}
700-
return (success, kernelFile, sourceLastChange);
737+
return (success, kernelFile, cacheFile);
701738
}
702739

703740
Future<bool> _compileHookForPackage(
@@ -859,12 +896,6 @@ ${compileResult.stdout}
859896
}
860897
}
861898

862-
extension on DateTime {
863-
DateTime roundDownToSeconds() =>
864-
DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch -
865-
millisecondsSinceEpoch % const Duration(seconds: 1).inMilliseconds);
866-
}
867-
868899
extension on Uri {
869900
Uri get parent => File(toFilePath()).parent.uri;
870901
}

0 commit comments

Comments
 (0)