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 <regex>
27 #include <string>
28
29 #include "CodeWriter.h"
30 #include "Common.h"
31 #include "sysprop.pb.h"
32
33 namespace {
34
35 constexpr const char* kIndent = " ";
36
37 constexpr const char* kCppHeaderIncludes =
38 R"(#include <cstdint>
39 #include <optional>
40 #include <string>
41 #include <vector>
42
43 )";
44
45 constexpr const char* kCppSourceIncludes =
46 R"(#include <cctype>
47 #include <cerrno>
48 #include <cstdio>
49 #include <cstring>
50 #include <limits>
51 #include <utility>
52
53 #include <strings.h>
54 #include <sys/system_properties.h>
55
56 #include <android-base/parseint.h>
57 #include <log/log.h>
58
59 )";
60
61 constexpr const char* kCppParsersAndFormatters =
62 R"(template <typename T> constexpr bool is_vector = false;
63
64 template <typename T> constexpr bool is_vector<std::vector<T>> = true;
65
66 template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) {
67 static constexpr const char* kYes[] = {"1", "true"};
68 static constexpr const char* kNo[] = {"0", "false"};
69
70 for (const char* yes : kYes) {
71 if (strcasecmp(yes, str) == 0) return std::make_optional(true);
72 }
73
74 for (const char* no : kNo) {
75 if (strcasecmp(no, str) == 0) return std::make_optional(false);
76 }
77
78 return std::nullopt;
79 }
80
81 template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) {
82 std::int32_t ret;
83 return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
84 }
85
86 template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) {
87 std::int64_t ret;
88 return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
89 }
90
91 template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) {
92 int old_errno = errno;
93 errno = 0;
94 char* end;
95 double ret = std::strtod(str, &end);
96 if (errno != 0) {
97 return std::nullopt;
98 }
99 if (str == end || *end != '\0') {
100 errno = EINVAL;
101 return std::nullopt;
102 }
103 errno = old_errno;
104 return std::make_optional(ret);
105 }
106
107 template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) {
108 return *str == '\0' ? std::nullopt : std::make_optional(str);
109 }
110
111 template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) {
112 Vec ret;
113 const char* p = str;
114 for (;;) {
115 const char* found = p;
116 while (*found != '\0' && *found != ',') {
117 ++found;
118 }
119 std::string value(p, found);
120 ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str()));
121 if (*found == '\0') break;
122 p = found + 1;
123 }
124 return ret;
125 }
126
127 template <typename T> inline T TryParse(const char* str) {
128 if constexpr(is_vector<T>) {
129 return DoParseList<T>(str);
130 } else {
131 return DoParse<T>(str);
132 }
133 }
134
135 [[maybe_unused]] std::string FormatValue(const std::optional<std::int32_t>& value) {
136 return value ? std::to_string(*value) : "";
137 }
138
139 [[maybe_unused]] std::string FormatValue(const std::optional<std::int64_t>& value) {
140 return value ? std::to_string(*value) : "";
141 }
142
143 [[maybe_unused]] std::string FormatValue(const std::optional<double>& value) {
144 if (!value) return "";
145 char buf[1024];
146 std::sprintf(buf, "%.*g", std::numeric_limits<double>::max_digits10, *value);
147 return buf;
148 }
149
150 [[maybe_unused]] std::string FormatValue(const std::optional<bool>& value) {
151 return value ? (*value ? "true" : "false") : "";
152 }
153
154 template <typename T>
155 [[maybe_unused]] std::string FormatValue(const std::vector<T>& value) {
156 if (value.empty()) return "";
157
158 std::string ret;
159 bool first = true;
160
161 for (auto&& element : value) {
162 if (!first) ret += ",";
163 else first = false;
164 if constexpr(std::is_same_v<T, std::optional<std::string>>) {
165 if (element) ret += *element;
166 } else {
167 ret += FormatValue(element);
168 }
169 }
170
171 return ret;
172 }
173
174 template <typename T>
175 T GetProp(const char* key) {
176 T ret;
177 auto pi = __system_property_find(key);
178 if (pi != nullptr) {
179 __system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) {
180 *static_cast<T*>(cookie) = TryParse<T>(value);
181 }, &ret);
182 }
183 return ret;
184 }
185
186 )";
187
188 const std::regex kRegexDot{"\\."};
189 const std::regex kRegexUnderscore{"_"};
190
191 std::string GetCppEnumName(const sysprop::Property& prop);
192 std::string GetCppPropTypeName(const sysprop::Property& prop);
193 std::string GetCppNamespace(const sysprop::Properties& props);
194
195 std::string GenerateHeader(const sysprop::Properties& props,
196 sysprop::Scope scope);
197 std::string GenerateSource(const sysprop::Properties& props,
198 const std::string& include_name);
199
GetCppEnumName(const sysprop::Property & prop)200 std::string GetCppEnumName(const sysprop::Property& prop) {
201 return ApiNameToIdentifier(prop.api_name()) + "_values";
202 }
203
GetCppPropTypeName(const sysprop::Property & prop)204 std::string GetCppPropTypeName(const sysprop::Property& prop) {
205 switch (prop.type()) {
206 case sysprop::Boolean:
207 return "std::optional<bool>";
208 case sysprop::Integer:
209 return "std::optional<std::int32_t>";
210 case sysprop::Long:
211 return "std::optional<std::int64_t>";
212 case sysprop::Double:
213 return "std::optional<double>";
214 case sysprop::String:
215 return "std::optional<std::string>";
216 case sysprop::Enum:
217 return "std::optional<" + GetCppEnumName(prop) + ">";
218 case sysprop::BooleanList:
219 return "std::vector<std::optional<bool>>";
220 case sysprop::IntegerList:
221 return "std::vector<std::optional<std::int32_t>>";
222 case sysprop::LongList:
223 return "std::vector<std::optional<std::int64_t>>";
224 case sysprop::DoubleList:
225 return "std::vector<std::optional<double>>";
226 case sysprop::StringList:
227 return "std::vector<std::optional<std::string>>";
228 case sysprop::EnumList:
229 return "std::vector<std::optional<" + GetCppEnumName(prop) + ">>";
230 default:
231 __builtin_unreachable();
232 }
233 }
234
GetCppNamespace(const sysprop::Properties & props)235 std::string GetCppNamespace(const sysprop::Properties& props) {
236 return std::regex_replace(props.module(), kRegexDot, "::");
237 }
238
GenerateHeader(const sysprop::Properties & props,sysprop::Scope scope)239 std::string GenerateHeader(const sysprop::Properties& props,
240 sysprop::Scope scope) {
241 CodeWriter writer(kIndent);
242
243 writer.Write("%s", kGeneratedFileFooterComments);
244
245 writer.Write("#pragma once\n\n");
246 writer.Write("%s", kCppHeaderIncludes);
247
248 std::string cpp_namespace = GetCppNamespace(props);
249 writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
250
251 bool first = true;
252
253 for (int i = 0; i < props.prop_size(); ++i) {
254 const sysprop::Property& prop = props.prop(i);
255
256 // Scope: Internal > System > Public
257 if (prop.scope() > scope) continue;
258
259 if (!first) {
260 writer.Write("\n");
261 } else {
262 first = false;
263 }
264
265 std::string prop_id = ApiNameToIdentifier(prop.api_name());
266 std::string prop_type = GetCppPropTypeName(prop);
267
268 if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
269 writer.Write("enum class %s {\n", GetCppEnumName(prop).c_str());
270 writer.Indent();
271 for (const std::string& name :
272 android::base::Split(prop.enum_values(), "|")) {
273 writer.Write("%s,\n", ToUpper(name).c_str());
274 }
275 writer.Dedent();
276 writer.Write("};\n\n");
277 }
278
279 writer.Write("%s %s();\n", prop_type.c_str(), prop_id.c_str());
280 if (prop.access() != sysprop::Readonly && scope == sysprop::Internal) {
281 writer.Write("bool %s(const %s& value);\n", prop_id.c_str(),
282 prop_type.c_str());
283 }
284 }
285
286 writer.Write("\n} // namespace %s\n", cpp_namespace.c_str());
287
288 return writer.Code();
289 }
290
GenerateSource(const sysprop::Properties & props,const std::string & include_name)291 std::string GenerateSource(const sysprop::Properties& props,
292 const std::string& include_name) {
293 CodeWriter writer(kIndent);
294 writer.Write("%s", kGeneratedFileFooterComments);
295 writer.Write("#include <%s>\n\n", include_name.c_str());
296 writer.Write("%s", kCppSourceIncludes);
297
298 std::string cpp_namespace = GetCppNamespace(props);
299
300 writer.Write("namespace {\n\n");
301 writer.Write("using namespace %s;\n\n", cpp_namespace.c_str());
302 writer.Write("template <typename T> T DoParse(const char* str);\n\n");
303
304 for (int i = 0; i < props.prop_size(); ++i) {
305 const sysprop::Property& prop = props.prop(i);
306 if (prop.type() != sysprop::Enum && prop.type() != sysprop::EnumList) {
307 continue;
308 }
309
310 std::string prop_id = ApiNameToIdentifier(prop.api_name());
311 std::string enum_name = GetCppEnumName(prop);
312
313 writer.Write("constexpr const std::pair<const char*, %s> %s_list[] = {\n",
314 enum_name.c_str(), prop_id.c_str());
315 writer.Indent();
316 for (const std::string& name :
317 android::base::Split(prop.enum_values(), "|")) {
318 writer.Write("{\"%s\", %s::%s},\n", name.c_str(), enum_name.c_str(),
319 ToUpper(name).c_str());
320 }
321 writer.Dedent();
322 writer.Write("};\n\n");
323
324 writer.Write("template <>\n");
325 writer.Write("std::optional<%s> DoParse(const char* str) {\n",
326 enum_name.c_str());
327 writer.Indent();
328 writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
329 writer.Indent();
330 writer.Write("if (strcmp(str, name) == 0) {\n");
331 writer.Indent();
332 writer.Write("return val;\n");
333 writer.Dedent();
334 writer.Write("}\n");
335 writer.Dedent();
336 writer.Write("}\n");
337 writer.Write("return std::nullopt;\n");
338 writer.Dedent();
339 writer.Write("}\n\n");
340
341 if (prop.access() != sysprop::Readonly) {
342 writer.Write("std::string FormatValue(std::optional<%s> value) {\n",
343 enum_name.c_str());
344 writer.Indent();
345 writer.Write("if (!value) return \"\";\n");
346 writer.Write("for (auto [name, val] : %s_list) {\n", prop_id.c_str());
347 writer.Indent();
348 writer.Write("if (val == *value) {\n");
349 writer.Indent();
350 writer.Write("return name;\n");
351 writer.Dedent();
352 writer.Write("}\n");
353 writer.Dedent();
354 writer.Write("}\n");
355
356 writer.Write(
357 "LOG_ALWAYS_FATAL(\"Invalid value %%d for property %s\", "
358 "static_cast<std::int32_t>(*value));\n",
359 prop.prop_name().c_str());
360
361 writer.Write("__builtin_unreachable();\n");
362 writer.Dedent();
363 writer.Write("}\n\n");
364 }
365 }
366 writer.Write("%s", kCppParsersAndFormatters);
367 writer.Write("} // namespace\n\n");
368
369 writer.Write("namespace %s {\n\n", cpp_namespace.c_str());
370
371 for (int i = 0; i < props.prop_size(); ++i) {
372 if (i > 0) writer.Write("\n");
373
374 const sysprop::Property& prop = props.prop(i);
375 std::string prop_id = ApiNameToIdentifier(prop.api_name());
376 std::string prop_type = GetCppPropTypeName(prop);
377
378 writer.Write("%s %s() {\n", prop_type.c_str(), prop_id.c_str());
379 writer.Indent();
380 writer.Write("return GetProp<%s>(\"%s\");\n", prop_type.c_str(),
381 prop.prop_name().c_str());
382 writer.Dedent();
383 writer.Write("}\n");
384
385 if (prop.access() != sysprop::Readonly) {
386 writer.Write("\nbool %s(const %s& value) {\n", prop_id.c_str(),
387 prop_type.c_str());
388 writer.Indent();
389
390 const char* format_expr = "FormatValue(value).c_str()";
391
392 // Specialized formatters here
393 if (prop.type() == sysprop::String) {
394 format_expr = "value ? value->c_str() : \"\"";
395 } else if (prop.integer_as_bool()) {
396 if (prop.type() == sysprop::Boolean) {
397 // optional<bool> -> optional<int>
398 format_expr = "FormatValue(std::optional<int>(value)).c_str()";
399 } else if (prop.type() == sysprop::BooleanList) {
400 // vector<optional<bool>> -> vector<optional<int>>
401 format_expr =
402 "FormatValue(std::vector<std::optional<int>>("
403 "value.begin(), value.end())).c_str()";
404 }
405 }
406
407 writer.Write("return __system_property_set(\"%s\", %s) == 0;\n",
408 prop.prop_name().c_str(), format_expr);
409 writer.Dedent();
410 writer.Write("}\n");
411 }
412 }
413
414 writer.Write("\n} // namespace %s\n", cpp_namespace.c_str());
415
416 return writer.Code();
417 }
418
419 } // namespace
420
GenerateCppFiles(const std::string & input_file_path,const std::string & header_dir,const std::string & system_header_dir,const std::string & source_output_dir,const std::string & include_name,std::string * err)421 bool GenerateCppFiles(const std::string& input_file_path,
422 const std::string& header_dir,
423 const std::string& system_header_dir,
424 const std::string& source_output_dir,
425 const std::string& include_name, std::string* err) {
426 sysprop::Properties props;
427
428 if (!ParseProps(input_file_path, &props, err)) {
429 return false;
430 }
431
432 std::string output_basename = android::base::Basename(input_file_path);
433
434 for (auto&& [scope, dir] : {
435 std::pair(sysprop::Internal, header_dir),
436 std::pair(sysprop::System, system_header_dir),
437 }) {
438 if (!IsDirectory(dir) && !CreateDirectories(dir)) {
439 *err = "Creating directory to " + dir + " failed: " + strerror(errno);
440 return false;
441 }
442
443 std::string path = dir + "/" + output_basename + ".h";
444 std::string result = GenerateHeader(props, scope);
445
446 if (!android::base::WriteStringToFile(result, path)) {
447 *err =
448 "Writing generated header to " + path + " failed: " + strerror(errno);
449 return false;
450 }
451 }
452
453 std::string source_path = source_output_dir + "/" + output_basename + ".cpp";
454 std::string source_result = GenerateSource(props, include_name);
455
456 if (!android::base::WriteStringToFile(source_result, source_path)) {
457 *err = "Writing generated source to " + source_path +
458 " failed: " + strerror(errno);
459 return false;
460 }
461
462 return true;
463 }
464