// file      : libbuild2/cxx/init.cxx -*- C++ -*-
// license   : MIT; see accompanying LICENSE file

#include <libbuild2/cxx/init.hxx>

#include <libbuild2/scope.hxx>
#include <libbuild2/diagnostics.hxx>

#include <libbuild2/config/utility.hxx>

#include <libbuild2/cc/guess.hxx>
#include <libbuild2/cc/module.hxx>

#include <libbuild2/cxx/target.hxx>

#ifndef BUILD2_DEFAULT_CXX
#  ifdef BUILD2_NATIVE_CXX
#    define BUILD2_DEFAULT_CXX BUILD2_NATIVE_CXX
#  else
#    define BUILD2_DEFAULT_CXX ""
#  endif
#endif

using namespace std;
using namespace butl;

namespace build2
{
  namespace cxx
  {
    using cc::compiler_id;
    using cc::compiler_type;
    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& tt,
                   scope& rs,
                   strings& mode,
                   const string* v) const
    {
      compiler_type ct (ci.id.type);
      compiler_class cl (ci.class_);
      uint64_t mj (ci.version.major);
      uint64_t mi (ci.version.minor);
      uint64_t p (ci.version.patch);

      // Besides various `c++NN` we have two special values: `latest` and
      // `experimental`.
      //
      // The semantics of the `latest` value is the latest available standard
      // that is not necessarily complete or final but is practically usable.
      // In other words, a project that uses this value and does not rely on
      // any unstable/bleeding edge parts of the standard (or takes care to
      // deal with them, for example, using feature test macros), can be
      // reasonably expected to work. In particular, this is the value we use
      // by default in projects created by bdep-new(1) as well as to build the
      // build2 toolchain itself.
      //
      // The `experimental` value, as the name suggests, is the latest
      // available standard that is not necessarily usable in real projects.
      // By definition, `experimental` >= `latest`.
      //
      // In addition to the `experimental` value itself we have a number of
      // feature flags that can be used to enable or disable certain major
      // parts (such as modules, concepts, etc) in this mode. They are also
      // used to signal back to the project whether a particular feature is
      // available. A feature flag set by the user has a tri-state semantics:
      //
      // - false        - disabled
      // - unspecified  - enabled if practically usable
      // - true         - enabled even if practically unusable
      //
      bool latest       (v != nullptr && *v == "latest");
      bool experimental (v != nullptr && *v == "experimental");

      // Feature flags.
      //
      auto& vp (rs.var_pool ());

      // Similar to config.cxx.std, config.cxx.features.* overrides
      // cxx.features.*.
      //
      struct feature
      {
        optional<bool>   value; // cxx.features.* value.
        optional<bool> c_value; // config.cxx.features.* value.
        bool            result; // Calculated result value.

        feature& operator= (bool r) {result = r; return *this;}

        build2::value&    value_; // cxx.features.* variable value.
        const char*       name_;  // Feature name.
      };

      auto get_feature = [&rs, &vp] (const char* name) -> feature
      {
        auto& var   (vp.insert<bool> (string ("cxx.features.") + name));
        auto& c_var (vp.insert<bool> (string ("config.cxx.features.") + name));

        pair<value&, bool> val (rs.vars.insert (var));
        lookup l (config::lookup_config (rs, c_var));

        optional<bool> v, c_v;
        if (l.defined ())
          v = c_v = cast_false<bool> (*l);
        else if (!val.second)
          v = cast_false<bool> (val.first);

        return feature {v, c_v, false, val.first, name};
      };

      auto set_feature = [&rs, &ci, v] (const feature& f)
      {
        if (f.c_value && *f.c_value != f.result)
        {
          fail << f.name_ << " cannot be "
               << (*f.c_value ? "enabled" : "disabled") << " for "
               << project (rs) << '@' << rs <<
          info << "C++ language standard is "
               << (v != nullptr ? v->c_str () : "compiler-default") <<
          info << "C++ compiler is " << ci.signature <<
          info << f.name_ << " state requested with config.cxx.features."
               << f.name_;
        }

        f.value_ = f.result;
      };

      feature modules (get_feature ("modules"));
      //feature concepts (get_feature ("concepts"));

      // NOTE: see also module sidebuild subproject if changing anything about
      // modules here.

      string o;

      auto prepend = [&mode, i = mode.begin ()] (string o) mutable
      {
        i = mode.insert (i, move (o)) + 1;
      };

      switch (cl)
      {
      case compiler_class::msvc:
        {
          // C++ standard-wise, with VC you got what you got up until 14.2.
          // Starting with 14.3 there is now the /std: switch which defaults
          // to c++14 but can be set to c++latest. And from 15.3 it can be
          // c++17. And from 16.11 it can be c++20 (we start with the compiler
          // version for 16.11.4 since 16.11.0 seems to be indistinguishable
          // from 16.10).
          //
          bool v16_11 (           mj >  19 || (mj == 19 && (mi > 29 || (mi == 29 && p >= 30136))));
          bool v16_0  (v16_11 || (mj == 19 && mi >= 20));
          bool v15_3  (v16_0  || (mj == 19 && mi >= 11));
          bool v14_3  (v15_3  || (mj == 19 && (mi > 0 || (mi == 0 && p >= 24215))));

          // The question is also 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 11, 14, and 17? 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).
          //
          // For now we are not going to bother doing this for C++03.
          //
          if (experimental)
          {
            if (v14_3)
              o = "/std:c++latest";
          }
          else if (latest)
          {
            // We used to map `latest` to `c++latest` but starting from 16.1,
            // VC seem to have adopted the "move fast and break things" motto
            // for this mode. So starting from 16 we only enable it in
            // `experimental`.
            //
            if (v16_11)
              o = "/std:c++20";
            else if (v16_0)
              o = "/std:c++17";
            else if (v14_3)
              o = "/std:c++latest";
          }
          else if (v == nullptr)
            ;
          else if (*v != "98" && *v != "03")
          {
            bool sup (false);

            if      (*v == "11") // C++11 since VS2010/10.0.
            {
              sup = mj >= 16;
            }
            else if (*v == "14") // C++14 since VS2015/14.0.
            {
              sup = mj >= 19;
            }
            else if (*v == "17") // C++17 since VS2015/14.0u2.
            {
              // Note: the VC15 compiler version is 19.10.
              //
              sup = (mj > 19 ||
                     (mj == 19 && (mi > 0 || (mi == 0 && p >= 23918))));
            }
            else if (*v == "20") // C++20 since VS2019/16.11.
            {
              sup = v16_11;
            }

            if (!sup)
              fail << "C++" << *v << " is not supported by " << ci.signature <<
                info << "required by " << project (rs) << '@' << rs;

            if (v15_3)
            {
              if      (*v == "20") o = "/std:c++20";
              else if (*v == "17") o = "/std:c++17";
              else if (*v == "14") o = "/std:c++14";
            }
            else if (v14_3)
            {
              if      (*v == "14") o = "/std:c++14";
              else if (*v == "17") o = "/std:c++latest";
            }
          }

          if (!o.empty ())
            prepend (move (o));

          // Since VC 15.7 we can get a (more) accurate __cplusplus value if
          // we ask for it with /Zc:__cplusplus:
          //
          // https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
          //
          if (mj > 19 || (mj == 19 &&  mi >= 14))
          {
            if (!find_option_prefix ("/Zc:__cplusplus", mode))
              prepend ("/Zc:__cplusplus");
          }

          break;
        }
      case compiler_class::gcc:
        {
          if (latest || experimental)
          {
            switch (ct)
            {
            case compiler_type::gcc:
              {
                if      (mj >= 11)           o = "-std=c++23"; // 23
                else if (mj >= 8)            o = "-std=c++2a"; // 20
                else if (mj >= 5)            o = "-std=c++1z"; // 17
                else if (mj == 4 && mi >= 8) o = "-std=c++1y"; // 14
                else if (mj == 4 && mi >= 4) o = "-std=c++0x"; // 11

                break;
              }
            case compiler_type::clang:
              {
                // Clang 10.0.0 targeting MSVC 16.4 and 16.5 (preview) in the
                // c++2a mode uncovers some Concepts-related bugs in MSVC STL
                // (LLVM bug #44956). So in this case we map `latest` to
                // c++17.
                //
                // While reportedly this has been fixed in the later versions
                // of MSVC, instead of somehow passing the version of MSVC
                // Clang is targeting, we will just assume that Clang 11
                // and later are used with a sufficiently new version of
                // MSVC.
                //

                if      (mj >= 13)                            o = "-std=c++2b";
                else if (mj == 10 &&
                         latest && tt.system == "win32-msvc") o = "-std=c++17";
                else if (mj >= 5)                             o = "-std=c++2a";
                else if (mj >  3 || (mj == 3 && mi >= 5))     o = "-std=c++1z";
                else if (mj == 3 && mi >= 4)                  o = "-std=c++1y";
                else     /* ??? */                            o = "-std=c++0x";

                break;
              }
            case compiler_type::icc:
              {
                if      (mj >= 17)                         o = "-std=c++1z";
                else if (mj >  15 || (mj == 15 && p >= 3)) o = "-std=c++1y";
                else    /* ??? */                          o = "-std=c++0x";

                break;
              }
            default:
              assert (false);
            }
          }
          else if (v == nullptr)
            ;
          else
          {
            // Translate 11 to 0x, 14 to 1y, 17 to 1z, 20 to 2a, and 23 to 2b
            // for compatibility with older versions of the compilers.
            //
            o = "-std=";

            if      (*v == "23") o += "c++2b";
            else if (*v == "20") o += "c++2a";
            else if (*v == "17") o += "c++1z";
            else if (*v == "14") o += "c++1y";
            else if (*v == "11") o += "c++0x";
            else if (*v == "03") o += "c++03";
            else if (*v == "98") o += "c++98";
            else o += *v; // In case the user specifies `gnu++NN` or some such.
          }

          if (!o.empty ())
            prepend (move (o));

          break;
        }
      }

      if (experimental)
      {
        switch (ct)
        {
        case compiler_type::msvc:
          {
            // Starting with 15.5 (19.12) Visual Studio-created projects
            // default to the strict mode. However, this flag currently tends
            // to trigger too many compiler bugs. So for now we leave it to
            // the experimenters to enjoy.
            //
            if (mj > 19 || (mj == 19 && mi >= 12))
              prepend ("/permissive-");

            break;
          }
        default:
          break;
        }

        // Unless disabled by the user, try to enable C++ modules.
        //
        if (!modules.value || *modules.value)
        {
          switch (ct)
          {
          case compiler_type::msvc:
            {
              // While modules are supported in VC 15.0 (19.10), there is a
              // bug in the separate interface/implementation unit support
              // which makes them pretty much unusable. This has been fixed in
              // 15.3 (19.11). And 15.5 (19.12) supports the `export module
              // M;` syntax. And 16.4 (19.24) supports the global module
              // fragment. And in 16.8 all the modules-related options have
              // been changed. Seeing that the whole thing is unusable anyway,
              // we disable it for 16.8 or later for now.
              //
              if ((mj > 19 || (mj == 19 && mi >= (modules.value ? 10 : 12))) &&
                  (mj < 19 || (mj == 19 && mi < 28) || modules.value))
              {
                prepend (
                  mj > 19  || mi >= 24     ?
                  "/D__cpp_modules=201810" : // p1103 (merged modules)
                  mj == 19 || mi >= 12     ?
                  "/D__cpp_modules=201704" : // p0629r0 (export module M;)
                  "/D__cpp_modules=201703"); // n4647   (       module M;)

                prepend ("/experimental:module");
                modules = true;
              }
              break;
            }
          case compiler_type::gcc:
            {
              // We use the module mapper support which is only available
              // since GCC 11. And since we are not yet capable of supporting
              // generated headers via the mapper, we require the user to
              // explicitly request modules.
              //
              if (mj >= 11 && modules.value)
              {
                // Defines __cpp_modules:
                //
                // 11 -- 201810
                //
                prepend ("-fmodules-ts");
                modules = true;
              }

              break;
            }
          case compiler_type::clang:
            {
              // At the time of this writing, support for C++20 modules in
              // Clang is incomplete. And starting with Clang 9 (Apple Clang
              // 11.0.3), they are enabled by default in the C++2a mode which
              // breaks the way we set things up for partial preprocessing;
              // see this post for details:
              //
              // http://lists.llvm.org/pipermail/cfe-dev/2019-October/063637.html
              //
              // As a result, for now, we only enable modules if forced with
              // explicit cxx.features.modules=true.
              //
              // Also see Clang modules support hack in cc::compile.
              //
              if (modules.value)
              {
                prepend ("-D__cpp_modules=201704"); // p0629r0
                mode.push_back ("-fmodules-ts"); // For the hack to work.
                modules = true;
              }

              break;
            }
          case compiler_type::icc:
            break; // No modules support yet.
          }
        }
      }

      set_feature (modules);
      //set_feature (concepts);
    }

    static const char* const hinters[] = {"c", 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 ("cxx::guess_init");
      l5 ([&]{trace << "for " << bs;});

      // We only support root loading (which means there can only be one).
      //
      if (rs != bs)
        fail (loc) << "cxx.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::cxx,

        "cxx",
        "c++",
        BUILD2_DEFAULT_CXX,
        ".ii",

        hinters,

        vp["bin.binless"],

        // NOTE: remember to update documentation if changing anything here.
        //
        vp.insert<strings> ("config.cxx"),
        vp.insert<string>  ("config.cxx.id"),
        vp.insert<string>  ("config.cxx.version"),
        vp.insert<string>  ("config.cxx.target"),
        vp.insert<string>  ("config.cxx.std"),
        vp.insert<strings> ("config.cxx.poptions"),
        vp.insert<strings> ("config.cxx.coptions"),
        vp.insert<strings> ("config.cxx.loptions"),
        vp.insert<strings> ("config.cxx.aoptions"),
        vp.insert<strings> ("config.cxx.libs"),

        // Project's internal scope.
        //
        // A header search path (-I) exported by a library that is outside of
        // the internal scope is considered external and, if supported by the
        // compiler, the corresponding -I option is translated to an
        // appropriate "external header search path" option (-isystem for
        // GCC/Clang, /external:I for MSVC 16.10 and later or clang-cl 13 and
        // later). In particular, this suppresses compiler warnings in such
        // external headers (/external:W0 is automatically added unless a
        // custom /external:Wn is specified).
        //
        // The internal scope can be specified by the project with the
        // cxx.internal.scope variable and overridden by the user with the
        // config.cxx.internal.scope variable. Note that cxx.internal.scope
        // must be specified before loading the cxx module (cxx.config, more
        // precisely) and after which it contains the effective value (see
        // below). For example:
        //
        //   # root.build
        //
        //   cxx.internal.scope = current
        //
        //   using cxx
        //
        // Valid values for cxx.internal.scope are:
        //
        //   current  -- current root scope (where variable is assigned)
        //   base     -- target's base scope
        //   root     -- target's root scope
        //   bundle   -- target's bundle amalgamation (see scope::bundle_root())
        //   strong   -- target's strong amalgamation (see scope::strong_root())
        //   weak     -- target's weak amalgamation (see scope::weak_root())
        //   global   -- global scope (everything is internal)
        //
        // Valid values for config.cxx.internal.scope are the same except for
        // `current`.
        //
        // Note also that there are [config.]cc.internal.scope variables that
        // can be used to specify the internal scope for all the cc-based
        // modules.
        //
        // The project's effective internal scope is chosen based on the
        // following priority list:
        //
        // 1. config.cxx.internal.scope
        //
        // 2. config.cc.internal.scope
        //
        // 3. effective scope from bundle amalgamation
        //
        // 4. cxx.internal.scope
        //
        // 5. cc.internal.scope
        //
        // In particular, item #3 allows an amalgamation that bundles a
        // project to override its internal scope.
        //
        // The recommended value for a typical project is `current`, meaning
        // that only headers inside the project will be considered internal.
        // The tests subproject, if present, will inherit its value from the
        // project (which acts as a bundle amalgamation), unless it is being
        // built out of source (for example, to test an installed library).
        //
        // A project can also whitelist specific libraries using the
        // cxx.internal.libs variable. If a library target name (that is, the
        // name inside lib{}) matches any of the wildcard patterns listed in
        // this variable, then the library is considered internal regardless
        // of its location. For example (notice that the pattern is quoted):
        //
        //   # root.build
        //
        //   cxx.internal.scope = current
        //   cxx.internal.libs = foo 'bar-*'
        //
        //   using cxx
        //
        // Note that this variable should also be set before loading the
        // cxx module and there is the common cc.internal.libs equivalent.
        // However, there are no config.* versions nor the override by the
        // bundle amalgamation semantics.
        //
        // Typically you would want to whitelist libraries that are developed
        // together but reside in separate build system projects. In
        // particular, a separate *-tests project for a library should
        // whitelist the library being tested if the internal scope
        // functionality is in use. Another reason to whitelist is to catch
        // warnings in instantiations of templates that belong to a library
        // that is otherwise warning-free (see the MSVC /external:templates-
        // option for background).
        //
        // Note also that if multiple libraries are installed into the same
        // location (or otherwise share the same header search paths, for
        // example, as a family of libraries), then the whitelist may not
        // be effective.
        //
        vp.insert<string> ("config.cxx.internal.scope"),

        // Headers and header groups whose inclusion should or should not be
        // translated to the corresponding header unit imports.
        //
        // A header can be specified either as an absolute and normalized path
        // or as a <>-style include file or file pattern (for example,
        // <vector>, <boost/**.hpp>). The latter kind is automatically
        // resolved to the absolute form based on the compiler's system (as
        // opposed to project's) header search paths.
        //
        // Currently recognized header groups are:
        //
        // std-importable -- translate importable standard library headers
        // std            -- translate all standard library headers
        // all-importable -- translate all importable headers
        // all            -- translate all headers
        //
        // Note that a header may belong to multiple groups which are looked
        // up from the most to least specific, for example: <vector>,
        // std-importable, std, all-importable, all.
        //
        // A header or group can also be excluded from being translated, for
        // example:
        //
        //   std-importable <vector>@false
        //
        // The config.cxx.translate_include value is prepended (merged with
        // override) into cxx.translate_include while loading the cxx.config
        // module. The headers and header groups in cxx.translate_include are
        // resolved while loading the cxx module. For example:
        //
        //   cxx.translate_include = <map>@false  # Can be overriden.
        //   using cxx.config
        //   cxx.translate_include =+ <set>@false # Cannot be overriden.
        //   using cxx
        //
        &vp.insert<cc::translatable_headers> ("config.cxx.translate_include"),

        vp.insert<process_path_ex> ("cxx.path"),
        vp.insert<strings>         ("cxx.mode"),
        vp.insert<path>            ("cxx.config.path"),
        vp.insert<strings>         ("cxx.config.mode"),
        vp.insert<dir_paths>       ("cxx.sys_lib_dirs"),
        vp.insert<dir_paths>       ("cxx.sys_hdr_dirs"),

        vp.insert<string>   ("cxx.std"),

        vp.insert<strings>  ("cxx.poptions"),
        vp.insert<strings>  ("cxx.coptions"),
        vp.insert<strings>  ("cxx.loptions"),
        vp.insert<strings>  ("cxx.aoptions"),
        vp.insert<strings>  ("cxx.libs"),

        vp.insert<string>  ("cxx.internal.scope"),
        vp.insert<strings> ("cxx.internal.libs"),

        &vp.insert<cc::translatable_headers> ("cxx.translate_include"),

        vp["cc.poptions"],
        vp["cc.coptions"],
        vp["cc.loptions"],
        vp["cc.aoptions"],
        vp["cc.libs"],

        vp.insert<strings>      ("cxx.export.poptions"),
        vp.insert<strings>      ("cxx.export.coptions"),
        vp.insert<strings>      ("cxx.export.loptions"),
        vp.insert<vector<name>> ("cxx.export.libs"),
        vp.insert<vector<name>> ("cxx.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["cc.pkconfig.include"],
        vp["cc.pkconfig.lib"],

        vp.insert<string> ("cxx.stdlib"),

        vp["cc.runtime"],
        vp["cc.stdlib"],

        vp["cc.type"],
        vp["cc.system"],
        vp["cc.module_name"],
        vp["cc.importable"],
        vp["cc.reprocess"],

        // Ability to signal that source is already (partially) preprocessed.
        // Valid values are 'none' (not preprocessed), 'includes' (no #include
        // directives in source), 'modules' (as above plus no module
        // declaration depends on preprocessor, e.g., #ifdef, etc), and 'all'
        // (the source is fully preprocessed). Note that for 'all' the source
        // can still contain comments and line continuations. Note also that
        // for some compilers (e.g., VC) there is no way to signal that the
        // source is already preprocessed.
        //
        // What about header unit imports? Well, they are in a sense
        // standardized precompiled headers so we treat them as includes.
        //
        vp.insert<string>   ("cxx.preprocessed"),

        nullptr, // cxx.features.symexport (set in init() below).

        vp.insert<string>   ("cxx.id"),
        vp.insert<string>   ("cxx.id.type"),
        vp.insert<string>   ("cxx.id.variant"),

        vp.insert<string>   ("cxx.class"),

        &vp.insert<string>   ("cxx.version"),
        &vp.insert<uint64_t> ("cxx.version.major"),
        &vp.insert<uint64_t> ("cxx.version.minor"),
        &vp.insert<uint64_t> ("cxx.version.patch"),
        &vp.insert<string>   ("cxx.version.build"),

        &vp.insert<string>   ("cxx.variant_version"),
        &vp.insert<uint64_t> ("cxx.variant_version.major"),
        &vp.insert<uint64_t> ("cxx.variant_version.minor"),
        &vp.insert<uint64_t> ("cxx.variant_version.patch"),
        &vp.insert<string>   ("cxx.variant_version.build"),

        vp.insert<string>   ("cxx.signature"),
        vp.insert<string>   ("cxx.checksum"),

        vp.insert<string>   ("cxx.pattern"),

        vp.insert<target_triplet> ("cxx.target"),

        vp.insert<string>   ("cxx.target.cpu"),
        vp.insert<string>   ("cxx.target.vendor"),
        vp.insert<string>   ("cxx.target.system"),
        vp.insert<string>   ("cxx.target.version"),
        vp.insert<string>   ("cxx.target.class")
      };

      // Alias some cc. variables as cxx.
      //
      vp.insert_alias (d.c_runtime,     "cxx.runtime");
      vp.insert_alias (d.c_module_name, "cxx.module_name");
      vp.insert_alias (d.c_importable,  "cxx.importable");

      vp.insert_alias (d.c_pkgconfig_include, "cxx.pkgconfig.include");
      vp.insert_alias (d.c_pkgconfig_lib,     "cxx.pkgconfig.lib");

      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 ("cxx::config_init");
      l5 ([&]{trace << "for " << bs;});

      // We only support root loading (which means there can only be one).
      //
      if (rs != bs)
        fail (loc) << "cxx.config module must be loaded in project root";

      // Load cxx.guess and share its module instance as ours.
      //
      extra.module = load_module (rs, rs, "cxx.guess", loc, extra.hints);
      extra.module_as<config_module> ().init (rs, loc, extra.hints);

      return true;
    }

    static const target_type* const hdr[] =
    {
      &hxx::static_type,
      &ixx::static_type,
      &txx::static_type,
      &mxx::static_type,
      nullptr
    };

    static const target_type* const inc[] =
    {
      &hxx::static_type,
      &h::static_type,
      &ixx::static_type,
      &txx::static_type,
      &mxx::static_type,
      &cxx::static_type,
      &c::static_type,
      nullptr
    };

    bool
    init (scope& rs,
          scope& bs,
          const location& loc,
          bool,
          bool,
          module_init_extra& extra)
    {
      tracer trace ("cxx::init");
      l5 ([&]{trace << "for " << bs;});

      // We only support root loading (which means there can only be one).
      //
      if (rs != bs)
        fail (loc) << "cxx module must be loaded in project root";

      // Load cxx.config.
      //
      auto& cm (
        load_module<config_module> (rs, rs, "cxx.config", loc, extra.hints));

      auto& vp (rs.var_pool ());

      bool modules (cast<bool> (rs["cxx.features.modules"]));

      bool symexport (false);
      if (modules)
      {
        auto& var (vp.insert<bool> ("cxx.features.symexport"));
        symexport = cast_false<bool> (rs[var]);
        cm.x_symexport = &var;
      }

      cc::data d {
        cm,

        "cxx.compile",
        "cxx.link",
        "cxx.install",

        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,

        modules,
        symexport,

        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,

        cxx::static_type,
        modules ? &mxx::static_type : nullptr,
        hdr,
        inc
      };

      auto& m (extra.set_module (new module (move (d), rs)));
      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.

      {"cxx.guess",  nullptr, guess_init},
      {"cxx.config", nullptr, config_init},
      {"cxx",        nullptr, init},
      {nullptr,      nullptr, nullptr}
    };

    const module_functions*
    build2_cxx_load ()
    {
      return mod_functions;
    }
  }
}