Unverified Commit b2ef2802 authored by Daco Harkes's avatar Daco Harkes Committed by GitHub

Native assets support for Android Add2app (#140802)

Support for FFI calls with @Native external functions through Native assets on Android add to app. This enables bundling native code without any build-system boilerplate code.

For more info see:

*  https://github.com/flutter/flutter/issues/129757

## Implementation details for Android add2app

The `.so` files are bundled with the same mechanism that bundles `libapp.so`.
parent 29db9dbd
......@@ -132,43 +132,6 @@ class ModuleTest {
});
// TODO(dacoharkes): Implement Add2app. https://github.com/flutter/flutter/issues/129757
section('Check native assets error');
await inDirectory(Directory(path.join(projectDir.path, '.android')),
() async {
final StringBuffer stderr = StringBuffer();
final int exitCode = await exec(
gradlewExecutable,
<String>['flutter:assembleDebug'],
environment: <String, String>{'JAVA_HOME': javaHome},
canFail: true,
stderr: stderr,
);
const String errorString =
'Native assets are not yet supported in Android add2app.';
if (!stderr.toString().contains(errorString) || exitCode == 0) {
throw TaskResult.failure(
'''
Expected to find `$errorString` in stderr and nonZero exit code.
$stderr
exitCode: $exitCode
''');
}
});
section('Remove FFI package');
content = content.replaceFirst(
' $ffiPackageName:$platformLineSep path: ..${Platform.pathSeparator}$ffiPackageName$platformLineSep',
'',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async {
await flutter(
'packages',
options: <String>['get'],
);
});
section('Build Flutter module library archive');
......@@ -331,6 +294,8 @@ exitCode: $exitCode
...flutterAssets,
...debugAssets,
...baseApkFiles,
'lib/arm64-v8a/lib$ffiPackageName.so',
'lib/armeabi-v7a/lib$ffiPackageName.so',
], await getFilesInApk(debugHostApk));
section('Check debug AndroidManifest.xml');
......@@ -409,8 +374,10 @@ exitCode: $exitCode
checkCollectionContains<String>(<String>[
...flutterAssets,
...baseApkFiles,
'lib/arm64-v8a/lib$ffiPackageName.so',
'lib/arm64-v8a/libapp.so',
'lib/arm64-v8a/libflutter.so',
'lib/armeabi-v7a/lib$ffiPackageName.so',
'lib/armeabi-v7a/libapp.so',
'lib/armeabi-v7a/libflutter.so',
], await getFilesInApk(releaseHostApk));
......
......@@ -1083,7 +1083,6 @@ class FlutterPlugin implements Plugin<Project> {
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
boolean isAndroidLibraryValue = isBuildingAar || isUsedAsSubproject
String variantBuildMode = buildModeFor(variant.buildType)
String flavorValue = variant.getFlavorName()
......@@ -1123,11 +1122,10 @@ class FlutterPlugin implements Plugin<Project> {
codeSizeDirectory(codeSizeDirectoryValue)
deferredComponents(deferredComponentsValue)
validateDeferredComponents(validateDeferredComponentsValue)
isAndroidLibrary(isAndroidLibraryValue)
flavor(flavorValue)
}
File libJar = project.file("${project.buildDir}/$INTERMEDIATES_DIR/flutter/${variant.name}/libs.jar")
Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
Task packJniLibsTask = project.tasks.create(name: "packJniLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
destinationDirectory = libJar.parentFile
archiveFileName = libJar.name
dependsOn compileTask
......@@ -1140,10 +1138,20 @@ class FlutterPlugin implements Plugin<Project> {
return "lib/${abi}/lib${filename}"
}
}
// Copy the native assets created by build.dart and placed in build/native_assets by flutter assemble.
// The `$project.buildDir` is '.android/Flutter/build/' instead of 'build/'.
def buildDir = "${getFlutterSourceDirectory()}/build"
def nativeAssetsDir = "${buildDir}/native_assets/android/jniLibs/lib"
from("${nativeAssetsDir}/${abi}") {
include "*.so"
rename { String filename ->
return "lib/${abi}/${filename}"
}
}
}
}
addApiDependencies(project, variant.name, project.files {
packFlutterAppAotTask
packJniLibsTask
})
Task copyFlutterAssetsTask = project.tasks.create(
name: "copyFlutterAssets${variant.name.capitalize()}",
......@@ -1413,8 +1421,6 @@ abstract class BaseFlutterTask extends DefaultTask {
@Optional @Input
Boolean validateDeferredComponents
@Optional @Input
Boolean isAndroidLibrary
@Optional @Input
String flavor
@OutputFiles
......@@ -1510,9 +1516,6 @@ abstract class BaseFlutterTask extends DefaultTask {
}
args("-dAndroidArchs=${targetPlatformValues.join(' ')}")
args("-dMinSdkVersion=${minSdkVersion}")
if (isAndroidLibrary != null) {
args("-dIsAndroidLibrary=${isAndroidLibrary ? "true" : "false"}")
}
args(ruleNames)
}
}
......
......@@ -78,7 +78,6 @@ Future<(Uri? nativeAssetsYaml, List<Uri> dependencies)>
Uri? yamlParentDirectory,
required FileSystem fileSystem,
required int targetAndroidNdkApi,
bool isAndroidLibrary = false,
}) async {
const OS targetOS = OS.android;
final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOS);
......@@ -115,9 +114,6 @@ Future<(Uri? nativeAssetsYaml, List<Uri> dependencies)>
}
ensureNoLinkModeStatic(nativeAssets);
globals.logger.printTrace('Building native assets for $targets done.');
if (isAndroidLibrary && nativeAssets.isNotEmpty) {
throwToolExit('Native assets are not yet supported in Android add2app.');
}
final Map<Asset, Asset> assetTargetLocations =
_assetTargetLocations(nativeAssets);
await _copyNativeAssetsAndroid(buildUri_, assetTargetLocations, fileSystem);
......
......@@ -932,13 +932,6 @@ const String kDarwinArchs = 'DarwinArchs';
/// This is expected to be a space-delimited list of architectures.
const String kAndroidArchs = 'AndroidArchs';
/// If the current build is `flutter build aar`.
///
/// This is expected to be a boolean.
///
/// If not provided, defaults to false.
const String kIsAndroidLibrary = 'IsAndroidLibrary';
/// The define to control what min Android SDK version is built for.
///
/// This is expected to be int.
......
......@@ -306,8 +306,6 @@ class NativeAssets extends Target {
Uri projectUri,
FileSystem fileSystem,
NativeAssetsBuildRunner buildRunner) {
final bool isAndroidLibrary =
environment.defines[kIsAndroidLibrary] == 'true';
final String? androidArchsEnvironment = environment.defines[kAndroidArchs];
final List<AndroidArch> androidArchs = _androidArchs(
targetPlatform,
......@@ -323,7 +321,6 @@ class NativeAssets extends Target {
buildRunner: buildRunner,
androidArchs: androidArchs,
targetAndroidNdkApi: targetAndroidNdkApi,
isAndroidLibrary: isAndroidLibrary,
);
}
......
......@@ -171,61 +171,43 @@ void main() {
},
);
for (final bool isAndroidLibrary in <bool>[true, false]) {
for (final bool hasAssets in <bool>[true, false]) {
final String buildType = isAndroidLibrary ? 'aar' : 'not-aar';
final String withOrWithout = hasAssets ? 'with' : 'without';
final String throwsOrDoesntThrow =
(isAndroidLibrary && hasAssets) ? 'throws' : 'does not throw';
testUsingContext(
'flutter build $buildType $withOrWithout native assets $throwsOrDoesntThrow',
overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
FeatureFlags: () => TestFeatureFlags(
isNativeAssetsEnabled: true,
),
},
() async {
await createPackageConfig(androidEnvironment);
await fileSystem.file('libfoo.so').create();
final NativeAssetsBuildRunner buildRunner =
FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('foo', androidEnvironment.buildDir.uri)
],
buildResult:
FakeNativeAssetsBuilderResult(assets: <native_assets_cli.Asset>[
if (hasAssets)
native_assets_cli.Asset(
id: 'package:foo/foo.dart',
linkMode: native_assets_cli.LinkMode.dynamic,
target: native_assets_cli.Target.androidArm64,
path: native_assets_cli.AssetAbsolutePath(
Uri.file('libfoo.so'),
),
)
], dependencies: <Uri>[
Uri.file('src/foo.c'),
]),
);
if (isAndroidLibrary) {
androidEnvironment.defines[kIsAndroidLibrary] = 'true';
}
if (hasAssets && isAndroidLibrary) {
expect(
NativeAssets(buildRunner: buildRunner).build(androidEnvironment),
throwsToolExit(),
);
} else {
await NativeAssets(buildRunner: buildRunner)
.build(androidEnvironment);
}
},
);
}
for (final bool hasAssets in <bool>[true, false]) {
final String withOrWithout = hasAssets ? 'with' : 'without';
testUsingContext(
'flutter build $withOrWithout native assets',
overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
FeatureFlags: () => TestFeatureFlags(
isNativeAssetsEnabled: true,
),
},
() async {
await createPackageConfig(androidEnvironment);
await fileSystem.file('libfoo.so').create();
final NativeAssetsBuildRunner buildRunner = FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('foo', androidEnvironment.buildDir.uri)
],
buildResult:
FakeNativeAssetsBuilderResult(assets: <native_assets_cli.Asset>[
if (hasAssets)
native_assets_cli.Asset(
id: 'package:foo/foo.dart',
linkMode: native_assets_cli.LinkMode.dynamic,
target: native_assets_cli.Target.androidArm64,
path: native_assets_cli.AssetAbsolutePath(
Uri.file('libfoo.so'),
),
)
], dependencies: <Uri>[
Uri.file('src/foo.c'),
]),
);
await NativeAssets(buildRunner: buildRunner).build(androidEnvironment);
},
);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment