blob: a1e731405ec8769d8d2da957622b923cad7b62dd [file] [log] [blame]
/*
* 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 {};
}