Skip to content

Commit 3113f5e

Browse files
committed
fold environment hashing in to the dependencies hash file
1 parent 62f0754 commit 3113f5e

File tree

4 files changed

+177
-91
lines changed

4 files changed

+177
-91
lines changed

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

+20-44
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import 'dart:async';
66
import 'dart:convert';
77
import 'dart:io';
88

9-
import 'package:collection/collection.dart';
109
import 'package:logging/logging.dart';
1110
import 'package:native_assets_cli/native_assets_cli_internal.dart';
1211
import 'package:package_config/package_config.dart';
@@ -442,8 +441,6 @@ class NativeAssetsBuildRunner {
442441
return hookResult;
443442
}
444443

445-
// TODO(https://github.com/dart-lang/native/issues/32): Rerun hook if
446-
// environment variables change.
447444
Future<HookOutput?> _runHookForPackageCached(
448445
Hook hook,
449446
HookConfig config,
@@ -453,6 +450,7 @@ class NativeAssetsBuildRunner {
453450
Uri? resources,
454451
PackageLayout packageLayout,
455452
) async {
453+
final environment = Platform.environment;
456454
final outDir = config.outputDirectory;
457455
return await runUnderDirectoriesLock(
458456
[
@@ -486,12 +484,7 @@ class NativeAssetsBuildRunner {
486484
final dependenciesHashes =
487485
DependenciesHashFile(file: dependenciesHashFile);
488486
final lastModifiedCutoffTime = DateTime.now();
489-
final environmentFile = File.fromUri(
490-
config.outputDirectory.resolve('../environment.json'),
491-
);
492-
if (buildOutputFile.existsSync() &&
493-
dependenciesHashFile.existsSync() &&
494-
environmentFile.existsSync()) {
487+
if (buildOutputFile.existsSync() && dependenciesHashFile.existsSync()) {
495488
late final HookOutput output;
496489
try {
497490
output = _readHookOutputFromUri(hook, buildOutputFile);
@@ -506,14 +499,9 @@ ${e.message}
506499
return null;
507500
}
508501

509-
final outdatedFile =
510-
await dependenciesHashes.findOutdatedFileSystemEntity();
511-
final environmentChanged = (!await environmentFile.exists()) ||
512-
!const MapEquality<String, String>().equals(
513-
(json.decode(await environmentFile.readAsString()) as Map)
514-
.cast<String, String>(),
515-
Platform.environment);
516-
if (outdatedFile == null && !environmentChanged) {
502+
final outdatedDependency =
503+
await dependenciesHashes.findOutdatedDependency(environment);
504+
if (outdatedDependency == null) {
517505
logger.info(
518506
'Skipping ${hook.name} for ${config.packageName}'
519507
' in ${outDir.toFilePath()}.'
@@ -523,19 +511,10 @@ ${e.message}
523511
// check here whether the config is equal.
524512
return output;
525513
}
526-
if (outdatedFile != null) {
527-
logger.info(
528-
'Rerunning ${hook.name} for ${config.packageName}'
529-
' in ${outDir.toFilePath()}.'
530-
' ${outdatedFile.toFilePath()} changed.',
531-
);
532-
} else {
533-
logger.info(
534-
'Rerunning ${hook.name} for ${config.packageName}'
535-
' in ${outDir.toFilePath()}.'
536-
' The environment variables changed.',
537-
);
538-
}
514+
logger.info(
515+
'Rerunning ${hook.name} for ${config.packageName}'
516+
' in ${outDir.toFilePath()}. $outdatedDependency',
517+
);
539518
}
540519

541520
final result = await _runHookForPackage(
@@ -553,17 +532,14 @@ ${e.message}
553532
await dependenciesHashFile.delete();
554533
}
555534
} else {
556-
await environmentFile.writeAsString(
557-
json.encode(Platform.environment),
558-
);
559-
final modifiedDuringBuild =
560-
await dependenciesHashes.hashFilesAndDirectories(
535+
final modifiedDuringBuild = await dependenciesHashes.hashDependencies(
561536
[
562537
...result.dependencies,
563538
// Also depend on the hook source code.
564539
hookHashesFile.uri,
565540
],
566-
validBeforeLastModified: lastModifiedCutoffTime,
541+
lastModifiedCutoffTime,
542+
environment,
567543
);
568544
if (modifiedDuringBuild != null) {
569545
logger.severe('File modified during build. Build must be rerun.');
@@ -690,6 +666,7 @@ ${e.message}
690666
Uri packageConfigUri,
691667
Uri workingDirectory,
692668
) async {
669+
final environment = Platform.environment;
693670
final kernelFile = File.fromUri(
694671
outputDirectory.resolve('../hook.dill'),
695672
);
@@ -705,13 +682,12 @@ ${e.message}
705682
if (!await dependenciesHashFile.exists()) {
706683
mustCompile = true;
707684
} else {
708-
final outdatedFile =
709-
await dependenciesHashes.findOutdatedFileSystemEntity();
710-
if (outdatedFile != null) {
685+
final outdatedDependency =
686+
await dependenciesHashes.findOutdatedDependency(environment);
687+
if (outdatedDependency != null) {
711688
mustCompile = true;
712689
logger.info(
713-
'Recompiling ${scriptUri.toFilePath()}, '
714-
'${outdatedFile.toFilePath()} changed.',
690+
'Recompiling ${scriptUri.toFilePath()}. $outdatedDependency',
715691
);
716692
}
717693
}
@@ -734,14 +710,14 @@ ${e.message}
734710
}
735711

736712
final dartSources = await _readDepFile(depFile);
737-
final modifiedDuringBuild =
738-
await dependenciesHashes.hashFilesAndDirectories(
713+
final modifiedDuringBuild = await dependenciesHashes.hashDependencies(
739714
[
740715
...dartSources,
741716
// If the Dart version changed, recompile.
742717
dartExecutable.resolve('../version'),
743718
],
744-
validBeforeLastModified: lastModifiedCutoffTime,
719+
lastModifiedCutoffTime,
720+
environment,
745721
);
746722
if (modifiedDuringBuild != null) {
747723
logger.severe('File modified during build. Build must be rerun.');

pkgs/native_assets_builder/lib/src/dependencies_hash_file/dependencies_hash_file.dart

+96-23
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,22 @@ class DependenciesHashFile {
3535
/// Populate the hashes and persist file with entries from
3636
/// [fileSystemEntities].
3737
///
38-
/// If [validBeforeLastModified] is provided, any entities that were modified
39-
/// after [validBeforeLastModified] will get a dummy hash so that they will
40-
/// show up as outdated. If any such entity exists, its uri will be returned.
41-
Future<Uri?> hashFilesAndDirectories(
42-
List<Uri> fileSystemEntities, {
43-
DateTime? validBeforeLastModified,
44-
}) async {
38+
/// If [fileSystemValidBeforeLastModified] is provided, any entities that were
39+
/// modified after [fileSystemValidBeforeLastModified] will get a dummy hash
40+
/// so that they will show up as outdated. If any such entity exists, its uri
41+
/// will be returned.
42+
Future<Uri?> hashDependencies(
43+
List<Uri> fileSystemEntities,
44+
DateTime fileSystemValidBeforeLastModified,
45+
Map<String, String> environment,
46+
) async {
4547
_reset();
4648

4749
Uri? modifiedAfterTimeStamp;
4850
for (final uri in fileSystemEntities) {
4951
int hash;
50-
if (validBeforeLastModified != null &&
51-
(await uri.fileSystemEntity.lastModified())
52-
.isAfter(validBeforeLastModified)) {
52+
if ((await uri.fileSystemEntity.lastModified())
53+
.isAfter(fileSystemValidBeforeLastModified)) {
5354
hash = _hashLastModifiedAfterCutoff;
5455
modifiedAfterTimeStamp = uri;
5556
} else {
@@ -61,30 +62,55 @@ class DependenciesHashFile {
6162
}
6263
_hashes.files.add(FilesystemEntityHash(uri, hash));
6364
}
65+
for (final entry in environment.entries) {
66+
_hashes.environment.add(EnvironmentVariableHash(
67+
entry.key, _hashEnvironmentValue(entry.value)));
68+
}
6469
await _persist();
6570
return modifiedAfterTimeStamp;
6671
}
6772

6873
Future<void> _persist() => _file.writeAsString(json.encode(_hashes.toJson()));
6974

70-
/// Reads the file with hashes and finds an outdated file or directory if it
71-
/// exists.
72-
Future<Uri?> findOutdatedFileSystemEntity() async {
75+
/// Reads the file with hashes and reports if there is an outdated file,
76+
/// directory or environment variable.
77+
Future<String?> findOutdatedDependency(
78+
Map<String, String> environment,
79+
) async {
7380
await _readFile();
7481

7582
for (final savedHash in _hashes.files) {
7683
final uri = savedHash.path;
7784
final savedHashValue = savedHash.hash;
78-
final int hashValue;
7985
if (_isDirectoryPath(uri.path)) {
80-
hashValue = await _hashDirectory(uri);
86+
final hashValue = await _hashDirectory(uri);
87+
if (savedHashValue != hashValue) {
88+
return 'Directory contents changed: ${uri.toFilePath()}.';
89+
}
8190
} else {
82-
hashValue = await _hashFile(uri);
91+
final hashValue = await _hashFile(uri);
92+
if (savedHashValue != hashValue) {
93+
return 'File contents changed: ${uri.toFilePath()}.';
94+
}
95+
}
96+
}
97+
98+
// Check if env vars changed or were removed.
99+
for (final savedHash in _hashes.environment) {
100+
final hashValue = _hashEnvironmentValue(environment[savedHash.key]);
101+
if (savedHash.hash != hashValue) {
102+
return 'Environment variable changed: ${savedHash.key}.';
83103
}
84-
if (savedHashValue != hashValue) {
85-
return uri;
104+
}
105+
106+
// Check if env vars were added.
107+
final savedEnvKeys = _hashes.environment.map((e) => e.key).toSet();
108+
for (final envKey in environment.keys) {
109+
if (!savedEnvKeys.contains(envKey)) {
110+
return 'Environment variable changed: $envKey.';
86111
}
87112
}
113+
88114
return null;
89115
}
90116

@@ -113,6 +139,11 @@ class DependenciesHashFile {
113139
return _md5int64(utf8.encode(childrenNames));
114140
}
115141

142+
int _hashEnvironmentValue(String? value) {
143+
if (value == null) return _hashNotExists;
144+
return _md5int64(utf8.encode(value));
145+
}
146+
116147
/// Predefined hash for files and directories that do not exist.
117148
///
118149
/// There are two predefined hash values. The chance that a predefined hash
@@ -135,27 +166,43 @@ class DependenciesHashFile {
135166
class FileSystemHashes {
136167
FileSystemHashes({
137168
List<FilesystemEntityHash>? files,
138-
}) : files = files ?? [];
169+
List<EnvironmentVariableHash>? environment,
170+
}) : files = files ?? [],
171+
environment = environment ?? [];
139172

140173
factory FileSystemHashes.fromJson(Map<String, Object> json) {
141-
final rawEntries = (json[_entitiesKey] as List).cast<Object>();
174+
final rawFilesystemEntries =
175+
(json[_filesystemKey] as List?)?.cast<Object>() ?? [];
142176
final files = <FilesystemEntityHash>[
143-
for (final rawEntry in rawEntries)
177+
for (final rawEntry in rawFilesystemEntries)
144178
FilesystemEntityHash._fromJson((rawEntry as Map).cast()),
145179
];
180+
final rawEnvironmentEntries =
181+
(json[_environmentKey] as List?)?.cast<Object>() ?? [];
182+
final environment = <EnvironmentVariableHash>[
183+
for (final rawEntry in rawEnvironmentEntries)
184+
EnvironmentVariableHash._fromJson((rawEntry as Map).cast()),
185+
];
146186
return FileSystemHashes(
147187
files: files,
188+
environment: environment,
148189
);
149190
}
150191

151192
final List<FilesystemEntityHash> files;
193+
final List<EnvironmentVariableHash> environment;
152194

153-
static const _entitiesKey = 'entities';
195+
static const _filesystemKey = 'file_system';
196+
197+
static const _environmentKey = 'environment';
154198

155199
Map<String, Object> toJson() => <String, Object>{
156-
_entitiesKey: <Object>[
200+
_filesystemKey: <Object>[
157201
for (final FilesystemEntityHash file in files) file.toJson(),
158202
],
203+
_environmentKey: <Object>[
204+
for (final EnvironmentVariableHash env in environment) env.toJson(),
205+
],
159206
};
160207
}
161208

@@ -190,6 +237,32 @@ class FilesystemEntityHash {
190237
};
191238
}
192239

240+
class EnvironmentVariableHash {
241+
EnvironmentVariableHash(
242+
this.key,
243+
this.hash,
244+
);
245+
246+
factory EnvironmentVariableHash._fromJson(Map<String, Object> json) =>
247+
EnvironmentVariableHash(
248+
json[_keyKey] as String,
249+
json[_hashKey] as int,
250+
);
251+
252+
static const _keyKey = 'key';
253+
static const _hashKey = 'hash';
254+
255+
final String key;
256+
257+
/// A 64 bit hash.
258+
final int hash;
259+
260+
Object toJson() => <String, Object>{
261+
_keyKey: key,
262+
_hashKey: hash,
263+
};
264+
}
265+
193266
bool _isDirectoryPath(String path) =>
194267
path.endsWith(Platform.pathSeparator) || path.endsWith('/');
195268

pkgs/native_assets_builder/test/build_runner/build_runner_caching_test.dart

+18-8
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ void main() async {
140140
stringContainsInOrder(
141141
[
142142
'Rerunning build for native_add in',
143-
'${cUri.toFilePath()} changed.'
143+
'File contents changed: ${cUri.toFilePath()}.'
144144
],
145145
),
146146
);
@@ -248,18 +248,24 @@ void main() async {
248248

249249
// Simulate that the environment variables changed by augmenting the
250250
// persisted environment from the last invocation.
251-
final environmentFile = File.fromUri(
251+
final dependenciesHashFile = File.fromUri(
252252
CodeAsset.fromEncoded(result.encodedAssets.single)
253253
.file!
254254
.parent
255255
.parent
256-
.resolve('environment.json'),
256+
.resolve('dependencies.dependencies_hash_file.json'),
257257
);
258-
expect(await environmentFile.exists(), true);
259-
await environmentFile.writeAsString(jsonEncode({
260-
...Platform.environment,
261-
'SOME_KEY_THAT_IS_NOT_ALREADY_IN_THE_ENVIRONMENT': 'some_value',
262-
}));
258+
expect(await dependenciesHashFile.exists(), true);
259+
final dependenciesContent =
260+
jsonDecode(await dependenciesHashFile.readAsString())
261+
as Map<Object, Object?>;
262+
const modifiedEnvKey = 'PATH';
263+
(dependenciesContent['environment'] as List<dynamic>).add({
264+
'key': modifiedEnvKey,
265+
'hash': 123456789,
266+
});
267+
await dependenciesHashFile
268+
.writeAsString(jsonEncode(dependenciesContent));
263269

264270
(await build(
265271
packageUri,
@@ -278,6 +284,10 @@ void main() async {
278284
logMessages.join('\n'),
279285
isNot(contains('Skipping build for native_add')),
280286
);
287+
expect(
288+
logMessages.join('\n'),
289+
contains('Environment variable changed: $modifiedEnvKey.'),
290+
);
281291
logMessages.clear();
282292
});
283293
},

0 commit comments

Comments
 (0)