diff --git a/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart b/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart index 99b90d9203..4fa3961269 100644 --- a/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/simple_data_asset/hook/build.dart @@ -1,34 +1,11 @@ // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:io'; import 'package:native_assets_cli/data_assets.dart'; void main(List args) async { await build(args, (input, output) async { - final assetDirectory = Directory.fromUri( - input.packageRoot.resolve('assets/'), - ); - // If assets are added, rerun hook. - output.addDependency(assetDirectory.uri); - - await for (final dataAsset in assetDirectory.list()) { - if (dataAsset is! File) { - continue; - } - - // The file path relative to the package root, with forward slashes. - final name = dataAsset.uri - .toFilePath(windows: false) - .substring(input.packageRoot.toFilePath(windows: false).length); - - output.assets.data.add( - DataAsset(package: input.packageName, name: name, file: dataAsset.uri), - ); - // TODO(https://github.com/dart-lang/native/issues/1208): Report - // dependency on asset. - output.addDependency(dataAsset.uri); - } + await output.addDataAssetDirectories(['assets'], input: input); }); } diff --git a/pkgs/native_assets_cli/CHANGELOG.md b/pkgs/native_assets_cli/CHANGELOG.md index c2dc096e90..93d10f568b 100644 --- a/pkgs/native_assets_cli/CHANGELOG.md +++ b/pkgs/native_assets_cli/CHANGELOG.md @@ -2,6 +2,7 @@ - Added validation that all URLs in the `Input` and `Output` of hooks are absolute. +- Added `addDataAssetDirectories` extension method on `BuildOutputBuilder`. ## 0.11.0 diff --git a/pkgs/native_assets_cli/lib/data_assets.dart b/pkgs/native_assets_cli/lib/data_assets.dart index 8c9b3f7f95..9d2c022056 100644 --- a/pkgs/native_assets_cli/lib/data_assets.dart +++ b/pkgs/native_assets_cli/lib/data_assets.dart @@ -12,6 +12,7 @@ export 'native_assets_cli.dart' EncodedAssetLinkOutputBuilder; export 'src/data_assets/config.dart' show + AddDataAssetsDirectory, DataAssetBuildOutputBuilder, DataAssetBuildOutputBuilderAdd, DataAssetLinkInput, diff --git a/pkgs/native_assets_cli/lib/src/data_assets/config.dart b/pkgs/native_assets_cli/lib/src/data_assets/config.dart index 7b37038344..32d79476ee 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/config.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/config.dart @@ -2,10 +2,82 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:io'; + import '../config.dart'; import 'data_asset.dart'; +extension AddDataAssetsDirectory on BuildOutputBuilder { + /// Extension on [BuildOutput] to handle data asset directories and files. + /// + /// This extension provides a convenient way for build hooks to add + /// [DataAsset] dependencies from one or more directories or individual files. + /// + /// If any specified path does not exist, a [FileSystemException] is thrown. + /// Any error during the directory listing is caught and rethrown with + /// additional context. + /// + /// When recursive is set to true, the method will also add all subdirectories + /// and their files as dependencies. + Future addDataAssetDirectories( + List paths, { + required BuildInput input, + bool recursive = false, + }) async { + String assetName(Uri assetUri) => assetUri + .toFilePath(windows: false) + .substring(input.packageRoot.toFilePath().length); + + for (final path in paths) { + final resolvedUri = input.packageRoot.resolve(path); + final directory = Directory.fromUri(resolvedUri); + final file = File.fromUri(resolvedUri); + + if (await directory.exists()) { + try { + addDependency(directory.uri); + await for (final entity in directory.list( + recursive: recursive, + followLinks: false, + )) { + if (entity is File) { + assets.data.add( + DataAsset( + package: input.packageName, + name: assetName(entity.uri), + file: entity.uri, + ), + ); + } + addDependency(entity.uri); + } + } on FileSystemException catch (e) { + throw FileSystemException( + 'Error reading directory "$path": ${e.message}', + directory.path, + e.osError, + ); + } + } else if (await file.exists()) { + assets.data.add( + DataAsset( + package: input.packageName, + name: assetName(file.uri), + file: file.uri, + ), + ); + addDependency(file.uri); + } else { + throw FileSystemException( + 'Path does not exist', + resolvedUri.toFilePath(windows: Platform.isWindows), + ); + } + } + } +} + /// Extension to the [HookConfig] providing access to configuration specific /// to data assets. extension CodeAssetHookConfig on HookConfig { diff --git a/pkgs/native_assets_cli/test/data_assets/validation_test.dart b/pkgs/native_assets_cli/test/data_assets/validation_test.dart index 2ab258b48b..52b4e8bf63 100644 --- a/pkgs/native_assets_cli/test/data_assets/validation_test.dart +++ b/pkgs/native_assets_cli/test/data_assets/validation_test.dart @@ -35,7 +35,7 @@ void main() { BuildInputBuilder() ..setupShared( packageName: packageName, - packageRoot: tempUri, + packageRoot: tempUri.resolve('$packageName/'), outputFile: tempUri.resolve('output.json'), outputDirectory: outDirUri, outputDirectoryShared: outDirSharedUri, @@ -108,4 +108,106 @@ void main() { ); expect(errors, contains(contains('More than one'))); }); + + test('addDataAssetDirectories processes multiple directories', () async { + final input = makeDataBuildInput(); + final outputBuilder = BuildOutputBuilder(); + + final assetsDir1Uri = packageRootUri.resolve('assets1'); + final assetsDir1 = Directory.fromUri(assetsDir1Uri); + await assetsDir1.create(recursive: true); + + final assetsDir2Uri = packageRootUri.resolve('assets2'); + final assetsDir2 = Directory.fromUri(assetsDir2Uri); + await assetsDir2.create(recursive: true); + + // Create a file in assets1. + final file1Uri = assetsDir1.uri.resolve('file1.txt'); + final file1 = File.fromUri(file1Uri); + await file1.writeAsString('Hello World'); + + // Create a file in assets2. + final file2Uri = assetsDir2.uri.resolve('file2.txt'); + final file2 = File.fromUri(file2Uri); + await file2.writeAsString('Hello Dart'); + + final output = BuildOutput(outputBuilder.json); + await outputBuilder.addDataAssetDirectories([ + 'assets1', + 'assets2', + ], input: input); + + // Check that the files in both directories were added as dependencies. + expect(output.dependencies, contains(file1Uri)); + expect(output.dependencies, contains(file2Uri)); + }); + + test('addDataAssetDirectories processes one file', () async { + final input = makeDataBuildInput(); + final outputBuilder = BuildOutputBuilder(); + + final assetsDirUri = packageRootUri.resolve('single_assets'); + final assetsDir = Directory.fromUri(assetsDirUri); + await assetsDir.create(recursive: true); + + // Create a file in the single_assets directory. + final fileUri = assetsDir.uri.resolve('single_file.txt'); + final file = File.fromUri(fileUri)..createSync(); + await file.writeAsString('Test content'); + + final output = BuildOutput(outputBuilder.json); + await outputBuilder.addDataAssetDirectories([ + 'single_assets/single_file.txt', + ], input: input); + + // Check that the file in the directory was added as a dependency. + expect(output.dependencies, contains(fileUri)); + }); + + test('addDataAssetDirectories processes nested directories', () async { + final input = makeDataBuildInput(); + final outputBuilder = BuildOutputBuilder(); + + // Create top-level assets directory. + final assetsDirUri = packageRootUri.resolve('assets3'); + final assetsDir = Directory.fromUri(assetsDirUri); + await assetsDir.create(recursive: true); + + // Create nested subdirectory. + final nestedDirUri = assetsDir.uri.resolve('subdir'); + final nestedDir = Directory.fromUri(nestedDirUri); + await nestedDir.create(recursive: true); + + final nestedDir2Uri = nestedDir.uri.resolve('subdir2'); + final nestedDir2 = Directory.fromUri(nestedDir2Uri); + await nestedDir2.create(recursive: true); + + // Create a file in the top-level assets directory. + final fileTopUri = assetsDir.uri.resolve('top_file.txt'); + final fileTop = File.fromUri(fileTopUri); + await fileTop.writeAsString('Top level file'); + + // Create a file in the nested subdirectory. + final nestedFileUri = nestedDir.uri.resolve('nested_file.txt'); + final nestedFile = File.fromUri(nestedFileUri); + await nestedFile.writeAsString('Nested file'); + + // Create a file in the nested subdirectory. + final nestedFile2Uri = nestedDir2.uri.resolve('nested_file2.txt'); + final nestedFile2 = File.fromUri(nestedFile2Uri); + await nestedFile2.writeAsString('Nested file 2'); + + final output = BuildOutput(outputBuilder.json); + await outputBuilder.addDataAssetDirectories( + ['assets3'], + input: input, + recursive: true, + ); + + // Verify that the top-level directory, nested directory, and both files are + // added. + expect(output.dependencies, contains(fileTopUri)); + expect(output.dependencies, contains(nestedFileUri)); + expect(output.dependencies, contains(nestedFile2Uri)); + }); }