/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "AST.h" #include "Coordinator.h" #include "Interface.h" #include "Scope.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace android; enum class OutputMode { NEEDS_DIR, // -o output option expects a directory NEEDS_FILE, // -o output option expects a file NEEDS_SRC, // for changes inside the source tree itself NOT_NEEDED // does not create files }; enum class GenerationGranularity { PER_PACKAGE, // Files generated for each package PER_FILE, // Files generated for each hal file PER_TYPE, // Files generated for each hal file + each type in HAL files }; // Represents a file that is generated by an -L option for an FQName struct FileGenerator { using ShouldGenerateFunction = std::function; using FileNameForFQName = std::function; using GetFormatter = std::function; using GenerationFunction = std::function; ShouldGenerateFunction mShouldGenerateForFqName; // If generate function applies to this target FileNameForFQName mFileNameForFqName; // Target -> filename GenerationFunction mGenerationFunction; // Function to generate output for file std::string getFileName(const FQName& fqName) const { return mFileNameForFqName ? mFileNameForFqName(fqName) : ""; } status_t getOutputFile(const FQName& fqName, const Coordinator* coordinator, Coordinator::Location location, std::string* file) const { if (!mShouldGenerateForFqName(fqName)) { return OK; } return coordinator->getFilepath(fqName, location, getFileName(fqName), file); } status_t appendOutputFiles(const FQName& fqName, const Coordinator* coordinator, Coordinator::Location location, std::vector* outputFiles) const { if (location == Coordinator::Location::STANDARD_OUT) { return OK; } if (mShouldGenerateForFqName(fqName)) { std::string fileName; status_t err = getOutputFile(fqName, coordinator, location, &fileName); if (err != OK) return err; if (!fileName.empty()) { outputFiles->push_back(fileName); } } return OK; } status_t generate(const FQName& fqName, const Coordinator* coordinator, Coordinator::Location location) const { CHECK(mShouldGenerateForFqName != nullptr); CHECK(mGenerationFunction != nullptr); if (!mShouldGenerateForFqName(fqName)) { return OK; } return mGenerationFunction(fqName, coordinator, [&] { return coordinator->getFormatter(fqName, location, getFileName(fqName)); }); } // Helper methods for filling out this struct static bool generateForTypes(const FQName& fqName) { const auto names = fqName.names(); return names.size() > 0 && names[0] == "types"; } static bool generateForInterfaces(const FQName& fqName) { return !generateForTypes(fqName); } static bool alwaysGenerate(const FQName&) { return true; } }; // Represents a -L option, takes a fqName and generates files struct OutputHandler { using ValidationFunction = std::function; std::string mKey; // -L in Android.bp std::string mDescription; // for display in help menu OutputMode mOutputMode; // how this option interacts with -o Coordinator::Location mLocation; // how to compute location relative to the output directory GenerationGranularity mGenerationGranularity; // what to run generate function on ValidationFunction mValidate; // if a given fqName is allowed for this option std::vector mGenerateFunctions; // run for each target at this granularity const std::string& name() const { return mKey; } const std::string& description() const { return mDescription; } status_t generate(const FQName& fqName, const Coordinator* coordinator) const; status_t validate(const FQName& fqName, const Coordinator* coordinator, const std::string& language) const { return mValidate(fqName, coordinator, language); } status_t writeDepFile(const FQName& fqName, const Coordinator* coordinator) const; private: status_t appendTargets(const FQName& fqName, const Coordinator* coordinator, std::vector* targets) const; status_t appendOutputFiles(const FQName& fqName, const Coordinator* coordinator, std::vector* outputFiles) const; }; // Helper method for GenerationGranularity::PER_TYPE // IFoo -> IFoo, types.hal (containing Bar, Baz) -> types.Bar, types.Baz static status_t appendPerTypeTargets(const FQName& fqName, const Coordinator* coordinator, std::vector* exportedPackageInterfaces) { CHECK(fqName.isFullyQualified()); if (fqName.name() != "types") { exportedPackageInterfaces->push_back(fqName); return OK; } AST* typesAST = coordinator->parse(fqName); if (typesAST == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } std::vector rootTypes = typesAST->getRootScope().getSubTypes(); for (const NamedType* rootType : rootTypes) { if (rootType->isTypeDef()) continue; FQName rootTypeName(fqName.package(), fqName.version(), "types." + rootType->definedName()); exportedPackageInterfaces->push_back(rootTypeName); } return OK; } status_t OutputHandler::appendTargets(const FQName& fqName, const Coordinator* coordinator, std::vector* targets) const { switch (mGenerationGranularity) { case GenerationGranularity::PER_PACKAGE: { targets->push_back(fqName.getPackageAndVersion()); } break; case GenerationGranularity::PER_FILE: { if (fqName.isFullyQualified()) { targets->push_back(fqName); break; } status_t err = coordinator->appendPackageInterfacesToVector(fqName, targets); if (err != OK) return err; } break; case GenerationGranularity::PER_TYPE: { if (fqName.isFullyQualified()) { status_t err = appendPerTypeTargets(fqName, coordinator, targets); if (err != OK) return err; break; } std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector(fqName, &packageInterfaces); if (err != OK) return err; for (const FQName& packageInterface : packageInterfaces) { err = appendPerTypeTargets(packageInterface, coordinator, targets); if (err != OK) return err; } } break; default: CHECK(!"Should be here"); } return OK; } status_t OutputHandler::generate(const FQName& fqName, const Coordinator* coordinator) const { std::vector targets; status_t err = appendTargets(fqName, coordinator, &targets); if (err != OK) return err; for (const FQName& fqName : targets) { for (const FileGenerator& file : mGenerateFunctions) { status_t err = file.generate(fqName, coordinator, mLocation); if (err != OK) return err; } } return OK; } status_t OutputHandler::appendOutputFiles(const FQName& fqName, const Coordinator* coordinator, std::vector* outputFiles) const { std::vector targets; status_t err = appendTargets(fqName, coordinator, &targets); if (err != OK) return err; for (const FQName& fqName : targets) { for (const FileGenerator& file : mGenerateFunctions) { err = file.appendOutputFiles(fqName, coordinator, mLocation, outputFiles); if (err != OK) return err; } } return OK; } status_t OutputHandler::writeDepFile(const FQName& fqName, const Coordinator* coordinator) const { std::vector outputFiles; status_t err = appendOutputFiles(fqName, coordinator, &outputFiles); if (err != OK) return err; // No need for dep files if (outputFiles.empty()) { return OK; } // Depfiles in Android for genrules should be for the 'main file'. Because hidl-gen doesn't have // a main file for most targets, we are just outputting a depfile for one single file only. const std::string forFile = outputFiles[0]; return coordinator->writeDepFile(forFile); } // Use an AST function as a OutputHandler GenerationFunction static FileGenerator::GenerationFunction astGenerationFunction(void (AST::*generate)(Formatter&) const = nullptr) { return [generate](const FQName& fqName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) -> status_t { AST* ast = coordinator->parse(fqName); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } if (generate == nullptr) return OK; // just parsing AST Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } (ast->*generate)(out); return OK; }; } // Common pattern: single file for package or standard out static FileGenerator singleFileGenerator( const std::string& fileName, const FileGenerator::GenerationFunction& generationFunction) { return { FileGenerator::alwaysGenerate, [fileName](const FQName&) { return fileName; }, generationFunction, }; } static status_t generateJavaForPackage(const FQName& fqName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) { AST* ast; std::string limitToType; FQName typeName; // See appendPerTypeTargets. // 'a.b.c@1.0::types.Foo' is used to compile 'Foo' for Java even though in // the rest of the compiler, this type is simply called 'a.b.c@1.0::Foo'. // However, here, we need to disambiguate an interface name and a type in // types.hal in order to figure out what to parse, so this legacy behavior // is kept. if (fqName.name().find("types.") == 0) { limitToType = fqName.name().substr(strlen("types.")); ast = coordinator->parse(fqName.getTypesForPackage()); const auto& names = fqName.names(); CHECK(names.size() == 2 && names[0] == "types") << fqName.string(); typeName = FQName(fqName.package(), fqName.version(), names[1]); } else { ast = coordinator->parse(fqName); typeName = fqName; } if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } Type* type = ast->lookupType(typeName, &ast->getRootScope()); CHECK(type != nullptr) << typeName.string(); if (!type->isJavaCompatible()) { return OK; } Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } ast->generateJava(out, limitToType); return OK; }; static status_t dumpDefinedButUnreferencedTypeNames(const FQName& packageFQName, const Coordinator* coordinator) { std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &packageInterfaces); if (err != OK) return err; std::set unreferencedDefinitions; std::set unreferencedImports; err = coordinator->addUnreferencedTypes(packageInterfaces, &unreferencedDefinitions, &unreferencedImports); if (err != OK) return err; for (const auto& fqName : unreferencedDefinitions) { std::cerr << "VERBOSE: DEFINED-BUT-NOT-REFERENCED " << fqName.string() << std::endl; } for (const auto& fqName : unreferencedImports) { std::cerr << "VERBOSE: IMPORTED-BUT-NOT-REFERENCED " << fqName.string() << std::endl; } return OK; } static std::string makeLibraryName(const FQName &packageFQName) { return packageFQName.string(); } static status_t isPackageJavaCompatible(const FQName& packageFQName, const Coordinator* coordinator, bool* compatible) { std::vector todo; status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &todo); if (err != OK) { return err; } std::set seen; for (const auto &iface : todo) { seen.insert(iface); } // Form the transitive closure of all imported interfaces (and types.hal-s) // If any one of them is not java compatible, this package isn't either. while (!todo.empty()) { const FQName fqName = todo.back(); todo.pop_back(); AST *ast = coordinator->parse(fqName); if (ast == nullptr) { return UNKNOWN_ERROR; } if (!ast->isJavaCompatible()) { *compatible = false; return OK; } std::set importedPackages; ast->getImportedPackages(&importedPackages); for (const auto &package : importedPackages) { std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector( package, &packageInterfaces); if (err != OK) { return err; } for (const auto &iface : packageInterfaces) { if (seen.find(iface) != seen.end()) { continue; } todo.push_back(iface); seen.insert(iface); } } } *compatible = true; return OK; } static bool packageNeedsJavaCode( const std::vector &packageInterfaces, AST *typesAST) { if (packageInterfaces.size() == 0) { return false; } // If there is more than just a types.hal file to this package we'll // definitely need to generate Java code. if (packageInterfaces.size() > 1 || packageInterfaces[0].name() != "types") { return true; } CHECK(typesAST != nullptr); // We'll have to generate Java code if types.hal contains any non-typedef // type declarations. std::vector subTypes = typesAST->getRootScope().getSubTypes(); for (const auto &subType : subTypes) { if (!subType->isTypeDef()) { return true; } } return false; } bool validateIsPackage(const FQName& fqName, const Coordinator*, const std::string& /* language */) { if (fqName.package().empty()) { fprintf(stderr, "ERROR: Expecting package name\n"); return false; } if (fqName.version().empty()) { fprintf(stderr, "ERROR: Expecting package version\n"); return false; } if (!fqName.name().empty()) { fprintf(stderr, "ERROR: Expecting only package name and version.\n"); return false; } return true; } bool isHidlTransportPackage(const FQName& fqName) { return fqName.package() == gIBaseFqName.package() || fqName.package() == gIManagerFqName.package(); } bool isSystemProcessSupportedPackage(const FQName& fqName) { // Technically, so is hidl IBase + IServiceManager, but // these are part of libhidlbase. return fqName.inPackage("android.hardware.graphics.common") || fqName.inPackage("android.hardware.graphics.mapper") || fqName.string() == "android.hardware.renderscript@1.0" || fqName.string() == "android.hidl.memory.token@1.0" || fqName.string() == "android.hidl.memory@1.0" || fqName.string() == "android.hidl.safe_union@1.0"; } bool isCoreAndroidPackage(const FQName& package) { return package.inPackage("android.hidl") || package.inPackage("android.system") || package.inPackage("android.frameworks") || package.inPackage("android.hardware"); } // Keep the list of libs which are used by VNDK core libs and should be part of // VNDK libs static const std::vector vndkLibs = { "android.hardware.audio.common@2.0", "android.hardware.configstore@1.0", "android.hardware.configstore@1.1", "android.hardware.graphics.allocator@2.0", "android.hardware.graphics.allocator@3.0", "android.hardware.graphics.allocator@4.0", "android.hardware.graphics.bufferqueue@1.0", "android.hardware.graphics.bufferqueue@2.0", "android.hardware.media.bufferpool@2.0", "android.hardware.media.omx@1.0", "android.hardware.media@1.0", "android.hardware.memtrack@1.0", "android.hardware.soundtrigger@2.0", "android.hidl.token@1.0", "android.system.suspend@1.0", }; bool isVndkCoreLib(const FQName& fqName) { return std::find(vndkLibs.begin(), vndkLibs.end(), fqName.string()) != vndkLibs.end(); } status_t hasVariantFile(const FQName& fqName, const Coordinator* coordinator, const std::string& fileName, bool* isVariant) { const auto fileExists = [](const std::string& file) { struct stat buf; return stat(file.c_str(), &buf) == 0; }; std::string path; status_t err = coordinator->getFilepath(fqName, Coordinator::Location::PACKAGE_ROOT, fileName, &path); if (err != OK) return err; const bool exists = fileExists(path); if (exists) { coordinator->onFileAccess(path, "r"); } *isVariant = exists; return OK; } status_t isSystemExtPackage(const FQName& fqName, const Coordinator* coordinator, bool* isSystemExt) { return hasVariantFile(fqName, coordinator, ".hidl_for_system_ext", isSystemExt); } status_t isOdmPackage(const FQName& fqName, const Coordinator* coordinator, bool* isOdm) { return hasVariantFile(fqName, coordinator, ".hidl_for_odm", isOdm); } static status_t generateAdapterMainSource(const FQName& packageFQName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) { std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &packageInterfaces); if (err != OK) { return err; } // b/146223994: parse all interfaces // - causes files to get read (filling out dep file) // - avoid creating successful output for broken files for (const FQName& fqName : packageInterfaces) { AST* ast = coordinator->parse(fqName); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } } Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } out << "#include \n"; for (auto &interface : packageInterfaces) { if (interface.name() == "types") { continue; } AST::generateCppPackageInclude(out, interface, interface.getInterfaceAdapterName()); } out << "int main(int argc, char** argv) "; out.block([&] { out << "return ::android::hardware::adapterMain<\n"; out.indent(); for (auto &interface : packageInterfaces) { if (interface.name() == "types") { continue; } out << interface.getInterfaceAdapterFqName().cppName(); if (&interface != &packageInterfaces.back()) { out << ",\n"; } } out << ">(\"" << packageFQName.string() << "\", argc, argv);\n"; out.unindent(); }).endl(); return OK; } static status_t generateAndroidBpForPackage(const FQName& packageFQName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) { CHECK(!packageFQName.isFullyQualified() && packageFQName.name().empty()); std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &packageInterfaces); if (err != OK) { return err; } std::set importedPackagesHierarchy; std::vector exportedTypes; AST* typesAST = nullptr; for (const auto& fqName : packageInterfaces) { AST* ast = coordinator->parse(fqName); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } if (fqName.name() == "types") { typesAST = ast; } ast->getImportedPackagesHierarchy(&importedPackagesHierarchy); ast->appendToExportedTypesVector(&exportedTypes); } bool needsJavaCode = packageNeedsJavaCode(packageInterfaces, typesAST); bool genJavaConstants = needsJavaCode && !exportedTypes.empty(); bool isJavaCompatible; err = isPackageJavaCompatible(packageFQName, coordinator, &isJavaCompatible); if (err != OK) return err; bool genJavaLibrary = needsJavaCode && isJavaCompatible; bool isCoreAndroid = isCoreAndroidPackage(packageFQName); bool isVndkCore = isVndkCoreLib(packageFQName); bool isVndkSp = isSystemProcessSupportedPackage(packageFQName); bool isSystemExtHidl; err = isSystemExtPackage(packageFQName, coordinator, &isSystemExtHidl); if (err != OK) return err; bool isSystemExt = isSystemExtHidl || !isCoreAndroid; bool isForOdm; err = isOdmPackage(packageFQName, coordinator, &isForOdm); if (err != OK) return err; std::string packageRoot; err = coordinator->getPackageRoot(packageFQName, &packageRoot); if (err != OK) return err; Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } out << "// This file is autogenerated by hidl-gen -Landroidbp.\n\n"; out << "hidl_interface "; out.block([&] { out << "name: \"" << makeLibraryName(packageFQName) << "\",\n"; if (!coordinator->getOwner().empty()) { out << "owner: \"" << coordinator->getOwner() << "\",\n"; } out << "root: \"" << packageRoot << "\",\n"; if (isVndkCore || isVndkSp) { out << "vndk: "; out.block([&]() { out << "enabled: true,\n"; if (isVndkSp) { out << "support_system_process: true,\n"; } }) << ",\n"; } if (isSystemExt) { out << "system_ext_specific: true,\n"; } if (isForOdm) { out << "odm_available: true,\n"; } (out << "srcs: [\n").indent([&] { for (const auto& fqName : packageInterfaces) { out << "\"" << fqName.name() << ".hal\",\n"; } }) << "],\n"; if (!importedPackagesHierarchy.empty()) { (out << "interfaces: [\n").indent([&] { for (const auto& fqName : importedPackagesHierarchy) { out << "\"" << fqName.string() << "\",\n"; } }) << "],\n"; } // Explicity call this out for developers. out << "gen_java: " << (genJavaLibrary ? "true" : "false") << ",\n"; if (genJavaConstants) { out << "gen_java_constants: true,\n"; } }).endl(); return OK; } static status_t generateAndroidBpImplForPackage(const FQName& packageFQName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) { const std::string libraryName = makeLibraryName(packageFQName) + "-impl"; std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector(packageFQName, &packageInterfaces); if (err != OK) { return err; } std::set importedPackages; for (const auto &fqName : packageInterfaces) { AST *ast = coordinator->parse(fqName); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } ast->getImportedPackages(&importedPackages); } Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } out << "// FIXME: your file license if you have one\n\n"; out << "cc_library_shared {\n"; out.indent([&] { out << "// FIXME: this should only be -impl for a passthrough hal.\n" << "// In most cases, to convert this to a binderized implementation, you should:\n" << "// - change '-impl' to '-service' here and make it a cc_binary instead of a\n" << "// cc_library_shared.\n" << "// - add a *.rc file for this module.\n" << "// - delete HIDL_FETCH_I* functions.\n" << "// - call configureRpcThreadpool and registerAsService on the instance.\n" << "// You may also want to append '-impl/-service' with a specific identifier like\n" << "// '-vendor' or '-' etc to distinguish it.\n"; out << "name: \"" << libraryName << "\",\n"; if (!coordinator->getOwner().empty()) { out << "owner: \"" << coordinator->getOwner() << "\",\n"; } out << "relative_install_path: \"hw\",\n"; if (coordinator->getOwner().empty()) { out << "// FIXME: this should be 'vendor: true' for modules that will eventually be\n" "// on AOSP.\n"; } out << "proprietary: true,\n"; out << "srcs: [\n"; out.indent([&] { for (const auto &fqName : packageInterfaces) { if (fqName.name() == "types") { continue; } out << "\"" << fqName.getInterfaceBaseName() << ".cpp\",\n"; } }); out << "],\n" << "shared_libs: [\n"; out.indent([&] { out << "\"libhidlbase\",\n" << "\"libutils\",\n" << "\"" << makeLibraryName(packageFQName) << "\",\n"; for (const auto &importedPackage : importedPackages) { if (isHidlTransportPackage(importedPackage)) { continue; } out << "\"" << makeLibraryName(importedPackage) << "\",\n"; } }); out << "],\n"; }); out << "}\n"; return OK; } bool validateForSource(const FQName& fqName, const Coordinator* coordinator, const std::string& language) { if (fqName.package().empty()) { fprintf(stderr, "ERROR: Expecting package name\n"); return false; } if (fqName.version().empty()) { fprintf(stderr, "ERROR: Expecting package version\n"); return false; } const std::string &name = fqName.name(); if (!name.empty()) { if (name.find('.') == std::string::npos) { return true; } if (language != "java" || name.find("types.") != 0) { // When generating java sources for "types.hal", output can be // constrained to just one of the top-level types declared // by using the extended syntax // android.hardware.Foo@1.0::types.TopLevelTypeName. // In all other cases (different language, not 'types') the dot // notation in the name is illegal in this context. return false; } return true; } if (language == "java") { bool isJavaCompatible; status_t err = isPackageJavaCompatible(fqName, coordinator, &isJavaCompatible); if (err != OK) return false; if (!isJavaCompatible) { fprintf(stderr, "ERROR: %s is not Java compatible. The Java backend does NOT support union " "types. In addition, vectors of arrays are limited to at most one-dimensional " "arrays and vectors of {vectors,interfaces,memory} are not supported.\n", fqName.string().c_str()); return false; } } return true; } bool validateForFormat(const FQName& fqName, const Coordinator* coordinator, const std::string& format) { CHECK_EQ(format, "format"); if (!validateForSource(fqName, coordinator, format)) return false; std::vector packageInterfaces; if (fqName.isFullyQualified()) { packageInterfaces.push_back(fqName); } else { status_t err = coordinator->appendPackageInterfacesToVector(fqName, &packageInterfaces); if (err != OK) return err; } bool destroysInformation = false; for (const auto& fqName : packageInterfaces) { AST* ast = coordinator->parse(fqName); if (ast->getUnhandledComments().size() > 0) { destroysInformation = true; for (const auto& i : ast->getUnhandledComments()) { std::cerr << "Unrecognized comment at " << i->location() << std::endl; Formatter err(stderr); err.indent(); i->emit(err); err.unindent(); err.endl(); } } } if (destroysInformation) { std::cerr << "\nhidl-gen does not support comments at these locations, and formatting " "the file would destroy them. HIDL doc comments need to be multiline comments " "(/*...*/) before specific elements. This will also cause them to be emitted " "in output files in the correct locations so that IDE users or people " "inspecting generated source can see them in the correct location. Formatting " "the file would destroy these comments.\n"; return false; } return true; } FileGenerator::GenerationFunction generateExportHeaderForPackage(bool forJava) { return [forJava](const FQName& packageFQName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) -> status_t { CHECK(!packageFQName.package().empty() && !packageFQName.version().empty() && packageFQName.name().empty()); std::vector packageInterfaces; status_t err = coordinator->appendPackageInterfacesToVector( packageFQName, &packageInterfaces); if (err != OK) { return err; } std::vector exportedTypes; for (const auto &fqName : packageInterfaces) { AST *ast = coordinator->parse(fqName); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } ast->appendToExportedTypesVector(&exportedTypes); } if (exportedTypes.empty()) { fprintf(stderr, "ERROR: -Ljava-constants (Android.bp: gen_java_constants) requested for %s, " "but no types declare @export.", packageFQName.string().c_str()); return UNKNOWN_ERROR; } Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } std::string packagePath; err = coordinator->getPackagePath(packageFQName, false /* relative */, false /* sanitized */, &packagePath); if (err != OK) return err; out << "// This file is autogenerated by hidl-gen. Do not edit manually.\n" << "// Source: " << packageFQName.string() << "\n" << "// Location: " << packagePath << "\n\n"; std::string guard; if (forJava) { out << "package " << packageFQName.javaPackage() << ";\n\n"; out << "public class Constants {\n"; out.indent(); } else { guard = "HIDL_GENERATED_"; guard += StringHelper::Uppercase(packageFQName.tokenName()); guard += "_"; guard += "EXPORTED_CONSTANTS_H_"; out << "#ifndef " << guard << "\n#define " << guard << "\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n"; } for (const auto &type : exportedTypes) { type->emitExportedHeader(out, forJava); } if (forJava) { out.unindent(); out << "}\n"; } else { out << "#ifdef __cplusplus\n}\n#endif\n\n#endif // " << guard << "\n"; } return OK; }; } static status_t generateHashOutput(const FQName& fqName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) { CHECK(fqName.isFullyQualified()); AST* ast = coordinator->parse(fqName, {} /* parsed */, Coordinator::Enforce::NO_HASH /* enforcement */); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } out << Hash::getHash(ast->getFilename()).hexString() << " " << fqName.string() << "\n"; return OK; } static status_t generateFunctionCount(const FQName& fqName, const Coordinator* coordinator, const FileGenerator::GetFormatter& getFormatter) { CHECK(fqName.isFullyQualified()); AST* ast = coordinator->parse(fqName, {} /* parsed */, Coordinator::Enforce::NO_HASH /* enforcement */); if (ast == nullptr) { fprintf(stderr, "ERROR: Could not parse %s. Aborting.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } const Interface* interface = ast->getInterface(); if (interface == nullptr) { fprintf(stderr, "ERROR: Function count requires interface: %s.\n", fqName.string().c_str()); return UNKNOWN_ERROR; } Formatter out = getFormatter(); if (!out.isValid()) { return UNKNOWN_ERROR; } // This is wrong for android.hidl.base@1.0::IBase, but in that case, it doesn't matter. // This is just the number of APIs that are added. out << fqName.string() << " " << interface->userDefinedMethods().size() << "\n"; return OK; } template std::vector operator+(const std::vector& lhs, const std::vector& rhs) { std::vector ret; ret.reserve(lhs.size() + rhs.size()); ret.insert(ret.begin(), lhs.begin(), lhs.end()); ret.insert(ret.end(), rhs.begin(), rhs.end()); return ret; } // clang-format off static const std::vector kCppHeaderFormats = { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.name() + ".h"; }, astGenerationFunction(&AST::generateInterfaceHeader), }, { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.isInterfaceName() ? fqName.getInterfaceHwName() + ".h" : "hwtypes.h"; }, astGenerationFunction(&AST::generateHwBinderHeader), }, { FileGenerator::generateForInterfaces, [](const FQName& fqName) { return fqName.getInterfaceStubName() + ".h"; }, astGenerationFunction(&AST::generateStubHeader), }, { FileGenerator::generateForInterfaces, [](const FQName& fqName) { return fqName.getInterfaceProxyName() + ".h"; }, astGenerationFunction(&AST::generateProxyHeader), }, { FileGenerator::generateForInterfaces, [](const FQName& fqName) { return fqName.getInterfacePassthroughName() + ".h"; }, astGenerationFunction(&AST::generatePassthroughHeader), }, }; static const std::vector kCppSourceFormats = { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.isInterfaceName() ? fqName.getInterfaceBaseName() + "All.cpp" : "types.cpp"; }, astGenerationFunction(&AST::generateCppSource), }, }; static const std::vector kCppImplHeaderFormats = { { FileGenerator::generateForInterfaces, [](const FQName& fqName) { return fqName.getInterfaceBaseName() + ".h"; }, astGenerationFunction(&AST::generateCppImplHeader), }, }; static const std::vector kCppImplSourceFormats = { { FileGenerator::generateForInterfaces, [](const FQName& fqName) { return fqName.getInterfaceBaseName() + ".cpp"; }, astGenerationFunction(&AST::generateCppImplSource), }, }; static const std::vector kCppAdapterHeaderFormats = { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.isInterfaceName() ? fqName.getInterfaceAdapterName() + ".h" : "Atypes.h"; }, astGenerationFunction(&AST::generateCppAdapterHeader), }, }; static const std::vector kCppAdapterSourceFormats = { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.isInterfaceName() ? fqName.getInterfaceAdapterName() + ".cpp" : "Atypes.cpp"; }, astGenerationFunction(&AST::generateCppAdapterSource), }, }; static const std::vector kFormats = { { "check", "Parses the interface to see if valid but doesn't write any files.", OutputMode::NOT_NEEDED, Coordinator::Location::STANDARD_OUT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::alwaysGenerate, nullptr /* filename for fqname */, astGenerationFunction(), }, }, }, { "c++", "(internal) (deprecated) Generates C++ interface files for talking to HIDL interfaces.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, kCppHeaderFormats + kCppSourceFormats, }, { "c++-headers", "(internal) Generates C++ headers for interface files for talking to HIDL interfaces.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, kCppHeaderFormats, }, { "c++-sources", "(internal) Generates C++ sources for interface files for talking to HIDL interfaces.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, kCppSourceFormats, }, { "export-header", "Generates a header file from @export enumerations to help maintain legacy code.", OutputMode::NEEDS_FILE, Coordinator::Location::DIRECT, GenerationGranularity::PER_PACKAGE, validateIsPackage, {singleFileGenerator("", generateExportHeaderForPackage(false /* forJava */))} }, { "c++-impl", "Generates boilerplate implementation of a hidl interface in C++ (for convenience).", OutputMode::NEEDS_DIR, Coordinator::Location::DIRECT, GenerationGranularity::PER_FILE, validateForSource, kCppImplHeaderFormats + kCppImplSourceFormats, }, { "c++-impl-headers", "c++-impl but headers only.", OutputMode::NEEDS_DIR, Coordinator::Location::DIRECT, GenerationGranularity::PER_FILE, validateForSource, kCppImplHeaderFormats, }, { "c++-impl-sources", "c++-impl but sources only.", OutputMode::NEEDS_DIR, Coordinator::Location::DIRECT, GenerationGranularity::PER_FILE, validateForSource, kCppImplSourceFormats, }, { "c++-adapter", "Takes a x.(y+n) interface and mocks an x.y interface.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, kCppAdapterHeaderFormats + kCppAdapterSourceFormats, }, { "c++-adapter-headers", "c++-adapter but helper headers only.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, kCppAdapterHeaderFormats, }, { "c++-adapter-sources", "c++-adapter but helper sources only.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, kCppAdapterSourceFormats, }, { "c++-adapter-main", "c++-adapter but the adapter binary source only.", OutputMode::NEEDS_DIR, Coordinator::Location::DIRECT, GenerationGranularity::PER_PACKAGE, validateIsPackage, {singleFileGenerator("main.cpp", generateAdapterMainSource)}, }, { "java", "(internal) Generates Java library for talking to HIDL interfaces in Java.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_SANITIZED, GenerationGranularity::PER_TYPE, validateForSource, { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return StringHelper::LTrim(fqName.name(), "types.") + ".java"; }, generateJavaForPackage, }, } }, { "java-impl", "Generates boilerplate implementation of a hidl interface in Java (for convenience).", OutputMode::NEEDS_DIR, Coordinator::Location::DIRECT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::generateForInterfaces, [](const FQName& fqName) { return fqName.getInterfaceBaseName() + ".java"; }, astGenerationFunction(&AST::generateJavaImpl), }, } }, { "java-constants", "(internal) Like export-header but for Java (always created by -Lmakefile if @export exists).", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_SANITIZED, GenerationGranularity::PER_PACKAGE, validateIsPackage, {singleFileGenerator("Constants.java", generateExportHeaderForPackage(true /* forJava */))} }, { "vts", "(internal) Generates vts proto files for use in vtsd.", OutputMode::NEEDS_DIR, Coordinator::Location::GEN_OUTPUT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.isInterfaceName() ? fqName.getInterfaceBaseName() + ".vts" : "types.vts"; }, astGenerationFunction(&AST::generateVts), }, } }, { "makefile", "(removed) Used to generate makefiles for -Ljava and -Ljava-constants.", OutputMode::NEEDS_SRC, Coordinator::Location::PACKAGE_ROOT, GenerationGranularity::PER_PACKAGE, [](const FQName &, const Coordinator*, const std::string &) { fprintf(stderr, "ERROR: makefile output is not supported. Use -Landroidbp for all build file generation.\n"); return false; }, {}, }, { "androidbp", "(internal) Generates Soong bp files for -Lc++-headers, -Lc++-sources, -Ljava, -Ljava-constants, and -Lc++-adapter.", OutputMode::NEEDS_SRC, Coordinator::Location::PACKAGE_ROOT, GenerationGranularity::PER_PACKAGE, validateIsPackage, {singleFileGenerator("Android.bp", generateAndroidBpForPackage)}, }, { "androidbp-impl", "Generates boilerplate bp files for implementation created with -Lc++-impl.", OutputMode::NEEDS_DIR, Coordinator::Location::DIRECT, GenerationGranularity::PER_PACKAGE, validateIsPackage, {singleFileGenerator("Android.bp", generateAndroidBpImplForPackage)}, }, { "hash", "Prints hashes of interface in `current.txt` format to standard out.", OutputMode::NOT_NEEDED, Coordinator::Location::STANDARD_OUT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::alwaysGenerate, nullptr /* file name for fqName */, generateHashOutput, }, } }, { "function-count", "Prints the total number of functions added by the package or interface.", OutputMode::NOT_NEEDED, Coordinator::Location::STANDARD_OUT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::generateForInterfaces, nullptr /* file name for fqName */, generateFunctionCount, }, } }, { "dependencies", "Prints all depended types.", OutputMode::NOT_NEEDED, Coordinator::Location::STANDARD_OUT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::alwaysGenerate, nullptr /* file name for fqName */, astGenerationFunction(&AST::generateDependencies), }, }, }, { "inheritance-hierarchy", "Prints the hierarchy of inherited types as a JSON object.", OutputMode::NOT_NEEDED, Coordinator::Location::STANDARD_OUT, GenerationGranularity::PER_FILE, validateForSource, { { FileGenerator::alwaysGenerate, nullptr /* file name for fqName */, astGenerationFunction(&AST::generateInheritanceHierarchy), }, }, }, { "format", "Reformats the .hal files", OutputMode::NEEDS_SRC, Coordinator::Location::PACKAGE_ROOT, GenerationGranularity::PER_FILE, validateForFormat, { { FileGenerator::alwaysGenerate, [](const FQName& fqName) { return fqName.name() + ".hal"; }, astGenerationFunction(&AST::generateFormattedHidl), }, } }, }; // clang-format on static void usage(const char* me) { Formatter out(stderr); out << "Usage: " << me << " -o -L [-O ] "; Coordinator::emitOptionsUsageString(out); out << " FQNAME...\n\n"; out << "Process FQNAME, PACKAGE(.SUBPACKAGE)*@[0-9]+.[0-9]+(::TYPE)?, to create output.\n\n"; out.indent(); out.indent(); out << "-h: Prints this menu.\n"; out << "-L : The following options are available:\n"; out.indent([&] { for (auto& e : kFormats) { std::stringstream sstream; sstream.fill(' '); sstream.width(16); sstream << std::left << e.name(); out << sstream.str() << ": " << e.description() << "\n"; } }); out << "-O : The owner of the module for -Landroidbp(-impl)?.\n"; out << "-o : Location to output files.\n"; Coordinator::emitOptionsDetailString(out); out.unindent(); out.unindent(); } // hidl is intentionally leaky. Turn off LeakSanitizer by default. extern "C" const char *__asan_default_options() { return "detect_leaks=0"; } int main(int argc, char **argv) { const char *me = argv[0]; if (argc == 1) { usage(me); exit(1); } const OutputHandler* outputFormat = nullptr; Coordinator coordinator; std::string outputPath; coordinator.parseOptions(argc, argv, "ho:O:L:", [&](int res, char* arg) { switch (res) { case 'o': { if (!outputPath.empty()) { fprintf(stderr, "ERROR: -o can only be specified once.\n"); exit(1); } outputPath = arg; break; } case 'O': { if (!coordinator.getOwner().empty()) { fprintf(stderr, "ERROR: -O can only be specified once.\n"); exit(1); } coordinator.setOwner(arg); break; } case 'L': { if (outputFormat != nullptr) { fprintf(stderr, "ERROR: only one -L option allowed. \"%s\" already specified.\n", outputFormat->name().c_str()); exit(1); } for (auto& e : kFormats) { if (e.name() == arg) { outputFormat = &e; break; } } if (outputFormat == nullptr) { fprintf(stderr, "ERROR: unrecognized -L option: \"%s\".\n", arg); exit(1); } break; } case '?': case 'h': default: { usage(me); exit(1); break; } } }); if (outputFormat == nullptr) { fprintf(stderr, "ERROR: no -L option provided.\n"); exit(1); } argc -= optind; argv += optind; if (argc == 0) { fprintf(stderr, "ERROR: no fqname specified.\n"); usage(me); exit(1); } // Valid options are now in argv[0] .. argv[argc - 1]. switch (outputFormat->mOutputMode) { case OutputMode::NEEDS_DIR: case OutputMode::NEEDS_FILE: { if (outputPath.empty()) { usage(me); exit(1); } if (outputFormat->mOutputMode == OutputMode::NEEDS_DIR) { if (outputPath.back() != '/') { outputPath += "/"; } } break; } case OutputMode::NEEDS_SRC: { if (outputPath.empty()) { outputPath = coordinator.getRootPath(); } if (outputPath.back() != '/') { outputPath += "/"; } break; } default: outputPath.clear(); // Unused. break; } coordinator.setOutputPath(outputPath); for (int i = 0; i < argc; ++i) { const char* arg = argv[i]; FQName fqName; if (!FQName::parse(arg, &fqName)) { fprintf(stderr, "ERROR: Invalid fully-qualified name as argument: %s.\n", arg); exit(1); } if (coordinator.getPackageInterfaceFiles(fqName, nullptr /*fileNames*/) != OK) { fprintf(stderr, "ERROR: Could not get sources for %s.\n", arg); exit(1); } // Dump extra verbose output if (coordinator.isVerbose()) { status_t err = dumpDefinedButUnreferencedTypeNames(fqName.getPackageAndVersion(), &coordinator); if (err != OK) return err; } if (!outputFormat->validate(fqName, &coordinator, outputFormat->name())) { fprintf(stderr, "ERROR: Validation failed.\n"); exit(1); } status_t err = outputFormat->generate(fqName, &coordinator); if (err != OK) exit(1); err = outputFormat->writeDepFile(fqName, &coordinator); if (err != OK) exit(1); } return 0; }