blob: a606196ba8c6e0f4d2268147553b2af1b4224932 [file] [log] [blame] [view]
Nate Fischerac07b2622020-10-01 20:20:141# Accessing C++ Features In Java
2
3[TOC]
4
5## Introduction
6
7Accessing C++ `base::Features` in Java is implemented via a Python script which
8analyzes the `*_features.cc` file and generates the corresponding Java class,
9based on a template file. The template file must be specified in the GN target.
10This outputs Java String constants which represent the name of the
11`base::Feature`.
12
13## Usage
14
151. Create a template file (ex. `FooFeatures.java.tmpl`). Change "Copyright
16 2020" to be whatever the year is at the time of writing (as you would for any
17 other file).
18 ```java
19 // Copyright 2020 The Chromium Authors. All rights reserved.
20 // Use of this source code is governed by a BSD-style license that can be
21 // found in the LICENSE file.
22
23 package org.chromium.foo;
24
25 // Be sure to escape any curly braces in your template by doubling as
26 // follows.
27 /**
28 * Contains features that are specific to the foo project.
29 */
30 public final class FooFeatures {{
31
32 {NATIVE_FEATURES}
33
34 // Prevents instantiation.
35 private FooFeatures() {{}}
36 }}
37 ```
38
392. Add a new build target and add it to the `srcjar_deps` of an
40 `android_library` target:
41
42 ```gn
43 if (is_android) {
44 import("//build/config/android/rules.gni")
45 }
46
47 if (is_android) {
48 java_cpp_features("java_features_srcjar") {
49 # External code should depend on ":foo_java" instead.
50 visibility = [ ":*" ]
51 sources = [
52 "//base/android/foo_features.cc",
53 ]
54 template = "//base/android/java_templates/FooFeatures.java.tmpl"
55 }
56
57 # If there's already an android_library target, you can add
58 # java_features_srcjar to that target's srcjar_deps. Otherwise, the best
59 # practice is to create a new android_library just for this target.
60 android_library("foo_java") {
61 srcjar_deps = [ ":java_features_srcjar" ]
62 }
63 }
64 ```
65
663. The generated file `out/Default/gen/.../org/chromium/foo/FooFeatures.java`
67 would contain:
68
69 ```java
70 // Copyright $YEAR The Chromium Authors. All rights reserved.
71 // Use of this source code is governed by a BSD-style license that can be
72 // found in the LICENSE file.
73
74 package org.chromium.foo;
75
76 // Be sure to escape any curly braces in your template by doubling as
77 // follows.
78 /**
79 * Contains features that are specific to the foo project.
80 */
81 public final class FooFeatures {
82
83 // This following string constants were inserted by
84 // java_cpp_features.py
85 // From
86 // ../../base/android/foo_features.cc
87 // Into
88 // ../../base/android/java_templates/FooFeatures.java.tmpl
89
90 // Documentation for the C++ Feature is copied here.
91 public static final String SOME_FEATURE = "SomeFeature";
92
93 // ...snip...
94
95 // Prevents instantiation.
96 private FooFeatures() {}
97 }
98 ```
99
100### Troubleshooting
101
102The script only supports limited syntaxes for declaring C++ base::Features. You
103may see an error like the following during compilation:
104
105```
106...
107org/chromium/foo/FooFeatures.java:41: error: duplicate declaration of field: MY_FEATURE
108 public static final String MY_FEATURE = "MyFeature";
109```
110
111This can happen if you've re-declared a feature for mutually-exclsuive build
112configs (ex. the feature is enabled-by-default for one config, but
113disabled-by-default for another). Example:
114
115```c++
116#if defined(...)
117const base::Feature kMyFeature{
118 "MyFeature", base::FEATURE_ENABLED_BY_DEFAULT};
119#else
120const base::Feature kMyFeature{
121 "MyFeature", base::FEATURE_DISABLED_BY_DEFAULT};
122#endif
123```
124
125The `java_cpp_features` rule doesn't know how to evaluate C++ preprocessor
126directives, so it generates two identical Java fields (which is what the
127compilation error is complaining about). Fortunately, the workaround is fairly
128simple. Rewrite the definition to only use directives around the enabled state:
129
130```c++
131const base::Feature kMyFeature{
132 "MyFeature",
133#if defined(...)
134 base::FEATURE_ENABLED_BY_DEFAULT
135#else
136 base::FEATURE_DISABLED_BY_DEFAULT
137#endif
138};
139
140```
141
142## Checking if a Feature is enabled
143
144The standard pattern is to create a `FooFeatureList.java` class with an
145`isEnabled()` method (ex.
146[`ContentFeatureList`](/content/public/android/java/src/org/chromium/content_public/browser/ContentFeatureList.java)).
147This should call into C++ (ex.
148[`content_feature_list`](/content/browser/android/content_feature_list.cc)),
149where a subset of features are exposed via the `kFeaturesExposedToJava` array.
150You can either add your `base::Feature` to an existing `feature_list` or create
151a new `FeatureList` class if no existing one is suitable. Then you can check the
152enabled state like so:
153
154```java
155// It's OK if ContentFeatureList checks FooFeatures.*, so long as
156// content_feature_list.cc exposes `kMyFeature`.
157if (ContentFeatureList.isEnabled(FooFeatures.MY_FEATURE)) {
158 // ...
159}
160```
161
162At the moment, `base::Features` must be explicitly exposed to Java this way, in
163whichever layer needs to access their state. See https://crbug.com/1060097.
164
165## See also
166* [Accessing C++ Enums In Java](android_accessing_cpp_enums_in_java.md)
167* [Accessing C++ Switches In Java](android_accessing_cpp_switches_in_java.md)
168
169## Code
170* [Generator code](/build/android/gyp/java_cpp_features.py) and
171 [Tests](/build/android/gyp/java_cpp_features_tests.py)
172* [GN template](/build/config/android/rules.gni)