Define C/C++ version for QtCreator project

Fix problem due to wrong parse C++14/17 features etc.

Change-Id: I07cab1487902683c3deeea67242d648264906ee1
Reviewed-on: https://gn-review.googlesource.com/c/3060
Reviewed-by: Dirk Pranke <dpranke@google.com>
Reviewed-by: Scott Graham <scottmg@chromium.org>
Commit-Queue: Dirk Pranke <dpranke@google.com>
diff --git a/tools/gn/qt_creator_writer.cc b/tools/gn/qt_creator_writer.cc
index b43b7d6..c245024 100644
--- a/tools/gn/qt_creator_writer.cc
+++ b/tools/gn/qt_creator_writer.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/optional.h"
 
 #include "tools/gn/builder.h"
 #include "tools/gn/config_values_extractors.h"
@@ -114,7 +115,119 @@
   }
 }
 
+namespace QtCreatorWriterUtils {
+
+enum class CVersion {
+  C99,
+  C11,
+};
+
+enum class CxxVersion {
+  CXX98,
+  CXX03,
+  CXX11,
+  CXX14,
+  CXX17,
+};
+
+std::string ToMacro(CVersion version) {
+  const std::string s = "__STDC_VERSION__";
+
+  switch(version) {
+    case CVersion::C99:
+      return s + " 199901L";
+    case CVersion::C11:
+      return s + " 201112L";
+    }
+
+  return std::string();
+}
+
+std::string ToMacro(CxxVersion version) {
+  const std::string name = "__cplusplus";
+
+  switch(version) {
+    case CxxVersion::CXX98:
+    case CxxVersion::CXX03:
+      return name + " 199711L";
+    case CxxVersion::CXX11:
+      return name + " 201103L";
+    case CxxVersion::CXX14:
+      return name + " 201402L";
+    case CxxVersion::CXX17:
+      return name + " 201703L";
+    }
+
+  return std::string();
+}
+
+const std::map<std::string, CVersion> kFlagToCVersion{
+  {"-std=gnu99" , CVersion::C99},
+  {"-std=c99"   , CVersion::C99},
+  {"-std=gnu11" , CVersion::C11},
+  {"-std=c11"   , CVersion::C11}
+};
+
+const std::map<std::string, CxxVersion> kFlagToCxxVersion{
+  {"-std=gnu++11", CxxVersion::CXX11}, {"-std=c++11", CxxVersion::CXX11},
+  {"-std=gnu++98", CxxVersion::CXX98}, {"-std=c++98", CxxVersion::CXX98},
+  {"-std=gnu++03", CxxVersion::CXX03}, {"-std=c++03", CxxVersion::CXX03},
+  {"-std=gnu++14", CxxVersion::CXX14}, {"-std=c++14", CxxVersion::CXX14},
+  {"-std=c++1y"  , CxxVersion::CXX14},
+  {"-std=gnu++17", CxxVersion::CXX17}, {"-std=c++17", CxxVersion::CXX17},
+  {"-std=c++1z"  , CxxVersion::CXX17},
+};
+
+template<typename Enum>
+struct CompVersion {
+  bool operator()(Enum a, Enum b) {
+    return static_cast<int>(a) < static_cast<int>(b);
+  }
+};
+
+struct CompilerOptions {
+  base::Optional<CVersion> c_version_;
+  base::Optional<CxxVersion> cxx_version_;
+
+  void SetCVersion(CVersion ver) {
+    SetVersionImpl(c_version_, ver);
+  }
+
+  void SetCxxVersion(CxxVersion ver) {
+    SetVersionImpl(cxx_version_, ver);
+  }
+
+private:
+  template<typename Version>
+  void SetVersionImpl(base::Optional<Version> &cur_ver, Version ver) {
+    if (cur_ver)
+      cur_ver = std::max(*cur_ver, ver, CompVersion<Version> {});
+    else
+      cur_ver = ver;
+  }
+};
+
+void ParseCompilerOption(const std::string& flag, CompilerOptions* options) {
+  auto c_ver = kFlagToCVersion.find(flag);
+  if (c_ver != kFlagToCVersion.end())
+    options->SetCVersion(c_ver->second);
+
+  auto cxx_ver = kFlagToCxxVersion.find(flag);
+  if (cxx_ver != kFlagToCxxVersion.end())
+    options->SetCxxVersion(cxx_ver->second);
+}
+
+void ParseCompilerOptions(const std::vector<std::string>& cflags,
+                          CompilerOptions* options) {
+  for (const std::string& flag : cflags)
+    ParseCompilerOption(flag, options);
+}
+
+} // QtCreatorWriterUtils
+
 void QtCreatorWriter::HandleTarget(const Target* target) {
+  using namespace QtCreatorWriterUtils;
+
   SourceFile build_file = Loader::BuildFileForLabel(target->label());
   sources_.insert(FilePathToUTF8(build_settings_->GetFullPath(build_file)));
   AddToSources(target->settings()->import_manager().GetImportedFiles());
@@ -137,13 +250,26 @@
           FilePathToUTF8(build_settings_->GetFullPath(include_dir)));
     }
 
+    static constexpr const char *define_str = "#define ";
     for (std::string define : it.cur().defines()) {
       size_t equal_pos = define.find('=');
       if (equal_pos != std::string::npos)
         define[equal_pos] = ' ';
-      define.insert(0, "#define ");
+      define.insert(0, define_str);
       defines_.insert(define);
     }
+
+    CompilerOptions options;
+    ParseCompilerOptions(it.cur().cflags(), &options);
+    ParseCompilerOptions(it.cur().cflags_c(), &options);
+    ParseCompilerOptions(it.cur().cflags_cc(), &options);
+
+    auto add_define_version = [this] (auto &ver) {
+      if (ver)
+        defines_.insert(define_str + ToMacro(*ver));
+    };
+    add_define_version(options.c_version_);
+    add_define_version(options.cxx_version_);
   }
 }