blob: 60cea7d0a19f33be683ebb8506ac918ac548d344 [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) {
Yifan Hong488e16a2017-07-11 13:50:41 -070045 std::cerr << "Warning: " << key << " is missing, defaulted to " << (*value)
46 << std::endl;
47 return true;
Yifan Hong4d18bcc2017-04-07 21:47:16 +000048 }
49
50 if (!parse(envValue, value)) {
51 std::cerr << "Cannot parse " << envValue << "." << std::endl;
52 return false;
53 }
54 return true;
55 }
56
Yifan Hong4650ad82017-05-01 17:28:02 -070057 static std::string read(std::basic_istream<char>& is) {
58 std::stringstream ss;
59 ss << is.rdbuf();
60 return ss.str();
61 }
62
Yifan Hong79efa8a2017-07-06 14:10:28 -070063 static bool parseFileForKernelConfigs(const std::string& path, std::vector<KernelConfig>* out) {
64 std::ifstream ifs{path};
65 if (!ifs.is_open()) {
66 std::cerr << "File '" << path << "' does not exist or cannot be read." << std::endl;
67 return false;
68 }
Yifan Hong02e94002017-07-10 15:41:56 -070069 KernelConfigParser parser(true /* processComments */, true /* relaxedFormat */);
Yifan Hong79efa8a2017-07-06 14:10:28 -070070 std::string content = read(ifs);
71 status_t err = parser.process(content.c_str(), content.size());
72 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -070073 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -070074 return false;
75 }
76 err = parser.finish();
77 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -070078 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -070079 return false;
80 }
81
82 for (auto& configPair : parser.configs()) {
83 out->push_back({});
84 KernelConfig& config = out->back();
85 config.first = std::move(configPair.first);
86 if (!parseKernelConfigTypedValue(configPair.second, &config.second)) {
87 std::cerr << "Unknown value type for key = '" << config.first << "', value = '"
88 << configPair.second << "'\n";
89 return false;
90 }
91 }
92 return true;
93 }
94
Steve Muckle0bef8682017-07-31 15:47:15 -070095 static bool parseFilesForKernelConfigs(const std::string& path, std::vector<KernelConfig>* out) {
96 bool ret = true;
97 char *pathIter;
98 char *modPath = new char[path.length() + 1];
99 strcpy(modPath, path.c_str());
100 pathIter = strtok(modPath, ":");
101 while (ret && pathIter != NULL) {
102 ret &= parseFileForKernelConfigs(pathIter, out);
103 pathIter = strtok(NULL, ":");
104 }
Luis A. Lozano82266ae2017-08-22 16:30:11 -0700105 delete[] modPath;
Steve Muckle0bef8682017-07-31 15:47:15 -0700106 return ret;
107 }
108
Yifan Hong9aa63702017-05-16 16:37:50 -0700109 std::basic_ostream<char>& out() const {
110 return mOutFileRef == nullptr ? std::cout : *mOutFileRef;
111 }
112
113 bool assembleHalManifest(HalManifest* halManifest) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700114 std::string error;
Yifan Hong9aa63702017-05-16 16:37:50 -0700115
116 if (halManifest->mType == SchemaType::DEVICE) {
117 if (!getFlag("BOARD_SEPOLICY_VERS", &halManifest->device.mSepolicyVersion)) {
118 return false;
119 }
120 }
121
122 if (mOutputMatrix) {
123 CompatibilityMatrix generatedMatrix = halManifest->generateCompatibleMatrix();
124 if (!halManifest->checkCompatibility(generatedMatrix, &error)) {
125 std::cerr << "FATAL ERROR: cannot generate a compatible matrix: " << error
126 << std::endl;
127 }
128 out() << "<!-- \n"
129 " Autogenerated skeleton compatibility matrix. \n"
130 " Use with caution. Modify it to suit your needs.\n"
131 " All HALs are set to optional.\n"
132 " Many entries other than HALs are zero-filled and\n"
133 " require human attention. \n"
134 "-->\n"
135 << gCompatibilityMatrixConverter(generatedMatrix);
136 } else {
137 out() << gHalManifestConverter(*halManifest);
138 }
139 out().flush();
140
141 if (mCheckFile.is_open()) {
142 CompatibilityMatrix checkMatrix;
143 if (!gCompatibilityMatrixConverter(&checkMatrix, read(mCheckFile))) {
144 std::cerr << "Cannot parse check file as a compatibility matrix: "
145 << gCompatibilityMatrixConverter.lastError() << std::endl;
146 return false;
147 }
148 if (!halManifest->checkCompatibility(checkMatrix, &error)) {
149 std::cerr << "Not compatible: " << error << std::endl;
150 return false;
151 }
152 }
153
154 return true;
155 }
156
Yifan Honge88e1672017-08-24 14:42:54 -0700157 bool assembleFrameworkCompatibilityMatrixKernels(CompatibilityMatrix* matrix) {
Yifan Hong4c34fee2017-08-24 16:03:34 -0700158 if (!matrix->framework.mKernels.empty()) {
159 // Remove hard-coded <kernel version="x.y.z" /> in legacy files.
160 std::cerr << "WARNING: framework compatibility matrix has hard-coded kernel"
161 << " requirements for version";
162 for (const auto& kernel : matrix->framework.mKernels) {
163 std::cerr << " " << kernel.minLts();
164 }
165 std::cerr << ". Hard-coded requirements are removed." << std::endl;
166 matrix->framework.mKernels.clear();
167 }
Yifan Honge88e1672017-08-24 14:42:54 -0700168 for (const auto& pair : mKernels) {
169 std::vector<KernelConfig> configs;
170 if (!parseFilesForKernelConfigs(pair.second, &configs)) {
171 return false;
172 }
Yifan Hong4c34fee2017-08-24 16:03:34 -0700173 matrix->framework.mKernels.push_back(
174 MatrixKernel{KernelVersion{pair.first}, std::move(configs)});
Yifan Honge88e1672017-08-24 14:42:54 -0700175 }
176 return true;
177 }
178
Yifan Hong9aa63702017-05-16 16:37:50 -0700179 bool assembleCompatibilityMatrix(CompatibilityMatrix* matrix) {
180 std::string error;
181
182 KernelSepolicyVersion kernelSepolicyVers;
183 Version sepolicyVers;
184 if (matrix->mType == SchemaType::FRAMEWORK) {
185 if (!getFlag("BOARD_SEPOLICY_VERS", &sepolicyVers)) {
186 return false;
187 }
188 if (!getFlag("POLICYVERS", &kernelSepolicyVers)) {
189 return false;
190 }
Yifan Honge88e1672017-08-24 14:42:54 -0700191
192 if (!assembleFrameworkCompatibilityMatrixKernels(matrix)) {
193 return false;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700194 }
Yifan Honge88e1672017-08-24 14:42:54 -0700195
Yifan Hong9aa63702017-05-16 16:37:50 -0700196 matrix->framework.mSepolicy =
197 Sepolicy(kernelSepolicyVers, {{sepolicyVers.majorVer, sepolicyVers.minorVer}});
Yifan Hong7f6c00c2017-07-06 19:50:29 +0000198
199 Version avbMetaVersion;
200 if (!getFlag("FRAMEWORK_VBMETA_VERSION", &avbMetaVersion)) {
201 return false;
202 }
203 matrix->framework.mAvbMetaVersion = avbMetaVersion;
Yifan Hong9aa63702017-05-16 16:37:50 -0700204 }
205 out() << gCompatibilityMatrixConverter(*matrix);
206 out().flush();
207
208 if (mCheckFile.is_open()) {
209 HalManifest checkManifest;
210 if (!gHalManifestConverter(&checkManifest, read(mCheckFile))) {
211 std::cerr << "Cannot parse check file as a HAL manifest: "
212 << gHalManifestConverter.lastError() << std::endl;
213 return false;
214 }
215 if (!checkManifest.checkCompatibility(*matrix, &error)) {
216 std::cerr << "Not compatible: " << error << std::endl;
217 return false;
218 }
219 }
220
221 return true;
222 }
223
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700224 enum AssembleStatus { SUCCESS, FAIL_AND_EXIT, TRY_NEXT };
225 template <typename Schema, typename AssembleFunc>
226 AssembleStatus tryAssemble(const XmlConverter<Schema>& converter, const std::string& schemaName,
227 AssembleFunc assemble) {
228 Schema schema;
229 if (!converter(&schema, read(mInFiles.front()))) {
230 return TRY_NEXT;
231 }
232 auto firstType = schema.type();
233 for (auto it = mInFiles.begin() + 1; it != mInFiles.end(); ++it) {
234 Schema additionalSchema;
235 if (!converter(&additionalSchema, read(*it))) {
236 std::cerr << "File \"" << mInFilePaths[std::distance(mInFiles.begin(), it)]
237 << "\" is not a valid " << firstType << " " << schemaName
238 << " (but the first file is a valid " << firstType << " " << schemaName
239 << "). Error: " << converter.lastError() << std::endl;
240 return FAIL_AND_EXIT;
241 }
242 if (additionalSchema.type() != firstType) {
243 std::cerr << "File \"" << mInFilePaths[std::distance(mInFiles.begin(), it)]
244 << "\" is a " << additionalSchema.type() << " " << schemaName
245 << " (but a " << firstType << " " << schemaName << " is expected)."
246 << std::endl;
247 return FAIL_AND_EXIT;
248 }
249 schema.addAll(std::move(additionalSchema));
250 }
251 return assemble(&schema) ? SUCCESS : FAIL_AND_EXIT;
252 }
253
Yifan Hong9aa63702017-05-16 16:37:50 -0700254 bool assemble() {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700255 using std::placeholders::_1;
256 if (mInFiles.empty()) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700257 std::cerr << "Missing input file." << std::endl;
258 return false;
259 }
260
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700261 auto status = tryAssemble(gHalManifestConverter, "manifest",
262 std::bind(&AssembleVintf::assembleHalManifest, this, _1));
263 if (status == SUCCESS) return true;
264 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000265
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700266 resetInFiles();
Yifan Honga59d2562017-04-18 18:01:16 -0700267
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700268 status = tryAssemble(gCompatibilityMatrixConverter, "compatibility matrix",
269 std::bind(&AssembleVintf::assembleCompatibilityMatrix, this, _1));
270 if (status == SUCCESS) return true;
271 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000272
Yifan Hong959ee1b2017-04-28 14:37:56 -0700273 std::cerr << "Input file has unknown format." << std::endl
274 << "Error when attempting to convert to manifest: "
275 << gHalManifestConverter.lastError() << std::endl
276 << "Error when attempting to convert to compatibility matrix: "
277 << gCompatibilityMatrixConverter.lastError() << std::endl;
278 return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000279 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700280
281 bool openOutFile(const char* path) {
282 mOutFileRef = std::make_unique<std::ofstream>();
283 mOutFileRef->open(path);
284 return mOutFileRef->is_open();
285 }
286
287 bool openInFile(const char* path) {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700288 mInFilePaths.push_back(path);
289 mInFiles.push_back({});
290 mInFiles.back().open(path);
291 return mInFiles.back().is_open();
Yifan Hong9aa63702017-05-16 16:37:50 -0700292 }
293
294 bool openCheckFile(const char* path) {
295 mCheckFile.open(path);
296 return mCheckFile.is_open();
297 }
298
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700299 void resetInFiles() {
300 for (auto& inFile : mInFiles) {
301 inFile.clear();
302 inFile.seekg(0);
303 }
304 }
305
Yifan Hong9aa63702017-05-16 16:37:50 -0700306 void setOutputMatrix() { mOutputMatrix = true; }
307
Yifan Hong79efa8a2017-07-06 14:10:28 -0700308 bool addKernel(const std::string& kernelArg) {
309 auto ind = kernelArg.find(':');
310 if (ind == std::string::npos) {
311 std::cerr << "Unrecognized --kernel option '" << kernelArg << "'" << std::endl;
312 return false;
313 }
314 std::string kernelVerStr{kernelArg.begin(), kernelArg.begin() + ind};
315 std::string kernelConfigPath{kernelArg.begin() + ind + 1, kernelArg.end()};
316 Version kernelVer;
317 if (!parse(kernelVerStr, &kernelVer)) {
318 std::cerr << "Unrecognized kernel version '" << kernelVerStr << "'" << std::endl;
319 return false;
320 }
321 mKernels.push_back({{kernelVer.majorVer, kernelVer.minorVer, 0u}, kernelConfigPath});
322 return true;
323 }
324
Yifan Hong9aa63702017-05-16 16:37:50 -0700325 private:
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700326 std::vector<std::string> mInFilePaths;
327 std::vector<std::ifstream> mInFiles;
Yifan Hong9aa63702017-05-16 16:37:50 -0700328 std::unique_ptr<std::ofstream> mOutFileRef;
329 std::ifstream mCheckFile;
330 bool mOutputMatrix = false;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700331 std::vector<std::pair<KernelVersion, std::string>> mKernels;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000332};
333
334} // namespace vintf
335} // namespace android
336
337void help() {
Yifan Hong9aa63702017-05-16 16:37:50 -0700338 std::cerr << "assemble_vintf: Checks if a given manifest / matrix file is valid and \n"
339 " fill in build-time flags into the given file.\n"
340 "assemble_vintf -h\n"
341 " Display this help text.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700342 "assemble_vintf -i <input file>[:<input file>[...]] [-o <output file>] [-m]\n"
343 " [-c [<check file>]]\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700344 " Fill in build-time flags into the given file.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700345 " -i <input file>[:<input file>[...]]\n"
346 " A list of input files. Format is automatically detected for the\n"
347 " first file, and the remaining files must have the same format.\n"
348 " Files other than the first file should only have <hal> defined;\n"
349 " other entries are ignored.\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700350 " -o <output file>\n"
351 " Optional output file. If not specified, write to stdout.\n"
352 " -m\n"
353 " a compatible compatibility matrix is\n"
354 " generated instead; for example, given a device manifest,\n"
355 " a framework compatibility matrix is generated. This flag\n"
356 " is ignored when input is a compatibility matrix.\n"
357 " -c [<check file>]\n"
358 " After writing the output file, check compatibility between\n"
359 " output file and check file.\n"
360 " If -c is set but the check file is not specified, a warning\n"
361 " message is written to stderr. Return 0.\n"
362 " If the check file is specified but is not compatible, an error\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700363 " message is written to stderr. Return 1.\n"
Steve Muckle0bef8682017-07-31 15:47:15 -0700364 " --kernel=<version>:<android-base.cfg>[:<android-base-arch.cfg>[...]]\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700365 " Add a kernel entry to framework compatibility matrix.\n"
366 " Ignored for other input format.\n"
367 " <version> has format: 3.18\n"
Steve Muckle0bef8682017-07-31 15:47:15 -0700368 " <android-base.cfg> is the location of android-base.cfg\n"
369 " <android-base-arch.cfg> is the location of an optional\n"
370 " arch-specific config fragment, more than one may be specified\n";
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000371}
372
373int main(int argc, char **argv) {
Yifan Hong79efa8a2017-07-06 14:10:28 -0700374 const struct option longopts[] = {{"kernel", required_argument, NULL, 'k'}, {0, 0, 0, 0}};
Yifan Hong9aa63702017-05-16 16:37:50 -0700375
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700376 std::string outFilePath;
Yifan Hong9aa63702017-05-16 16:37:50 -0700377 ::android::vintf::AssembleVintf assembleVintf;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000378 int res;
Yifan Hong9aa63702017-05-16 16:37:50 -0700379 int optind;
380 while ((res = getopt_long(argc, argv, "hi:o:mc:", longopts, &optind)) >= 0) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000381 switch (res) {
382 case 'i': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700383 char* inFilePath = strtok(optarg, ":");
384 while (inFilePath != NULL) {
385 if (!assembleVintf.openInFile(inFilePath)) {
386 std::cerr << "Failed to open " << optarg << std::endl;
387 return 1;
388 }
389 inFilePath = strtok(NULL, ":");
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000390 }
391 } break;
392
393 case 'o': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700394 outFilePath = optarg;
Yifan Hong9aa63702017-05-16 16:37:50 -0700395 if (!assembleVintf.openOutFile(optarg)) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000396 std::cerr << "Failed to open " << optarg << std::endl;
397 return 1;
398 }
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000399 } break;
400
Yifan Honga59d2562017-04-18 18:01:16 -0700401 case 'm': {
Yifan Hong9aa63702017-05-16 16:37:50 -0700402 assembleVintf.setOutputMatrix();
Yifan Honga59d2562017-04-18 18:01:16 -0700403 } break;
404
Yifan Hong4650ad82017-05-01 17:28:02 -0700405 case 'c': {
406 if (strlen(optarg) != 0) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700407 if (!assembleVintf.openCheckFile(optarg)) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700408 std::cerr << "Failed to open " << optarg << std::endl;
409 return 1;
410 }
411 } else {
412 std::cerr << "WARNING: no compatibility check is done on "
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700413 << (outFilePath.empty() ? "output" : outFilePath) << std::endl;
Yifan Hong4650ad82017-05-01 17:28:02 -0700414 }
415 } break;
416
Yifan Hong79efa8a2017-07-06 14:10:28 -0700417 case 'k': {
418 if (!assembleVintf.addKernel(optarg)) {
419 std::cerr << "ERROR: Unrecognized --kernel argument." << std::endl;
420 return 1;
421 }
422 } break;
423
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000424 case 'h':
425 default: {
426 help();
427 return 1;
428 } break;
429 }
430 }
431
Yifan Hong9aa63702017-05-16 16:37:50 -0700432 bool success = assembleVintf.assemble();
Yifan Hong4650ad82017-05-01 17:28:02 -0700433
434 return success ? 0 : 1;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000435}