Skip to content

[native_assets_cli] Make BuildInput JSON hierarchical #1872

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
Jan 8, 2025
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ const _rightContents = '''{
"encodedAssets": [],
"dependencies": [],
"metadata": {},
"version": "1.6.0"
"version": "1.7.0"
}''';
90 changes: 72 additions & 18 deletions pkgs/native_assets_cli/lib/src/code_assets/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,22 @@ class CodeConfig {
factory CodeConfig.fromJson(Map<String, Object?> json) {
final dryRun = json.getOptional<bool>(_dryRunConfigKey) ?? false;

final linkModePreference =
LinkModePreference.fromString(json.string(_linkModePreferenceKey));
final linkModePreference = LinkModePreference.fromString(
json.code?.optionalString(_linkModePreferenceKey) ??
json.string(_linkModePreferenceKey),
);
final targetArchitecture = dryRun
? null
: Architecture.fromString(json.string(_targetArchitectureKey,
validValues: Architecture.values.map((a) => a.name)));
final targetOS = OS.fromString(json.string(_targetOSConfigKey));
final cCompiler = switch (json.optionalMap(_compilerKey)) {
: Architecture.fromString(json.code?.optionalString(
_targetArchitectureKey,
validValues: Architecture.values.map((a) => a.name)) ??
json.string(_targetArchitectureKey,
validValues: Architecture.values.map((a) => a.name)));
final targetOS = OS.fromString(
json.code?.optionalString(_targetOSConfigKey) ??
json.string(_targetOSConfigKey));
final cCompiler = switch (json.code?.optionalMap(_compilerKey) ??
json.optionalMap(_compilerKey)) {
final Map<String, Object?> map => CCompilerConfig.fromJson(map),
null => null
};
Expand Down Expand Up @@ -151,8 +159,12 @@ class IOSConfig {
_targetVersion = targetVersion;

IOSConfig.fromJson(Map<String, Object?> json)
: _targetVersion = json.optionalInt(_targetIOSVersionKey),
_targetSdk = switch (json.optionalString(_targetIOSSdkKey)) {
: _targetVersion =
json.code?.optionalMap(_iosKey)?.optionalInt(_targetVersionKey) ??
json.optionalInt(_targetIOSVersionKeyDeprecated),
_targetSdk = switch (
json.code?.optionalMap(_iosKey)?.optionalString(_targetSdkKey) ??
json.optionalString(_targetIOSSdkKeyDeprecated)) {
null => null,
String e => IOSSdk.fromString(e)
};
Expand All @@ -176,7 +188,10 @@ class AndroidConfig {
}) : _targetNdkApi = targetNdkApi;

AndroidConfig.fromJson(Map<String, Object?> json)
: _targetNdkApi = json.optionalInt(_targetAndroidNdkApiKey);
: _targetNdkApi = json.code
?.optionalMap(_androidKey)
?.optionalInt(_targetNdkApiKey) ??
json.optionalInt(_targetAndroidNdkApiKeyDeprecated);
}

extension AndroidConfigSyntactic on AndroidConfig {
Expand All @@ -195,7 +210,9 @@ class MacOSConfig {
}) : _targetVersion = targetVersion;

MacOSConfig.fromJson(Map<String, Object?> json)
: _targetVersion = json.optionalInt(_targetMacOSVersionKey);
: _targetVersion =
json.code?.optionalMap(_macosKey)?.optionalInt(_targetVersionKey) ??
json.optionalInt(_targetMacOSVersionKeyDeprecated);
}

extension MacOSConfigSyntactic on MacOSConfig {
Expand Down Expand Up @@ -258,22 +275,45 @@ extension CodeAssetBuildInputBuilder on HookConfigBuilder {
}) {
if (targetArchitecture != null) {
json[_targetArchitectureKey] = targetArchitecture.toString();
json.setNested([_configKey, _codeKey, _targetArchitectureKey],
targetArchitecture.toString());
}
json[_targetOSConfigKey] = targetOS.toString();
json.setNested(
[_configKey, _codeKey, _targetOSConfigKey], targetOS.toString());
json[_linkModePreferenceKey] = linkModePreference.toString();
json.setNested([_configKey, _codeKey, _linkModePreferenceKey],
linkModePreference.toString());
if (cCompiler != null) {
json[_compilerKey] = cCompiler.toJson();
json.setNested([_configKey, _codeKey, _compilerKey], cCompiler.toJson());
}

// Note, using ?. instead of !. makes missing data be a semantic error
// rather than a syntactic error to be caught in the validation.
if (targetOS == OS.android) {
json[_targetAndroidNdkApiKey] = android?.targetNdkApi;
json[_targetAndroidNdkApiKeyDeprecated] = android?.targetNdkApi;
json.setNested(
[_configKey, _codeKey, _androidKey, _targetNdkApiKey],
android?.targetNdkApi,
);
} else if (targetOS == OS.iOS) {
json[_targetIOSSdkKey] = iOS?.targetSdk.toString();
json[_targetIOSVersionKey] = iOS?.targetVersion;
json[_targetIOSSdkKeyDeprecated] = iOS?.targetSdk.toString();
json[_targetIOSVersionKeyDeprecated] = iOS?.targetVersion;
json.setNested(
[_configKey, _codeKey, _iosKey, _targetSdkKey],
iOS?.targetSdk.toString(),
);
json.setNested(
[_configKey, _codeKey, _iosKey, _targetVersionKey],
iOS?.targetVersion,
);
} else if (targetOS == OS.macOS) {
json[_targetMacOSVersionKey] = macOS?.targetVersion;
json[_targetMacOSVersionKeyDeprecated] = macOS?.targetVersion;
json.setNested(
[_configKey, _codeKey, _macosKey, _targetVersionKey],
macOS?.targetVersion,
);
}
}
}
Expand All @@ -298,11 +338,25 @@ extension CodeAssetLinkOutput on LinkOutputAssets {

const String _compilerKey = 'c_compiler';
const String _linkModePreferenceKey = 'link_mode_preference';
const String _targetAndroidNdkApiKey = 'target_android_ndk_api';
const String _targetNdkApiKey = 'target_ndk_api';
const String _targetAndroidNdkApiKeyDeprecated = 'target_android_ndk_api';
const String _targetArchitectureKey = 'target_architecture';
const String _targetIOSSdkKey = 'target_ios_sdk';
const String _targetIOSVersionKey = 'target_ios_version';
const String _targetMacOSVersionKey = 'target_macos_version';
const String _targetSdkKey = 'target_sdk';
const String _targetIOSSdkKeyDeprecated = 'target_ios_sdk';
const String _targetVersionKey = 'target_version';
const String _targetIOSVersionKeyDeprecated = 'target_ios_version';
const String _targetMacOSVersionKeyDeprecated = 'target_macos_version';
const String _targetOSConfigKey = 'target_os';

const _dryRunConfigKey = 'dry_run';

const _configKey = 'config';
const _codeKey = 'code';
const _androidKey = 'android';
const _iosKey = 'ios';
const _macosKey = 'macos';

extension on Map<String, Object?> {
Map<String, Object?>? get code =>
optionalMap(_configKey)?.optionalMap(_codeKey);
}
28 changes: 14 additions & 14 deletions pkgs/native_assets_cli/lib/src/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,9 @@ sealed class HookInputBuilder {
/// [BuildInput] being built with this [BuildInputBuilder]. It is therefore
/// assumed the output directory has not been set yet.
String computeChecksum() {
if (json.containsKey(_outDirInputKey) ||
json.containsKey(_outDirSharedInputKey) ||
json.containsKey(_assetsKey)) {
// The bundling tools would first calculate the checksum, create an output
// directory and then call [setupHookInput].
// The output directory should not depend on the assets passed in for
// linking.
throw StateError('The checksum should be generated before setting '
'up the hook configuration');
}
final config = json[_configKey];
final hash = sha256
.convert(const JsonEncoder().fuse(const Utf8Encoder()).convert(json))
.convert(const JsonEncoder().fuse(const Utf8Encoder()).convert(config))
.toString()
// 256 bit hashes lead to 64 hex character strings.
// To avoid overflowing file paths limits, only use 32.
Expand All @@ -137,6 +128,8 @@ const _packageRootInputKey = 'package_root';
const _supportedAssetTypesKey = 'supported_asset_types';
const _buildAssetTypesKey = 'build_asset_types';

const _configKey = 'config';

final class BuildInput extends HookInput {
final Map<String, Metadata> metadata;

Expand Down Expand Up @@ -179,6 +172,7 @@ final class HookConfigBuilder {
}) {
json[_buildAssetTypesKey] = buildAssetTypes;
json[_supportedAssetTypesKey] = buildAssetTypes;
json.setNested([_configKey, _buildAssetTypesKey], buildAssetTypes);
}
}

Expand All @@ -193,6 +187,7 @@ extension BuildConfigBuilderSetup on BuildConfigBuilder {
}) {
json[_dryRunConfigKey] = dryRun;
json[_linkingEnabledKey] = linkingEnabled;
json.setNested([_configKey, _linkingEnabledKey], linkingEnabled);

// TODO: Bump min-SDK constraint to 3.7 and remove once stable.
if (!dryRun) {
Expand Down Expand Up @@ -583,7 +578,7 @@ extension type EncodedAssetLinkOutputBuilder._(LinkOutputBuilder _builder) {
///
/// We'll never bump the major version. Removing old keys from the input and
/// output is done via modifying [latestParsableVersion].
final latestVersion = Version(1, 6, 0);
final latestVersion = Version(1, 7, 0);

/// The parser can deal with inputs and outputs down to this version.
///
Expand All @@ -607,7 +602,10 @@ final class HookConfig {
final List<String> buildAssetTypes;

HookConfig(this.json)
: buildAssetTypes = json.optionalStringList(_buildAssetTypesKey) ??
: buildAssetTypes = json
.optionalMap(_configKey)
?.optionalStringList(_buildAssetTypesKey) ??
json.optionalStringList(_buildAssetTypesKey) ??
json.optionalStringList(_supportedAssetTypesKey) ??
const [];
}
Expand All @@ -627,5 +625,7 @@ final class BuildConfig extends HookConfig {
BuildConfig(super.json)
// ignore: deprecated_member_use_from_same_package
: dryRun = json.getOptional<bool>(_dryRunConfigKey) ?? false,
linkingEnabled = json.get<bool>(_linkingEnabledKey);
linkingEnabled =
json.optionalMap(_configKey)?.optionalBool(_linkingEnabledKey) ??
json.get<bool>(_linkingEnabledKey);
}
18 changes: 17 additions & 1 deletion pkgs/native_assets_cli/lib/src/json_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ extension MapJsonUtils on Map<String, Object?> {
return value;
}

String? optionalString(String key) => getOptional<String>(key);
String? optionalString(String key, {Iterable<String>? validValues}) {
final value = getOptional<String>(key);
if (value == null) return null;
if (validValues != null && !validValues.contains(value)) {
throw FormatException('Json "$key" had value $value but expected one of '
'${validValues.join(',')}');
}
return value;
}

bool? optionalBool(String key) => getOptional<bool>(key);
core.int int(String key) => get<core.int>(key);
Expand Down Expand Up @@ -62,6 +70,14 @@ extension MapJsonUtils on Map<String, Object?> {
'Unexpected value \'$value\' for key \'.$key\' in input file. '
'Expected a $T?.');
}

void setNested(List<String> nestedMapKeys, Object? value) {
var map = this;
for (final key in nestedMapKeys.sublist(0, nestedMapKeys.length - 1)) {
map = (map[key] ??= <String, Object?>{}) as Map<String, Object?>;
}
map[nestedMapKeys.last] = value;
}
}

extension ListJsonUtils on List<Object?> {
Expand Down
8 changes: 8 additions & 0 deletions pkgs/native_assets_cli/test/build_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ void main() async {
final expectedInputJson = {
'build_asset_types': ['my-asset-type'],
'build_mode': 'release',
'config': {
'build_asset_types': ['my-asset-type'],
'linking_enabled': false,
},
'dependency_metadata': {
'bar': {
'key': 'value',
Expand Down Expand Up @@ -108,6 +112,10 @@ void main() async {

final expectedInputJson = {
'build_asset_types': ['my-asset-type'],
'config': {
'build_asset_types': ['my-asset-type'],
'linking_enabled': true,
},
'dependency_metadata': <String, Object?>{},
'dry_run': true,
'linking_enabled': true,
Expand Down
2 changes: 1 addition & 1 deletion pkgs/native_assets_cli/test/build_output_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void main() {

// The JSON format of the build output.
<String, Object?>{
'version': '1.6.0',
'version': '1.7.0',
'dependencies': ['path0', 'path1', 'path2'],
'metadata': {
'meta-a': 'meta-b',
Expand Down
Loading
Loading