Modules: Add initial native Chrome code docs
Change-Id: Ifd1be95869fa4b3c8bea29fff2e07671abe82e7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1725189
Commit-Queue: Christopher Grant <[email protected]>
Reviewed-by: Tibor Goldschwendt <[email protected]>
Reviewed-by: Andrew Grieve <[email protected]>
Cr-Commit-Position: refs/heads/master@{#682858}
diff --git a/docs/android_dynamic_feature_modules.md b/docs/android_dynamic_feature_modules.md
index 18faaf01..494a2b0 100644
--- a/docs/android_dynamic_feature_modules.md
+++ b/docs/android_dynamic_feature_modules.md
@@ -21,9 +21,6 @@
you can add your feature to the Android K APK build. See
[crbug/881354](https://bugs.chromium.org/p/chromium/issues/detail?id=881354)
for progress.
-* **Native Code:** We cannot move native Chrome code into a DFM. See
- [crbug/874564](https://bugs.chromium.org/p/chromium/issues/detail?id=874564)
- for progress.
## Getting started
@@ -316,15 +313,11 @@
module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
logcat. Yay!
+### Adding third-party native code
-### Adding native code
-
-Coming soon (
-[crbug/874564](https://bugs.chromium.org/p/chromium/issues/detail?id=874564)).
-
-You can already add third party native code or native Chrome code that has no
-dependency on other Chrome code. To add such code add it as a loadable module to
-the module descriptor in `//chrome/android/features/foo/foo_module.gni`:
+You can add a third-party native library (or any standalone library that doesn't
+depend on Chrome code) by adding it as a loadable module to the module descriptor in
+`//chrome/android/features/foo/foo_module.gni`:
```gn
foo_module_desc = {
@@ -334,8 +327,205 @@
}
```
+### Adding Chrome native code
-### Adding android resources
+Chrome native code may be placed in a DFM.
+
+A linker-assisted partitioning system automates the placement of code into
+either the main Chrome library or feature-specific .so libraries. Feature code
+may continue to make use of core Chrome code (eg. base::) without modification,
+but Chrome must call feature code through a virtual interface.
+
+Partitioning is explained in [Android Native
+Libraries](android_native_libraries.md#partitioned-libraries).
+
+#### Creating an interface to feature code
+
+One way of creating an interface to a feature library is through an interface
+definition. Feature Foo could define the following in
+`//chrome/android/features/foo/public/foo_interface.h`:
+
+```c++
+class FooInterface {
+ public:
+ virtual ~FooInterface() = default;
+
+ virtual void ProcessInput(const std::string& input) = 0;
+}
+```
+
+Alongside the interface definition, also in
+`//chrome/android/features/foo/public/foo_interface.h`, it's helpful to define a
+factory function type that can be used to create a Foo instance:
+
+```c++
+typedef FooInterface* CreateFooFunction(bool arg1, bool arg2);
+```
+
+<!--- TODO(cjgrant): Add a full, pastable Foo implementation. -->
+The feature library implements class Foo, hiding its implementation within the
+library. The library may then expose a single entrypoint, a Foo factory
+function. Here, C naming is (optionally) used so that the entrypoint symbol
+isn't mangled. In `//chrome/android/features/foo/internal/foo.cc`:
+
+```c++
+extern "C" {
+// This symbol is retrieved from the Foo feature module library via dlsym(),
+// where it's bare address is type-cast to its actual type and executed.
+// The forward declaration here ensures that CreateFoo()'s signature is correct.
+CreateFooFunction CreateFoo;
+
+__attribute__((visibility("default"))) FooInterface* CreateFoo(
+ bool arg1, bool arg2) {
+ return new Foo(arg1, arg2);
+}
+} // extern "C"
+```
+
+Ideally, the interface to the feature will avoid feature-specific types. If a
+feature defines complex data types, and uses them in its own interface, then its
+likely the main library will utilize the code backing these types. That code,
+and anything it references, will in turn be pulled back into the main library.
+
+Therefore, designing the feature inferface to use C types, C++ standard types,
+or classes that aren't expected to move out of Chrome's main library is ideal.
+If feature-specific classes are needed, they simply need to avoid referencing
+feature library internals.
+
+*** note
+**Note:** To help enforce separation between the feature interface and
+implementation, the interface class is best placed in its own GN target, on
+which the feature and main library code both depend.
+***
+
+#### Marking feature entrypoints
+
+Foo's feature module descriptor needs to pull in the appropriate native GN code
+dependencies, and also indicate the name of the file that lists the entrypoint
+symbols. In `//chrome/android/features/foo/foo_module.gni`:
+
+```gn
+foo_module_desc = {
+ ...
+ native_deps = [ "//chrome/android/features/foo/internal:foo" ]
+ native_entrypoints = "//chrome/android/features/foo/internal/module_entrypoints.lst"
+}
+```
+
+The module entrypoint file is a text file listing symbols. In this example,
+`//chrome/android/features/foo/internal/module_entrypoints.lst` has only a
+single factory function exposed:
+
+```shell
+# This file lists entrypoints exported from the Foo native feature library.
+
+CreateFoo
+```
+
+These symbols will be pulled into a version script for the linker, indicating
+that they should be exported in the dynamic symbol table of the feature library.
+
+*** note
+**Note:** If C++ symbol names are chosen as entrypoints, the full mangled names
+must be listed.
+***
+
+Additionally, it's necessary to map entrypoints to a particular partition. To
+follow compiler/linker convention, this is done at the compiler stage. A cflag
+is applied to source file(s) that may supply entrypoints (it's okay to apply the
+flag to all feature source - the attribute is utilized only on modules that
+export symbols). In `//chrome/android/features/foo/internal/BUILD.gn`:
+
+```gn
+static_library("foo") {
+ sources = [
+ ...
+ ]
+
+ # Mark symbols in this target as belonging to the Foo library partition. Only
+ # exported symbols (entrypoints) are affected, and only if this build supports
+ # native modules.
+ if (use_native_modules) {
+ cflags = [ "-fsymbol-partition=libfoo.so" ]
+ }
+}
+```
+
+Feature code is free to use any existing Chrome code (eg. logging, base::,
+skia::, cc::, etc), as well as other feature targets. From a GN build config
+perspective, the dependencies are defined as they normally would. The
+partitioning operation works independently of GN's dependency tree.
+
+```gn
+static_library("foo") {
+ ...
+
+ # It's fine to depend on base:: and other Chrome code.
+ deps = [
+ "//base",
+ "//cc/animation",
+ ...
+ ]
+
+ # Also fine to depend on other feature sub-targets.
+ deps += [
+ ":some_other_foo_target"
+ ]
+
+ # And fine to depend on the interface.
+ deps += [
+ ":foo_interface"
+ ]
+}
+```
+
+#### Opening the feature library
+
+Now, code in the main library can open the feature library and create an
+instance of feature Foo. Note that in this example, no care is taken to scope
+the lifetime of the opened library. Depending on the feature, it may be
+preferable to open and close the library as a feature is used.
+`//chrome/android/features/foo/factory/foo_factory.cc` may contain this:
+
+```c++
+std::unique_ptr<FooInterface> FooFactory(bool arg1, bool arg2) {
+ // Open the feature library, using the partition library helper to map it into
+ // the correct memory location. Specifying partition name *foo* will open
+ // libfoo.so.
+ void* foo_library_handle =
+ base::android::BundleUtils::DlOpenModuleLibraryPartition("foo");
+ }
+ DCHECK(foo_library_handle != nullptr) << "Could not open foo library:"
+ << dlerror();
+
+ // Pull the Foo factory function out of the library. The function name isn't
+ // mangled because it was extern "C".
+ CreateFooFunction* create_foo = reinterpret_cast<CreateFooFunction*>(
+ dlsym(foo_library_handle, "CreateFoo"));
+ DCHECK(create_foo != nullptr);
+
+ // Make and return a Foo!
+ return base::WrapUnique(create_foo(arg1, arg2));
+}
+
+```
+
+*** note
+**Note:** Component builds do not support partitioned libraries (code splitting
+happens across component boundaries instead). As such, an alternate, simplified
+feature factory implementation must be supplied (either by linking in a
+different factory source file, or using #defines in the factory) that simply
+instantiates a Foo object directly.
+***
+
+Finally, the main library is free to utilize Foo:
+
+```c++
+ auto foo = FooFactory::Create(arg1, arg2);
+ foo->ProcessInput(const std::string& input);
+```
+
+### Adding Android resources
In this section we will add the required build targets to add Android resources
to the Foo DFM.