diff --git a/pkgs/code_assets/CHANGELOG.md b/pkgs/code_assets/CHANGELOG.md index ca0282bc1..3c681dc5a 100644 --- a/pkgs/code_assets/CHANGELOG.md +++ b/pkgs/code_assets/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.20.0 + +* Enable passing metadata from link hooks of a package to the link hooks in + depending packages, by fixing the link hook execution order. + ## 0.19.4 * Add doc comments to all public members. diff --git a/pkgs/code_assets/lib/src/code_assets/config.dart b/pkgs/code_assets/lib/src/code_assets/config.dart index 22ef3b237..0a5d158b1 100644 --- a/pkgs/code_assets/lib/src/code_assets/config.dart +++ b/pkgs/code_assets/lib/src/code_assets/config.dart @@ -185,11 +185,19 @@ final class LinkOutputCodeAssetBuilder { LinkOutputCodeAssetBuilder._(this._output); - /// Adds the given [asset] to the link hook output. - void add(CodeAsset asset) => _output.addEncodedAsset(asset.encode()); + /// Adds the given [asset] to the hook output with [routing]. + void add(CodeAsset asset, {LinkAssetRouting routing = const ToAppBundle()}) => + _output.addEncodedAsset(asset.encode(), routing: routing); - /// Adds the given [assets] to the link hook output. - void addAll(Iterable assets) => assets.forEach(add); + /// Adds the given [assets] to the hook output with [routing]. + void addAll( + Iterable assets, { + LinkAssetRouting routing = const ToAppBundle(), + }) { + for (final asset in assets) { + add(asset, routing: routing); + } + } } /// Extension to initialize code specific configuration on link/build inputs. diff --git a/pkgs/code_assets/lib/src/code_assets/validation.dart b/pkgs/code_assets/lib/src/code_assets/validation.dart index 68769bc4d..278c81f7a 100644 --- a/pkgs/code_assets/lib/src/code_assets/validation.dart +++ b/pkgs/code_assets/lib/src/code_assets/validation.dart @@ -91,8 +91,7 @@ Future validateCodeAssetBuildOutput( output.assets.encodedAssets, [ ...output.assets.encodedAssetsForBuild, - for (final assetList in output.assets.encodedAssetsForLinking.values) - ...assetList, + ...output.assets.encodedAssetsForLinking.values.expand((l) => l), ], output, true, @@ -106,7 +105,7 @@ Future validateCodeAssetLinkOutput( input, input.config.code, output.assets.encodedAssets, - [], + output.assets.encodedAssetsForLink.values.expand((l) => l), output, false, ); @@ -135,8 +134,8 @@ Future validateCodeAssetInApplication( Future _validateCodeAssetBuildOrLinkOutput( HookInput input, CodeConfig codeConfig, - List encodedAssetsBundled, - List encodedAssetsNotBundled, + Iterable encodedAssetsBundled, + Iterable encodedAssetsNotBundled, HookOutput output, bool isBuild, ) async { diff --git a/pkgs/code_assets/pubspec.yaml b/pkgs/code_assets/pubspec.yaml index fe4abd186..265d8c1a8 100644 --- a/pkgs/code_assets/pubspec.yaml +++ b/pkgs/code_assets/pubspec.yaml @@ -3,7 +3,7 @@ description: >- This library contains the hook protocol specification for bundling native code with Dart packages. -version: 0.19.4 +version: 0.20.0 repository: https://github.com/dart-lang/native/tree/main/pkgs/code_assets @@ -21,7 +21,7 @@ environment: dependencies: collection: ^1.19.1 - hooks: ^0.19.5 + hooks: ^0.20.0 dev_dependencies: custom_lint: ^0.7.5 diff --git a/pkgs/code_assets/test/code_assets/config_test.dart b/pkgs/code_assets/test/code_assets/config_test.dart index 335ce4956..e74b84cf1 100644 --- a/pkgs/code_assets/test/code_assets/config_test.dart +++ b/pkgs/code_assets/test/code_assets/config_test.dart @@ -87,6 +87,7 @@ void main() async { 'extensions': {'code_assets': codeConfig}, if (hookType == 'build') 'linking_enabled': false, }, + if (hookType == 'link') 'internal_assets': [], 'out_dir_shared': outputDirectoryShared.toFilePath(), 'out_file': outFile.toFilePath(), 'package_name': packageName, @@ -168,7 +169,7 @@ void main() async { outputFile: outFile, outputDirectoryShared: outputDirectoryShared, ) - ..setupLink(assets: assets, recordedUsesFile: null) + ..setupLink(assets: assets, recordedUsesFile: null, internalAssets: []) ..addExtension( CodeAssetExtension( targetOS: OS.android, diff --git a/pkgs/data_assets/CHANGELOG.md b/pkgs/data_assets/CHANGELOG.md index 7e414efeb..bf9449228 100644 --- a/pkgs/data_assets/CHANGELOG.md +++ b/pkgs/data_assets/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.20.0 + +* Enable passing metadata from link hooks of a package to the link hooks in + depending packages, by fixing the link hook execution order. + ## 0.19.1 * Bump the SDK constraint to at least the one from `package:hooks` to fix diff --git a/pkgs/data_assets/lib/src/data_assets/config.dart b/pkgs/data_assets/lib/src/data_assets/config.dart index f8bddcf34..229574853 100644 --- a/pkgs/data_assets/lib/src/data_assets/config.dart +++ b/pkgs/data_assets/lib/src/data_assets/config.dart @@ -27,11 +27,24 @@ extension BuildOutputBuilderAddDataAssetsDirectories on BuildOutputBuilder { List paths, { required BuildInput input, bool recursive = false, + AssetRouting routing = const ToAppBundle(), }) async { String assetName(Uri assetUri) => assetUri .toFilePath(windows: false) .substring(input.packageRoot.toFilePath().length); + void addAsset(File file) { + assets.data.add( + DataAsset( + package: input.packageName, + name: assetName(file.uri), + file: file.uri, + ), + routing: routing, + ); + addDependency(file.uri); + } + for (final path in paths) { final resolvedUri = input.packageRoot.resolve(path); final directory = Directory.fromUri(resolvedUri); @@ -45,15 +58,10 @@ extension BuildOutputBuilderAddDataAssetsDirectories on BuildOutputBuilder { followLinks: false, )) { if (entity is File) { - assets.data.add( - DataAsset( - package: input.packageName, - name: assetName(entity.uri), - file: entity.uri, - ), - ); + addAsset(entity); + } else { + addDependency(entity.uri); } - addDependency(entity.uri); } } on FileSystemException catch (e) { throw FileSystemException( @@ -63,14 +71,7 @@ extension BuildOutputBuilderAddDataAssetsDirectories on BuildOutputBuilder { ); } } else if (await file.exists()) { - assets.data.add( - DataAsset( - package: input.packageName, - name: assetName(file.uri), - file: file.uri, - ), - ); - addDependency(file.uri); + addAsset(file); } else { throw FileSystemException( 'Path does not exist', @@ -140,10 +141,18 @@ final class LinkOutputDataAssetsBuilder { LinkOutputDataAssetsBuilder(this._output); /// Adds the given [asset] to the link hook output. - void add(DataAsset asset) => _output.addEncodedAsset(asset.encode()); + void add(DataAsset asset, {LinkAssetRouting routing = const ToAppBundle()}) => + _output.addEncodedAsset(asset.encode(), routing: routing); /// Adds the given [assets] to the link hook output. - void addAll(Iterable assets) => assets.forEach(add); + void addAll( + Iterable assets, { + LinkAssetRouting routing = const ToAppBundle(), + }) { + for (final asset in assets) { + add(asset, routing: routing); + } + } } /// Provides access to [DataAsset]s from a build hook output. diff --git a/pkgs/data_assets/pubspec.yaml b/pkgs/data_assets/pubspec.yaml index 9ebfeed24..61bc337ec 100644 --- a/pkgs/data_assets/pubspec.yaml +++ b/pkgs/data_assets/pubspec.yaml @@ -3,7 +3,7 @@ description: >- This library contains the hook protocol specification for bundling data assets with Dart packages. -version: 0.19.1 +version: 0.20.0 repository: https://github.com/dart-lang/native/tree/main/pkgs/data_assets @@ -17,7 +17,7 @@ environment: sdk: '>=3.9.0-21.0.dev <4.0.0' dependencies: - hooks: ^0.19.5 + hooks: ^0.20.0 dev_dependencies: custom_lint: ^0.7.5 diff --git a/pkgs/hooks/CHANGELOG.md b/pkgs/hooks/CHANGELOG.md index a51a170f3..66cfb3121 100644 --- a/pkgs/hooks/CHANGELOG.md +++ b/pkgs/hooks/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.20.0 + +* Enable passing metadata from link hooks of a package to the link hooks in + depending packages, by fixing the link hook execution order. + ## 0.19.5 * Stop leaking unexported symbols. diff --git a/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json b/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json index bb8805c6f..5a1428b96 100644 --- a/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json +++ b/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json @@ -250,6 +250,12 @@ "$ref": "#/definitions/Asset" } }, + "internal_assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, "resource_identifiers": { "$ref": "#/definitions/absolutePath" } @@ -261,6 +267,18 @@ ] }, "LinkOutput": { + "type": "object", + "properties": { + "assets_for_link": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + }, "allOf": [ { "$ref": "#/definitions/HookOutput" diff --git a/pkgs/hooks/lib/hooks.dart b/pkgs/hooks/lib/hooks.dart index 82bf86dd6..141482a1c 100644 --- a/pkgs/hooks/lib/hooks.dart +++ b/pkgs/hooks/lib/hooks.dart @@ -41,6 +41,7 @@ export 'src/config.dart' HookOutputBuilder, HookOutputFailure, InfraError, + LinkAssetRouting, LinkConfig, LinkConfigBuilder, LinkInput, diff --git a/pkgs/hooks/lib/src/config.dart b/pkgs/hooks/lib/src/config.dart index e5fa388a4..bffddb7d4 100644 --- a/pkgs/hooks/lib/src/config.dart +++ b/pkgs/hooks/lib/src/config.dart @@ -331,10 +331,7 @@ final class BuildConfigBuilder extends HookConfigBuilder { /// The input for a `hook/link.dart`. final class LinkInput extends HookInput { - List get _encodedAssets { - final assets = _syntaxLinkInput.assets; - return EncodedAssetSyntax._fromSyntax(assets); - } + List get _encodedAssets => assets.encodedAssets; /// The file containing recorded usages, if any. Uri? get recordedUsagesFile => _syntaxLinkInput.resourceIdentifiers; @@ -355,6 +352,12 @@ final class LinkInput extends HookInput { /// The assets passed to `hook/link.dart`. LinkInputAssets get assets => LinkInputAssets._(this); + + /// The metadata emitted by dependent build hooks. + List get metadata => assets.encodedInternalAssets + .where((e) => e.isMetadataAsset) + .map((e) => e.asMetadataAsset) + .toList(); } /// The assets in [LinkInput.assets]; @@ -364,7 +367,12 @@ final class LinkInputAssets { LinkInputAssets._(this._input); /// The encoded assets passed to `hook/link.dart`. - List get encodedAssets => _input._encodedAssets; + List get encodedAssets => + EncodedAssetSyntax._fromSyntax(_input._syntaxLinkInput.assets); + + /// The encoded assets from direct dependencies. + List get encodedInternalAssets => + EncodedAssetSyntax._fromSyntax(_input._syntaxLinkInput.internalAssets); } /// The builder for [LinkInput]. @@ -376,11 +384,16 @@ final class LinkInputBuilder extends HookInputBuilder { void setupLink({ required List assets, required Uri? recordedUsesFile, + required List internalAssets, }) { _syntax.setup( assets: [ for (final asset in assets) AssetSyntax.fromJson(asset.toJson()), ], + internalAssets: [ + for (final asset in internalAssets) + AssetSyntax.fromJson(asset.toJson()), + ], resourceIdentifiers: recordedUsesFile, ); } @@ -612,6 +625,26 @@ final class BuildOutputMetadataBuilder { } } +/// The builder for [BuildOutputBuilder.metadata]. +final class LinkOutputMetadataBuilder { + final LinkOutputBuilder _output; + + LinkOutputMetadataBuilder._(this._output); + + /// Sets the metadata [value] for the given [key]. + void add(String packageName, String key, Object value) { + _output.assets.addEncodedAsset( + MetadataAsset(key: key, value: value).encode(), + routing: ToLinkHook(packageName), + ); + } + + /// Adds all entries from [metadata]. + void addAll(String packageName, Map metadata) { + metadata.forEach((key, value) => add(packageName, key, value)); + } +} + /// The destination for assets in the [BuildOutput]. /// /// Currently supported routings: @@ -622,9 +655,19 @@ sealed class AssetRouting { const AssetRouting(); } +/// The destination for assets in the [LinkOutput]. +/// +/// Currently supported routings: +/// * [ToBuildHooks]: From build hook to all dependent builds hooks. +/// * [ToLinkHook]: From build hook to a specific link hook. +/// * [ToAppBundle]: From build or link hook to the application Bundle. +sealed class LinkAssetRouting { + const LinkAssetRouting(); +} + /// Assets with this [AssetRouting] in the [BuildOutput] will be sent to the SDK /// to be bundled with the app. -final class ToAppBundle extends AssetRouting { +final class ToAppBundle implements AssetRouting, LinkAssetRouting { /// Creates a [ToAppBundle]. const ToAppBundle(); } @@ -643,7 +686,7 @@ final class ToAppBundle extends AssetRouting { /// 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 { +final class ToBuildHooks implements AssetRouting { /// Creates a [ToBuildHooks]. const ToBuildHooks(); } @@ -659,7 +702,7 @@ final class ToBuildHooks extends AssetRouting { /// 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 class ToLinkHook implements AssetRouting, LinkAssetRouting { /// The name of the package that contains the `hook/link.dart` to which assets /// should be sent. final String packageName; @@ -764,6 +807,16 @@ final class BuildOutputAssetsBuilder { /// /// See [LinkOutputFailure] for failure. final class LinkOutput extends HookOutput implements LinkOutputMaybeFailure { + /// The assets produced by this link which are sent to linkers upstream. + /// + /// Every key in the map is a package name. These assets in the values are not + /// bundled with the application, but are sent to the link hook of the package + /// specified in the key, which can decide what to do with them. + Map> get _encodedAssetsForLinking => { + for (final MapEntry(:key, :value) in (_syntax.assetsForLink ?? {}).entries) + key: EncodedAssetSyntax._fromSyntax(value), + }; + /// Creates a [LinkOutput] from the given [json]. LinkOutput(super.json) : _syntax = LinkOutputSyntax.fromJson(json), super._(); @@ -782,6 +835,11 @@ final class LinkOutputAssets { /// The assets produced by this build. List get encodedAssets => _output._encodedAssets; + + /// The assets produced by this build which should be available to subsequent + /// link hooks. + Map> get encodedAssetsForLink => + _output._encodedAssetsForLinking; } /// The builder for [LinkOutput]. @@ -799,6 +857,9 @@ final class LinkOutputAssets { /// } /// ``` final class LinkOutputBuilder extends HookOutputBuilder { + /// The metadata builder for this build output. + LinkOutputMetadataBuilder get metadata => LinkOutputMetadataBuilder._(this); + /// The assets builder for this link output. LinkOutputAssetsBuilder get assets => LinkOutputAssetsBuilder._(this); @@ -825,10 +886,24 @@ final class LinkOutputAssetsBuilder { /// }); /// } /// ``` - void addEncodedAsset(EncodedAsset asset) { - final list = _syntax.assets ?? []; - list.add(AssetSyntax.fromJson(asset.toJson())); - _syntax.assets = list; + void addEncodedAsset( + EncodedAsset asset, { + LinkAssetRouting routing = const ToAppBundle(), + }) { + switch (routing) { + case ToAppBundle(): + final assets = _syntax.assets ?? []; + assets.add(AssetSyntax.fromJson(asset.toJson())); + _syntax.assets = assets; + case ToLinkHook(): + final packageName = routing.packageName; + final assetsForLinking = _syntax.assetsForLink ?? {}; + assetsForLinking[packageName] ??= []; + assetsForLinking[packageName]!.add( + AssetSyntax.fromJson(asset.toJson()), + ); + _syntax.assetsForLink = assetsForLinking; + } } /// Adds [EncodedAsset]s produced by this build. @@ -844,12 +919,26 @@ final class LinkOutputAssetsBuilder { /// }); /// } /// ``` - void addEncodedAssets(Iterable assets) { - final list = _syntax.assets ?? []; - for (final asset in assets) { - list.add(AssetSyntax.fromJson(asset.toJson())); + void addEncodedAssets( + Iterable assets, { + LinkAssetRouting routing = const ToAppBundle(), + }) { + switch (routing) { + case ToAppBundle(): + final list = _syntax.assets ?? []; + for (final asset in assets) { + list.add(AssetSyntax.fromJson(asset.toJson())); + } + _syntax.assets = list; + case ToLinkHook(): + final linkInPackage = routing.packageName; + final assetsForLinking = _syntax.assetsForLink ?? {}; + final list = assetsForLinking[linkInPackage] ??= []; + for (final asset in assets) { + list.add(AssetSyntax.fromJson(asset.toJson())); + } + _syntax.assetsForLink = assetsForLinking; } - _syntax.assets = list; } LinkOutputSyntax get _syntax => diff --git a/pkgs/hooks/lib/src/hooks/syntax.g.dart b/pkgs/hooks/lib/src/hooks/syntax.g.dart index 34dc7fcdc..5c41b8cb2 100644 --- a/pkgs/hooks/lib/src/hooks/syntax.g.dart +++ b/pkgs/hooks/lib/src/hooks/syntax.g.dart @@ -742,6 +742,7 @@ class LinkInputSyntax extends HookInputSyntax { LinkInputSyntax({ required List? assets, required super.config, + required List? internalAssets, required super.outDirShared, required super.outFile, required super.packageName, @@ -750,6 +751,7 @@ class LinkInputSyntax extends HookInputSyntax { required super.userDefines, }) : super() { _assets = assets; + _internalAssets = internalAssets; _resourceIdentifiers = resourceIdentifiers; json.sortOnKey(); } @@ -758,9 +760,11 @@ class LinkInputSyntax extends HookInputSyntax { /// [HookInputSyntax]. void setup({ required List? assets, + required List? internalAssets, required Uri? resourceIdentifiers, }) { _assets = assets; + _internalAssets = internalAssets; _resourceIdentifiers = resourceIdentifiers; json.sortOnKey(); } @@ -799,6 +803,40 @@ class LinkInputSyntax extends HookInputSyntax { return [for (final element in elements) ...element.validate()]; } + List? get internalAssets { + final jsonValue = _reader.optionalList('internal_assets'); + if (jsonValue == null) return null; + return [ + for (final (index, element) in jsonValue.indexed) + AssetSyntax.fromJson( + element as Map, + path: [...path, 'internal_assets', index], + ), + ]; + } + + set _internalAssets(List? value) { + if (value == null) { + json.remove('internal_assets'); + } else { + json['internal_assets'] = [for (final item in value) item.json]; + } + } + + List _validateInternalAssets() { + final listErrors = _reader.validateOptionalList>( + 'internal_assets', + ); + if (listErrors.isNotEmpty) { + return listErrors; + } + final elements = internalAssets; + if (elements == null) { + return []; + } + return [for (final element in elements) ...element.validate()]; + } + Uri? get resourceIdentifiers => _reader.optionalPath('resource_identifiers'); set _resourceIdentifiers(Uri? value) { @@ -812,6 +850,7 @@ class LinkInputSyntax extends HookInputSyntax { List validate() => [ ...super.validate(), ..._validateAssets(), + ..._validateInternalAssets(), ..._validateResourceIdentifiers(), ]; @@ -824,15 +863,75 @@ class LinkOutputSyntax extends HookOutputSyntax { LinkOutputSyntax({ required super.assets, + required Map>? assetsForLink, required super.dependencies, required super.failureDetails, required super.status, required super.timestamp, - }) : super(); + }) : super() { + this.assetsForLink = assetsForLink; + json.sortOnKey(); + } + + /// Setup all fields for [LinkOutputSyntax] that are not in + /// [HookOutputSyntax]. + void setup({required Map>? assetsForLink}) { + this.assetsForLink = assetsForLink; + json.sortOnKey(); + } + + Map>? get assetsForLink { + final jsonValue = _reader.optionalMap('assets_for_link'); + if (jsonValue == null) { + return null; + } + final result = >{}; + for (final MapEntry(:key, :value) in jsonValue.entries) { + result[key] = [ + for (final (index, item) in (value as List).indexed) + AssetSyntax.fromJson( + item as Map, + path: [...path, key, index], + ), + ]; + } + return result; + } + + set assetsForLink(Map>? value) { + if (value == null) { + json.remove('assets_for_link'); + } else { + json['assets_for_link'] = { + for (final MapEntry(:key, :value) in value.entries) + key: [for (final item in value) item.json], + }; + } + json.sortOnKey(); + } + + List _validateAssetsForLink() { + final mapErrors = _reader.validateOptionalMap('assets_for_link'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + final jsonValue = _reader.optionalMap('assets_for_link'); + if (jsonValue == null) { + return []; + } + final result = []; + for (final list in assetsForLink!.values) { + for (final element in list) { + result.addAll(element.validate()); + } + } + return result; + } @override List validate() => [ ...super.validate(), + ..._validateAssetsForLink(), ..._validateExtraRulesLinkOutput(), ]; diff --git a/pkgs/hooks/pubspec.yaml b/pkgs/hooks/pubspec.yaml index 56d23d18e..611cb9c37 100644 --- a/pkgs/hooks/pubspec.yaml +++ b/pkgs/hooks/pubspec.yaml @@ -3,7 +3,7 @@ description: >- A library that contains a Dart API for the JSON-based protocol for `hook/build.dart` and `hook/link.dart`. -version: 0.19.5 +version: 0.20.0 repository: https://github.com/dart-lang/native/tree/main/pkgs/hooks @@ -29,7 +29,7 @@ dependencies: dev_dependencies: args: ^2.6.0 - code_assets: ^0.19.4 # Used for running tests with real asset types. + code_assets: ^0.20.0 # Used for running tests with real asset types. custom_lint: ^0.7.5 dart_flutter_team_lints: ^3.5.2 data_assets: any # Used for running tests with real asset types. diff --git a/pkgs/hooks/test/link_input_test.dart b/pkgs/hooks/test/link_input_test.dart index 48d6254f0..3f89b7bc3 100644 --- a/pkgs/hooks/test/link_input_test.dart +++ b/pkgs/hooks/test/link_input_test.dart @@ -34,6 +34,7 @@ void main() async { 'config': { 'build_asset_types': ['asset-type-1', 'asset-type-2'], }, + 'internal_assets': [], 'out_dir_shared': outputDirectoryShared.toFilePath(), 'out_file': outFile.toFilePath(), 'package_name': packageName, @@ -50,7 +51,7 @@ void main() async { outputDirectoryShared: outputDirectoryShared, ) ..config.addBuildAssetTypes(['asset-type-1', 'asset-type-2']) - ..setupLink(assets: assets, recordedUsesFile: null); + ..setupLink(assets: assets, recordedUsesFile: null, internalAssets: []); final input = inputBuilder.build(); expect(input.json, inputJson); diff --git a/pkgs/hooks_runner/CHANGELOG.md b/pkgs/hooks_runner/CHANGELOG.md index 7997727af..b9932663c 100644 --- a/pkgs/hooks_runner/CHANGELOG.md +++ b/pkgs/hooks_runner/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.22.0 + +* Enable passing metadata from link hooks of a package to the link hooks in + depending packages, by fixing the link hook execution order. + ## 0.21.0 * Add `includeDevDependencies` param to `BuildLayout` to enable building the diff --git a/pkgs/hooks_runner/lib/src/build_runner/build_planner.dart b/pkgs/hooks_runner/lib/src/build_runner/build_planner.dart index 3c8347434..c9a63b0ef 100644 --- a/pkgs/hooks_runner/lib/src/build_runner/build_planner.dart +++ b/pkgs/hooks_runner/lib/src/build_runner/build_planner.dart @@ -112,6 +112,7 @@ class NativeAssetsBuildPlanner { } BuildPlan? _buildHookPlan; + BuildPlan? _linkHookPlan; /// Plans in what order to run build hooks. /// @@ -126,40 +127,84 @@ class NativeAssetsBuildPlanner { /// hooks, the [Result] is a [Failure] containing a /// [HooksRunnerFailure.projectConfig]. Future> makeBuildHookPlan() async => - _timeAsync('BuildPlanner.makeBuildHookPlan', () async { - if (_buildHookPlan != null) return Success(_buildHookPlan!); - final packagesWithNativeAssets = await packagesWithHook(Hook.build); - if (packagesWithNativeAssets.isEmpty) { - // Avoid calculating the package graph if there are no hooks. - return const Success([]); - } - final packageMap = { - for (final package in packagesWithNativeAssets) package.name: package, - }; - final packagesToBuild = packageMap.keys.toSet(); - final stronglyConnectedComponents = packageGraph - .computeStrongComponents(); - final result = []; - for (final stronglyConnectedComponent in stronglyConnectedComponents) { - final stronglyConnectedComponentWithNativeAssets = [ - for (final packageName in stronglyConnectedComponent) - if (packagesToBuild.contains(packageName)) packageName, - ]; - if (stronglyConnectedComponentWithNativeAssets.length > 1) { - logger.severe( - 'Cyclic dependency for native asset builds in the following ' - 'packages: $stronglyConnectedComponentWithNativeAssets.', - ); - return const Failure(HooksRunnerFailure.projectConfig); - } else if (stronglyConnectedComponentWithNativeAssets.length == 1) { - result.add( - packageMap[stronglyConnectedComponentWithNativeAssets.single]!, - ); - } - } - _buildHookPlan = result; - return Success(result); - }); + _timeAsync( + 'BuildPlanner.makeBuildHookPlan', + () async => _makeHookPlan( + hookType: Hook.build, + cachedPlan: _buildHookPlan, + setCachedPlan: (plan) => _buildHookPlan = plan, + reverseOrder: false, + ), + ); + + /// Plans in what order to run link hooks. + /// + /// [PackageLayout.runPackageName] provides the entry-point in the graph. The + /// hooks of packages not in the transitive dependencies of + /// [PackageLayout.runPackageName] will not be run. + /// + /// Returns a [Future] that completes with a [Result]. On success, the + /// [Result] is a [Success] containing the [BuildPlan], which is a list of + /// packages in the order their link hooks should be executed. On failure, if + /// a cyclic dependency is detected among packages with native asset link + /// hooks, the [Result] is a [Failure] containing a + /// [HooksRunnerFailure.projectConfig]. + Future> makeLinkHookPlan() async => + _timeAsync( + 'BuildPlanner.makeLinkHookPlan', + () async => _makeHookPlan( + hookType: Hook.link, + cachedPlan: _linkHookPlan, + setCachedPlan: (plan) => _linkHookPlan = plan, + reverseOrder: true, // Key difference here + ), + ); + + Future> _makeHookPlan({ + required Hook hookType, + required BuildPlan? cachedPlan, + required void Function(BuildPlan) setCachedPlan, + required bool reverseOrder, + }) async { + if (cachedPlan != null) return Success(cachedPlan); + + final packagesWithNativeAssets = await packagesWithHook(hookType); + if (packagesWithNativeAssets.isEmpty) { + return const Success([]); + } + + final packageMap = { + for (final package in packagesWithNativeAssets) package.name: package, + }; + final packagesToProcess = packageMap.keys.toSet(); + + final stronglyConnectedComponents = reverseOrder + ? packageGraph.computeStrongComponents().reversed + : packageGraph.computeStrongComponents(); + + final result = []; + for (final stronglyConnectedComponent in stronglyConnectedComponents) { + final stronglyConnectedComponentWithNativeAssets = [ + for (final packageName in stronglyConnectedComponent) + if (packagesToProcess.contains(packageName)) packageName, + ]; + + if (stronglyConnectedComponentWithNativeAssets.length > 1) { + logger.severe( + 'Cyclic dependency for native asset ${hookType.name}s in the ' + 'following packages: $stronglyConnectedComponentWithNativeAssets.', + ); + return const Failure(HooksRunnerFailure.projectConfig); + } else if (stronglyConnectedComponentWithNativeAssets.length == 1) { + result.add( + packageMap[stronglyConnectedComponentWithNativeAssets.single]!, + ); + } + } + + setCachedPlan(result); + return Success(result); + } Future _timeAsync( String name, @@ -181,9 +226,24 @@ class NativeAssetsBuildPlanner { /// dependencies. class PackageGraph { final Map> map; + late final Map> _inverseMap = _computeInverseMap(map); PackageGraph(this.map); + // Helper method to compute the inverse map + static Map> _computeInverseMap( + Map> graphMap, + ) { + final inverse = >{}; + for (final packageName in graphMap.keys) { + inverse.putIfAbsent(packageName, () => []); + for (final dependency in graphMap[packageName]!) { + inverse.putIfAbsent(dependency, () => []).add(packageName); + } + } + return inverse; + } + factory PackageGraph.fromPackageGraphJsonString( String json, String runPackageName, { @@ -222,6 +282,10 @@ class PackageGraph { Iterable neighborsOf(String vertex) => map[vertex] ?? []; + // New method to get inverse neighbors (incoming edges) + Iterable inverseNeighborsOf(String vertex) => + _inverseMap[vertex] ?? []; + Iterable get vertices => map.keys; List> computeStrongComponents() => diff --git a/pkgs/hooks_runner/lib/src/build_runner/build_runner.dart b/pkgs/hooks_runner/lib/src/build_runner/build_runner.dart index bd7a9ba7b..35fe45acd 100644 --- a/pkgs/hooks_runner/lib/src/build_runner/build_runner.dart +++ b/pkgs/hooks_runner/lib/src/build_runner/build_runner.dart @@ -152,11 +152,11 @@ class NativeAssetsBuildRunner { /// Key is packageName. final globalAssetsForBuild = >{}; for (final package in buildPlan) { - final assetsForBuild = _assetsForBuildForPackage( - packageGraph: packageGraph!, - packageName: package.name, - globalAssetsForBuild: globalAssetsForBuild, - ); + final dependencies = packageGraph!.neighborsOf(package.name).toSet(); + final assetsForBuild = >{ + for (final entry in globalAssetsForBuild.entries) + if (dependencies.contains(entry.key)) entry.key: entry.value, + }; final inputBuilder = BuildInputBuilder(); @@ -248,21 +248,36 @@ class NativeAssetsBuildRunner { Uri? resourceIdentifiers, required BuildResult buildResult, }) async => _timeAsync('BuildRunner.link', () async { - final loadedUserDefines = await _loadedUserDefines; - final hookResultUserDefines = await _checkUserDefines(loadedUserDefines); - if (hookResultUserDefines.isFailure) { - return hookResultUserDefines; - } - var linkResult = hookResultUserDefines.success; - final planResult = await _makePlan( hook: Hook.link, buildResult: buildResult, ); if (planResult.isFailure) return planResult.asFailure; final (buildPlan, packageGraph) = planResult.success; + if (buildPlan.isEmpty) { + // Return eagerly if there are no link hooks at all. + return Success(HookResult()); + } + final loadedUserDefines = await _loadedUserDefines; + final hookResultUserDefines = await _checkUserDefines(loadedUserDefines); + if (hookResultUserDefines.isFailure) { + return hookResultUserDefines; + } + var linkResult = hookResultUserDefines.success; + + /// Key is packageName. + final globalAssetsForBuild = >>{}; for (final package in buildPlan) { + final dependencies = packageGraph! + .inverseNeighborsOf(package.name) + .toSet(); + + final internalAssets = (globalAssetsForBuild[package.name] ?? {}).entries + .where((entry) => dependencies.contains(entry.key)) + .expand((e) => e.value) + .toList(); + final inputBuilder = LinkInputBuilder(); for (final e in extensions) { e.setupLinkInput(inputBuilder); @@ -288,9 +303,11 @@ class NativeAssetsBuildRunner { outputDirectoryShared: outDirSharedUri, userDefines: loadedUserDefines?[package.name], ); + inputBuilder.setupLink( assets: buildResult.encodedAssetsForLinking[package.name] ?? [], recordedUsesFile: resourcesFile?.uri, + internalAssets: internalAssets, ); final input = inputBuilder.build(); @@ -299,7 +316,6 @@ class NativeAssetsBuildRunner { for (final e in extensions) ...await e.validateLinkInput(input), ]; if (errors.isNotEmpty) { - print(input.assets.encodedAssets); _printErrors('Link input for ${package.name} contains errors', errors); return const Failure(HooksRunnerFailure.internal); } @@ -318,9 +334,23 @@ class NativeAssetsBuildRunner { buildDirUri, outDirUri, ); - if (result.isFailure) return result.asFailure; + if (result.isFailure) { + return result.asFailure; + } final (hookOutput, hookDeps) = result.success; linkResult = linkResult.copyAdd(hookOutput, hookDeps); + + // Merge the assets into the global asset map. + (hookOutput as LinkOutput).assets.encodedAssetsForLink.forEach( + (packageName, assetsForPackage) => globalAssetsForBuild.update( + packageName, + (current) => { + ...current, + ...{package.name: assetsForPackage}, + }, + ifAbsent: () => {package.name: assetsForPackage}, + ), + ); } final errors = [ @@ -784,23 +814,6 @@ ${compileResult.stdout} }, ); - /// Returns only the assets output as assetForBuild by the packages that are - /// the direct dependencies of [packageName]. - Map>? _assetsForBuildForPackage({ - required PackageGraph packageGraph, - required String packageName, - Map>? 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 _validate( HookInput input, HookOutput output, @@ -850,42 +863,25 @@ ${compileResult.stdout} Future< Result<(BuildPlan plan, PackageGraph? dependencyGraph), HooksRunnerFailure> > - _makePlan({required Hook hook, BuildResult? buildResult}) async => _timeAsync( - '_makePlan', - () async { - switch (hook) { - case Hook.build: - final planner = await _planner; - final planResult = await planner.makeBuildHookPlan(); - if (planResult.isFailure) { - return planResult.asFailure; - } - return Success((planResult.success, planner.packageGraph)); - case Hook.link: - // Link hooks are not run in any particular order. - // Link hooks are skipped if no assets for linking are provided. - final buildPlan = []; - final skipped = []; - final encodedAssetsForLinking = buildResult!.encodedAssetsForLinking; - final planner = await _planner; - final packagesWithHook = await planner.packagesWithHook(Hook.link); - for (final package in packagesWithHook) { - if (encodedAssetsForLinking[package.name]?.isNotEmpty ?? false) { - buildPlan.add(package); - } else { - skipped.add(package.name); + _makePlan({required Hook hook, BuildResult? buildResult}) async => + _timeAsync('_makePlan', () async { + switch (hook) { + case Hook.build: + final planner = await _planner; + final planResult = await planner.makeBuildHookPlan(); + if (planResult.isFailure) { + return planResult.asFailure; } - } - if (skipped.isNotEmpty) { - logger.info( - 'Skipping link hooks from ${skipped.join(', ')}' - ' due to no assets provided to link for these link hooks.', - ); - } - return Success((buildPlan, null)); - } - }, - ); + return Success((planResult.success, planner.packageGraph)); + case Hook.link: + final planner = await _planner; + final planResult = await planner.makeLinkHookPlan(); + if (planResult.isFailure) { + return planResult.asFailure; + } + return Success((planResult.success, planner.packageGraph)); + } + }); Future> _readHookOutputFromUri( Hook hook, diff --git a/pkgs/hooks_runner/pubspec.yaml b/pkgs/hooks_runner/pubspec.yaml index db6b476d0..02250c918 100644 --- a/pkgs/hooks_runner/pubspec.yaml +++ b/pkgs/hooks_runner/pubspec.yaml @@ -2,7 +2,7 @@ name: hooks_runner description: >- This package is the backend that invokes build hooks. -version: 0.21.0 +version: 0.22.0 repository: https://github.com/dart-lang/native/tree/main/pkgs/hooks_runner @@ -12,12 +12,12 @@ environment: sdk: '>=3.9.0-21.0.dev <4.0.0' dependencies: - code_assets: ^0.19.4 # Needed for OS for Target for KernelAssets. + code_assets: ^0.20.0 # Needed for OS for Target for KernelAssets. collection: ^1.19.1 crypto: ^3.0.6 file: ^7.0.1 graphs: ^2.3.2 - hooks: ^0.19.5 + hooks: ^0.20.0 logging: ^1.3.0 meta: ^1.16.0 package_config: ^2.1.0 diff --git a/pkgs/hooks_runner/test/build_runner/link_test.dart b/pkgs/hooks_runner/test/build_runner/link_test.dart index 050f48b03..4c89e2fd2 100644 --- a/pkgs/hooks_runner/test/build_runner/link_test.dart +++ b/pkgs/hooks_runner/test/build_runner/link_test.dart @@ -103,41 +103,6 @@ void main() async { }); }); - test('no_asset_for_link', timeout: longTimeout, () async { - await inTempDir((tempUri) async { - await copyTestProjects(targetUri: tempUri); - final packageUri = tempUri.resolve('no_asset_for_link/'); - - // First, run `pub get`, we need pub to resolve our dependencies. - await runPubGet(workingDirectory: packageUri, logger: logger); - - final buildResult = (await buildDataAssets( - packageUri, - linkingEnabled: true, - )).success; - expect(buildResult.encodedAssets.length, 0); - expect(buildResult.encodedAssetsForLinking.length, 0); - - final logMessages = []; - final linkResult = (await link( - packageUri, - logger, - dartExecutable, - buildResult: buildResult, - capturedLogs: logMessages, - buildAssetTypes: [BuildAssetType.data], - )).success; - expect(linkResult.encodedAssets.length, 0); - expect( - logMessages, - contains( - 'Skipping link hooks from no_asset_for_link due to ' - 'no assets provided to link for these link hooks.', - ), - ); - }); - }); - if (Platform.isMacOS || Platform.isWindows) { // https://github.com/dart-lang/native/issues/1376. return; diff --git a/pkgs/hooks_runner/test_data/drop_dylib_link/hook/build.dart b/pkgs/hooks_runner/test_data/drop_dylib_link/hook/build.dart index 63738396d..f27ad7244 100644 --- a/pkgs/hooks_runner/test_data/drop_dylib_link/hook/build.dart +++ b/pkgs/hooks_runner/test_data/drop_dylib_link/hook/build.dart @@ -15,7 +15,7 @@ void main(List arguments) async { print('${record.level.name}: ${record.time}: ${record.message}'); }); final routing = input.config.linkingEnabled - ? [ToLinkHook(input.packageName)] + ? [ToLinkHook(input.packageName)] : [const ToAppBundle()]; await CBuilder.library( name: 'add', diff --git a/pkgs/hooks_runner/test_data/flag_app/.gitignore b/pkgs/hooks_runner/test_data/flag_app/.gitignore new file mode 100644 index 000000000..3a8579040 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_app/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/pkgs/hooks_runner/test_data/flag_app/README.md b/pkgs/hooks_runner/test_data/flag_app/README.md new file mode 100644 index 000000000..8780e56d8 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_app/README.md @@ -0,0 +1,4 @@ +A sample application using `flag_enthusiast_1` and `flag_enthusiast_2`. + +When compiling as a bundle, this leads to only two flags being bundled, as the +`ca.txt` flag is not used. diff --git a/pkgs/hooks_runner/test_data/flag_app/bin/flag_app.dart b/pkgs/hooks_runner/test_data/flag_app/bin/flag_app.dart new file mode 100644 index 000000000..890ffd681 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_app/bin/flag_app.dart @@ -0,0 +1,9 @@ +// 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:flag_app/flag_app.dart' as flag_app; + +void main(List arguments) { + print('Hello world: ${flag_app.calculate()}!'); +} diff --git a/pkgs/hooks_runner/test_data/flag_app/lib/flag_app.dart b/pkgs/hooks_runner/test_data/flag_app/lib/flag_app.dart new file mode 100644 index 000000000..a6b1fa180 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_app/lib/flag_app.dart @@ -0,0 +1,5 @@ +// 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. + +int calculate() => 6 * 7; diff --git a/pkgs/hooks_runner/test_data/flag_app/pubspec.yaml b/pkgs/hooks_runner/test_data/flag_app/pubspec.yaml new file mode 100644 index 000000000..7dfdfc85a --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_app/pubspec.yaml @@ -0,0 +1,15 @@ +name: flag_app +description: A sample application using flags. + +publish_to: none + +resolution: workspace + +environment: + sdk: '>=3.8.0 <4.0.0' + +dependencies: + flag_enthusiast_1: + path: ../flag_enthusiast_1 + flag_enthusiast_2: + path: ../flag_enthusiast_2 diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_1/.gitignore b/pkgs/hooks_runner/test_data/flag_enthusiast_1/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_1/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_1/README.md b/pkgs/hooks_runner/test_data/flag_enthusiast_1/README.md new file mode 100644 index 000000000..8831761b8 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_1/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_1/hook/link.dart b/pkgs/hooks_runner/test_data/flag_enthusiast_1/hook/link.dart new file mode 100644 index 000000000..9537ba10b --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_1/hook/link.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:fun_with_flags/src/hook.dart' show flagsUsed; +import 'package:hooks/hooks.dart'; + +void main(List args) async { + await link(args, (input, output) async { + flagsUsed(input, output, ['de']); + }); +} diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_1/lib/flag_enthusiast_1.dart b/pkgs/hooks_runner/test_data/flag_enthusiast_1/lib/flag_enthusiast_1.dart new file mode 100644 index 000000000..5d63224cf --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_1/lib/flag_enthusiast_1.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/flag_enthusiast_1_base.dart'; diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_1/lib/src/flag_enthusiast_1_base.dart b/pkgs/hooks_runner/test_data/flag_enthusiast_1/lib/src/flag_enthusiast_1_base.dart new file mode 100644 index 000000000..8300e7828 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_1/lib/src/flag_enthusiast_1_base.dart @@ -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. + +import 'package:fun_with_flags/fun_with_flags.dart'; +import 'package:meta/meta.dart' show RecordUse; + +class SingleFlag { + @RecordUse() + String loadFlag(String country) => FlagLoader.load(country); +} diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_1/pubspec.yaml b/pkgs/hooks_runner/test_data/flag_enthusiast_1/pubspec.yaml new file mode 100644 index 000000000..df9385d21 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_1/pubspec.yaml @@ -0,0 +1,22 @@ +name: flag_enthusiast_1 +description: Add and retrieve a data asset. + + +publish_to: none + +resolution: workspace + +environment: + sdk: '>=3.8.0 <4.0.0' + +dependencies: + data_assets: any + fun_with_flags: + path: ../fun_with_flags + hooks: any + logging: ^1.3.0 + meta: any + +dev_dependencies: + lints: ^6.0.0 + test: ^1.25.15 diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_2/.gitignore b/pkgs/hooks_runner/test_data/flag_enthusiast_2/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_2/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_2/README.md b/pkgs/hooks_runner/test_data/flag_enthusiast_2/README.md new file mode 100644 index 000000000..8831761b8 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_2/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_2/hook/link.dart b/pkgs/hooks_runner/test_data/flag_enthusiast_2/hook/link.dart new file mode 100644 index 000000000..a4fc12711 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_2/hook/link.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:fun_with_flags/src/hook.dart' show flagsUsed; +import 'package:hooks/hooks.dart'; + +void main(List args) async { + await link(args, (input, output) async { + flagsUsed(input, output, ['fr']); + }); +} diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_2/lib/flag_enthusiast_2.dart b/pkgs/hooks_runner/test_data/flag_enthusiast_2/lib/flag_enthusiast_2.dart new file mode 100644 index 000000000..5b7cd277a --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_2/lib/flag_enthusiast_2.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/flag_enthusiast_2_base.dart'; diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_2/lib/src/flag_enthusiast_2_base.dart b/pkgs/hooks_runner/test_data/flag_enthusiast_2/lib/src/flag_enthusiast_2_base.dart new file mode 100644 index 000000000..63e6d1138 --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_2/lib/src/flag_enthusiast_2_base.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. + +import 'package:fun_with_flags/fun_with_flags.dart'; +import 'package:meta/meta.dart' show RecordUse; + +class MultiFlag { + @RecordUse() + List loadFlag(Iterable countries) => + countries.map(FlagLoader.load).toList(); +} diff --git a/pkgs/hooks_runner/test_data/flag_enthusiast_2/pubspec.yaml b/pkgs/hooks_runner/test_data/flag_enthusiast_2/pubspec.yaml new file mode 100644 index 000000000..b115f32bf --- /dev/null +++ b/pkgs/hooks_runner/test_data/flag_enthusiast_2/pubspec.yaml @@ -0,0 +1,21 @@ +name: flag_enthusiast_2 +description: Add and retrieve a data asset. + +publish_to: none + +resolution: workspace + +environment: + sdk: '>=3.8.0 <4.0.0' + +dependencies: + data_assets: any + fun_with_flags: + path: ../fun_with_flags + hooks: any + logging: ^1.3.0 + meta: any + +dev_dependencies: + lints: ^6.0.0 + test: ^1.25.15 diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/.gitignore b/pkgs/hooks_runner/test_data/fun_with_flags/.gitignore new file mode 100644 index 000000000..3cceda557 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/README.md b/pkgs/hooks_runner/test_data/fun_with_flags/README.md new file mode 100644 index 000000000..8831761b8 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/assets/ca.txt b/pkgs/hooks_runner/test_data/fun_with_flags/assets/ca.txt new file mode 100644 index 000000000..8eb84e14e --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/assets/ca.txt @@ -0,0 +1 @@ +🇨🇦 diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/assets/de.txt b/pkgs/hooks_runner/test_data/fun_with_flags/assets/de.txt new file mode 100644 index 000000000..89b17ea46 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/assets/de.txt @@ -0,0 +1 @@ +🇩🇪 diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/assets/fr.txt b/pkgs/hooks_runner/test_data/fun_with_flags/assets/fr.txt new file mode 100644 index 000000000..bebcb9af3 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/assets/fr.txt @@ -0,0 +1 @@ +🇫🇷 diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/hook/build.dart b/pkgs/hooks_runner/test_data/fun_with_flags/hook/build.dart new file mode 100644 index 000000000..fe9c1d2db --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/hook/build.dart @@ -0,0 +1,16 @@ +// 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:data_assets/data_assets.dart'; +import 'package:hooks/hooks.dart'; + +void main(List args) async { + await build(args, (input, output) async { + await output.addDataAssetDirectories( + ['assets'], + input: input, + routing: ToLinkHook(input.packageName), + ); + }); +} diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/hook/link.dart b/pkgs/hooks_runner/test_data/fun_with_flags/hook/link.dart new file mode 100644 index 000000000..b8805c132 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/hook/link.dart @@ -0,0 +1,29 @@ +// 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:data_assets/data_assets.dart'; +import 'package:fun_with_flags/src/hook.dart'; +import 'package:hooks/hooks.dart'; + +void main(List args) { + link(args, (input, output) async { + print('Metadata: ${input.metadata}'); + final usedFlags = input.metadata + .where((asset) => asset.key.startsWith(prefix)) + .expand((e) => e.value as List) + .map((e) => e as String) + .map((country) => 'package:fun_with_flags/assets/$country.txt') + .toSet(); + + print('Got $usedFlags for linking. Checking against ${input.assets.data}'); + + final usedFlagAssets = input.assets.data.where( + (flagAsset) => usedFlags.contains(flagAsset.id), + ); + + print('After filtering, got $usedFlagAssets'); + + output.assets.data.addAll(usedFlagAssets); + }); +} diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/lib/fun_with_flags.dart b/pkgs/hooks_runner/test_data/fun_with_flags/lib/fun_with_flags.dart new file mode 100644 index 000000000..ead97c535 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/lib/fun_with_flags.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/fun_with_flags_base.dart'; diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/lib/hook.dart b/pkgs/hooks_runner/test_data/fun_with_flags/lib/hook.dart new file mode 100644 index 000000000..f63f343da --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/lib/hook.dart @@ -0,0 +1,5 @@ +// 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. + +export 'src/hook.dart' show flagsUsed; diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/lib/src/fun_with_flags_base.dart b/pkgs/hooks_runner/test_data/fun_with_flags/lib/src/fun_with_flags_base.dart new file mode 100644 index 000000000..eedb8b570 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/lib/src/fun_with_flags_base.dart @@ -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. + +class FlagLoader { + static String load(String country) => 'placeholder for flag loading'; +} diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/lib/src/hook.dart b/pkgs/hooks_runner/test_data/fun_with_flags/lib/src/hook.dart new file mode 100644 index 000000000..8d724b04a --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/lib/src/hook.dart @@ -0,0 +1,17 @@ +// 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:hooks/hooks.dart' show LinkInput, LinkOutputBuilder; + +const prefix = 'used_flags_'; + +void flagsUsed( + LinkInput input, + LinkOutputBuilder output, + List countries, +) => output.metadata.add( + 'fun_with_flags', + '$prefix${input.packageName}', + countries, +); diff --git a/pkgs/hooks_runner/test_data/fun_with_flags/pubspec.yaml b/pkgs/hooks_runner/test_data/fun_with_flags/pubspec.yaml new file mode 100644 index 000000000..dfeeca720 --- /dev/null +++ b/pkgs/hooks_runner/test_data/fun_with_flags/pubspec.yaml @@ -0,0 +1,18 @@ +name: fun_with_flags +description: Add and retrieve a data asset. + +publish_to: none + +resolution: workspace + +environment: + sdk: '>=3.8.0 <4.0.0' + +dependencies: + data_assets: any + hooks: any + logging: ^1.3.0 + +dev_dependencies: + lints: ^6.0.0 + test: ^1.25.15 diff --git a/pkgs/hooks_runner/test_data/manifest.yaml b/pkgs/hooks_runner/test_data/manifest.yaml index 9fa1d3cfd..4ea081cf9 100644 --- a/pkgs/hooks_runner/test_data/manifest.yaml +++ b/pkgs/hooks_runner/test_data/manifest.yaml @@ -201,3 +201,24 @@ - wrong_linker/pubspec.yaml - wrong_namespace_asset/hook/build.dart - wrong_namespace_asset/pubspec.yaml +- flag_app/pubspec.yaml +- flag_app/lib/flag_app.dart +- flag_app/bin/flag_app.dart +- flag_enthusiast_1/pubspec.yaml +- flag_enthusiast_1/lib/flag_enthusiast_1.dart +- flag_enthusiast_1/lib/src/flag_enthusiast_1_base.dart +- flag_enthusiast_1/hook/link.dart +- flag_enthusiast_2/pubspec.yaml +- flag_enthusiast_2/lib/flag_enthusiast_2.dart +- flag_enthusiast_2/lib/src/flag_enthusiast_2_base.dart +- flag_enthusiast_2/hook/link.dart +- fun_with_flags/pubspec.yaml +- fun_with_flags/assets/de.txt +- fun_with_flags/assets/fr.txt +- fun_with_flags/assets/ca.txt +- fun_with_flags/lib/fun_with_flags.dart +- fun_with_flags/lib/src/fun_with_flags_base.dart +- fun_with_flags/lib/src/hook.dart +- fun_with_flags/lib/hook.dart +- fun_with_flags/hook/link.dart +- fun_with_flags/hook/build.dart diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index c5327dc25..00c03989d 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.18.0 + +- Bump `package:hooks` and `package:code_assets`to 0.20.0. + ## 0.17.0 * Fix treeshaking on mac. diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml index 54de4073e..becd0de70 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.17.0 +version: 0.18.0 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c topics: @@ -17,9 +17,9 @@ environment: sdk: '>=3.9.0-21.0.dev <4.0.0' dependencies: - code_assets: ^0.19.4 + code_assets: ^0.20.0 glob: ^2.1.1 - hooks: ^0.19.5 + hooks: ^0.20.0 logging: ^1.3.0 meta: ^1.16.0 pub_semver: ^2.2.0 diff --git a/pkgs/native_toolchain_c/test/clinker/objects_helper.dart b/pkgs/native_toolchain_c/test/clinker/objects_helper.dart index 22eb9c240..0b50cd1ed 100644 --- a/pkgs/native_toolchain_c/test/clinker/objects_helper.dart +++ b/pkgs/native_toolchain_c/test/clinker/objects_helper.dart @@ -56,7 +56,7 @@ void runObjectsTests( outputFile: tempUri.resolve('output.json'), outputDirectoryShared: tempUri2, ) - ..setupLink(assets: [], recordedUsesFile: null) + ..setupLink(assets: [], recordedUsesFile: null, internalAssets: []) ..addExtension( CodeAssetExtension( targetOS: targetOS, diff --git a/pkgs/native_toolchain_c/test/clinker/rust_test.dart b/pkgs/native_toolchain_c/test/clinker/rust_test.dart index 2223106eb..a1cbaae29 100644 --- a/pkgs/native_toolchain_c/test/clinker/rust_test.dart +++ b/pkgs/native_toolchain_c/test/clinker/rust_test.dart @@ -41,7 +41,7 @@ Future main() async { outputFile: tempUri.resolve('output.json'), outputDirectoryShared: tempUri2, ) - ..setupLink(assets: [], recordedUsesFile: null) + ..setupLink(assets: [], recordedUsesFile: null, internalAssets: []) ..addExtension( CodeAssetExtension( targetOS: targetOS, diff --git a/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart b/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart index 44c8c765d..2aa592eb3 100644 --- a/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart +++ b/pkgs/native_toolchain_c/test/clinker/treeshake_helper.dart @@ -91,7 +91,7 @@ void runTreeshakeTests( outputFile: tempUri.resolve('output.json'), outputDirectoryShared: tempUri2, ) - ..setupLink(assets: [], recordedUsesFile: null) + ..setupLink(assets: [], recordedUsesFile: null, internalAssets: []) ..addExtension( CodeAssetExtension( targetOS: targetOS, diff --git a/pkgs/native_toolchain_c/test/clinker/windows_module_definition_helper.dart b/pkgs/native_toolchain_c/test/clinker/windows_module_definition_helper.dart index 3dd8e8f80..bfa42cfd3 100644 --- a/pkgs/native_toolchain_c/test/clinker/windows_module_definition_helper.dart +++ b/pkgs/native_toolchain_c/test/clinker/windows_module_definition_helper.dart @@ -40,7 +40,7 @@ void runWindowsModuleDefinitionTests(List architectures) { outputFile: tempUri.resolve('output.json'), outputDirectoryShared: tempUri2, ) - ..setupLink(assets: [], recordedUsesFile: null) + ..setupLink(assets: [], recordedUsesFile: null, internalAssets: []) ..addExtension( CodeAssetExtension( targetOS: targetOS, diff --git a/pubspec.yaml b/pubspec.yaml index 3a68ee6f0..596a5aaee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,10 @@ workspace: - pkgs/hooks_runner/test_data/reusable_dynamic_library - pkgs/hooks_runner/test_data/reuse_dynamic_library - pkgs/hooks_runner/test_data/simple_data_asset + - pkgs/hooks_runner/test_data/flag_app + - pkgs/hooks_runner/test_data/fun_with_flags + - pkgs/hooks_runner/test_data/flag_enthusiast_1 + - pkgs/hooks_runner/test_data/flag_enthusiast_2 - pkgs/hooks_runner/test_data/simple_link - pkgs/hooks_runner/test_data/some_dev_dep - pkgs/hooks_runner/test_data/system_library