diff options
author | 2023-11-29 15:44:24 +0000 | |
---|---|---|
committer | 2023-11-29 15:44:24 +0000 | |
commit | 4a6af78b6905b90549686b0a107c80ea8533f76c (patch) | |
tree | 2b59669e4630a3643335d8529256a476bc0e6c34 /tools/aconfig/src/codegen_java.rs | |
parent | 53084d3f9177a5f07eb7446a56759ae18497b575 (diff) |
aconfig: Add exported mode to aconfig Java library generation.
This commit adds a third codegen mode, _exported_, in addition to
the existing modes, production and test.
When codegen mode is _exported_, getters are generated _only_ for
flags marked as exported as well. Also the getters always look
up DeviceConfig values at runtime, and have a default value of
false.
This only implements exported mode for Java codegen, follow-up CLs
will support Rust and C++.
Test: atest aconfig.test
Bug: 311152507
Change-Id: Ie39379b40de072180e05d84c76361b24cc0e0d83
Diffstat (limited to 'tools/aconfig/src/codegen_java.rs')
-rw-r--r-- | tools/aconfig/src/codegen_java.rs | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs index a822cd5fd1..b3e5e6cf6f 100644 --- a/tools/aconfig/src/codegen_java.rs +++ b/tools/aconfig/src/codegen_java.rs @@ -39,6 +39,7 @@ where 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 library_exported = codegen_mode == CodegenMode::Exported; let context = Context { flag_elements, namespace_flags, @@ -46,6 +47,7 @@ where is_read_write, properties_set, package_name: package.to_string(), + library_exported, }; let mut template = TinyTemplate::new(); template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?; @@ -103,6 +105,7 @@ struct Context { pub is_read_write: bool, pub properties_set: BTreeSet<String>, pub package_name: String, + pub library_exported: bool, } #[derive(Serialize, Debug)] @@ -120,6 +123,7 @@ struct FlagElement { pub is_read_write: bool, pub method_name: String, pub properties: String, + pub exported: bool, } fn create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement { @@ -133,6 +137,7 @@ fn create_flag_element(package: &str, pf: &ProtoParsedFlag) -> FlagElement { is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE, method_name: format_java_method_name(pf.name()), properties: format_property_name(pf.namespace()), + exported: pf.is_exported.unwrap_or(false), } } @@ -179,6 +184,8 @@ mod tests { @UnsupportedAppUsage boolean disabledRw(); @UnsupportedAppUsage + boolean disabledRwExported(); + @UnsupportedAppUsage boolean disabledRwInOtherNamespace(); @com.android.aconfig.annotations.AssumeTrueForR8 @UnsupportedAppUsage @@ -202,6 +209,8 @@ mod tests { /** @hide */ public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw"; /** @hide */ + public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported"; + /** @hide */ public static final String FLAG_DISABLED_RW_IN_OTHER_NAMESPACE = "com.android.aconfig.test.disabled_rw_in_other_namespace"; /** @hide */ public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro"; @@ -220,6 +229,10 @@ mod tests { return FEATURE_FLAGS.disabledRw(); } @UnsupportedAppUsage + public static boolean disabledRwExported() { + return FEATURE_FLAGS.disabledRwExported(); + } + @UnsupportedAppUsage public static boolean disabledRwInOtherNamespace() { return FEATURE_FLAGS.disabledRwInOtherNamespace(); } @@ -262,6 +275,11 @@ mod tests { } @Override @UnsupportedAppUsage + public boolean disabledRwExported() { + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); + } + @Override + @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); } @@ -302,6 +320,7 @@ mod tests { Map.ofEntries( Map.entry(Flags.FLAG_DISABLED_RO, false), Map.entry(Flags.FLAG_DISABLED_RW, false), + Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), Map.entry(Flags.FLAG_ENABLED_RO, false), @@ -336,6 +355,7 @@ mod tests { private static boolean aconfig_test_is_cached = false; private static boolean other_namespace_is_cached = false; private static boolean disabledRw = false; + private static boolean disabledRwExported = false; private static boolean disabledRwInOtherNamespace = false; private static boolean enabledRw = true; @@ -345,6 +365,8 @@ mod tests { Properties properties = DeviceConfig.getProperties("aconfig_test"); disabledRw = properties.getBoolean("com.android.aconfig.test.disabled_rw", false); + disabledRwExported = + properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false); enabledRw = properties.getBoolean("com.android.aconfig.test.enabled_rw", true); } catch (NullPointerException e) { @@ -394,6 +416,14 @@ mod tests { } @Override @UnsupportedAppUsage + public boolean disabledRwExported() { + if (!aconfig_test_is_cached) { + load_overrides_aconfig_test(); + } + return disabledRwExported; + } + @Override + @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { if (!other_namespace_is_cached) { load_overrides_other_namespace(); @@ -449,6 +479,202 @@ mod tests { } #[test] + fn test_generate_java_code_exported() { + let parsed_flags = crate::test::parse_test_flags(); + let generated_files = generate_java_code( + crate::test::TEST_PACKAGE, + parsed_flags.parsed_flag.iter(), + CodegenMode::Exported, + ) + .unwrap(); + + let expect_flags_content = r#" + package com.android.aconfig.test; + // TODO(b/303773055): Remove the annotation after access issue is resolved. + import android.compat.annotation.UnsupportedAppUsage; + /** @hide */ + public final class Flags { + /** @hide */ + public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw"; + /** @hide */ + public static final String FLAG_DISABLED_RW_EXPORTED = "com.android.aconfig.test.disabled_rw_exported"; + + @UnsupportedAppUsage + public static boolean disabledRw() { + return FEATURE_FLAGS.disabledRw(); + } + @UnsupportedAppUsage + public static boolean disabledRwExported() { + return FEATURE_FLAGS.disabledRwExported(); + } + private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); + } + "#; + + let expect_feature_flags_content = r#" + package com.android.aconfig.test; + // TODO(b/303773055): Remove the annotation after access issue is resolved. + import android.compat.annotation.UnsupportedAppUsage; + /** @hide */ + public interface FeatureFlags { + @UnsupportedAppUsage + boolean disabledRw(); + @UnsupportedAppUsage + boolean disabledRwExported(); + } + "#; + + let expect_feature_flags_impl_content = r#" + package com.android.aconfig.test; + // TODO(b/303773055): Remove the annotation after access issue is resolved. + import android.compat.annotation.UnsupportedAppUsage; + import android.provider.DeviceConfig; + import android.provider.DeviceConfig.Properties; + /** @hide */ + public final class FeatureFlagsImpl implements FeatureFlags { + private static boolean aconfig_test_is_cached = false; + private static boolean other_namespace_is_cached = false; + private static boolean disabledRw = false; + private static boolean disabledRwExported = false; + + + private void load_overrides_aconfig_test() { + try { + Properties properties = DeviceConfig.getProperties("aconfig_test"); + disabledRw = + properties.getBoolean("com.android.aconfig.test.disabled_rw", false); + disabledRwExported = + properties.getBoolean("com.android.aconfig.test.disabled_rw_exported", false); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace aconfig_test " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + aconfig_test_is_cached = true; + } + + private void load_overrides_other_namespace() { + try { + Properties properties = DeviceConfig.getProperties("other_namespace"); + } catch (NullPointerException e) { + throw new RuntimeException( + "Cannot read value from namespace other_namespace " + + "from DeviceConfig. It could be that the code using flag " + + "executed before SettingsProvider initialization. Please use " + + "fixed read-only flag by adding is_fixed_read_only: true in " + + "flag declaration.", + e + ); + } + other_namespace_is_cached = true; + } + + @Override + @UnsupportedAppUsage + public boolean disabledRw() { + if (!aconfig_test_is_cached) { + load_overrides_aconfig_test(); + } + return disabledRw; + } + + @Override + @UnsupportedAppUsage + public boolean disabledRwExported() { + if (!aconfig_test_is_cached) { + load_overrides_aconfig_test(); + } + return disabledRwExported; + } + }"#; + + let expect_fake_feature_flags_impl_content = r#" + package com.android.aconfig.test; + // TODO(b/303773055): Remove the annotation after access issue is resolved. + import android.compat.annotation.UnsupportedAppUsage; + import java.util.HashMap; + import java.util.Map; + /** @hide */ + public class FakeFeatureFlagsImpl implements FeatureFlags { + public FakeFeatureFlagsImpl() { + resetAll(); + } + @Override + @UnsupportedAppUsage + public boolean disabledRw() { + return getValue(Flags.FLAG_DISABLED_RW); + } + @Override + @UnsupportedAppUsage + public boolean disabledRwExported() { + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); + } + public void setFlag(String flagName, boolean value) { + if (!this.mFlagMap.containsKey(flagName)) { + throw new IllegalArgumentException("no such flag " + flagName); + } + this.mFlagMap.put(flagName, value); + } + public void resetAll() { + for (Map.Entry entry : mFlagMap.entrySet()) { + entry.setValue(null); + } + } + private boolean getValue(String flagName) { + Boolean value = this.mFlagMap.get(flagName); + if (value == null) { + throw new IllegalArgumentException(flagName + " is not set"); + } + return value; + } + private Map<String, Boolean> mFlagMap = new HashMap<>( + Map.ofEntries( + Map.entry(Flags.FLAG_DISABLED_RO, false), + Map.entry(Flags.FLAG_DISABLED_RW, false), + Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), + Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), + Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), + Map.entry(Flags.FLAG_ENABLED_RO, false), + Map.entry(Flags.FLAG_ENABLED_RW, false) + ) + ); + } + "#; + + let mut file_set = HashMap::from([ + ("com/android/aconfig/test/Flags.java", expect_flags_content), + ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content), + ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content), + ( + "com/android/aconfig/test/FakeFeatureFlagsImpl.java", + expect_fake_feature_flags_impl_content, + ), + ]); + + for file in generated_files { + let file_path = file.path.to_str().unwrap(); + assert!(file_set.contains_key(file_path), "Cannot find {}", file_path); + assert_eq!( + None, + crate::test::first_significant_code_diff( + file_set.get(file_path).unwrap(), + &String::from_utf8(file.contents).unwrap() + ), + "File {} content is not correct", + file_path + ); + file_set.remove(file_path); + } + + assert!(file_set.is_empty()); + } + + #[test] fn test_generate_java_code_test() { let parsed_flags = crate::test::parse_test_flags(); let generated_files = generate_java_code( @@ -489,6 +715,12 @@ mod tests { } @Override @UnsupportedAppUsage + public boolean disabledRwExported() { + throw new UnsupportedOperationException( + "Method is not implemented."); + } + @Override + @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { throw new UnsupportedOperationException( "Method is not implemented."); |