1 /*
2 * Copyright 2019 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 #include <unistd.h>
18 #include <cerrno>
19 #include <cstdio>
20 #include <filesystem>
21 #include <fstream>
22 #include <iomanip>
23 #include <iostream>
24 #include <queue>
25 #include <regex>
26 #include <sstream>
27 #include <vector>
28
29 #include "declarations.h"
30 #include "struct_parser_generator.h"
31
32 #include "language_y.h"
33
34
35 int yylex_init(void**);
36 int yylex_destroy(void*);
37 void yyset_debug(int, void*);
38 void yyset_in(FILE*, void*);
39
40 bool generate_cpp_headers_one_file(
41 const Declarations& decls,
42 const std::filesystem::path& input_file,
43 const std::filesystem::path& include_dir,
44 const std::filesystem::path& out_dir,
45 const std::string& root_namespace);
46
47 bool generate_pybind11_sources_one_file(
48 const Declarations& decls,
49 const std::filesystem::path& input_file,
50 const std::filesystem::path& include_dir,
51 const std::filesystem::path& out_dir,
52 const std::string& root_namespace,
53 size_t num_shards);
54
55 bool generate_rust_source_one_file(
56 const Declarations& decls,
57 const std::filesystem::path& input_file,
58 const std::filesystem::path& include_dir,
59 const std::filesystem::path& out_dir,
60 const std::string& root_namespace);
61
parse_declarations_one_file(const std::filesystem::path & input_file,Declarations * declarations)62 bool parse_declarations_one_file(const std::filesystem::path& input_file, Declarations* declarations) {
63 void* scanner;
64 yylex_init(&scanner);
65
66 FILE* in_file = fopen(input_file.string().c_str(), "r");
67 if (in_file == nullptr) {
68 std::cerr << "can't open " << input_file << ": " << strerror(errno) << std::endl;
69 return false;
70 }
71
72 yyset_in(in_file, scanner);
73
74 int ret = yy::parser(scanner, declarations).parse();
75 if (ret != 0) {
76 std::cerr << "yylex parsing failed: returned " << ret << std::endl;
77 return false;
78 }
79
80 yylex_destroy(scanner);
81
82 fclose(in_file);
83
84 // Set endianess before returning
85 for (auto& s : declarations->type_defs_queue_) {
86 if (s.second->GetDefinitionType() == TypeDef::Type::STRUCT) {
87 auto* struct_def = static_cast<StructDef*>(s.second);
88 struct_def->SetEndianness(declarations->is_little_endian);
89 }
90 }
91
92 for (auto& packet_def : declarations->packet_defs_queue_) {
93 packet_def.second->SetEndianness(declarations->is_little_endian);
94 if (packet_def.second->parent_ != nullptr) {
95 packet_def.second->parent_->children_.push_back(packet_def.second);
96 }
97 }
98
99 return true;
100 }
101
102 // TODO(b/141583809): stop leaks
__asan_default_options()103 extern "C" const char* __asan_default_options() {
104 return "detect_leaks=0";
105 }
106
usage(const char * prog)107 void usage(const char* prog) {
108 auto& ofs = std::cerr;
109
110 ofs << "Usage: " << prog << " [OPTIONS] file1 file2..." << std::endl;
111
112 ofs << std::setw(24) << "--out= ";
113 ofs << "Root directory for generated output (relative to cwd)." << std::endl;
114
115 ofs << std::setw(24) << "--include= ";
116 ofs << "Generate namespaces relative to this path per file." << std::endl;
117
118 ofs << std::setw(24) << "--root_namespace= ";
119 ofs << "Change root namespace (default = bluetooth)." << std::endl;
120
121 ofs << std::setw(24) << "--source_root= ";
122 ofs << "Root path to the source directory. Find input files relative to this." << std::endl;
123
124 ofs << std::setw(24) << "--num_shards= ";
125 ofs << "Number of shards per output pybind11 cc file." << std::endl;
126 }
127
main(int argc,const char ** argv)128 int main(int argc, const char** argv) {
129 std::filesystem::path out_dir;
130 std::filesystem::path include_dir;
131 std::filesystem::path cwd = std::filesystem::current_path();
132 std::filesystem::path source_root = cwd;
133 std::string root_namespace = "bluetooth";
134 // Number of shards per output pybind11 cc file
135 size_t num_shards = 1;
136 bool generate_rust = false;
137 std::queue<std::filesystem::path> input_files;
138
139 const std::string arg_out = "--out=";
140 const std::string arg_include = "--include=";
141 const std::string arg_namespace = "--root_namespace=";
142 const std::string arg_num_shards = "--num_shards=";
143 const std::string arg_rust = "--rust";
144 const std::string arg_source_root = "--source_root=";
145
146 // Parse the source root first (if it exists) since it will be used for other
147 // paths.
148 for (int i = 1; i < argc; i++) {
149 std::string arg = argv[i];
150 if (arg.find(arg_source_root) == 0) {
151 source_root = std::filesystem::path(arg.substr(arg_source_root.size()));
152 }
153 }
154
155 for (int i = 1; i < argc; i++) {
156 std::string arg = argv[i];
157 if (arg.find(arg_out) == 0) {
158 out_dir = cwd / std::filesystem::path(arg.substr(arg_out.size()));
159 } else if (arg.find(arg_include) == 0) {
160 include_dir = source_root / std::filesystem::path(arg.substr(arg_include.size()));
161 } else if (arg.find(arg_namespace) == 0) {
162 root_namespace = arg.substr(arg_namespace.size());
163 } else if (arg.find(arg_num_shards) == 0) {
164 num_shards = std::stoul(arg.substr(arg_num_shards.size()));
165 } else if (arg.find(arg_rust) == 0) {
166 generate_rust = true;
167 } else if (arg.find(arg_source_root) == 0) {
168 // Do nothing (just don't treat it as input_files)
169 } else {
170 input_files.emplace(source_root / std::filesystem::path(arg));
171 }
172 }
173 if (out_dir == std::filesystem::path() || include_dir == std::filesystem::path() || num_shards == 0) {
174 usage(argv[0]);
175 return 1;
176 }
177
178 std::cout << "out dir: " << out_dir << std::endl;
179
180 while (!input_files.empty()) {
181 Declarations declarations;
182 std::cout << "parsing: " << input_files.front() << std::endl;
183 if (!parse_declarations_one_file(input_files.front(), &declarations)) {
184 std::cerr << "Cannot parse " << input_files.front() << " correctly" << std::endl;
185 return 2;
186 }
187 if (generate_rust) {
188 std::cout << "generating rust" << std::endl;
189 if (!generate_rust_source_one_file(declarations, input_files.front(), include_dir, out_dir, root_namespace)) {
190 std::cerr << "Didn't generate rust source for " << input_files.front() << std::endl;
191 return 5;
192 }
193 } else {
194 std::cout << "generating c++ and pybind11" << std::endl;
195 if (!generate_cpp_headers_one_file(declarations, input_files.front(), include_dir, out_dir, root_namespace)) {
196 std::cerr << "Didn't generate cpp headers for " << input_files.front() << std::endl;
197 return 3;
198 }
199 if (!generate_pybind11_sources_one_file(
200 declarations, input_files.front(), include_dir, out_dir, root_namespace, num_shards)) {
201 std::cerr << "Didn't generate pybind11 sources for " << input_files.front() << std::endl;
202 return 4;
203 }
204 }
205 input_files.pop();
206 }
207
208 return 0;
209 }
210