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 | # build/config/android/rules.gni |
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 | # chrome/android/BUILD.gn |
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 | # somedir/BUILD.gn |
1 | # somedir/somesubdir/BUILD.gn |
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 | # configure args declared by the build (args.gn file) |
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 | # BUILD.gn |
Using Config
config
can define variables for other targets, and we can use +=
-=
for configs. For example:
1 | config("cpp17") { |
Integrate Android Library from Sources
Android Resources / Manifest
1 | # target name should ends with `_resources` |
Override Manifest minSdkVersion
If you need to override minSdkVersion
of manifest in SDK, you can do it as follows.
1 | <!-- chrome/android/java/AndroidManifest.xml --> |
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 | # target name should ends with `_java` |
1 | # chrome/android/BUILD.gn |
assets
1 | android_assets("login_sdk_assets_java") { |
1 | # chrome/android/BUILD.gn |
AnnotationProcessor
1 | java_annotation_processor("login_processor") { |
AIDL
1 | android_aidl("login_sdk_aidl") { |
1 | # chrome/android/BUILD.gn |
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 | # chrome/browser/BUILD.gn |
1 | # third_party/android_sdk/login/BUILD.gn |
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 | # chrome/android/BUILD.gn |
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 | java_prebuilt("login_sdk_java") { |
1 | # chrome/android/BUILD.gn |
Integrate Local AAR Files
If you have a local AAR file, you can use android_aar_prebuilt
rule to integrate it into project.
1 | # target name must ends with "_java" |
1 | # chrome/android/BUILD.gn |
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 | aidl = [] |
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 | android_aar_prebuilt("login_sdk_java") { |
1 | # chrome/android/BUILD.gn |
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 | android_aar_prebuilt("login_sdk_java") { |
1 | android_assets("login_sdk_asset_java") { |
1 | android_aidl("login_sdk_aidl") { |
1 | # chrome/android/BUILD.gn |
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 | android_aar_prebuilt("login_sdk") { |
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, usebuildCompile
. If your package needs to be compiled into Android APK, usecompile
. - 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:
- https://chromium.googlesource.com/chromium/src.git/+/master/third_party/android_deps/
- https://chromium.googlesource.com/chromium/src.git/+/master/docs/cipd.md
More Details of Java Related Rules
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 | # build/config/android/rules.gni |
1 | # build/config/android/internal_rules.gni |
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 | jar_excluded_patterns = [ |
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 | shared_library("login_sdk_so") { |
Defines for C++ code
We can use defines
to define some macro for C++ code.
1 | shared_library("login_sdk_so") { |
1 | // C++ code |
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 | # version script file: login_so.lst |
1 | # BUILD.gn |
1 | // C++ code |
We can use nm
command to view exported symbols of so file.
1 | # view exported symbols for so. |
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 | # login_so.lst |
1 | // Main.java |
Java Calls C++
You can use native
keywords to call C++ code in Java.
1 | package com.login.main; |
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.
1 |
|
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 intojstring
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 | package android.util; |
1 |
|
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 |
|
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 | // chrome/android/java/src/com/login/LoginUtils.java |
C++ Code:
1 | // componets/login/android/login_utils.cc |
GN Config:
1 | # chrome/android/BUILD.gn |
1 | # chrome/android/chrome_java_sources.gni |
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 | Inner class (%s) can not be imported and used by JNI (%s). Please import the outer class and use Outer.Inner instead. |
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 | // use full class name as native method params or return type without import it, |
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 | # chrome/android/BUILD.gn |
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 | java_prebuilt("com_xxx_java") { |
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 | Target: //third_party/android_deps:com_google_zxing_android_core_java |
If missing class is from Android SDK, you need to set requires_android
to true for the target.
1 | java_prebuilt("com_google_zxing_android_core_java") { |
If dependent class if from other target (maven package / local Java library / sources ), normally you need to add deps on the target.
1 | java_prebuilt("target_a_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 | java_prebuilt("xxx_java") { |
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 | ../../third_party/xxx.h:7:10: fatal error: 'xxx.hpp' file not found |
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 | Target //third_party/xxx:xxx |