Snap for 10843824 from 9d830a88b4aa9e2a47b21b9c614f1b18e8c6800d to 24Q1-release
Change-Id: I9a901aa0427210c4bbdb7b176b1907affba51925
diff --git a/Android.bp b/Android.bp
index 5ebe66f..c4af5eb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -42,6 +42,12 @@
}
cc_binary_host {
+ name: "sysprop_rust",
+ defaults: ["sysprop-defaults"],
+ srcs: ["RustGen.cpp", "RustMain.cpp"],
+}
+
+cc_binary_host {
name: "sysprop_api_checker",
defaults: ["sysprop-defaults"],
srcs: ["ApiChecker.cpp", "ApiCheckerMain.cpp"],
@@ -65,6 +71,7 @@
srcs: ["ApiChecker.cpp",
"CppGen.cpp",
"JavaGen.cpp",
+ "RustGen.cpp",
"TypeChecker.cpp",
"tests/*.cpp"],
whole_static_libs: ["libcom.android.sysprop.tests"],
diff --git a/Common.cpp b/Common.cpp
index ec7d120..fc64099 100644
--- a/Common.cpp
+++ b/Common.cpp
@@ -360,3 +360,34 @@
return (isdigit(name[0]) ? "_" : "") +
std::regex_replace(name, kRegexAllowed, "_");
}
+
+std::string SnakeCaseToCamelCase(const std::string& s) {
+ std::string result;
+ for (int i = 0; i < s.size(); ++i) {
+ if (s[i] == '_') continue;
+ char current = tolower(s[i]); // Handle screaming snake case.
+ if (i == 0 || s[i - 1] == '_') {
+ current = toupper(current);
+ }
+ result += current;
+ }
+ return result;
+}
+
+std::string CamelCaseToSnakeCase(const std::string& s) {
+ // Desired behaviour: "CurrentAPIVersion" -> "current_api_version".
+ std::string result;
+ for (int i = 0; i < s.size(); ++i) {
+ char current = s[i];
+ char prev = (i > 0) ? s[i - 1] : 0;
+ char next = (i < s.size() - 1) ? s[i + 1] : 0;
+ if (prev && isupper(prev) && (!next || isupper(next) || !isalpha(next))) {
+ current = tolower(current);
+ }
+ if (i > 0 && isupper(current) && result[result.size() - 1] != '_') {
+ result += '_';
+ }
+ result += tolower(current);
+ }
+ return result;
+}
\ No newline at end of file
diff --git a/RustGen.cpp b/RustGen.cpp
new file mode 100644
index 0000000..a1e7314
--- /dev/null
+++ b/RustGen.cpp
@@ -0,0 +1,516 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "sysprop_rust_gen"
+
+#include "RustGen.h"
+
+#include <android-base/file.h>
+
+#include <regex>
+#include <string>
+
+#include "CodeWriter.h"
+#include "Common.h"
+#include "sysprop.pb.h"
+
+using android::base::Result;
+
+namespace {
+
+constexpr const char* kParsersAndFormatters = R"(//! Parsers and formatters.
+//!
+//! This code must only be used in the system properties generate code.
+//!
+//! This is autogenerated code. DO NOT EDIT!
+
+use std::str::FromStr;
+use std::string::ToString;
+
+#[allow(missing_docs)]
+pub type Result<T> = std::result::Result<T, String>;
+
+// Parsers.
+
+#[allow(dead_code,missing_docs)]
+pub fn parse<T: FromStr>(s: &str) -> Result<T> {
+ s.parse::<T>().map_err(|_| format!("Can't convert '{}' to '{}'.",
+ s, std::any::type_name::<T>()))
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn parse_bool(s: &str) -> Result<bool> {
+ match s {
+ "1" | "true" => Ok(true),
+ "0" | "false" => Ok(false),
+ _ => Err(format!("Can't convert '{}' to 'bool'.", s)),
+ }
+}
+
+fn parse_list_with<T, F>(s: &str, f: F) -> Result<Vec<T>>
+where F: Fn(&str) -> Result<T> {
+
+ let mut result = Vec::new();
+ if s.is_empty() { return Ok(result); }
+
+ let mut chars = s.chars();
+ let mut current = chars.next();
+ while current.is_some() {
+ // Extract token.
+ let mut token = String::with_capacity(s.len());
+ while let Some(value) = current {
+ if value == ',' { break; }
+ if value == '\\' {
+ current = chars.next()
+ }
+ if let Some(value) = current {
+ token.push(value);
+ }
+ current = chars.next();
+ }
+ // Parse token.
+ result.push(f(token.as_str())?);
+ current = chars.next()
+ }
+
+ Ok(result)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn parse_list<T: FromStr>(s: &str) -> Result<Vec<T>> {
+ parse_list_with(s, parse)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn parse_bool_list(s: &str) -> Result<Vec<bool>> {
+ parse_list_with(s, parse_bool)
+}
+
+// Formatters.
+
+#[allow(dead_code,missing_docs)]
+pub fn format<T: ToString>(v: &T) -> String {
+ v.to_string()
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool(v: &bool) -> String {
+ if *v {
+ return "true".into();
+ }
+ "false".into()
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool_as_int(v: &bool) -> String {
+ if *v {
+ return "1".into();
+ }
+ "0".into()
+}
+
+fn format_list_with<T, F>(v: &[T], f: F) -> String
+where F: Fn(&T) -> String {
+ let mut result = String::new();
+ for item in v {
+ let formatted = f(item);
+ result.push_str(formatted.as_str());
+ result.push(',');
+ }
+ result.pop();
+ result
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_list<T: ToString>(v: &[T]) -> String {
+ format_list_with(v, format)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool_list(v: &[bool]) -> String {
+ format_list_with(v, format_bool)
+}
+
+#[allow(dead_code,missing_docs)]
+pub fn format_bool_list_as_int(v: &[bool]) -> String {
+ format_list_with(v, format_bool_as_int)
+})";
+
+constexpr const char* kDocs = R"(//! Autogenerated system properties.
+//!
+//! This is autogenerated crate. The crate contains methods for easy access to
+//! the Android system properties.)";
+
+constexpr const char* kRustFileImports = R"(use std::fmt;
+use rustutils::system_properties;
+
+mod gen_parsers_and_formatters;)";
+
+constexpr const char* kIndent = " ";
+
+constexpr const char* kDeprecated = "#[deprecated]";
+
+constexpr const char* kError = R"(/// Errors this crate could generate.
+#[derive(Debug)]
+pub enum SysPropError {
+ /// Failed to fetch the system property.
+ FetchError(system_properties::PropertyWatcherError),
+ /// Failed to set the system property.
+ SetError(system_properties::PropertyWatcherError),
+ /// Failed to parse the system property value.
+ ParseError(String),
+}
+
+impl fmt::Display for SysPropError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ SysPropError::FetchError(err) =>
+ write!(f, "failed to fetch the system property: {}", err),
+ SysPropError::SetError(err) =>
+ write!(f, "failed to set the system property: {}", err),
+ SysPropError::ParseError(err) =>
+ write!(f, "failed to parse the system property value: {}", err),
+ }
+ }
+})";
+
+constexpr const char* kResult = R"(/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, SysPropError>;)";
+
+std::string GetRustEnumType(const sysprop::Property& prop) {
+ std::string result = ApiNameToIdentifier(prop.api_name());
+ return SnakeCaseToCamelCase(result) + "Values";
+}
+
+std::string GetRustReturnType(const sysprop::Property& prop) {
+ switch (prop.type()) {
+ case sysprop::Boolean:
+ return "bool";
+ case sysprop::Integer:
+ return "i32";
+ case sysprop::UInt:
+ return "u32";
+ case sysprop::Long:
+ return "i64";
+ case sysprop::ULong:
+ return "u64";
+ case sysprop::Double:
+ return "f64";
+ case sysprop::String:
+ return "String";
+ case sysprop::Enum:
+ return GetRustEnumType(prop);
+ case sysprop::BooleanList:
+ return "Vec<bool>";
+ case sysprop::IntegerList:
+ return "Vec<i32>";
+ case sysprop::UIntList:
+ return "Vec<u32>";
+ case sysprop::LongList:
+ return "Vec<i64>";
+ case sysprop::ULongList:
+ return "Vec<u64>";
+ case sysprop::DoubleList:
+ return "Vec<f64>";
+ case sysprop::StringList:
+ return "Vec<String>";
+ case sysprop::EnumList:
+ return "Vec<" + GetRustEnumType(prop) + ">";
+ default:
+ __builtin_unreachable();
+ }
+}
+
+std::string GetRustAcceptType(const sysprop::Property& prop) {
+ switch (prop.type()) {
+ case sysprop::Boolean:
+ return "bool";
+ case sysprop::Integer:
+ return "i32";
+ case sysprop::UInt:
+ return "u32";
+ case sysprop::Long:
+ return "i64";
+ case sysprop::ULong:
+ return "u64";
+ case sysprop::Double:
+ return "f64";
+ case sysprop::String:
+ return "&str";
+ case sysprop::Enum:
+ return GetRustEnumType(prop);
+ case sysprop::BooleanList:
+ return "&[bool]";
+ case sysprop::IntegerList:
+ return "&[i32]";
+ case sysprop::UIntList:
+ return "&[u32]";
+ case sysprop::LongList:
+ return "&[i64]";
+ case sysprop::ULongList:
+ return "&[u64]";
+ case sysprop::DoubleList:
+ return "&[f64]";
+ case sysprop::StringList:
+ return "&[String]";
+ case sysprop::EnumList:
+ return "&[" + GetRustEnumType(prop) + "]";
+ default:
+ __builtin_unreachable();
+ }
+}
+
+std::string GetTypeParser(const sysprop::Property& prop) {
+ switch (prop.type()) {
+ case sysprop::Boolean:
+ return "gen_parsers_and_formatters::parse_bool";
+ case sysprop::Integer:
+ case sysprop::UInt:
+ case sysprop::Long:
+ case sysprop::ULong:
+ case sysprop::Double:
+ case sysprop::String:
+ case sysprop::Enum:
+ return "gen_parsers_and_formatters::parse";
+ case sysprop::BooleanList:
+ return "gen_parsers_and_formatters::parse_bool_list";
+ case sysprop::IntegerList:
+ case sysprop::UIntList:
+ case sysprop::LongList:
+ case sysprop::ULongList:
+ case sysprop::DoubleList:
+ case sysprop::StringList:
+ case sysprop::EnumList:
+ return "gen_parsers_and_formatters::parse_list";
+ default:
+ __builtin_unreachable();
+ }
+}
+
+std::string GetTypeFormatter(const sysprop::Property& prop) {
+ switch (prop.type()) {
+ case sysprop::Boolean:
+ if (prop.integer_as_bool()) {
+ return "gen_parsers_and_formatters::format_bool_as_int";
+ }
+ return "gen_parsers_and_formatters::format_bool";
+ case sysprop::String:
+ case sysprop::Integer:
+ case sysprop::UInt:
+ case sysprop::Long:
+ case sysprop::ULong:
+ case sysprop::Double:
+ case sysprop::Enum:
+ return "gen_parsers_and_formatters::format";
+ case sysprop::BooleanList:
+ if (prop.integer_as_bool()) {
+ return "gen_parsers_and_formatters::format_bool_list_as_int";
+ }
+ return "gen_parsers_and_formatters::format_bool_list";
+ case sysprop::IntegerList:
+ case sysprop::UIntList:
+ case sysprop::LongList:
+ case sysprop::ULongList:
+ case sysprop::DoubleList:
+ case sysprop::StringList:
+ case sysprop::EnumList:
+ return "gen_parsers_and_formatters::format_list";
+ default:
+ __builtin_unreachable();
+ }
+}
+
+std::string GenerateRustSource(sysprop::Properties props, sysprop::Scope scope) {
+ CodeWriter writer(kIndent);
+ writer.Write("%s\n\n", kDocs);
+ writer.Write("%s", kGeneratedFileFooterComments);
+ writer.Write("%s\n\n", kRustFileImports);
+ writer.Write("%s\n\n", kError);
+ writer.Write("%s\n\n", kResult);
+
+ for (int i = 0; i < props.prop_size(); ++i) {
+ const sysprop::Property& prop = props.prop(i);
+ if (prop.scope() > scope) continue;
+
+ std::string prop_id =
+ CamelCaseToSnakeCase(ApiNameToIdentifier(prop.api_name()));
+
+ // Create enum.
+ if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
+ auto enum_type = GetRustEnumType(prop);
+ auto values = ParseEnumValues(prop.enum_values());
+
+ writer.Write(
+ "#[derive(Copy, Clone, Debug, Eq, "
+ "PartialEq, PartialOrd, Hash, Ord)]\n");
+ writer.Write("pub enum %s {\n", enum_type.c_str());
+ writer.Indent();
+ for (const std::string& value : values) {
+ writer.Write("%s,\n", SnakeCaseToCamelCase(value).c_str());
+ }
+ writer.Dedent();
+ writer.Write("}\n\n");
+
+ // Enum parser.
+ writer.Write("impl std::str::FromStr for %s {\n", enum_type.c_str());
+ writer.Indent();
+ writer.Write("type Err = String;\n\n");
+ writer.Write(
+ "fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n");
+ writer.Indent();
+ writer.Write("match s {\n");
+ writer.Indent();
+ for (const std::string& value : values) {
+ writer.Write("\"%s\" => Ok(%s::%s),\n", value.c_str(),
+ enum_type.c_str(), SnakeCaseToCamelCase(value).c_str());
+ }
+ writer.Write("_ => Err(format!(\"'{}' cannot be parsed for %s\", s)),\n",
+ enum_type.c_str());
+ writer.Dedent();
+ writer.Write("}\n");
+ writer.Dedent();
+ writer.Write("}\n");
+ writer.Dedent();
+ writer.Write("}\n\n");
+
+ // Enum formatter.
+ writer.Write("impl fmt::Display for %s {\n", enum_type.c_str());
+ writer.Indent();
+ writer.Write(
+ "fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n");
+ writer.Indent();
+ writer.Write("match self {\n");
+ writer.Indent();
+ for (const std::string& value : values) {
+ writer.Write("%s::%s => write!(f, \"%s\"),\n", enum_type.c_str(),
+ SnakeCaseToCamelCase(value).c_str(), value.c_str());
+ }
+ writer.Write("_ => Err(fmt::Error),\n");
+ writer.Dedent();
+ writer.Write("}\n");
+ writer.Dedent();
+ writer.Write("}\n");
+ writer.Dedent();
+ writer.Write("}\n\n");
+ }
+
+ // Write getter.
+ std::string prop_return_type = GetRustReturnType(prop);
+ std::string parser = GetTypeParser(prop);
+ writer.Write("/// Returns the value of the property '%s' if set.\n",
+ prop.prop_name().c_str());
+ if (prop.deprecated()) writer.Write("%s\n", kDeprecated);
+ // Escape prop id if it is similar to `type` keyword.
+ std::string identifier = (prop_id == "type") ? "r#" + prop_id : prop_id;
+ writer.Write("pub fn %s() -> Result<Option<%s>> {\n", identifier.c_str(),
+ prop_return_type.c_str());
+ writer.Indent();
+ // Try original property.
+ writer.Write("let result = match system_properties::read(\"%s\") {\n",
+ prop.prop_name().c_str());
+ writer.Indent();
+ writer.Write("Err(e) => Err(SysPropError::FetchError(e)),\n");
+ writer.Write(
+ "Ok(Some(val)) => "
+ "%s(val.as_str()).map_err(SysPropError::ParseError).map(Some),\n",
+ parser.c_str());
+ writer.Write("Ok(None) => Ok(None),\n");
+ writer.Dedent();
+ writer.Write("};\n");
+ // Try legacy property
+ if (!prop.legacy_prop_name().empty()) {
+ writer.Write("if result.is_ok() { return result; }\n");
+ // Avoid omitting the error when fallback to legacy.
+ writer.Write(
+ "log::debug!(\"Failed to fetch the original property '%s' ('{}'), "
+ "falling back to the legacy one '%s'.\", result.unwrap_err());\n",
+ prop.prop_name().c_str(), prop.legacy_prop_name().c_str());
+ writer.Write("match system_properties::read(\"%s\") {\n",
+ prop.legacy_prop_name().c_str());
+ writer.Indent();
+ writer.Write("Err(e) => Err(SysPropError::FetchError(e)),\n");
+ writer.Write(
+ "Ok(Some(val)) => "
+ "%s(val.as_str()).map_err(SysPropError::ParseError).map(Some),\n",
+ parser.c_str());
+ writer.Write("Ok(None) => Ok(None),\n");
+ writer.Dedent();
+ writer.Write("}\n");
+ } else {
+ writer.Write("result\n");
+ }
+ writer.Dedent();
+ writer.Write("}\n\n");
+
+ // Write setter.
+ if (prop.access() == sysprop::Readonly) continue;
+ std::string prop_accept_type = GetRustAcceptType(prop);
+ std::string formatter = GetTypeFormatter(prop);
+ writer.Write(
+ "/// Sets the value of the property '%s', "
+ "returns 'Ok' if successful.\n",
+ prop.prop_name().c_str());
+ if (prop.deprecated()) writer.Write("%s\n", kDeprecated);
+ writer.Write("pub fn set_%s(v: %s) -> Result<()> {\n", prop_id.c_str(),
+ prop_accept_type.c_str());
+ writer.Indent();
+ std::string write_arg;
+ if (prop.type() == sysprop::String) {
+ write_arg = "v";
+ } else {
+ // We need to borrow single values.
+ std::string format_arg = prop.type() >= 20 ? "v" : "&v";
+ writer.Write("let value = %s(%s);\n", formatter.c_str(),
+ format_arg.c_str());
+ write_arg = "value.as_str()";
+ }
+ writer.Write(
+ "system_properties::write(\"%s\", "
+ "%s).map_err(SysPropError::SetError)\n",
+ prop.prop_name().c_str(), write_arg.c_str());
+ writer.Dedent();
+ writer.Write("}\n\n");
+ }
+ return writer.Code();
+}
+
+}; // namespace
+
+Result<void> GenerateRustLibrary(const std::string& input_file_path,
+ sysprop::Scope scope,
+ const std::string& rust_output_dir) {
+ sysprop::Properties props;
+
+ if (auto res = ParseProps(input_file_path); res.ok()) {
+ props = std::move(*res);
+ } else {
+ return res.error();
+ }
+
+ std::string lib_path = rust_output_dir + "/lib.rs";
+ std::string lib_result = GenerateRustSource(props, scope);
+ if (!android::base::WriteStringToFile(lib_result, lib_path)) {
+ return ErrnoErrorf("Writing generated rust lib to {} failed", lib_path);
+ }
+
+ std::string parsers_path = rust_output_dir + "/gen_parsers_and_formatters.rs";
+ std::string parsers_result = std::string(kParsersAndFormatters);
+ if (!android::base::WriteStringToFile(parsers_result, parsers_path)) {
+ return ErrnoErrorf("Writing generated rust lib to {} failed", parsers_path);
+ }
+
+ return {};
+}
diff --git a/RustMain.cpp b/RustMain.cpp
new file mode 100644
index 0000000..3414c3c
--- /dev/null
+++ b/RustMain.cpp
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "sysprop_rust"
+
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+#include "RustGen.h"
+#include "sysprop.pb.h"
+
+using android::base::Result;
+
+namespace {
+
+struct Arguments {
+ std::string input_file_path;
+ std::string rust_output_dir;
+ sysprop::Scope scope;
+};
+
+[[noreturn]] void PrintUsage(const char* exe_name) {
+ std::printf(
+ "Usage %s --scope (internal|public) --rust-output-dir dir "
+ "sysprop_file\n",
+ exe_name);
+ std::exit(EXIT_FAILURE);
+}
+
+Result<void> ParseArgs(int argc, char* argv[], Arguments* args) {
+ for (;;) {
+ static struct option long_options[] = {
+ {"rust-output-dir", required_argument, 0, 'r'},
+ {"scope", required_argument, 0, 's'},
+ };
+
+ int opt = getopt_long_only(argc, argv, "", long_options, nullptr);
+ if (opt == -1) break;
+
+ switch (opt) {
+ case 'r':
+ args->rust_output_dir = optarg;
+ break;
+ case 's':
+ if (strcmp(optarg, "public") == 0) {
+ args->scope = sysprop::Scope::Public;
+ } else if (strcmp(optarg, "internal") == 0) {
+ args->scope = sysprop::Scope::Internal;
+ } else {
+ return Errorf("Invalid option {} for scope", optarg);
+ }
+ break;
+ default:
+ PrintUsage(argv[0]);
+ }
+ }
+
+ if (optind >= argc) {
+ return Errorf("No input file specified");
+ }
+
+ if (optind + 1 < argc) {
+ return Errorf("More than one input file");
+ }
+
+ args->input_file_path = argv[optind];
+ if (args->rust_output_dir.empty()) args->rust_output_dir = ".";
+
+ return {};
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ Arguments args;
+ std::string err;
+ if (auto res = ParseArgs(argc, argv, &args); !res.ok()) {
+ LOG(ERROR) << res.error();
+ PrintUsage(argv[0]);
+ }
+
+ if (auto res = GenerateRustLibrary(args.input_file_path, args.scope,
+ args.rust_output_dir);
+ !res.ok()) {
+ LOG(FATAL) << "Error during generating rust sysprop from "
+ << args.input_file_path << ": " << res.error();
+ }
+}
\ No newline at end of file
diff --git a/include/Common.h b/include/Common.h
index 8ad6fb1..7d3a2ad 100644
--- a/include/Common.h
+++ b/include/Common.h
@@ -32,3 +32,5 @@
android::base::Result<sysprop::SyspropLibraryApis> ParseApiFile(
const std::string& file_path);
std::string ToUpper(std::string str);
+std::string CamelCaseToSnakeCase(const std::string& str);
+std::string SnakeCaseToCamelCase(const std::string& str);
diff --git a/include/RustGen.h b/include/RustGen.h
new file mode 100644
index 0000000..4843a52
--- /dev/null
+++ b/include/RustGen.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+
+#include <string>
+
+#include "sysprop.pb.h"
+
+android::base::Result<void> GenerateRustLibrary(
+ const std::string& input_file_path, sysprop::Scope scope,
+ const std::string& rust_output_dir);
diff --git a/tests/CommonTest.cpp b/tests/CommonTest.cpp
new file mode 100644
index 0000000..cd343f7
--- /dev/null
+++ b/tests/CommonTest.cpp
@@ -0,0 +1,39 @@
+/* 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.
+ */
+
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "Common.h"
+
+TEST(SyspropTest, CamelToSnakeTest) {
+ EXPECT_EQ(CamelCaseToSnakeCase("CurrentAPIVersion"), "current_api_version");
+ EXPECT_EQ(CamelCaseToSnakeCase("CurrentAPI"), "current_api");
+ EXPECT_EQ(CamelCaseToSnakeCase("APIVersion"), "api_version");
+ EXPECT_EQ(CamelCaseToSnakeCase("API"), "api");
+ EXPECT_EQ(CamelCaseToSnakeCase("API2"), "api2");
+ EXPECT_EQ(CamelCaseToSnakeCase("SomeRandomCamelCase"),
+ "some_random_camel_case");
+ EXPECT_EQ(CamelCaseToSnakeCase("snake_case"), "snake_case");
+ EXPECT_EQ(CamelCaseToSnakeCase("Strange_Camel_Case"), "strange_camel_case");
+}
+
+TEST(SyspropTest, SnakeToCamelTest) {
+ EXPECT_EQ(SnakeCaseToCamelCase("current_api_version"), "CurrentApiVersion");
+ EXPECT_EQ(SnakeCaseToCamelCase("random"), "Random");
+ EXPECT_EQ(SnakeCaseToCamelCase("SCREAMING_SNAKE_CASE_100"),
+ "ScreamingSnakeCase100");
+ EXPECT_EQ(SnakeCaseToCamelCase("double__underscore"), "DoubleUnderscore");
+}
\ No newline at end of file
diff --git a/tests/RustGenTest.cpp b/tests/RustGenTest.cpp
new file mode 100644
index 0000000..fdddf93
--- /dev/null
+++ b/tests/RustGenTest.cpp
@@ -0,0 +1,555 @@
+/* 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.
+ */
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "RustGen.h"
+
+namespace {
+
+constexpr const char* kTestSyspropFile =
+ R"(owner: Platform
+module: "android.sysprop.PlatformProperties"
+prop {
+ api_name: "test_double"
+ type: Double
+ prop_name: "android.test_double"
+ scope: Internal
+ access: ReadWrite
+}
+prop {
+ api_name: "test_int"
+ type: Integer
+ prop_name: "android.test_int"
+ scope: Public
+ access: ReadWrite
+}
+prop {
+ api_name: "test_string"
+ type: String
+ prop_name: "android.test.string"
+ scope: Public
+ access: Readonly
+ legacy_prop_name: "legacy.android.test.string"
+}
+prop {
+ api_name: "test_enum"
+ type: Enum
+ prop_name: "android.test.enum"
+ enum_values: "a|b|c|D|e|f|G"
+ scope: Internal
+ access: ReadWrite
+}
+prop {
+ api_name: "test_BOOLeaN"
+ type: Boolean
+ prop_name: "ro.android.test.b"
+ scope: Public
+ access: Writeonce
+}
+prop {
+ api_name: "android_os_test-long"
+ type: Long
+ scope: Public
+ access: ReadWrite
+}
+prop {
+ api_name: "test_double_list"
+ type: DoubleList
+ scope: Internal
+ access: ReadWrite
+}
+prop {
+ api_name: "test_list_int"
+ type: IntegerList
+ scope: Public
+ access: ReadWrite
+}
+prop {
+ api_name: "test_strlist"
+ type: StringList
+ scope: Public
+ access: ReadWrite
+ deprecated: true
+}
+prop {
+ api_name: "el"
+ type: EnumList
+ enum_values: "enu|mva|lue"
+ scope: Internal
+ access: ReadWrite
+ deprecated: true
+}
+)";
+
+constexpr const char* kExpectedPublicOutput =
+ R"(//! Autogenerated system properties.
+//!
+//! This is autogenerated crate. The crate contains methods for easy access to
+//! the Android system properties.
+
+// Generated by the sysprop generator. DO NOT EDIT!
+
+use std::fmt;
+use rustutils::system_properties;
+
+mod gen_parsers_and_formatters;
+
+/// Errors this crate could generate.
+#[derive(Debug)]
+pub enum SysPropError {
+ /// Failed to fetch the system property.
+ FetchError(system_properties::PropertyWatcherError),
+ /// Failed to set the system property.
+ SetError(system_properties::PropertyWatcherError),
+ /// Failed to parse the system property value.
+ ParseError(String),
+}
+
+impl fmt::Display for SysPropError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ SysPropError::FetchError(err) =>
+ write!(f, "failed to fetch the system property: {}", err),
+ SysPropError::SetError(err) =>
+ write!(f, "failed to set the system property: {}", err),
+ SysPropError::ParseError(err) =>
+ write!(f, "failed to parse the system property value: {}", err),
+ }
+ }
+}
+
+/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, SysPropError>;
+
+/// Returns the value of the property 'android.test_int' if set.
+pub fn test_int() -> Result<Option<i32>> {
+ let result = match system_properties::read("android.test_int") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'android.test_int', returns 'Ok' if successful.
+pub fn set_test_int(v: i32) -> Result<()> {
+ let value = gen_parsers_and_formatters::format(&v);
+ system_properties::write("android.test_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android.test.string' if set.
+pub fn test_string() -> Result<Option<String>> {
+ let result = match system_properties::read("android.test.string") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ if result.is_ok() { return result; }
+ log::debug!("Failed to fetch the original property 'android.test.string' ('{}'), falling back to the legacy one 'legacy.android.test.string'.", result.unwrap_err());
+ match system_properties::read("legacy.android.test.string") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ }
+}
+
+/// Returns the value of the property 'ro.android.test.b' if set.
+pub fn test_boo_lea_n() -> Result<Option<bool>> {
+ let result = match system_properties::read("ro.android.test.b") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_bool(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'ro.android.test.b', returns 'Ok' if successful.
+pub fn set_test_boo_lea_n(v: bool) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_bool(&v);
+ system_properties::write("ro.android.test.b", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android_os_test-long' if set.
+pub fn android_os_test_long() -> Result<Option<i64>> {
+ let result = match system_properties::read("android_os_test-long") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'android_os_test-long', returns 'Ok' if successful.
+pub fn set_android_os_test_long(v: i64) -> Result<()> {
+ let value = gen_parsers_and_formatters::format(&v);
+ system_properties::write("android_os_test-long", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_list_int' if set.
+pub fn test_list_int() -> Result<Option<Vec<i32>>> {
+ let result = match system_properties::read("test_list_int") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'test_list_int', returns 'Ok' if successful.
+pub fn set_test_list_int(v: &[i32]) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_list(v);
+ system_properties::write("test_list_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_strlist' if set.
+#[deprecated]
+pub fn test_strlist() -> Result<Option<Vec<String>>> {
+ let result = match system_properties::read("test_strlist") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'test_strlist', returns 'Ok' if successful.
+#[deprecated]
+pub fn set_test_strlist(v: &[String]) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_list(v);
+ system_properties::write("test_strlist", value.as_str()).map_err(SysPropError::SetError)
+}
+
+)";
+
+constexpr const char* kExpectedInternalOutput =
+ R"(//! Autogenerated system properties.
+//!
+//! This is autogenerated crate. The crate contains methods for easy access to
+//! the Android system properties.
+
+// Generated by the sysprop generator. DO NOT EDIT!
+
+use std::fmt;
+use rustutils::system_properties;
+
+mod gen_parsers_and_formatters;
+
+/// Errors this crate could generate.
+#[derive(Debug)]
+pub enum SysPropError {
+ /// Failed to fetch the system property.
+ FetchError(system_properties::PropertyWatcherError),
+ /// Failed to set the system property.
+ SetError(system_properties::PropertyWatcherError),
+ /// Failed to parse the system property value.
+ ParseError(String),
+}
+
+impl fmt::Display for SysPropError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ SysPropError::FetchError(err) =>
+ write!(f, "failed to fetch the system property: {}", err),
+ SysPropError::SetError(err) =>
+ write!(f, "failed to set the system property: {}", err),
+ SysPropError::ParseError(err) =>
+ write!(f, "failed to parse the system property value: {}", err),
+ }
+ }
+}
+
+/// Result type specific for this crate.
+pub type Result<T> = std::result::Result<T, SysPropError>;
+
+/// Returns the value of the property 'android.test_double' if set.
+pub fn test_double() -> Result<Option<f64>> {
+ let result = match system_properties::read("android.test_double") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'android.test_double', returns 'Ok' if successful.
+pub fn set_test_double(v: f64) -> Result<()> {
+ let value = gen_parsers_and_formatters::format(&v);
+ system_properties::write("android.test_double", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android.test_int' if set.
+pub fn test_int() -> Result<Option<i32>> {
+ let result = match system_properties::read("android.test_int") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'android.test_int', returns 'Ok' if successful.
+pub fn set_test_int(v: i32) -> Result<()> {
+ let value = gen_parsers_and_formatters::format(&v);
+ system_properties::write("android.test_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android.test.string' if set.
+pub fn test_string() -> Result<Option<String>> {
+ let result = match system_properties::read("android.test.string") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ if result.is_ok() { return result; }
+ log::debug!("Failed to fetch the original property 'android.test.string' ('{}'), falling back to the legacy one 'legacy.android.test.string'.", result.unwrap_err());
+ match system_properties::read("legacy.android.test.string") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord)]
+pub enum TestEnumValues {
+ A,
+ B,
+ C,
+ D,
+ E,
+ F,
+ G,
+}
+
+impl std::str::FromStr for TestEnumValues {
+ type Err = String;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s {
+ "a" => Ok(TestEnumValues::A),
+ "b" => Ok(TestEnumValues::B),
+ "c" => Ok(TestEnumValues::C),
+ "D" => Ok(TestEnumValues::D),
+ "e" => Ok(TestEnumValues::E),
+ "f" => Ok(TestEnumValues::F),
+ "G" => Ok(TestEnumValues::G),
+ _ => Err(format!("'{}' cannot be parsed for TestEnumValues", s)),
+ }
+ }
+}
+
+impl fmt::Display for TestEnumValues {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ TestEnumValues::A => write!(f, "a"),
+ TestEnumValues::B => write!(f, "b"),
+ TestEnumValues::C => write!(f, "c"),
+ TestEnumValues::D => write!(f, "D"),
+ TestEnumValues::E => write!(f, "e"),
+ TestEnumValues::F => write!(f, "f"),
+ TestEnumValues::G => write!(f, "G"),
+ _ => Err(fmt::Error),
+ }
+ }
+}
+
+/// Returns the value of the property 'android.test.enum' if set.
+pub fn test_enum() -> Result<Option<TestEnumValues>> {
+ let result = match system_properties::read("android.test.enum") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'android.test.enum', returns 'Ok' if successful.
+pub fn set_test_enum(v: TestEnumValues) -> Result<()> {
+ let value = gen_parsers_and_formatters::format(&v);
+ system_properties::write("android.test.enum", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'ro.android.test.b' if set.
+pub fn test_boo_lea_n() -> Result<Option<bool>> {
+ let result = match system_properties::read("ro.android.test.b") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_bool(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'ro.android.test.b', returns 'Ok' if successful.
+pub fn set_test_boo_lea_n(v: bool) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_bool(&v);
+ system_properties::write("ro.android.test.b", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'android_os_test-long' if set.
+pub fn android_os_test_long() -> Result<Option<i64>> {
+ let result = match system_properties::read("android_os_test-long") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'android_os_test-long', returns 'Ok' if successful.
+pub fn set_android_os_test_long(v: i64) -> Result<()> {
+ let value = gen_parsers_and_formatters::format(&v);
+ system_properties::write("android_os_test-long", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_double_list' if set.
+pub fn test_double_list() -> Result<Option<Vec<f64>>> {
+ let result = match system_properties::read("test_double_list") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'test_double_list', returns 'Ok' if successful.
+pub fn set_test_double_list(v: &[f64]) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_list(v);
+ system_properties::write("test_double_list", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_list_int' if set.
+pub fn test_list_int() -> Result<Option<Vec<i32>>> {
+ let result = match system_properties::read("test_list_int") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'test_list_int', returns 'Ok' if successful.
+pub fn set_test_list_int(v: &[i32]) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_list(v);
+ system_properties::write("test_list_int", value.as_str()).map_err(SysPropError::SetError)
+}
+
+/// Returns the value of the property 'test_strlist' if set.
+#[deprecated]
+pub fn test_strlist() -> Result<Option<Vec<String>>> {
+ let result = match system_properties::read("test_strlist") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'test_strlist', returns 'Ok' if successful.
+#[deprecated]
+pub fn set_test_strlist(v: &[String]) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_list(v);
+ system_properties::write("test_strlist", value.as_str()).map_err(SysPropError::SetError)
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord)]
+pub enum ElValues {
+ Enu,
+ Mva,
+ Lue,
+}
+
+impl std::str::FromStr for ElValues {
+ type Err = String;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s {
+ "enu" => Ok(ElValues::Enu),
+ "mva" => Ok(ElValues::Mva),
+ "lue" => Ok(ElValues::Lue),
+ _ => Err(format!("'{}' cannot be parsed for ElValues", s)),
+ }
+ }
+}
+
+impl fmt::Display for ElValues {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ElValues::Enu => write!(f, "enu"),
+ ElValues::Mva => write!(f, "mva"),
+ ElValues::Lue => write!(f, "lue"),
+ _ => Err(fmt::Error),
+ }
+ }
+}
+
+/// Returns the value of the property 'el' if set.
+#[deprecated]
+pub fn el() -> Result<Option<Vec<ElValues>>> {
+ let result = match system_properties::read("el") {
+ Err(e) => Err(SysPropError::FetchError(e)),
+ Ok(Some(val)) => gen_parsers_and_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
+ Ok(None) => Ok(None),
+ };
+ result
+}
+
+/// Sets the value of the property 'el', returns 'Ok' if successful.
+#[deprecated]
+pub fn set_el(v: &[ElValues]) -> Result<()> {
+ let value = gen_parsers_and_formatters::format_list(v);
+ system_properties::write("el", value.as_str()).map_err(SysPropError::SetError)
+}
+
+)";
+
+} // namespace
+
+using namespace std::string_literals;
+
+TEST(SyspropTest, RustGenTest) {
+ TemporaryFile temp_file;
+
+ // strlen is optimized for constants, so don't worry about it.
+ ASSERT_EQ(write(temp_file.fd, kTestSyspropFile, strlen(kTestSyspropFile)),
+ strlen(kTestSyspropFile));
+ close(temp_file.fd);
+ temp_file.fd = -1;
+
+ TemporaryDir temp_dir;
+
+ std::pair<sysprop::Scope, const char*> tests[] = {
+ {sysprop::Scope::Internal, kExpectedInternalOutput},
+ {sysprop::Scope::Public, kExpectedPublicOutput},
+ };
+
+ for (auto [scope, expected_output] : tests) {
+ std::string rust_output_path = temp_dir.path + "/lib.rs"s;
+
+ ASSERT_RESULT_OK(GenerateRustLibrary(temp_file.path, scope, temp_dir.path));
+
+ std::string rust_output;
+ ASSERT_TRUE(
+ android::base::ReadFileToString(rust_output_path, &rust_output, true));
+ EXPECT_EQ(rust_output, expected_output);
+
+ unlink(rust_output.c_str());
+ rmdir((temp_dir.path + "/com/somecompany"s).c_str());
+ rmdir((temp_dir.path + "/com"s).c_str());
+ }
+}
\ No newline at end of file