/* * 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 #include #include #include "CodeWriter.h" #include "Common.h" #include "sysprop.pb.h" using android::base::Result; namespace { constexpr const char* kDocs = R"(//! Autogenerated system property accessors. //! //! This is an autogenerated module. The module contains methods for typed access to //! Android system properties.)"; constexpr const char* kRustFileImports = R"(use std::fmt; use rustutils::system_properties::{self, error::SysPropError, parsers_formatters};)"; constexpr const char* kIndent = " "; constexpr const char* kDeprecated = "#[deprecated]"; 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"; case sysprop::IntegerList: return "Vec"; case sysprop::UIntList: return "Vec"; case sysprop::LongList: return "Vec"; case sysprop::ULongList: return "Vec"; case sysprop::DoubleList: return "Vec"; case sysprop::StringList: return "Vec"; 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 "parsers_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 "parsers_formatters::parse"; case sysprop::BooleanList: return "parsers_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 "parsers_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 "parsers_formatters::format_bool_as_int"; } return "parsers_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 "parsers_formatters::format"; case sysprop::BooleanList: if (prop.integer_as_bool()) { return "parsers_formatters::format_bool_list_as_int"; } return "parsers_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 "parsers_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); 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("#[allow(missing_docs)]\n"); 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 {\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.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() -> std::result::Result, SysPropError> {\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) -> std::result::Result<(), SysPropError> {\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 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 + "/mod.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); } return {}; }