| /* |
| * Copyright (C) 2018 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_cpp_gen" |
| |
| #include "CppGen.h" |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <cerrno> |
| #include <filesystem> |
| #include <regex> |
| #include <string> |
| |
| #include "CodeWriter.h" |
| #include "Common.h" |
| #include "sysprop.pb.h" |
| |
| using android::base::Result; |
| |
| namespace { |
| |
| constexpr const char* kIndent = " "; |
| |
| constexpr const char* kCppHeaderIncludes = |
| R"(#include <cstdint> |
| #include <optional> |
| #include <string> |
| #include <vector> |
| |
| )"; |
| |
| constexpr const char* kCppSourceIncludes = |
| R"(#include <cctype> |
| #include <cerrno> |
| #include <cstdio> |
| #include <cstring> |
| #include <limits> |
| #include <utility> |
| |
| #include <strings.h> |
| #ifdef __BIONIC__ |
| #include <sys/system_properties.h> |
| #else |
| #include <android-base/properties.h> |
| static int __system_property_set(const char* key, const char* value) { |
| android::base::SetProperty(key, value); |
| return 0; |
| } |
| #endif |
| |
| #include <android-base/parseint.h> |
| #include <log/log.h> |
| |
| )"; |
| |
| constexpr const char* kCppParsersAndFormatters = |
| R"(template <typename T> constexpr bool is_vector = false; |
| |
| template <typename T> constexpr bool is_vector<std::vector<T>> = true; |
| |
| template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) { |
| static constexpr const char* kYes[] = {"1", "true"}; |
| static constexpr const char* kNo[] = {"0", "false"}; |
| |
| for (const char* yes : kYes) { |
| if (strcasecmp(yes, str) == 0) return std::make_optional(true); |
| } |
| |
| for (const char* no : kNo) { |
| if (strcasecmp(no, str) == 0) return std::make_optional(false); |
| } |
| |
| return std::nullopt; |
| } |
| |
| template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) { |
| std::int32_t ret; |
| return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt; |
| } |
| |
| template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) { |
| std::int64_t ret; |
| return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt; |
| } |
| |
| template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) { |
| int old_errno = errno; |
| errno = 0; |
| char* end; |
| double ret = std::strtod(str, &end); |
| if (errno != 0) { |
| return std::nullopt; |
| } |
| if (str == end || *end != '\0') { |
| errno = EINVAL; |
| return std::nullopt; |
| } |
| errno = old_errno; |
| return std::make_optional(ret); |
| } |
| |
| template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) { |
| return *str == '\0' ? std::nullopt : std::make_optional(str); |
| } |
| |
| template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) { |
| Vec ret; |
| if (*str == '\0') return ret; |
| const char* p = str; |
| for (;;) { |
| const char* r = p; |
| std::string value; |
| while (*r != ',') { |
| if (*r == '\\') ++r; |
| if (*r == '\0') break; |
| value += *r++; |
| } |
| ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str())); |
| if (*r == '\0') break; |
| p = r + 1; |
| } |
| return ret; |
| } |
| |
| template <typename T> inline T TryParse(const char* str) { |
| if constexpr(is_vector<T>) { |
| return DoParseList<T>(str); |
| } else { |
| return DoParse<T>(str); |
| } |
| } |
| |
| [[maybe_unused]] std::string FormatValue(const std::optional<std::int32_t>& value) { |
| return value ? std::to_string(*value) : ""; |
| } |
| |
| [[maybe_unused]] std::string FormatValue(const std::optional<std::int64_t>& value) { |
| return value ? std::to_string(*value) : ""; |
| } |
| |
| [[maybe_unused]] std::string FormatValue(const std::optional<double>& value) { |
| if (!value) return ""; |
| char buf[1024]; |
| std::sprintf(buf, "%.*g", std::numeric_limits<double>::max_digits10, *value); |
| return buf; |
| } |
| |
| [[maybe_unused]] std::string FormatValue(const std::optional<bool>& value) { |
| return value ? (*value ? "true" : "false") : ""; |
| } |
| |
| template <typename T> |
| [[maybe_unused]] std::string FormatValue(const std::vector<T>& value) { |
| if (value.empty()) return ""; |
| |
| std::string ret; |
| bool first = true; |
| |
| for (auto&& element : value) { |
| if (!first) ret += ','; |
| else first = false; |
| if constexpr(std::is_same_v<T, std::optional<std::string>>) { |
| if (element) { |
| for (char c : *element) { |
| if (c == '\\' || c == ',') ret += '\\'; |
| ret += c; |
| } |
| } |
| } else { |
| ret += FormatValue(element); |
| } |
| } |
| |
| return ret; |
| } |
| |
| template <typename T> |
| T GetProp(const char* key) { |
| #ifdef __BIONIC__ |
| T ret; |
| auto pi = __system_property_find(key); |
| if (pi != nullptr) { |
| __system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) { |
| *static_cast<T*>(cookie) = TryParse<T>(value); |
| }, &ret); |
| } |
| return ret; |
| #else |
| return TryParse<T>(android::base::GetProperty(key, "").c_str()); |
| #endif |
| } |
| |
| )"; |
| |
| const std::regex kRegexDot{"\\."}; |
| const std::regex kRegexUnderscore{"_"}; |
| |
| std::string GetCppEnumName(const sysprop::Property& prop); |
| std::string GetCppPropTypeName(const sysprop::Property& prop); |
| std::string GetCppNamespace(const sysprop::Properties& props); |
| |
| std::string GenerateHeader(const sysprop::Properties& props, |
| sysprop::Scope scope); |
| std::string GenerateSource(const sysprop::Properties& props, |
| const std::string& include_name); |
| |
| std::string GetCppEnumName(const sysprop::Property& prop) { |
| return ApiNameToIdentifier(prop.api_name()) + "_values"; |
| } |
| |
| std::string GetCppPropTypeName(const sysprop::Property& prop) { |
| switch (prop.type()) { |
| case sysprop::Boolean: |
| return "std::optional<bool>"; |
| case sysprop::Integer: |
| return "std::optional<std::int32_t>"; |
| case sysprop::Long: |
| return "std::optional<std::int64_t>"; |
| case sysprop::Double: |
| return "std::optional<double>"; |
| case sysprop::String: |
| return "std::optional<std::string>"; |
| case sysprop::Enum: |
| return "std::optional<" + GetCppEnumName(prop) + ">"; |
| case sysprop::BooleanList: |
| return "std::vector<std::optional<bool>>"; |
| case sysprop::IntegerList: |
| return "std::vector<std::optional<std::int32_t>>"; |
| case sysprop::LongList: |
| return "std::vector<std::optional<std::int64_t>>"; |
| case sysprop::DoubleList: |
| return "std::vector<std::optional<double>>"; |
| case sysprop::StringList: |
| return "std::vector<std::optional<std::string>>"; |
| case sysprop::EnumList: |
| return "std::vector<std::optional<" + GetCppEnumName(prop) + ">>"; |
| default: |
| __builtin_unreachable(); |
| } |
| } |
| |
| std::string GetCppNamespace(const sysprop::Properties& props) { |
| return std::regex_replace(props.module(), kRegexDot, "::"); |
| } |
| |
| std::string GenerateHeader(const sysprop::Properties& props, |
| sysprop::Scope scope) { |
| CodeWriter writer(kIndent); |
| |
| writer.Write("%s", kGeneratedFileFooterComments); |
| |
| writer.Write("#pragma once\n\n"); |
| writer.Write("%s", kCppHeaderIncludes); |
| |
| std::string cpp_namespace = GetCppNamespace(props); |
| writer.Write("namespace %s {\n\n", cpp_namespace.c_str()); |
| |
| bool first = true; |
| |
| for (int i = 0; i < props.prop_size(); ++i) { |
| const sysprop::Property& prop = props.prop(i); |
| |
| // Scope: Internal > Public |
| if (prop.scope() > scope) continue; |
| |
| if (!first) { |
| writer.Write("\n"); |
| } else { |
| first = false; |
| } |
| |
| std::string prop_id = ApiNameToIdentifier(prop.api_name()); |
| std::string prop_type = GetCppPropTypeName(prop); |
| |
| if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) { |
| writer.Write("enum class %s {\n", GetCppEnumName(prop).c_str()); |
| writer.Indent(); |
| for (const std::string& name : |
| android::base::Split(prop.enum_values(), "|")) { |
| writer.Write("%s,\n", ToUpper(name).c_str()); |
| } |
| writer.Dedent(); |
| writer.Write("};\n\n"); |
| } |
| |
| if (prop.deprecated()) writer.Write("[[deprecated]] "); |
| writer.Write("%s %s();\n", prop_type.c_str(), prop_id.c_str()); |
| if (prop.access() != sysprop::Readonly) { |
| if (prop.deprecated()) writer.Write("[[deprecated]] "); |
| writer.Write("bool %s(const %s& value);\n", prop_id.c_str(), |
| prop_type.c_str()); |
| } |
| } |
| |
| writer.Write("\n} // namespace %s\n", cpp_namespace.c_str()); |
| |
| return writer.Code(); |
| } |
| |
| std::string GenerateSource(const sysprop::Properties& props, |
| const std::string& include_name) { |
| CodeWriter writer(kIndent); |
| writer.Write("%s", kGeneratedFileFooterComments); |
| writer.Write("#include <%s>\n\n", include_name.c_str()); |
| writer.Write("%s", kCppSourceIncludes); |
| |
| std::string cpp_namespace = GetCppNamespace(props); |
| |
| writer.Write("namespace {\n\n"); |
| writer.Write("using namespace %s;\n\n", cpp_namespace.c_str()); |
| writer.Write("template <typename T> T DoParse(const char* str);\n\n"); |
| |
| for (int i = 0; i < props.prop_size(); ++i) { |
| const sysprop::Property& prop = props.prop(i); |
| if (prop.type() != sysprop::Enum && prop.type() != sysprop::EnumList) { |
| continue; |
| } |
| |
| std::string prop_id = ApiNameToIdentifier(prop.api_name()); |
| std::string enum_name = GetCppEnumName(prop); |
| |
| writer.Write("constexpr const std::pair<const char*, %s> %s_list[] = {\n", |
| enum_name.c_str(), prop_id.c_str()); |
| writer.Indent(); |
| for (const std::string& name : |
| android::base::Split(prop.enum_values(), "|")) { |
| writer.Write("{\"%s\", %s::%s},\n", name.c_str(), enum_name.c_str(), |
| ToUpper(name).c_str()); |
| } |
| writer.Dedent(); |
| writer.Write("};\n\n"); |
| |
| writer.Write("template <>\n"); |
| writer.Write("std::optional<%s> DoParse(const char* str) {\n", |
| enum_name.c_str()); |
| writer.Indent(); |
| writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str()); |
| writer.Indent(); |
| writer.Write("if (strcmp(str, name) == 0) {\n"); |
| writer.Indent(); |
| writer.Write("return val;\n"); |
| writer.Dedent(); |
| writer.Write("}\n"); |
| writer.Dedent(); |
| writer.Write("}\n"); |
| writer.Write("return std::nullopt;\n"); |
| writer.Dedent(); |
| writer.Write("}\n\n"); |
| |
| if (prop.access() != sysprop::Readonly) { |
| writer.Write("std::string FormatValue(std::optional<%s> value) {\n", |
| enum_name.c_str()); |
| writer.Indent(); |
| writer.Write("if (!value) return \"\";\n"); |
| writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str()); |
| writer.Indent(); |
| writer.Write("if (val == *value) {\n"); |
| writer.Indent(); |
| writer.Write("return name;\n"); |
| writer.Dedent(); |
| writer.Write("}\n"); |
| writer.Dedent(); |
| writer.Write("}\n"); |
| |
| writer.Write( |
| "LOG_ALWAYS_FATAL(\"Invalid value %%d for property %s\", " |
| "static_cast<std::int32_t>(*value));\n", |
| prop.prop_name().c_str()); |
| |
| writer.Write("__builtin_unreachable();\n"); |
| writer.Dedent(); |
| writer.Write("}\n\n"); |
| } |
| } |
| writer.Write("%s", kCppParsersAndFormatters); |
| writer.Write("} // namespace\n\n"); |
| |
| writer.Write("namespace %s {\n\n", cpp_namespace.c_str()); |
| |
| for (int i = 0; i < props.prop_size(); ++i) { |
| if (i > 0) writer.Write("\n"); |
| |
| const sysprop::Property& prop = props.prop(i); |
| std::string prop_id = ApiNameToIdentifier(prop.api_name()); |
| std::string prop_type = GetCppPropTypeName(prop); |
| |
| writer.Write("%s %s() {\n", prop_type.c_str(), prop_id.c_str()); |
| writer.Indent(); |
| writer.Write("return GetProp<%s>(\"%s\");\n", prop_type.c_str(), |
| prop.prop_name().c_str()); |
| writer.Dedent(); |
| writer.Write("}\n"); |
| |
| if (prop.access() != sysprop::Readonly) { |
| writer.Write("\nbool %s(const %s& value) {\n", prop_id.c_str(), |
| prop_type.c_str()); |
| writer.Indent(); |
| |
| const char* format_expr = "FormatValue(value).c_str()"; |
| |
| // Specialized formatters here |
| if (prop.type() == sysprop::String) { |
| format_expr = "value ? value->c_str() : \"\""; |
| } else if (prop.integer_as_bool()) { |
| if (prop.type() == sysprop::Boolean) { |
| // optional<bool> -> optional<int> |
| format_expr = "FormatValue(std::optional<int>(value)).c_str()"; |
| } else if (prop.type() == sysprop::BooleanList) { |
| // vector<optional<bool>> -> vector<optional<int>> |
| format_expr = |
| "FormatValue(std::vector<std::optional<int>>(" |
| "value.begin(), value.end())).c_str()"; |
| } |
| } |
| |
| writer.Write("return __system_property_set(\"%s\", %s) == 0;\n", |
| prop.prop_name().c_str(), format_expr); |
| writer.Dedent(); |
| writer.Write("}\n"); |
| } |
| } |
| |
| writer.Write("\n} // namespace %s\n", cpp_namespace.c_str()); |
| |
| return writer.Code(); |
| } |
| |
| } // namespace |
| |
| Result<void> GenerateCppFiles(const std::string& input_file_path, |
| const std::string& header_dir, |
| const std::string& public_header_dir, |
| const std::string& source_output_dir, |
| const std::string& include_name) { |
| sysprop::Properties props; |
| |
| if (auto res = ParseProps(input_file_path); res.ok()) { |
| props = std::move(*res); |
| } else { |
| return res.error(); |
| } |
| |
| std::string output_basename = android::base::Basename(input_file_path); |
| |
| for (auto&& [scope, dir] : { |
| std::pair(sysprop::Internal, header_dir), |
| std::pair(sysprop::Public, public_header_dir), |
| }) { |
| std::error_code ec; |
| std::filesystem::create_directories(dir, ec); |
| if (ec) { |
| return Errorf("Creating directory to {} failed: {}", dir, ec.message()); |
| } |
| |
| std::string path = dir + "/" + output_basename + ".h"; |
| std::string result = GenerateHeader(props, scope); |
| |
| if (!android::base::WriteStringToFile(result, path)) { |
| return ErrnoErrorf("Writing generated header to {} failed", path); |
| } |
| } |
| |
| std::string source_path = source_output_dir + "/" + output_basename + ".cpp"; |
| std::string source_result = GenerateSource(props, include_name); |
| |
| if (!android::base::WriteStringToFile(source_result, source_path)) { |
| return ErrnoErrorf("Writing generated source to {} failed", source_path); |
| } |
| |
| return {}; |
| } |