Merge "aconfig: cache flag value in generated java code" into main
This commit is contained in:
commit
da88660301
6 changed files with 172 additions and 79 deletions
|
@ -26,4 +26,14 @@ public class DeviceConfig {
|
||||||
public static boolean getBoolean(String ns, String name, boolean def) {
|
public static boolean getBoolean(String ns, String name, boolean def) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Properties getProperties(String namespace, String... names) {
|
||||||
|
return new Properties();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Properties {
|
||||||
|
public boolean getBoolean(String name, boolean def) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tinytemplate::TinyTemplate;
|
use tinytemplate::TinyTemplate;
|
||||||
|
|
||||||
|
@ -31,12 +32,19 @@ pub fn generate_java_code<'a, I>(
|
||||||
where
|
where
|
||||||
I: Iterator<Item = &'a ProtoParsedFlag>,
|
I: Iterator<Item = &'a ProtoParsedFlag>,
|
||||||
{
|
{
|
||||||
let class_elements: Vec<ClassElement> =
|
let flag_elements: Vec<FlagElement> =
|
||||||
parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
|
parsed_flags_iter.map(|pf| create_flag_element(package, pf)).collect();
|
||||||
let is_read_write = class_elements.iter().any(|elem| elem.is_read_write);
|
let properties_set: BTreeSet<String> =
|
||||||
|
flag_elements.iter().map(|fe| format_property_name(&fe.device_config_namespace)).collect();
|
||||||
|
let is_read_write = flag_elements.iter().any(|elem| elem.is_read_write);
|
||||||
let is_test_mode = codegen_mode == CodegenMode::Test;
|
let is_test_mode = codegen_mode == CodegenMode::Test;
|
||||||
let context =
|
let context = Context {
|
||||||
Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() };
|
flag_elements,
|
||||||
|
is_test_mode,
|
||||||
|
is_read_write,
|
||||||
|
properties_set,
|
||||||
|
package_name: package.to_string(),
|
||||||
|
};
|
||||||
let mut template = TinyTemplate::new();
|
let mut template = TinyTemplate::new();
|
||||||
template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
|
template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
|
||||||
template.add_template(
|
template.add_template(
|
||||||
|
@ -66,39 +74,46 @@ where
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Context {
|
struct Context {
|
||||||
pub class_elements: Vec<ClassElement>,
|
pub flag_elements: Vec<FlagElement>,
|
||||||
pub is_test_mode: bool,
|
pub is_test_mode: bool,
|
||||||
pub is_read_write: bool,
|
pub is_read_write: bool,
|
||||||
|
pub properties_set: BTreeSet<String>,
|
||||||
pub package_name: String,
|
pub package_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct ClassElement {
|
struct FlagElement {
|
||||||
pub default_value: bool,
|
pub default_value: bool,
|
||||||
pub device_config_namespace: String,
|
pub device_config_namespace: String,
|
||||||
pub device_config_flag: String,
|
pub device_config_flag: String,
|
||||||
pub flag_name_constant_suffix: String,
|
pub flag_name_constant_suffix: String,
|
||||||
pub is_read_write: bool,
|
pub is_read_write: bool,
|
||||||
pub method_name: String,
|
pub method_name: String,
|
||||||
|
pub properties: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
|
fn create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement {
|
||||||
let device_config_flag = codegen::create_device_config_ident(package, pf.name())
|
let device_config_flag = codegen::create_device_config_ident(package, pf.name())
|
||||||
.expect("values checked at flag parse time");
|
.expect("values checked at flag parse time");
|
||||||
ClassElement {
|
FlagElement {
|
||||||
default_value: pf.state() == ProtoFlagState::ENABLED,
|
default_value: pf.state() == ProtoFlagState::ENABLED,
|
||||||
device_config_namespace: pf.namespace().to_string(),
|
device_config_namespace: pf.namespace().to_string(),
|
||||||
device_config_flag,
|
device_config_flag,
|
||||||
flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
|
flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
|
||||||
is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
|
is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
|
||||||
method_name: format_java_method_name(pf.name()),
|
method_name: format_java_method_name(pf.name()),
|
||||||
|
properties: format_property_name(pf.namespace()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_java_method_name(flag_name: &str) -> String {
|
fn format_java_method_name(flag_name: &str) -> String {
|
||||||
flag_name
|
let splits: Vec<&str> = flag_name.split('_').filter(|&word| !word.is_empty()).collect();
|
||||||
.split('_')
|
if splits.len() == 1 {
|
||||||
.filter(|&word| !word.is_empty())
|
let name = splits[0];
|
||||||
|
name[0..1].to_ascii_lowercase() + &name[1..]
|
||||||
|
} else {
|
||||||
|
splits
|
||||||
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, word)| {
|
.map(|(index, word)| {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
|
@ -109,6 +124,12 @@ fn format_java_method_name(flag_name: &str) -> String {
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("")
|
.join("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_property_name(property_name: &str) -> String {
|
||||||
|
let name = format_java_method_name(property_name);
|
||||||
|
format!("mProperties{}{}", &name[0..1].to_ascii_uppercase(), &name[1..])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -265,8 +286,10 @@ mod tests {
|
||||||
// TODO(b/303773055): Remove the annotation after access issue is resolved.
|
// TODO(b/303773055): Remove the annotation after access issue is resolved.
|
||||||
import android.compat.annotation.UnsupportedAppUsage;
|
import android.compat.annotation.UnsupportedAppUsage;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
|
import android.provider.DeviceConfig.Properties;
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public final class FeatureFlagsImpl implements FeatureFlags {
|
public final class FeatureFlagsImpl implements FeatureFlags {
|
||||||
|
private Properties mPropertiesAconfigTest;
|
||||||
@Override
|
@Override
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean disabledRo() {
|
public boolean disabledRo() {
|
||||||
|
@ -275,8 +298,15 @@ mod tests {
|
||||||
@Override
|
@Override
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean disabledRw() {
|
public boolean disabledRw() {
|
||||||
return getValue(
|
if (mPropertiesAconfigTest == null) {
|
||||||
|
mPropertiesAconfigTest =
|
||||||
|
getProperties(
|
||||||
"aconfig_test",
|
"aconfig_test",
|
||||||
|
"com.android.aconfig.test.disabled_rw"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return mPropertiesAconfigTest
|
||||||
|
.getBoolean(
|
||||||
"com.android.aconfig.test.disabled_rw",
|
"com.android.aconfig.test.disabled_rw",
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
@ -294,32 +324,36 @@ mod tests {
|
||||||
@Override
|
@Override
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean enabledRw() {
|
public boolean enabledRw() {
|
||||||
return getValue(
|
if (mPropertiesAconfigTest == null) {
|
||||||
|
mPropertiesAconfigTest =
|
||||||
|
getProperties(
|
||||||
"aconfig_test",
|
"aconfig_test",
|
||||||
|
"com.android.aconfig.test.enabled_rw"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return mPropertiesAconfigTest
|
||||||
|
.getBoolean(
|
||||||
"com.android.aconfig.test.enabled_rw",
|
"com.android.aconfig.test.enabled_rw",
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private boolean getValue(String nameSpace,
|
private Properties getProperties(
|
||||||
String flagName, boolean defaultValue) {
|
String namespace,
|
||||||
boolean value = defaultValue;
|
String flagName) {
|
||||||
|
Properties properties = null;
|
||||||
try {
|
try {
|
||||||
value = DeviceConfig.getBoolean(
|
properties = DeviceConfig.getProperties(namespace);
|
||||||
nameSpace,
|
|
||||||
flagName,
|
|
||||||
defaultValue
|
|
||||||
);
|
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Cannot read value of flag " + flagName + " from DeviceConfig. " +
|
"Cannot read value of flag " + flagName + " from DeviceConfig. "
|
||||||
"It could be that the code using flag executed " +
|
+ "It could be that the code using flag executed "
|
||||||
"before SettingsProvider initialization. " +
|
+ "before SettingsProvider initialization. "
|
||||||
"Please use fixed read-only flag by adding " +
|
+ "Please use fixed read-only flag by adding "
|
||||||
"is_fixed_read_only: true in flag declaration.",
|
+ "is_fixed_read_only: true in flag declaration.",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return value;
|
return properties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
@ -441,9 +475,45 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_format_java_method_name() {
|
fn test_format_java_method_name() {
|
||||||
let input = "____some_snake___name____";
|
|
||||||
let expected = "someSnakeName";
|
let expected = "someSnakeName";
|
||||||
|
let input = "____some_snake___name____";
|
||||||
|
let formatted_name = format_java_method_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "someSnakeName";
|
||||||
|
let formatted_name = format_java_method_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "SomeSnakeName";
|
||||||
|
let formatted_name = format_java_method_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "SomeSnakeName_";
|
||||||
|
let formatted_name = format_java_method_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "_SomeSnakeName";
|
||||||
let formatted_name = format_java_method_name(input);
|
let formatted_name = format_java_method_name(input);
|
||||||
assert_eq!(expected, formatted_name);
|
assert_eq!(expected, formatted_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_property_name() {
|
||||||
|
let expected = "mPropertiesSomeSnakeName";
|
||||||
|
let input = "____some_snake___name____";
|
||||||
|
let formatted_name = format_property_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "someSnakeName";
|
||||||
|
let formatted_name = format_property_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "SomeSnakeName";
|
||||||
|
let formatted_name = format_property_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
|
||||||
|
let input = "SomeSnakeName_";
|
||||||
|
let formatted_name = format_property_name(input);
|
||||||
|
assert_eq!(expected, formatted_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ public class FakeFeatureFlagsImpl implements FeatureFlags \{
|
||||||
resetAll();
|
resetAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
{{ for item in class_elements}}
|
{{ for item in flag_elements}}
|
||||||
@Override
|
@Override
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean {item.method_name}() \{
|
public boolean {item.method_name}() \{
|
||||||
|
@ -41,7 +41,7 @@ public class FakeFeatureFlagsImpl implements FeatureFlags \{
|
||||||
|
|
||||||
private Map<String, Boolean> mFlagMap = new HashMap<>(
|
private Map<String, Boolean> mFlagMap = new HashMap<>(
|
||||||
Map.ofEntries(
|
Map.ofEntries(
|
||||||
{{-for item in class_elements}}
|
{{-for item in flag_elements}}
|
||||||
Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
|
Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false)
|
||||||
{{ -if not @last }},{{ endif }}
|
{{ -if not @last }},{{ endif }}
|
||||||
{{ -endfor }}
|
{{ -endfor }}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.compat.annotation.UnsupportedAppUsage;
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public interface FeatureFlags \{
|
public interface FeatureFlags \{
|
||||||
{{ for item in class_elements}}
|
{{ for item in flag_elements }}
|
||||||
{{ -if not item.is_read_write }}
|
{{ -if not item.is_read_write }}
|
||||||
{{ -if item.default_value }}
|
{{ -if item.default_value }}
|
||||||
@com.android.aconfig.annotations.AssumeTrueForR8
|
@com.android.aconfig.annotations.AssumeTrueForR8
|
||||||
|
|
|
@ -4,45 +4,58 @@ import android.compat.annotation.UnsupportedAppUsage;
|
||||||
{{ if not is_test_mode }}
|
{{ if not is_test_mode }}
|
||||||
{{ if is_read_write- }}
|
{{ if is_read_write- }}
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
|
import android.provider.DeviceConfig.Properties;
|
||||||
{{ endif }}
|
{{ endif }}
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public final class FeatureFlagsImpl implements FeatureFlags \{
|
public final class FeatureFlagsImpl implements FeatureFlags \{
|
||||||
{{ for item in class_elements}}
|
{{ if is_read_write- }}
|
||||||
|
{{ for properties in properties_set }}
|
||||||
|
private Properties {properties};
|
||||||
|
{{ endfor }}
|
||||||
|
{{ endif- }}
|
||||||
|
|
||||||
|
{{ for flag in flag_elements }}
|
||||||
@Override
|
@Override
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean {item.method_name}() \{
|
public boolean {flag.method_name}() \{
|
||||||
{{ -if item.is_read_write }}
|
{{ -if flag.is_read_write }}
|
||||||
return getValue(
|
if ({flag.properties} == null) \{
|
||||||
"{item.device_config_namespace}",
|
{flag.properties} =
|
||||||
"{item.device_config_flag}",
|
getProperties(
|
||||||
{item.default_value}
|
"{flag.device_config_namespace}",
|
||||||
|
"{flag.device_config_flag}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {flag.properties}
|
||||||
|
.getBoolean(
|
||||||
|
"{flag.device_config_flag}",
|
||||||
|
{flag.default_value}
|
||||||
);
|
);
|
||||||
{{ else }}
|
{{ else }}
|
||||||
return {item.default_value};
|
return {flag.default_value};
|
||||||
{{ endif- }}
|
{{ endif- }}
|
||||||
}
|
}
|
||||||
{{ endfor }}
|
{{ endfor }}
|
||||||
{{ if is_read_write- }}
|
|
||||||
private boolean getValue(String nameSpace,
|
{{ -if is_read_write }}
|
||||||
String flagName, boolean defaultValue) \{
|
private Properties getProperties(
|
||||||
boolean value = defaultValue;
|
String namespace,
|
||||||
|
String flagName) \{
|
||||||
|
Properties properties = null;
|
||||||
try \{
|
try \{
|
||||||
value = DeviceConfig.getBoolean(
|
properties = DeviceConfig.getProperties(namespace);
|
||||||
nameSpace,
|
|
||||||
flagName,
|
|
||||||
defaultValue
|
|
||||||
);
|
|
||||||
} catch (NullPointerException e) \{
|
} catch (NullPointerException e) \{
|
||||||
throw new RuntimeException(
|
throw new RuntimeException(
|
||||||
"Cannot read value of flag " + flagName + " from DeviceConfig. " +
|
"Cannot read value of flag " + flagName + " from DeviceConfig. "
|
||||||
"It could be that the code using flag executed " +
|
+ "It could be that the code using flag executed "
|
||||||
"before SettingsProvider initialization. " +
|
+ "before SettingsProvider initialization. "
|
||||||
"Please use fixed read-only flag by adding " +
|
+ "Please use fixed read-only flag by adding "
|
||||||
"is_fixed_read_only: true in flag declaration.",
|
+ "is_fixed_read_only: true in flag declaration.",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return value;
|
|
||||||
|
return properties;
|
||||||
}
|
}
|
||||||
{{ endif- }}
|
{{ endif- }}
|
||||||
}
|
}
|
||||||
|
@ -50,10 +63,10 @@ public final class FeatureFlagsImpl implements FeatureFlags \{
|
||||||
{#- Generate only stub if in test mode #}
|
{#- Generate only stub if in test mode #}
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public final class FeatureFlagsImpl implements FeatureFlags \{
|
public final class FeatureFlagsImpl implements FeatureFlags \{
|
||||||
{{ for item in class_elements}}
|
{{ for flag in flag_elements }}
|
||||||
@Override
|
@Override
|
||||||
@UnsupportedAppUsage
|
@UnsupportedAppUsage
|
||||||
public boolean {item.method_name}() \{
|
public boolean {flag.method_name}() \{
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
"Method is not implemented.");
|
"Method is not implemented.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@ import android.compat.annotation.UnsupportedAppUsage;
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public final class Flags \{
|
public final class Flags \{
|
||||||
{{- for item in class_elements}}
|
{{- for item in flag_elements}}
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
|
public static final String FLAG_{item.flag_name_constant_suffix} = "{item.device_config_flag}";
|
||||||
{{- endfor }}
|
{{- endfor }}
|
||||||
{{ for item in class_elements}}
|
{{ for item in flag_elements}}
|
||||||
{{ -if not item.is_read_write }}
|
{{ -if not item.is_read_write }}
|
||||||
{{ -if item.default_value }}
|
{{ -if item.default_value }}
|
||||||
@com.android.aconfig.annotations.AssumeTrueForR8
|
@com.android.aconfig.annotations.AssumeTrueForR8
|
||||||
|
|
Loading…
Reference in a new issue