You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
527 lines
16 KiB
527 lines
16 KiB
/*
|
|
* 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>
|
|
[[maybe_unused]] static bool SetProp(const char* key, const char* value) {
|
|
return __system_property_set(key, value) == 0;
|
|
}
|
|
#else
|
|
#include <android-base/properties.h>
|
|
[[maybe_unused]] static bool SetProp(const char* key, const char* value) {
|
|
android::base::SetProperty(key, value);
|
|
return true;
|
|
}
|
|
#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::uint32_t> DoParse(const char* str) {
|
|
std::uint32_t ret;
|
|
return android::base::ParseUint(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<std::uint64_t> DoParse(const char* str) {
|
|
std::uint64_t ret;
|
|
return android::base::ParseUint(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::uint32_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<std::uint64_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, const char* legacy = nullptr) {
|
|
std::string value;
|
|
#ifdef __BIONIC__
|
|
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<std::string*>(cookie) = value;
|
|
}, &value);
|
|
}
|
|
#else
|
|
value = android::base::GetProperty(key, "");
|
|
#endif
|
|
if (value.empty() && legacy) {
|
|
ALOGV("prop %s doesn't exist; fallback to legacy prop %s", key, legacy);
|
|
return GetProp<T>(legacy);
|
|
}
|
|
return TryParse<T>(value.c_str());
|
|
}
|
|
|
|
)";
|
|
|
|
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::UInt:
|
|
return "std::optional<std::uint32_t>";
|
|
case sysprop::ULong:
|
|
return "std::optional<std::uint64_t>";
|
|
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) + ">>";
|
|
case sysprop::UIntList:
|
|
return "std::vector<std::optional<std::uint32_t>>";
|
|
case sysprop::ULongList:
|
|
return "std::vector<std::optional<std::uint64_t>>";
|
|
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 : ParseEnumValues(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);
|
|
std::string prop_name = prop.prop_name();
|
|
std::string legacy_name = prop.legacy_prop_name();
|
|
|
|
writer.Write("%s %s() {\n", prop_type.c_str(), prop_id.c_str());
|
|
writer.Indent();
|
|
if (legacy_name.empty()) {
|
|
writer.Write("return GetProp<%s>(\"%s\");\n", prop_type.c_str(),
|
|
prop_name.c_str());
|
|
} else {
|
|
writer.Write("return GetProp<%s>(\"%s\", \"%s\");\n", prop_type.c_str(),
|
|
prop_name.c_str(), legacy_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 SetProp(\"%s\", %s);\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 {};
|
|
}
|