Skip to content

Commit 9596c1f

Browse files
authored
[native_assets_builder] Use package:files FileSystem abstraction (#1825)
The main motivation for this change is to make `package:native_assets_builder` cleanly usable in flutter tools. Right now flutter tools operates on a `FileSystem` and checks whether `.dart_tool/package_config.json` exists using that `FileSystem` but then reads the file with this packagag's code which uses `dart:io`. This inconsistency causes issues in flutter tools. Using `package:file` should solve this and would also allow injecting mocks. To be fully mockable the implementation would also need to make process invocations mockable via `package:process`. That's a future task. See also #90
1 parent 8c16b6c commit 9596c1f

20 files changed

+158
-102
lines changed

pkgs/native_assets_builder/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
- Various fixes to caching.
55
- **Breaking change** `BuildConfig.targetOS` is now only provided if
66
`buildAssetTypes` contains the code asset.
7+
- **Breaking change** `NativeAssetsBuildRunner` and `PackageLayout` now take a
8+
`FileSystem` from `package:file/file.dart`s.
79

810
## 0.9.0
911

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:convert';
6-
import 'dart:io';
6+
import 'dart:io' show Process;
77

88
import 'package:graphs/graphs.dart' as graphs;
99
import 'package:logging/logging.dart';

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

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7-
import 'dart:io';
7+
import 'dart:io' show Platform;
88

9+
import 'package:file/file.dart';
910
import 'package:logging/logging.dart';
1011
import 'package:native_assets_cli/native_assets_cli_internal.dart';
1112
import 'package:package_config/package_config.dart';
@@ -63,15 +64,18 @@ typedef ApplicationAssetValidator = Future<ValidationErrors> Function(
6364
/// [BuildConfig] and [LinkConfig]! For more info see:
6465
/// https://github.com/dart-lang/native/issues/1319
6566
class NativeAssetsBuildRunner {
67+
final FileSystem _fileSystem;
6668
final Logger logger;
6769
final Uri dartExecutable;
6870
final Duration singleHookTimeout;
6971

7072
NativeAssetsBuildRunner({
7173
required this.logger,
7274
required this.dartExecutable,
75+
required FileSystem fileSystem,
7376
Duration? singleHookTimeout,
74-
}) : singleHookTimeout = singleHookTimeout ?? const Duration(minutes: 5);
77+
}) : _fileSystem = fileSystem,
78+
singleHookTimeout = singleHookTimeout ?? const Duration(minutes: 5);
7579

7680
/// [workingDirectory] is expected to contain `.dart_tool`.
7781
///
@@ -98,7 +102,8 @@ class NativeAssetsBuildRunner {
98102
required List<String> buildAssetTypes,
99103
required bool linkingEnabled,
100104
}) async {
101-
packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory);
105+
packageLayout ??=
106+
await PackageLayout.fromRootPackageRoot(_fileSystem, workingDirectory);
102107

103108
final (buildPlan, packageGraph) = await _makePlan(
104109
hook: Hook.build,
@@ -203,7 +208,8 @@ class NativeAssetsBuildRunner {
203208
required List<String> buildAssetTypes,
204209
required BuildResult buildResult,
205210
}) async {
206-
packageLayout ??= await PackageLayout.fromRootPackageRoot(workingDirectory);
211+
packageLayout ??=
212+
await PackageLayout.fromRootPackageRoot(_fileSystem, workingDirectory);
207213

208214
final (buildPlan, packageGraph) = await _makePlan(
209215
hook: Hook.link,
@@ -231,9 +237,9 @@ class NativeAssetsBuildRunner {
231237

232238
File? resourcesFile;
233239
if (resourceIdentifiers != null) {
234-
resourcesFile = File.fromUri(buildDirUri.resolve('resources.json'));
240+
resourcesFile = _fileSystem.file(buildDirUri.resolve('resources.json'));
235241
await resourcesFile.create();
236-
await File.fromUri(resourceIdentifiers).copy(resourcesFile.path);
242+
await _fileSystem.file(resourceIdentifiers).copy(resourcesFile.path);
237243
}
238244
configBuilder.setupLinkRunConfig(
239245
outputDirectory: outDirUri,
@@ -291,14 +297,14 @@ class NativeAssetsBuildRunner {
291297
final buildDirUri =
292298
packageLayout.dartToolNativeAssetsBuilder.resolve('$buildDirName/');
293299
final outDirUri = buildDirUri.resolve('out/');
294-
final outDir = Directory.fromUri(outDirUri);
300+
final outDir = _fileSystem.directory(outDirUri);
295301
if (!await outDir.exists()) {
296302
// TODO(https://dartbug.com/50565): Purge old or unused folders.
297303
await outDir.create(recursive: true);
298304
}
299305
final outDirSharedUri = packageLayout.dartToolNativeAssetsBuilder
300306
.resolve('shared/${package.name}/$hook/');
301-
final outDirShared = Directory.fromUri(outDirSharedUri);
307+
final outDirShared = _fileSystem.directory(outDirSharedUri);
302308
if (!await outDirShared.exists()) {
303309
// TODO(https://dartbug.com/50565): Purge old or unused folders.
304310
await outDirShared.create(recursive: true);
@@ -318,9 +324,10 @@ class NativeAssetsBuildRunner {
318324
final environment = _filteredEnvironment(_environmentVariablesFilter);
319325
final outDir = config.outputDirectory;
320326
return await runUnderDirectoriesLock(
327+
_fileSystem,
321328
[
322-
Directory.fromUri(config.outputDirectoryShared.parent),
323-
Directory.fromUri(config.outputDirectory.parent),
329+
_fileSystem.directory(config.outputDirectoryShared).parent.uri,
330+
_fileSystem.directory(config.outputDirectory).parent.uri,
324331
],
325332
timeout: singleHookTimeout,
326333
logger: logger,
@@ -338,15 +345,13 @@ class NativeAssetsBuildRunner {
338345
final (hookKernelFile, hookHashes) = hookCompileResult;
339346

340347
final buildOutputFile =
341-
File.fromUri(config.outputDirectory.resolve(hook.outputName));
342-
final dependenciesHashFile = File.fromUri(
343-
config.outputDirectory
344-
.resolve('../dependencies.dependencies_hash_file.json'),
345-
);
348+
_fileSystem.file(config.outputDirectory.resolve(hook.outputName));
349+
final dependenciesHashFile = config.outputDirectory
350+
.resolve('../dependencies.dependencies_hash_file.json');
346351
final dependenciesHashes =
347-
DependenciesHashFile(file: dependenciesHashFile);
352+
DependenciesHashFile(_fileSystem, fileUri: dependenciesHashFile);
348353
final lastModifiedCutoffTime = DateTime.now();
349-
if (buildOutputFile.existsSync() && dependenciesHashFile.existsSync()) {
354+
if (buildOutputFile.existsSync() && await dependenciesHashes.exists()) {
350355
late final HookOutput output;
351356
try {
352357
output = _readHookOutputFromUri(hook, buildOutputFile);
@@ -391,8 +396,8 @@ ${e.message}
391396
environment,
392397
);
393398
if (result == null) {
394-
if (await dependenciesHashFile.exists()) {
395-
await dependenciesHashFile.delete();
399+
if (await dependenciesHashes.exists()) {
400+
await dependenciesHashes.delete();
396401
}
397402
return null;
398403
} else {
@@ -446,9 +451,9 @@ ${e.message}
446451
final configFileContents =
447452
const JsonEncoder.withIndent(' ').convert(config.json);
448453
logger.info('config.json contents: $configFileContents');
449-
await File.fromUri(configFile).writeAsString(configFileContents);
454+
await _fileSystem.file(configFile).writeAsString(configFileContents);
450455
final hookOutputUri = config.outputDirectory.resolve(hook.outputName);
451-
final hookOutputFile = File.fromUri(hookOutputUri);
456+
final hookOutputFile = _fileSystem.file(hookOutputUri);
452457
if (await hookOutputFile.exists()) {
453458
// Ensure we'll never read outdated build results.
454459
await hookOutputFile.delete();
@@ -461,6 +466,7 @@ ${e.message}
461466
if (resources != null) resources.toFilePath(),
462467
];
463468
final result = await runProcess(
469+
filesystem: _fileSystem,
464470
workingDirectory: workingDirectory,
465471
executable: dartExecutable,
466472
arguments: arguments,
@@ -472,7 +478,8 @@ ${e.message}
472478
var deleteOutputIfExists = false;
473479
try {
474480
if (result.exitCode != 0) {
475-
final printWorkingDir = workingDirectory != Directory.current.uri;
481+
final printWorkingDir =
482+
workingDirectory != _fileSystem.currentDirectory.uri;
476483
final commandString = [
477484
if (printWorkingDir) '(cd ${workingDirectory.toFilePath()};',
478485
dartExecutable.toFilePath(),
@@ -558,19 +565,19 @@ ${e.message}
558565
) async {
559566
// Don't invalidate cache with environment changes.
560567
final environmentForCaching = <String, String>{};
561-
final kernelFile = File.fromUri(
568+
final kernelFile = _fileSystem.file(
562569
outputDirectory.resolve('../hook.dill'),
563570
);
564-
final depFile = File.fromUri(
571+
final depFile = _fileSystem.file(
565572
outputDirectory.resolve('../hook.dill.d'),
566573
);
567-
final dependenciesHashFile = File.fromUri(
568-
outputDirectory.resolve('../hook.dependencies_hash_file.json'),
569-
);
570-
final dependenciesHashes = DependenciesHashFile(file: dependenciesHashFile);
574+
final dependenciesHashFile =
575+
outputDirectory.resolve('../hook.dependencies_hash_file.json');
576+
final dependenciesHashes =
577+
DependenciesHashFile(_fileSystem, fileUri: dependenciesHashFile);
571578
final lastModifiedCutoffTime = DateTime.now();
572579
var mustCompile = false;
573-
if (!await dependenciesHashFile.exists()) {
580+
if (!await dependenciesHashes.exists()) {
574581
mustCompile = true;
575582
} else {
576583
final outdatedDependency = await dependenciesHashes
@@ -633,6 +640,7 @@ ${e.message}
633640
scriptUri.toFilePath(),
634641
];
635642
final compileResult = await runProcess(
643+
filesystem: _fileSystem,
636644
workingDirectory: workingDirectory,
637645
executable: dartExecutable,
638646
arguments: compileArguments,
@@ -641,7 +649,8 @@ ${e.message}
641649
);
642650
var success = true;
643651
if (compileResult.exitCode != 0) {
644-
final printWorkingDir = workingDirectory != Directory.current.uri;
652+
final printWorkingDir =
653+
workingDirectory != _fileSystem.currentDirectory.uri;
645654
final commandString = [
646655
if (printWorkingDir) '(cd ${workingDirectory.toFilePath()};',
647656
dartExecutable.toFilePath(),
@@ -780,10 +789,6 @@ ${compileResult.stdout}
780789
}
781790
}
782791

783-
extension on Uri {
784-
Uri get parent => File(toFilePath()).parent.uri;
785-
}
786-
787792
/// Parses depfile contents.
788793
///
789794
/// Format: `path/to/my.dill: path/to/my.dart, path/to/more.dart`

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,32 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:convert';
6-
import 'dart:io';
6+
import 'dart:io' show Platform;
77
import 'dart:typed_data';
88

99
import 'package:crypto/crypto.dart';
10+
import 'package:file/file.dart';
1011

1112
import '../utils/file.dart';
1213
import '../utils/uri.dart';
1314

1415
class DependenciesHashFile {
15-
DependenciesHashFile({
16-
required this.file,
16+
DependenciesHashFile(
17+
this._fileSystem, {
18+
required this.fileUri,
1719
});
1820

19-
final File file;
21+
final FileSystem _fileSystem;
22+
final Uri fileUri;
2023
FileSystemHashes _hashes = FileSystemHashes();
2124

2225
List<Uri> get fileSystemEntities => _hashes.files.map((e) => e.path).toList();
2326

27+
Future<bool> exists() async => await _fileSystem.file(fileUri).exists();
28+
Future<void> delete() async => await _fileSystem.file(fileUri).delete();
29+
2430
Future<void> _readFile() async {
31+
final file = _fileSystem.file(fileUri);
2532
if (!await file.exists()) {
2633
_hashes = FileSystemHashes();
2734
return;
@@ -51,7 +58,7 @@ class DependenciesHashFile {
5158
Uri? modifiedAfterTimeStamp;
5259
for (final uri in fileSystemEntities) {
5360
int hash;
54-
if ((await uri.fileSystemEntity.lastModified())
61+
if ((await _fileSystem.fileSystemEntity(uri).lastModified(_fileSystem))
5562
.isAfter(fileSystemValidBeforeLastModified)) {
5663
hash = _hashLastModifiedAfterCutoff;
5764
modifiedAfterTimeStamp = uri;
@@ -72,7 +79,8 @@ class DependenciesHashFile {
7279
return modifiedAfterTimeStamp;
7380
}
7481

75-
Future<void> _persist() => file.writeAsString(json.encode(_hashes.toJson()));
82+
Future<void> _persist() =>
83+
_fileSystem.file(fileUri).writeAsString(json.encode(_hashes.toJson()));
7684

7785
/// Reads the file with hashes and reports if there is an outdated file,
7886
/// directory or environment variable.
@@ -124,15 +132,15 @@ class DependenciesHashFile {
124132
}
125133

126134
Future<int> _hashFile(Uri uri) async {
127-
final file = File.fromUri(uri);
135+
final file = _fileSystem.file(uri);
128136
if (!await file.exists()) {
129137
return _hashNotExists;
130138
}
131139
return _md5int64(await file.readAsBytes());
132140
}
133141

134142
Future<int> _hashDirectory(Uri uri) async {
135-
final directory = Directory.fromUri(uri);
143+
final directory = _fileSystem.directory(uri);
136144
if (!await directory.exists()) {
137145
return _hashNotExists;
138146
}

pkgs/native_assets_builder/lib/src/locking/locking.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:async';
6-
import 'dart:io';
6+
import 'dart:io' show Platform, pid;
7+
import 'package:file/file.dart';
78

89
import 'package:logging/logging.dart';
910

1011
Future<T> runUnderDirectoriesLock<T>(
11-
List<Directory> directories,
12+
FileSystem fileSystem,
13+
List<Uri> directories,
1214
Future<T> Function() callback, {
1315
Duration? timeout,
1416
Logger? logger,
@@ -17,8 +19,10 @@ Future<T> runUnderDirectoriesLock<T>(
1719
return await callback();
1820
}
1921
return await runUnderDirectoryLock(
22+
fileSystem,
2023
directories.first,
2124
() => runUnderDirectoriesLock<T>(
25+
fileSystem,
2226
directories.skip(1).toList(),
2327
callback,
2428
timeout: timeout,
@@ -41,13 +45,14 @@ Future<T> runUnderDirectoriesLock<T>(
4145
/// If provided, the [logger] streams information on the the locking status, and
4246
/// also streams error messages.
4347
Future<T> runUnderDirectoryLock<T>(
44-
Directory directory,
48+
FileSystem fileSystem,
49+
Uri directory,
4550
Future<T> Function() callback, {
4651
Duration? timeout,
4752
Logger? logger,
4853
}) async {
4954
const lockFileName = '.lock';
50-
final lockFile = _fileInDir(directory, lockFileName);
55+
final lockFile = _fileInDir(fileSystem.directory(directory), lockFileName);
5156
return _runUnderFileLock(
5257
lockFile,
5358
callback,
@@ -60,7 +65,7 @@ File _fileInDir(Directory path, String filename) {
6065
final dirPath = path.path;
6166
var separator = Platform.pathSeparator;
6267
if (dirPath.endsWith(separator)) separator = '';
63-
return File('$dirPath$separator$filename');
68+
return path.fileSystem.file('$dirPath$separator$filename');
6469
}
6570

6671
/// Run [callback] with this Dart process having exclusive access to [file].

0 commit comments

Comments
 (0)