-
Notifications
You must be signed in to change notification settings - Fork 68
[native_assets_builder] Use file content hashing #1750
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,15 +10,14 @@ import 'package:logging/logging.dart'; | |
import 'package:native_assets_cli/native_assets_cli_internal.dart'; | ||
import 'package:package_config/package_config.dart'; | ||
|
||
import '../dependencies_hash_file/dependencies_hash_file.dart'; | ||
import '../locking/locking.dart'; | ||
import '../model/build_dry_run_result.dart'; | ||
import '../model/build_result.dart'; | ||
import '../model/hook_result.dart'; | ||
import '../model/link_result.dart'; | ||
import '../package_layout/package_layout.dart'; | ||
import '../utils/file.dart'; | ||
import '../utils/run_process.dart'; | ||
import '../utils/uri.dart'; | ||
import 'build_planner.dart'; | ||
|
||
typedef DependencyMetadata = Map<String, Metadata>; | ||
|
@@ -451,6 +450,8 @@ class NativeAssetsBuildRunner { | |
return hookResult; | ||
} | ||
|
||
// TODO(https://github.com/dart-lang/native/issues/32): Rerun hook if | ||
// environment variables change. | ||
Future<HookOutput?> _runHookForPackageCached( | ||
Hook hook, | ||
HookConfig config, | ||
|
@@ -473,7 +474,7 @@ class NativeAssetsBuildRunner { | |
final ( | ||
compileSuccess, | ||
hookKernelFile, | ||
hookLastSourceChange, | ||
hookHashesFile, | ||
) = await _compileHookForPackageCached( | ||
config.packageName, | ||
config.outputDirectory, | ||
|
@@ -488,7 +489,14 @@ class NativeAssetsBuildRunner { | |
|
||
final buildOutputFile = | ||
File.fromUri(config.outputDirectory.resolve(hook.outputName)); | ||
if (buildOutputFile.existsSync()) { | ||
final dependenciesHashFile = File.fromUri( | ||
config.outputDirectory | ||
.resolve('../dependencies.dependencies_hash_file.json'), | ||
); | ||
final dependenciesHashes = | ||
DependenciesHashFile(file: dependenciesHashFile); | ||
final lastModifiedCutoffTime = DateTime.now(); | ||
if (buildOutputFile.existsSync() && dependenciesHashFile.existsSync()) { | ||
late final HookOutput output; | ||
try { | ||
output = _readHookOutputFromUri(hook, buildOutputFile); | ||
|
@@ -503,17 +511,13 @@ ${e.message} | |
return null; | ||
} | ||
|
||
final lastBuilt = output.timestamp.roundDownToSeconds(); | ||
final dependenciesLastChange = | ||
await Dependencies(output.dependencies).lastModified(); | ||
if (lastBuilt.isAfter(dependenciesLastChange) && | ||
lastBuilt.isAfter(hookLastSourceChange)) { | ||
final outdated = | ||
(await dependenciesHashes.findOutdatedFileSystemEntity()) != null; | ||
if (!outdated) { | ||
logger.info( | ||
[ | ||
'Skipping ${hook.name} for ${config.packageName} in $outDir.', | ||
'Last build on $lastBuilt.', | ||
'Last dependencies change on $dependenciesLastChange.', | ||
'Last hook change on $hookLastSourceChange.', | ||
'Last build on ${output.timestamp}.', | ||
].join(' '), | ||
); | ||
// All build flags go into [outDir]. Therefore we do not have to | ||
|
@@ -522,7 +526,7 @@ ${e.message} | |
} | ||
} | ||
|
||
return await _runHookForPackage( | ||
final result = await _runHookForPackage( | ||
hook, | ||
config, | ||
validator, | ||
|
@@ -533,6 +537,24 @@ ${e.message} | |
hookKernelFile, | ||
packageLayout, | ||
); | ||
if (result == null) { | ||
if (await dependenciesHashFile.exists()) { | ||
await dependenciesHashFile.delete(); | ||
} | ||
} else { | ||
final modifiedDuringBuild = await dependenciesHashes.hashFiles( | ||
[ | ||
...result.dependencies, | ||
// Also depend on the hook source code. | ||
hookHashesFile.uri, | ||
], | ||
validBeforeLastModified: lastModifiedCutoffTime, | ||
); | ||
if (modifiedDuringBuild != null) { | ||
logger.severe('File modified during build. Build must be rerun.'); | ||
} | ||
} | ||
return result; | ||
}, | ||
); | ||
} | ||
|
@@ -644,7 +666,10 @@ ${e.message} | |
/// It does not reuse the cached kernel for different configs due to | ||
/// reentrancy requirements. For more info see: | ||
/// https://github.com/dart-lang/native/issues/1319 | ||
Future<(bool success, File kernelFile, DateTime lastSourceChange)> | ||
/// | ||
/// TODO(https://github.com/dart-lang/native/issues/1578): Compile only once | ||
/// instead of per config. This requires more locking. | ||
Future<(bool success, File kernelFile, File cacheFile)> | ||
_compileHookForPackageCached( | ||
String packageName, | ||
Uri outputDirectory, | ||
|
@@ -659,29 +684,17 @@ ${e.message} | |
final depFile = File.fromUri( | ||
outputDirectory.resolve('../hook.dill.d'), | ||
); | ||
final dependenciesHashFile = File.fromUri( | ||
outputDirectory.resolve('../hook.dependencies_hash_file.json'), | ||
); | ||
final dependenciesHashes = DependenciesHashFile(file: dependenciesHashFile); | ||
final lastModifiedCutoffTime = DateTime.now(); | ||
final bool mustCompile; | ||
final DateTime sourceLastChange; | ||
if (!await depFile.exists()) { | ||
if (!await dependenciesHashFile.exists()) { | ||
mustCompile = true; | ||
sourceLastChange = DateTime.now(); | ||
} else { | ||
// Format: `path/to/my.dill: path/to/my.dart, path/to/more.dart` | ||
final depFileContents = await depFile.readAsString(); | ||
final dartSourceFiles = depFileContents | ||
.trim() | ||
.split(' ') | ||
.skip(1) // '<kernel file>:' | ||
.map((u) => Uri.file(u).fileSystemEntity) | ||
.toList(); | ||
final dartFilesLastChange = await dartSourceFiles.lastModified(); | ||
final packageConfigLastChange = | ||
await packageConfigUri.fileSystemEntity.lastModified(); | ||
sourceLastChange = packageConfigLastChange.isAfter(dartFilesLastChange) | ||
? packageConfigLastChange | ||
: dartFilesLastChange; | ||
final kernelLastChange = await kernelFile.lastModified(); | ||
mustCompile = sourceLastChange == kernelLastChange || | ||
sourceLastChange.isAfter(kernelLastChange); | ||
mustCompile = | ||
(await dependenciesHashes.findOutdatedFileSystemEntity()) != null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider logging the file name that caused to re-compile it. |
||
} | ||
final bool success; | ||
if (!mustCompile) { | ||
|
@@ -696,8 +709,28 @@ ${e.message} | |
kernelFile, | ||
depFile, | ||
); | ||
|
||
if (success) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Consider using early returns and less nesting (on the lines above) - it makes it easier to read this code: if (!mustCompile) {
return (true, kernelFile, dependenciesHashFile);
}
final success = await _compileHookForPackage(...):
if (!success) {
return (false, ...);
}
...
return (true, ...); |
||
// Format: `path/to/my.dill: path/to/my.dart, path/to/more.dart` | ||
final depFileContents = await depFile.readAsString(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Consider moving this code (that knows about the format of |
||
final dartSources = depFileContents | ||
.trim() | ||
.split(' ') | ||
.skip(1) // '<kernel file>:' | ||
.map(Uri.file) | ||
.toList(); | ||
final modifiedDuringBuild = await dependenciesHashes.hashFiles( | ||
dartSources, | ||
validBeforeLastModified: lastModifiedCutoffTime, | ||
); | ||
if (modifiedDuringBuild != null) { | ||
logger.severe('File modified during build. Build must be rerun.'); | ||
} | ||
} else { | ||
await dependenciesHashFile.delete(); | ||
} | ||
} | ||
return (success, kernelFile, sourceLastChange); | ||
return (success, kernelFile, dependenciesHashFile); | ||
} | ||
|
||
Future<bool> _compileHookForPackage( | ||
|
@@ -859,12 +892,6 @@ ${compileResult.stdout} | |
} | ||
} | ||
|
||
extension on DateTime { | ||
DateTime roundDownToSeconds() => | ||
DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch - | ||
millisecondsSinceEpoch % const Duration(seconds: 1).inMilliseconds); | ||
} | ||
|
||
extension on Uri { | ||
Uri get parent => File(toFilePath()).parent.uri; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will we re-build the kernel file if the Dart SDK has changed? (otherwise invoking the old kernel on newer SDK may just crash)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why you review the code! 🤓 🙏