/* * 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> where I: Iterator, { let class_elements: Vec = parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect(); let readwrite = class_elements.iter().any(|item| item.readwrite); let header = package.replace('.', "_"); let cpp_namespace = package.replace('.', "::"); ensure!(codegen::is_valid_name_ident(&header)); let context = Context { header: header.clone(), cpp_namespace, package: package.to_string(), readwrite, 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 { 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 { pub header: String, pub cpp_namespace: String, pub package: String, pub readwrite: bool, pub for_test: bool, pub class_elements: Vec, } #[derive(Serialize)] pub struct ClassElement { pub readwrite: bool, pub default_value: String, pub flag_name: String, pub device_config_namespace: String, pub device_config_flag: String, } fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement { ClassElement { readwrite: pf.permission() == ProtoFlagPermission::READ_WRITE, default_value: if pf.state() == ProtoFlagState::ENABLED { "true".to_string() } else { "false".to_string() }, flag_name: pf.name().to_string(), 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 #ifdef __cplusplus #include 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 enabled_fixed_ro() = 0; virtual bool enabled_ro() = 0; virtual bool enabled_rw() = 0; }; extern std::unique_ptr provider_; inline bool disabled_ro() { return false; } inline bool disabled_rw() { return provider_->disabled_rw(); } inline bool enabled_fixed_ro() { return true; } 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_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 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 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 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 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_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 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 { return server_configurable_flags::GetServerConfigurableFlag( "aconfig_flags.aconfig_test", "com.android.aconfig.test.disabled_rw", "false") == "true"; } virtual bool enabled_fixed_ro() override { return true; } virtual bool enabled_ro() override { return true; } virtual bool enabled_rw() override { return server_configurable_flags::GetServerConfigurableFlag( "aconfig_flags.aconfig_test", "com.android.aconfig.test.enabled_rw", "true") == "true"; } }; std::unique_ptr provider_ = std::make_unique(); } 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_enabled_fixed_ro() { return true; } 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 namespace com::android::aconfig::test { class flag_provider : public flag_provider_interface { private: std::unordered_map 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 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 provider_ = std::make_unique(); } 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_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.clone()).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, }, 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, }, 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); } }