thrift: Add -cpp_use_include_prefix flag to compiler

Summary: Adds a new flag to allow for a mode where #include statements in generated c++ will include path context information.  For example, if my .thrift file includes "foo/bar/baz.thrift", the generated source files will contain #include statements like:

         #include "foo/bar/gen-cpp/baz_types.h"

         instead of just:

         #include "baz_types.h"

         -cpp_use_include_prefix is OFF by default.

Reviewed By: dreiss

Test Plan: Tested against multiple thrift input files both with and without the new flag.

Revert: OK

DiffCamp Revision: 5522


git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665431 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/compiler/cpp/src/generate/t_cpp_generator.cc b/compiler/cpp/src/generate/t_cpp_generator.cc
index 620d09e..7200b55 100644
--- a/compiler/cpp/src/generate/t_cpp_generator.cc
+++ b/compiler/cpp/src/generate/t_cpp_generator.cc
@@ -54,7 +54,8 @@
   const vector<t_program*>& includes = program_->get_includes();
   for (size_t i = 0; i < includes.size(); ++i) {
     f_types_ <<
-      "#include \"" << includes[i]->get_name() << "_types.h\"" << endl;
+      "#include \"" << get_include_prefix(*(includes[i])) <<
+      includes[i]->get_name() << "_types.h\"" << endl;
   }
   f_types_ << endl;
 
@@ -69,7 +70,8 @@
 
   // Include the types file
   f_types_impl_ <<
-    "#include \"" << program_name_ << "_types.h\"" << endl <<
+    "#include \"" << get_include_prefix(*get_program()) << program_name_ << 
+    "_types.h\"" << endl <<
     endl;
 
   // If we are generating local reflection metadata, we need to include
@@ -183,13 +185,15 @@
     "#ifndef " << program_name_ << "_CONSTANTS_H" << endl <<
     "#define " << program_name_ << "_CONSTANTS_H" << endl <<
     endl <<
-    "#include \"" << program_name_ << "_types.h\"" << endl <<
+    "#include \"" << get_include_prefix(*get_program()) << program_name_ << 
+    "_types.h\"" << endl <<
     endl <<
     ns_open_ << endl <<
     endl;
 
   f_consts_impl <<
-    "#include \"" << program_name_ << "_constants.h\"" << endl <<
+    "#include \"" << get_include_prefix(*get_program()) << program_name_ << 
+    "_constants.h\"" << endl <<
     endl <<
     ns_open_ << endl <<
     endl;
@@ -1014,11 +1018,14 @@
     "#define " << svcname << "_H" << endl <<
     endl <<
     "#include <TProcessor.h>" << endl <<
-    "#include \"" << program_name_ << "_types.h\"" << endl;
+    "#include \"" << get_include_prefix(*get_program()) << program_name_ <<
+    "_types.h\"" << endl;
 
-  if (tservice->get_extends() != NULL) {
+  t_service* extends_service = tservice->get_extends();
+  if (extends_service != NULL) {
     f_header_ <<
-      "#include \"" << tservice->get_extends()->get_name() << ".h\"" << endl;
+      "#include \"" << get_include_prefix(*(extends_service->get_program())) <<
+      extends_service->get_name() << ".h\"" << endl;
   }
 
   f_header_ <<
@@ -1032,7 +1039,8 @@
   f_service_ <<
     autogen_comment();
   f_service_ <<
-    "#include \"" << svcname << ".h\"" << endl <<
+    "#include \"" << get_include_prefix(*get_program()) << svcname << ".h\"" <<
+    endl <<
     endl <<
     ns_open_ << endl <<
     endl;
@@ -2076,7 +2084,7 @@
     "// This autogenerated skeleton file illustrates how to build a server." << endl <<
     "// You should copy it to another filename to avoid overwriting it." << endl <<
     endl <<
-    "#include \"" << svcname << ".h\"" << endl <<
+    "#include \"" << get_include_prefix(*get_program()) << svcname << ".h\"" << endl <<
     "#include <protocol/TBinaryProtocol.h>" << endl <<
     "#include <server/TSimpleServer.h>" << endl <<
     "#include <transport/TServerSocket.h>" << endl <<
@@ -2867,3 +2875,19 @@
 
   return string() + "trlo_" + prefix + "_" + prog + "_" + name;
 }
+
+string t_cpp_generator::get_include_prefix(const t_program& program) const {
+  string include_prefix = program.get_include_prefix();
+  if (!use_include_prefix_ ||
+      (include_prefix.size() > 0 && include_prefix[0] == '/')) {
+    // if flag is turned off or this is absolute path, return empty prefix
+    return "";
+  }
+
+  string::size_type last_slash = string::npos;
+  if ((last_slash = include_prefix.rfind("/")) != string::npos) {
+    return include_prefix.substr(0, last_slash) + "/" + out_dir_base_ + "/";
+  }
+  
+  return "";
+}
diff --git a/compiler/cpp/src/generate/t_cpp_generator.h b/compiler/cpp/src/generate/t_cpp_generator.h
index 574a7d2..4bc9783 100644
--- a/compiler/cpp/src/generate/t_cpp_generator.h
+++ b/compiler/cpp/src/generate/t_cpp_generator.h
@@ -23,7 +23,8 @@
  public:
   t_cpp_generator(t_program* program, bool gen_dense) :
     t_oop_generator(program),
-    gen_dense_(gen_dense) {
+    gen_dense_(gen_dense),
+    use_include_prefix_(false) {
 
     out_dir_base_ = "gen-cpp";
   }
@@ -163,7 +164,16 @@
       (ttype->is_base_type() && (((t_base_type*)ttype)->get_base() == t_base_type::TYPE_STRING));
   }
 
+  void set_use_include_prefix(bool use_include_prefix) {
+    use_include_prefix_ = use_include_prefix;
+  }
+
  private:
+  /**
+   * Returns the include prefix to use for a file generated by program, or the
+   * empty string if no include prefix should be used.
+   */
+  std::string get_include_prefix(const t_program& program) const;
 
   /**
    * True iff we should generate local reflection metadata for TDenseProtocol.
@@ -171,6 +181,12 @@
   bool gen_dense_;
 
   /**
+   * True iff we should use a path prefix in our #include statements for other
+   * thrift-generated header files.
+   */
+  bool use_include_prefix_;
+
+  /**
    * Strings for namespace, computed once up front then used directly
    */
 
diff --git a/compiler/cpp/src/generate/t_generator.h b/compiler/cpp/src/generate/t_generator.h
index 72ef144..734e033 100644
--- a/compiler/cpp/src/generate/t_generator.h
+++ b/compiler/cpp/src/generate/t_generator.h
@@ -38,6 +38,8 @@
    */
   void generate_program();
 
+  const t_program* get_program() const { return program_; }
+
  protected:
 
   /**
diff --git a/compiler/cpp/src/main.cc b/compiler/cpp/src/main.cc
index 131820a..e0eb4b7 100644
--- a/compiler/cpp/src/main.cc
+++ b/compiler/cpp/src/main.cc
@@ -106,6 +106,12 @@
 vector<string> g_incl_searchpath;
 
 /**
+ * Should C++ include statements use path prefixes for other thrift-generated
+ * header files
+ */
+bool g_cpp_use_include_prefix = false;
+
+/**
  * Global debug state
  */
 int g_debug = 0;
@@ -613,6 +619,8 @@
   fprintf(stderr, "               (default: current directory)\n");
   fprintf(stderr, "  -I dir      Add a directory to the list of directories\n");
   fprintf(stderr, "                searched for include directives\n");
+  fprintf(stderr, "  -cpp_use_include_prefix\n");
+  fprintf(stderr, "              Make C++ include statements use path prefixes\n");
   fprintf(stderr, "  -dense      Generate metadata for TDenseProtocol (C++)\n");
   fprintf(stderr, "  -rest       Generate PHP REST processors (with -php)\n");
   fprintf(stderr, "  -nowarn     Suppress all compiler warnings (BAD!)\n");
@@ -832,6 +840,7 @@
     if (gen_cpp) {
       pverbose("Generating C++\n");
       t_cpp_generator* cpp = new t_cpp_generator(program, gen_dense);
+      cpp->set_use_include_prefix(g_cpp_use_include_prefix);
       cpp->generate_program();
       delete cpp;
     }
@@ -933,7 +942,7 @@
       csharp->generate_program();
       delete csharp;
     }
-	
+
     if (dump_docs) {
       dump_docstrings(program);
     }
@@ -1034,6 +1043,8 @@
         gen_st = true;
       } else if (strcmp(arg, "-csharp") == 0) {
         gen_csharp = true;
+      } else if (strcmp(arg, "-cpp_use_include_prefix") == 0) {
+        g_cpp_use_include_prefix = true;
       } else if (strcmp(arg, "-I") == 0) {
         // An argument of "-I\ asdf" is invalid and has unknown results
         arg = argv[++i];
@@ -1097,6 +1108,18 @@
   if (out_path.size()) {
     program->set_out_path(out_path);
   }
+  if (g_cpp_use_include_prefix) {
+    // infer this from the filename passed in
+    string input_filename = argv[i];
+    string include_prefix;
+
+    string::size_type last_slash = string::npos;
+    if ((last_slash = input_filename.rfind("/")) != string::npos) {
+      include_prefix = input_filename.substr(0, last_slash);
+    }
+
+    program->set_include_prefix(include_prefix);
+  }
 
   // Initialize global types
   g_type_void   = new t_base_type("void",   t_base_type::TYPE_VOID);
diff --git a/compiler/cpp/src/parse/t_program.h b/compiler/cpp/src/parse/t_program.h
index 62528e6..7b369d6 100644
--- a/compiler/cpp/src/parse/t_program.h
+++ b/compiler/cpp/src/parse/t_program.h
@@ -70,6 +70,9 @@
   // Namespace
   const std::string& get_namespace() const { return namespace_; }
 
+  // Include prefix accessor
+  const std::string& get_include_prefix() const { return include_prefix_; }
+
   // Accessors for program elements
   const std::vector<t_typedef*>& get_typedefs()  const { return typedefs_;  }
   const std::vector<t_enum*>&    get_enums()     const { return enums_;     }
@@ -113,14 +116,35 @@
 
   // Includes
 
-  void add_include(std::string path) {
-    includes_.push_back(new t_program(path));
+  void add_include(std::string path, std::string include_site) {
+    t_program* program = new t_program(path);
+
+    // include prefix for this program is the site at which it was included
+    // (minus the filename)
+    std::string include_prefix;
+    std::string::size_type last_slash = std::string::npos;
+    if ((last_slash = include_site.rfind("/")) != std::string::npos) {
+      include_prefix = include_site.substr(0, last_slash);
+    }
+
+    program->set_include_prefix(include_prefix);
+    includes_.push_back(program);
   }
 
   std::vector<t_program*>& get_includes() {
     return includes_;
   }
 
+  void set_include_prefix(std::string include_prefix) {
+    include_prefix_ = include_prefix;
+
+    // this is intended to be a directory; add a trailing slash if necessary
+    int len = include_prefix_.size();
+    if (len > 0 && include_prefix_[len - 1] != '/') {
+      include_prefix_ += '/';
+    }
+  }
+
   // Language specific namespace / packaging
 
   void set_cpp_namespace(std::string cpp_namespace) {
@@ -236,6 +260,9 @@
   // Included programs
   std::vector<t_program*> includes_;
 
+  // Include prefix for this program, if any
+  std::string include_prefix_;
+
   // Identifier lookup scope
   t_scope* scope_;
 
diff --git a/compiler/cpp/src/thrifty.yy b/compiler/cpp/src/thrifty.yy
index 5be19b3..2b09972 100644
--- a/compiler/cpp/src/thrifty.yy
+++ b/compiler/cpp/src/thrifty.yy
@@ -347,7 +347,7 @@
       if (g_parse_mode == INCLUDES) {
         std::string path = include_file(std::string($2));
         if (!path.empty()) {
-          g_program->add_include(path);
+          g_program->add_include(path, std::string($2));
         }
       }
     }