diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 3ca3c8b099..40cf09d19b 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -108,6 +108,12 @@ jobs: - run: dart pub get -C test_data/native_dynamic_linking/ if: ${{ matrix.package == 'native_assets_builder' }} + - run: dart pub get -C test_data/reusable_dynamic_library/ + if: ${{ matrix.package == 'native_assets_builder' }} + + - run: dart pub get -C test_data/reuse_dynamic_library/ + if: ${{ matrix.package == 'native_assets_builder' }} + - run: dart pub get -C test_data/no_hook/ if: ${{ matrix.package == 'native_assets_builder' }} diff --git a/pkgs/native_assets_builder/CHANGELOG.md b/pkgs/native_assets_builder/CHANGELOG.md index fadb99469d..31a504a2d8 100644 --- a/pkgs/native_assets_builder/CHANGELOG.md +++ b/pkgs/native_assets_builder/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.13.1-wip +## 0.14.0-wip -- Nothing yet. +- Bump `package:native_assets_cli` to 0.14.0. +- Route assets from build hook to build hook with `ToBuild` `Routing`. ## 0.13.0 diff --git a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart index 8deb0009a4..4797b13623 100644 --- a/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart +++ b/pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart @@ -91,14 +91,25 @@ class NativeAssetsBuildRunner { if (buildPlan == null) return null; var hookResult = HookResult(); + + /// Key is packageName. final globalMetadata = <String, Metadata>{}; + + /// Key is packageName. + final globalAssetsForBuild = <String, List<EncodedAsset>>{}; for (final package in buildPlan) { - final metadata = <String, Metadata>{}; - _metadataForPackage( - packageGraph: packageGraph!, + final metadata = + _metadataForPackage( + packageGraph: packageGraph!, + packageName: package.name, + targetMetadata: globalMetadata, + ) ?? + {}; + final assetsForBuild = _assetsForBuildForPackage( + packageGraph: packageGraph, packageName: package.name, - targetMetadata: globalMetadata, - )?.forEach((key, value) => metadata[key] = value); + globalAssetsForBuild: globalAssetsForBuild, + ); final inputBuilder = BuildInputBuilder(); @@ -106,7 +117,7 @@ class NativeAssetsBuildRunner { e.setupBuildInput(inputBuilder); } inputBuilder.config.setupBuild(linkingEnabled: linkingEnabled); - inputBuilder.setupBuildInput(metadata: metadata); + inputBuilder.setupBuildInput(metadata: metadata, assets: assetsForBuild); final (buildDirUri, outDirUri, outDirSharedUri) = await _setupDirectories( Hook.build, @@ -152,6 +163,8 @@ class NativeAssetsBuildRunner { final (hookOutput, hookDeps) = result; hookResult = hookResult.copyAdd(hookOutput, hookDeps); globalMetadata[package.name] = (hookOutput as BuildOutput).metadata; + globalAssetsForBuild[package.name] = + hookOutput.assets.encodedAssetsForBuild; } // We only perform application wide validation in the final result of @@ -732,6 +745,23 @@ ${compileResult.stdout} }; } + /// Returns only the assets output as assetForBuild by the packages that are + /// the direct dependencies of [packageName]. + Map<String, List<EncodedAsset>>? _assetsForBuildForPackage({ + required PackageGraph packageGraph, + required String packageName, + Map<String, List<EncodedAsset>>? globalAssetsForBuild, + }) { + if (globalAssetsForBuild == null) { + return null; + } + final dependencies = packageGraph.neighborsOf(packageName).toSet(); + return { + for (final entry in globalAssetsForBuild.entries) + if (dependencies.contains(entry.key)) entry.key: entry.value, + }; + } + Future<ValidationErrors> _validate( HookInput input, HookOutput output, diff --git a/pkgs/native_assets_builder/pubspec.yaml b/pkgs/native_assets_builder/pubspec.yaml index bd6854aa3d..a6b3b912f9 100644 --- a/pkgs/native_assets_builder/pubspec.yaml +++ b/pkgs/native_assets_builder/pubspec.yaml @@ -1,7 +1,7 @@ name: native_assets_builder description: >- This package is the backend that invokes build hooks. -version: 0.13.1-wip +version: 0.14.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_builder publish_to: none diff --git a/pkgs/native_assets_builder/test/test_data/reusable_dynamic_library_test.dart b/pkgs/native_assets_builder/test/test_data/reusable_dynamic_library_test.dart new file mode 100644 index 0000000000..6a7cc589bb --- /dev/null +++ b/pkgs/native_assets_builder/test/test_data/reusable_dynamic_library_test.dart @@ -0,0 +1,37 @@ +// 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. + +@OnPlatform({'mac-os': Timeout.factor(2), 'windows': Timeout.factor(10)}) +library; + +import 'package:test/test.dart'; + +import '../build_runner/helpers.dart'; +import '../helpers.dart'; + +void main() async { + const name = 'reuse_dynamic_library'; + + test( + '$name build', + () => inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('$name/'); + + await runPubGet(workingDirectory: packageUri, logger: logger); + + final logMessages = <String>[]; + final result = + (await build( + packageUri, + logger, + dartExecutable, + capturedLogs: logMessages, + buildAssetTypes: [BuildAssetType.code], + ))!; + + expect(result.encodedAssets.length, 2); + }), + ); +} diff --git a/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart index e2811a8f37..c1ca18abc4 100644 --- a/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/add_asset_link/hook/build.dart @@ -26,7 +26,7 @@ void main(List<String> arguments) async { input: input, output: output, logger: logger, - linkInPackage: 'add_asset_link', + routing: const [ToLinkHook('add_asset_link')], ); }); } diff --git a/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart index 6bfff46ac1..1f3012b043 100644 --- a/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/complex_link/hook/build.dart @@ -27,7 +27,10 @@ void main(List<String> args) async { output.assets.data.add( DataAsset(package: packageName, name: name, file: dataAsset.uri), - linkInPackage: input.config.linkingEnabled ? packageName : null, + routing: + input.config.linkingEnabled + ? ToLinkHook(packageName) + : const ToAppBundle(), ); // TODO(https://github.com/dart-lang/native/issues/1208): Report // dependency on asset. diff --git a/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart index 033d9329e8..17ad3e52fd 100644 --- a/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/complex_link_helper/hook/build.dart @@ -28,8 +28,10 @@ void main(List<String> args) async { final forLinking = name.contains('2') || name.contains('3'); output.assets.data.add( DataAsset(package: packageName, name: name, file: dataAsset.uri), - linkInPackage: - forLinking && input.config.linkingEnabled ? 'complex_link' : null, + routing: + forLinking && input.config.linkingEnabled + ? const ToLinkHook('complex_link') + : const ToAppBundle(), ); // TODO(https://github.com/dart-lang/native/issues/1208): Report // dependency on asset. diff --git a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart index 69228e97d2..dc465fe3eb 100644 --- a/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/drop_dylib_link/hook/build.dart @@ -14,30 +14,22 @@ void main(List<String> arguments) async { ..onRecord.listen((record) { print('${record.level.name}: ${record.time}: ${record.message}'); }); - final linkInPackage = - input.config.linkingEnabled ? input.packageName : null; + final routing = + input.config.linkingEnabled + ? [ToLinkHook(input.packageName)] + : [const ToAppBundle()]; await CBuilder.library( name: 'add', assetName: 'dylib_add', sources: ['src/native_add.c'], linkModePreference: LinkModePreference.dynamic, - ).run( - input: input, - output: output, - logger: logger, - linkInPackage: linkInPackage, - ); + ).run(input: input, output: output, logger: logger, routing: routing); await CBuilder.library( name: 'multiply', assetName: 'dylib_multiply', sources: ['src/native_multiply.c'], linkModePreference: LinkModePreference.dynamic, - ).run( - input: input, - output: output, - logger: logger, - linkInPackage: linkInPackage, - ); + ).run(input: input, output: output, logger: logger, routing: routing); }); } diff --git a/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart b/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart index 8cfdb1ef4b..723bfd507d 100644 --- a/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/fail_on_os_sdk_version_link/hook/build.dart @@ -12,8 +12,10 @@ void main(List<String> arguments) async { file: input.packageRoot.resolve('assets/data.json'), package: input.packageName, ), - linkInPackage: - input.config.linkingEnabled ? 'fail_on_os_sdk_version_linker' : null, + routing: + input.config.linkingEnabled + ? const ToLinkHook('fail_on_os_sdk_version_linker') + : const ToAppBundle(), ); }); } diff --git a/pkgs/native_assets_builder/test_data/manifest.yaml b/pkgs/native_assets_builder/test_data/manifest.yaml index 819718c56c..387f53315b 100644 --- a/pkgs/native_assets_builder/test_data/manifest.yaml +++ b/pkgs/native_assets_builder/test_data/manifest.yaml @@ -112,6 +112,21 @@ - relative_path/assets/test_asset.txt - relative_path/hook/build.dart - relative_path/pubspec.yaml +- reusable_dynamic_library/ffigen.yaml +- reusable_dynamic_library/hook/build.dart +- reusable_dynamic_library/lib/add.dart +- reusable_dynamic_library/lib/hook.dart +- reusable_dynamic_library/pubspec.yaml +- reusable_dynamic_library/src/add.c +- reusable_dynamic_library/src/add.h +- reusable_dynamic_library/test/add_test.dart +- reuse_dynamic_library/ffigen.yaml +- reuse_dynamic_library/hook/build.dart +- reuse_dynamic_library/lib/my_add.dart +- reuse_dynamic_library/pubspec.yaml +- reuse_dynamic_library/src/my_add.c +- reuse_dynamic_library/src/my_add.h +- reuse_dynamic_library/test/add_test.dart - simple_data_asset/assets/test_asset.txt - simple_data_asset/bin/deep_modify_data_asset.dart.debug - simple_data_asset/bin/modify_data_asset.dart.debug diff --git a/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart b/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart index 509fbbaed1..c9bf9e0b6a 100644 --- a/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/native_add_duplicate/hook/build.dart @@ -32,7 +32,10 @@ void main(List<String> arguments) async { output.assets.code.add( tempBuildOutput.assets.code.single, // Send dylib to linking if linking is enabled. - linkInPackage: input.config.linkingEnabled ? packageName : null, + routing: + input.config.linkingEnabled + ? ToLinkHook(packageName) + : const ToAppBundle(), ); output.addDependencies(tempBuildOutput.dependencies); }); diff --git a/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart b/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart index 9a4c8d7c33..d85f93e033 100644 --- a/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/package_reading_metadata/hook/build.dart @@ -2,16 +2,21 @@ // 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. -// ignore_for_file: deprecated_member_use - import 'package:native_assets_cli/native_assets_cli.dart'; void main(List<String> args) async { await build(args, (input, _) async { - final someValue = input.metadatum('package_with_metadata', 'some_key'); + final someValue = input.metadata['package_with_metadata']['some_key']; assert(someValue != null); - final someInt = input.metadatum('package_with_metadata', 'some_int'); + final someInt = input.metadata['package_with_metadata']['some_int']; assert(someInt != null); print({'some_int': someInt, 'some_key': someValue}); + + // ignore: deprecated_member_use + final someValueOld = input.metadatum('package_with_metadata', 'some_key'); + assert(someValueOld != null); + // ignore: deprecated_member_use + final someIntOld = input.metadatum('package_with_metadata', 'some_int'); + assert(someIntOld != null); }); } diff --git a/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart b/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart index f933efab6a..d38a47a393 100644 --- a/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/package_with_metadata/hook/build.dart @@ -6,6 +6,7 @@ import 'package:native_assets_cli/native_assets_cli.dart'; void main(List<String> arguments) async { await build(arguments, (input, output) async { + output.metadata.addAll({'some_key': 'some_value', 'some_int': 3}); // ignore: deprecated_member_use output.addMetadata({'some_key': 'some_value', 'some_int': 3}); }); diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/README.md b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/README.md new file mode 100644 index 0000000000..d2dc5759eb --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/README.md @@ -0,0 +1,7 @@ +An example of a package with a dynamic library that can be linked against in +a dependent package. + +## Usage + +Run tests with `dart --enable-experiment=native-assets test`. + diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/ffigen.yaml b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/ffigen.yaml new file mode 100644 index 0000000000..c8aba3eaa2 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/ffigen.yaml @@ -0,0 +1,20 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: AddBindings +description: | + Bindings for `src/add.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: 'lib/add.dart' +headers: + entry-points: + - 'src/add.h' + include-directives: + - 'src/add.h' +preamble: | + // Copyright (c) 2025, 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. +comments: + style: any + length: full +ffi-native: diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/hook/build.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/hook/build.dart new file mode 100644 index 0000000000..6e72c6c18b --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/hook/build.dart @@ -0,0 +1,38 @@ +// 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 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +void main(List<String> args) async { + await build(args, (input, output) async { + final logger = + Logger('') + ..level = Level.ALL + ..onRecord.listen((record) => print(record.message)); + + final builder = CBuilder.library( + name: 'add', + assetName: 'add.dart', + sources: ['src/add.c'], + buildMode: BuildMode.debug, + ); + + await builder.run( + input: input, + output: output, + logger: logger, + routing: const [ + // Bundle the dylib in the app, someone might use it. + ToAppBundle(), + // Enable other packages to link to the dylib. + ToBuildHooks(), + ], + ); + + // Enable other packages to find the headers. + output.metadata['include'] = input.packageRoot.resolve('src/').toFilePath(); + }); +} diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/add.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/add.dart new file mode 100644 index 0000000000..c471412290 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/add.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2025, 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. + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'add') +external int add(int a, int b); diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/hook.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/hook.dart new file mode 100644 index 0000000000..00effbe1c2 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/lib/hook.dart @@ -0,0 +1,55 @@ +// Copyright (c) 2025, 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. + +/// This library is meant to be used in hooks of packages depending on this +/// package. +library; + +import 'dart:io'; + +import 'package:native_assets_cli/code_assets.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; + +/// Helper class for populating a [CBuilder.library] call with the fields of +/// this class to link against the `add` dynamic library. +class AddLibrary { + final BuildInput input; + + /// Librarie names for [CBuilder]. + final List<String> libraries = ['add']; + + /// Library directories for [CBuilder]. + late final List<String> libraryDirectories; + + /// Include directories for [CBuilder]. + late final List<String> includes; + + AddLibrary(this.input) { + const packageName = 'reusable_dynamic_library'; + final buildAssetsFromPackage = input.assets[packageName]; + final codeAssetsFromPackage = + buildAssetsFromPackage + .where((a) => a.isCodeAsset) + .map((a) => a.asCodeAsset) + .toList(); + if (codeAssetsFromPackage.length != 1) { + throw Exception( + 'Did not find a build asset from $packageName:' + ' $codeAssetsFromPackage', + ); + } + final codeAsset = codeAssetsFromPackage.first; + final dylibFile = File.fromUri(codeAsset.file!); + if (!dylibFile.existsSync()) { + throw Exception('Dylib file does not exist: $dylibFile'); + } + final libraryDirectory = Directory.fromUri(dylibFile.uri.resolve('.')); + libraryDirectories = [libraryDirectory.path]; + final includeDirectory = input.metadata[packageName]['include']; + if (includeDirectory is! String) { + throw Exception('Include directory is not a string: $includeDirectory'); + } + includes = [includeDirectory]; + } +} diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.c b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.c new file mode 100644 index 0000000000..41cd96a2ae --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.c @@ -0,0 +1,7 @@ +// Copyright (c) 2025, 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. + +#include "add.h" + +int32_t add(int32_t a, int32_t b) { return a + b; } diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.h b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.h new file mode 100644 index 0000000000..275878fe85 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/src/add.h @@ -0,0 +1,13 @@ +// 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. + +#include <stdint.h> + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_builder/test_data/reusable_dynamic_library/test/add_test.dart b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/test/add_test.dart new file mode 100644 index 0000000000..0b3b5e9f35 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reusable_dynamic_library/test/add_test.dart @@ -0,0 +1,12 @@ +// 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 'package:reusable_dynamic_library/add.dart'; +import 'package:test/test.dart'; + +void main() { + test('invoke native function', () { + expect(add(24, 18), 42); + }); +} diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/README.md b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/README.md new file mode 100644 index 0000000000..fbc12b69b5 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/README.md @@ -0,0 +1,7 @@ +An example of a native library which is dynamically linked against a dynamic +library from another package. + +## Usage + +Run tests with `dart --enable-experiment=native-assets test`. + diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/ffigen.yaml b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/ffigen.yaml new file mode 100644 index 0000000000..f056629b49 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/ffigen.yaml @@ -0,0 +1,20 @@ +# Run with `flutter pub run ffigen --config ffigen.yaml`. +name: AddBindings +description: | + Bindings for `src/my_add.h`. + + Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`. +output: 'lib/my_add.dart' +headers: + entry-points: + - 'src/my_add.h' + include-directives: + - 'src/my_add.h' +preamble: | + // Copyright (c) 2025, 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. +comments: + style: any + length: full +ffi-native: diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/hook/build.dart b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/hook/build.dart new file mode 100644 index 0000000000..49bbb402ef --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/hook/build.dart @@ -0,0 +1,35 @@ +// Copyright (c) 2025, 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 'package:logging/logging.dart'; +import 'package:native_assets_cli/native_assets_cli.dart'; +import 'package:native_toolchain_c/native_toolchain_c.dart'; +import 'package:reusable_dynamic_library/hook.dart'; + +void main(List<String> args) async { + await build(args, (input, output) async { + final logger = + Logger('') + ..level = Level.ALL + ..onRecord.listen((record) => print(record.message)); + + final addLibrary = AddLibrary(input); + final builder = CBuilder.library( + name: 'my_add', + assetName: 'my_add.dart', + sources: ['src/my_add.c'], + libraries: [...addLibrary.libraries], + libraryDirectories: [...addLibrary.libraryDirectories], + includes: [...addLibrary.includes], + buildMode: BuildMode.debug, + ); + + await builder.run( + input: input, + output: output, + logger: logger, + routing: const [ToAppBundle()], + ); + }); +} diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/lib/my_add.dart b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/lib/my_add.dart new file mode 100644 index 0000000000..357fc359c7 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/lib/my_add.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2025, 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. + +// AUTO GENERATED FILE, DO NOT EDIT. +// +// Generated by `package:ffigen`. +// ignore_for_file: type=lint +import 'dart:ffi' as ffi; + +@ffi.Native<ffi.Int32 Function(ffi.Int32, ffi.Int32)>(symbol: 'my_add') +external int my_add(int a, int b); diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.c b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.c new file mode 100644 index 0000000000..ec0313cd0e --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.c @@ -0,0 +1,11 @@ +// Copyright (c) 2025, 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. + +#include "my_add.h" +#include "add.h" + +int32_t my_add(int32_t a, int32_t b) { + // Here we are calling a function from a dynamically linked library. + return add(a, b); +} diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.h b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.h new file mode 100644 index 0000000000..85c40cf1e6 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/src/my_add.h @@ -0,0 +1,13 @@ +// 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. + +#include <stdint.h> + +#if _WIN32 +#define MYLIB_EXPORT __declspec(dllexport) +#else +#define MYLIB_EXPORT +#endif + +MYLIB_EXPORT int32_t my_add(int32_t a, int32_t b); diff --git a/pkgs/native_assets_builder/test_data/reuse_dynamic_library/test/add_test.dart b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/test/add_test.dart new file mode 100644 index 0000000000..b63ce88e7c --- /dev/null +++ b/pkgs/native_assets_builder/test_data/reuse_dynamic_library/test/add_test.dart @@ -0,0 +1,12 @@ +// 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 'package:reuse_dynamic_library/my_add.dart'; +import 'package:test/test.dart'; + +void main() { + test('invoke native function', () { + expect(my_add(24, 18), 42); + }); +} diff --git a/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart index 6bfff46ac1..1f3012b043 100644 --- a/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/simple_link/hook/build.dart @@ -27,7 +27,10 @@ void main(List<String> args) async { output.assets.data.add( DataAsset(package: packageName, name: name, file: dataAsset.uri), - linkInPackage: input.config.linkingEnabled ? packageName : null, + routing: + input.config.linkingEnabled + ? ToLinkHook(packageName) + : const ToAppBundle(), ); // TODO(https://github.com/dart-lang/native/issues/1208): Report // dependency on asset. diff --git a/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart b/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart index b28738411d..5c1f9e5d68 100644 --- a/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/treeshaking_native_libs/hook/build.dart @@ -20,7 +20,10 @@ void main(List<String> arguments) async { await cbuilder.run( input: input, output: output, - linkInPackage: input.config.linkingEnabled ? input.packageName : null, + routing: + input.config.linkingEnabled + ? [ToLinkHook(input.packageName)] + : [const ToAppBundle()], logger: Logger('') ..level = Level.ALL diff --git a/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart b/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart index ac8ae9fda8..7474410dc0 100644 --- a/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/use_all_api/hook/build.dart @@ -48,7 +48,10 @@ void main(List<String> args) async { name: 'name', package: 'package', ), - linkInPackage: 'foo', + routing: + input.config.linkingEnabled + ? const ToLinkHook('foo') + : const ToAppBundle(), ); output.addDependency(input.packageRoot.resolve('x.txt')); }); diff --git a/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart b/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart index 6eeab74451..afe6948817 100644 --- a/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart +++ b/pkgs/native_assets_builder/test_data/wrong_linker/hook/build.dart @@ -23,7 +23,7 @@ void main(List<String> arguments) async { os: OS.current, architecture: Architecture.current, ), - linkInPackage: 'a_package_that_does_not_exist', + routing: const ToLinkHook('a_package_that_does_not_exist'), ); }); } diff --git a/pkgs/native_assets_cli/CHANGELOG.md b/pkgs/native_assets_cli/CHANGELOG.md index bca4550557..916df2eef8 100644 --- a/pkgs/native_assets_cli/CHANGELOG.md +++ b/pkgs/native_assets_cli/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.13.1-wip +## 0.14.0-wip -- Nothing yet. +- Added support for sending assets between build hooks via the `ToBuild` + `Routing`. This replaces the deprecated `Metadata`. ## 0.13.0 diff --git a/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart index 6bfff46ac1..1f3012b043 100644 --- a/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart +++ b/pkgs/native_assets_cli/example/link/package_with_assets/hook/build.dart @@ -27,7 +27,10 @@ void main(List<String> args) async { output.assets.data.add( DataAsset(package: packageName, name: name, file: dataAsset.uri), - linkInPackage: input.config.linkingEnabled ? packageName : null, + routing: + input.config.linkingEnabled + ? ToLinkHook(packageName) + : const ToAppBundle(), ); // TODO(https://github.com/dart-lang/native/issues/1208): Report // dependency on asset. diff --git a/pkgs/native_assets_cli/lib/native_assets_cli.dart b/pkgs/native_assets_cli/lib/native_assets_cli.dart index 4dae32f44b..b0f16c6d96 100644 --- a/pkgs/native_assets_cli/lib/native_assets_cli.dart +++ b/pkgs/native_assets_cli/lib/native_assets_cli.dart @@ -12,6 +12,7 @@ export 'src/api/link.dart' show link; export 'src/api/linker.dart' show Linker; export 'src/config.dart' show + AssetRouting, BuildConfig, BuildConfigBuilder, BuildInput, @@ -22,6 +23,9 @@ export 'src/config.dart' HookConfigBuilder, HookInput, LinkInput, - LinkOutputBuilder; + LinkOutputBuilder, + ToAppBundle, + ToBuildHooks, + ToLinkHook; export 'src/encoded_asset.dart' show EncodedAsset; -export 'src/metadata.dart'; +export 'src/metadata.dart' show Metadata; diff --git a/pkgs/native_assets_cli/lib/src/code_assets/config.dart b/pkgs/native_assets_cli/lib/src/code_assets/config.dart index 1f0dbad75d..d7d732a50d 100644 --- a/pkgs/native_assets_cli/lib/src/code_assets/config.dart +++ b/pkgs/native_assets_cli/lib/src/code_assets/config.dart @@ -151,16 +151,17 @@ extension CodeAssetBuildOutputBuilder on EncodedAssetBuildOutputBuilder { extension type CodeAssetBuildOutputBuilderAdd._( EncodedAssetBuildOutputBuilder _output ) { - /// Adds the given [asset] to the hook output (or send to [linkInPackage] - /// for linking if provided). - void add(CodeAsset asset, {String? linkInPackage}) => - _output.addEncodedAsset(asset.encode(), linkInPackage: linkInPackage); - - /// Adds the given [assets] to the hook output (or send to [linkInPackage] - /// for linking if provided). - void addAll(Iterable<CodeAsset> assets, {String? linkInPackage}) { + /// Adds the given [asset] to the hook output with [routing]. + void add(CodeAsset asset, {AssetRouting routing = const ToAppBundle()}) => + _output.addEncodedAsset(asset.encode(), routing: routing); + + /// Adds the given [assets] to the hook output with [routing]. + void addAll( + Iterable<CodeAsset> assets, { + AssetRouting routing = const ToAppBundle(), + }) { for (final asset in assets) { - add(asset, linkInPackage: linkInPackage); + add(asset, routing: routing); } } } diff --git a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart index 96b179624d..3fb1c16a0e 100644 --- a/pkgs/native_assets_cli/lib/src/code_assets/validation.dart +++ b/pkgs/native_assets_cli/lib/src/code_assets/validation.dart @@ -15,11 +15,16 @@ import 'os.dart'; import 'syntax.g.dart' as syntax; Future<ValidationErrors> validateCodeAssetBuildInput(BuildInput input) async => - _validateConfig('BuildInput.config.code', input.config); + [ + ..._validateConfig('BuildInput.config.code', input.config), + ...await _validateCodeAssetHookInput([ + for (final assets in input.assets.encodedAssets.values) ...assets, + ]), + ]; Future<ValidationErrors> validateCodeAssetLinkInput(LinkInput input) async => [ ..._validateConfig('LinkInput.config.code', input.config), - ...await _validateCodeAssetLinkInput(input.assets.encodedAssets), + ...await _validateCodeAssetHookInput(input.assets.encodedAssets), ]; ValidationErrors _validateConfig(String inputName, HookConfig config) { @@ -59,7 +64,7 @@ ValidationErrors _validateConfigSyntax(HookConfig config) { return [...syntaxErrors, _semanticValidationSkippedMessage(syntaxNode.path)]; } -Future<ValidationErrors> _validateCodeAssetLinkInput( +Future<ValidationErrors> _validateCodeAssetHookInput( List<EncodedAsset> encodedAssets, ) async { final errors = <String>[]; @@ -83,6 +88,7 @@ Future<ValidationErrors> validateCodeAssetBuildOutput( input.config.code, output.assets.encodedAssets, [ + ...output.assets.encodedAssetsForBuild, for (final assetList in output.assets.encodedAssetsForLinking.values) ...assetList, ], @@ -126,16 +132,16 @@ Future<ValidationErrors> validateCodeAssetInApplication( Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( HookInput input, CodeConfig codeConfig, - List<EncodedAsset> encodedAssets, - List<EncodedAsset> encodedAssetsForLinking, + List<EncodedAsset> encodedAssetsBundled, + List<EncodedAsset> encodedAssetsNotBundled, HookOutput output, bool isBuild, ) async { final errors = <String>[]; - final ids = <String>{}; + final idsBundled = <String>{}; final fileNameToEncodedAssetId = <String, Set<String>>{}; - for (final asset in encodedAssets) { + for (final asset in encodedAssetsBundled) { if (!asset.isCodeAsset) continue; final syntaxErrors = _validateCodeAssetSyntax(asset); if (syntaxErrors.isNotEmpty) { @@ -147,7 +153,7 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( codeConfig, CodeAsset.fromEncoded(asset), errors, - ids, + idsBundled, isBuild, true, ); @@ -157,7 +163,8 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( ); } - for (final asset in encodedAssetsForLinking) { + final idsNotBundled = <String>{}; + for (final asset in encodedAssetsNotBundled) { if (!asset.isCodeAsset) continue; final syntaxErrors = _validateCodeAssetSyntax(asset); if (syntaxErrors.isNotEmpty) { @@ -169,7 +176,7 @@ Future<ValidationErrors> _validateCodeAssetBuildOrLinkOutput( codeConfig, CodeAsset.fromEncoded(asset), errors, - ids, + idsNotBundled, isBuild, false, ); diff --git a/pkgs/native_assets_cli/lib/src/config.dart b/pkgs/native_assets_cli/lib/src/config.dart index d1e6e256dc..58b853a320 100644 --- a/pkgs/native_assets_cli/lib/src/config.dart +++ b/pkgs/native_assets_cli/lib/src/config.dart @@ -5,6 +5,7 @@ import 'dart:convert' hide json; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:crypto/crypto.dart' show sha256; import 'package:pub_semver/pub_semver.dart'; @@ -146,7 +147,8 @@ String _jsonChecksum(Map<String, Object?> json) { } final class BuildInput extends HookInput { - Map<String, Metadata> get metadata => { + @Deprecated('Use metadata backed by MetadataAsset instead.') + Map<String, Metadata> get metadataOld => { for (final entry in (_syntaxBuildInput.dependencyMetadata ?? {}).entries) entry.key: Metadata(entry.value), }; @@ -160,14 +162,49 @@ final class BuildInput extends HookInput { BuildInput(super.json) : _syntaxBuildInput = syntax.BuildInput.fromJson(json) { // Run validation. - metadata; + // ignore: deprecated_member_use_from_same_package + metadataOld; } + @Deprecated('Use metadata backed by MetadataAsset instead.') Object? metadatum(String packageName, String key) => - metadata[packageName]?.metadata[key]; + metadataOld[packageName]?.metadata[key]; @override BuildConfig get config => BuildConfig._(this); + + BuildInputAssets get assets => BuildInputAssets._(this); + + BuildInputMetadata get metadata => BuildInputMetadata._(this); +} + +extension type BuildInputMetadata._(BuildInput _input) { + PackageMetadata operator [](String packageName) => PackageMetadata( + (_input.assets.encodedAssets[packageName] ?? []) + .where((e) => e.isMetadataAsset) + .map((e) => e.asMetadataAsset) + .toList(), + ); +} + +class PackageMetadata { + PackageMetadata(this._metadata); + + final List<MetadataAsset> _metadata; + + Object? operator [](String key) => + _metadata.firstWhereOrNull((e) => e.key == key)?.value; +} + +extension type BuildInputAssets._(BuildInput _input) { + Map<String, List<EncodedAsset>> get encodedAssets => { + for (final MapEntry(:key, :value) + in (_input._syntaxBuildInput.assets ?? {}).entries) + key: _parseAssets(value), + }; + + List<EncodedAsset> operator [](String packageName) => + encodedAssets[packageName] ?? []; } final class BuildInputBuilder extends HookInputBuilder { @@ -175,7 +212,10 @@ final class BuildInputBuilder extends HookInputBuilder { syntax.BuildInput get _syntax => syntax.BuildInput.fromJson(super._syntax.json); - void setupBuildInput({Map<String, Metadata> metadata = const {}}) { + void setupBuildInput({ + Map<String, Metadata> metadata = const {}, + Map<String, List<EncodedAsset>>? assets, + }) { if (metadata.isEmpty) { return; } @@ -183,7 +223,16 @@ final class BuildInputBuilder extends HookInputBuilder { dependencyMetadata: { for (final key in metadata.keys) key: metadata[key]!.toJson(), }, - assets: null, // TODO: Implement this. + assets: + assets == null + ? null + : { + for (final MapEntry(:key, :value) in assets.entries) + key: [ + for (final asset in value) + syntax.Asset.fromJson(asset.toJson()), + ], + }, ); } @@ -356,6 +405,9 @@ class BuildOutput extends HookOutput { key: _parseAssets(value), }; + List<EncodedAsset> get _encodedAssetsForBuild => + _parseAssets(_syntax.assetsForBuild ?? []); + /// Metadata passed to dependent build hook invocations. Metadata get metadata => Metadata(_syntax.metadata?.json ?? {}); @@ -381,6 +433,9 @@ extension type BuildOutputAssets._(BuildOutput _output) { /// specified in the key, which can decide if they are bundled or not. Map<String, List<EncodedAsset>> get encodedAssetsForLinking => _output._encodedAssetsForLinking; + + List<EncodedAsset> get encodedAssetsForBuild => + _output._encodedAssetsForBuild; } /// Builder to produce the output of a build hook. @@ -418,6 +473,8 @@ class BuildOutputBuilder extends HookOutputBuilder { _syntax.metadata = syntax.JsonObject.fromJson(value); } + MetadataOutputBuilder get metadata => MetadataOutputBuilder._(this); + EncodedAssetBuildOutputBuilder get assets => EncodedAssetBuildOutputBuilder._(this); @@ -426,12 +483,68 @@ class BuildOutputBuilder extends HookOutputBuilder { syntax.BuildOutput.fromJson(super._syntax.json); } +extension type MetadataOutputBuilder._(BuildOutputBuilder _output) { + void operator []=(String key, Object value) { + _output.assets.addEncodedAsset( + MetadataAsset(key: key, value: value).encode(), + routing: const ToBuildHooks(), + ); + } + + void addAll(Map<String, Object> metadata) { + for (final MapEntry(:key, :value) in metadata.entries) { + this[key] = value; + } + } +} + +/// The destination for assets output in a build hook. +sealed class AssetRouting { + const AssetRouting(); +} + +/// Assets with this routing will be sent to the SDK to be bundled with the app. +final class ToAppBundle extends AssetRouting { + const ToAppBundle(); +} + +/// Assets with this routing will be sent to build hooks. +/// +/// The assets are only available for build hooks of packages that have a direct +/// dependency on the package emitting the asset with this routing. +/// +/// The assets will not be bundled in the final application unless also added +/// with [ToAppBundle]. Prefer bundling the asset in the sending hook, otherwise +/// multiple receivers might try to bundle the asset leading to duplicate assets +/// in the app bundle. +/// +/// The receiver will know about sender package (it must be a direct +/// dependency), the sender does not know about the receiver. Hence this routing +/// is a broadcast with 0-N receivers. +final class ToBuildHooks extends AssetRouting { + const ToBuildHooks(); +} + +/// Assets with this routing will be sent to the link hook of [packageName]. +/// +/// The assets are only available to the link hook of [packageName]. +/// +/// The assets will not be bundled in the final application unless added with +/// [ToAppBundle] in the link hook of [packageName]. +/// +/// The receiver will not know about the sender package. The sender knows about +/// the receiver package. Hence, the receiver must be specified and there is +/// exactly one receiver. +final class ToLinkHook extends AssetRouting { + final String packageName; + + const ToLinkHook(this.packageName); +} + extension type EncodedAssetBuildOutputBuilder._(BuildOutputBuilder _output) { /// Adds [EncodedAsset]s produced by this build. /// - /// If the [linkInPackage] argument is specified, the asset will not be - /// bundled during the build step, but sent as input to the link hook of the - /// specified package, where it can be further processed and possibly bundled. + /// The asset is routed according to [routing]. /// /// Note to hook writers. Prefer using the `.add` method on the extension for /// the specific asset type being added: @@ -444,28 +557,34 @@ extension type EncodedAssetBuildOutputBuilder._(BuildOutputBuilder _output) { /// }); /// } /// ``` - void addEncodedAsset(EncodedAsset asset, {String? linkInPackage}) { - if (linkInPackage != null) { - final assetsForLinking = _syntax.assetsForLinking ?? {}; - assetsForLinking[linkInPackage] ??= []; - assetsForLinking[linkInPackage]!.add( - syntax.Asset.fromJson(asset.toJson()), - ); - _syntax.assetsForLinking = assetsForLinking; - _syntax.assetsForLinkingOld = assetsForLinking; - } else { - final assets = _syntax.assets ?? []; - assets.add(syntax.Asset.fromJson(asset.toJson())); - _syntax.assets = assets; + void addEncodedAsset( + EncodedAsset asset, { + AssetRouting routing = const ToAppBundle(), + }) { + switch (routing) { + case ToAppBundle(): + final assets = _syntax.assets ?? []; + assets.add(syntax.Asset.fromJson(asset.toJson())); + _syntax.assets = assets; + case ToBuildHooks(): + final assets = _syntax.assetsForBuild ?? []; + assets.add(syntax.Asset.fromJson(asset.toJson())); + _syntax.assetsForBuild = assets; + case ToLinkHook(): + final packageName = routing.packageName; + final assetsForLinking = _syntax.assetsForLinking ?? {}; + assetsForLinking[packageName] ??= []; + assetsForLinking[packageName]!.add( + syntax.Asset.fromJson(asset.toJson()), + ); + _syntax.assetsForLinking = assetsForLinking; + _syntax.assetsForLinkingOld = assetsForLinking; } } /// Adds [EncodedAsset]s produced by this build. /// - /// If the [linkInPackage] argument is specified, the assets will not be - /// bundled during the build step, but sent as input to the link hook of the - /// specified package, where they can be further processed and possibly - /// bundled. + /// The asset is routed according to [routing]. /// /// Note to hook writers. Prefer using the `.addAll` method on the extension /// for the specific asset type being added: @@ -480,23 +599,31 @@ extension type EncodedAssetBuildOutputBuilder._(BuildOutputBuilder _output) { /// ``` void addEncodedAssets( Iterable<EncodedAsset> assets, { - String? linkInPackage, + AssetRouting routing = const ToAppBundle(), }) { - if (linkInPackage != null) { - final assetsForLinking = - _syntax.assetsForLinking ?? _syntax.assetsForLinkingOld ?? {}; - final list = assetsForLinking[linkInPackage] ??= []; - for (final asset in assets) { - list.add(syntax.Asset.fromJson(asset.toJson())); - } - _syntax.assetsForLinking = assetsForLinking; - _syntax.assetsForLinkingOld = assetsForLinking; - } else { - final list = _syntax.assets ?? []; - for (final asset in assets) { - list.add(syntax.Asset.fromJson(asset.toJson())); - } - _syntax.assets = list; + switch (routing) { + case ToAppBundle(): + final list = _syntax.assets ?? []; + for (final asset in assets) { + list.add(syntax.Asset.fromJson(asset.toJson())); + } + _syntax.assets = list; + case ToBuildHooks(): + final list = _syntax.assetsForBuild ?? []; + for (final asset in assets) { + list.add(syntax.Asset.fromJson(asset.toJson())); + } + _syntax.assetsForBuild = list; + case ToLinkHook(): + final linkInPackage = routing.packageName; + final assetsForLinking = + _syntax.assetsForLinking ?? _syntax.assetsForLinkingOld ?? {}; + final list = assetsForLinking[linkInPackage] ??= []; + for (final asset in assets) { + list.add(syntax.Asset.fromJson(asset.toJson())); + } + _syntax.assetsForLinking = assetsForLinking; + _syntax.assetsForLinkingOld = assetsForLinking; } } 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 86b8632d6b..978ceba0c3 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/config.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/config.dart @@ -114,16 +114,17 @@ extension DataAssetBuildOutputBuilder on EncodedAssetBuildOutputBuilder { extension type DataAssetBuildOutputBuilderAdd._( EncodedAssetBuildOutputBuilder _output ) { - /// Adds the given [asset] to the hook output (or send to [linkInPackage] - /// for linking if provided). - void add(DataAsset asset, {String? linkInPackage}) => - _output.addEncodedAsset(asset.encode(), linkInPackage: linkInPackage); - - /// Adds the given [assets] to the hook output (or send to [linkInPackage] - /// for linking if provided). - void addAll(Iterable<DataAsset> assets, {String? linkInPackage}) { + /// Adds the given [asset] to the hook output with [routing]. + void add(DataAsset asset, {AssetRouting routing = const ToAppBundle()}) => + _output.addEncodedAsset(asset.encode(), routing: routing); + + /// Adds the given [assets] to the hook output with [routing]. + void addAll( + Iterable<DataAsset> assets, { + AssetRouting routing = const ToAppBundle(), + }) { for (final asset in assets) { - add(asset, linkInPackage: linkInPackage); + add(asset, routing: routing); } } } diff --git a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart index 95f60e1ab3..15b9fbb9ed 100644 --- a/pkgs/native_assets_cli/lib/src/data_assets/validation.dart +++ b/pkgs/native_assets_cli/lib/src/data_assets/validation.dart @@ -8,11 +8,18 @@ import '../../data_assets_builder.dart'; import 'syntax.g.dart' as syntax; Future<ValidationErrors> validateDataAssetBuildInput(BuildInput input) async => - const []; + [ + ..._validateHookInput([ + for (final assets in input.assets.encodedAssets.values) ...assets, + ]), + ]; -Future<ValidationErrors> validateDataAssetLinkInput(LinkInput input) async { +Future<ValidationErrors> validateDataAssetLinkInput(LinkInput input) async => + _validateHookInput(input.assets.encodedAssets); + +List<String> _validateHookInput(List<EncodedAsset> assets) { final errors = <String>[]; - for (final asset in input.assets.encodedAssets) { + for (final asset in assets) { final syntaxErrors = _validateDataAssetSyntax(asset); if (!asset.isDataAsset) continue; if (syntaxErrors.isNotEmpty) { @@ -35,6 +42,7 @@ Future<ValidationErrors> validateDataAssetBuildOutput( BuildOutput output, ) => _validateDataAssetBuildOrLinkOutput(input, [ ...output.assets.encodedAssets, + ...output.assets.encodedAssetsForBuild, for (final assetList in output.assets.encodedAssetsForLinking.values) ...assetList, ], true); diff --git a/pkgs/native_assets_cli/lib/src/metadata.dart b/pkgs/native_assets_cli/lib/src/metadata.dart index 19255ead3f..48d94899d5 100644 --- a/pkgs/native_assets_cli/lib/src/metadata.dart +++ b/pkgs/native_assets_cli/lib/src/metadata.dart @@ -4,6 +4,10 @@ import 'package:collection/collection.dart'; +import 'config.dart'; +import 'encoded_asset.dart'; +import 'hooks/syntax.g.dart' as syntax; + class Metadata { final UnmodifiableMapView<String, Object?> metadata; @@ -30,3 +34,51 @@ class Metadata { @override String toString() => 'Metadata(${toJson()})'; } + +/// An asset that contains metadata. +/// +/// Should only be used with [ToLinkHook] and [ToBuildHooks]. +// +// Note: not exported to public API. The public API only contains a way to read +// and write metadata, it doesn't expose the underlying mechanism. +final class MetadataAsset { + final String key; + + final Object? value; + + MetadataAsset({required this.key, required this.value}); + + factory MetadataAsset.fromEncoded(EncodedAsset asset) { + assert(asset.type == _type); + final syntaxNode = syntax.MetadataAssetEncoding.fromJson( + asset.encoding, + path: asset.jsonPath ?? [], + ); + return MetadataAsset(key: syntaxNode.key, value: syntaxNode.value); + } + + @override + bool operator ==(Object other) { + if (other is! MetadataAsset) { + return false; + } + return other.key == key && + const DeepCollectionEquality().equals(value, other.value); + } + + @override + int get hashCode => + Object.hash(key, const DeepCollectionEquality().hash(value)); + + EncodedAsset encode() => EncodedAsset(_type, {'key': key, 'value': value}); + + @override + String toString() => 'MetadataAsset(${encode().encoding})'; + + static const _type = 'hooks/metadata'; +} + +extension EncodedMetadataAsset on EncodedAsset { + bool get isMetadataAsset => type == MetadataAsset._type; + MetadataAsset get asMetadataAsset => MetadataAsset.fromEncoded(this); +} diff --git a/pkgs/native_assets_cli/pubspec.yaml b/pkgs/native_assets_cli/pubspec.yaml index e42a689b1e..200eb7916e 100644 --- a/pkgs/native_assets_cli/pubspec.yaml +++ b/pkgs/native_assets_cli/pubspec.yaml @@ -3,7 +3,7 @@ description: >- A library that contains the argument and file formats for implementing a native assets CLI. -version: 0.13.1-wip +version: 0.14.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_assets_cli publish_to: none diff --git a/pkgs/native_assets_cli/test/build_input_test.dart b/pkgs/native_assets_cli/test/build_input_test.dart index 78b91bec2f..6afde591d9 100644 --- a/pkgs/native_assets_cli/test/build_input_test.dart +++ b/pkgs/native_assets_cli/test/build_input_test.dart @@ -20,6 +20,7 @@ void main() async { late String packageName; late Uri packageRootUri; late Map<String, Metadata> metadata; + late Map<String, List<EncodedAsset>> assets; late Map<String, Object?> inputJson; setUp(() async { @@ -36,8 +37,33 @@ void main() async { }), 'foo': Metadata({'key': 321}), }; + assets = { + 'my_package': [ + for (int i = 0; i < 3; i++) + EncodedAsset('my-asset-type', {'a-$i': 'v-$i'}), + ], + }; inputJson = { + 'assets': { + 'my_package': [ + { + 'a-0': 'v-0', + 'encoding': {'a-0': 'v-0'}, + 'type': 'my-asset-type', + }, + { + 'a-1': 'v-1', + 'encoding': {'a-1': 'v-1'}, + 'type': 'my-asset-type', + }, + { + 'a-2': 'v-2', + 'encoding': {'a-2': 'v-2'}, + 'type': 'my-asset-type', + }, + ], + }, 'config': { 'build_asset_types': ['my-asset-type'], 'linking_enabled': false, @@ -70,7 +96,7 @@ void main() async { ) ..config.addBuildAssetTypes(['my-asset-type']) ..config.setupBuild(linkingEnabled: false) - ..setupBuildInput(metadata: metadata); + ..setupBuildInput(metadata: metadata, assets: assets); final input = BuildInput(inputBuilder.json); expect(input.json, inputJson); @@ -87,7 +113,8 @@ void main() async { expect(input.config.buildAssetTypes, ['my-asset-type']); expect(input.config.linkingEnabled, false); - expect(input.metadata, metadata); + expect(input.metadataOld, metadata); + expect(input.assets.encodedAssets, assets); }); group('BuildInput format issues', () { diff --git a/pkgs/native_assets_cli/test/build_output_test.dart b/pkgs/native_assets_cli/test/build_output_test.dart index 0c2bf54c74..9708a178e0 100644 --- a/pkgs/native_assets_cli/test/build_output_test.dart +++ b/pkgs/native_assets_cli/test/build_output_test.dart @@ -21,20 +21,28 @@ void main() { final builder = BuildOutputBuilder(); final after = DateTime.now().roundDownToSeconds(); - builder.addDependency(uris.take(1).single); + builder.addDependency(uris.first); builder.addDependencies(uris.skip(1).toList()); builder.addMetadatum(metadata0.keys.single, metadata0.values.single); builder.addMetadata(metadata1); - builder.assets.addEncodedAsset(assets.take(1).single); + builder.assets.addEncodedAsset(assets.first); + builder.assets.addEncodedAsset( + assets.skip(2).first, + routing: const ToBuildHooks(), + ); builder.assets.addEncodedAsset( assets.skip(1).first, - linkInPackage: 'package:linker1', + routing: const ToLinkHook('package:linker1'), ); builder.assets.addEncodedAssets(assets.skip(2).take(2).toList()); + builder.assets.addEncodedAssets( + assets.take(2), + routing: const ToBuildHooks(), + ); builder.assets.addEncodedAssets( assets.skip(4).toList(), - linkInPackage: 'package:linker2', + routing: const ToLinkHook('package:linker2'), ); final input = BuildOutput(builder.json); @@ -46,7 +54,7 @@ void main() { ); // The JSON format of the build output. - <String, Object?>{ + final expectedJson = <String, Object?>{ 'version': '1.9.0', 'dependencies': ['path0', 'path1', 'path2'], 'metadata': { @@ -66,6 +74,23 @@ void main() { 'type': 'my-asset-type', }, ], + 'assets_for_build': [ + { + 'a-2': 'v-2', + 'encoding': {'a-2': 'v-2'}, + 'type': 'my-asset-type', + }, + { + 'a-0': 'v-0', + 'encoding': {'a-0': 'v-0'}, + 'type': 'my-asset-type', + }, + { + 'a-1': 'v-1', + 'encoding': {'a-1': 'v-1'}, + 'type': 'my-asset-type', + }, + ], 'assetsForLinking': { 'package:linker1': [ { @@ -86,9 +111,10 @@ void main() { ], 'package:linker2': <Object?>[], }, - }.forEach((k, v) { - expect(input.json[k], equals(v)); - }); + 'timestamp': input.timestamp.toString(), + }; + + expect(input.json, equals(expectedJson)); }); for (final version in ['9001.0.0', '0.0.1']) { diff --git a/pkgs/native_assets_cli/test/model/metadata_asset_test.dart b/pkgs/native_assets_cli/test/model/metadata_asset_test.dart new file mode 100644 index 0000000000..247dcaaae2 --- /dev/null +++ b/pkgs/native_assets_cli/test/model/metadata_asset_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2025, 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 'package:native_assets_cli/src/encoded_asset.dart'; +import 'package:native_assets_cli/src/metadata.dart'; +import 'package:test/test.dart'; + +void main() async { + final jsonEncoding = { + 'encoding': { + 'key': 'some_key', + 'value': {'foo': 'bar'}, + }, + 'key': 'some_key', + 'type': 'hooks/metadata', + 'value': {'foo': 'bar'}, + }; + final metadataAsset = MetadataAsset(key: 'some_key', value: {'foo': 'bar'}); + + test('CodeAsset toJson', () { + expect(metadataAsset.encode().toJson(), equals(jsonEncoding)); + }); + + test('CodeAsset fromJson', () { + final encodedAsset = EncodedAsset.fromJson(jsonEncoding); + expect(encodedAsset.isMetadataAsset, isTrue); + expect(MetadataAsset.fromEncoded(encodedAsset), metadataAsset); + }); +} diff --git a/pkgs/native_assets_cli/test/validation_test.dart b/pkgs/native_assets_cli/test/validation_test.dart index 810bc91216..130f8c4b92 100644 --- a/pkgs/native_assets_cli/test/validation_test.dart +++ b/pkgs/native_assets_cli/test/validation_test.dart @@ -50,7 +50,7 @@ void main() { await assetFile.writeAsBytes([1, 2, 3]); outputBuilder.assets.addEncodedAsset( EncodedAsset('my-asset-type', {}), - linkInPackage: 'bar', + routing: const ToLinkHook('bar'), ); final errors = await validateBuildOutput( input, diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index 93ee66ce10..222615ffdf 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.10.1-wip +## 0.11.0-wip -- Nothing yet. +- Replace `linkInPackage` with `Routing`. +- Bump `package:native_assets_cli` to 0.14.0. ## 0.10.0 diff --git a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart index 561210bc37..d18d9e67d9 100644 --- a/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart +++ b/pkgs/native_toolchain_c/lib/src/cbuilder/cbuilder.dart @@ -117,7 +117,7 @@ class CBuilder extends CTool implements Builder { required BuildInput input, required BuildOutputBuilder output, required Logger? logger, - String? linkInPackage, + List<AssetRouting> routing = const [ToAppBundle()], }) async { if (!input.config.buildCodeAssets) { logger?.info( @@ -127,8 +127,8 @@ class CBuilder extends CTool implements Builder { return; } assert( - input.config.linkingEnabled || linkInPackage == null, - 'linkInPackage can only be provided if input.config.linkingEnabled' + input.config.linkingEnabled || routing.whereType<ToLinkHook>().isEmpty, + 'ToLinker can only be provided if input.config.linkingEnabled' ' is true.', ); final outDir = input.outputDirectory; @@ -200,17 +200,19 @@ class CBuilder extends CTool implements Builder { await task.run(); if (assetName != null) { - output.assets.code.add( - CodeAsset( - package: input.packageName, - name: assetName!, - file: libUri, - linkMode: linkMode, - os: input.config.code.targetOS, - architecture: input.config.code.targetArchitecture, - ), - linkInPackage: linkInPackage, - ); + for (final route in routing) { + output.assets.code.add( + CodeAsset( + package: input.packageName, + name: assetName!, + file: libUri, + linkMode: linkMode, + os: input.config.code.targetOS, + architecture: input.config.code.targetArchitecture, + ), + routing: route, + ); + } } final includeFiles = diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml index 845c87a78b..5148fd09d4 100644 --- a/pkgs/native_toolchain_c/pubspec.yaml +++ b/pkgs/native_toolchain_c/pubspec.yaml @@ -1,7 +1,7 @@ name: native_toolchain_c description: >- A library to invoke the native C compiler installed on the host machine. -version: 0.10.1-wip +version: 0.11.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c publish_to: none