blob: 251d4bc8c6299e792c2a8853f38718958bac9a74 [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 Hong9a8b1a72017-08-25 17:55:33 -070027#include <android-base/file.h>
28
Yifan Hong79efa8a2017-07-06 14:10:28 -070029#include <vintf/KernelConfigParser.h>
Yifan Hong4d18bcc2017-04-07 21:47:16 +000030#include <vintf/parse_string.h>
31#include <vintf/parse_xml.h>
32
Yifan Hong79efa8a2017-07-06 14:10:28 -070033#define BUFFER_SIZE sysconf(_SC_PAGESIZE)
34
Yifan Hong4d18bcc2017-04-07 21:47:16 +000035namespace android {
36namespace vintf {
37
Yifan Hong9a8b1a72017-08-25 17:55:33 -070038static const std::string gConfigPrefix = "android-base-";
39static const std::string gConfigSuffix = ".cfg";
40static const std::string gBaseConfig = "android-base.cfg";
41
Yifan Hong4d18bcc2017-04-07 21:47:16 +000042/**
43 * Slurps the device manifest file and add build time flag to it.
44 */
45class AssembleVintf {
Yifan Hong9a8b1a72017-08-25 17:55:33 -070046 using Condition = std::unique_ptr<KernelConfig>;
47 using ConditionedConfig = std::pair<Condition, std::vector<KernelConfig> /* configs */>;
48
49 public:
Yifan Hong4d18bcc2017-04-07 21:47:16 +000050 template<typename T>
51 static bool getFlag(const std::string& key, T* value) {
52 const char *envValue = getenv(key.c_str());
53 if (envValue == NULL) {
Yifan Hong488e16a2017-07-11 13:50:41 -070054 std::cerr << "Warning: " << key << " is missing, defaulted to " << (*value)
55 << std::endl;
56 return true;
Yifan Hong4d18bcc2017-04-07 21:47:16 +000057 }
58
59 if (!parse(envValue, value)) {
60 std::cerr << "Cannot parse " << envValue << "." << std::endl;
61 return false;
62 }
63 return true;
64 }
65
Yifan Hong4650ad82017-05-01 17:28:02 -070066 static std::string read(std::basic_istream<char>& is) {
67 std::stringstream ss;
68 ss << is.rdbuf();
69 return ss.str();
70 }
71
Yifan Hong9a8b1a72017-08-25 17:55:33 -070072 static bool isCommonConfig(const std::string& path) {
73 return ::android::base::Basename(path) == gBaseConfig;
74 }
75
Yifan Hong79efa8a2017-07-06 14:10:28 -070076 static bool parseFileForKernelConfigs(const std::string& path, std::vector<KernelConfig>* out) {
77 std::ifstream ifs{path};
78 if (!ifs.is_open()) {
79 std::cerr << "File '" << path << "' does not exist or cannot be read." << std::endl;
80 return false;
81 }
Yifan Hong02e94002017-07-10 15:41:56 -070082 KernelConfigParser parser(true /* processComments */, true /* relaxedFormat */);
Yifan Hong79efa8a2017-07-06 14:10:28 -070083 std::string content = read(ifs);
84 status_t err = parser.process(content.c_str(), content.size());
85 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -070086 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -070087 return false;
88 }
89 err = parser.finish();
90 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -070091 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -070092 return false;
93 }
94
95 for (auto& configPair : parser.configs()) {
96 out->push_back({});
97 KernelConfig& config = out->back();
98 config.first = std::move(configPair.first);
99 if (!parseKernelConfigTypedValue(configPair.second, &config.second)) {
100 std::cerr << "Unknown value type for key = '" << config.first << "', value = '"
101 << configPair.second << "'\n";
102 return false;
103 }
104 }
105 return true;
106 }
107
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700108 static bool parseFilesForKernelConfigs(const std::string& path,
109 std::vector<ConditionedConfig>* out) {
110 out->clear();
111 ConditionedConfig commonConfig;
112 bool foundCommonConfig = false;
Steve Muckle0bef8682017-07-31 15:47:15 -0700113 bool ret = true;
114 char *pathIter;
115 char *modPath = new char[path.length() + 1];
116 strcpy(modPath, path.c_str());
117 pathIter = strtok(modPath, ":");
118 while (ret && pathIter != NULL) {
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700119 if (isCommonConfig(pathIter)) {
120 ret &= parseFileForKernelConfigs(pathIter, &commonConfig.second);
121 foundCommonConfig = true;
122 } else {
123 std::vector<KernelConfig> kernelConfigs;
124 if ((ret &= parseFileForKernelConfigs(pathIter, &kernelConfigs)))
125 out->emplace_back(nullptr, std::move(kernelConfigs));
126 }
Steve Muckle0bef8682017-07-31 15:47:15 -0700127 pathIter = strtok(NULL, ":");
128 }
Luis A. Lozano82266ae2017-08-22 16:30:11 -0700129 delete[] modPath;
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700130
131 if (!foundCommonConfig) {
132 std::cerr << "No android-base.cfg is found in these paths: '" << path << "'"
133 << std::endl;
134 }
135 ret &= foundCommonConfig;
136 // first element is always common configs (no conditions).
137 out->insert(out->begin(), std::move(commonConfig));
Steve Muckle0bef8682017-07-31 15:47:15 -0700138 return ret;
139 }
140
Yifan Hong9aa63702017-05-16 16:37:50 -0700141 std::basic_ostream<char>& out() const {
142 return mOutFileRef == nullptr ? std::cout : *mOutFileRef;
143 }
144
145 bool assembleHalManifest(HalManifest* halManifest) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700146 std::string error;
Yifan Hong9aa63702017-05-16 16:37:50 -0700147
148 if (halManifest->mType == SchemaType::DEVICE) {
149 if (!getFlag("BOARD_SEPOLICY_VERS", &halManifest->device.mSepolicyVersion)) {
150 return false;
151 }
152 }
153
154 if (mOutputMatrix) {
155 CompatibilityMatrix generatedMatrix = halManifest->generateCompatibleMatrix();
156 if (!halManifest->checkCompatibility(generatedMatrix, &error)) {
157 std::cerr << "FATAL ERROR: cannot generate a compatible matrix: " << error
158 << std::endl;
159 }
160 out() << "<!-- \n"
161 " Autogenerated skeleton compatibility matrix. \n"
162 " Use with caution. Modify it to suit your needs.\n"
163 " All HALs are set to optional.\n"
164 " Many entries other than HALs are zero-filled and\n"
165 " require human attention. \n"
166 "-->\n"
167 << gCompatibilityMatrixConverter(generatedMatrix);
168 } else {
169 out() << gHalManifestConverter(*halManifest);
170 }
171 out().flush();
172
173 if (mCheckFile.is_open()) {
174 CompatibilityMatrix checkMatrix;
175 if (!gCompatibilityMatrixConverter(&checkMatrix, read(mCheckFile))) {
176 std::cerr << "Cannot parse check file as a compatibility matrix: "
177 << gCompatibilityMatrixConverter.lastError() << std::endl;
178 return false;
179 }
180 if (!halManifest->checkCompatibility(checkMatrix, &error)) {
181 std::cerr << "Not compatible: " << error << std::endl;
182 return false;
183 }
184 }
185
186 return true;
187 }
188
Yifan Honge88e1672017-08-24 14:42:54 -0700189 bool assembleFrameworkCompatibilityMatrixKernels(CompatibilityMatrix* matrix) {
Yifan Hong4c34fee2017-08-24 16:03:34 -0700190 if (!matrix->framework.mKernels.empty()) {
191 // Remove hard-coded <kernel version="x.y.z" /> in legacy files.
192 std::cerr << "WARNING: framework compatibility matrix has hard-coded kernel"
193 << " requirements for version";
194 for (const auto& kernel : matrix->framework.mKernels) {
195 std::cerr << " " << kernel.minLts();
196 }
197 std::cerr << ". Hard-coded requirements are removed." << std::endl;
198 matrix->framework.mKernels.clear();
199 }
Yifan Honge88e1672017-08-24 14:42:54 -0700200 for (const auto& pair : mKernels) {
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700201 std::vector<ConditionedConfig> conditionedConfigs;
202 if (!parseFilesForKernelConfigs(pair.second, &conditionedConfigs)) {
Yifan Honge88e1672017-08-24 14:42:54 -0700203 return false;
204 }
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700205 for (ConditionedConfig& conditionedConfig : conditionedConfigs) {
206 matrix->framework.mKernels.push_back(
207 MatrixKernel{KernelVersion{pair.first}, std::move(conditionedConfig.second)});
208 }
Yifan Honge88e1672017-08-24 14:42:54 -0700209 }
210 return true;
211 }
212
Yifan Hong9aa63702017-05-16 16:37:50 -0700213 bool assembleCompatibilityMatrix(CompatibilityMatrix* matrix) {
214 std::string error;
215
216 KernelSepolicyVersion kernelSepolicyVers;
217 Version sepolicyVers;
218 if (matrix->mType == SchemaType::FRAMEWORK) {
219 if (!getFlag("BOARD_SEPOLICY_VERS", &sepolicyVers)) {
220 return false;
221 }
222 if (!getFlag("POLICYVERS", &kernelSepolicyVers)) {
223 return false;
224 }
Yifan Honge88e1672017-08-24 14:42:54 -0700225
226 if (!assembleFrameworkCompatibilityMatrixKernels(matrix)) {
227 return false;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700228 }
Yifan Honge88e1672017-08-24 14:42:54 -0700229
Yifan Hong9aa63702017-05-16 16:37:50 -0700230 matrix->framework.mSepolicy =
231 Sepolicy(kernelSepolicyVers, {{sepolicyVers.majorVer, sepolicyVers.minorVer}});
Yifan Hong7f6c00c2017-07-06 19:50:29 +0000232
233 Version avbMetaVersion;
234 if (!getFlag("FRAMEWORK_VBMETA_VERSION", &avbMetaVersion)) {
235 return false;
236 }
237 matrix->framework.mAvbMetaVersion = avbMetaVersion;
Yifan Hong9aa63702017-05-16 16:37:50 -0700238 }
239 out() << gCompatibilityMatrixConverter(*matrix);
240 out().flush();
241
242 if (mCheckFile.is_open()) {
243 HalManifest checkManifest;
244 if (!gHalManifestConverter(&checkManifest, read(mCheckFile))) {
245 std::cerr << "Cannot parse check file as a HAL manifest: "
246 << gHalManifestConverter.lastError() << std::endl;
247 return false;
248 }
249 if (!checkManifest.checkCompatibility(*matrix, &error)) {
250 std::cerr << "Not compatible: " << error << std::endl;
251 return false;
252 }
253 }
254
255 return true;
256 }
257
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700258 enum AssembleStatus { SUCCESS, FAIL_AND_EXIT, TRY_NEXT };
259 template <typename Schema, typename AssembleFunc>
260 AssembleStatus tryAssemble(const XmlConverter<Schema>& converter, const std::string& schemaName,
261 AssembleFunc assemble) {
262 Schema schema;
263 if (!converter(&schema, read(mInFiles.front()))) {
264 return TRY_NEXT;
265 }
266 auto firstType = schema.type();
267 for (auto it = mInFiles.begin() + 1; it != mInFiles.end(); ++it) {
268 Schema additionalSchema;
269 if (!converter(&additionalSchema, read(*it))) {
270 std::cerr << "File \"" << mInFilePaths[std::distance(mInFiles.begin(), it)]
271 << "\" is not a valid " << firstType << " " << schemaName
272 << " (but the first file is a valid " << firstType << " " << schemaName
273 << "). Error: " << converter.lastError() << std::endl;
274 return FAIL_AND_EXIT;
275 }
276 if (additionalSchema.type() != firstType) {
277 std::cerr << "File \"" << mInFilePaths[std::distance(mInFiles.begin(), it)]
278 << "\" is a " << additionalSchema.type() << " " << schemaName
279 << " (but a " << firstType << " " << schemaName << " is expected)."
280 << std::endl;
281 return FAIL_AND_EXIT;
282 }
283 schema.addAll(std::move(additionalSchema));
284 }
285 return assemble(&schema) ? SUCCESS : FAIL_AND_EXIT;
286 }
287
Yifan Hong9aa63702017-05-16 16:37:50 -0700288 bool assemble() {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700289 using std::placeholders::_1;
290 if (mInFiles.empty()) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700291 std::cerr << "Missing input file." << std::endl;
292 return false;
293 }
294
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700295 auto status = tryAssemble(gHalManifestConverter, "manifest",
296 std::bind(&AssembleVintf::assembleHalManifest, this, _1));
297 if (status == SUCCESS) return true;
298 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000299
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700300 resetInFiles();
Yifan Honga59d2562017-04-18 18:01:16 -0700301
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700302 status = tryAssemble(gCompatibilityMatrixConverter, "compatibility matrix",
303 std::bind(&AssembleVintf::assembleCompatibilityMatrix, this, _1));
304 if (status == SUCCESS) return true;
305 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000306
Yifan Hong959ee1b2017-04-28 14:37:56 -0700307 std::cerr << "Input file has unknown format." << std::endl
308 << "Error when attempting to convert to manifest: "
309 << gHalManifestConverter.lastError() << std::endl
310 << "Error when attempting to convert to compatibility matrix: "
311 << gCompatibilityMatrixConverter.lastError() << std::endl;
312 return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000313 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700314
315 bool openOutFile(const char* path) {
316 mOutFileRef = std::make_unique<std::ofstream>();
317 mOutFileRef->open(path);
318 return mOutFileRef->is_open();
319 }
320
321 bool openInFile(const char* path) {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700322 mInFilePaths.push_back(path);
323 mInFiles.push_back({});
324 mInFiles.back().open(path);
325 return mInFiles.back().is_open();
Yifan Hong9aa63702017-05-16 16:37:50 -0700326 }
327
328 bool openCheckFile(const char* path) {
329 mCheckFile.open(path);
330 return mCheckFile.is_open();
331 }
332
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700333 void resetInFiles() {
334 for (auto& inFile : mInFiles) {
335 inFile.clear();
336 inFile.seekg(0);
337 }
338 }
339
Yifan Hong9aa63702017-05-16 16:37:50 -0700340 void setOutputMatrix() { mOutputMatrix = true; }
341
Yifan Hong79efa8a2017-07-06 14:10:28 -0700342 bool addKernel(const std::string& kernelArg) {
343 auto ind = kernelArg.find(':');
344 if (ind == std::string::npos) {
345 std::cerr << "Unrecognized --kernel option '" << kernelArg << "'" << std::endl;
346 return false;
347 }
348 std::string kernelVerStr{kernelArg.begin(), kernelArg.begin() + ind};
349 std::string kernelConfigPath{kernelArg.begin() + ind + 1, kernelArg.end()};
350 Version kernelVer;
351 if (!parse(kernelVerStr, &kernelVer)) {
352 std::cerr << "Unrecognized kernel version '" << kernelVerStr << "'" << std::endl;
353 return false;
354 }
355 mKernels.push_back({{kernelVer.majorVer, kernelVer.minorVer, 0u}, kernelConfigPath});
356 return true;
357 }
358
Yifan Hong9aa63702017-05-16 16:37:50 -0700359 private:
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700360 std::vector<std::string> mInFilePaths;
361 std::vector<std::ifstream> mInFiles;
Yifan Hong9aa63702017-05-16 16:37:50 -0700362 std::unique_ptr<std::ofstream> mOutFileRef;
363 std::ifstream mCheckFile;
364 bool mOutputMatrix = false;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700365 std::vector<std::pair<KernelVersion, std::string>> mKernels;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000366};
367
368} // namespace vintf
369} // namespace android
370
371void help() {
Yifan Hong9aa63702017-05-16 16:37:50 -0700372 std::cerr << "assemble_vintf: Checks if a given manifest / matrix file is valid and \n"
373 " fill in build-time flags into the given file.\n"
374 "assemble_vintf -h\n"
375 " Display this help text.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700376 "assemble_vintf -i <input file>[:<input file>[...]] [-o <output file>] [-m]\n"
377 " [-c [<check file>]]\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700378 " Fill in build-time flags into the given file.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700379 " -i <input file>[:<input file>[...]]\n"
380 " A list of input files. Format is automatically detected for the\n"
381 " first file, and the remaining files must have the same format.\n"
382 " Files other than the first file should only have <hal> defined;\n"
383 " other entries are ignored.\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700384 " -o <output file>\n"
385 " Optional output file. If not specified, write to stdout.\n"
386 " -m\n"
387 " a compatible compatibility matrix is\n"
388 " generated instead; for example, given a device manifest,\n"
389 " a framework compatibility matrix is generated. This flag\n"
390 " is ignored when input is a compatibility matrix.\n"
391 " -c [<check file>]\n"
392 " After writing the output file, check compatibility between\n"
393 " output file and check file.\n"
394 " If -c is set but the check file is not specified, a warning\n"
395 " message is written to stderr. Return 0.\n"
396 " If the check file is specified but is not compatible, an error\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700397 " message is written to stderr. Return 1.\n"
Steve Muckle0bef8682017-07-31 15:47:15 -0700398 " --kernel=<version>:<android-base.cfg>[:<android-base-arch.cfg>[...]]\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700399 " Add a kernel entry to framework compatibility matrix.\n"
400 " Ignored for other input format.\n"
401 " <version> has format: 3.18\n"
Steve Muckle0bef8682017-07-31 15:47:15 -0700402 " <android-base.cfg> is the location of android-base.cfg\n"
403 " <android-base-arch.cfg> is the location of an optional\n"
404 " arch-specific config fragment, more than one may be specified\n";
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000405}
406
407int main(int argc, char **argv) {
Yifan Hong79efa8a2017-07-06 14:10:28 -0700408 const struct option longopts[] = {{"kernel", required_argument, NULL, 'k'}, {0, 0, 0, 0}};
Yifan Hong9aa63702017-05-16 16:37:50 -0700409
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700410 std::string outFilePath;
Yifan Hong9aa63702017-05-16 16:37:50 -0700411 ::android::vintf::AssembleVintf assembleVintf;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000412 int res;
Yifan Hong9aa63702017-05-16 16:37:50 -0700413 int optind;
414 while ((res = getopt_long(argc, argv, "hi:o:mc:", longopts, &optind)) >= 0) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000415 switch (res) {
416 case 'i': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700417 char* inFilePath = strtok(optarg, ":");
418 while (inFilePath != NULL) {
419 if (!assembleVintf.openInFile(inFilePath)) {
420 std::cerr << "Failed to open " << optarg << std::endl;
421 return 1;
422 }
423 inFilePath = strtok(NULL, ":");
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000424 }
425 } break;
426
427 case 'o': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700428 outFilePath = optarg;
Yifan Hong9aa63702017-05-16 16:37:50 -0700429 if (!assembleVintf.openOutFile(optarg)) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000430 std::cerr << "Failed to open " << optarg << std::endl;
431 return 1;
432 }
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000433 } break;
434
Yifan Honga59d2562017-04-18 18:01:16 -0700435 case 'm': {
Yifan Hong9aa63702017-05-16 16:37:50 -0700436 assembleVintf.setOutputMatrix();
Yifan Honga59d2562017-04-18 18:01:16 -0700437 } break;
438
Yifan Hong4650ad82017-05-01 17:28:02 -0700439 case 'c': {
440 if (strlen(optarg) != 0) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700441 if (!assembleVintf.openCheckFile(optarg)) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700442 std::cerr << "Failed to open " << optarg << std::endl;
443 return 1;
444 }
445 } else {
446 std::cerr << "WARNING: no compatibility check is done on "
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700447 << (outFilePath.empty() ? "output" : outFilePath) << std::endl;
Yifan Hong4650ad82017-05-01 17:28:02 -0700448 }
449 } break;
450
Yifan Hong79efa8a2017-07-06 14:10:28 -0700451 case 'k': {
452 if (!assembleVintf.addKernel(optarg)) {
453 std::cerr << "ERROR: Unrecognized --kernel argument." << std::endl;
454 return 1;
455 }
456 } break;
457
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000458 case 'h':
459 default: {
460 help();
461 return 1;
462 } break;
463 }
464 }
465
Yifan Hong9aa63702017-05-16 16:37:50 -0700466 bool success = assembleVintf.assemble();
Yifan Hong4650ad82017-05-01 17:28:02 -0700467
468 return success ? 0 : 1;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000469}