Add Android NDK API version (#49)
diff --git a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart
index b535183..9a89459 100644
--- a/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart
+++ b/pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart
@@ -95,7 +95,9 @@
// The sysroot should be discovered automatically after NDK 22.
// Workaround:
if (dynamicLibrary != null) '-nostartfiles',
- '--target=${androidNdkClangTargetFlags[target]!}',
+ '--target='
+ '${androidNdkClangTargetFlags[target]!}'
+ '${buildConfig.targetAndroidNdkApi!}',
],
if (target.os == OS.macOS || target.os == OS.iOS)
'--target=${appleClangTargetFlags[target]!}',
diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart
index 967b98f..6ea3520 100644
--- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart
+++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart
@@ -33,37 +33,22 @@
Target.androidX64: 'elf64-x86-64',
};
+ /// From https://docs.flutter.dev/reference/supported-platforms.
+ const flutterAndroidNdkVersionLowestSupported = 21;
+
+ /// From https://docs.flutter.dev/reference/supported-platforms.
+ const flutterAndroidNdkVersionHighestSupported = 30;
+
for (final linkMode in LinkMode.values) {
for (final target in targets) {
test('Cbuilder $linkMode library $target', () async {
await inTempDir((tempUri) async {
- final addCUri =
- packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
- const name = 'add';
-
- final buildConfig = BuildConfig(
- outDir: tempUri,
- packageRoot: tempUri,
- target: target,
- linkModePreference: linkMode == LinkMode.dynamic
- ? LinkModePreference.dynamic
- : LinkModePreference.static,
+ final libUri = await buildLib(
+ tempUri,
+ target,
+ flutterAndroidNdkVersionLowestSupported,
+ linkMode,
);
- final buildOutput = BuildOutput();
-
- final cbuilder = CBuilder.library(
- name: 'add',
- assetName: 'add',
- sources: [addCUri.toFilePath()],
- );
- await cbuilder.run(
- buildConfig: buildConfig,
- buildOutput: buildOutput,
- logger: logger,
- );
-
- final libUri =
- tempUri.resolve(target.os.libraryFileName(name, linkMode));
if (Platform.isLinux) {
final result = await runProcess(
executable: Uri.file('readelf'),
@@ -91,4 +76,64 @@
});
}
}
+
+ test('Cbuilder API levels binary difference', () async {
+ const target = Target.androidArm64;
+ const linkMode = LinkMode.dynamic;
+ const apiLevel1 = flutterAndroidNdkVersionLowestSupported;
+ const apiLevel2 = flutterAndroidNdkVersionHighestSupported;
+ await inTempDir((tempUri) async {
+ final out1Uri = tempUri.resolve('out1/');
+ final out2Uri = tempUri.resolve('out2/');
+ final out3Uri = tempUri.resolve('out3/');
+ await Directory.fromUri(out1Uri).create();
+ await Directory.fromUri(out2Uri).create();
+ await Directory.fromUri(out3Uri).create();
+ final lib1Uri = await buildLib(out1Uri, target, apiLevel1, linkMode);
+ final lib2Uri = await buildLib(out2Uri, target, apiLevel2, linkMode);
+ final lib3Uri = await buildLib(out3Uri, target, apiLevel2, linkMode);
+ final bytes1 = await File.fromUri(lib1Uri).readAsBytes();
+ final bytes2 = await File.fromUri(lib2Uri).readAsBytes();
+ final bytes3 = await File.fromUri(lib3Uri).readAsBytes();
+ // Different API levels should lead to a different binary.
+ expect(bytes1, isNot(bytes2));
+ // Identical API levels should lead to an identical binary.
+ expect(bytes2, bytes3);
+ });
+ });
+}
+
+Future<Uri> buildLib(
+ Uri tempUri,
+ Target target,
+ int androidNdkApi,
+ LinkMode linkMode,
+) async {
+ final addCUri = packageUri.resolve('test/cbuilder/testfiles/add/src/add.c');
+ const name = 'add';
+
+ final buildConfig = BuildConfig(
+ outDir: tempUri,
+ packageRoot: tempUri,
+ target: target,
+ targetAndroidNdkApi: androidNdkApi,
+ linkModePreference: linkMode == LinkMode.dynamic
+ ? LinkModePreference.dynamic
+ : LinkModePreference.static,
+ );
+ final buildOutput = BuildOutput();
+
+ final cbuilder = CBuilder.library(
+ name: name,
+ assetName: name,
+ sources: [addCUri.toFilePath()],
+ );
+ await cbuilder.run(
+ buildConfig: buildConfig,
+ buildOutput: buildOutput,
+ logger: logger,
+ );
+
+ final libUri = tempUri.resolve(target.os.libraryFileName(name, linkMode));
+ return libUri;
}
diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_ios_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_ios_test.dart
index e4dcf00..bbcb70d 100644
--- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_ios_test.dart
+++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_ios_test.dart
@@ -53,8 +53,8 @@
final buildOutput = BuildOutput();
final cbuilder = CBuilder.library(
- name: 'add',
- assetName: 'add',
+ name: name,
+ assetName: name,
sources: [addCUri.toFilePath()],
);
await cbuilder.run(
diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart
index 9a4f34a..0142554 100644
--- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart
+++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_linux_host_test.dart
@@ -53,8 +53,8 @@
final buildOutput = BuildOutput();
final cbuilder = CBuilder.library(
- name: 'add',
- assetName: 'add',
+ name: name,
+ assetName: name,
sources: [addCUri.toFilePath()],
);
await cbuilder.run(
diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_macos_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_macos_host_test.dart
index 06f4ff1..b68e924 100644
--- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_macos_host_test.dart
+++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_macos_host_test.dart
@@ -53,8 +53,8 @@
final buildOutput = BuildOutput();
final cbuilder = CBuilder.library(
- name: 'add',
- assetName: 'add',
+ name: name,
+ assetName: name,
sources: [addCUri.toFilePath()],
);
await cbuilder.run(
diff --git a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_windows_host_test.dart b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_windows_host_test.dart
index 230c9af..f00feeb 100644
--- a/pkgs/c_compiler/test/cbuilder/cbuilder_cross_windows_host_test.dart
+++ b/pkgs/c_compiler/test/cbuilder/cbuilder_cross_windows_host_test.dart
@@ -65,8 +65,8 @@
final buildOutput = BuildOutput();
final cbuilder = CBuilder.library(
- name: 'add',
- assetName: 'add',
+ name: name,
+ assetName: name,
sources: [addCUri.toFilePath()],
);
await cbuilder.run(
diff --git a/pkgs/native_assets_cli/lib/src/model/build_config.dart b/pkgs/native_assets_cli/lib/src/model/build_config.dart
index 5886efd..8e657cc 100644
--- a/pkgs/native_assets_cli/lib/src/model/build_config.dart
+++ b/pkgs/native_assets_cli/lib/src/model/build_config.dart
@@ -39,6 +39,12 @@
IOSSdk? get targetIOSSdk => _targetIOSSdk;
late final IOSSdk? _targetIOSSdk;
+ /// When compiling for Android, the API version to target.
+ ///
+ /// Required when [target.os] equals [OS.android].
+ int? get targetAndroidNdkApi => _targetAndroidNdkApi;
+ late final int? _targetAndroidNdkApi;
+
/// Preferred linkMode method for library.
LinkModePreference get linkModePreference => _linkModePreference;
late final LinkModePreference _linkModePreference;
@@ -66,6 +72,7 @@
required Uri packageRoot,
required Target target,
IOSSdk? targetIOSSdk,
+ int? targetAndroidNdkApi,
CCompilerConfig? cCompiler,
required LinkModePreference linkModePreference,
Map<String, Metadata>? dependencyMetadata,
@@ -75,6 +82,7 @@
.._packageRoot = packageRoot
.._target = target
.._targetIOSSdk = targetIOSSdk
+ .._targetAndroidNdkApi = targetAndroidNdkApi
.._cCompiler = cCompiler ?? CCompilerConfig()
.._linkModePreference = linkModePreference
.._dependencyMetadata = dependencyMetadata;
@@ -94,6 +102,7 @@
required Uri packageRoot,
required Target target,
IOSSdk? targetIOSSdk,
+ int? targetAndroidNdkApi,
CCompilerConfig? cCompiler,
required LinkModePreference linkModePreference,
Map<String, Metadata>? dependencyMetadata,
@@ -103,6 +112,7 @@
packageName,
target.toString(),
targetIOSSdk.toString(),
+ targetAndroidNdkApi.toString(),
linkModePreference.toString(),
cCompiler?.ar.toString(),
cCompiler?.cc.toString(),
@@ -184,6 +194,7 @@
static const packageRootConfigKey = 'package_root';
static const dependencyMetadataConfigKey = 'dependency_metadata';
static const _versionKey = 'version';
+ static const targetAndroidNdkApiConfigKey = 'target_android_ndk_api';
List<void Function(Config)> _readFieldsFromConfig() {
var targetSet = false;
@@ -227,6 +238,9 @@
),
)
: null,
+ (config) => _targetAndroidNdkApi = (targetSet && _target.os == OS.android)
+ ? config.int(targetAndroidNdkApiConfigKey)
+ : null,
(config) => cCompiler._ar =
config.optionalPath(CCompilerConfig.arConfigKeyFull, mustExist: true),
(config) {
@@ -292,6 +306,8 @@
packageRootConfigKey: _packageRoot.toFilePath(),
Target.configKey: _target.toString(),
if (_targetIOSSdk != null) IOSSdk.configKey: _targetIOSSdk.toString(),
+ if (_targetAndroidNdkApi != null)
+ targetAndroidNdkApiConfigKey: _targetAndroidNdkApi!,
if (cCompilerYaml.isNotEmpty) CCompilerConfig.configKey: cCompilerYaml,
LinkModePreference.configKey: _linkModePreference.toString(),
if (_dependencyMetadata != null)
@@ -314,6 +330,7 @@
if (other._packageRoot != _packageRoot) return false;
if (other._target != _target) return false;
if (other._targetIOSSdk != _targetIOSSdk) return false;
+ if (other._targetAndroidNdkApi != _targetAndroidNdkApi) return false;
if (other._cCompiler != _cCompiler) return false;
if (other._linkModePreference != _linkModePreference) return false;
if (!DeepCollectionEquality()
@@ -327,6 +344,7 @@
_packageRoot,
_target,
_targetIOSSdk,
+ _targetAndroidNdkApi,
_cCompiler,
_linkModePreference,
DeepCollectionEquality().hash(_dependencyMetadata),
diff --git a/pkgs/native_assets_cli/test/model/asset_test.dart b/pkgs/native_assets_cli/test/model/asset_test.dart
index c542c5e..a4aa644 100644
--- a/pkgs/native_assets_cli/test/model/asset_test.dart
+++ b/pkgs/native_assets_cli/test/model/asset_test.dart
@@ -139,7 +139,12 @@
});
test('AssetPath factory', () async {
- expect(() => AssetPath('wrong', null), throwsFormatException);
+ expect(
+ () => AssetPath('wrong', null),
+ throwsA(predicate(
+ (e) => e is FormatException && e.message.contains('Unknown pathType'),
+ )),
+ );
});
test('Asset hashCode copyWith', () async {
diff --git a/pkgs/native_assets_cli/test/model/build_config_test.dart b/pkgs/native_assets_cli/test/model/build_config_test.dart
index 9b3ecfa..780a65c 100644
--- a/pkgs/native_assets_cli/test/model/build_config_test.dart
+++ b/pkgs/native_assets_cli/test/model/build_config_test.dart
@@ -63,6 +63,7 @@
outDir: outDir2Uri,
packageRoot: tempUri,
target: Target.androidArm64,
+ targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
);
@@ -88,6 +89,7 @@
outDir: outDirUri,
packageRoot: packageRootUri,
target: Target.androidArm64,
+ targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
);
@@ -95,6 +97,7 @@
'out_dir': outDirUri.toFilePath(),
'package_root': packageRootUri.toFilePath(),
'target': 'android_arm64',
+ 'target_android_ndk_api': 30,
'link_mode_preference': 'prefer-static',
'version': BuildOutput.version.toString(),
});
@@ -127,6 +130,7 @@
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
+ targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
dependencyMetadata: {
'bar': Metadata({
@@ -143,6 +147,7 @@
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
+ targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
dependencyMetadata: {
'bar': Metadata({
@@ -213,28 +218,67 @@
test('BuildConfig FormatExceptions', () {
expect(
() => BuildConfig.fromConfig(Config(fileParsed: {})),
- throwsFormatException,
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains(
+ 'No value was provided for required key: target',
+ ),
+ )),
);
expect(
() => BuildConfig.fromConfig(Config(fileParsed: {
+ 'version': BuildConfig.version.toString(),
'package_root': packageRootUri.toFilePath(),
'target': 'android_arm64',
+ 'target_android_ndk_api': 30,
'link_mode_preference': 'prefer-static',
})),
- throwsFormatException,
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains(
+ 'No value was provided for required key: out_dir',
+ ),
+ )),
);
expect(
() => BuildConfig.fromConfig(Config(fileParsed: {
+ 'version': BuildConfig.version.toString(),
'out_dir': outDirUri.toFilePath(),
'package_root': packageRootUri.toFilePath(),
'target': 'android_arm64',
+ 'target_android_ndk_api': 30,
'link_mode_preference': 'prefer-static',
'dependency_metadata': {
'bar': {'key': 'value'},
'foo': <int>[],
},
})),
- throwsFormatException,
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains(
+ "Unexpected value '[]' for key 'dependency_metadata.foo' in "
+ 'config file. Expected a Map.',
+ ),
+ )),
+ );
+ expect(
+ () => BuildConfig.fromConfig(Config(fileParsed: {
+ 'out_dir': outDirUri.toFilePath(),
+ 'version': BuildConfig.version.toString(),
+ 'package_root': packageRootUri.toFilePath(),
+ 'target': 'android_arm64',
+ 'link_mode_preference': 'prefer-static',
+ })),
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains(
+ 'No value was provided for required key: target_android_ndk_api',
+ ),
+ )),
);
});
@@ -271,6 +315,7 @@
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
+ targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
);
final configFileContents = buildConfig.toYamlString();
@@ -289,6 +334,7 @@
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
+ targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
dependencyMetadata: {
'bar': Metadata({
@@ -337,7 +383,15 @@
'target': 'linux_x64',
'version': version,
});
- expect(() => BuildConfig.fromConfig(config), throwsFormatException);
+ expect(
+ () => BuildConfig.fromConfig(config),
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains(version) &&
+ e.message.contains(BuildConfig.version.toString()),
+ )),
+ );
});
}
@@ -354,7 +408,7 @@
);
// Using the checksum for a build folder should be stable.
- expect(name1, '96819d83ae789cb65752986a4abb4071');
+ expect(name1, '02dce8b58210deaf9f278772e892d01f');
// Build folder different due to metadata.
final name2 = BuildConfig.checksum(
diff --git a/pkgs/native_assets_cli/test/model/build_output_test.dart b/pkgs/native_assets_cli/test/model/build_output_test.dart
index c99a7be..cf9a889 100644
--- a/pkgs/native_assets_cli/test/model/build_output_test.dart
+++ b/pkgs/native_assets_cli/test/model/build_output_test.dart
@@ -100,7 +100,12 @@
test('BuildOutput version $version', () {
expect(
() => BuildOutput.fromYamlString('version: $version'),
- throwsFormatException,
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains(version) &&
+ e.message.contains(BuildConfig.version.toString()),
+ )),
);
});
}
diff --git a/pkgs/native_assets_cli/test/model/target_test.dart b/pkgs/native_assets_cli/test/model/target_test.dart
index ce74672..1412c8a 100644
--- a/pkgs/native_assets_cli/test/model/target_test.dart
+++ b/pkgs/native_assets_cli/test/model/target_test.dart
@@ -27,11 +27,26 @@
test('Target fromDartPlatform', () async {
final current = Target.fromDartPlatform(Platform.version);
expect(current.toString(), Abi.current().toString());
- expect(() => Target.fromDartPlatform('bogus'), throwsFormatException);
expect(
- () => Target.fromDartPlatform(
- '3.0.0 (be) (Wed Apr 5 14:19:42 2023 +0000) on "myfancyos_ia32"'),
- throwsFormatException);
+ () => Target.fromDartPlatform('bogus'),
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains('bogus') &&
+ e.message.contains('Unknown version'),
+ )),
+ );
+ expect(
+ () => Target.fromDartPlatform(
+ '3.0.0 (be) (Wed Apr 5 14:19:42 2023 +0000) on "myfancyos_ia32"',
+ ),
+ throwsA(predicate(
+ (e) =>
+ e is FormatException &&
+ e.message.contains('myfancyos_ia32') &&
+ e.message.contains('Unknown ABI'),
+ )),
+ );
});
test('Target cross compilation', () async {