GN Cheat Sheet for Chromium Android

GN Basics

Ninja and GN

Ninja is a build system focus on speed. It has very little options.

GN is a meta-build system that generates build files for Ninja.

GN Script

GN script file usually has an extension of gn or gni. *.gni are normally been imported by BUILD.gn scripts.

Normally, we have some BUILD.gn file in project directories. In the script, we define rules, targets… for GN.

Rule

GN has some built-in rules which can do specified work, rule can be called with predefined variables.

E.g. copy rule can do some file copy works, and shared_library rule can build C++ code into a shared library.

And we can define new rules or redefine built-in rules using template keywords.

Examples in Chromium Project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# build/config/android/rules.gni

# define rule named `android_library`
template("android_library") {

# call another rule named `java_library`
java_library(target_name) {

# copy variables from android_library to java_library
forward_variables_from(invoker, "*")

# set variables for java_library rule
supports_android = true
requires_android = true

if (!defined(jar_excluded_patterns)) {
jar_excluded_patterns = []
}
jar_excluded_patterns += [
"*/R.class",
"*/R\$*.class",
"*/Manifest.class",
"*/Manifest\$*.class",
"*/GEN_JNI.class",
]
}
}

Target

Target represents a build operation, and it can have dependencies of other targets. We can use a rule to define targets.

For example, we have a GN file chrome/android/BUILD.gn and it defined a target with name chrome_public_apk using rule chrome_public_apk_or_module_tmpl. And this target can build an apk of Chromium. The full name of this target is //chrome/android:chrome_public_apk.

1
2
3
4
5
# chrome/android/BUILD.gn

chrome_public_apk_or_module_tmpl("chrome_public_apk") {
# ...
}

When a target depends on other targets, we can use deps to define that. And we can use relative name or full name of other targets.

1
2
3
4
5
6
7
8
9
# somedir/BUILD.gn
some_rule("target_a") {
}
some_rule("target_b") {
deps = [
# use relative name
":target_a"
]
}
1
2
3
4
5
6
7
8
9
# somedir/somesubdir/BUILD.gn
some_rule("target_c") {
deps = [
# use relative name
"..:target_a"
# use full name
"//somedir:target_b"
]
}

GN Grammar

GN grammar is very simple, and can be found here.

https://gn.googlesource.com/gn/+/master/docs/reference.md#grammar

Frequently Used Commands

Here is some frequently used commands of GN and Ninja.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# configure args declared by the build (args.gn file)
gn args out/debug

# show all args declared by the build
gn args --list out/debug

# generate ninja files (build.ninja file)
gn gen out/debug

# list all targets
gn ls out/debug

# show dependencies of an target
# optional param all: including transitive dependencies
# optional param tree: show dependencies as a tree
# note: this command may stuck if target has too many dependencies
gn desc out/debug <target-name> --all --tree

# show what targets are depend on the specified target
gn refs out/debug <target-name>

# build target with ninja
ninja -C out/debug <target-name>

GN in Chromium Android

Chromium project has defined many platform specified rules for different platforms including Windows, Linux, Mac, Android, iOS. For Android, these rules are defined in build/config/android/rules.gni file. If you want to use these rules, import rules.gni file first.

1
2
3
4
5
6
7
8
9
# BUILD.gn

# import rules for android
import("//build/config/android/rules.gni")

# if the gn script is used for multi platform, add if statement for the import
if (is_android) {
import("//build/config/android/rules.gni")
}

Using Config

config can define variables for other targets, and we can use += -= for configs. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
config("cpp17") {
# define veriables of cflags_cc
cflags_cc = [
"-Xclang",
"-std=c++17",
]
}

config("login_sdk_config") {

# include predefined cpp17 config
configs = [ ":cpp17" ]

# enable C++ exceptions using predefined config in chromium
configs -= [ "//build/config/compiler:no_exceptions" ]
configs += [ "//build/config/compiler:exceptions" ]

# define include_dirs variable for C++ code
include_dirs = [
"src",
"deps/somesdk/src",
]
}

shared_library("login_sdk") {
# use login_sdk_config
configs += [ ":login_sdk_config" ]
# set other variables
sources = [
# ...
]
}

Integrate Android Library from Sources

Android Resources / Manifest

1
2
3
4
5
6
7
# target name should ends with `_resources`
android_resources("login_sdk_resources") {
sources = [
"src/res/layout/xxx.xml",
]
android_manifest = "src/AndroidManifest.xml"
}

Override Manifest minSdkVersion

If you need to override minSdkVersion of manifest in SDK, you can do it as follows.

1
2
3
4
<!-- chrome/android/java/AndroidManifest.xml -->
<manifest>
<uses-sdk tools:overrideLibrary="com.login.sdk"/>
</manifest>

R.java

R.java will be generated by the android_resources target.

BuildConfig.java

Normally, BuildConfig File is generated by gradle task. If build with GN, we can simply write a BuildConfig.java file by hand and put it into some directory.

Java code

we can use android_library to integrate Java source code of an Android Library.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# target name should ends with `_java`
android_library("login_sdk_java") {
sources = [
"src/java/com/login/Main.java",
"src/java/com/login/Login.java",
# java code depends on BuildConfig.java
"src/build/BuildConfig.java",
]
deps = [
"//third_party/android_deps:androidx_constraintlayout_constraintlayout_java",
":login_sdk_resources", # java code depends on R.java
]
# compile time deps needs to be set here, these jar file will not be compiled into the apk
input_jars_paths = [
"libs/compile_time_deps.jar",
]
}
1
2
3
4
5
6
7
8
# chrome/android/BUILD.gn

android_library("chrome_java") {
deps = [
# add deps into chrome project
"//third-party/android-sdks/login:login_sdk_java"
]
}

assets

1
2
3
4
5
android_assets("login_sdk_assets_java") {
sources = [
"src/assets/login.json",
]
}
1
2
3
4
5
6
7
8
# chrome/android/BUILD.gn

android_library("chrome_java") {
deps = [
# add deps into chrome project
"//third-party/android-sdks/login:login_sdk_assets_java",
]
}

AnnotationProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java_annotation_processor("login_processor") {
sources = [
# ...
]
deps = [
# ...
]
main_class = "com.login.LoginProcessor"
}

android_library("login_sdk_java") {
annotation_processor_deps = [
":login_processor",
]
}

AIDL

1
2
3
4
5
6
7
8
9
10
11
12
android_aidl("login_sdk_aidl") {
import_include = [ "src/java" ]
sources = [
"src/aidl/com/login/demo/LoginService.aidl",
]
}

# this target represent java files generated from aidl
# if java code needs to use them, should depends on this target
android_library("login_sdk_aidl_java") {
srcjar_deps = [ ":login_sdk_aidl" ]
}
1
2
3
4
5
6
7
8
9
# chrome/android/BUILD.gn

android_library("chrome_java") {
deps = [
# add deps into chrome project
"//third-party/android-sdks/login:login_sdk_asset_java",
"//third-party/android-sdks/login:login_sdk_aidl_java",
]
}

CPP Source Code

If CPP code needs to be integrated as source code into main project, you can use source_set or sources to add then into main project.

1
2
3
4
5
6
7
8
9
10
11
12
# chrome/browser/BUILD.gn

static_library("browser") {
sources += [
# add source file directly
"xxx.cc",
]
deps += [
# add deps of source_set
"//third_party/android_sdk/login:login_src"
]
}
1
2
3
4
5
6
7
8
9
10
# third_party/android_sdk/login/BUILD.gn
source_set(":login_src") {
sources = [
"src/xxx.cc",
"src/xxx.h"
]
include_dirs = [
"src"
]
}

SO File

If CPP code needs to be compiled into standalone shared library (.so file), then you need to see shared libary part for more details. Then use loadable_modules variable to integrate the so file into main project.

1
2
3
4
5
6
7
8
9
10
# chrome/android/BUILD.gn

template("chrome_public_apk_or_module_tmpl") {
chrome_public_common_apk_or_module_tmpl(target_name) {
# add loadable modules
loadable_modules += [
"$root_out_dir/libLogin.so"
]
}
}

Proguard

You can config proguard here: chrome/android/proguard/main.flags

Integrate Local JAR Files

If you have a local JAR file, you can use java_prebuilt rule to integrate it into project.

1
2
3
4
5
java_prebuilt("login_sdk_java") {
jar_path = "libs/login-sdk.jar"
output_name = "login_sdk"
supports_android = true
}
1
2
3
4
5
6
7
8
# chrome/android/BUILD.gn

android_library("chrome_java") {
deps = [
# add deps into chrome project
"//third-party/android-sdks/login:login_sdk_java"
]
}

Integrate Local AAR Files

If you have a local AAR file, you can use android_aar_prebuilt rule to integrate it into project.

1
2
3
4
5
6
7
8
9
10
11
# target name must ends with "_java"
android_aar_prebuilt("login_sdk_java") {
aar_path = "libs/login-sdk.aar"
# info file can be generated with aar.py
info_path = "libs/login-sdk.info"

# if aar depends on other libs, should be defined here
deps = [
"//third_party/android_deps:androidx_annotation_annotation_java",
]
}
1
2
3
4
5
6
7
8
# chrome/android/BUILD.gn

android_library("chrome_java") {
deps = [
# add deps into chrome project
"//third-party/android-sdks/login:login_sdk_java"
]
}

Info File for AAR

AAR file needs an info file which describe the contents in the AAR and it is used for speeding up the build. Info file can be generated by aar.py as follows.

1
python ./build/android/gyp/aar.py list input.aar --output out/output.info

The info file content is like follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
aidl = []
assets = []
has_classes_jar = true
has_native_libraries = true
has_proguard_flags = false
has_r_text_file = true
is_manifest_empty = false
native_libraries = [
"jni/arm64-v8a/libLogin.so",
"jni/armeabi-v7a/libLogin.so",
"jni/x86/libLogin.so",
]
resources = [
"res/drawable/logo.png",
"res/layout/some_layout.xml",
"res/values/values.xml",
]
subjar_tuples = []
subjars = []

Native Libraries (.so files)

If AAR contains so files, you must set one of ignore_native_libraries to ignore the native libraries or extract_native_libraries to use the native libraries and then use loadable_modules to integrate.

1
2
3
4
android_aar_prebuilt("login_sdk_java") {
# ignore_native_libraries = true
extract_native_libraries = true
}
1
2
3
4
5
6
7
8
9
10
# chrome/android/BUILD.gn

template("chrome_public_apk_or_module_tmpl") {
chrome_public_common_apk_or_module_tmpl(target_name) {
# add loadable modules
loadable_modules += [
"$root_out_dir/obj/third_party/android_sdk/login_sdk_aar_java/jni/x86/libLogin.so"
]
}
}

AIDL and Assets

android_aar_prebuilt currently not supports aidl and assets. If AAR contains these files, you must configure ignore_aidl = true and ignore_assets = true. If you need to add these files into project, you can extract the files and add them by other rules. See integrate android library from sources part for more detail.

1
2
3
4
android_aar_prebuilt("login_sdk_java") {
ignore_aidl = true
ignore_assets = true
}
1
2
3
4
5
android_assets("login_sdk_asset_java") {
sources = [
"login.json",
]
}
1
2
3
4
5
6
7
8
9
10
11
12
android_aidl("login_sdk_aidl") {
import_include = [ "java/src" ]
sources = [
"aidl/com/login/demo/LoginService.aidl",
]
}

# this target represent java files generated from aidl
# if java code needs to use them, should config deps on this target
android_library("login_sdk_aidl_java") {
srcjar_deps = [ ":login_sdk_aidl" ]
}
1
2
3
4
5
6
7
8
9
# chrome/android/BUILD.gn

android_library("chrome_java") {
deps = [
# add deps into chrome project
"//third-party/android-sdks/login:login_sdk_asset_java",
"//third-party/android-sdks/login:login_sdk_aidl_java",
]
}

Ignore/Replace Manifest in AAR

We can use ignore_manifest to exclude manifest from AAR. If you need to replace the manifest file, you need to create an android_resources target to specify the new manifest file and add it to the deps of android_aar_prebuilt target.

1
2
3
4
5
6
7
8
9
10
11
12
android_aar_prebuilt("login_sdk") {
ignore_manifest = true
deps = [
":login_sdk_manifest_resources"
]
}

# specify the new manifest resources target
android_resources("login_sdk_manifest_resources") {
sources = [] # sources remain empty
android_manifest = "build/AndroidManifest.xml"
}

Integrate Maven Project with Gradle

If you want to add maven dependencies for Android, a simple solution is to use android_deps module provided by Chromium instead of downloading AAR/JAR and writing GN manually. This tool can resolve all the transitive dependencies and download all the AAR/JAR, generate info file, generate GN script and integrate them into project.

Here is the steps:

  • Add your repositories and dependencies in third_party/android_deps/build.gradle, just like the normal Gradle project does. If your package is used for building, use buildCompile. If your package needs to be compiled into Android APK, use compile.
  • Run third_party/android_deps/fetch_all.py, this script will run Gradle first to resolve dependencies and then do the remaining works.
  • The script will create cpid package for each AAR/JAR file, and the packages will be managed by gclient. Run the commands printed by fetch_all.py to create new and updated packages via cpid.
  • The generated GN targets is in third_party/android_deps/BUILD.gn. You can use them in other GN scripts.

See more details here:

The java_library rule is defined in rules.gni can it called java_library_impl defined in internal_rules.gni.

The java_prebuilt rule called java_library_impl, too.

The android_library rule called java_library, and the android_aar_prebuilt called java_prebuilt.

We can see more details about the variables of these rules in the source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# build/config/android/rules.gni

template("java_library") {
java_library_impl(target_name) {
forward_variables_from(invoker, "*")
type = "java_library"
}
}

template("java_prebuilt") {
java_library_impl(target_name) {
forward_variables_from(invoker, "*")
type = "java_library"
}
}

template("android_library") {
java_library(target_name) {
forward_variables_from(invoker, "*")

supports_android = true
requires_android = true

if (!defined(jar_excluded_patterns)) {
jar_excluded_patterns = []
}
jar_excluded_patterns += [
"*/R.class",
"*/R\$*.class",
"*/Manifest.class",
"*/Manifest\$*.class",
"*/GEN_JNI.class",
]
}
}

template("android_aar_prebuilt") {
# ...
java_prebuilt(_jar_target_name) {
forward_variables_from(invoker, _java_library_vars)
forward_variables_from(invoker, [
"deps",
"input_jars_paths",
"proguard_configs",
])
# ...
}
# ...
}
1
2
3
4
5
# build/config/android/internal_rules.gni

template("java_library_impl") {
# ...
}

Here is some common and useful variables:

supports_android: True if target can run on Android. This value is true by default in android_library rule. If an target supports Android, then all its deps need to support Android.

require_android: True if target can only run on Android. It also means the target will depends on Android Java SDK. This value is true by default in android_library rule.

jar_excluded_patterns: We can use this variable to exclude/replace some Java class from an JAR/AAR file. For example:

1
2
3
4
jar_excluded_patterns = [
"android/support/v4/graphics/drawable/IconCompat.class",
"androidx/*",
]

enable_bytecode_checks: By default, GN will run bytecode checks for prebuilt JAR/AAR to ensure Java class dependencies is OK. If class A in target T1 dependent on class B in target T2, then target T1 should dependent on T2. Sometimes we may want to disable bytecode checks. See trouble shotting part for more details.

Build C++ Code into Shared Library

A shared library is a binary file (dll for windows, so for linux) which can be loaded in the runtime. The following demo is on linux platform.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
shared_library("login_sdk_so") {
# output file will be libLogin.so
output_name = "Login"
sources = [
"src/main.hpp",
"src/main.cpp",
]
include_dirs = [
"src",
"deps/somesdk/src",
]
libs = [
"android",
"log",
]
deps = [
":other_sdk",
]
}

Defines for C++ code

We can use defines to define some macro for C++ code.

1
2
3
4
5
6
shared_library("login_sdk_so") {
defines = [
"TARGET_IS_ANDROID",
"LOGIN_SDK_VERSION=\"1.0.0\"",
]
}
1
2
3
4
5
6
7
8
9
// C++ code

#ifdef TARGET_IS_ANDROID
// ...
#endif

#ifdef LOGIN_SDK_VERSION
std:cout << "Version = " << LOGIN_SDK_VERSION << std:endl;
#endif

Define exported symbols

Shared library binary file needs to define what symbols to be exported. Only exported symbols can be accessed from outside.

In linux, we can use an version script file to define exported C++ symbols for so file.

https://www.gnu.org/software/gnulib/manual/html_node/Exported-Symbols-of-Shared-Libraries.html

https://www.gnu.org/software/gnulib/manual/html_node/LD-Version-Scripts.html

1
2
3
4
5
6
7
8
# version script file: login_so.lst
{
global:
# exports some method
SomeStaticMethod;
local:
*;
};
1
2
3
4
5
6
7
8
# BUILD.gn
shared_library(...) {
# flags for ld
ldflags = [
# specify version script files
"-Wl,--version-script=" + rebase_path("login_so.lst", root_build_dir),
]
}
1
2
3
4
5
6
7
8
// C++ code
#define EXPORTS __attribute__((visibility("default")))

// use extern C to prevent name mangling
// https://stackoverflow.com/questions/2587613/what-is-the-effect-of-declaring-extern-c-in-the-header-to-a-c-shared-libra
extern "C" {
EXPORTS void SomeStaticMethod();
}

We can use nm command to view exported symbols of so file.

1
2
3
4
5
6
7
8
# view exported symbols for so.
# U means the symbol should be loaded from outside.
# T means the symbol is in the so and can be accessed from outside.
> nm -CD somelib.so
U abort
U __android_log_write
00318a40 T Java_com_demo_login_native_onFailure
00318821 T Java_com_demo_login_native_onSuccess

JNI Config for so File

See JNI part for more details.

JNI

If a so file has JNI calls, JNI related symbols must be exported, and Java code should call loadLibrary to load the so. See Shared Library part to known more about export symbols for so file.

1
2
3
4
5
6
7
8
9
10
# login_so.lst
{
global:
# export all jni related functions
JNI_OnLoad;
JNI_OnUnload;
Java_*;
local:
*;
};
1
2
3
4
5
6
7
// Main.java
// will load libLogin.so file
public class Main {
static {
System.loadLibrary("Login");
}
}

Java Calls C++

You can use native keywords to call C++ code in Java.

1
2
3
4
5
package com.login.main;

public class Account {
public native int getUserId(String username);
}

When running to this native method call, JVM will find the corresponding symbols from the loaded so files. The C++ code is like follows. If the native method is non-static, the parameter of obj will be this object from Java. If the native method is static, then the parameter of obj will be a reference to containg class instead.

Reference: https://stackoverflow.com/questions/15253914/are-native-java-methods-equivalent-to-static-java-methods

1
2
3
4
5
#include <jni.h> // jni.h is defined in android ndk

extern "C" {
JNIEXPORT jint com_login_main_Account_getUserId(JNIEnv* env, jobject obj, jstring username);
}

C++ Calls Java

C++ calls Java code is bit like using reflection to call Java code.

When using JNI, most of the data type conversion job is done in the C++ code. jni.h file has provide many definition of Java basic data types and conversion functions.

Here is a very simple demo of calling android log method from C++.

  • Find the Java class with full class name.
  • Get the static method with its name and class signature.
  • Convert C++ string of char* type into jstring type as arguments.
  • Call static Java method with arguments.
  • Get jint result returned by Java code.
  • Covert the result from Java type to C++ type and return the result.

Notes: Don’t forget to config proguard to avoid code obfuscation of the Java class.

1
2
3
4
5
6
package android.util;
public class Log {
public static int i(String tag, String message) {
// ...
}
}
1
2
3
4
5
6
7
8
9
10
11
#include <jni.h>

// call static Java method of android.util.Log.i(String tag, String message)
int log(JNIEnv* env, const char* message) {
jclass cls = env->FindClass("android/util/Log");
jmethodID method = env->GetStaticMethodID(cls, "i", "(Ljava/lang/String;Ljava/lang/String;)I");
jstring tag = env->NewStringUTF("Login");
jstring msg = env->NewStringUTF(message);
jint result = env->CallStaticIntMethod(cls, method, tag, msg);
return (int) result;
}

Reference:

https://stackoverflow.com/questions/9909228/what-does-v-mean-in-a-class-signature

https://docs.oracle.com/javase/6/docs/technotes/guides/jni/

JNIEnv

You may noticed that a JNIEnv type of variable is always needed when C++ make any Java related operations. If C++ is called by Java, the method can receive this env arguments. But what if C++ code whats to call Java but no env arguments are passed here ?

Here is a simple solution:

  • When JNI_OnLoad is called, save the JavaVM reference, and clear it when JNI_OnUnload is called.
  • When a env arguments is needed, call AttachCurrentThread to get it.
  • In fact, this is already implemented in Chromium project in base/android/jni_android.cc file. But if you have an standalone so file, you cannot access the code from other so directly. So you can implement it for you own so.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <sys/prctl.h>
#include <jni.h>

// declare an anonymous namespace to avoid naming conflict
namespace {
JavaVM *g_jvm = nullptr;
}

extern "C" {
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
g_jvm = vm;
return JNI_VERSION_1_4;
}

JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) {
g_jvm = nullptr;
}
}

namespace LoginJNI {

// copied from base/android/jni_android.cc
JNIEnv* AttachCurrentThread() {
JNIEnv* env = nullptr;
jint ret = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_4);
if (ret == JNI_EDETACHED || !env) {
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_4;
args.group = nullptr;

// 16 is the maximum size for thread names on Android.
char thread_name[16];
int err = prctl(PR_GET_NAME, thread_name);
if (err < 0) {
// DPLOG(ERROR) << "prctl(PR_GET_NAME)";
args.name = nullptr;
} else {
args.name = thread_name;
}

ret = g_jvm->AttachCurrentThread(&env, &args);
// CHECK_EQ(JNI_OK, ret);
}
return env;
}

} // namespace LoginJNI

Djinni

Djinni is a tool for generating cross-language type declarations and interface bindings. It’s designed to connect C++ with either Java or Objective-C.

You may noticed that if you use JNI directly without any auxiliary tools, C++ code of data type conversion and method call will be very complicated. One option is to use Djinni to help us generate these code. Of course, Djinni is not designed only to solve this problem.

Djinni’s support lib has many data type conversion utils, you can see it here:

https://github.com/dropbox/djinni/blob/master/support-lib/jni/Marshal.hpp

JNI in Chromium

In Chromium project, JNI binding can be generated automatically with jni_generator.py configured in GN.

Here is a simple demo of using JNI in Chromium.

Java Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// chrome/android/java/src/com/login/LoginUtils.java

package com.login;

public class LoginUtils {

void someMethod() {
// LoginUtilsJni.java will be generated automatically
LoginUtilsJni.get().load();
}

@NativeMethods
interface Natives {
void load();
}
}

C++ Code:

1
2
3
4
5
6
7
8
9
10
// componets/login/android/login_utils.cc

#include <jni.h>

// LoginUtils_jni.h will be generated automatically
#include "chrome/android/chrome_jni_headers/LoginUtils_jni.h"

void JNI_LoginUtils_Load(JNIEnv* env) {
// implement native function here...
}

GN Config:

1
2
3
4
5
6
7
# chrome/android/BUILD.gn

generate_jni("chrome_jni_headers") {
sources = [
"java/src/com/login/LoginUtils.java",
]
}
1
2
3
4
5
# chrome/android/chrome_java_sources.gni

chrome_java_sources = [
"java/src/com/login/LoginUtils.java",
]

You can see some more details here.

https://chromium.googlesource.com/chromium/src/+/master/base/android/jni_generator/README.md

Trouble Shotting

Type xxx is defined multiple times

some Java class is duplicated, normally because some package is defined repeatedly through more than one targets.

1
Type android.support.customtabs.ICustomTabsCallback is defined multiple times

Errors from jni_generator.py

Errors comes from base/android/jni_generator/jni_generator.py.

1
2
Inner class (%s) can not be imported and used by JNI (%s). Please import the outer class and use Outer.Inner instead.
Inner class (%s) can not be used directly by JNI. Please import the outer class, probably: import %s.%s;

By default, target of chrome_public_apk will generate JNI binding for all Java source code if found keywords native. But it has some issue currently. Some style of Java code is not supported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// use full class name as native method params or return type without import it,
// will recogonize it as an inner class com.xxx.java.util.UUID.
// SyntaxError: Inner class (java.util.UUID) can not be used directly by JNI.
// Please import the outer class, probably:
// import com.xxx.java.util.UUID
package com.xxx;
class XXX {
native void someMethod(java.util.UUID uuid);
}

// use same name as the java.lang package,
// this type will be recognized as java.lang.InternalError.
// Ambiguous class (%s) can not be used directly by JNI.
// Please import it, probably:
// import java.lang.InternalError;
class XXX {
class InternalError {
}
native void someMethod(InternalError error);
}

If the code needs JNI binding generation, you can rewrite the code to make it works. If the code does not need JNI binding generation, you can exclude them from JNI sources.

1
2
3
4
5
6
7
8
9
10
11
12
# chrome/android/BUILD.gn

chrome_jni_sources_exclusions = []
chrome_jni_sources_exclusions += [
"//third_party/android_sdks/login/com/main/Main.java",
]

template("chrome_public_apk_or_module_tmpl") {
chrome_public_common_apk_or_module_tmpl(target_name) {
jni_sources_exclusions = chrome_jni_sources_exclusions
}
}

missing and no known rule to make it

You need to check if the missing files exists.

1
ninja: error: xxx.aar/java/cpp, needed by xxx, missing and no known rule to make it.

Not all deps support the Android platform

1
Exception: Not all deps support the Android platform: [u'com_xxx_java.build_config']
1
2
3
4
5
6
7
8
9
java_prebuilt("com_xxx_java") {
deps = [
":com_xxx_dep_java",
]
}
java_prebuilt("com_xxx_dep_java") {
# set supports_android as true
supports_android = true
}

Dependency Checks Failed

Normally, GN will run byte code checks for prebuilt jar/aar to ensure Java class dependencies is OK. If class A in target T1 dependent on class B in target T2, then target T1 should dependent on T2.

1
2
3
Target: //third_party/android_deps:com_google_zxing_android_core_java
Class "android/graphics/Point" not found on any classpath. Used by class "com/google/zxing/client/android/camera/CameraConfigurationUtils"
...

If missing class is from Android SDK, you need to set requires_android to true for the target.

1
2
3
java_prebuilt("com_google_zxing_android_core_java") {
requires_android = true
}

If dependent class if from other target (maven package / local Java library / sources ), normally you need to add deps on the target.

1
2
3
4
5
java_prebuilt("target_a_java") {
deps = [
":target_b_java",
]
}

Sometimes a maven package can have optional dependencies of other packages, and code in these dependencies will only be called when running specified code logic. In this scenario, you may want to disable byte code checks. This can lead to risks of Runtime Error, so use this option only if you are pretty sure the class in the optional packages will not be called.

1
2
3
4
java_prebuilt("xxx_java") {
# some deps are optional, skip bytecode checks
enable_bytecode_checks = false
}

C++ Build Issue

You may encounter some C++ build issue. For example, header file not found. Normally it is because of wrong include_dirs setting.

1
2
../../third_party/xxx.h:7:10: fatal error: 'xxx.hpp' file not found
#include "xxx.hpp"

You can use gn desc to view all the variables for your target, including sources, configs, include_dirs, cflags, defines, ldflags, etc. Here is an incomplete output example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Target //third_party/xxx:xxx
type: source_set
toolchain: //build/toolchain/android:android_clang_x86

visibility
*

metadata
{

}

testonly
false

check_includes
true

allow_circular_includes_from

sources
//third_party/xxx.h
//third_party/xxx.cpp

configs (in order applying, try also --tree)
//build/config:feature_flags
//build/config/compiler:afdo
//build/config/compiler:afdo_optimize_size
//build/config/compiler:compiler
//build/config/compiler:compiler_arm_fpu
//build/config/compiler:compiler_arm_thumb
//build/config/compiler:default_include_dirs

arflags
-T

asmflags
-fPIC
-fno-strict-aliasing
--param=ssp-buffer-size=4
-fno-stack-protector
-funwind-tables
-fPIC

cflags
-fno-strict-aliasing
--param=ssp-buffer-size=4
-fno-stack-protector
-funwind-tables
-fPIC
-fcolor-diagnostics
-fmerge-all-constants
-fcrash-diagnostics-dir=../../tools/clang/crashreports
-Xclang

cflags_c
-std=c11
--sysroot=../../third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot
-Wno-implicit-fallthrough

cflags_cc
-isystem../../buildtools/third_party/libc++/trunk/include
-isystem../../buildtools/third_party/libc++abi/trunk/include
--sysroot=../../third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/sysroot
-fvisibility-inlines-hidden
-Xclang
-std=c++17
-fexceptions
-frtti

defines
ANDROID

include_dirs
//third_party/xxx

ldflags
-Wl,--fatal-warnings
-Wl,--build-id
-fPIC
-Wl,-z,noexecstack

Direct dependencies (try also "--all", "--tree", or even "--all --tree")

libs
android_support

externs