Skip to content

[native_assets_cli] Add user_defines #2165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 4, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/native.yaml
Original file line number Diff line number Diff line change
@@ -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' }}

3 changes: 3 additions & 0 deletions pkgs/code_assets/test/data/build_input_macos.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 3 additions & 0 deletions pkgs/code_assets/test/data/link_input_macos.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 3 additions & 0 deletions pkgs/data_assets/test/data/build_input.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 3 additions & 0 deletions pkgs/data_assets/test/data/link_input.json
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 4 additions & 0 deletions pkgs/hooks/doc/schema/shared/shared_definitions.schema.json
Original file line number Diff line number Diff line change
@@ -174,6 +174,10 @@
"package_root": {
"$ref": "#/definitions/absolutePath"
},
"user_defines": {
"type": "object",
"additionalProperties": true
},
"version": {
"type": "string"
}
3 changes: 3 additions & 0 deletions pkgs/hooks/test/data/build_input.json
Original file line number Diff line number Diff line change
@@ -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"
}
3 changes: 3 additions & 0 deletions pkgs/hooks/test/data/link_input.json
Original file line number Diff line number Diff line change
@@ -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"
}
1 change: 1 addition & 0 deletions pkgs/hooks/test/schema/helpers.dart
Original file line number Diff line number Diff line change
@@ -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),
78 changes: 78 additions & 0 deletions pkgs/native_assets_builder/lib/src/build_runner/build_runner.dart
Original file line number Diff line number Diff line change
@@ -46,6 +46,7 @@ class NativeAssetsBuildRunner {
final Uri dartExecutable;
final Duration singleHookTimeout;
final Map<String, String> hookEnvironment;
final Map<String, Map<String, Object?>?> userDefines;
final PackageLayout packageLayout;

NativeAssetsBuildRunner({
@@ -55,6 +56,7 @@ class NativeAssetsBuildRunner {
required this.packageLayout,
Duration? singleHookTimeout,
Map<String, String>? 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,80 @@ ${compileResult.stdout}
? BuildOutput(hookOutputJson)
: LinkOutput(hookOutputJson);
}

/// Returns a list of errors for [readHooksUserDefinesFromPubspec].
static List<String> validateHooksUserDefinesFromPubspec(
Map<Object?, Object?> 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 = <String>[];
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<String, Map<String, Object?>> readHooksUserDefinesFromPubspec(
Map<Object?, Object?> pubspec,
) {
assert(validateHooksUserDefinesFromPubspec(pubspec).isEmpty);
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.
2 changes: 2 additions & 0 deletions pkgs/native_assets_builder/test/build_runner/helpers.dart
Original file line number Diff line number Diff line change
@@ -75,6 +75,7 @@ Future<BuildResult?> build(
bool linkingEnabled = false,
required List<BuildAssetType> buildAssetTypes,
Map<String, String>? hookEnvironment,
Map<String, Map<String, Object?>?>? userDefines,
}) async {
final targetOS = target?.os ?? OS.current;
final runPackageName_ =
@@ -91,6 +92,7 @@ Future<BuildResult?> build(
fileSystem: const LocalFileSystem(),
hookEnvironment: hookEnvironment,
packageLayout: packageLayout,
userDefines: userDefines ?? {},
).build(
extensions: [
if (buildAssetTypes.contains(BuildAssetType.code))
56 changes: 56 additions & 0 deletions pkgs/native_assets_builder/test/test_data/user_defines_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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;
expect(
NativeAssetsBuildRunner.validateHooksUserDefinesFromPubspec(pubspec),
isEmpty,
);
final userDefines =
NativeAssetsBuildRunner.readHooksUserDefinesFromPubspec(pubspec);

final logMessages = <String>[];
final result =
(await build(
packageUri,
logger,
dartExecutable,
capturedLogs: logMessages,
buildAssetTypes: [BuildAssetType.data],
userDefines: userDefines,
))!;

expect(result.encodedAssets.length, 1);
}),
);
}
2 changes: 2 additions & 0 deletions pkgs/native_assets_builder/test_data/manifest.yaml
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions pkgs/native_assets_builder/test_data/user_defines/hook/build.dart
Original file line number Diff line number Diff line change
@@ -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<String> 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);
});
}
14 changes: 14 additions & 0 deletions pkgs/native_assets_cli/lib/src/config.dart
Original file line number Diff line number Diff line change
@@ -89,6 +89,17 @@ 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.
///
/// 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];
}

sealed class HookInputBuilder {
@@ -110,13 +121,16 @@ sealed class HookInputBuilder {
required Uri outputDirectory,
required Uri outputDirectoryShared,
required Uri outputFile,
Map<String, Object?>? userDefines,
}) {
_syntax.version = latestVersion.toString();
_syntax.packageRoot = packageRoot;
_syntax.packageName = packageName;
_syntax.outDir = outputDirectory;
_syntax.outDirShared = outputDirectoryShared;
_syntax.outFile = outputFile;
_syntax.userDefines =
userDefines == null ? null : syntax.JsonObject.fromJson(userDefines);
}

/// Constructs a checksum for a [BuildInput].
24 changes: 24 additions & 0 deletions pkgs/native_assets_cli/lib/src/hooks/syntax.g.dart
Original file line number Diff line number Diff line change
@@ -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<String> _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<String> _validateUserDefines() {
final mapErrors = _reader.validate<Map<String, Object?>?>('user_defines');
if (mapErrors.isNotEmpty) {
return mapErrors;
}
return userDefines?.validate() ?? [];
}

String? get version => _reader.get<String?>('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;