blob: fd8eb9f3ca0009d0063e070db9cc14c5146ad4ca [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>
Yifan Hongdbe9db32017-12-11 19:06:11 -080028#include <android-base/parseint.h>
Yifan Hong9a8b1a72017-08-25 17:55:33 -070029
Yifan Hong79efa8a2017-07-06 14:10:28 -070030#include <vintf/KernelConfigParser.h>
Yifan Hong4d18bcc2017-04-07 21:47:16 +000031#include <vintf/parse_string.h>
32#include <vintf/parse_xml.h>
33
Yifan Hong79efa8a2017-07-06 14:10:28 -070034#define BUFFER_SIZE sysconf(_SC_PAGESIZE)
35
Yifan Hong4d18bcc2017-04-07 21:47:16 +000036namespace android {
37namespace vintf {
38
Yifan Hong9a8b1a72017-08-25 17:55:33 -070039static const std::string gConfigPrefix = "android-base-";
40static const std::string gConfigSuffix = ".cfg";
41static const std::string gBaseConfig = "android-base.cfg";
42
Yifan Hongaa219f52017-12-18 18:51:59 -080043// An input stream with a name.
44// The input stream may be an actual file, or a stringstream for testing.
45// It takes ownership on the istream.
46class NamedIstream {
47 public:
48 NamedIstream(const std::string& name, std::unique_ptr<std::istream>&& stream)
49 : mName(name), mStream(std::move(stream)) {}
50 const std::string& name() const { return mName; }
51 std::istream& stream() { return *mStream; }
52
53 private:
54 std::string mName;
55 std::unique_ptr<std::istream> mStream;
56};
57
Yifan Hong4d18bcc2017-04-07 21:47:16 +000058/**
59 * Slurps the device manifest file and add build time flag to it.
60 */
61class AssembleVintf {
Yifan Hong9a8b1a72017-08-25 17:55:33 -070062 using Condition = std::unique_ptr<KernelConfig>;
63 using ConditionedConfig = std::pair<Condition, std::vector<KernelConfig> /* configs */>;
64
65 public:
Yifan Hong4d18bcc2017-04-07 21:47:16 +000066 template<typename T>
67 static bool getFlag(const std::string& key, T* value) {
68 const char *envValue = getenv(key.c_str());
69 if (envValue == NULL) {
Yifan Hong488e16a2017-07-11 13:50:41 -070070 std::cerr << "Warning: " << key << " is missing, defaulted to " << (*value)
71 << std::endl;
72 return true;
Yifan Hong4d18bcc2017-04-07 21:47:16 +000073 }
74
75 if (!parse(envValue, value)) {
76 std::cerr << "Cannot parse " << envValue << "." << std::endl;
77 return false;
78 }
79 return true;
80 }
81
Yifan Hongeff04662017-12-18 16:27:24 -080082 /**
83 * Set *out to environment variable if *out is not a dummy value (i.e. default constructed).
84 */
85 template <typename T>
86 bool getFlagIfUnset(const std::string& envKey, T* out) {
87 bool hasExistingValue = !(*out == T{});
88
89 bool hasEnvValue = false;
90 T envValue;
91 const char* envCValue = getenv(envKey.c_str());
92 if (envCValue != nullptr) {
93 if (!parse(envCValue, &envValue)) {
94 std::cerr << "Cannot parse " << envCValue << "." << std::endl;
95 return false;
96 }
97 hasEnvValue = true;
98 }
99
100 if (hasExistingValue) {
101 if (hasEnvValue) {
102 std::cerr << "Warning: cannot override existing value " << *out << " with "
103 << envKey << " (which is " << envValue << ")." << std::endl;
104 }
105 return false;
106 }
107 if (!hasEnvValue) {
108 std::cerr << "Warning: " << envKey << " is not specified. Default to " << T{} << "."
109 << std::endl;
110 return false;
111 }
112 *out = envValue;
113 return true;
114 }
115
Yifan Hongdbe9db32017-12-11 19:06:11 -0800116 static bool getBooleanFlag(const char* key) {
117 const char* envValue = getenv(key);
118 return envValue != nullptr && strcmp(envValue, "true") == 0;
119 }
120
121 static size_t getIntegerFlag(const char* key, size_t defaultValue = 0) {
122 std::string envValue = getenv(key);
123 if (envValue.empty()) {
124 return defaultValue;
125 }
126 size_t value;
127 if (!base::ParseUint(envValue, &value)) {
128 std::cerr << "Error: " << key << " must be a number." << std::endl;
129 return defaultValue;
130 }
131 return value;
132 }
133
Yifan Hong4650ad82017-05-01 17:28:02 -0700134 static std::string read(std::basic_istream<char>& is) {
135 std::stringstream ss;
136 ss << is.rdbuf();
137 return ss.str();
138 }
139
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700140 static bool isCommonConfig(const std::string& path) {
141 return ::android::base::Basename(path) == gBaseConfig;
142 }
143
Yifan Hongdbe9db32017-12-11 19:06:11 -0800144 static Level convertFromApiLevel(size_t apiLevel) {
145 if (apiLevel < 26) {
146 return Level::LEGACY;
147 } else if (apiLevel == 26) {
148 return Level::O;
149 } else if (apiLevel == 27) {
150 return Level::O_MR1;
151 } else {
152 return Level::UNSPECIFIED;
153 }
154 }
155
Yifan Hong079ec242017-08-25 18:53:38 -0700156 // nullptr on any error, otherwise the condition.
157 static Condition generateCondition(const std::string& path) {
158 std::string fname = ::android::base::Basename(path);
159 if (fname.size() <= gConfigPrefix.size() + gConfigSuffix.size() ||
160 !std::equal(gConfigPrefix.begin(), gConfigPrefix.end(), fname.begin()) ||
161 !std::equal(gConfigSuffix.rbegin(), gConfigSuffix.rend(), fname.rbegin())) {
162 return nullptr;
163 }
164
165 std::string sub = fname.substr(gConfigPrefix.size(),
166 fname.size() - gConfigPrefix.size() - gConfigSuffix.size());
167 if (sub.empty()) {
168 return nullptr; // should not happen
169 }
170 for (size_t i = 0; i < sub.size(); ++i) {
171 if (sub[i] == '-') {
172 sub[i] = '_';
173 continue;
174 }
175 if (isalnum(sub[i])) {
176 sub[i] = toupper(sub[i]);
177 continue;
178 }
179 std::cerr << "'" << fname << "' (in " << path
180 << ") is not a valid kernel config file name. Must match regex: "
181 << "android-base(-[0-9a-zA-Z-]+)?\\.cfg" << std::endl;
182 return nullptr;
183 }
184 sub.insert(0, "CONFIG_");
185 return std::make_unique<KernelConfig>(std::move(sub), Tristate::YES);
186 }
187
Yifan Hong79efa8a2017-07-06 14:10:28 -0700188 static bool parseFileForKernelConfigs(const std::string& path, std::vector<KernelConfig>* out) {
189 std::ifstream ifs{path};
190 if (!ifs.is_open()) {
191 std::cerr << "File '" << path << "' does not exist or cannot be read." << std::endl;
192 return false;
193 }
Yifan Hong02e94002017-07-10 15:41:56 -0700194 KernelConfigParser parser(true /* processComments */, true /* relaxedFormat */);
Yifan Hong79efa8a2017-07-06 14:10:28 -0700195 std::string content = read(ifs);
196 status_t err = parser.process(content.c_str(), content.size());
197 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -0700198 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -0700199 return false;
200 }
201 err = parser.finish();
202 if (err != OK) {
Yifan Hongae53a0e2017-07-07 15:19:06 -0700203 std::cerr << parser.error();
Yifan Hong79efa8a2017-07-06 14:10:28 -0700204 return false;
205 }
206
207 for (auto& configPair : parser.configs()) {
208 out->push_back({});
209 KernelConfig& config = out->back();
210 config.first = std::move(configPair.first);
211 if (!parseKernelConfigTypedValue(configPair.second, &config.second)) {
212 std::cerr << "Unknown value type for key = '" << config.first << "', value = '"
213 << configPair.second << "'\n";
214 return false;
215 }
216 }
217 return true;
218 }
219
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700220 static bool parseFilesForKernelConfigs(const std::string& path,
221 std::vector<ConditionedConfig>* out) {
222 out->clear();
223 ConditionedConfig commonConfig;
224 bool foundCommonConfig = false;
Steve Muckle0bef8682017-07-31 15:47:15 -0700225 bool ret = true;
226 char *pathIter;
227 char *modPath = new char[path.length() + 1];
228 strcpy(modPath, path.c_str());
229 pathIter = strtok(modPath, ":");
230 while (ret && pathIter != NULL) {
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700231 if (isCommonConfig(pathIter)) {
232 ret &= parseFileForKernelConfigs(pathIter, &commonConfig.second);
233 foundCommonConfig = true;
234 } else {
Yifan Hong079ec242017-08-25 18:53:38 -0700235 Condition condition = generateCondition(pathIter);
236 ret &= (condition != nullptr);
237
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700238 std::vector<KernelConfig> kernelConfigs;
239 if ((ret &= parseFileForKernelConfigs(pathIter, &kernelConfigs)))
Yifan Hong079ec242017-08-25 18:53:38 -0700240 out->emplace_back(std::move(condition), std::move(kernelConfigs));
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700241 }
Steve Muckle0bef8682017-07-31 15:47:15 -0700242 pathIter = strtok(NULL, ":");
243 }
Luis A. Lozano82266ae2017-08-22 16:30:11 -0700244 delete[] modPath;
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700245
246 if (!foundCommonConfig) {
247 std::cerr << "No android-base.cfg is found in these paths: '" << path << "'"
248 << std::endl;
249 }
250 ret &= foundCommonConfig;
251 // first element is always common configs (no conditions).
252 out->insert(out->begin(), std::move(commonConfig));
Steve Muckle0bef8682017-07-31 15:47:15 -0700253 return ret;
254 }
255
Yifan Hongdbe9db32017-12-11 19:06:11 -0800256 static std::string getFileNameFromPath(std::string path) {
257 auto idx = path.find_last_of("\\/");
258 if (idx != std::string::npos) {
259 path.erase(0, idx + 1);
260 }
261 return path;
262 }
263
Yifan Hong9aa63702017-05-16 16:37:50 -0700264 std::basic_ostream<char>& out() const {
265 return mOutFileRef == nullptr ? std::cout : *mOutFileRef;
266 }
267
Yifan Hongdbe9db32017-12-11 19:06:11 -0800268 template <typename S>
269 using Schemas = std::vector<std::pair<std::string, S>>;
270 using HalManifests = Schemas<HalManifest>;
271 using CompatibilityMatrices = Schemas<CompatibilityMatrix>;
272
273 bool assembleHalManifest(HalManifests* halManifests) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700274 std::string error;
Yifan Hongdbe9db32017-12-11 19:06:11 -0800275 HalManifest* halManifest = &halManifests->front().second;
276 for (auto it = halManifests->begin() + 1; it != halManifests->end(); ++it) {
277 const std::string& path = it->first;
278 HalManifest& halToAdd = it->second;
279
280 if (halToAdd.level() != Level::UNSPECIFIED) {
281 if (halManifest->level() == Level::UNSPECIFIED) {
282 halManifest->mLevel = halToAdd.level();
283 } else if (halManifest->level() != halToAdd.level()) {
284 std::cerr << "Inconsistent FCM Version in HAL manifests:" << std::endl
285 << " File '" << halManifests->front().first << "' has level "
286 << halManifest->level() << std::endl
287 << " File '" << path << "' has level " << halToAdd.level()
288 << std::endl;
289 return false;
290 }
291 }
292
Yifan Hongea25dd42017-12-18 17:03:24 -0800293 if (!halManifest->addAllHals(&halToAdd, &error)) {
Yifan Hongdbe9db32017-12-11 19:06:11 -0800294 std::cerr << "File \"" << path << "\" cannot be added: conflict on HAL \"" << error
295 << "\" with an existing HAL. See <hal> with the same name "
296 << "in previously parsed files or previously declared in this file."
297 << std::endl;
298 return false;
299 }
300 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700301
302 if (halManifest->mType == SchemaType::DEVICE) {
303 if (!getFlag("BOARD_SEPOLICY_VERS", &halManifest->device.mSepolicyVersion)) {
304 return false;
305 }
Yifan Hongdbe9db32017-12-11 19:06:11 -0800306 if (!setDeviceFcmVersion(halManifest)) {
307 return false;
308 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700309 }
310
311 if (mOutputMatrix) {
312 CompatibilityMatrix generatedMatrix = halManifest->generateCompatibleMatrix();
313 if (!halManifest->checkCompatibility(generatedMatrix, &error)) {
314 std::cerr << "FATAL ERROR: cannot generate a compatible matrix: " << error
315 << std::endl;
316 }
317 out() << "<!-- \n"
318 " Autogenerated skeleton compatibility matrix. \n"
319 " Use with caution. Modify it to suit your needs.\n"
320 " All HALs are set to optional.\n"
321 " Many entries other than HALs are zero-filled and\n"
322 " require human attention. \n"
323 "-->\n"
Yifan Honga2635c42017-12-12 13:20:33 -0800324 << gCompatibilityMatrixConverter(generatedMatrix, mSerializeFlags);
Yifan Hong9aa63702017-05-16 16:37:50 -0700325 } else {
Yifan Honga2635c42017-12-12 13:20:33 -0800326 out() << gHalManifestConverter(*halManifest, mSerializeFlags);
Yifan Hong9aa63702017-05-16 16:37:50 -0700327 }
328 out().flush();
329
330 if (mCheckFile.is_open()) {
331 CompatibilityMatrix checkMatrix;
332 if (!gCompatibilityMatrixConverter(&checkMatrix, read(mCheckFile))) {
333 std::cerr << "Cannot parse check file as a compatibility matrix: "
334 << gCompatibilityMatrixConverter.lastError() << std::endl;
335 return false;
336 }
337 if (!halManifest->checkCompatibility(checkMatrix, &error)) {
338 std::cerr << "Not compatible: " << error << std::endl;
339 return false;
340 }
341 }
342
343 return true;
344 }
345
Yifan Honge88e1672017-08-24 14:42:54 -0700346 bool assembleFrameworkCompatibilityMatrixKernels(CompatibilityMatrix* matrix) {
Yifan Hong4c34fee2017-08-24 16:03:34 -0700347 if (!matrix->framework.mKernels.empty()) {
348 // Remove hard-coded <kernel version="x.y.z" /> in legacy files.
349 std::cerr << "WARNING: framework compatibility matrix has hard-coded kernel"
350 << " requirements for version";
351 for (const auto& kernel : matrix->framework.mKernels) {
352 std::cerr << " " << kernel.minLts();
353 }
354 std::cerr << ". Hard-coded requirements are removed." << std::endl;
355 matrix->framework.mKernels.clear();
356 }
Yifan Honge88e1672017-08-24 14:42:54 -0700357 for (const auto& pair : mKernels) {
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700358 std::vector<ConditionedConfig> conditionedConfigs;
359 if (!parseFilesForKernelConfigs(pair.second, &conditionedConfigs)) {
Yifan Honge88e1672017-08-24 14:42:54 -0700360 return false;
361 }
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700362 for (ConditionedConfig& conditionedConfig : conditionedConfigs) {
Yifan Hong48602df2017-08-28 13:04:12 -0700363 MatrixKernel kernel(KernelVersion{pair.first.majorVer, pair.first.minorVer, 0u},
364 std::move(conditionedConfig.second));
Yifan Hong079ec242017-08-25 18:53:38 -0700365 if (conditionedConfig.first != nullptr)
366 kernel.mConditions.push_back(std::move(*conditionedConfig.first));
367 matrix->framework.mKernels.push_back(std::move(kernel));
Yifan Hong9a8b1a72017-08-25 17:55:33 -0700368 }
Yifan Honge88e1672017-08-24 14:42:54 -0700369 }
370 return true;
371 }
372
Yifan Hongdbe9db32017-12-11 19:06:11 -0800373 bool setDeviceFcmVersion(HalManifest* manifest) {
374 size_t shippingApiLevel = getIntegerFlag("PRODUCT_SHIPPING_API_LEVEL");
Yifan Hong9aa63702017-05-16 16:37:50 -0700375
Yifan Hongdbe9db32017-12-11 19:06:11 -0800376 if (manifest->level() != Level::UNSPECIFIED) {
377 return true;
378 }
379 if (!getBooleanFlag("PRODUCT_ENFORCE_VINTF_MANIFEST")) {
380 manifest->mLevel = Level::LEGACY;
381 return true;
382 }
383 // TODO(b/70628538): Do not infer from Shipping API level.
384 if (shippingApiLevel) {
385 std::cerr << "Warning: Shipping FCM Version is inferred from Shipping API level. "
386 << "Declare Shipping FCM Version in device manifest directly." << std::endl;
387 manifest->mLevel = convertFromApiLevel(shippingApiLevel);
388 if (manifest->mLevel == Level::UNSPECIFIED) {
389 std::cerr << "Error: Shipping FCM Version cannot be inferred from Shipping API "
390 << "level " << shippingApiLevel << "."
391 << "Declare Shipping FCM Version in device manifest directly."
392 << std::endl;
393 return false;
394 }
395 return true;
396 }
397 // TODO(b/69638851): should be an error if Shipping API level is not defined.
398 // For now, just leave it empty; when framework compatibility matrix is built,
399 // lowest FCM Version is assumed.
400 std::cerr << "Warning: Shipping FCM Version cannot be inferred, because:" << std::endl
401 << " (1) It is not explicitly declared in device manifest;" << std::endl
402 << " (2) PRODUCT_ENFORCE_VINTF_MANIFEST is set to true;" << std::endl
403 << " (3) PRODUCT_SHIPPING_API_LEVEL is undefined." << std::endl
404 << "Assuming 'unspecified' Shipping FCM Version. " << std::endl
405 << "To remove this warning, define 'level' attribute in device manifest."
406 << std::endl;
407 return true;
408 }
409
410 Level getLowestFcmVersion(const CompatibilityMatrices& matrices) {
411 Level ret = Level::UNSPECIFIED;
412 for (const auto& e : matrices) {
413 if (ret == Level::UNSPECIFIED || ret > e.second.level()) {
414 ret = e.second.level();
415 }
416 }
417 return ret;
418 }
419
420 bool assembleCompatibilityMatrix(CompatibilityMatrices* matrices) {
421 std::string error;
422 CompatibilityMatrix* matrix = nullptr;
Yifan Hong9aa63702017-05-16 16:37:50 -0700423 KernelSepolicyVersion kernelSepolicyVers;
424 Version sepolicyVers;
Yifan Hongdbe9db32017-12-11 19:06:11 -0800425 std::unique_ptr<HalManifest> checkManifest;
426 if (matrices->front().second.mType == SchemaType::DEVICE) {
427 matrix = &matrices->front().second;
428 }
429
430 if (matrices->front().second.mType == SchemaType::FRAMEWORK) {
431 Level deviceLevel = Level::UNSPECIFIED;
432 std::vector<std::string> fileList;
433 if (mCheckFile.is_open()) {
434 checkManifest = std::make_unique<HalManifest>();
435 if (!gHalManifestConverter(checkManifest.get(), read(mCheckFile))) {
436 std::cerr << "Cannot parse check file as a HAL manifest: "
437 << gHalManifestConverter.lastError() << std::endl;
438 return false;
439 }
440 deviceLevel = checkManifest->level();
441 }
442
443 if (deviceLevel == Level::UNSPECIFIED) {
444 // For GSI build, legacy devices that do not have a HAL manifest,
445 // and devices in development, merge all compatibility matrices.
446 deviceLevel = getLowestFcmVersion(*matrices);
447 }
448
449 for (auto& e : *matrices) {
450 if (e.second.level() == deviceLevel) {
451 fileList.push_back(e.first);
452 matrix = &e.second;
453 }
454 }
455 if (matrix == nullptr) {
456 std::cerr << "FATAL ERROR: cannot find matrix with level '" << deviceLevel << "'"
457 << std::endl;
458 return false;
459 }
460 for (auto& e : *matrices) {
461 if (e.second.level() <= deviceLevel) {
462 continue;
463 }
464 fileList.push_back(e.first);
465 if (!matrix->addAllHalsAsOptional(&e.second, &error)) {
466 std::cerr << "File \"" << e.first << "\" cannot be added: " << error
467 << ". See <hal> with the same name "
468 << "in previously parsed files or previously declared in this file."
469 << std::endl;
470 return false;
471 }
472 }
473
Yifan Hong9aa63702017-05-16 16:37:50 -0700474 if (!getFlag("BOARD_SEPOLICY_VERS", &sepolicyVers)) {
475 return false;
476 }
477 if (!getFlag("POLICYVERS", &kernelSepolicyVers)) {
478 return false;
479 }
Yifan Honge88e1672017-08-24 14:42:54 -0700480
481 if (!assembleFrameworkCompatibilityMatrixKernels(matrix)) {
482 return false;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700483 }
Yifan Honge88e1672017-08-24 14:42:54 -0700484
Yifan Hong9aa63702017-05-16 16:37:50 -0700485 matrix->framework.mSepolicy =
486 Sepolicy(kernelSepolicyVers, {{sepolicyVers.majorVer, sepolicyVers.minorVer}});
Yifan Hong7f6c00c2017-07-06 19:50:29 +0000487
488 Version avbMetaVersion;
489 if (!getFlag("FRAMEWORK_VBMETA_VERSION", &avbMetaVersion)) {
490 return false;
491 }
492 matrix->framework.mAvbMetaVersion = avbMetaVersion;
Yifan Hongdbe9db32017-12-11 19:06:11 -0800493
494 out() << "<!--" << std::endl;
495 out() << " Input:" << std::endl;
496 for (const auto& path : fileList) {
497 out() << " " << getFileNameFromPath(path) << std::endl;
498 }
499 out() << "-->" << std::endl;
Yifan Hong9aa63702017-05-16 16:37:50 -0700500 }
Yifan Honga2635c42017-12-12 13:20:33 -0800501 out() << gCompatibilityMatrixConverter(*matrix, mSerializeFlags);
Yifan Hong9aa63702017-05-16 16:37:50 -0700502 out().flush();
503
Yifan Hongdbe9db32017-12-11 19:06:11 -0800504 if (checkManifest != nullptr && getBooleanFlag("PRODUCT_ENFORCE_VINTF_MANIFEST") &&
505 !checkManifest->checkCompatibility(*matrix, &error)) {
506 std::cerr << "Not compatible: " << error << std::endl;
507 return false;
Yifan Hong9aa63702017-05-16 16:37:50 -0700508 }
509
510 return true;
511 }
512
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700513 enum AssembleStatus { SUCCESS, FAIL_AND_EXIT, TRY_NEXT };
514 template <typename Schema, typename AssembleFunc>
515 AssembleStatus tryAssemble(const XmlConverter<Schema>& converter, const std::string& schemaName,
516 AssembleFunc assemble) {
Yifan Hongdbe9db32017-12-11 19:06:11 -0800517 Schemas<Schema> schemas;
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700518 Schema schema;
Yifan Hongaa219f52017-12-18 18:51:59 -0800519 if (!converter(&schema, read(mInFiles.front().stream()))) {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700520 return TRY_NEXT;
521 }
522 auto firstType = schema.type();
Yifan Hongaa219f52017-12-18 18:51:59 -0800523 schemas.emplace_back(mInFiles.front().name(), std::move(schema));
Yifan Hongdbe9db32017-12-11 19:06:11 -0800524
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700525 for (auto it = mInFiles.begin() + 1; it != mInFiles.end(); ++it) {
526 Schema additionalSchema;
Yifan Hongaa219f52017-12-18 18:51:59 -0800527 const std::string& fileName = it->name();
528 if (!converter(&additionalSchema, read(it->stream()))) {
Yifan Hongdbe9db32017-12-11 19:06:11 -0800529 std::cerr << "File \"" << fileName << "\" is not a valid " << firstType << " "
530 << schemaName << " (but the first file is a valid " << firstType << " "
531 << schemaName << "). Error: " << converter.lastError() << std::endl;
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700532 return FAIL_AND_EXIT;
533 }
534 if (additionalSchema.type() != firstType) {
Yifan Hongdbe9db32017-12-11 19:06:11 -0800535 std::cerr << "File \"" << fileName << "\" is a " << additionalSchema.type() << " "
536 << schemaName << " (but a " << firstType << " " << schemaName
537 << " is expected)." << std::endl;
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700538 return FAIL_AND_EXIT;
539 }
Yifan Hongdbe9db32017-12-11 19:06:11 -0800540
541 schemas.emplace_back(fileName, std::move(additionalSchema));
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700542 }
Yifan Hongdbe9db32017-12-11 19:06:11 -0800543 return assemble(&schemas) ? SUCCESS : FAIL_AND_EXIT;
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700544 }
545
Yifan Hong9aa63702017-05-16 16:37:50 -0700546 bool assemble() {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700547 using std::placeholders::_1;
548 if (mInFiles.empty()) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700549 std::cerr << "Missing input file." << std::endl;
550 return false;
551 }
552
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700553 auto status = tryAssemble(gHalManifestConverter, "manifest",
554 std::bind(&AssembleVintf::assembleHalManifest, this, _1));
555 if (status == SUCCESS) return true;
556 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000557
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700558 resetInFiles();
Yifan Honga59d2562017-04-18 18:01:16 -0700559
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700560 status = tryAssemble(gCompatibilityMatrixConverter, "compatibility matrix",
561 std::bind(&AssembleVintf::assembleCompatibilityMatrix, this, _1));
562 if (status == SUCCESS) return true;
563 if (status == FAIL_AND_EXIT) return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000564
Yifan Hong959ee1b2017-04-28 14:37:56 -0700565 std::cerr << "Input file has unknown format." << std::endl
566 << "Error when attempting to convert to manifest: "
567 << gHalManifestConverter.lastError() << std::endl
568 << "Error when attempting to convert to compatibility matrix: "
569 << gCompatibilityMatrixConverter.lastError() << std::endl;
570 return false;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000571 }
Yifan Hong9aa63702017-05-16 16:37:50 -0700572
573 bool openOutFile(const char* path) {
574 mOutFileRef = std::make_unique<std::ofstream>();
575 mOutFileRef->open(path);
576 return mOutFileRef->is_open();
577 }
578
579 bool openInFile(const char* path) {
Yifan Hongaa219f52017-12-18 18:51:59 -0800580 auto s = std::make_unique<std::ifstream>(path);
581 if (!s->is_open()) return false;
582 mInFiles.emplace(mInFiles.end(), std::string{path}, std::move(s));
583 return true;
Yifan Hong9aa63702017-05-16 16:37:50 -0700584 }
585
586 bool openCheckFile(const char* path) {
587 mCheckFile.open(path);
588 return mCheckFile.is_open();
589 }
590
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700591 void resetInFiles() {
592 for (auto& inFile : mInFiles) {
Yifan Hongaa219f52017-12-18 18:51:59 -0800593 inFile.stream().clear();
594 inFile.stream().seekg(0);
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700595 }
596 }
597
Yifan Hong9aa63702017-05-16 16:37:50 -0700598 void setOutputMatrix() { mOutputMatrix = true; }
599
Yifan Honga2635c42017-12-12 13:20:33 -0800600 bool setHalsOnly() {
601 if (mSerializeFlags) return false;
602 mSerializeFlags |= SerializeFlag::HALS_ONLY;
603 return true;
604 }
605
606 bool setNoHals() {
607 if (mSerializeFlags) return false;
608 mSerializeFlags |= SerializeFlag::NO_HALS;
609 return true;
610 }
611
Yifan Hong79efa8a2017-07-06 14:10:28 -0700612 bool addKernel(const std::string& kernelArg) {
613 auto ind = kernelArg.find(':');
614 if (ind == std::string::npos) {
615 std::cerr << "Unrecognized --kernel option '" << kernelArg << "'" << std::endl;
616 return false;
617 }
618 std::string kernelVerStr{kernelArg.begin(), kernelArg.begin() + ind};
619 std::string kernelConfigPath{kernelArg.begin() + ind + 1, kernelArg.end()};
620 Version kernelVer;
621 if (!parse(kernelVerStr, &kernelVer)) {
622 std::cerr << "Unrecognized kernel version '" << kernelVerStr << "'" << std::endl;
623 return false;
624 }
Yifan Hong48602df2017-08-28 13:04:12 -0700625 if (mKernels.find(kernelVer) != mKernels.end()) {
626 std::cerr << "Multiple --kernel for " << kernelVer << " is specified." << std::endl;
627 return false;
628 }
629 mKernels[kernelVer] = kernelConfigPath;
Yifan Hong79efa8a2017-07-06 14:10:28 -0700630 return true;
631 }
632
Yifan Hong9aa63702017-05-16 16:37:50 -0700633 private:
Yifan Hongaa219f52017-12-18 18:51:59 -0800634 std::vector<NamedIstream> mInFiles;
Yifan Hong9aa63702017-05-16 16:37:50 -0700635 std::unique_ptr<std::ofstream> mOutFileRef;
636 std::ifstream mCheckFile;
637 bool mOutputMatrix = false;
Yifan Honga2635c42017-12-12 13:20:33 -0800638 SerializeFlags mSerializeFlags = SerializeFlag::EVERYTHING;
Yifan Hong48602df2017-08-28 13:04:12 -0700639 std::map<Version, std::string> mKernels;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000640};
641
642} // namespace vintf
643} // namespace android
644
645void help() {
Yifan Hong9aa63702017-05-16 16:37:50 -0700646 std::cerr << "assemble_vintf: Checks if a given manifest / matrix file is valid and \n"
647 " fill in build-time flags into the given file.\n"
648 "assemble_vintf -h\n"
649 " Display this help text.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700650 "assemble_vintf -i <input file>[:<input file>[...]] [-o <output file>] [-m]\n"
651 " [-c [<check file>]]\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700652 " Fill in build-time flags into the given file.\n"
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700653 " -i <input file>[:<input file>[...]]\n"
654 " A list of input files. Format is automatically detected for the\n"
655 " first file, and the remaining files must have the same format.\n"
656 " Files other than the first file should only have <hal> defined;\n"
657 " other entries are ignored.\n"
Yifan Hong9aa63702017-05-16 16:37:50 -0700658 " -o <output file>\n"
659 " Optional output file. If not specified, write to stdout.\n"
660 " -m\n"
661 " a compatible compatibility matrix is\n"
662 " generated instead; for example, given a device manifest,\n"
663 " a framework compatibility matrix is generated. This flag\n"
664 " is ignored when input is a compatibility matrix.\n"
665 " -c [<check file>]\n"
666 " After writing the output file, check compatibility between\n"
667 " output file and check file.\n"
668 " If -c is set but the check file is not specified, a warning\n"
669 " message is written to stderr. Return 0.\n"
670 " If the check file is specified but is not compatible, an error\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700671 " message is written to stderr. Return 1.\n"
Steve Muckle0bef8682017-07-31 15:47:15 -0700672 " --kernel=<version>:<android-base.cfg>[:<android-base-arch.cfg>[...]]\n"
Yifan Hong79efa8a2017-07-06 14:10:28 -0700673 " Add a kernel entry to framework compatibility matrix.\n"
674 " Ignored for other input format.\n"
675 " <version> has format: 3.18\n"
Steve Muckle0bef8682017-07-31 15:47:15 -0700676 " <android-base.cfg> is the location of android-base.cfg\n"
677 " <android-base-arch.cfg> is the location of an optional\n"
Yifan Honga2635c42017-12-12 13:20:33 -0800678 " arch-specific config fragment, more than one may be specified\n"
679 " -l, --hals-only\n"
680 " Output has only <hal> entries. Cannot be used with -n.\n"
681 " -n, --no-hals\n"
682 " Output has no <hal> entries (but all other entries).\n"
683 " Cannot be used with -l.\n";
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000684}
685
686int main(int argc, char **argv) {
Yifan Honga2635c42017-12-12 13:20:33 -0800687 const struct option longopts[] = {{"kernel", required_argument, NULL, 'k'},
688 {"hals-only", no_argument, NULL, 'l'},
689 {"no-hals", no_argument, NULL, 'n'},
690 {0, 0, 0, 0}};
Yifan Hong9aa63702017-05-16 16:37:50 -0700691
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700692 std::string outFilePath;
Yifan Hong9aa63702017-05-16 16:37:50 -0700693 ::android::vintf::AssembleVintf assembleVintf;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000694 int res;
Yifan Hong9aa63702017-05-16 16:37:50 -0700695 int optind;
Yifan Honga2635c42017-12-12 13:20:33 -0800696 while ((res = getopt_long(argc, argv, "hi:o:mc:nl", longopts, &optind)) >= 0) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000697 switch (res) {
698 case 'i': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700699 char* inFilePath = strtok(optarg, ":");
700 while (inFilePath != NULL) {
701 if (!assembleVintf.openInFile(inFilePath)) {
702 std::cerr << "Failed to open " << optarg << std::endl;
703 return 1;
704 }
705 inFilePath = strtok(NULL, ":");
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000706 }
707 } break;
708
709 case 'o': {
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700710 outFilePath = optarg;
Yifan Hong9aa63702017-05-16 16:37:50 -0700711 if (!assembleVintf.openOutFile(optarg)) {
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000712 std::cerr << "Failed to open " << optarg << std::endl;
713 return 1;
714 }
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000715 } break;
716
Yifan Honga59d2562017-04-18 18:01:16 -0700717 case 'm': {
Yifan Hong9aa63702017-05-16 16:37:50 -0700718 assembleVintf.setOutputMatrix();
Yifan Honga59d2562017-04-18 18:01:16 -0700719 } break;
720
Yifan Hong4650ad82017-05-01 17:28:02 -0700721 case 'c': {
722 if (strlen(optarg) != 0) {
Yifan Hong9aa63702017-05-16 16:37:50 -0700723 if (!assembleVintf.openCheckFile(optarg)) {
Yifan Hong4650ad82017-05-01 17:28:02 -0700724 std::cerr << "Failed to open " << optarg << std::endl;
725 return 1;
726 }
727 } else {
728 std::cerr << "WARNING: no compatibility check is done on "
Yifan Hongbfb3c1d2017-05-24 14:38:48 -0700729 << (outFilePath.empty() ? "output" : outFilePath) << std::endl;
Yifan Hong4650ad82017-05-01 17:28:02 -0700730 }
731 } break;
732
Yifan Hong79efa8a2017-07-06 14:10:28 -0700733 case 'k': {
734 if (!assembleVintf.addKernel(optarg)) {
735 std::cerr << "ERROR: Unrecognized --kernel argument." << std::endl;
736 return 1;
737 }
738 } break;
739
Yifan Honga2635c42017-12-12 13:20:33 -0800740 case 'l': {
741 if (!assembleVintf.setHalsOnly()) {
742 return 1;
743 }
744 } break;
745
746 case 'n': {
747 if (!assembleVintf.setNoHals()) {
748 return 1;
749 }
750 } break;
751
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000752 case 'h':
753 default: {
754 help();
755 return 1;
756 } break;
757 }
758 }
759
Yifan Hong9aa63702017-05-16 16:37:50 -0700760 bool success = assembleVintf.assemble();
Yifan Hong4650ad82017-05-01 17:28:02 -0700761
762 return success ? 0 : 1;
Yifan Hong4d18bcc2017-04-07 21:47:16 +0000763}