1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "sysprop_cpp_gen"
18
19 #include "CppGen.h"
20
21 #include <android-base/file.h>
22 #include <android-base/logging.h>
23 #include <android-base/stringprintf.h>
24 #include <android-base/strings.h>
25 #include <cerrno>
26 #include <filesystem>
27 #include <regex>
28 #include <string>
29
30 #include "CodeWriter.h"
31 #include "Common.h"
32 #include "sysprop.pb.h"
33
34 using android::base::Result;
35
36 namespace {
37
38 constexpr const char* kIndent = " ";
39
40 constexpr const char* kCppHeaderIncludes =
41 R"(#include <cstdint>
42 #include <optional>
43 #include <string>
44 #include <vector>
45
46 )";
47
48 constexpr const char* kCppSourceIncludes =
49 R"(#include <cctype>
50 #include <cerrno>
51 #include <cstdio>
52 #include <cstring>
53 #include <limits>
54 #include <utility>
55
56 #include <strings.h>
57 #ifdef __BIONIC__
58 #include <sys/system_properties.h>
59 [[maybe_unused]] static bool SetProp(const char* key, const char* value) {
60 return __system_property_set(key, value) == 0;
61 }
62 #else
63 #include <android-base/properties.h>
64 [[maybe_unused]] static bool SetProp(const char* key, const char* value) {
65 android::base::SetProperty(key, value);
66 return true;
67 }
68 #endif
69
70 #include <android-base/parseint.h>
71 #include <log/log.h>
72
73 )";
74
75 constexpr const char* kCppParsersAndFormatters =
76 R"(template <typename T> constexpr bool is_vector = false;
77
78 template <typename T> constexpr bool is_vector<std::vector<T>> = true;
79
80 template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) {
81 static constexpr const char* kYes[] = {"1", "true"};
82 static constexpr const char* kNo[] = {"0", "false"};
83
84 for (const char* yes : kYes) {
85 if (strcasecmp(yes, str) == 0) return std::make_optional(true);
86 }
87
88 for (const char* no : kNo) {
89 if (strcasecmp(no, str) == 0) return std::make_optional(false);
90 }
91
92 return std::nullopt;
93 }
94
95 template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) {
96 std::int32_t ret;
97 return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
98 }
99
100 template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) {
101 std::int64_t ret;
102 return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
103 }
104
105 template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) {
106 int old_errno = errno;
107 errno = 0;
108 char* end;
109 double ret = std::strtod(str, &end);
110 if (errno != 0) {
111 return std::nullopt;
112 }
113 if (str == end || *end != '\0') {
114 errno = EINVAL;
115 return std::nullopt;
116 }
117 errno = old_errno;
118 return std::make_optional(ret);
119 }
120
121 template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) {
122 return *str == '\0' ? std::nullopt : std::make_optional(str);
123 }
124
125 template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) {
126 Vec ret;
127 if (*str == '\0') return ret;
128 const char* p = str;
129 for (;;) {
130 const char* r = p;
131 std::string value;
132 while (*r != ',') {
133 if (*r == '\\') ++r;
134 if (*r == '\0') break;
135 value += *r++;
136 }
137 ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str()));
138 if (*r == '\0') break;
139 p = r + 1;
140 }
141 return ret;
142 }
143
144 template <typename T> inline T TryParse(const char* str) {
145 if constexpr(is_vector<T>) {
146 return DoParseList<T>(str);
147 } else {
148 return DoParse<T>(str);
149 }
150 }
151
152 [[maybe_unused]] std::string FormatValue(const std::optional<std::int32_t>& value) {
153 return value ? std::to_string(*value) : "";
154 }
155
156 [[maybe_unused]] std::string FormatValue(const std::optional<std::int64_t>& value) {
157 return value ? std::to_string(*value) : "";
158 }
159
160 [[maybe_unused]] std::string FormatValue(const std::optional<double>& value) {
161 if (!value) return "";
162 char buf[1024];
163 std::sprintf(buf, "%.*g", std::numeric_limits<double>::max_digits10, *value);
164 return buf;
165 }
166
167 [[maybe_unused]] std::string FormatValue(const std::optional<bool>& value) {
168 return value ? (*value ? "true" : "false") : "";
169 }
170
171 template <typename T>
172 [[maybe_unused]] std::string FormatValue(const std::vector<T>& value) {
173 if (value.empty()) return "";
174
175 std::string ret;
176 bool first = true;
177
178 for (auto&& element : value) {
179 if (!first) ret += ',';
180 else first = false;
181 if constexpr(std::is_same_v<T, std::optional<std::string>>) {
182 if (element) {
183 for (char c : *element) {
184 if (c == '\\' || c == ',') ret += '\\';
185 ret += c;
186 }
187 }
188 } else {
189 ret += FormatValue(element);
190 }
191 }
192
193 return ret;
194 }
195
196 template <typename T>
197 T GetProp(const char* key, const char* legacy = nullptr) {
198 std::string value;
199 #ifdef __BIONIC__
200 auto pi = __system_property_find(key);
201 if (pi != nullptr) {
202 __system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) {
203 *static_cast<std::string*>(cookie) = value;
204 }, &value);
205 }
206 #else
207 value = android::base::GetProperty(key, "");
208 #endif
209 if (value.empty() && legacy) {
210 ALOGD("prop %s doesn't exist; fallback to legacy prop %s", key, legacy);
211 return GetProp<T>(legacy);
212 }
213 return TryParse<T>(value.c_str());
214 }
215
216 )";
217
218 const std::regex kRegexDot{"\\."};
219 const std::regex kRegexUnderscore{"_"};
220
221 std::string GetCppEnumName(const sysprop::Property& prop);
222 std::string GetCppPropTypeName(const sysprop::Property& prop);
223 std::string GetCppNamespace(const sysprop::Properties& props);
224
225 std::string GenerateHeader(const sysprop::Properties& props,
226 sysprop::Scope scope);
227 std::string GenerateSource(const sysprop::Properties& props,
228 const std::string& include_name);
229
GetCppEnumName(const sysprop::Property & prop)230 std::string GetCppEnumName(const sysprop::Property& prop) {
231 return ApiNameToIdentifier(prop.api_name()) + "_values";
232 }
233
234 std::string GetCppPropTypeName(const sysprop::Property& prop) {
235 switch (prop.type()) {
236 case sysprop::Boolean:
237 return "std::optional<bool>";
238 case sysprop::Integer:
239 return "std::optional<std::int32_t>";
240 case sysprop::Long:
241 return "std::optional<std::int64_t>";
242 case sysprop::Double:
243 return "std::optional<double>";
244 case sysprop::String:
245 return "std::optional<std::string>";
246 case sysprop::Enum:
247 return "std::optional<" + GetCppEnumName(prop) + ">";
248 case sysprop::BooleanList:
249 return "std::vector<std::optional<bool>>";
250 case sysprop::IntegerList:
251 return "std::vector<std::optional<std::int32_t>>";
252 case sysprop::LongList:
253 return "std::vector<std::optional<std::int64_t>>";
254 case sysprop::DoubleList:
255 return "std::vector<std::optional<double>>";
256 case sysprop::StringList:
257 return "std::vector<std::optional<std::string>>";
258 case sysprop::EnumList:
259 return "std::vector<std::optional<" + GetCppEnumName(prop) + ">>";
260 default:
261 __builtin_unreachable();
262 }
263 }
264
265 std::string GetCppNamespace(const sysprop::Properties& props) {
266 return std::regex_replace(props.module(), kRegexDot, "::");
267 }
268
269 std::string GenerateHeader(const sysprop::Properties& props,
270 sysprop::Scope scope) {
271 CodeWriter writer(kIndent);
272
273 writer.Write("%s", kGeneratedFileFooterComments);
274
275 writer.Write("#pragma once\n\n");
276 writer.Write("%s", kCppHeaderIncludes);
277
278 std::string cpp_namespace = GetCppNamespace(props);
279 writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
280
281 bool first = true;
282
283 for (int i = 0; i < props.prop_size(); ++i) {
284 const sysprop::Property& prop = props.prop(i);
285
286 // Scope: Internal > Public
287 if (prop.scope() > scope) continue;
288
289 if (!first) {
290 writer.Write("\n");
291 } else {
292 first = false;
293 }
294
295 std::string prop_id = ApiNameToIdentifier(prop.api_name());
296 std::string prop_type = GetCppPropTypeName(prop);
297
298 if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
299 writer.Write("enum class %s {\n", GetCppEnumName(prop).c_str());
300 writer.Indent();
301 for (const std::string& name :
302 android::base::Split(prop.enum_values(), "|")) {
303 writer.Write("%s,\n", ToUpper(name).c_str());
304 }
305 writer.Dedent();
306 writer.Write("};\n\n");
307 }
308
309 if (prop.deprecated()) writer.Write("[[deprecated]] ");
310 writer.Write("%s %s();\n", prop_type.c_str(), prop_id.c_str());
311 if (prop.access() != sysprop::Readonly) {
312 if (prop.deprecated()) writer.Write("[[deprecated]] ");
313 writer.Write("bool %s(const %s& value);\n", prop_id.c_str(),
314 prop_type.c_str());
315 }
316 }
317
318 writer.Write("\n} // namespace %s\n", cpp_namespace.c_str());
319
320 return writer.Code();
321 }
322
323 std::string GenerateSource(const sysprop::Properties& props,
324 const std::string& include_name) {
325 CodeWriter writer(kIndent);
326 writer.Write("%s", kGeneratedFileFooterComments);
327 writer.Write("#include <%s>\n\n", include_name.c_str());
328 writer.Write("%s", kCppSourceIncludes);
329
330 std::string cpp_namespace = GetCppNamespace(props);
331
332 writer.Write("namespace {\n\n");
333 writer.Write("using namespace %s;\n\n", cpp_namespace.c_str());
334 writer.Write("template <typename T> T DoParse(const char* str);\n\n");
335
336 for (int i = 0; i < props.prop_size(); ++i) {
337 const sysprop::Property& prop = props.prop(i);
338 if (prop.type() != sysprop::Enum && prop.type() != sysprop::EnumList) {
339 continue;
340 }
341
342 std::string prop_id = ApiNameToIdentifier(prop.api_name());
343 std::string enum_name = GetCppEnumName(prop);
344
345 writer.Write("constexpr const std::pair<const char*, %s> %s_list[] = {\n",
346 enum_name.c_str(), prop_id.c_str());
347 writer.Indent();
348 for (const std::string& name : ParseEnumValues(prop.enum_values())) {
349 writer.Write("{\"%s\", %s::%s},\n", name.c_str(), enum_name.c_str(),
350 ToUpper(name).c_str());
351 }
352 writer.Dedent();
353 writer.Write("};\n\n");
354
355 writer.Write("template <>\n");
356 writer.Write("std::optional<%s> DoParse(const char* str) {\n",
357 enum_name.c_str());
358 writer.Indent();
359 writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
360 writer.Indent();
361 writer.Write("if (strcmp(str, name) == 0) {\n");
362 writer.Indent();
363 writer.Write("return val;\n");
364 writer.Dedent();
365 writer.Write("}\n");
366 writer.Dedent();
367 writer.Write("}\n");
368 writer.Write("return std::nullopt;\n");
369 writer.Dedent();
370 writer.Write("}\n\n");
371
372 if (prop.access() != sysprop::Readonly) {
373 writer.Write("std::string FormatValue(std::optional<%s> value) {\n",
374 enum_name.c_str());
375 writer.Indent();
376 writer.Write("if (!value) return \"\";\n");
377 writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
378 writer.Indent();
379 writer.Write("if (val == *value) {\n");
380 writer.Indent();
381 writer.Write("return name;\n");
382 writer.Dedent();
383 writer.Write("}\n");
384 writer.Dedent();
385 writer.Write("}\n");
386
387 writer.Write(
388 "LOG_ALWAYS_FATAL(\"Invalid value %%d for property %s\", "
389 "static_cast<std::int32_t>(*value));\n",
390 prop.prop_name().c_str());
391
392 writer.Write("__builtin_unreachable();\n");
393 writer.Dedent();
394 writer.Write("}\n\n");
395 }
396 }
397 writer.Write("%s", kCppParsersAndFormatters);
398 writer.Write("} // namespace\n\n");
399
400 writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
401
402 for (int i = 0; i < props.prop_size(); ++i) {
403 if (i > 0) writer.Write("\n");
404
405 const sysprop::Property& prop = props.prop(i);
406 std::string prop_id = ApiNameToIdentifier(prop.api_name());
407 std::string prop_type = GetCppPropTypeName(prop);
408 std::string prop_name = prop.prop_name();
409 std::string legacy_name = prop.legacy_prop_name();
410
411 writer.Write("%s %s() {\n", prop_type.c_str(), prop_id.c_str());
412 writer.Indent();
413 if (legacy_name.empty()) {
414 writer.Write("return GetProp<%s>(\"%s\");\n", prop_type.c_str(),
415 prop_name.c_str());
416 } else {
417 writer.Write("return GetProp<%s>(\"%s\", \"%s\");\n", prop_type.c_str(),
418 prop_name.c_str(), legacy_name.c_str());
419 }
420 writer.Dedent();
421 writer.Write("}\n");
422
423 if (prop.access() != sysprop::Readonly) {
424 writer.Write("\nbool %s(const %s& value) {\n", prop_id.c_str(),
425 prop_type.c_str());
426 writer.Indent();
427
428 const char* format_expr = "FormatValue(value).c_str()";
429
430 // Specialized formatters here
431 if (prop.type() == sysprop::String) {
432 format_expr = "value ? value->c_str() : \"\"";
433 } else if (prop.integer_as_bool()) {
434 if (prop.type() == sysprop::Boolean) {
435 // optional<bool> -> optional<int>
436 format_expr = "FormatValue(std::optional<int>(value)).c_str()";
437 } else if (prop.type() == sysprop::BooleanList) {
438 // vector<optional<bool>> -> vector<optional<int>>
439 format_expr =
440 "FormatValue(std::vector<std::optional<int>>("
441 "value.begin(), value.end())).c_str()";
442 }
443 }
444
445 writer.Write("return SetProp(\"%s\", %s) == 0;\n",
446 prop.prop_name().c_str(), format_expr);
447 writer.Dedent();
448 writer.Write("}\n");
449 }
450 }
451
452 writer.Write("\n} // namespace %s\n", cpp_namespace.c_str());
453
454 return writer.Code();
455 }
456
457 } // namespace
458
459 Result<void> GenerateCppFiles(const std::string& input_file_path,
460 const std::string& header_dir,
461 const std::string& public_header_dir,
462 const std::string& source_output_dir,
463 const std::string& include_name) {
464 sysprop::Properties props;
465
466 if (auto res = ParseProps(input_file_path); res.ok()) {
467 props = std::move(*res);
468 } else {
469 return res.error();
470 }
471
472 std::string output_basename = android::base::Basename(input_file_path);
473
474 for (auto&& [scope, dir] : {
475 std::pair(sysprop::Internal, header_dir),
476 std::pair(sysprop::Public, public_header_dir),
477 }) {
478 std::error_code ec;
479 std::filesystem::create_directories(dir, ec);
480 if (ec) {
481 return Errorf("Creating directory to {} failed: {}", dir, ec.message());
482 }
483
484 std::string path = dir + "/" + output_basename + ".h";
485 std::string result = GenerateHeader(props, scope);
486
487 if (!android::base::WriteStringToFile(result, path)) {
488 return ErrnoErrorf("Writing generated header to {} failed", path);
489 }
490 }
491
492 std::string source_path = source_output_dir + "/" + output_basename + ".cpp";
493 std::string source_result = GenerateSource(props, include_name);
494
495 if (!android::base::WriteStringToFile(source_result, source_path)) {
496 return ErrnoErrorf("Writing generated source to {} failed", source_path);
497 }
498
499 return {};
500 }
501