AssembleVintf.cpp revision ea25dd45961fc0b23ae9ba602c6d090590aa8241
1/*
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
17#include <getopt.h>
18#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
27#include <android-base/file.h>
28#include <android-base/parseint.h>
29
30#include <vintf/KernelConfigParser.h>
31#include <vintf/parse_string.h>
32#include <vintf/parse_xml.h>
33
34#define BUFFER_SIZE sysconf(_SC_PAGESIZE)
35
36namespace android {
37namespace vintf {
38
39static const std::string gConfigPrefix = "android-base-";
40static const std::string gConfigSuffix = ".cfg";
41static const std::string gBaseConfig = "android-base.cfg";
42
43/**
44 * Slurps the device manifest file and add build time flag to it.
45 */
46class AssembleVintf {
47    using Condition = std::unique_ptr<KernelConfig>;
48    using ConditionedConfig = std::pair<Condition, std::vector<KernelConfig> /* configs */>;
49
50   public:
51    template<typename T>
52    static bool getFlag(const std::string& key, T* value) {
53        const char *envValue = getenv(key.c_str());
54        if (envValue == NULL) {
55            std::cerr << "Warning: " << key << " is missing, defaulted to " << (*value)
56                      << std::endl;
57            return true;
58        }
59
60        if (!parse(envValue, value)) {
61            std::cerr << "Cannot parse " << envValue << "." << std::endl;
62            return false;
63        }
64        return true;
65    }
66
67    static bool getBooleanFlag(const char* key) {
68        const char* envValue = getenv(key);
69        return envValue != nullptr && strcmp(envValue, "true") == 0;
70    }
71
72    static size_t getIntegerFlag(const char* key, size_t defaultValue = 0) {
73        std::string envValue = getenv(key);
74        if (envValue.empty()) {
75            return defaultValue;
76        }
77        size_t value;
78        if (!base::ParseUint(envValue, &value)) {
79            std::cerr << "Error: " << key << " must be a number." << std::endl;
80            return defaultValue;
81        }
82        return value;
83    }
84
85    static std::string read(std::basic_istream<char>& is) {
86        std::stringstream ss;
87        ss << is.rdbuf();
88        return ss.str();
89    }
90
91    static bool isCommonConfig(const std::string& path) {
92        return ::android::base::Basename(path) == gBaseConfig;
93    }
94
95    static Level convertFromApiLevel(size_t apiLevel) {
96        if (apiLevel < 26) {
97            return Level::LEGACY;
98        } else if (apiLevel == 26) {
99            return Level::O;
100        } else if (apiLevel == 27) {
101            return Level::O_MR1;
102        } else {
103            return Level::UNSPECIFIED;
104        }
105    }
106
107    // nullptr on any error, otherwise the condition.
108    static Condition generateCondition(const std::string& path) {
109        std::string fname = ::android::base::Basename(path);
110        if (fname.size() <= gConfigPrefix.size() + gConfigSuffix.size() ||
111            !std::equal(gConfigPrefix.begin(), gConfigPrefix.end(), fname.begin()) ||
112            !std::equal(gConfigSuffix.rbegin(), gConfigSuffix.rend(), fname.rbegin())) {
113            return nullptr;
114        }
115
116        std::string sub = fname.substr(gConfigPrefix.size(),
117                                       fname.size() - gConfigPrefix.size() - gConfigSuffix.size());
118        if (sub.empty()) {
119            return nullptr;  // should not happen
120        }
121        for (size_t i = 0; i < sub.size(); ++i) {
122            if (sub[i] == '-') {
123                sub[i] = '_';
124                continue;
125            }
126            if (isalnum(sub[i])) {
127                sub[i] = toupper(sub[i]);
128                continue;
129            }
130            std::cerr << "'" << fname << "' (in " << path
131                      << ") is not a valid kernel config file name. Must match regex: "
132                      << "android-base(-[0-9a-zA-Z-]+)?\\.cfg" << std::endl;
133            return nullptr;
134        }
135        sub.insert(0, "CONFIG_");
136        return std::make_unique<KernelConfig>(std::move(sub), Tristate::YES);
137    }
138
139    static bool parseFileForKernelConfigs(const std::string& path, std::vector<KernelConfig>* out) {
140        std::ifstream ifs{path};
141        if (!ifs.is_open()) {
142            std::cerr << "File '" << path << "' does not exist or cannot be read." << std::endl;
143            return false;
144        }
145        KernelConfigParser parser(true /* processComments */, true /* relaxedFormat */);
146        std::string content = read(ifs);
147        status_t err = parser.process(content.c_str(), content.size());
148        if (err != OK) {
149            std::cerr << parser.error();
150            return false;
151        }
152        err = parser.finish();
153        if (err != OK) {
154            std::cerr << parser.error();
155            return false;
156        }
157
158        for (auto& configPair : parser.configs()) {
159            out->push_back({});
160            KernelConfig& config = out->back();
161            config.first = std::move(configPair.first);
162            if (!parseKernelConfigTypedValue(configPair.second, &config.second)) {
163                std::cerr << "Unknown value type for key = '" << config.first << "', value = '"
164                          << configPair.second << "'\n";
165                return false;
166            }
167        }
168        return true;
169    }
170
171    static bool parseFilesForKernelConfigs(const std::string& path,
172                                           std::vector<ConditionedConfig>* out) {
173        out->clear();
174        ConditionedConfig commonConfig;
175        bool foundCommonConfig = false;
176        bool ret = true;
177        char *pathIter;
178        char *modPath = new char[path.length() + 1];
179        strcpy(modPath, path.c_str());
180        pathIter = strtok(modPath, ":");
181        while (ret && pathIter != NULL) {
182            if (isCommonConfig(pathIter)) {
183                ret &= parseFileForKernelConfigs(pathIter, &commonConfig.second);
184                foundCommonConfig = true;
185            } else {
186                Condition condition = generateCondition(pathIter);
187                ret &= (condition != nullptr);
188
189                std::vector<KernelConfig> kernelConfigs;
190                if ((ret &= parseFileForKernelConfigs(pathIter, &kernelConfigs)))
191                    out->emplace_back(std::move(condition), std::move(kernelConfigs));
192            }
193            pathIter = strtok(NULL, ":");
194        }
195        delete[] modPath;
196
197        if (!foundCommonConfig) {
198            std::cerr << "No android-base.cfg is found in these paths: '" << path << "'"
199                      << std::endl;
200        }
201        ret &= foundCommonConfig;
202        // first element is always common configs (no conditions).
203        out->insert(out->begin(), std::move(commonConfig));
204        return ret;
205    }
206
207    static std::string getFileNameFromPath(std::string path) {
208        auto idx = path.find_last_of("\\/");
209        if (idx != std::string::npos) {
210            path.erase(0, idx + 1);
211        }
212        return path;
213    }
214
215    std::basic_ostream<char>& out() const {
216        return mOutFileRef == nullptr ? std::cout : *mOutFileRef;
217    }
218
219    template <typename S>
220    using Schemas = std::vector<std::pair<std::string, S>>;
221    using HalManifests = Schemas<HalManifest>;
222    using CompatibilityMatrices = Schemas<CompatibilityMatrix>;
223
224    bool assembleHalManifest(HalManifests* halManifests) {
225        std::string error;
226        HalManifest* halManifest = &halManifests->front().second;
227        for (auto it = halManifests->begin() + 1; it != halManifests->end(); ++it) {
228            const std::string& path = it->first;
229            HalManifest& halToAdd = it->second;
230
231            if (halToAdd.level() != Level::UNSPECIFIED) {
232                if (halManifest->level() == Level::UNSPECIFIED) {
233                    halManifest->mLevel = halToAdd.level();
234                } else if (halManifest->level() != halToAdd.level()) {
235                    std::cerr << "Inconsistent FCM Version in HAL manifests:" << std::endl
236                              << "    File '" << halManifests->front().first << "' has level "
237                              << halManifest->level() << std::endl
238                              << "    File '" << path << "' has level " << halToAdd.level()
239                              << std::endl;
240                    return false;
241                }
242            }
243
244            if (!halManifest->addAllHals(&halToAdd, &error)) {
245                std::cerr << "File \"" << path << "\" cannot be added: conflict on HAL \"" << error
246                          << "\" with an existing HAL. See <hal> with the same name "
247                          << "in previously parsed files or previously declared in this file."
248                          << std::endl;
249                return false;
250            }
251        }
252
253        if (halManifest->mType == SchemaType::DEVICE) {
254            if (!getFlag("BOARD_SEPOLICY_VERS", &halManifest->device.mSepolicyVersion)) {
255                return false;
256            }
257            if (!setDeviceFcmVersion(halManifest)) {
258                return false;
259            }
260        }
261
262        if (mOutputMatrix) {
263            CompatibilityMatrix generatedMatrix = halManifest->generateCompatibleMatrix();
264            if (!halManifest->checkCompatibility(generatedMatrix, &error)) {
265                std::cerr << "FATAL ERROR: cannot generate a compatible matrix: " << error
266                          << std::endl;
267            }
268            out() << "<!-- \n"
269                     "    Autogenerated skeleton compatibility matrix. \n"
270                     "    Use with caution. Modify it to suit your needs.\n"
271                     "    All HALs are set to optional.\n"
272                     "    Many entries other than HALs are zero-filled and\n"
273                     "    require human attention. \n"
274                     "-->\n"
275                  << gCompatibilityMatrixConverter(generatedMatrix, mSerializeFlags);
276        } else {
277            out() << gHalManifestConverter(*halManifest, mSerializeFlags);
278        }
279        out().flush();
280
281        if (mCheckFile.is_open()) {
282            CompatibilityMatrix checkMatrix;
283            if (!gCompatibilityMatrixConverter(&checkMatrix, read(mCheckFile))) {
284                std::cerr << "Cannot parse check file as a compatibility matrix: "
285                          << gCompatibilityMatrixConverter.lastError() << std::endl;
286                return false;
287            }
288            if (!halManifest->checkCompatibility(checkMatrix, &error)) {
289                std::cerr << "Not compatible: " << error << std::endl;
290                return false;
291            }
292        }
293
294        return true;
295    }
296
297    bool assembleFrameworkCompatibilityMatrixKernels(CompatibilityMatrix* matrix) {
298        if (!matrix->framework.mKernels.empty()) {
299            // Remove hard-coded <kernel version="x.y.z" /> in legacy files.
300            std::cerr << "WARNING: framework compatibility matrix has hard-coded kernel"
301                      << " requirements for version";
302            for (const auto& kernel : matrix->framework.mKernels) {
303                std::cerr << " " << kernel.minLts();
304            }
305            std::cerr << ". Hard-coded requirements are removed." << std::endl;
306            matrix->framework.mKernels.clear();
307        }
308        for (const auto& pair : mKernels) {
309            std::vector<ConditionedConfig> conditionedConfigs;
310            if (!parseFilesForKernelConfigs(pair.second, &conditionedConfigs)) {
311                return false;
312            }
313            for (ConditionedConfig& conditionedConfig : conditionedConfigs) {
314                MatrixKernel kernel(KernelVersion{pair.first.majorVer, pair.first.minorVer, 0u},
315                                    std::move(conditionedConfig.second));
316                if (conditionedConfig.first != nullptr)
317                    kernel.mConditions.push_back(std::move(*conditionedConfig.first));
318                matrix->framework.mKernels.push_back(std::move(kernel));
319            }
320        }
321        return true;
322    }
323
324    bool setDeviceFcmVersion(HalManifest* manifest) {
325        size_t shippingApiLevel = getIntegerFlag("PRODUCT_SHIPPING_API_LEVEL");
326
327        if (manifest->level() != Level::UNSPECIFIED) {
328            return true;
329        }
330        if (!getBooleanFlag("PRODUCT_ENFORCE_VINTF_MANIFEST")) {
331            manifest->mLevel = Level::LEGACY;
332            return true;
333        }
334        // TODO(b/70628538): Do not infer from Shipping API level.
335        if (shippingApiLevel) {
336            std::cerr << "Warning: Shipping FCM Version is inferred from Shipping API level. "
337                      << "Declare Shipping FCM Version in device manifest directly." << std::endl;
338            manifest->mLevel = convertFromApiLevel(shippingApiLevel);
339            if (manifest->mLevel == Level::UNSPECIFIED) {
340                std::cerr << "Error: Shipping FCM Version cannot be inferred from Shipping API "
341                          << "level " << shippingApiLevel << "."
342                          << "Declare Shipping FCM Version in device manifest directly."
343                          << std::endl;
344                return false;
345            }
346            return true;
347        }
348        // TODO(b/69638851): should be an error if Shipping API level is not defined.
349        // For now, just leave it empty; when framework compatibility matrix is built,
350        // lowest FCM Version is assumed.
351        std::cerr << "Warning: Shipping FCM Version cannot be inferred, because:" << std::endl
352                  << "    (1) It is not explicitly declared in device manifest;" << std::endl
353                  << "    (2) PRODUCT_ENFORCE_VINTF_MANIFEST is set to true;" << std::endl
354                  << "    (3) PRODUCT_SHIPPING_API_LEVEL is undefined." << std::endl
355                  << "Assuming 'unspecified' Shipping FCM Version. " << std::endl
356                  << "To remove this warning, define 'level' attribute in device manifest."
357                  << std::endl;
358        return true;
359    }
360
361    Level getLowestFcmVersion(const CompatibilityMatrices& matrices) {
362        Level ret = Level::UNSPECIFIED;
363        for (const auto& e : matrices) {
364            if (ret == Level::UNSPECIFIED || ret > e.second.level()) {
365                ret = e.second.level();
366            }
367        }
368        return ret;
369    }
370
371    bool assembleCompatibilityMatrix(CompatibilityMatrices* matrices) {
372        std::string error;
373        CompatibilityMatrix* matrix = nullptr;
374        KernelSepolicyVersion kernelSepolicyVers;
375        Version sepolicyVers;
376        std::unique_ptr<HalManifest> checkManifest;
377        if (matrices->front().second.mType == SchemaType::DEVICE) {
378            matrix = &matrices->front().second;
379        }
380
381        if (matrices->front().second.mType == SchemaType::FRAMEWORK) {
382            Level deviceLevel = Level::UNSPECIFIED;
383            std::vector<std::string> fileList;
384            if (mCheckFile.is_open()) {
385                checkManifest = std::make_unique<HalManifest>();
386                if (!gHalManifestConverter(checkManifest.get(), read(mCheckFile))) {
387                    std::cerr << "Cannot parse check file as a HAL manifest: "
388                              << gHalManifestConverter.lastError() << std::endl;
389                    return false;
390                }
391                deviceLevel = checkManifest->level();
392            }
393
394            if (deviceLevel == Level::UNSPECIFIED) {
395                // For GSI build, legacy devices that do not have a HAL manifest,
396                // and devices in development, merge all compatibility matrices.
397                deviceLevel = getLowestFcmVersion(*matrices);
398            }
399
400            for (auto& e : *matrices) {
401                if (e.second.level() == deviceLevel) {
402                    fileList.push_back(e.first);
403                    matrix = &e.second;
404                }
405            }
406            if (matrix == nullptr) {
407                std::cerr << "FATAL ERROR: cannot find matrix with level '" << deviceLevel << "'"
408                          << std::endl;
409                return false;
410            }
411            for (auto& e : *matrices) {
412                if (e.second.level() <= deviceLevel) {
413                    continue;
414                }
415                fileList.push_back(e.first);
416                if (!matrix->addAllHalsAsOptional(&e.second, &error)) {
417                    std::cerr << "File \"" << e.first << "\" cannot be added: " << error
418                              << ". See <hal> with the same name "
419                              << "in previously parsed files or previously declared in this file."
420                              << std::endl;
421                    return false;
422                }
423            }
424
425            if (!getFlag("BOARD_SEPOLICY_VERS", &sepolicyVers)) {
426                return false;
427            }
428            if (!getFlag("POLICYVERS", &kernelSepolicyVers)) {
429                return false;
430            }
431
432            if (!assembleFrameworkCompatibilityMatrixKernels(matrix)) {
433                return false;
434            }
435
436            matrix->framework.mSepolicy =
437                Sepolicy(kernelSepolicyVers, {{sepolicyVers.majorVer, sepolicyVers.minorVer}});
438
439            Version avbMetaVersion;
440            if (!getFlag("FRAMEWORK_VBMETA_VERSION", &avbMetaVersion)) {
441                return false;
442            }
443            matrix->framework.mAvbMetaVersion = avbMetaVersion;
444
445            out() << "<!--" << std::endl;
446            out() << "    Input:" << std::endl;
447            for (const auto& path : fileList) {
448                out() << "        " << getFileNameFromPath(path) << std::endl;
449            }
450            out() << "-->" << std::endl;
451        }
452        out() << gCompatibilityMatrixConverter(*matrix, mSerializeFlags);
453        out().flush();
454
455        if (checkManifest != nullptr && getBooleanFlag("PRODUCT_ENFORCE_VINTF_MANIFEST") &&
456            !checkManifest->checkCompatibility(*matrix, &error)) {
457            std::cerr << "Not compatible: " << error << std::endl;
458            return false;
459        }
460
461        return true;
462    }
463
464    enum AssembleStatus { SUCCESS, FAIL_AND_EXIT, TRY_NEXT };
465    template <typename Schema, typename AssembleFunc>
466    AssembleStatus tryAssemble(const XmlConverter<Schema>& converter, const std::string& schemaName,
467                               AssembleFunc assemble) {
468        Schemas<Schema> schemas;
469        Schema schema;
470        if (!converter(&schema, read(mInFiles.front()))) {
471            return TRY_NEXT;
472        }
473        auto firstType = schema.type();
474        schemas.emplace_back(mInFilePaths.front(), std::move(schema));
475
476        for (auto it = mInFiles.begin() + 1; it != mInFiles.end(); ++it) {
477            Schema additionalSchema;
478            const std::string fileName = mInFilePaths[std::distance(mInFiles.begin(), it)];
479            if (!converter(&additionalSchema, read(*it))) {
480                std::cerr << "File \"" << fileName << "\" is not a valid " << firstType << " "
481                          << schemaName << " (but the first file is a valid " << firstType << " "
482                          << schemaName << "). Error: " << converter.lastError() << std::endl;
483                return FAIL_AND_EXIT;
484            }
485            if (additionalSchema.type() != firstType) {
486                std::cerr << "File \"" << fileName << "\" is a " << additionalSchema.type() << " "
487                          << schemaName << " (but a " << firstType << " " << schemaName
488                          << " is expected)." << std::endl;
489                return FAIL_AND_EXIT;
490            }
491
492            schemas.emplace_back(fileName, std::move(additionalSchema));
493        }
494        return assemble(&schemas) ? SUCCESS : FAIL_AND_EXIT;
495    }
496
497    bool assemble() {
498        using std::placeholders::_1;
499        if (mInFiles.empty()) {
500            std::cerr << "Missing input file." << std::endl;
501            return false;
502        }
503
504        auto status = tryAssemble(gHalManifestConverter, "manifest",
505                                  std::bind(&AssembleVintf::assembleHalManifest, this, _1));
506        if (status == SUCCESS) return true;
507        if (status == FAIL_AND_EXIT) return false;
508
509        resetInFiles();
510
511        status = tryAssemble(gCompatibilityMatrixConverter, "compatibility matrix",
512                             std::bind(&AssembleVintf::assembleCompatibilityMatrix, this, _1));
513        if (status == SUCCESS) return true;
514        if (status == FAIL_AND_EXIT) return false;
515
516        std::cerr << "Input file has unknown format." << std::endl
517                  << "Error when attempting to convert to manifest: "
518                  << gHalManifestConverter.lastError() << std::endl
519                  << "Error when attempting to convert to compatibility matrix: "
520                  << gCompatibilityMatrixConverter.lastError() << std::endl;
521        return false;
522    }
523
524    bool openOutFile(const char* path) {
525        mOutFileRef = std::make_unique<std::ofstream>();
526        mOutFileRef->open(path);
527        return mOutFileRef->is_open();
528    }
529
530    bool openInFile(const char* path) {
531        mInFilePaths.push_back(path);
532        mInFiles.push_back({});
533        mInFiles.back().open(path);
534        return mInFiles.back().is_open();
535    }
536
537    bool openCheckFile(const char* path) {
538        mCheckFile.open(path);
539        return mCheckFile.is_open();
540    }
541
542    void resetInFiles() {
543        for (auto& inFile : mInFiles) {
544            inFile.clear();
545            inFile.seekg(0);
546        }
547    }
548
549    void setOutputMatrix() { mOutputMatrix = true; }
550
551    bool setHalsOnly() {
552        if (mSerializeFlags) return false;
553        mSerializeFlags |= SerializeFlag::HALS_ONLY;
554        return true;
555    }
556
557    bool setNoHals() {
558        if (mSerializeFlags) return false;
559        mSerializeFlags |= SerializeFlag::NO_HALS;
560        return true;
561    }
562
563    bool addKernel(const std::string& kernelArg) {
564        auto ind = kernelArg.find(':');
565        if (ind == std::string::npos) {
566            std::cerr << "Unrecognized --kernel option '" << kernelArg << "'" << std::endl;
567            return false;
568        }
569        std::string kernelVerStr{kernelArg.begin(), kernelArg.begin() + ind};
570        std::string kernelConfigPath{kernelArg.begin() + ind + 1, kernelArg.end()};
571        Version kernelVer;
572        if (!parse(kernelVerStr, &kernelVer)) {
573            std::cerr << "Unrecognized kernel version '" << kernelVerStr << "'" << std::endl;
574            return false;
575        }
576        if (mKernels.find(kernelVer) != mKernels.end()) {
577            std::cerr << "Multiple --kernel for " << kernelVer << " is specified." << std::endl;
578            return false;
579        }
580        mKernels[kernelVer] = kernelConfigPath;
581        return true;
582    }
583
584   private:
585    std::vector<std::string> mInFilePaths;
586    std::vector<std::ifstream> mInFiles;
587    std::unique_ptr<std::ofstream> mOutFileRef;
588    std::ifstream mCheckFile;
589    bool mOutputMatrix = false;
590    SerializeFlags mSerializeFlags = SerializeFlag::EVERYTHING;
591    std::map<Version, std::string> mKernels;
592};
593
594}  // namespace vintf
595}  // namespace android
596
597void help() {
598    std::cerr << "assemble_vintf: Checks if a given manifest / matrix file is valid and \n"
599                 "    fill in build-time flags into the given file.\n"
600                 "assemble_vintf -h\n"
601                 "               Display this help text.\n"
602                 "assemble_vintf -i <input file>[:<input file>[...]] [-o <output file>] [-m]\n"
603                 "               [-c [<check file>]]\n"
604                 "               Fill in build-time flags into the given file.\n"
605                 "    -i <input file>[:<input file>[...]]\n"
606                 "               A list of input files. Format is automatically detected for the\n"
607                 "               first file, and the remaining files must have the same format.\n"
608                 "               Files other than the first file should only have <hal> defined;\n"
609                 "               other entries are ignored.\n"
610                 "    -o <output file>\n"
611                 "               Optional output file. If not specified, write to stdout.\n"
612                 "    -m\n"
613                 "               a compatible compatibility matrix is\n"
614                 "               generated instead; for example, given a device manifest,\n"
615                 "               a framework compatibility matrix is generated. This flag\n"
616                 "               is ignored when input is a compatibility matrix.\n"
617                 "    -c [<check file>]\n"
618                 "               After writing the output file, check compatibility between\n"
619                 "               output file and check file.\n"
620                 "               If -c is set but the check file is not specified, a warning\n"
621                 "               message is written to stderr. Return 0.\n"
622                 "               If the check file is specified but is not compatible, an error\n"
623                 "               message is written to stderr. Return 1.\n"
624                 "    --kernel=<version>:<android-base.cfg>[:<android-base-arch.cfg>[...]]\n"
625                 "               Add a kernel entry to framework compatibility matrix.\n"
626                 "               Ignored for other input format.\n"
627                 "               <version> has format: 3.18\n"
628                 "               <android-base.cfg> is the location of android-base.cfg\n"
629                 "               <android-base-arch.cfg> is the location of an optional\n"
630                 "               arch-specific config fragment, more than one may be specified\n"
631                 "    -l, --hals-only\n"
632                 "               Output has only <hal> entries. Cannot be used with -n.\n"
633                 "    -n, --no-hals\n"
634                 "               Output has no <hal> entries (but all other entries).\n"
635                 "               Cannot be used with -l.\n";
636}
637
638int main(int argc, char **argv) {
639    const struct option longopts[] = {{"kernel", required_argument, NULL, 'k'},
640                                      {"hals-only", no_argument, NULL, 'l'},
641                                      {"no-hals", no_argument, NULL, 'n'},
642                                      {0, 0, 0, 0}};
643
644    std::string outFilePath;
645    ::android::vintf::AssembleVintf assembleVintf;
646    int res;
647    int optind;
648    while ((res = getopt_long(argc, argv, "hi:o:mc:nl", longopts, &optind)) >= 0) {
649        switch (res) {
650            case 'i': {
651                char* inFilePath = strtok(optarg, ":");
652                while (inFilePath != NULL) {
653                    if (!assembleVintf.openInFile(inFilePath)) {
654                        std::cerr << "Failed to open " << optarg << std::endl;
655                        return 1;
656                    }
657                    inFilePath = strtok(NULL, ":");
658                }
659            } break;
660
661            case 'o': {
662                outFilePath = optarg;
663                if (!assembleVintf.openOutFile(optarg)) {
664                    std::cerr << "Failed to open " << optarg << std::endl;
665                    return 1;
666                }
667            } break;
668
669            case 'm': {
670                assembleVintf.setOutputMatrix();
671            } break;
672
673            case 'c': {
674                if (strlen(optarg) != 0) {
675                    if (!assembleVintf.openCheckFile(optarg)) {
676                        std::cerr << "Failed to open " << optarg << std::endl;
677                        return 1;
678                    }
679                } else {
680                    std::cerr << "WARNING: no compatibility check is done on "
681                              << (outFilePath.empty() ? "output" : outFilePath) << std::endl;
682                }
683            } break;
684
685            case 'k': {
686                if (!assembleVintf.addKernel(optarg)) {
687                    std::cerr << "ERROR: Unrecognized --kernel argument." << std::endl;
688                    return 1;
689                }
690            } break;
691
692            case 'l': {
693                if (!assembleVintf.setHalsOnly()) {
694                    return 1;
695                }
696            } break;
697
698            case 'n': {
699                if (!assembleVintf.setNoHals()) {
700                    return 1;
701                }
702            } break;
703
704            case 'h':
705            default: {
706                help();
707                return 1;
708            } break;
709        }
710    }
711
712    bool success = assembleVintf.assemble();
713
714    return success ? 0 : 1;
715}
716