From 3a396a5492b656d7d1ef2c33a750966e73052122 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 4 Apr 2025 10:35:17 +0200 Subject: [PATCH 1/3] [native_assets_cli] Add `user_defines` --- .github/workflows/native.yaml | 3 ++ .../test/data/build_input_macos.json | 3 ++ .../test/data/link_input_macos.json | 3 ++ pkgs/data_assets/test/data/build_input.json | 3 ++ pkgs/data_assets/test/data/link_input.json | 3 ++ .../shared/shared_definitions.schema.json | 4 ++ pkgs/hooks/test/data/build_input.json | 3 ++ pkgs/hooks/test/data/link_input.json | 3 ++ pkgs/hooks/test/schema/helpers.dart | 1 + .../lib/src/build_runner/build_runner.dart | 32 ++++++++++++ .../test/build_runner/helpers.dart | 2 + .../test/test_data/user_defines_test.dart | 52 +++++++++++++++++++ .../test_data/manifest.yaml | 2 + .../test_data/user_defines/hook/build.dart | 27 ++++++++++ pkgs/native_assets_cli/lib/src/config.dart | 11 ++++ .../lib/src/hooks/syntax.g.dart | 24 +++++++++ 16 files changed, 176 insertions(+) create mode 100644 pkgs/native_assets_builder/test/test_data/user_defines_test.dart create mode 100644 pkgs/native_assets_builder/test_data/user_defines/hook/build.dart diff --git a/.github/workflows/native.yaml b/.github/workflows/native.yaml index 40cf09d19..eae9ffa06 100644 --- a/.github/workflows/native.yaml +++ b/.github/workflows/native.yaml @@ -114,6 +114,9 @@ jobs: - run: dart pub get -C test_data/reuse_dynamic_library/ if: ${{ matrix.package == 'native_assets_builder' }} + - run: dart pub get -C test_data/user_defines/ + 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/code_assets/test/data/build_input_macos.json b/pkgs/code_assets/test/data/build_input_macos.json index 2de3cec82..ce9539a05 100644 --- a/pkgs/code_assets/test/data/build_input_macos.json +++ b/pkgs/code_assets/test/data/build_input_macos.json @@ -71,5 +71,8 @@ "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", "package_name": "my_package", "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "user_defines": { + "some_key": "some_value" + }, "version": "1.9.0" } diff --git a/pkgs/code_assets/test/data/link_input_macos.json b/pkgs/code_assets/test/data/link_input_macos.json index f6666e325..bd02f5e88 100644 --- a/pkgs/code_assets/test/data/link_input_macos.json +++ b/pkgs/code_assets/test/data/link_input_macos.json @@ -71,5 +71,8 @@ "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", "package_name": "my_package", "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "user_defines": { + "some_key": "some_value" + }, "version": "1.9.0" } diff --git a/pkgs/data_assets/test/data/build_input.json b/pkgs/data_assets/test/data/build_input.json index 2bc1f2c82..4817020dc 100644 --- a/pkgs/data_assets/test/data/build_input.json +++ b/pkgs/data_assets/test/data/build_input.json @@ -46,5 +46,8 @@ "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", "package_name": "my_package", "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "user_defines": { + "some_key": "some_value" + }, "version": "1.9.0" } diff --git a/pkgs/data_assets/test/data/link_input.json b/pkgs/data_assets/test/data/link_input.json index 6c3c0a575..4c8e88081 100644 --- a/pkgs/data_assets/test/data/link_input.json +++ b/pkgs/data_assets/test/data/link_input.json @@ -32,5 +32,8 @@ "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", "package_name": "my_package", "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "user_defines": { + "some_key": "some_value" + }, "version": "1.9.0" } diff --git a/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json b/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json index b64ed7b59..5fd9a8a3d 100644 --- a/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json +++ b/pkgs/hooks/doc/schema/shared/shared_definitions.schema.json @@ -174,6 +174,10 @@ "package_root": { "$ref": "#/definitions/absolutePath" }, + "user_defines": { + "type": "object", + "additionalProperties": true + }, "version": { "type": "string" } diff --git a/pkgs/hooks/test/data/build_input.json b/pkgs/hooks/test/data/build_input.json index 8b7d189d0..dbed238a0 100644 --- a/pkgs/hooks/test/data/build_input.json +++ b/pkgs/hooks/test/data/build_input.json @@ -35,5 +35,8 @@ "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", "package_name": "my_package", "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "user_defines": { + "some_key": "some_value" + }, "version": "1.9.0" } diff --git a/pkgs/hooks/test/data/link_input.json b/pkgs/hooks/test/data/link_input.json index 2e13e31db..b5b5fcb52 100644 --- a/pkgs/hooks/test/data/link_input.json +++ b/pkgs/hooks/test/data/link_input.json @@ -24,5 +24,8 @@ "out_file": "/Users/dacoharkes/src/dacoharkes/playground/my_package/example/.dart_tool/native_assets_builder/my_package/ca4e7d3d4e7b8912cbd24d9e8a6cecdc/output.json", "package_name": "my_package", "package_root": "/Users/dacoharkes/src/dacoharkes/playground/my_package/", + "user_defines": { + "some_key": "some_value" + }, "version": "1.9.0" } diff --git a/pkgs/hooks/test/schema/helpers.dart b/pkgs/hooks/test/schema/helpers.dart index ad2c876fc..59ddf3b54 100644 --- a/pkgs/hooks/test/schema/helpers.dart +++ b/pkgs/hooks/test/schema/helpers.dart @@ -323,6 +323,7 @@ FieldsReturn _hookFields({ ([r'$schema'], expectOptionalFieldMissing), (['version'], versionMissingExpectation), if (inputOrOutput == InputOrOutput.input) ...[ + (['user_defines'], expectOptionalFieldMissing), (['out_dir_shared'], expectRequiredFieldMissing), (['out_dir'], expectRequiredFieldMissing), (['package_name'], expectRequiredFieldMissing), 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 4797b1362..e6f01a223 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 @@ -46,6 +46,7 @@ class NativeAssetsBuildRunner { final Uri dartExecutable; final Duration singleHookTimeout; final Map hookEnvironment; + final Map?> userDefines; final PackageLayout packageLayout; NativeAssetsBuildRunner({ @@ -55,6 +56,7 @@ class NativeAssetsBuildRunner { required this.packageLayout, Duration? singleHookTimeout, Map? hookEnvironment, + this.userDefines = const {}, }) : _fileSystem = fileSystem, singleHookTimeout = singleHookTimeout ?? const Duration(minutes: 5), hookEnvironment = @@ -131,6 +133,7 @@ class NativeAssetsBuildRunner { outputFile: buildDirUri.resolve('output.json'), outputDirectory: outDirUri, outputDirectoryShared: outDirSharedUri, + userDefines: userDefines[package.name], ); final input = BuildInput(inputBuilder.json); @@ -228,6 +231,7 @@ class NativeAssetsBuildRunner { outputFile: buildDirUri.resolve('output.json'), outputDirectory: outDirUri, outputDirectoryShared: outDirSharedUri, + userDefines: userDefines[package.name], ); inputBuilder.setupLink( assets: buildResult.encodedAssetsForLinking[package.name] ?? [], @@ -861,6 +865,34 @@ ${compileResult.stdout} ? BuildOutput(hookOutputJson) : LinkOutput(hookOutputJson); } + + /// Reads the user-defines from a pubspec.yaml in the suggested location. + /// + /// SDKs do not have to follow this, they might support user-defines in a + /// different way. + /// + /// The [pubspec] is expected to be the decoded yaml, a Map. + static Map> readHooksUserDefinesFromPubspec( + Map pubspec, + ) { + final hooks = pubspec['hooks']; + if (hooks is! Map) { + return {}; + } + final userDefines = hooks['user_defines']; + if (userDefines is! Map) { + return {}; + } + return { + for (final MapEntry(:key, :value) in userDefines.entries) + if (key is String) + key: { + if (value is Map) + for (final MapEntry(:key, :value) in value.entries) + if (key is String) key: value, + }, + }; + } } /// Parses depfile contents. diff --git a/pkgs/native_assets_builder/test/build_runner/helpers.dart b/pkgs/native_assets_builder/test/build_runner/helpers.dart index 0f13a736a..33f0718c1 100644 --- a/pkgs/native_assets_builder/test/build_runner/helpers.dart +++ b/pkgs/native_assets_builder/test/build_runner/helpers.dart @@ -75,6 +75,7 @@ Future build( bool linkingEnabled = false, required List buildAssetTypes, Map? hookEnvironment, + Map?>? userDefines, }) async { final targetOS = target?.os ?? OS.current; final runPackageName_ = @@ -91,6 +92,7 @@ Future build( fileSystem: const LocalFileSystem(), hookEnvironment: hookEnvironment, packageLayout: packageLayout, + userDefines: userDefines ?? {}, ).build( extensions: [ if (buildAssetTypes.contains(BuildAssetType.code)) diff --git a/pkgs/native_assets_builder/test/test_data/user_defines_test.dart b/pkgs/native_assets_builder/test/test_data/user_defines_test.dart new file mode 100644 index 000000000..7ae71f2e7 --- /dev/null +++ b/pkgs/native_assets_builder/test/test_data/user_defines_test.dart @@ -0,0 +1,52 @@ +// 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. + +@OnPlatform({'mac-os': Timeout.factor(2), 'windows': Timeout.factor(10)}) +library; + +import 'dart:io'; + +import 'package:native_assets_builder/native_assets_builder.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +import '../build_runner/helpers.dart'; +import '../helpers.dart'; + +void main() async { + const name = 'user_defines'; + + test( + '$name build', + () => inTempDir((tempUri) async { + await copyTestProjects(targetUri: tempUri); + final packageUri = tempUri.resolve('$name/'); + + await runPubGet(workingDirectory: packageUri, logger: logger); + + final pubspec = + loadYamlDocument( + File.fromUri( + packageUri.resolve('pubspec.yaml'), + ).readAsStringSync(), + ).contents + as YamlMap; + final userDefines = + NativeAssetsBuildRunner.readHooksUserDefinesFromPubspec(pubspec); + + final logMessages = []; + final result = + (await build( + packageUri, + logger, + dartExecutable, + capturedLogs: logMessages, + buildAssetTypes: [BuildAssetType.data], + userDefines: userDefines, + ))!; + + expect(result.encodedAssets.length, 1); + }), + ); +} diff --git a/pkgs/native_assets_builder/test_data/manifest.yaml b/pkgs/native_assets_builder/test_data/manifest.yaml index 387f53315..2e865108a 100644 --- a/pkgs/native_assets_builder/test_data/manifest.yaml +++ b/pkgs/native_assets_builder/test_data/manifest.yaml @@ -179,6 +179,8 @@ - use_all_api/hook/build.dart - use_all_api/hook/link.dart - use_all_api/pubspec.yaml +- user_defines/hook/build.dart +- user_defines/pubspec.yaml - wrong_build_output/hook/build.dart - wrong_build_output/pubspec.yaml - wrong_build_output_2/hook/build.dart diff --git a/pkgs/native_assets_builder/test_data/user_defines/hook/build.dart b/pkgs/native_assets_builder/test_data/user_defines/hook/build.dart new file mode 100644 index 000000000..7a3c5f911 --- /dev/null +++ b/pkgs/native_assets_builder/test_data/user_defines/hook/build.dart @@ -0,0 +1,27 @@ +// 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 'dart:convert'; +import 'dart:io'; + +import 'package:native_assets_cli/data_assets.dart'; + +void main(List arguments) async { + await build(arguments, (input, output) async { + final value1 = input.userDefines['user_define_key']; + if (value1 != 'user_define_value') { + throw Exception( + 'User-define user_define_key does not have the right value.', + ); + } + final value2 = input.userDefines['user_define_key2']; + final dataAsset = DataAsset( + file: input.outputDirectoryShared.resolve('my_asset.json'), + name: 'my_asset', + package: input.packageName, + ); + File.fromUri(dataAsset.file).writeAsStringSync(jsonEncode(value2)); + output.assets.data.add(dataAsset); + }); +} diff --git a/pkgs/native_assets_cli/lib/src/config.dart b/pkgs/native_assets_cli/lib/src/config.dart index 58b853a32..9baf5119f 100644 --- a/pkgs/native_assets_cli/lib/src/config.dart +++ b/pkgs/native_assets_cli/lib/src/config.dart @@ -89,6 +89,14 @@ sealed class HookInput { String toString() => const JsonEncoder.withIndent(' ').convert(json); HookConfig get config => HookConfig._(this); + + /// The user-defines for this [packageName]. + HookInputUserDefines get userDefines => HookInputUserDefines._(this); +} + +extension type HookInputUserDefines._(HookInput _input) { + /// The value for the user-define for [key] for this package. + Object? operator [](String key) => _input._syntax.userDefines?.json[key]; } sealed class HookInputBuilder { @@ -110,6 +118,7 @@ sealed class HookInputBuilder { required Uri outputDirectory, required Uri outputDirectoryShared, required Uri outputFile, + Map? userDefines, }) { _syntax.version = latestVersion.toString(); _syntax.packageRoot = packageRoot; @@ -117,6 +126,8 @@ sealed class HookInputBuilder { _syntax.outDir = outputDirectory; _syntax.outDirShared = outputDirectoryShared; _syntax.outFile = outputFile; + _syntax.userDefines = + userDefines == null ? null : syntax.JsonObject.fromJson(userDefines); } /// Constructs a checksum for a [BuildInput]. diff --git a/pkgs/native_assets_cli/lib/src/hooks/syntax.g.dart b/pkgs/native_assets_cli/lib/src/hooks/syntax.g.dart index cb44527f2..8c1437987 100644 --- a/pkgs/native_assets_cli/lib/src/hooks/syntax.g.dart +++ b/pkgs/native_assets_cli/lib/src/hooks/syntax.g.dart @@ -134,6 +134,7 @@ class BuildInput extends HookInput { required super.outFile, required super.packageName, required super.packageRoot, + required super.userDefines, required super.version, }) : super(config: config) { _assets = assets; @@ -487,6 +488,7 @@ class HookInput extends JsonObject { required Uri? outFile, required String packageName, required Uri packageRoot, + required JsonObject? userDefines, required String? version, }) : super() { this.config = config; @@ -495,6 +497,7 @@ class HookInput extends JsonObject { this.outFile = outFile; this.packageName = packageName; this.packageRoot = packageRoot; + this.userDefines = userDefines; this.version = version; json.sortOnKey(); } @@ -564,6 +567,25 @@ class HookInput extends JsonObject { List _validatePackageRoot() => _reader.validatePath('package_root'); + JsonObject? get userDefines { + final jsonValue = _reader.optionalMap('user_defines'); + if (jsonValue == null) return null; + return JsonObject.fromJson(jsonValue, path: [...path, 'user_defines']); + } + + set userDefines(JsonObject? value) { + json.setOrRemove('user_defines', value?.json); + json.sortOnKey(); + } + + List _validateUserDefines() { + final mapErrors = _reader.validate?>('user_defines'); + if (mapErrors.isNotEmpty) { + return mapErrors; + } + return userDefines?.validate() ?? []; + } + String? get version => _reader.get('version'); set version(String? value) { @@ -582,6 +604,7 @@ class HookInput extends JsonObject { ..._validateOutFile(), ..._validatePackageName(), ..._validatePackageRoot(), + ..._validateUserDefines(), ..._validateVersion(), ]; @@ -728,6 +751,7 @@ class LinkInput extends HookInput { required super.packageName, required super.packageRoot, required Uri? resourceIdentifiers, + required super.userDefines, required super.version, }) : super() { _assets = assets; From 7ce17b80f8ef7674565f21a49b69af7a611ebd02 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 4 Apr 2025 16:47:21 +0200 Subject: [PATCH 2/3] address comment --- .../lib/src/build_runner/build_runner.dart | 46 +++++++++++++++++++ .../test/test_data/user_defines_test.dart | 4 ++ 2 files changed, 50 insertions(+) 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 e6f01a223..c88d15770 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 @@ -866,15 +866,61 @@ ${compileResult.stdout} : LinkOutput(hookOutputJson); } + /// Returns a list of errors for [readHooksUserDefinesFromPubspec]. + static List validateHooksUserDefinesFromPubspec( + Map pubspec, + ) { + final hooks = pubspec['hooks']; + if (hooks == null) return []; + if (hooks is! Map) { + return ["Expected 'hooks' to be a map. Found: '$hooks'"]; + } + final userDefines = hooks['user_defines']; + if (userDefines == null) return []; + if (userDefines is! Map) { + return [ + "Expected 'hooks.user_defines' to be a map. Found: '$userDefines'", + ]; + } + + final errors = []; + for (final MapEntry(:key, :value) in userDefines.entries) { + if (key is! String) { + errors.add( + "Expected 'hooks.user_defines' to be a map with string keys." + " Found key: '$key'.", + ); + } + if (value is! Map) { + errors.add( + "Expected 'hooks.user_defines.$key' to be a map. Found: '$value'", + ); + continue; + } + for (final childKey in value.keys) { + if (childKey is! String) { + errors.add( + "Expected 'hooks.user_defines.$key' to be a " + "map with string keys. Found key: '$childKey'.", + ); + } + } + } + return errors; + } + /// Reads the user-defines from a pubspec.yaml in the suggested location. /// /// SDKs do not have to follow this, they might support user-defines in a /// different way. /// /// The [pubspec] is expected to be the decoded yaml, a Map. + /// + /// Before invoking, check errors with [validateHooksUserDefinesFromPubspec]. static Map> readHooksUserDefinesFromPubspec( Map pubspec, ) { + assert(validateHooksUserDefinesFromPubspec(pubspec).isEmpty); final hooks = pubspec['hooks']; if (hooks is! Map) { return {}; diff --git a/pkgs/native_assets_builder/test/test_data/user_defines_test.dart b/pkgs/native_assets_builder/test/test_data/user_defines_test.dart index 7ae71f2e7..08ba0ff13 100644 --- a/pkgs/native_assets_builder/test/test_data/user_defines_test.dart +++ b/pkgs/native_assets_builder/test/test_data/user_defines_test.dart @@ -32,6 +32,10 @@ void main() async { ).readAsStringSync(), ).contents as YamlMap; + expect( + NativeAssetsBuildRunner.validateHooksUserDefinesFromPubspec(pubspec), + isEmpty, + ); final userDefines = NativeAssetsBuildRunner.readHooksUserDefinesFromPubspec(pubspec); From df5af88ef2cb44f81592555dc9f3c44e8a45978b Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Fri, 4 Apr 2025 17:00:59 +0200 Subject: [PATCH 3/3] add comment --- pkgs/native_assets_cli/lib/src/config.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/native_assets_cli/lib/src/config.dart b/pkgs/native_assets_cli/lib/src/config.dart index 9baf5119f..0e0832f77 100644 --- a/pkgs/native_assets_cli/lib/src/config.dart +++ b/pkgs/native_assets_cli/lib/src/config.dart @@ -96,6 +96,9 @@ sealed class HookInput { extension type HookInputUserDefines._(HookInput _input) { /// The value for the user-define for [key] for this package. + /// + /// This can be arbitrary JSON/YAML if provided from the SDK from such source. + /// If it's provided from command-line arguments, it's likely a string. Object? operator [](String key) => _input._syntax.userDefines?.json[key]; }