text: // file      : libbuild2/c/init.cxx -*- C++ -*-
  // license   : MIT; see accompanying LICENSE file
  
  #include <libbuild2/c/init.hxx>
  
  #include <libbuild2/scope.hxx>
  #include <libbuild2/diagnostics.hxx>
  
  #include <libbuild2/cc/guess.hxx>
  #include <libbuild2/cc/module.hxx>
  
  #include <libbuild2/c/target.hxx>
  
  #ifndef BUILD2_DEFAULT_C
  #  ifdef BUILD2_NATIVE_C
  #    define BUILD2_DEFAULT_C BUILD2_NATIVE_C
  #  else
  #    define BUILD2_DEFAULT_C ""
  #  endif
  #endif
  
  using namespace std;
  using namespace butl;
  
  namespace build2
  {
    namespace c
    {
      using cc::compiler_id;
      using cc::compiler_class;
      using cc::compiler_info;
  
      class config_module: public cc::config_module
      {
      public:
        explicit
        config_module (config_data&& d): cc::config_module (move (d)) {}
  
        virtual void
        translate_std (const compiler_info&,
                       const target_triplet&,
                       scope&,
                       strings&,
                       const string*) const override;
      };
  
      using cc::module;
  
      void config_module::
      translate_std (const compiler_info& ci,
                     const target_triplet&,
                     scope& rs,
                     strings& mode,
                     const string* v) const
      {
        switch (ci.class_)
        {
        case compiler_class::msvc:
          {
            // Standard-wise, with VC you get what you get. The question is
            // whether we should verify that the requested standard is provided
            // by this VC version. And if so, from which version should we say
            // VC supports 90, 99, and 11? We should probably be as loose as
            // possible here since the author will always be able to tighten
            // (but not loosen) this in the buildfile (i.e., detect unsupported
            // versions).
            //
            // The state of affairs seem to be (from Herb Sutter's blog):
            //
            // 10.0 - most of C95 plus a few C99 features
            // 11.0 - partial support for the C++11 subset of C11
            // 12.0 - more C11 features from the C++11 subset, most of C99
            //
            // So let's say C99 is supported from 10.0 and C11 from 11.0. And
            // C90 is supported by everything we care to support.
            //
            // C17/18 is a bug-fix version of C11 so here we assume it is the
            // same as C11.
            //
            // And it's still early days for C2X.
            //
            // From version 16.8 VC now supports /std:c11 and /std:c17 options
            // which enable C11/17 conformance. However, as of version 16.10,
            // neither SDK nor CRT can be compiled in these modes (see the /std
            // option documentation for details/updates).
            //
            if (v == nullptr)
              ;
            else if (*v != "90")
            {
              uint64_t cver (ci.version.major);
  
              if ((*v == "99"   && cver < 16) ||  // Since VS2010/10.0.
                  ((*v == "11" ||
                    *v == "17" ||
                    *v == "18") && cver < 18) ||
                  (*v == "2x"               ))
              {
                fail << "C" << *v << " is not supported by " << ci.signature <<
                  info << "required by " << project (rs) << '@' << rs;
              }
            }
            break;
          }
        case compiler_class::gcc:
          {
            // 90 and 89 are the same standard. Translate 99 to 9x and 11 to 1x
            // for compatibility with older versions of the compilers.
            //
            if (v == nullptr)
              ;
            else
            {
              string o ("-std=");
  
              if      (*v == "2x") o += "c2x"; // GCC 9, Clang 9 (8?).
              else if (*v == "17" ||
                       *v == "18") o += "c17"; // GCC 8, Clang 6.
              else if (*v == "11") o += "c1x";
              else if (*v == "99") o += "c9x";
              else if (*v == "90") o += "c90";
              else o += *v; // In case the user specifies `gnuNN` or some such.
  
              mode.insert (mode.begin (), move (o));
            }
            break;
          }
        }
      }
  
      static const char* const hinters[] = {"cxx", nullptr};
  
      // See cc::module for details on guess_init vs config_init.
      //
      bool
      guess_init (scope& rs,
                  scope& bs,
                  const location& loc,
                  bool,
                  bool,
                  module_init_extra& extra)
      {
        tracer trace ("c::guess_init");
        l5 ([&]{trace << "for " << bs;});
  
        // We only support root loading (which means there can only be one).
        //
        if (rs != bs)
          fail (loc) << "c.guess module must be loaded in project root";
  
        // Load cc.core.vars so that we can cache all the cc.* variables.
        //
        load_module (rs, rs, "cc.core.vars", loc);
  
        // Enter all the variables and initialize the module data.
        //
        auto& vp (rs.var_pool ());
  
        cc::config_data d {
          cc::lang::c,
  
          "c",
          "c",
          BUILD2_DEFAULT_C,
          ".i",
  
          hinters,
  
          vp["bin.binless"],
  
          // NOTE: remember to update documentation if changing anything here.
          //
          vp.insert<strings> ("config.c"),
          vp.insert<string>  ("config.c.id"),
          vp.insert<string>  ("config.c.version"),
          vp.insert<string>  ("config.c.target"),
          vp.insert<string>  ("config.c.std"),
          vp.insert<strings> ("config.c.poptions"),
          vp.insert<strings> ("config.c.coptions"),
          vp.insert<strings> ("config.c.loptions"),
          vp.insert<strings> ("config.c.aoptions"),
          vp.insert<strings> ("config.c.libs"),
  
          // See config.cxx.internal.scope for details.
          //
          vp.insert<string> ("config.c.internal.scope"),
  
          nullptr /* config.c.translate_include */,
  
          vp.insert<process_path_ex> ("c.path"),
          vp.insert<strings>         ("c.mode"),
          vp.insert<path>            ("c.config.path"),
          vp.insert<strings>         ("c.config.mode"),
          vp.insert<dir_paths>       ("c.sys_lib_dirs"),
          vp.insert<dir_paths>       ("c.sys_hdr_dirs"),
  
          vp.insert<string>       ("c.std"),
  
          vp.insert<strings>      ("c.poptions"),
          vp.insert<strings>      ("c.coptions"),
          vp.insert<strings>      ("c.loptions"),
          vp.insert<strings>      ("c.aoptions"),
          vp.insert<strings>      ("c.libs"),
  
          vp.insert<string>  ("c.internal.scope"),
          vp.insert<strings> ("c.internal.libs"),
  
          nullptr /* c.translate_include */,
  
          vp["cc.poptions"],
          vp["cc.coptions"],
          vp["cc.loptions"],
          vp["cc.aoptions"],
          vp["cc.libs"],
  
          vp.insert<strings>      ("c.export.poptions"),
          vp.insert<strings>      ("c.export.coptions"),
          vp.insert<strings>      ("c.export.loptions"),
          vp.insert<vector<name>> ("c.export.libs"),
          vp.insert<vector<name>> ("c.export.impl_libs"),
  
          vp["cc.export.poptions"],
          vp["cc.export.coptions"],
          vp["cc.export.loptions"],
          vp["cc.export.libs"],
          vp["cc.export.impl_libs"],
  
          vp.insert_alias (vp["cc.stdlib"], "c.stdlib"), // Same as cc.stdlib.
  
          vp["cc.runtime"],
          vp["cc.stdlib"],
  
          vp["cc.type"],
          vp["cc.system"],
          vp["cc.module_name"],
          vp["cc.importable"],
          vp["cc.reprocess"],
  
          vp.insert<string>   ("c.preprocessed"), // See cxx.preprocessed.
          nullptr,                                // No __symexport (no modules).
  
          vp.insert<string>   ("c.id"),
          vp.insert<string>   ("c.id.type"),
          vp.insert<string>   ("c.id.variant"),
  
          vp.insert<string>   ("c.class"),
  
          &vp.insert<string>   ("c.version"),
          &vp.insert<uint64_t> ("c.version.major"),
          &vp.insert<uint64_t> ("c.version.minor"),
          &vp.insert<uint64_t> ("c.version.patch"),
          &vp.insert<string>   ("c.version.build"),
  
          &vp.insert<string>   ("c.variant_version"),
          &vp.insert<uint64_t> ("c.variant_version.major"),
          &vp.insert<uint64_t> ("c.variant_version.minor"),
          &vp.insert<uint64_t> ("c.variant_version.patch"),
          &vp.insert<string>   ("c.variant_version.build"),
  
          vp.insert<string>   ("c.signature"),
          vp.insert<string>   ("c.checksum"),
  
          vp.insert<string>   ("c.pattern"),
  
          vp.insert<target_triplet> ("c.target"),
  
          vp.insert<string>   ("c.target.cpu"),
          vp.insert<string>   ("c.target.vendor"),
          vp.insert<string>   ("c.target.system"),
          vp.insert<string>   ("c.target.version"),
          vp.insert<string>   ("c.target.class")
        };
  
        // Alias some cc. variables as c.
        //
        vp.insert_alias (d.c_runtime,    "c.runtime");
        vp.insert_alias (d.c_importable, "c.importable");
  
        auto& m (extra.set_module (new config_module (move (d))));
        m.guess (rs, loc, extra.hints);
  
        return true;
      }
  
      bool
      config_init (scope& rs,
                   scope& bs,
                   const location& loc,
                   bool,
                   bool,
                   module_init_extra& extra)
      {
        tracer trace ("c::config_init");
        l5 ([&]{trace << "for " << bs;});
  
        // We only support root loading (which means there can only be one).
        //
        if (rs != bs)
          fail (loc) << "c.config module must be loaded in project root";
  
        // Load c.guess and share its module instance as ours.
        //
        extra.module = load_module (rs, rs, "c.guess", loc, extra.hints);
        extra.module_as<config_module> ().init (rs, loc, extra.hints);
  
        return true;
      }
  
      static const target_type* const hdr[] =
      {
        &h::static_type,
        nullptr
      };
  
      static const target_type* const inc[] =
      {
        &h::static_type,
        &c::static_type,
        nullptr
      };
  
      bool
      init (scope& rs,
            scope& bs,
            const location& loc,
            bool,
            bool,
            module_init_extra& extra)
      {
        tracer trace ("c::init");
        l5 ([&]{trace << "for " << bs;});
  
        // We only support root loading (which means there can only be one).
        //
        if (rs != bs)
          fail (loc) << "c module must be loaded in project root";
  
        // Load c.config.
        //
        auto& cm (
          load_module<config_module> (rs, rs, "c.config", loc, extra.hints));
  
        cc::data d {
          cm,
  
          "c.compile",
          "c.link",
          "c.install",
          "c.uninstall",
  
          cm.x_info->id.type,
          cm.x_info->id.variant,
          cm.x_info->class_,
          cm.x_info->version.major,
          cm.x_info->version.minor,
          cm.x_info->variant_version ? cm.x_info->variant_version->major : 0,
          cm.x_info->variant_version ? cm.x_info->variant_version->minor : 0,
          cast<process_path>   (rs[cm.x_path]),
          cast<strings>        (rs[cm.x_mode]),
          cast<target_triplet> (rs[cm.x_target]),
          cm.env_checksum,
  
          false, // No C modules yet.
          false, // No __symexport support since no modules.
  
          cm.iscope,
          cm.iscope_current,
  
          cast_null<strings> (rs["cc.internal.libs"]),
          cast_null<strings> (rs[cm.x_internal_libs]),
  
          cast<dir_paths> (rs[cm.x_sys_lib_dirs]),
          cast<dir_paths> (rs[cm.x_sys_hdr_dirs]),
          cm.x_info->sys_mod_dirs ? &cm.x_info->sys_mod_dirs->first : nullptr,
  
          cm.sys_lib_dirs_mode,
          cm.sys_hdr_dirs_mode,
          cm.sys_mod_dirs_mode,
  
          cm.sys_lib_dirs_extra,
          cm.sys_hdr_dirs_extra,
  
          c::static_type,
          nullptr,        // No C modules yet.
          hdr,
          inc
        };
  
        auto& m (extra.set_module (new module (move (d))));
        m.init (rs, loc, extra.hints, *cm.x_info);
  
        return true;
      }
  
      static const module_functions mod_functions[] =
      {
        // NOTE: don't forget to also update the documentation in init.hxx if
        //       changing anything here.
  
        {"c.guess",  nullptr, guess_init},
        {"c.config", nullptr, config_init},
        {"c",        nullptr, init},
        {nullptr,    nullptr, nullptr}
      };
  
      const module_functions*
      build2_c_load ()
      {
        return mod_functions;
      }
    }
  }
  