aconfig: move codegen into separate module

Consolidate the code generation sources into its own module. This is
done in preparation for when support for the new flag storage will be
added, which should also be placed in a separate module.

Bug: N/A
Test: atest aconfig.test aconfig.test.java
Change-Id: I8246729218ec60f2af4568c460e98329665a89fa
This commit is contained in:
Mårten Kongstad
2023-12-08 11:24:54 +01:00
parent 14a82dab15
commit 12e593cd02
6 changed files with 15 additions and 14 deletions

View File

@@ -0,0 +1,732 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use anyhow::{ensure, Result};
use serde::Serialize;
use std::path::PathBuf;
use tinytemplate::TinyTemplate;
use crate::codegen;
use crate::commands::{CodegenMode, OutputFile};
use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
pub fn generate_cpp_code<'a, I>(
package: &str,
parsed_flags_iter: I,
codegen_mode: CodegenMode,
) -> Result<Vec<OutputFile>>
where
I: Iterator<Item = &'a ProtoParsedFlag>,
{
let mut readwrite_count = 0;
let class_elements: Vec<ClassElement> = parsed_flags_iter
.map(|pf| create_class_element(package, pf, &mut readwrite_count))
.collect();
let readwrite = readwrite_count > 0;
let has_fixed_read_only = class_elements.iter().any(|item| item.is_fixed_read_only);
let header = package.replace('.', "_");
let package_macro = header.to_uppercase();
let cpp_namespace = package.replace('.', "::");
ensure!(codegen::is_valid_name_ident(&header));
let context = Context {
header: &header,
package_macro: &package_macro,
cpp_namespace: &cpp_namespace,
package,
has_fixed_read_only,
readwrite,
readwrite_count,
for_test: codegen_mode == CodegenMode::Test,
class_elements,
};
let files = [
FileSpec {
name: &format!("{}.h", header),
template: include_str!("../../templates/cpp_exported_header.template"),
dir: "include",
},
FileSpec {
name: &format!("{}.cc", header),
template: include_str!("../../templates/cpp_source_file.template"),
dir: "",
},
];
files.iter().map(|file| generate_file(file, &context)).collect()
}
pub fn generate_file(file: &FileSpec, context: &Context) -> Result<OutputFile> {
let mut template = TinyTemplate::new();
template.add_template(file.name, file.template)?;
let contents = template.render(file.name, &context)?;
let path: PathBuf = [&file.dir, &file.name].iter().collect();
Ok(OutputFile { contents: contents.into(), path })
}
#[derive(Serialize)]
pub struct FileSpec<'a> {
pub name: &'a str,
pub template: &'a str,
pub dir: &'a str,
}
#[derive(Serialize)]
pub struct Context<'a> {
pub header: &'a str,
pub package_macro: &'a str,
pub cpp_namespace: &'a str,
pub package: &'a str,
pub has_fixed_read_only: bool,
pub readwrite: bool,
pub readwrite_count: i32,
pub for_test: bool,
pub class_elements: Vec<ClassElement>,
}
#[derive(Serialize)]
pub struct ClassElement {
pub readwrite_idx: i32,
pub readwrite: bool,
pub is_fixed_read_only: bool,
pub default_value: String,
pub flag_name: String,
pub flag_macro: String,
pub device_config_namespace: String,
pub device_config_flag: String,
}
fn create_class_element(package: &str, pf: &ProtoParsedFlag, rw_count: &mut i32) -> ClassElement {
ClassElement {
readwrite_idx: if pf.permission() == ProtoFlagPermission::READ_WRITE {
let index = *rw_count;
*rw_count += 1;
index
} else {
-1
},
readwrite: pf.permission() == ProtoFlagPermission::READ_WRITE,
is_fixed_read_only: pf.is_fixed_read_only(),
default_value: if pf.state() == ProtoFlagState::ENABLED {
"true".to_string()
} else {
"false".to_string()
},
flag_name: pf.name().to_string(),
flag_macro: pf.name().to_uppercase(),
device_config_namespace: pf.namespace().to_string(),
device_config_flag: codegen::create_device_config_ident(package, pf.name())
.expect("values checked at flag parse time"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
const EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
#pragma once
#ifndef COM_ANDROID_ACONFIG_TEST
#define COM_ANDROID_ACONFIG_TEST(FLAG) COM_ANDROID_ACONFIG_TEST_##FLAG
#endif
#ifndef COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO
#define COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO true
#endif
#ifdef __cplusplus
#include <memory>
namespace com::android::aconfig::test {
class flag_provider_interface {
public:
virtual ~flag_provider_interface() = default;
virtual bool disabled_ro() = 0;
virtual bool disabled_rw() = 0;
virtual bool disabled_rw_exported() = 0;
virtual bool disabled_rw_in_other_namespace() = 0;
virtual bool enabled_fixed_ro() = 0;
virtual bool enabled_ro() = 0;
virtual bool enabled_rw() = 0;
};
extern std::unique_ptr<flag_provider_interface> provider_;
inline bool disabled_ro() {
return false;
}
inline bool disabled_rw() {
return provider_->disabled_rw();
}
inline bool disabled_rw_exported() {
return provider_->disabled_rw_exported();
}
inline bool disabled_rw_in_other_namespace() {
return provider_->disabled_rw_in_other_namespace();
}
inline bool enabled_fixed_ro() {
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
inline bool enabled_ro() {
return true;
}
inline bool enabled_rw() {
return provider_->enabled_rw();
}
}
extern "C" {
#endif // __cplusplus
bool com_android_aconfig_test_disabled_ro();
bool com_android_aconfig_test_disabled_rw();
bool com_android_aconfig_test_disabled_rw_exported();
bool com_android_aconfig_test_disabled_rw_in_other_namespace();
bool com_android_aconfig_test_enabled_fixed_ro();
bool com_android_aconfig_test_enabled_ro();
bool com_android_aconfig_test_enabled_rw();
#ifdef __cplusplus
} // extern "C"
#endif
"#;
const EXPORTED_TEST_HEADER_EXPECTED: &str = r#"
#pragma once
#ifdef __cplusplus
#include <memory>
namespace com::android::aconfig::test {
class flag_provider_interface {
public:
virtual ~flag_provider_interface() = default;
virtual bool disabled_ro() = 0;
virtual void disabled_ro(bool val) = 0;
virtual bool disabled_rw() = 0;
virtual void disabled_rw(bool val) = 0;
virtual bool disabled_rw_exported() = 0;
virtual void disabled_rw_exported(bool val) = 0;
virtual bool disabled_rw_in_other_namespace() = 0;
virtual void disabled_rw_in_other_namespace(bool val) = 0;
virtual bool enabled_fixed_ro() = 0;
virtual void enabled_fixed_ro(bool val) = 0;
virtual bool enabled_ro() = 0;
virtual void enabled_ro(bool val) = 0;
virtual bool enabled_rw() = 0;
virtual void enabled_rw(bool val) = 0;
virtual void reset_flags() {}
};
extern std::unique_ptr<flag_provider_interface> provider_;
inline bool disabled_ro() {
return provider_->disabled_ro();
}
inline void disabled_ro(bool val) {
provider_->disabled_ro(val);
}
inline bool disabled_rw() {
return provider_->disabled_rw();
}
inline void disabled_rw(bool val) {
provider_->disabled_rw(val);
}
inline bool disabled_rw_exported() {
return provider_->disabled_rw_exported();
}
inline void disabled_rw_exported(bool val) {
provider_->disabled_rw_exported(val);
}
inline bool disabled_rw_in_other_namespace() {
return provider_->disabled_rw_in_other_namespace();
}
inline void disabled_rw_in_other_namespace(bool val) {
provider_->disabled_rw_in_other_namespace(val);
}
inline bool enabled_fixed_ro() {
return provider_->enabled_fixed_ro();
}
inline void enabled_fixed_ro(bool val) {
provider_->enabled_fixed_ro(val);
}
inline bool enabled_ro() {
return provider_->enabled_ro();
}
inline void enabled_ro(bool val) {
provider_->enabled_ro(val);
}
inline bool enabled_rw() {
return provider_->enabled_rw();
}
inline void enabled_rw(bool val) {
provider_->enabled_rw(val);
}
inline void reset_flags() {
return provider_->reset_flags();
}
}
extern "C" {
#endif // __cplusplus
bool com_android_aconfig_test_disabled_ro();
void set_com_android_aconfig_test_disabled_ro(bool val);
bool com_android_aconfig_test_disabled_rw();
void set_com_android_aconfig_test_disabled_rw(bool val);
bool com_android_aconfig_test_disabled_rw_exported();
void set_com_android_aconfig_test_disabled_rw_exported(bool val);
bool com_android_aconfig_test_disabled_rw_in_other_namespace();
void set_com_android_aconfig_test_disabled_rw_in_other_namespace(bool val);
bool com_android_aconfig_test_enabled_fixed_ro();
void set_com_android_aconfig_test_enabled_fixed_ro(bool val);
bool com_android_aconfig_test_enabled_ro();
void set_com_android_aconfig_test_enabled_ro(bool val);
bool com_android_aconfig_test_enabled_rw();
void set_com_android_aconfig_test_enabled_rw(bool val);
void com_android_aconfig_test_reset_flags();
#ifdef __cplusplus
} // extern "C"
#endif
"#;
const PROD_SOURCE_FILE_EXPECTED: &str = r#"
#include "com_android_aconfig_test.h"
#include <server_configurable_flags/get_flags.h>
#include <vector>
namespace com::android::aconfig::test {
class flag_provider : public flag_provider_interface {
public:
virtual bool disabled_ro() override {
return false;
}
virtual bool disabled_rw() override {
if (cache_[0] == -1) {
cache_[0] = server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.aconfig_test",
"com.android.aconfig.test.disabled_rw",
"false") == "true";
}
return cache_[0];
}
virtual bool disabled_rw_exported() override {
if (cache_[1] == -1) {
cache_[1] = server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.aconfig_test",
"com.android.aconfig.test.disabled_rw_exported",
"false") == "true";
}
return cache_[1];
}
virtual bool disabled_rw_in_other_namespace() override {
if (cache_[2] == -1) {
cache_[2] = server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.other_namespace",
"com.android.aconfig.test.disabled_rw_in_other_namespace",
"false") == "true";
}
return cache_[2];
}
virtual bool enabled_fixed_ro() override {
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
virtual bool enabled_ro() override {
return true;
}
virtual bool enabled_rw() override {
if (cache_[3] == -1) {
cache_[3] = server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.aconfig_test",
"com.android.aconfig.test.enabled_rw",
"true") == "true";
}
return cache_[3];
}
private:
std::vector<int8_t> cache_ = std::vector<int8_t>(4, -1);
};
std::unique_ptr<flag_provider_interface> provider_ =
std::make_unique<flag_provider>();
}
bool com_android_aconfig_test_disabled_ro() {
return false;
}
bool com_android_aconfig_test_disabled_rw() {
return com::android::aconfig::test::disabled_rw();
}
bool com_android_aconfig_test_disabled_rw_exported() {
return com::android::aconfig::test::disabled_rw_exported();
}
bool com_android_aconfig_test_disabled_rw_in_other_namespace() {
return com::android::aconfig::test::disabled_rw_in_other_namespace();
}
bool com_android_aconfig_test_enabled_fixed_ro() {
return COM_ANDROID_ACONFIG_TEST_ENABLED_FIXED_RO;
}
bool com_android_aconfig_test_enabled_ro() {
return true;
}
bool com_android_aconfig_test_enabled_rw() {
return com::android::aconfig::test::enabled_rw();
}
"#;
const TEST_SOURCE_FILE_EXPECTED: &str = r#"
#include "com_android_aconfig_test.h"
#include <server_configurable_flags/get_flags.h>
#include <unordered_map>
#include <string>
namespace com::android::aconfig::test {
class flag_provider : public flag_provider_interface {
private:
std::unordered_map<std::string, bool> overrides_;
public:
flag_provider()
: overrides_()
{}
virtual bool disabled_ro() override {
auto it = overrides_.find("disabled_ro");
if (it != overrides_.end()) {
return it->second;
} else {
return false;
}
}
virtual void disabled_ro(bool val) override {
overrides_["disabled_ro"] = val;
}
virtual bool disabled_rw() override {
auto it = overrides_.find("disabled_rw");
if (it != overrides_.end()) {
return it->second;
} else {
return server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.aconfig_test",
"com.android.aconfig.test.disabled_rw",
"false") == "true";
}
}
virtual void disabled_rw(bool val) override {
overrides_["disabled_rw"] = val;
}
virtual bool disabled_rw_exported() override {
auto it = overrides_.find("disabled_rw_exported");
if (it != overrides_.end()) {
return it->second;
} else {
return server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.aconfig_test",
"com.android.aconfig.test.disabled_rw_exported",
"false") == "true";
}
}
virtual void disabled_rw_exported(bool val) override {
overrides_["disabled_rw_exported"] = val;
}
virtual bool disabled_rw_in_other_namespace() override {
auto it = overrides_.find("disabled_rw_in_other_namespace");
if (it != overrides_.end()) {
return it->second;
} else {
return server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.other_namespace",
"com.android.aconfig.test.disabled_rw_in_other_namespace",
"false") == "true";
}
}
virtual void disabled_rw_in_other_namespace(bool val) override {
overrides_["disabled_rw_in_other_namespace"] = val;
}
virtual bool enabled_fixed_ro() override {
auto it = overrides_.find("enabled_fixed_ro");
if (it != overrides_.end()) {
return it->second;
} else {
return true;
}
}
virtual void enabled_fixed_ro(bool val) override {
overrides_["enabled_fixed_ro"] = val;
}
virtual bool enabled_ro() override {
auto it = overrides_.find("enabled_ro");
if (it != overrides_.end()) {
return it->second;
} else {
return true;
}
}
virtual void enabled_ro(bool val) override {
overrides_["enabled_ro"] = val;
}
virtual bool enabled_rw() override {
auto it = overrides_.find("enabled_rw");
if (it != overrides_.end()) {
return it->second;
} else {
return server_configurable_flags::GetServerConfigurableFlag(
"aconfig_flags.aconfig_test",
"com.android.aconfig.test.enabled_rw",
"true") == "true";
}
}
virtual void enabled_rw(bool val) override {
overrides_["enabled_rw"] = val;
}
virtual void reset_flags() override {
overrides_.clear();
}
};
std::unique_ptr<flag_provider_interface> provider_ =
std::make_unique<flag_provider>();
}
bool com_android_aconfig_test_disabled_ro() {
return com::android::aconfig::test::disabled_ro();
}
void set_com_android_aconfig_test_disabled_ro(bool val) {
com::android::aconfig::test::disabled_ro(val);
}
bool com_android_aconfig_test_disabled_rw() {
return com::android::aconfig::test::disabled_rw();
}
void set_com_android_aconfig_test_disabled_rw(bool val) {
com::android::aconfig::test::disabled_rw(val);
}
bool com_android_aconfig_test_disabled_rw_exported() {
return com::android::aconfig::test::disabled_rw_exported();
}
void set_com_android_aconfig_test_disabled_rw_exported(bool val) {
com::android::aconfig::test::disabled_rw_exported(val);
}
bool com_android_aconfig_test_disabled_rw_in_other_namespace() {
return com::android::aconfig::test::disabled_rw_in_other_namespace();
}
void set_com_android_aconfig_test_disabled_rw_in_other_namespace(bool val) {
com::android::aconfig::test::disabled_rw_in_other_namespace(val);
}
bool com_android_aconfig_test_enabled_fixed_ro() {
return com::android::aconfig::test::enabled_fixed_ro();
}
void set_com_android_aconfig_test_enabled_fixed_ro(bool val) {
com::android::aconfig::test::enabled_fixed_ro(val);
}
bool com_android_aconfig_test_enabled_ro() {
return com::android::aconfig::test::enabled_ro();
}
void set_com_android_aconfig_test_enabled_ro(bool val) {
com::android::aconfig::test::enabled_ro(val);
}
bool com_android_aconfig_test_enabled_rw() {
return com::android::aconfig::test::enabled_rw();
}
void set_com_android_aconfig_test_enabled_rw(bool val) {
com::android::aconfig::test::enabled_rw(val);
}
void com_android_aconfig_test_reset_flags() {
com::android::aconfig::test::reset_flags();
}
"#;
fn test_generate_cpp_code(mode: CodegenMode) {
let parsed_flags = crate::test::parse_test_flags();
let generated =
generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
.unwrap();
let mut generated_files_map = HashMap::new();
for file in generated {
generated_files_map.insert(
String::from(file.path.to_str().unwrap()),
String::from_utf8(file.contents).unwrap(),
);
}
let mut target_file_path = String::from("include/com_android_aconfig_test.h");
assert!(generated_files_map.contains_key(&target_file_path));
assert_eq!(
None,
crate::test::first_significant_code_diff(
match mode {
CodegenMode::Production => EXPORTED_PROD_HEADER_EXPECTED,
CodegenMode::Test => EXPORTED_TEST_HEADER_EXPECTED,
CodegenMode::Exported =>
todo!("exported mode not yet supported for cpp, see b/313894653."),
},
generated_files_map.get(&target_file_path).unwrap()
)
);
target_file_path = String::from("com_android_aconfig_test.cc");
assert!(generated_files_map.contains_key(&target_file_path));
assert_eq!(
None,
crate::test::first_significant_code_diff(
match mode {
CodegenMode::Production => PROD_SOURCE_FILE_EXPECTED,
CodegenMode::Test => TEST_SOURCE_FILE_EXPECTED,
CodegenMode::Exported =>
todo!("exported mode not yet supported for cpp, see b/313894653."),
},
generated_files_map.get(&target_file_path).unwrap()
)
);
}
#[test]
fn test_generate_cpp_code_for_prod() {
test_generate_cpp_code(CodegenMode::Production);
}
#[test]
fn test_generate_cpp_code_for_test() {
test_generate_cpp_code(CodegenMode::Test);
}
}