hidl-gen: auto-generate depdendencies for nested types

HIDL allows for types to be defined within interfaces, and for these
types to be able to be referred to from outside of these interfaces.
hidl-gen was not emitting the correct #include and import paths in C++
and Java respectively for such cases, and also Makefile dependencies.

b/31268323

Change-Id: I168c52823098348b4161b5d2c151d1d8eadec9b8
Signed-off-by: Iliyan Malchev <malchev@google.com>
diff --git a/AST.cpp b/AST.cpp
index d25e4e1..8fb5ef0 100644
--- a/AST.cpp
+++ b/AST.cpp
@@ -217,6 +217,7 @@
     }
 
     Type *resolvedType = nullptr;
+    Type *returnedType = nullptr;
     FQName resolvedName;
 
     for (const auto &importedAST : mImportedASTs) {
@@ -236,6 +237,7 @@
             }
 
             resolvedType = match;
+            returnedType = resolvedType;
             resolvedName = matchingName;
 
             // Keep going even after finding a match.
@@ -257,6 +259,44 @@
                 static_cast<TypeDef *>(resolvedType)->referencedType();
         }
 
+        returnedType = resolvedType;
+
+        // If the resolved type is not an interface, we need to determine
+        // whether it is defined in types.hal, or in some other interface.  In
+        // the latter case, we need to emit a dependency for the interface in
+        // which the type is defined.
+        //
+        // Consider the following:
+        //    android.hardware.tests.foo@1.0::Record
+        //    android.hardware.tests.foo@1.0::IFoo.Folder
+        //    android.hardware.tests.foo@1.0::Folder
+        //
+        // If Record is an interface, then we keep track of it for the purpose
+        // of emitting dependencies in the target language (for example #include
+        // in C++).  If Record is a UDT, then we assume it is defined in
+        // types.hal in android.hardware.tests.foo@1.0.
+        //
+        // In the case of IFoo.Folder, the same applies.  If IFoo is an
+        // interface, we need to track this for the purpose of emitting
+        // dependencies.  If not, then it must have been defined in types.hal.
+        //
+        // In the case of just specifying Folder, the resolved type is
+        // android.hardware.tests.foo@1.0::IFoo.Folder, and the same logic as
+        // above applies.
+
+        if (!resolvedType->isInterface()) {
+            FQName ifc(resolvedName.package(),
+                       resolvedName.version(),
+                       resolvedName.names().at(0));
+            for (const auto &importedAST : mImportedASTs) {
+                FQName matchingName;
+                Type *match = importedAST->findDefinedType(ifc, &matchingName);
+                if (match != nullptr && match->isInterface()) {
+                    resolvedType = match;
+                }
+            }
+        }
+
         if (!resolvedType->isInterface()) {
             // Non-interface types are declared in the associated types header.
             FQName typesName(
@@ -283,7 +323,7 @@
         }
     }
 
-    return resolvedType->ref();
+    return returnedType->ref();
 }
 
 Type *AST::findDefinedType(const FQName &fqName, FQName *matchingName) const {
diff --git a/FQName.cpp b/FQName.cpp
index 9aa5a61..6f6fd09 100644
--- a/FQName.cpp
+++ b/FQName.cpp
@@ -17,7 +17,9 @@
 #include "FQName.h"
 
 #include <android-base/logging.h>
+#include <iostream>
 #include <regex>
+#include <sstream>
 
 #define RE_COMPONENT    "[a-zA-Z_][a-zA-Z_0-9]*"
 #define RE_PATH         RE_COMPONENT "([.]" RE_COMPONENT ")*"
@@ -102,6 +104,16 @@
     return mName;
 }
 
+std::vector<std::string> FQName::names() const {
+    std::vector<std::string> res {};
+    std::istringstream ss(name());
+    std::string s;
+    while (std::getline(ss, s, '.')) {
+        res.push_back(s);
+    }
+    return res;
+}
+
 void FQName::applyDefaults(
         const std::string &defaultPackage,
         const std::string &defaultVersion) {
diff --git a/FQName.h b/FQName.h
index 40f3ae1..6f5f8d5 100644
--- a/FQName.h
+++ b/FQName.h
@@ -41,7 +41,33 @@
 
     std::string package() const;
     std::string version() const;
+
+    // The next two methods return the name part of the FQName, that is, the
+    // part after the version field.  For example:
+    //
+    // package android.hardware.tests.foo@1.0;
+    // interface IFoo {
+    //    struct bar {
+    //        struct baz {
+    //            ...
+    //        };
+    //    };
+    // };
+    //
+    // package android.hardware.tests.bar@1.0;
+    // import android.hardware.tests.foo@1.0;
+    // interface {
+    //    struct boo {
+    //        IFoo.bar.baz base;
+    //    };
+    // }
+    //
+    // The FQName for base is android.hardware.tests.foo@1.0::IFoo.bar.baz; so
+    // FQName::name() will return "IFoo.bar.baz". FQName::names() will return
+    // std::vector<std::string>{"IFoo","bar","baz"}
+
     std::string name() const;
+    std::vector<std::string> names() const;
 
     bool isFullyQualified() const;
 
diff --git a/generateJava.cpp b/generateJava.cpp
index a3c4761..77d8ccb 100644
--- a/generateJava.cpp
+++ b/generateJava.cpp
@@ -93,6 +93,11 @@
 
         out << "import java.util.Vector;\n\n";
 
+        for (const auto &item : mImportedNamesForJava) {
+            out << "import " << item.javaName() << ";\n";
+        }
+        out << "\n";
+
         status_t err =
             type->emitJavaTypeDeclarations(out, true /* atTopLevel */);
 
diff --git a/main.cpp b/main.cpp
index e7d959a..6f0f248 100644
--- a/main.cpp
+++ b/main.cpp
@@ -338,6 +338,17 @@
             << hidl_gen
             << "$(HOST_EXECUTABLE_SUFFIX)";
 
+        if (!importedPackages.empty()) {
+            out << "\n"
+                << "\nLOCAL_JAVA_LIBRARIES := \\";
+            out.indent();
+            for (const auto &importedPackage : importedPackages) {
+                out << "\n" << makeLibraryName(importedPackage) << "-java \\";
+            }
+            out << "\n";
+            out.unindent();
+        }
+
         generateMakefileSectionForLanguage(
                 out,
                 coordinator,