Yet another backend for hidl-gen, this one generates a C-compatible header file

containing those enum types annotated in the package like so:

@export
enum Foo {
    ...
};

Optionally, the name to be used for the type declaration in the header file
can be different from that used in the .hal interface description by specifying

@export(name="foo_t")
enum Foo {
    ...
};

Finally, overriding the name to be empty, i.e.

@export(name="")
enum Foo {
    ...
};

will cause the generator to emit an anonymous enum.

Bug: 31800672
Change-Id: Idffb2c1700af1c7fd312941d80c3373add8ae558
Test: make
diff --git a/AST.cpp b/AST.cpp
index 555f084..a1fab37 100644
--- a/AST.cpp
+++ b/AST.cpp
@@ -393,4 +393,9 @@
     return iface->isJavaCompatible();
 }
 
+void AST::appendToExportedTypesVector(
+        std::vector<const Type *> *exportedTypes) const {
+    mRootScope->appendToExportedTypesVector(exportedTypes);
+}
+
 }  // namespace android;
diff --git a/AST.h b/AST.h
index 88f2a43..33c41e1 100644
--- a/AST.h
+++ b/AST.h
@@ -99,6 +99,9 @@
         return mImportedNames;
     }
 
+    void appendToExportedTypesVector(
+            std::vector<const Type *> *exportedTypes) const;
+
 private:
     Coordinator *mCoordinator;
     std::string mPath;
diff --git a/Annotation.cpp b/Annotation.cpp
index 62a7563..d3b4d01 100644
--- a/Annotation.cpp
+++ b/Annotation.cpp
@@ -56,7 +56,7 @@
     return *mParams;
 }
 
-const AnnotationParam *Annotation::getParam(const std::string &name) {
+const AnnotationParam *Annotation::getParam(const std::string &name) const {
     for (auto *i: *mParams) {
         if (i->getName() == name) {
             return i;
diff --git a/Annotation.h b/Annotation.h
index f2826a0..063b8e4 100644
--- a/Annotation.h
+++ b/Annotation.h
@@ -49,7 +49,7 @@
 
     std::string name() const;
     const AnnotationParamVector &params() const;
-    const AnnotationParam *getParam(const std::string &name);
+    const AnnotationParam *getParam(const std::string &name) const;
 
     void dump(Formatter &out) const;
 
diff --git a/EnumType.cpp b/EnumType.cpp
index efc0f08..5a8706b 100644
--- a/EnumType.cpp
+++ b/EnumType.cpp
@@ -16,6 +16,7 @@
 
 #include "EnumType.h"
 
+#include "Annotation.h"
 #include "ScalarType.h"
 
 #include <inttypes.h>
@@ -286,6 +287,86 @@
     mStorageType->getAlignmentAndSize(align, size);
 }
 
+const Annotation *EnumType::findExportAnnotation() const {
+    for (const auto &annotation : annotations()) {
+        if (annotation->name() == "export") {
+            return annotation;
+        }
+    }
+
+    return nullptr;
+}
+
+void EnumType::appendToExportedTypesVector(
+        std::vector<const Type *> *exportedTypes) const {
+    if (findExportAnnotation() != nullptr) {
+        exportedTypes->push_back(this);
+    }
+}
+
+status_t EnumType::emitExportedHeader(Formatter &out) const {
+    const Annotation *annotation = findExportAnnotation();
+    CHECK(annotation != nullptr);
+
+    std::string name = localName();
+
+    const AnnotationParam *nameParam = annotation->getParam("name");
+    if (nameParam != nullptr) {
+        CHECK_EQ(nameParam->getValues()->size(), 1u);
+
+        std::string quotedString = nameParam->getValues()->at(0);
+        name = quotedString.substr(1, quotedString.size() - 2);
+    }
+
+    const ScalarType *scalarType = mStorageType->resolveToScalarType();
+    CHECK(scalarType != NULL);
+
+    std::string extra;
+
+    if (!name.empty()) {
+        out << "typedef ";
+    }
+
+    out << "enum {\n";
+
+    out.indent();
+
+    std::vector<const EnumType *> chain;
+    getTypeChain(&chain);
+
+    for (auto it = chain.rbegin(); it != chain.rend(); ++it) {
+        const auto &type = *it;
+
+        for (const auto &entry : type->values()) {
+            out << entry->name();
+
+            std::string value = entry->cppValue(scalarType->getKind());
+            CHECK(!value.empty()); // use autofilled values for c++.
+            out << " = " << value;
+
+            out << ",";
+
+            std::string comment = entry->comment();
+            if (!comment.empty() && comment != value) {
+                out << " // " << comment;
+            }
+
+            out << "\n";
+        }
+    }
+
+    out.unindent();
+    out << "}";
+
+    if (!name.empty()) {
+        out << " " << name;
+    }
+
+    out << ";\n\n";
+
+    return OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 EnumValue::EnumValue(const char *name, ConstantExpression *value)
diff --git a/EnumType.h b/EnumType.h
index 8090e92..1e6a334 100644
--- a/EnumType.h
+++ b/EnumType.h
@@ -79,8 +79,15 @@
 
     void getAlignmentAndSize(size_t *align, size_t *size) const override;
 
+    void appendToExportedTypesVector(
+            std::vector<const Type *> *exportedTypes) const override;
+
+    status_t emitExportedHeader(Formatter &out) const override;
+
 private:
     void getTypeChain(std::vector<const EnumType *> *out) const;
+    const Annotation *findExportAnnotation() const;
+
     std::vector<EnumValue *> mValues;
     Type *mStorageType;
 
diff --git a/Scope.cpp b/Scope.cpp
index 92b9858..452a41f 100644
--- a/Scope.cpp
+++ b/Scope.cpp
@@ -191,6 +191,13 @@
     return true;
 }
 
+void Scope::appendToExportedTypesVector(
+        std::vector<const Type *> *exportedTypes) const {
+    for (const NamedType *type : mTypes) {
+        type->appendToExportedTypesVector(exportedTypes);
+    }
+}
+
 LocalIdentifier::LocalIdentifier(){}
 LocalIdentifier::~LocalIdentifier(){}
 
diff --git a/Scope.h b/Scope.h
index 6365bb4..08683dd 100644
--- a/Scope.h
+++ b/Scope.h
@@ -65,6 +65,9 @@
 
     bool isJavaCompatible() const override;
 
+    void appendToExportedTypesVector(
+            std::vector<const Type *> *exportedTypes) const override;
+
 private:
     std::vector<NamedType *> mTypes;
     std::map<std::string, size_t> mTypeIndexByName;
diff --git a/Type.cpp b/Type.cpp
index 54f5956..5c65a3e 100644
--- a/Type.cpp
+++ b/Type.cpp
@@ -390,6 +390,14 @@
     CHECK(!"Should not be here");
 }
 
+void Type::appendToExportedTypesVector(
+        std::vector<const Type *> * /* exportedTypes */) const {
+}
+
+status_t Type::emitExportedHeader(Formatter & /* out */) const {
+    return OK;
+}
+
 ////////////////////////////////////////
 
 TemplatedType::TemplatedType() : mElementType(nullptr) {
diff --git a/Type.h b/Type.h
index b693b5f..da97f80 100644
--- a/Type.h
+++ b/Type.h
@@ -188,6 +188,11 @@
     void setAnnotations(std::vector<Annotation *> *annotations);
     const std::vector<Annotation *> &annotations() const;
 
+    virtual void appendToExportedTypesVector(
+            std::vector<const Type *> *exportedTypes) const;
+
+    virtual status_t emitExportedHeader(Formatter &out) const;
+
 protected:
     void handleError(Formatter &out, ErrorMode mode) const;
     void handleError2(Formatter &out, ErrorMode mode) const;
diff --git a/main.cpp b/main.cpp
index 91fc81c..b81f004 100644
--- a/main.cpp
+++ b/main.cpp
@@ -31,7 +31,12 @@
 
 struct OutputHandler {
     std::string mKey;
-    bool mNeedsOutputDir;
+    enum OutputMode {
+        NEEDS_DIR,
+        NEEDS_FILE,
+        NOT_NEEDED
+    } mOutputMode;
+
     enum ValRes {
         FAILED,
         PASS_PACKAGE,
@@ -678,9 +683,104 @@
     return OutputHandler::PASS_PACKAGE;
 }
 
+OutputHandler::ValRes validateForExportHeader(
+        const FQName &fqName, const std::string & /* language */) {
+    if (fqName.package().empty()) {
+        fprintf(stderr, "ERROR: Expecting package name\n");
+        return OutputHandler::FAILED;
+    }
+
+    if (fqName.version().empty()) {
+        fprintf(stderr, "ERROR: Expecting package version\n");
+        return OutputHandler::FAILED;
+    }
+
+    if (!fqName.name().empty()) {
+        fprintf(stderr,
+                "ERROR: Expecting only package name and version.\n");
+        return OutputHandler::FAILED;
+    }
+
+    return OutputHandler::PASS_PACKAGE;
+}
+
+
+static status_t generateExportHeaderForPackage(
+        const FQName &packageFQName,
+        const char * /* hidl_gen */,
+        Coordinator *coordinator,
+        const std::string &outputPath) {
+
+    CHECK(packageFQName.isValid()
+            && !packageFQName.isFullyQualified()
+            && packageFQName.name().empty());
+
+    std::vector<FQName> packageInterfaces;
+
+    status_t err = coordinator->appendPackageInterfacesToVector(
+            packageFQName, &packageInterfaces);
+
+    if (err != OK) {
+        return err;
+    }
+
+    std::vector<const Type *> exportedTypes;
+
+    for (const auto &fqName : packageInterfaces) {
+        AST *ast = coordinator->parse(fqName);
+
+        if (ast == NULL) {
+            fprintf(stderr,
+                    "ERROR: Could not parse %s. Aborting.\n",
+                    fqName.string().c_str());
+
+            return UNKNOWN_ERROR;
+        }
+
+        ast->appendToExportedTypesVector(&exportedTypes);
+    }
+
+    if (exportedTypes.empty()) {
+        return OK;
+    }
+
+    CHECK(Coordinator::MakeParentHierarchy(outputPath));
+    FILE *file = fopen(outputPath.c_str(), "w");
+
+    if (file == nullptr) {
+        return -errno;
+    }
+
+    Formatter out(file);
+
+    out << "// This file is autogenerated by hidl-gen. Do not edit manually."
+           "\n\n";
+
+    std::string guard = "HIDL_GENERATED_";
+    guard += 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);
+    }
+
+    out << "#ifdef __cplusplus\n}\n#endif\n\n#endif  // "
+        << guard
+        << "\n";
+
+    return OK;
+}
+
 static std::vector<OutputHandler> formats = {
     {"c++",
-     true /* mNeedsOutputDir */,
+     OutputHandler::NEEDS_DIR /* mOutputMode */,
      validateForSource,
      [](const FQName &fqName,
         const char *hidl_gen, Coordinator *coordinator,
@@ -701,8 +801,25 @@
         }
     },
 
+    {"export-header",
+     OutputHandler::NEEDS_FILE /* mOutputMode */,
+     validateForExportHeader,
+     [](const FQName &fqName,
+        const char *hidl_gen,
+        Coordinator *coordinator,
+        const std::string &outputPath) -> status_t {
+            CHECK(!fqName.isFullyQualified());
+
+            return generateExportHeaderForPackage(
+                    fqName,
+                    hidl_gen,
+                    coordinator,
+                    outputPath);
+        }
+    },
+
     {"c++-impl",
-     true /* mNeedsOutputDir */,
+     OutputHandler::NEEDS_DIR /* mOutputMode */,
      validateForSource,
      [](const FQName &fqName,
         const char *hidl_gen, Coordinator *coordinator,
@@ -723,7 +840,7 @@
 
 
     {"java",
-     true /* mNeedsOutputDir */,
+     OutputHandler::NEEDS_DIR /* mOutputMode */,
      validateForSource,
      [](const FQName &fqName,
         const char *hidl_gen, Coordinator *coordinator,
@@ -746,7 +863,7 @@
     },
 
     {"vts",
-     true,
+     OutputHandler::NEEDS_DIR /* mOutputMode */,
      validateForSource,
      [](const FQName &fqName,
         const char * hidl_gen,
@@ -767,19 +884,19 @@
     },
 
     {"makefile",
-     false /* mNeedsOutputDir */,
+     OutputHandler::NOT_NEEDED /* mOutputMode */,
      validateForMakefile,
      generateMakefileForPackage,
     },
 
     {"androidbp",
-     false /* mNeedsOutputDir */,
+     OutputHandler::NOT_NEEDED /* mOutputMode */,
      validateForMakefile,
      generateAndroidBpForPackage,
     },
 
     {"makefile-impl",
-     true /* mNeedsOutputDir */,
+     OutputHandler::NOT_NEEDED /* mOutputMode */,
      validateForMakefile,
      generateMakefileImplForPackage,
     }
@@ -804,7 +921,7 @@
 }
 
 int main(int argc, char **argv) {
-    std::string outputDir;
+    std::string outputPath;
     std::vector<std::string> packageRootPaths;
     std::vector<std::string> packageRoots;
 
@@ -816,7 +933,7 @@
         switch (res) {
             case 'o':
             {
-                outputDir = optarg;
+                outputPath = optarg;
                 break;
             }
 
@@ -881,16 +998,27 @@
 
     // Valid options are now in argv[0] .. argv[argc - 1].
 
-    if (!outputFormat->mNeedsOutputDir) {
-        outputDir.clear();  // Unused.
-    } else if (outputDir.empty()) {
-        usage(me);
-        exit(1);
-    } else {
-        const size_t len = outputDir.size();
-        if (outputDir[len - 1] != '/') {
-            outputDir += "/";
+    switch (outputFormat->mOutputMode) {
+        case OutputHandler::NEEDS_DIR:
+        case OutputHandler::NEEDS_FILE:
+        {
+            if (outputPath.empty()) {
+                usage(me);
+                exit(1);
+            }
+
+            if (outputFormat->mOutputMode == OutputHandler::NEEDS_DIR) {
+                const size_t len = outputPath.size();
+                if (outputPath[len - 1] != '/') {
+                    outputPath += "/";
+                }
+            }
+            break;
         }
+
+        default:
+            outputPath.clear();  // Unused.
+            break;
     }
 
     Coordinator coordinator(packageRootPaths, packageRoots);
@@ -912,7 +1040,7 @@
         }
 
         status_t err =
-            outputFormat->generate(fqName, me, &coordinator, outputDir);
+            outputFormat->generate(fqName, me, &coordinator, outputPath);
 
         if (err != OK) {
             exit(1);