blob: a802725841ef4592cf67079b35219f153f86ec1d [file] [log] [blame]
Dennis Shen1dc9ad42023-05-12 00:21:55 +00001/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Mårten Kongstadfbd71e22023-05-31 13:29:35 +020017use anyhow::{ensure, Result};
Dennis Shen1dc9ad42023-05-12 00:21:55 +000018use serde::Serialize;
Dennis Shen8d544f72023-06-29 00:45:42 +000019use std::path::PathBuf;
Dennis Shen1dc9ad42023-05-12 00:21:55 +000020use tinytemplate::TinyTemplate;
21
Mårten Kongstadfbd71e22023-05-31 13:29:35 +020022use crate::codegen;
Dennis Shen8d544f72023-06-29 00:45:42 +000023use crate::commands::{CodegenMode, OutputFile};
Mårten Kongstad403658f2023-06-14 09:51:56 +020024use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
Dennis Shen1dc9ad42023-05-12 00:21:55 +000025
Dennis Shen8d544f72023-06-29 00:45:42 +000026pub fn generate_cpp_code<'a, I>(
27 package: &str,
28 parsed_flags_iter: I,
29 codegen_mode: CodegenMode,
30) -> Result<Vec<OutputFile>>
Mårten Kongstad403658f2023-06-14 09:51:56 +020031where
32 I: Iterator<Item = &'a ProtoParsedFlag>,
33{
Mårten Kongstad066575b2023-06-07 16:29:25 +020034 let class_elements: Vec<ClassElement> =
Mårten Kongstad403658f2023-06-14 09:51:56 +020035 parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
Dennis Shen1dc9ad42023-05-12 00:21:55 +000036 let readwrite = class_elements.iter().any(|item| item.readwrite);
Mårten Kongstadfbd71e22023-05-31 13:29:35 +020037 let header = package.replace('.', "_");
38 let cpp_namespace = package.replace('.', "::");
39 ensure!(codegen::is_valid_name_ident(&header));
Mårten Kongstad066575b2023-06-07 16:29:25 +020040 let context = Context {
41 header: header.clone(),
42 cpp_namespace,
43 package: package.to_string(),
44 readwrite,
Dennis Shen8d544f72023-06-29 00:45:42 +000045 for_prod: codegen_mode == CodegenMode::Production,
Mårten Kongstad066575b2023-06-07 16:29:25 +020046 class_elements,
47 };
Dennis Shen8d544f72023-06-29 00:45:42 +000048
49 let files = [
50 FileSpec {
51 name: &format!("{}.h", header),
52 template: include_str!("../templates/cpp_exported_header.template"),
53 dir: "include",
54 },
55 FileSpec {
56 name: &format!("{}.cc", header),
57 template: include_str!("../templates/cpp_source_file.template"),
58 dir: "",
59 },
60 FileSpec {
61 name: &format!("{}_flag_provider.h", header),
62 template: match codegen_mode {
63 CodegenMode::Production => {
64 include_str!("../templates/cpp_prod_flag_provider.template")
65 }
66 CodegenMode::Test => include_str!("../templates/cpp_test_flag_provider.template"),
67 },
68 dir: "",
69 },
70 ];
71 files.iter().map(|file| generate_file(file, &context)).collect()
72}
73
74pub fn generate_file(file: &FileSpec, context: &Context) -> Result<OutputFile> {
Dennis Shen1dc9ad42023-05-12 00:21:55 +000075 let mut template = TinyTemplate::new();
Dennis Shen8d544f72023-06-29 00:45:42 +000076 template.add_template(file.name, file.template)?;
77 let contents = template.render(file.name, &context)?;
78 let path: PathBuf = [&file.dir, &file.name].iter().collect();
Dennis Shen1dc9ad42023-05-12 00:21:55 +000079 Ok(OutputFile { contents: contents.into(), path })
80}
81
82#[derive(Serialize)]
Dennis Shen8d544f72023-06-29 00:45:42 +000083pub struct FileSpec<'a> {
84 pub name: &'a str,
85 pub template: &'a str,
86 pub dir: &'a str,
87}
88
89#[derive(Serialize)]
90pub struct Context {
Mårten Kongstadfbd71e22023-05-31 13:29:35 +020091 pub header: String,
92 pub cpp_namespace: String,
Mårten Kongstad9fb58962023-05-31 13:02:13 +020093 pub package: String,
Dennis Shen1dc9ad42023-05-12 00:21:55 +000094 pub readwrite: bool,
Dennis Shen8d544f72023-06-29 00:45:42 +000095 pub for_prod: bool,
Dennis Shen1dc9ad42023-05-12 00:21:55 +000096 pub class_elements: Vec<ClassElement>,
97}
98
99#[derive(Serialize)]
Dennis Shen8d544f72023-06-29 00:45:42 +0000100pub struct ClassElement {
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000101 pub readwrite: bool,
102 pub default_value: String,
103 pub flag_name: String,
Dennis Shen8d544f72023-06-29 00:45:42 +0000104 pub uppercase_flag_name: String,
Mårten Kongstad066575b2023-06-07 16:29:25 +0200105 pub device_config_namespace: String,
106 pub device_config_flag: String,
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000107}
108
Mårten Kongstad403658f2023-06-14 09:51:56 +0200109fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000110 ClassElement {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200111 readwrite: pf.permission() == ProtoFlagPermission::READ_WRITE,
112 default_value: if pf.state() == ProtoFlagState::ENABLED {
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000113 "true".to_string()
114 } else {
115 "false".to_string()
116 },
Mårten Kongstad403658f2023-06-14 09:51:56 +0200117 flag_name: pf.name().to_string(),
Dennis Shen8d544f72023-06-29 00:45:42 +0000118 uppercase_flag_name: pf.name().to_string().to_ascii_uppercase(),
Mårten Kongstad403658f2023-06-14 09:51:56 +0200119 device_config_namespace: pf.namespace().to_string(),
120 device_config_flag: codegen::create_device_config_ident(package, pf.name())
121 .expect("values checked at flag parse time"),
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
Dennis Shen8d544f72023-06-29 00:45:42 +0000128 use std::collections::HashMap;
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000129
Dennis Shen8d544f72023-06-29 00:45:42 +0000130 const EXPORTED_PROD_HEADER_EXPECTED: &str = r#"
Mårten Kongstad403658f2023-06-14 09:51:56 +0200131#ifndef com_android_aconfig_test_HEADER_H
132#define com_android_aconfig_test_HEADER_H
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000133
Dennis Shen8d544f72023-06-29 00:45:42 +0000134#include <string>
135#include <memory>
136#include <server_configurable_flags/get_flags.h>
Mårten Kongstad403658f2023-06-14 09:51:56 +0200137using namespace server_configurable_flags;
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000138
Mårten Kongstad403658f2023-06-14 09:51:56 +0200139namespace com::android::aconfig::test {
Dennis Shen8d544f72023-06-29 00:45:42 +0000140class flag_provider_interface {
141public:
142
143 virtual ~flag_provider_interface() = default;
144
145 virtual bool disabled_ro() = 0;
146
147 virtual bool disabled_rw() = 0;
148
149 virtual bool enabled_ro() = 0;
150
151 virtual bool enabled_rw() = 0;
152
153 virtual void override_flag(std::string const&, bool) {}
154
155 virtual void reset_overrides() {}
156};
157
158extern std::unique_ptr<flag_provider_interface> provider_;
159
160extern std::string const DISABLED_RO;
161extern std::string const DISABLED_RW;
162extern std::string const ENABLED_RO;
163extern std::string const ENABLED_RW;
164
165inline bool disabled_ro() {
166 return false;
167}
168
169inline bool disabled_rw() {
170 return provider_->disabled_rw();
171}
172
173inline bool enabled_ro() {
174 return true;
175}
176
177inline bool enabled_rw() {
178 return provider_->enabled_rw();
179}
180
181inline void override_flag(std::string const& name, bool val) {
182 return provider_->override_flag(name, val);
183}
184
185inline void reset_overrides() {
186 return provider_->reset_overrides();
187}
188
189}
190#endif
191"#;
192
193 const EXPORTED_TEST_HEADER_EXPECTED: &str = r#"
194#ifndef com_android_aconfig_test_HEADER_H
195#define com_android_aconfig_test_HEADER_H
196
197#include <string>
198#include <memory>
199#include <server_configurable_flags/get_flags.h>
200using namespace server_configurable_flags;
201
202namespace com::android::aconfig::test {
203class flag_provider_interface {
204public:
205
206 virtual ~flag_provider_interface() = default;
207
208 virtual bool disabled_ro() = 0;
209
210 virtual bool disabled_rw() = 0;
211
212 virtual bool enabled_ro() = 0;
213
214 virtual bool enabled_rw() = 0;
215
216 virtual void override_flag(std::string const&, bool) {}
217
218 virtual void reset_overrides() {}
219};
220
221extern std::unique_ptr<flag_provider_interface> provider_;
222
223extern std::string const DISABLED_RO;
224extern std::string const DISABLED_RW;
225extern std::string const ENABLED_RO;
226extern std::string const ENABLED_RW;
227
228inline bool disabled_ro() {
229 return provider_->disabled_ro();
230}
231
232inline bool disabled_rw() {
233 return provider_->disabled_rw();
234}
235
236inline bool enabled_ro() {
237 return provider_->enabled_ro();
238}
239
240inline bool enabled_rw() {
241 return provider_->enabled_rw();
242}
243
244inline void override_flag(std::string const& name, bool val) {
245 return provider_->override_flag(name, val);
246}
247
248inline void reset_overrides() {
249 return provider_->reset_overrides();
250}
251
252}
253#endif
254"#;
255
256 const PROD_FLAG_PROVIDER_HEADER_EXPECTED: &str = r#"
257#ifndef com_android_aconfig_test_flag_provider_HEADER_H
258#define com_android_aconfig_test_flag_provider_HEADER_H
259
260#include "com_android_aconfig_test.h"
261
262namespace com::android::aconfig::test {
263class flag_provider : public flag_provider_interface {
264public:
265
266 virtual bool disabled_ro() override {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200267 return false;
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000268 }
269
Dennis Shen8d544f72023-06-29 00:45:42 +0000270 virtual bool disabled_rw() override {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200271 return GetServerConfigurableFlag(
272 "aconfig_test",
273 "com.android.aconfig.test.disabled_rw",
274 "false") == "true";
275 }
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000276
Dennis Shen8d544f72023-06-29 00:45:42 +0000277 virtual bool enabled_ro() override {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200278 return true;
279 }
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000280
Dennis Shen8d544f72023-06-29 00:45:42 +0000281 virtual bool enabled_rw() override {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200282 return GetServerConfigurableFlag(
283 "aconfig_test",
284 "com.android.aconfig.test.enabled_rw",
285 "true") == "true";
286 }
Dennis Shen8d544f72023-06-29 00:45:42 +0000287};
Mårten Kongstad403658f2023-06-14 09:51:56 +0200288}
289#endif
290"#;
Dennis Shen8d544f72023-06-29 00:45:42 +0000291
292 const TEST_FLAG_PROVIDER_HEADER_EXPECTED: &str = r#"
293#ifndef com_android_aconfig_test_flag_provider_HEADER_H
294#define com_android_aconfig_test_flag_provider_HEADER_H
295
296#include "com_android_aconfig_test.h"
297
298#include <unordered_map>
299#include <unordered_set>
300#include <cassert>
301
302namespace com::android::aconfig::test {
303class flag_provider : public flag_provider_interface {
304private:
305 std::unordered_map<std::string, bool> overrides_;
306 std::unordered_set<std::string> flag_names_;
307
308public:
309
310 flag_provider()
311 : overrides_(),
312 flag_names_() {
313 flag_names_.insert(DISABLED_RO);
314 flag_names_.insert(DISABLED_RW);
315 flag_names_.insert(ENABLED_RO);
316 flag_names_.insert(ENABLED_RW);
317 }
318
319 virtual bool disabled_ro() override {
320 auto it = overrides_.find(DISABLED_RO);
321 if (it != overrides_.end()) {
322 return it->second;
323 } else {
324 return false;
325 }
326 }
327
328 virtual bool disabled_rw() override {
329 auto it = overrides_.find(DISABLED_RW);
330 if (it != overrides_.end()) {
331 return it->second;
332 } else {
333 return GetServerConfigurableFlag(
334 "aconfig_test",
335 "com.android.aconfig.test.disabled_rw",
336 "false") == "true";
337 }
338 }
339
340 virtual bool enabled_ro() override {
341 auto it = overrides_.find(ENABLED_RO);
342 if (it != overrides_.end()) {
343 return it->second;
344 } else {
345 return true;
346 }
347 }
348
349 virtual bool enabled_rw() override {
350 auto it = overrides_.find(ENABLED_RW);
351 if (it != overrides_.end()) {
352 return it->second;
353 } else {
354 return GetServerConfigurableFlag(
355 "aconfig_test",
356 "com.android.aconfig.test.enabled_rw",
357 "true") == "true";
358 }
359 }
360
361 virtual void override_flag(std::string const& flag, bool val) override {
362 assert(flag_names_.count(flag));
363 overrides_[flag] = val;
364 }
365
366 virtual void reset_overrides() override {
367 overrides_.clear();
368 }
369};
370}
371#endif
372"#;
373
374 const SOURCE_FILE_EXPECTED: &str = r#"
375#include "com_android_aconfig_test.h"
376#include "com_android_aconfig_test_flag_provider.h"
377
378namespace com::android::aconfig::test {
379
380 std::string const DISABLED_RO = "com.android.aconfig.test.disabled_ro";
381 std::string const DISABLED_RW = "com.android.aconfig.test.disabled_rw";
382 std::string const ENABLED_RO = "com.android.aconfig.test.enabled_ro";
383 std::string const ENABLED_RW = "com.android.aconfig.test.enabled_rw";
384
385 std::unique_ptr<flag_provider_interface> provider_ =
386 std::make_unique<flag_provider>();
387}
388"#;
389
390 fn test_generate_cpp_code(mode: CodegenMode) {
391 let parsed_flags = crate::test::parse_test_flags();
392 let generated =
393 generate_cpp_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter(), mode)
394 .unwrap();
395 let mut generated_files_map = HashMap::new();
396 for file in generated {
397 generated_files_map.insert(
398 String::from(file.path.to_str().unwrap()),
399 String::from_utf8(file.contents.clone()).unwrap(),
400 );
401 }
402
403 let mut target_file_path = String::from("include/com_android_aconfig_test.h");
404 assert!(generated_files_map.contains_key(&target_file_path));
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000405 assert_eq!(
Mårten Kongstadb0255072023-06-08 10:15:43 +0200406 None,
407 crate::test::first_significant_code_diff(
Dennis Shen8d544f72023-06-29 00:45:42 +0000408 match mode {
409 CodegenMode::Production => EXPORTED_PROD_HEADER_EXPECTED,
410 CodegenMode::Test => EXPORTED_TEST_HEADER_EXPECTED,
411 },
412 generated_files_map.get(&target_file_path).unwrap()
Mårten Kongstadb0255072023-06-08 10:15:43 +0200413 )
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000414 );
Dennis Shen8d544f72023-06-29 00:45:42 +0000415
416 target_file_path = String::from("com_android_aconfig_test_flag_provider.h");
417 assert!(generated_files_map.contains_key(&target_file_path));
418 assert_eq!(
419 None,
420 crate::test::first_significant_code_diff(
421 match mode {
422 CodegenMode::Production => PROD_FLAG_PROVIDER_HEADER_EXPECTED,
423 CodegenMode::Test => TEST_FLAG_PROVIDER_HEADER_EXPECTED,
424 },
425 generated_files_map.get(&target_file_path).unwrap()
426 )
427 );
428
429 target_file_path = String::from("com_android_aconfig_test.cc");
430 assert!(generated_files_map.contains_key(&target_file_path));
431 assert_eq!(
432 None,
433 crate::test::first_significant_code_diff(
434 SOURCE_FILE_EXPECTED,
435 generated_files_map.get(&target_file_path).unwrap()
436 )
437 );
438 }
439
440 #[test]
441 fn test_generate_cpp_code_for_prod() {
442 test_generate_cpp_code(CodegenMode::Production);
443 }
444
445 #[test]
446 fn test_generate_cpp_code_for_test() {
447 test_generate_cpp_code(CodegenMode::Test);
Dennis Shen1dc9ad42023-05-12 00:21:55 +0000448 }
449}