Skip to content
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

Add Android NDK API version #49

Merged
merged 3 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 3 additions & 1 deletion pkgs/c_compiler/lib/src/cbuilder/run_cbuilder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ class RunCBuilder {
// 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]!}',
Expand Down
98 changes: 72 additions & 26 deletions pkgs/c_compiler/test/cbuilder/cbuilder_cross_android_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:io';

import 'package:c_compiler/c_compiler.dart';
import 'package:c_compiler/src/utils/run_process.dart';
import 'package:collection/collection.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -33,37 +34,22 @@ void main() {
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'),
Expand Down Expand Up @@ -91,4 +77,64 @@ void main() {
});
}
}

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.equals(bytes2), false);
// Identical API levels should lead to an identical binary.
expect(bytes2.equals(bytes3), true);
dcharkes marked this conversation as resolved.
Show resolved Hide resolved
});
});
}

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: 'add',
dcharkes marked this conversation as resolved.
Show resolved Hide resolved
assetName: 'add',
sources: [addCUri.toFilePath()],
);
await cbuilder.run(
buildConfig: buildConfig,
buildOutput: buildOutput,
logger: logger,
);

final libUri = tempUri.resolve(target.os.libraryFileName(name, linkMode));
return libUri;
}
18 changes: 18 additions & 0 deletions pkgs/native_assets_cli/lib/src/model/build_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ class BuildConfig {
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;
Expand Down Expand Up @@ -66,6 +72,7 @@ class BuildConfig {
required Uri packageRoot,
required Target target,
IOSSdk? targetIOSSdk,
int? targetAndroidNdkApi,
CCompilerConfig? cCompiler,
required LinkModePreference linkModePreference,
Map<String, Metadata>? dependencyMetadata,
Expand All @@ -75,6 +82,7 @@ class BuildConfig {
.._packageRoot = packageRoot
.._target = target
.._targetIOSSdk = targetIOSSdk
.._targetAndroidNdkApi = targetAndroidNdkApi
.._cCompiler = cCompiler ?? CCompilerConfig()
.._linkModePreference = linkModePreference
.._dependencyMetadata = dependencyMetadata;
Expand All @@ -94,6 +102,7 @@ class BuildConfig {
required Uri packageRoot,
required Target target,
IOSSdk? targetIOSSdk,
int? targetAndroidNdkApi,
CCompilerConfig? cCompiler,
required LinkModePreference linkModePreference,
Map<String, Metadata>? dependencyMetadata,
Expand All @@ -103,6 +112,7 @@ class BuildConfig {
packageName,
target.toString(),
targetIOSSdk.toString(),
targetAndroidNdkApi.toString(),
linkModePreference.toString(),
cCompiler?.ar.toString(),
cCompiler?.cc.toString(),
Expand Down Expand Up @@ -184,6 +194,7 @@ class BuildConfig {
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;
Expand Down Expand Up @@ -227,6 +238,9 @@ class BuildConfig {
),
)
: null,
(config) => _targetAndroidNdkApi = (targetSet && _target.os == OS.android)
? config.int(targetAndroidNdkApiConfigKey)
: null,
(config) => cCompiler._ar =
config.optionalPath(CCompilerConfig.arConfigKeyFull, mustExist: true),
(config) {
Expand Down Expand Up @@ -292,6 +306,8 @@ class BuildConfig {
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)
Expand All @@ -314,6 +330,7 @@ class BuildConfig {
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()
Expand All @@ -327,6 +344,7 @@ class BuildConfig {
_packageRoot,
_target,
_targetIOSSdk,
_targetAndroidNdkApi,
_cCompiler,
_linkModePreference,
DeepCollectionEquality().hash(_dependencyMetadata),
Expand Down
7 changes: 6 additions & 1 deletion pkgs/native_assets_cli/test/model/asset_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,12 @@ native-assets:
});

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 {
Expand Down
64 changes: 59 additions & 5 deletions pkgs/native_assets_cli/test/model/build_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ void main() async {
outDir: outDir2Uri,
packageRoot: tempUri,
target: Target.androidArm64,
targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
);

Expand All @@ -88,13 +89,15 @@ void main() async {
outDir: outDirUri,
packageRoot: packageRootUri,
target: Target.androidArm64,
targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
);

final config = Config(fileParsed: {
'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(),
});
Expand Down Expand Up @@ -127,6 +130,7 @@ void main() async {
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
dependencyMetadata: {
'bar': Metadata({
Expand All @@ -143,6 +147,7 @@ void main() async {
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
dependencyMetadata: {
'bar': Metadata({
Expand Down Expand Up @@ -213,28 +218,67 @@ version: ${BuildConfig.version}''';
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',
),
)),
);
});

Expand Down Expand Up @@ -271,6 +315,7 @@ version: ${BuildConfig.version}''';
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
);
final configFileContents = buildConfig.toYamlString();
Expand All @@ -289,6 +334,7 @@ version: ${BuildConfig.version}''';
outDir: outDirUri,
packageRoot: tempUri,
target: Target.androidArm64,
targetAndroidNdkApi: 30,
linkModePreference: LinkModePreference.preferStatic,
dependencyMetadata: {
'bar': Metadata({
Expand Down Expand Up @@ -337,7 +383,15 @@ version: ${BuildConfig.version}''';
'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()),
)),
);
});
}

Expand All @@ -354,7 +408,7 @@ version: ${BuildConfig.version}''';
);

// 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(
Expand Down
Loading