blob: 2752078ce55dbd5d40dfc3b173741a0433ef8359 [file] [log] [blame]
Yifan Hong4d18bcc2017-04-07 21:47:16 +00001/*
2 * Copyright (C) 2017 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
Yifan Hong9aa63702017-05-16 16:37:50 -070017#include <getopt.h>
Yifan Hong4d18bcc2017-04-07 21:47:16 +000018#include <stdlib.h>
19#include <unistd.h>
20
21#include <fstream>
22#include <iostream>
23#include <unordered_map>
24#include <sstream>
25#include <string>
26
Yifan Hong79efa8a2017-07-06 14:10:28 -070027#include <vintf/KernelConfigParser.h>
Yifan Hong4d18bcc2017-04-07 21:47:16 +000028#include <vintf/parse_string.h>
29#include <vintf/parse_xml.h>
30
Yifan Hong79efa8a2017-07-06 14:10:28 -070031#define BUFFER_SIZE sysconf(_SC_PAGESIZE)
32
Yifan Hong4d18bcc2017-04-07 21:47:16 +000033namespace android {
34namespace vintf {
35
36/**
37 * Slurps the device manifest file and add build time flag to it.
38 */
39class AssembleVintf {
40public:
41 template<typename T>
42 static bool getFlag(const std::string& key, T* value) {
43 const char *envValue = getenv(key.c_str());
44 if (envValue == NULL) {
45 std::cerr << "Required " << key << " flag." << std::endl;
46 return false;
47 }
48
49 if (!parse(envValue, value)) {
50 std::cerr << "Cannot parse " << envValue << "." << std::endl;
51 return false;
52 }
53 return true;
54 }
55
Yifan Hong4650ad82017-05-01 17:28:02 -070056 static std::string read(std::basic_istream<char>& is) {
57 std::stringstream ss;
58 ss << is.rdbuf();
59 return ss.str();
60 }
61
Yifan Hong79efa8a2017-07-06 14:10:28 -070062 static bool parseFileForKernelConfigs(const std::string& path, std::vector<KernelConfig>* out) {
63 std::ifstream ifs{path};
64 if (!ifs.is_open()) {
65 std::cerr << "File '" << path << "' does not exist or cannot be read." << std::endl;
66 return false;
67 }
Yifan Hong02e94002017-07-10 15:41:56 -070068 KernelConfigParser parser(true /* processComments */, true /* relaxedFormat */);
Yifan Hong79efa8a2017-07-06 14:10:28 -070069 std::string content = read(ifs);
70 status_t err = parser.process(content.c_str(), content.size());
71 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -070072 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -070073 return false;
74 }
75 err = parser.finish();
76 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -070077 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -070078 return false;
79 }
80
81 for (auto& configPair : parser.configs()) {
82 out->push_back({});
83 KernelConfig& config = out->back();
84 config.first = std::move(configPair.first);
85 if (!parseKernelConfigTypedValue(configPair.second, &config.second)) {
86 std::cerr << "Unknown value type for key = '" << config.first << "', value = '"
87 << configPair.second << "'\n";
88 return false;
89 }
90 }
91 return true;
92 }
93
Yifan Hong9aa63702017-05-16 16:37:50 -070094 std::basic_ostream<char>& out() const {
95 return mOutFileRef == nullptr ? std::cout : *mOutFileRef;
96 }
97
98 bool assembleHalManifest(HalManifest* halManifest) {
Yifan Hong4650ad82017-05-01 17:28:02 -070099 std::string error;
Yifan Hong9aa63702017-05-16 16:37:50 -0700100
101 if (halManifest->mType == SchemaType::DEVICE) {
102 if (!getFlag("BOARD_SEPOLICY_VERS", &halManifest->device.mSepolicyVersion)) {
103 return false;
104 }
105 }
106
107 if (mOutputMatrix) {
108 CompatibilityMatrix generatedMatrix = halManifest->generateCompatibleMatrix();
109 if (!halManifest->checkCompatibility(generatedMatrix, &error)) {
110 std::cerr << "FATAL ERROR: cannot generate a compatible matrix: " << error
111 << std::endl;
112 }
113 out() << "<!-- \n"
114 " Autogenerated skeleton compatibility matrix. \n"
115 " Use with caution. Modify it to suit your needs.\n"
116 " All HALs are set to optional.\n"
117 " Many entries other than HALs are zero-filled and\n"
118 " require human attention. \n"
119 "-->\n"
120 << gCompatibilityMatrixConverter(generatedMatrix);
121 } else {
122 out() << gHalManifestConverter(*halManifest);
123 }
124 out().flush();
125
126 if (mCheckFile.is_open()) {
127 CompatibilityMatrix checkMatrix;
128 if (!gCompatibilityMatrixConverter(&checkMatrix, read(mCheckFile))) {
129 std::cerr << "Cannot parse check file as a compatibility matrix: "
130 << gCompatibilityMatrixConverter.lastError() << std::endl;
131 return false;
132 }
133 if (!halManifest->checkCompatibility(checkMatrix, &error)) {
134 std::cerr << "Not compatible: " << error << std::endl;
135 return false;
136 }
137 }
138
139 return true;
140 }
141
142 bool assembleCompatibilityMatrix(CompatibilityMatrix* matrix) {
143 std::string error;
144
145 KernelSepolicyVersion kernelSepolicyVers;
146 Version sepolicyVers;
147 if (matrix->mType == SchemaType::FRAMEWORK) {
148 if (!getFlag("BOARD_SEPOLICY_VERS", &sepolicyVers)) {
149 return false;
150 }
151 if (!getFlag("POLICYVERS", &kernelSepolicyVers)) {
152 return false;
153 }
Yifan Hong79efa8a2017-07-06 14:10:28 -0700154 for (const auto& pair : mKernels) {
155 std::vector<KernelConfig> configs;
156 if (!parseFileForKernelConfigs(pair.second, &configs)) {
157 return false;
158 }
159 bool added = false;
160 for (auto& e : matrix->framework.mKernels) {
161 if (e.minLts() == pair.first) {
162 e.mConfigs.insert(e.mConfigs.end(), configs.begin(), configs.end());
163 added = true;
164 break;
165 }
166 }
167 if (!added) {
168 matrix->framework.mKernels.push_back(
169 MatrixKernel{KernelVersion{pair.first}, std::move(configs)});
170 }
171 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700172 matrix->framework.mSepolicy =
173 Sepolicy(kernelSepolicyVers, {{sepolicyVers.majorVer, sepolicyVers.minorVer}});
174 }
175 out() << gCompatibilityMatrixConverter(*matrix);
176 out().flush();
177
178 if (mCheckFile.is_open()) {
179 HalManifest checkManifest;
180 if (!gHalManifestConverter(&checkManifest, read(mCheckFile))) {
181 std::cerr << "Cannot parse check file as a HAL manifest: "
182 << gHalManifestConverter.lastError() << std::endl;
183 return false;
184 }
185 if (!checkManifest.checkCompatibility(*matrix, &error)) {
186 std::cerr << "Not compatible: " << error << std::endl;
187 return false;
188 }
189 }
190
191 return true;
192 }
193
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700194 enum AssembleStatus { SUCCESS, FAIL_AND_EXIT, TRY_NEXT };
195 template <typename Schema, typename AssembleFunc>
196 AssembleStatus tryAssemble(const XmlConverter<Schema>& converter, const std::string& schemaName,
197 AssembleFunc assemble) {
198 Schema schema;
199 if (!converter(&schema, read(mInFiles.front()))) {
200 return TRY_NEXT;
201 }
202 auto firstType = schema.type();
203 for (auto it = mInFiles.begin() + 1; it != mInFiles.end(); ++it) {
204 Schema additionalSchema;
205 if (!converter(&additionalSchema, read(*it))) {
206 std::cerr << "File \"" << mInFilePaths[std::distance(mInFiles.begin(), it)]
207 << "\" is not a valid " << firstType << " " << schemaName
208 << " (but the first file is a valid " << firstType << " " << schemaName
209 << "). Error: " << converter.lastError() << std::endl;
210 return FAIL_AND_EXIT;
211 }
212 if (additionalSchema.type() != firstType) {
213 std::cerr << "File \"" << mInFilePaths[std::distance(mInFiles.begin(), it)]
214 << "\" is a " << additionalSchema.type() << " " << schemaName
215 << " (but a " << firstType << " " << schemaName << " is expected)."
216 << std::endl;
217 return FAIL_AND_EXIT;
218 }
219 schema.addAll(std::move(additionalSchema));
220 }
221 return assemble(&schema) ? SUCCESS : FAIL_AND_EXIT;
222 }
223
Yifan Hong9aa63702017-05-16 16:37:50 -0700224 bool assemble() {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700225 using std::placeholders::_1;
226 if (mInFiles.empty()) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700227 std::cerr << "Missing input file." << std::endl;
228 return false;
229 }
230
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700231 auto status = tryAssemble(gHalManifestConverter, "manifest",
232 std::bind(&AssembleVintf::assembleHalManifest, this, _1));
233 if (status == SUCCESS) return true;
234 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000235
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700236 resetInFiles();
Yifan Honga59d2562017-04-18 18:01:16 -0700237
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700238 status = tryAssemble(gCompatibilityMatrixConverter, "compatibility matrix",
239 std::bind(&AssembleVintf::assembleCompatibilityMatrix, this, _1));
240 if (status == SUCCESS) return true;
241 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000242
Yifan Hong959ee1b2017-04-28 14:37:56 -0700243 std::cerr << "Input file has unknown format." << std::endl
244 << "Error when attempting to convert to manifest: "
245 << gHalManifestConverter.lastError() << std::endl
246 << "Error when attempting to convert to compatibility matrix: "
247 << gCompatibilityMatrixConverter.lastError() << std::endl;
248 return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000249 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700250
251 bool openOutFile(const char* path) {
252 mOutFileRef = std::make_unique<std::ofstream>();
253 mOutFileRef->open(path);
254 return mOutFileRef->is_open();
255 }
256
257 bool openInFile(const char* path) {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700258 mInFilePaths.push_back(path);
259 mInFiles.push_back({});
260 mInFiles.back().open(path);
261 return mInFiles.back().is_open();
Yifan Hong9aa63702017-05-16 16:37:50 -0700262 }
263
264 bool openCheckFile(const char* path) {
265 mCheckFile.open(path);
266 return mCheckFile.is_open();
267 }
268
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700269 void resetInFiles() {
270 for (auto& inFile : mInFiles) {
271 inFile.clear();
272 inFile.seekg(0);
273 }
274 }
275
Yifan Hong9aa63702017-05-16 16:37:50 -0700276 void setOutputMatrix() { mOutputMatrix = true; }
277
Yifan Hong79efa8a2017-07-06 14:10:28 -0700278 bool addKernel(const std::string& kernelArg) {
279 auto ind = kernelArg.find(':');
280 if (ind == std::string::npos) {
281 std::cerr << "Unrecognized --kernel option '" << kernelArg << "'" << std::endl;
282 return false;
283 }
284 std::string kernelVerStr{kernelArg.begin(), kernelArg.begin() + ind};
285 std::string kernelConfigPath{kernelArg.begin() + ind + 1, kernelArg.end()};
286 Version kernelVer;
287 if (!parse(kernelVerStr, &kernelVer)) {
288 std::cerr << "Unrecognized kernel version '" << kernelVerStr << "'" << std::endl;
289 return false;
290 }
291 mKernels.push_back({{kernelVer.majorVer, kernelVer.minorVer, 0u}, kernelConfigPath});
292 return true;
293 }
294
Yifan Hong9aa63702017-05-16 16:37:50 -0700295 private:
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700296 std::vector<std::string> mInFilePaths;
297 std::vector<std::ifstream> mInFiles;
Yifan Hong9aa63702017-05-16 16:37:50 -0700298 std::unique_ptr<std::ofstream> mOutFileRef;
299 std::ifstream mCheckFile;
300 bool mOutputMatrix = false;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700301 std::vector<std::pair<KernelVersion, std::string>> mKernels;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000302};
303
304} // namespace vintf
305} // namespace android
306
307void help() {
Yifan Hong9aa63702017-05-16 16:37:50 -0700308 std::cerr << "assemble_vintf: Checks if a given manifest / matrix file is valid and \n"
309 " fill in build-time flags into the given file.\n"
310 "assemble_vintf -h\n"
311 " Display this help text.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700312 "assemble_vintf -i <input file>[:<input file>[...]] [-o <output file>] [-m]\n"
313 " [-c [<check file>]]\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700314 " Fill in build-time flags into the given file.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700315 " -i <input file>[:<input file>[...]]\n"
316 " A list of input files. Format is automatically detected for the\n"
317 " first file, and the remaining files must have the same format.\n"
318 " Files other than the first file should only have <hal> defined;\n"
319 " other entries are ignored.\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700320 " -o <output file>\n"
321 " Optional output file. If not specified, write to stdout.\n"
322 " -m\n"
323 " a compatible compatibility matrix is\n"
324 " generated instead; for example, given a device manifest,\n"
325 " a framework compatibility matrix is generated. This flag\n"
326 " is ignored when input is a compatibility matrix.\n"
327 " -c [<check file>]\n"
328 " After writing the output file, check compatibility between\n"
329 " output file and check file.\n"
330 " If -c is set but the check file is not specified, a warning\n"
331 " message is written to stderr. Return 0.\n"
332 " If the check file is specified but is not compatible, an error\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700333 " message is written to stderr. Return 1.\n"
334 " --kernel=<version>:<android-base.cfg>\n"
335 " Add a kernel entry to framework compatibility matrix.\n"
336 " Ignored for other input format.\n"
337 " <version> has format: 3.18\n"
338 " <android-base.cfg> is the location of android-base.cfg\n";
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000339}
340
341int main(int argc, char **argv) {
Yifan Hong79efa8a2017-07-06 14:10:28 -0700342 const struct option longopts[] = {{"kernel", required_argument, NULL, 'k'}, {0, 0, 0, 0}};
Yifan Hong9aa63702017-05-16 16:37:50 -0700343
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700344 std::string outFilePath;
Yifan Hong9aa63702017-05-16 16:37:50 -0700345 ::android::vintf::AssembleVintf assembleVintf;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000346 int res;
Yifan Hong9aa63702017-05-16 16:37:50 -0700347 int optind;
348 while ((res = getopt_long(argc, argv, "hi:o:mc:", longopts, &optind)) >= 0) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000349 switch (res) {
350 case 'i': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700351 char* inFilePath = strtok(optarg, ":");
352 while (inFilePath != NULL) {
353 if (!assembleVintf.openInFile(inFilePath)) {
354 std::cerr << "Failed to open " << optarg << std::endl;
355 return 1;
356 }
357 inFilePath = strtok(NULL, ":");
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000358 }
359 } break;
360
361 case 'o': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700362 outFilePath = optarg;
Yifan Hong9aa63702017-05-16 16:37:50 -0700363 if (!assembleVintf.openOutFile(optarg)) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000364 std::cerr << "Failed to open " << optarg << std::endl;
365 return 1;
366 }
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000367 } break;
368
Yifan Honga59d2562017-04-18 18:01:16 -0700369 case 'm': {
Yifan Hong9aa63702017-05-16 16:37:50 -0700370 assembleVintf.setOutputMatrix();
Yifan Honga59d2562017-04-18 18:01:16 -0700371 } break;
372
Yifan Hong4650ad82017-05-01 17:28:02 -0700373 case 'c': {
374 if (strlen(optarg) != 0) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700375 if (!assembleVintf.openCheckFile(optarg)) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700376 std::cerr << "Failed to open " << optarg << std::endl;
377 return 1;
378 }
379 } else {
380 std::cerr << "WARNING: no compatibility check is done on "
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700381 << (outFilePath.empty() ? "output" : outFilePath) << std::endl;
Yifan Hong4650ad82017-05-01 17:28:02 -0700382 }
383 } break;
384
Yifan Hong79efa8a2017-07-06 14:10:28 -0700385 case 'k': {
386 if (!assembleVintf.addKernel(optarg)) {
387 std::cerr << "ERROR: Unrecognized --kernel argument." << std::endl;
388 return 1;
389 }
390 } break;
391
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000392 case 'h':
393 default: {
394 help();
395 return 1;
396 } break;
397 }
398 }
399
Yifan Hong9aa63702017-05-16 16:37:50 -0700400 bool success = assembleVintf.assemble();
Yifan Hong4650ad82017-05-01 17:28:02 -0700401
402 return success ? 0 : 1;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000403}
404