Skip to content

Commit 6ad7555

Browse files
authored
Native assets support for Android (#135148)
Support for FFI calls with `@Native external` functions through Native assets on Android. This enables bundling native code without any build-system boilerplate code. For more info see: * flutter/flutter#129757 ### Implementation details for Android. Mainly follows the design of the previous PRs. For Android, we detect the compilers inside the NDK inside SDK. And bundling of the assets is done by the flutter.groovy file. The `minSdkVersion` is propagated from the flutter.groovy file as well. The NDK is not part of `flutter doctor`, and users can omit it if no native assets have to be build. However, if any native assets must be built, flutter throws a tool exit if the NDK is not installed. Add 2 app is not part of this PR yet, instead `flutter build aar` will tool exit if there are any native assets.
1 parent da7e5e3 commit 6ad7555

File tree

19 files changed

+1680
-451
lines changed

19 files changed

+1680
-451
lines changed

.ci.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,6 +2404,16 @@ targets:
24042404
["devicelab", "android", "linux"]
24052405
task_name: list_text_layout_impeller_perf__e2e_summary
24062406

2407+
- name: Linux_android native_assets_android
2408+
recipe: devicelab/devicelab_drone
2409+
presubmit: false
2410+
bringup: true
2411+
timeout: 60
2412+
properties:
2413+
tags: >
2414+
["devicelab", "android", "linux"]
2415+
task_name: native_assets_android
2416+
24072417
- name: Linux_android new_gallery__crane_perf
24082418
recipe: devicelab/devicelab_drone
24092419
presubmit: false
@@ -3797,6 +3807,16 @@ targets:
37973807
["devicelab", "android", "mac"]
37983808
task_name: microbenchmarks
37993809

3810+
- name: Mac_android native_assets_android
3811+
recipe: devicelab/devicelab_drone
3812+
presubmit: false
3813+
bringup: true
3814+
timeout: 60
3815+
properties:
3816+
tags: >
3817+
["devicelab", "android", "mac"]
3818+
task_name: native_assets_android
3819+
38003820
- name: Mac_android run_debug_test_android
38013821
recipe: devicelab/devicelab_drone
38023822
presubmit: false
@@ -5525,6 +5545,16 @@ targets:
55255545
["devicelab", "android", "windows"]
55265546
task_name: hot_mode_dev_cycle_win__benchmark
55275547

5548+
- name: Windows_android native_assets_android
5549+
recipe: devicelab/devicelab_drone
5550+
presubmit: false
5551+
bringup: true
5552+
timeout: 60
5553+
properties:
5554+
tags: >
5555+
["devicelab", "android", "windows"]
5556+
task_name: native_assets_android
5557+
55285558
- name: Windows_android windows_chrome_dev_mode
55295559
recipe: devicelab/devicelab_drone
55305560
presubmit: false

TESTOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @zanderso @flutter/engine
203203
/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @vashworth @flutter/engine
204204
/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart @vashworth @flutter/engine
205+
/dev/devicelab/bin/tasks/native_assets_android.dart @dacoharkes @flutter/android
205206
/dev/devicelab/bin/tasks/native_assets_ios.dart @dacoharkes @flutter/ios
206207
/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios
207208
/dev/devicelab/bin/tasks/new_gallery_ios__transition_perf.dart @zanderso @flutter/engine

dev/devicelab/bin/tasks/module_test.dart

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,32 @@ Future<void> main() async {
4343
);
4444
});
4545

46+
section('Create package with native assets');
47+
48+
await flutter(
49+
'config',
50+
options: <String>['--enable-native-assets'],
51+
);
52+
53+
const String ffiPackageName = 'ffi_package';
54+
await createFfiPackage(ffiPackageName, tempDir);
55+
56+
section('Add FFI package');
57+
58+
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
59+
String content = await pubspec.readAsString();
60+
content = content.replaceFirst(
61+
'dependencies:$platformLineSep',
62+
'dependencies:$platformLineSep $ffiPackageName:$platformLineSep path: ..${Platform.pathSeparator}$ffiPackageName$platformLineSep',
63+
);
64+
await pubspec.writeAsString(content, flush: true);
65+
await inDirectory(projectDir, () async {
66+
await flutter(
67+
'packages',
68+
options: <String>['get'],
69+
);
70+
});
71+
4672
section('Add read-only asset');
4773

4874
final File readonlyTxtAssetFile = await File(path.join(
@@ -63,8 +89,6 @@ Future<void> main() async {
6389
]);
6490
}
6591

66-
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
67-
String content = await pubspec.readAsString();
6892
content = content.replaceFirst(
6993
'$platformLineSep # assets:$platformLineSep',
7094
'$platformLineSep assets:$platformLineSep - assets/read-only.txt$platformLineSep',
@@ -73,7 +97,6 @@ Future<void> main() async {
7397

7498
section('Add plugins');
7599

76-
content = await pubspec.readAsString();
77100
content = content.replaceFirst(
78101
'${platformLineSep}dependencies:$platformLineSep',
79102
'${platformLineSep}dependencies:$platformLineSep device_info: 2.0.3$platformLineSep package_info: 2.0.2$platformLineSep',
@@ -86,6 +109,45 @@ Future<void> main() async {
86109
);
87110
});
88111

112+
// TODO(dacoharkes): Implement Add2app. https://github.com/flutter/flutter/issues/129757
113+
section('Check native assets error');
114+
115+
await inDirectory(Directory(path.join(projectDir.path, '.android')),
116+
() async {
117+
final StringBuffer stderr = StringBuffer();
118+
final int exitCode = await exec(
119+
gradlewExecutable,
120+
<String>['flutter:assembleDebug'],
121+
environment: <String, String>{'JAVA_HOME': javaHome},
122+
canFail: true,
123+
stderr: stderr,
124+
);
125+
const String errorString =
126+
'Native assets are not yet supported in Android add2app.';
127+
if (!stderr.toString().contains(errorString) || exitCode == 0) {
128+
throw TaskResult.failure(
129+
'''
130+
Expected to find `$errorString` in stderr and nonZero exit code.
131+
$stderr
132+
exitCode: $exitCode
133+
''');
134+
}
135+
});
136+
137+
section('Remove FFI package');
138+
139+
content = content.replaceFirst(
140+
' $ffiPackageName:$platformLineSep path: ..${Platform.pathSeparator}$ffiPackageName$platformLineSep',
141+
'',
142+
);
143+
await pubspec.writeAsString(content, flush: true);
144+
await inDirectory(projectDir, () async {
145+
await flutter(
146+
'packages',
147+
options: <String>['get'],
148+
);
149+
});
150+
89151
section('Build Flutter module library archive');
90152

91153
await inDirectory(Directory(path.join(projectDir.path, '.android')), () async {

dev/devicelab/bin/tasks/module_test_ios.dart

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ Future<void> main() async {
6767
);
6868

6969
const String ffiPackageName = 'ffi_package';
70-
await _createFfiPackage(ffiPackageName, tempDir);
70+
await createFfiPackage(ffiPackageName, tempDir);
7171

7272
section('Add FFI package');
7373

@@ -730,30 +730,3 @@ class $dartPluginClass {
730730
// Remove the native plugin code.
731731
await Directory(path.join(pluginDir, 'ios')).delete(recursive: true);
732732
}
733-
734-
Future<void> _createFfiPackage(String name, Directory parent) async {
735-
await inDirectory(parent, () async {
736-
await flutter(
737-
'create',
738-
options: <String>[
739-
'--no-pub',
740-
'--org',
741-
'io.flutter.devicelab',
742-
'--template=package_ffi',
743-
name,
744-
],
745-
);
746-
await _pinDependencies(
747-
File(path.join(parent.path, name, 'pubspec.yaml')),
748-
);
749-
await _pinDependencies(
750-
File(path.join(parent.path, name, 'example', 'pubspec.yaml')),
751-
);
752-
});
753-
}
754-
755-
Future<void> _pinDependencies(File pubspecFile) async {
756-
final String oldPubspec = await pubspecFile.readAsString();
757-
final String newPubspec = oldPubspec.replaceAll(': ^', ': ');
758-
await pubspecFile.writeAsString(newPubspec);
759-
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_devicelab/framework/devices.dart';
6+
import 'package:flutter_devicelab/framework/framework.dart';
7+
import 'package:flutter_devicelab/tasks/native_assets_test.dart';
8+
9+
Future<void> main() async {
10+
await task(() async {
11+
deviceOperatingSystem = DeviceOperatingSystem.android;
12+
return createNativeAssetsTest()();
13+
});
14+
}

dev/devicelab/lib/framework/utils.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,13 +339,17 @@ Future<int> exec(
339339
Map<String, String>? environment,
340340
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
341341
String? workingDirectory,
342+
StringBuffer? output, // if not null, the stdout will be written here
343+
StringBuffer? stderr, // if not null, the stderr will be written here
342344
}) async {
343345
return _execute(
344346
executable,
345347
arguments,
346348
environment: environment,
347349
canFail : canFail,
348350
workingDirectory: workingDirectory,
351+
output: output,
352+
stderr: stderr,
349353
);
350354
}
351355

@@ -898,3 +902,30 @@ Future<T> retry<T>(
898902
await Future<void>.delayed(delayDuration);
899903
}
900904
}
905+
906+
Future<void> createFfiPackage(String name, Directory parent) async {
907+
await inDirectory(parent, () async {
908+
await flutter(
909+
'create',
910+
options: <String>[
911+
'--no-pub',
912+
'--org',
913+
'io.flutter.devicelab',
914+
'--template=package_ffi',
915+
name,
916+
],
917+
);
918+
await _pinDependencies(
919+
File(path.join(parent.path, name, 'pubspec.yaml')),
920+
);
921+
await _pinDependencies(
922+
File(path.join(parent.path, name, 'example', 'pubspec.yaml')),
923+
);
924+
});
925+
}
926+
927+
Future<void> _pinDependencies(File pubspecFile) async {
928+
final String oldPubspec = await pubspecFile.readAsString();
929+
final String newPubspec = oldPubspec.replaceAll(': ^', ': ');
930+
await pubspecFile.writeAsString(newPubspec);
931+
}

packages/flutter_tools/gradle/src/main/groovy/flutter.groovy

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,15 @@ class FlutterPlugin implements Plugin<Project> {
10321032
}
10331033
}
10341034
}
1035+
// Build an AAR when this property is defined.
1036+
boolean isBuildingAar = project.hasProperty('is-plugin')
1037+
// In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project.
1038+
// `:flutter` is used as a subproject when these tasks exists and the build isn't building an AAR.
1039+
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
1040+
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
1041+
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
1042+
boolean isAndroidLibraryValue = isBuildingAar || isUsedAsSubproject
1043+
10351044
String variantBuildMode = buildModeFor(variant.buildType)
10361045
String taskName = toCamelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])
10371046
// Be careful when configuring task below, Groovy has bizarre
@@ -1044,6 +1053,7 @@ class FlutterPlugin implements Plugin<Project> {
10441053
flutterRoot this.flutterRoot
10451054
flutterExecutable this.flutterExecutable
10461055
buildMode variantBuildMode
1056+
minSdkVersion variant.mergedFlavor.minSdkVersion.apiLevel
10471057
localEngine this.localEngine
10481058
localEngineHost this.localEngineHost
10491059
localEngineSrcPath this.localEngineSrcPath
@@ -1068,6 +1078,7 @@ class FlutterPlugin implements Plugin<Project> {
10681078
codeSizeDirectory codeSizeDirectoryValue
10691079
deferredComponents deferredComponentsValue
10701080
validateDeferredComponents validateDeferredComponentsValue
1081+
isAndroidLibrary isAndroidLibraryValue
10711082
doLast {
10721083
project.exec {
10731084
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
@@ -1097,13 +1108,6 @@ class FlutterPlugin implements Plugin<Project> {
10971108
addApiDependencies(project, variant.name, project.files {
10981109
packFlutterAppAotTask
10991110
})
1100-
// We build an AAR when this property is defined.
1101-
boolean isBuildingAar = project.hasProperty('is-plugin')
1102-
// In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project.
1103-
// We know that `:flutter` is used as a subproject when these tasks exists and we aren't building an AAR.
1104-
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
1105-
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
1106-
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
11071111
Task copyFlutterAssetsTask = project.tasks.create(
11081112
name: "copyFlutterAssets${variant.name.capitalize()}",
11091113
type: Copy,
@@ -1194,6 +1198,9 @@ class FlutterPlugin implements Plugin<Project> {
11941198
}
11951199
}
11961200
}
1201+
// Copy the native assets created by build.dart and placed here by flutter assemble.
1202+
def nativeAssetsDir = "${project.buildDir}/../native_assets/android/jniLibs/lib/"
1203+
project.android.sourceSets.main.jniLibs.srcDir nativeAssetsDir
11971204
}
11981205
configurePlugins()
11991206
detectLowCompileSdkVersionOrNdkVersion()
@@ -1219,7 +1226,7 @@ class FlutterPlugin implements Plugin<Project> {
12191226
// | ----------------- | ----------------------------- |
12201227
// | Build Variant | Flutter Equivalent Variant |
12211228
// | ----------------- | ----------------------------- |
1222-
// | freeRelease | release |
1229+
// | freeRelease | release |
12231230
// | freeDebug | debug |
12241231
// | freeDevelop | debug |
12251232
// | profile | profile |
@@ -1277,6 +1284,8 @@ abstract class BaseFlutterTask extends DefaultTask {
12771284
File flutterExecutable
12781285
@Input
12791286
String buildMode
1287+
@Input
1288+
int minSdkVersion
12801289
@Optional @Input
12811290
String localEngine
12821291
@Optional @Input
@@ -1325,6 +1334,8 @@ abstract class BaseFlutterTask extends DefaultTask {
13251334
Boolean deferredComponents
13261335
@Optional @Input
13271336
Boolean validateDeferredComponents
1337+
@Optional @Input
1338+
Boolean isAndroidLibrary
13281339

13291340
@OutputFiles
13301341
FileCollection getDependenciesFiles() {
@@ -1414,6 +1425,11 @@ abstract class BaseFlutterTask extends DefaultTask {
14141425
if (extraFrontEndOptions != null) {
14151426
args "--ExtraFrontEndOptions=${extraFrontEndOptions}"
14161427
}
1428+
args "-dAndroidArchs=${targetPlatformValues.join(' ')}"
1429+
args "-dMinSdkVersion=${minSdkVersion}"
1430+
if (isAndroidLibrary != null) {
1431+
args "-dIsAndroidLibrary=${isAndroidLibrary ? "true" : "false"}"
1432+
}
14171433
args ruleNames
14181434
}
14191435
}

0 commit comments

Comments
 (0)