hidl-gen: multiple changes

1. Break up generateMakefileOrSourcesForPackage() into separate
   functions for generating C++ and Android.mk files.

2. Add new command-line switch (-L) followed by a string to identify the
   code generator.  Supported code generators are -Lc++ and -Lmakefile,
   like so:

	hidl-gen -Lc++ \
		 -randroid.hardware:hardware/interfaces \
		 android.hardware.nfc@1.0

3. Remove the -m switch

4. Restore the ability to generate code for individual interfaces within
   a package, as well as for entire packages.  The following are valid
   invokations of hidl-gen:

	hidl-gen -o/tmp -Lc++ \
		 -randroid.hardware:hardware/interfaces \
		 android.hardware.nfc@1.0

	hidl-gen -o /tmp -Lc++ \
		 -randroid.hardware:hardware/interfaces \
		 android.hardware.nfc@1.0::INfc

   The first invokation will generate the following files under
   /tmp/android/hardware/nfc/1.0:

	/tmp/android/hardware/nfc/1.0/INfc.h
	/tmp/android/hardware/nfc/1.0/BnNfc.h
	/tmp/android/hardware/nfc/1.0/NfcAll.cpp
	/tmp/android/hardware/nfc/1.0/BpNfc.h

   The second invokation will result in:

	/tmp/android/hardware/nfc/1.0/types.cpp
	/tmp/android/hardware/nfc/1.0/INfc.h
	/tmp/android/hardware/nfc/1.0/BnNfc.h
	/tmp/android/hardware/nfc/1.0/NfcClientCallbackAll.cpp
	/tmp/android/hardware/nfc/1.0/NfcAll.cpp
	/tmp/android/hardware/nfc/1.0/BpNfcClientCallback.h
	/tmp/android/hardware/nfc/1.0/BpNfc.h
	/tmp/android/hardware/nfc/1.0/types.h
	/tmp/android/hardware/nfc/1.0/BnNfcClientCallback.h
	/tmp/android/hardware/nfc/1.0/INfcClientCallback.h

5. Fixed Android.mk generation to build every .hal file once.  To make,
   a HIDL interface I${name}.hal now maps to the target ${name}All.cpp

Change-Id: I83f76cbc847536bec03520bcaa39d8f449da67f2
Signed-off-by: Iliyan Malchev <malchev@google.com>
diff --git a/main.cpp b/main.cpp
index 9976999..752e5f8 100644
--- a/main.cpp
+++ b/main.cpp
@@ -4,6 +4,7 @@
 #include "FQName.h"
 
 #include <android-base/logging.h>
+#include <set>
 #include <stdio.h>
 #include <string>
 #include <unistd.h>
@@ -11,33 +12,93 @@
 
 using namespace android;
 
-static void usage(const char *me) {
-    fprintf(stderr,
-            "usage: %s -o output-path [-m] (-r interface-root)+ fqname+\n",
-            me);
+struct OutputHandler {
+    std::string mKey;
+    bool mNeedsOutputDir;
+    enum ValRes {
+        FAILED,
+        PASS_PACKAGE,
+        PASS_FULL
+    };
+    ValRes (*validate)(const FQName &);
+    status_t (*generate)(const FQName &fqName,
+                         const char *hidl_gen,
+                         Coordinator *coordinator,
+                         const std::string &outputDir);
+};
 
-    fprintf(stderr, "         -o output path\n");
-    fprintf(stderr, "         -m generate makefile instead of sources\n");
+static status_t generateSourcesForFile(
+        const FQName &fqName,
+        const char *,
+        Coordinator *coordinator,
+        const std::string &outputDir) {
 
-    fprintf(stderr,
-            "         -r package:path root "
-            "(e.g., android.hardware:hardware/interfaces)\n");
+    CHECK(fqName.isFullyQualified());
+
+    AST *ast = coordinator->parse(fqName);
+
+    if (ast == NULL) {
+        fprintf(stderr,
+                "Could not parse %s. Aborting.\n",
+                fqName.string().c_str());
+
+        return UNKNOWN_ERROR;
+    }
+
+    status_t err = ast->generateCpp(outputDir);
+
+    return err;
+}
+
+static status_t generateSourcesForPackage(
+        const FQName &packageFQName,
+        const char *hidl_gen,
+        Coordinator *coordinator,
+        const std::string &outputDir) {
+
+    CHECK(packageFQName.isValid() &&
+          !packageFQName.isFullyQualified() &&
+          packageFQName.name().empty());
+
+    std::vector<FQName> packageInterfaces;
+
+    status_t err =
+        coordinator->appendPackageInterfacesToSet(packageFQName,
+                                                  &packageInterfaces);
+
+    if (err != OK) {
+        return err;
+    }
+
+    for (const auto &fqName : packageInterfaces) {
+        err = generateSourcesForFile(fqName, hidl_gen, coordinator, outputDir);
+        if (err != OK) {
+            return err;
+        }
+    }
+
+    return OK;
 }
 
 static std::string makeLibraryName(const FQName &packageFQName) {
     return packageFQName.string();
 }
 
-static status_t generateMakefileOrSourcesForPackage(
+static status_t generateMakefileForPackage(
+        const FQName &packageFQName,
         const char *hidl_gen,
         Coordinator *coordinator,
-        const std::string &outputDir,
-        bool createMakefile,
-        const FQName &packageFQName) {
+        const std::string &) {
+
+    CHECK(packageFQName.isValid() &&
+          !packageFQName.isFullyQualified() &&
+          packageFQName.name().empty());
+
     std::vector<FQName> packageInterfaces;
 
     status_t err =
-        coordinator->getPackageInterfaces(packageFQName, &packageInterfaces);
+        coordinator->appendPackageInterfacesToSet(packageFQName,
+                                                  &packageInterfaces);
 
     if (err != OK) {
         return err;
@@ -55,19 +116,7 @@
             return UNKNOWN_ERROR;
         }
 
-        if (!createMakefile) {
-            status_t err = ast->generateCpp(outputDir);
-
-            if (err != OK) {
-                return err;
-            }
-        } else {
-            ast->addImportedPackages(&importedPackages);
-        }
-    }
-
-    if (!createMakefile) {
-        return OK;
+        ast->getImportedPackages(&importedPackages);
     }
 
     std::string path =
@@ -93,87 +142,160 @@
         << "\n"
         << "LOCAL_MODULE_CLASS := SHARED_LIBRARIES\n\n"
         << "intermediates := $(local-generated-sources-dir)\n\n"
-        << "GEN := \\\n";
+        << "HIDL := $(HOST_OUT_EXECUTABLES)/"
+        << hidl_gen << "$(HOST_EXECUTABLE_SUFFIX)";
 
-    out.indent();
     for (const auto &fqName : packageInterfaces) {
-        out << "$(intermediates)/"
+
+        out << "\n"
+            << "\n#"
+            << "\n# Build " << fqName.name() << ".hal"
+            << "\n#";
+        out << "\nGEN := $(intermediates)/"
             << coordinator->convertPackageRootToPath(packageFQName)
             << coordinator->getPackagePath(packageFQName, true /* relative */);
-
         if (fqName.name() == "types") {
             out << "types.cpp";
         } else {
             out << fqName.name().substr(1) << "All.cpp";
         }
 
-        out << " \\\n";
+        out << "\n$(GEN): $(HIDL)";
+        out << "\n$(GEN): PRIVATE_HIDL := $(HIDL)";
+        out << "\n$(GEN): PRIVATE_DEPS := $(LOCAL_PATH)/"
+            << fqName.name() << ".hal";
+        out << "\n$(GEN): PRIVATE_OUTPUT_DIR := $(intermediates)"
+            << "\n$(GEN): PRIVATE_CUSTOM_TOOL = \\";
+        out.indent();
+        out.indent();
+        out << "\n$(PRIVATE_HIDL) -o $(PRIVATE_OUTPUT_DIR) \\"
+            << "\n-Lc++ -r"
+            << coordinator->getPackageRoot(packageFQName) << ":"
+            << coordinator->getPackageRootPath(packageFQName) << "\\";
+        out << "\n"
+            << packageFQName.string()
+            << "::$(patsubst %.hal,%,$(notdir $(PRIVATE_DEPS)))"
+            << "\n";
+        out.unindent();
+        out.unindent();
+
+        out << "\n$(GEN): $(LOCAL_PATH)/" << fqName.name() << ".hal";
+        out << "\n\t$(transform-generated-source)";
+        out << "\nLOCAL_GENERATED_SOURCES += $(GEN)";
     }
 
-    out.unindent();
-
-    out << "\n$(GEN): PRIVATE_OUTPUT_DIR := $(intermediates)\n\n"
-        << "$(GEN): PRIVATE_CUSTOM_TOOL = \\\n";
-
-    out.indent();
-
-    out << hidl_gen << " -o $(PRIVATE_OUTPUT_DIR) "
-        << packageFQName.string()
-        << "\n";
-
-    out.unindent();
-
-    out << "\n$(GEN): ";
-
-    bool first = true;
-    for (const auto &fqName : packageInterfaces) {
-        if (!first) {
-            out << " ";
-        }
-
-        out << "$(LOCAL_PATH)/" << fqName.name() << ".hal";
-
-        first = false;
-    }
-    out << "\n\t$(transform-generated-source)\n\n";
-
-    out << "LOCAL_GENERATED_SOURCES += $(GEN)\n\n"
-        << "LOCAL_EXPORT_C_INCLUDE_DIRS := $(intermediates)\n\n"
-        << "LOCAL_SHARED_LIBRARIES := \\\n";
-
-    out.indent();
-    out << "libhwbinder \\\n"
-        << "libutils \\\n";
-
-    for (const auto &importedPackage : importedPackages) {
-        out << makeLibraryName(importedPackage) << " \\\n";
-    }
-
-    out.unindent();
-
     out << "\n"
-        << "include $(BUILD_SHARED_LIBRARY)\n";
+        << "\nLOCAL_EXPORT_C_INCLUDE_DIRS := $(intermediates)"
+        << "\nLOCAL_SHARED_LIBRARIES := \\";
+    out.indent();
+    out << "\nlibhwbinder \\"
+        << "\nlibutils \\";
+
+    for (const auto &importedPackage : importedPackages) {
+        out << "\n" << makeLibraryName(importedPackage) << " \\";
+    }
+    out << "\n";
+    out.unindent();
+
+    out << "\ninclude $(BUILD_SHARED_LIBRARY)\n";
 
     return OK;
 }
 
+OutputHandler::ValRes validateForMakefile(const FQName &fqName) {
+    if (fqName.package().empty()) {
+        fprintf(stderr, "Expecting package name\n");
+        return OutputHandler::FAILED;
+    }
+
+    if (fqName.version().empty()) {
+        fprintf(stderr, "Expecting package version\n");
+        return OutputHandler::FAILED;
+    }
+
+    if (!fqName.name().empty()) {
+        fprintf(stderr,
+                "Expecting only package name and version.\n");
+        return OutputHandler::FAILED;
+    }
+
+    return OutputHandler::PASS_PACKAGE;
+}
+
+OutputHandler::ValRes validateForCpp(const FQName &fqName) {
+    if (fqName.package().empty()) {
+        fprintf(stderr, "Expecting package name\n");
+        return OutputHandler::FAILED;
+    }
+
+    if (fqName.version().empty()) {
+        fprintf(stderr, "Expecting package version\n");
+        return OutputHandler::FAILED;
+    }
+
+    return fqName.name().empty() ?
+        OutputHandler::PASS_PACKAGE :
+        OutputHandler::PASS_FULL;
+}
+
+static std::vector<OutputHandler> formats = {
+    {"c++",
+     true,
+     validateForCpp,
+     [](const FQName &fqName,
+        const char *hidl_gen, Coordinator *coordinator,
+        const std::string &outputDir) -> status_t {
+            if (fqName.isFullyQualified()) {
+                return generateSourcesForFile(fqName,
+                                              hidl_gen,
+                                              coordinator,
+                                              outputDir);
+            }
+            else {
+                return generateSourcesForPackage(fqName,
+                                                 hidl_gen,
+                                                 coordinator,
+                                                 outputDir);
+            }
+        }
+    },
+
+    {"makefile",
+     false,
+     validateForMakefile,
+     generateMakefileForPackage,
+    },
+};
+
+static void usage(const char *me) {
+    fprintf(stderr,
+            "usage: %s -o output-path -L <language> (-r interface-root)+ fqname+\n",
+            me);
+
+    fprintf(stderr, "         -o output path\n");
+
+    fprintf(stderr, "         -L <language> (one of");
+    for (auto &e : formats) {
+        fprintf(stderr, " %s", e.mKey.c_str());
+    }
+    fprintf(stderr, ")\n");
+
+    fprintf(stderr,
+            "         -r package:path root "
+            "(e.g., android.hardware:hardware/interfaces)\n");
+}
+
 int main(int argc, char **argv) {
     std::string outputDir;
     std::vector<std::string> packageRootPaths;
     std::vector<std::string> packageRoots;
-    bool createMakefile = false;
 
     const char *me = argv[0];
+    OutputHandler *outputFormat = nullptr;
 
     int res;
-    while ((res = getopt(argc, argv, "hmo:r:")) >= 0) {
+    while ((res = getopt(argc, argv, "ho:r:L:")) >= 0) {
         switch (res) {
-            case 'm':
-            {
-                createMakefile = true;
-                break;
-            }
-
             case 'o':
             {
                 outputDir = optarg;
@@ -193,6 +315,19 @@
                 break;
             }
 
+            case 'L':
+            {
+                CHECK(outputFormat == nullptr); // only one -L option
+                for (auto &e : formats) {
+                    if (e.mKey == optarg) {
+                        outputFormat = &e;
+                        break;
+                    }
+                }
+                CHECK(outputFormat != nullptr);
+                break;
+            }
+
             case '?':
             case 'h':
             default:
@@ -204,6 +339,11 @@
         }
     }
 
+    if (outputFormat == nullptr) {
+        usage(me);
+        exit(1);
+    }
+
     argc -= optind;
     argv += optind;
 
@@ -223,7 +363,7 @@
 
     // Valid options are now in argv[0] .. argv[argc - 1].
 
-    if (createMakefile) {
+    if (!outputFormat->mNeedsOutputDir) {
         outputDir.clear();  // Unused.
     } else if (outputDir.empty()) {
         usage(me);
@@ -240,19 +380,19 @@
     for (int i = 0; i < argc; ++i) {
         FQName fqName(argv[i]);
 
-        if (!fqName.isValid()
-                || fqName.package().empty()
-                || fqName.version().empty()
-                || !fqName.name().empty()) {
+        if (!fqName.isValid()) {
             fprintf(stderr,
-                    "Each fqname argument should specify a valid package.\n");
+                    "Invalid fully-qualified name.\n");
+            exit(1);
+        }
 
+        OutputHandler::ValRes valid = outputFormat->validate(fqName);
+        if (valid == OutputHandler::FAILED) {
             exit(1);
         }
 
         status_t err =
-            generateMakefileOrSourcesForPackage(me,
-                    &coordinator, outputDir, createMakefile, fqName);
+            outputFormat->generate(fqName, me, &coordinator, outputDir);
 
         if (err != OK) {
             break;