From 9ae4897cfe935598333a5f709e967fefc4c161aa Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 31 May 2017 16:35:50 +0200 Subject: C++ modules work: add target types --- build2/bin/init.cxx | 9 ++++ build2/bin/rule.cxx | 14 ++++++ build2/bin/rule.hxx | 12 +++++ build2/bin/target.cxx | 135 ++++++++++++++++++++++++++++++++------------------ build2/bin/target.hxx | 61 +++++++++++++++++++++++ build2/c/init.cxx | 3 +- build2/cc/common.hxx | 10 ++-- build2/cc/compile.cxx | 117 +++++++++++++++++++++++++++++++------------ build2/cc/link.cxx | 2 +- build2/cc/module.cxx | 33 +++++++++--- build2/cc/utility.hxx | 2 +- build2/cc/utility.ixx | 14 ++++-- build2/cxx/init.cxx | 11 ++-- build2/cxx/target.cxx | 13 +++++ build2/cxx/target.hxx | 15 ++++++ 15 files changed, 346 insertions(+), 105 deletions(-) (limited to 'build2') diff --git a/build2/bin/init.cxx b/build2/bin/init.cxx index 71c77b2..71c580b 100644 --- a/build2/bin/init.cxx +++ b/build2/bin/init.cxx @@ -25,6 +25,7 @@ namespace build2 namespace bin { static const obj_rule obj_; + static const bmi_rule bmi_; static const lib_rule lib_; // Default config.bin.*.lib values. @@ -362,6 +363,11 @@ namespace build2 t.insert (); t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); + t.insert (); t.insert (); t.insert (); @@ -419,6 +425,9 @@ namespace build2 r.insert (perform_update_id, "bin.obj", obj_); r.insert (perform_clean_id, "bin.obj", obj_); + r.insert (perform_update_id, "bin.bmi", bmi_); + r.insert (perform_clean_id, "bin.bmi", bmi_); + r.insert (perform_update_id, "bin.lib", lib_); r.insert (perform_clean_id, "bin.lib", lib_); diff --git a/build2/bin/rule.cxx b/build2/bin/rule.cxx index 3aa3c81..b6e5a53 100644 --- a/build2/bin/rule.cxx +++ b/build2/bin/rule.cxx @@ -31,6 +31,20 @@ namespace build2 recipe obj_rule:: apply (action, target&) const {return empty_recipe;} + // bmi + // + match_result bmi_rule:: + match (action a, target& t, const string&) const + { + fail << diag_doing (a, t) << " target group" << + info << "explicitly select bmie{}, bmia{}, or bmis{} member"; + + return false; + } + + recipe bmi_rule:: + apply (action, target&) const {return empty_recipe;} + // lib // // The whole logic is pretty much as if we had our two group members as diff --git a/build2/bin/rule.hxx b/build2/bin/rule.hxx index 7109473..4637479 100644 --- a/build2/bin/rule.hxx +++ b/build2/bin/rule.hxx @@ -26,6 +26,18 @@ namespace build2 apply (action, target&) const override; }; + class bmi_rule: public rule + { + public: + bmi_rule () {} + + virtual match_result + match (action, target&, const string&) const override; + + virtual recipe + apply (action, target&) const override; + }; + class lib_rule: public rule { public: diff --git a/build2/bin/target.cxx b/build2/bin/target.cxx index 962befa..f0e0152 100644 --- a/build2/bin/target.cxx +++ b/build2/bin/target.cxx @@ -19,27 +19,41 @@ namespace build2 extern const char ext_var[] = "extension"; // VC14 rejects constexpr. - template + // obj*{} and bmi*{} member factory. + // + template static pair> - objx_factory (const target_type&, - dir_path dir, - dir_path out, - string n, - optional ext) + m_factory (const target_type&, + dir_path dir, + dir_path out, + string n, + optional ext) { - const obj* g (targets.find (dir, out, n)); + const G* g (targets.find (dir, out, n)); - T* x (new T (move (dir), move (out), move (n))); - x->group = g; + M* m (new M (move (dir), move (out), move (n))); + m->group = g; - return make_pair (x, move (ext)); + return make_pair (m, move (ext)); } const target_type obje::static_type { "obje", &file::static_type, - &objx_factory, + &m_factory, + &target_extension_var, + &target_pattern_var, + nullptr, + &target_search, // Note: not _file(); don't look for an existing file. + false + }; + + const target_type bmie::static_type + { + "bmie", + &file::static_type, + &m_factory, &target_extension_var, &target_pattern_var, nullptr, @@ -51,7 +65,19 @@ namespace build2 { "obja", &file::static_type, - &objx_factory, + &m_factory, + &target_extension_var, + &target_pattern_var, + nullptr, + &target_search, // Note: not _file(); don't look for an existing file. + false + }; + + const target_type bmia::static_type + { + "bmia", + &file::static_type, + &m_factory, &target_extension_var, &target_pattern_var, nullptr, @@ -63,7 +89,7 @@ namespace build2 { "objs", &file::static_type, - &objx_factory, + &m_factory, &target_extension_var, &target_pattern_var, nullptr, @@ -71,39 +97,54 @@ namespace build2 false }; + const target_type bmis::static_type + { + "bmis", + &file::static_type, + &m_factory, + &target_extension_var, + &target_pattern_var, + nullptr, + &target_search, // Note: not _file(); don't look for an existing file. + false + }; + + // obj{} and bmi{} group factory. + // + template static pair> - obj_factory (const target_type&, - dir_path dir, - dir_path out, - string n, - optional ext) + g_factory (const target_type&, + dir_path dir, + dir_path out, + string n, + optional ext) { // Casts are MT-aware (during serial load). // - obje* e (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) - : nullptr); - obja* a (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) - : nullptr); - objs* s (phase == run_phase::load - ? const_cast (targets.find (dir, out, n)) - : nullptr); + E* e (phase == run_phase::load + ? const_cast (targets.find (dir, out, n)) + : nullptr); + A* a (phase == run_phase::load + ? const_cast (targets.find (dir, out, n)) + : nullptr); + S* s (phase == run_phase::load + ? const_cast (targets.find (dir, out, n)) + : nullptr); - obj* o (new obj (move (dir), move (out), move (n))); + G* g (new G (move (dir), move (out), move (n))); - if (e != nullptr) e->group = o; - if (a != nullptr) a->group = o; - if (s != nullptr) s->group = o; + if (e != nullptr) e->group = g; + if (a != nullptr) a->group = g; + if (s != nullptr) s->group = g; - return make_pair (o, move (ext)); + return make_pair (g, move (ext)); } const target_type obj::static_type { "obj", &target::static_type, - &obj_factory, + &g_factory, nullptr, nullptr, nullptr, @@ -111,21 +152,17 @@ namespace build2 false }; - template - static pair> - libx_factory (const target_type&, - dir_path dir, - dir_path out, - string n, - optional ext) + const target_type bmi::static_type { - const lib* g (targets.find (dir, out, n)); - - T* x (new T (move (dir), move (out), move (n))); - x->group = g; - - return make_pair (x, move (ext)); - } + "bmi", + &target::static_type, + &g_factory, + nullptr, + nullptr, + nullptr, + &target_search, + false + }; // What extensions should we use? At the outset, this is platform- // dependent. And if we consider cross-compilation, is it build or @@ -141,7 +178,7 @@ namespace build2 { "liba", &file::static_type, - &libx_factory, + &m_factory, &target_extension_var, &target_pattern_var, nullptr, @@ -153,7 +190,7 @@ namespace build2 { "libs", &file::static_type, - &libx_factory, + &m_factory, &target_extension_var, &target_pattern_var, nullptr, diff --git a/build2/bin/target.hxx b/build2/bin/target.hxx index d920cc1..13d7596 100644 --- a/build2/bin/target.hxx +++ b/build2/bin/target.hxx @@ -56,6 +56,67 @@ namespace build2 virtual const target_type& dynamic_type () const {return static_type;} }; + // Binary module interface. + // + // While currently there are only C++ modules, if things pan out, chances + // are we will have C (or Obj-C) modules. And in that case it is plausible + // we will also have some binutils to examine BMIs, similar to objdump, + // etc. So that's why this target type is in bin and not cxx. + // + // bmi*{} is similar to obj*{} though the semantics is a bit different: + // the idea is that we should try hard to re-use a single bmiX{} file for + // an entire "build" but if that's not possible (because the compilation + // options are too different), then compile a private version for + // ourselves (the definition of "too different" is, of course, compiler- + // specific). + // + // When we compile a module interface unit, we end up with bmi*{} and + // obj*{}. How that obj*{} is produced is compiler-dependent. While it + // makes sense to decouple the production of the two in order to increase + // parallelism, doing so will further complicate the already hairy + // organization. So, at least for now, we produce the two at the same time + // and make obj*{} an ad hoc member of bmi*{}. + // + class bmie: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class bmia: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class bmis: public file + { + public: + using file::file; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + + class bmi: public target + { + public: + using target::target; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; + // The lib{} target group. // class liba: public file diff --git a/build2/c/init.cxx b/build2/c/init.cxx index b4b2bca..00482a7 100644 --- a/build2/c/init.cxx +++ b/build2/c/init.cxx @@ -262,13 +262,14 @@ namespace build2 cm.tstd, - false, // No C modules for now. + false, // No C modules yet. cast_null (rs["pkgconfig.path"]), cast (rs[cm.x_sys_lib_dirs]), cast (rs[cm.x_sys_inc_dirs]), c::static_type, + nullptr, // No C modules yet. hdr, inc }; diff --git a/build2/cc/common.hxx b/build2/cc/common.hxx index 155d670..7d520d0 100644 --- a/build2/cc/common.hxx +++ b/build2/cc/common.hxx @@ -120,7 +120,8 @@ namespace build2 const dir_paths& sys_lib_dirs; // x.sys_lib_dirs const dir_paths& sys_inc_dirs; // x.sys_inc_dirs - const target_type& x_src; // Source target type (c{}, cxx{}). + const target_type& x_src; // Source target type (c{}, cxx{}). + const target_type* x_mod; // Module target type (mxx{}), if any. // Array of target types that are considered headers. Keep them in the // most likely to appear order and terminate with NULL. @@ -156,11 +157,12 @@ namespace build2 const process_path& path, const target_triplet& tg, const strings& std, - bool mod, + bool fm, const process_path* pkgc, const dir_paths& sld, const dir_paths& sid, const target_type& src, + const target_type* mod, const target_type* const* hdr, const target_type* const* inc) : config_data (cd), @@ -171,9 +173,9 @@ namespace build2 cid (id), cvar (var), cmaj (mj), cmin (mi), cpath (path), ctg (tg), tsys (ctg.system), tclass (ctg.class_), tstd (std), - modules (mod), + modules (fm), pkgconfig (pkgc), sys_lib_dirs (sld), sys_inc_dirs (sid), - x_src (src), x_hdr (hdr), x_inc (inc) {} + x_src (src), x_mod (mod), x_hdr (hdr), x_inc (inc) {} }; class common: protected data diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx index c7fc3ea..e5f23c4 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile.cxx @@ -56,9 +56,13 @@ namespace build2 struct match_data { explicit - match_data (const prerequisite_member& s) - : src (s), pp (preprocessed::none), mt (timestamp_unknown) {} + match_data (bool m, const prerequisite_member& s) + : mod (m), + src (s), + pp (preprocessed::none), + mt (timestamp_unknown) {} + bool mod; // Target is bmi*{} and src is x_mod. prerequisite_member src; preprocessed pp; auto_rmfile psrc; // Preprocessed source, if any. @@ -73,29 +77,28 @@ namespace build2 { tracer trace (x, "compile::match"); - // @@ TODO: - // - // - check prerequisites: single source file - // - if path already assigned, verify extension? - // + bool mod (t.is_a () || t.is_a () || t.is_a ()); - // Link-up to our group (this is the obj{} target group protocol which - // means this can be done whether we match or not). + // Link-up to our group (this is the obj/bmi{} target group protocol + // which means this can be done whether we match or not). // if (t.group == nullptr) - t.group = targets.find (t.dir, t.out, t.name); + { + const target_type& tt (mod ? bmi::static_type : obj::static_type); + t.group = targets.find (tt, t.dir, t.out, t.name); + } // See if we have a source file. Iterate in reverse so that a source - // file specified for an obj*{} member overrides the one specified for - // the group. Also "see through" groups. + // file specified for a member overrides the one specified for the + // group. Also "see through" groups. // for (prerequisite_member p: reverse_group_prerequisite_members (act, t)) { - if (p.is_a (x_src)) + if (p.is_a (mod ? *x_mod : x_src)) { // Save in the target's auxilary storage. // - t.data (match_data (p)); + t.data (match_data (mod, p)); return true; } } @@ -312,57 +315,82 @@ namespace build2 { tracer trace (x, "compile::apply"); - file& t (xt.as ()); + file& t (xt.as ()); // Either obj*{} or bmi*{}. + match_data& md (t.data ()); + bool mod (md.mod); const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); - otype ct (compile_type (t)); + otype ct (compile_type (t, mod)); lorder lo (link_order (bs, ct)); // Derive file name from target name. // - const char* e (nullptr); + string e; // In case of a module, this is the object file extension. if (tsys == "win32-msvc") { switch (ct) { - case otype::e: e = "exe.obj"; break; - case otype::a: e = "lib.obj"; break; - case otype::s: e = "dll.obj"; break; + case otype::e: e = "exe."; break; + case otype::a: e = "lib."; break; + case otype::s: e = "dll."; break; } } else if (tsys == "mingw32") { switch (ct) { - case otype::e: e = "exe.o"; break; - case otype::a: e = "a.o"; break; - case otype::s: e = "dll.o"; break; + case otype::e: e = "exe."; break; + case otype::a: e = "a."; break; + case otype::s: e = "dll."; break; } } else if (tsys == "darwin") { switch (ct) { - case otype::e: e = "o"; break; - case otype::a: e = "a.o"; break; - case otype::s: e = "dylib.o"; break; + case otype::e: e = ""; break; + case otype::a: e = "a."; break; + case otype::s: e = "dylib."; break; } } else { switch (ct) { - case otype::e: e = "o"; break; - case otype::a: e = "a.o"; break; - case otype::s: e = "so.o"; break; + case otype::e: e = ""; break; + case otype::a: e = "a."; break; + case otype::s: e = "so."; break; + } + } + + switch (cid) + { + case compiler_id::gcc: + { + if (mod) e += "nms."; + e += "o"; + break; + } + case compiler_id::clang: + { + if (mod) e += "pcm."; + e += "o"; + break; + } + case compiler_id::msvc: + case compiler_id::icc: + { + if (mod) e += "ifc."; + e += (tsys == "win32-msvc" ? "obj" : "o"); + break; } } - const path& tp (t.derive_path (e)); + const path& tp (t.derive_path (e.c_str ())); // Inject dependency on the output directory. // @@ -2206,7 +2234,26 @@ namespace build2 throw failed (); } - //@@ TODO: if bmi{}, make sure module_name is not empty. + // Sanity checks. + // + if (modules) + { + // If we are compiling a module interface unit, make sure it has the + // necessary declarations. + // + if (src.is_a (*x_mod)) + { + // VC is not (yet) using the 'export module' syntax so use the + // preprequisite type to distinguish between interface and + // implementation units. + // + if (cid == compiler_id::msvc) + tu.module_interface = true; + + if (tu.module_name.empty () || !tu.module_interface) + fail << src << " is not a module interface unit"; + } + } if (tu.module_name.empty () && tu.module_imports.empty ()) return; @@ -2228,12 +2275,16 @@ namespace build2 { const file& t (xt.as ()); const path& tp (t.path ()); + match_data md (move (t.data ())); + bool mod (md.mod); // While all our prerequisites are already up-to-date, we still have // to execute them to keep the dependency counts straight. // - auto pr (execute_prerequisites (x_src, act, t, md.mt)); + auto pr ( + execute_prerequisites ( + (mod ? *x_mod : x_src), act, t, md.mt)); if (pr.first) { @@ -2246,7 +2297,7 @@ namespace build2 const scope& bs (t.base_scope ()); const scope& rs (*bs.root_scope ()); - otype ct (compile_type (t)); + otype ct (compile_type (t, mod)); lorder lo (link_order (bs, ct)); cstrings args {cpath.recall_string ()}; diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 5c75d34..0d2a14f 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -641,7 +641,7 @@ namespace build2 p1.is_a () || p1.is_a () || p1.is_a () || - (p.is_a (x_src) && x_header (p1)) || + (p.is_a (x_src) && x_header (p1)) || // Includes x_mod. (p.is_a () && p1.is_a ())) continue; diff --git a/build2/cc/module.cxx b/build2/cc/module.cxx index cdc45e5..3755817 100644 --- a/build2/cc/module.cxx +++ b/build2/cc/module.cxx @@ -348,6 +348,8 @@ namespace build2 t.insert (x_src); + // Note: module (x_mod) is in x_hdr. + for (const target_type* const* ht (x_hdr); *ht != nullptr; ++ht) { t.insert (**ht); @@ -376,22 +378,37 @@ namespace build2 r.insert (perform_clean_id, x_compile, cr); r.insert (configure_update_id, x_compile, cr); - r.insert (perform_update_id, x_link, lr); - r.insert (perform_clean_id, x_link, lr); - r.insert (configure_update_id, x_link, lr); - r.insert (perform_update_id, x_compile, cr); r.insert (perform_clean_id, x_compile, cr); r.insert (configure_update_id, x_compile, cr); - r.insert (perform_update_id, x_link, lr); - r.insert (perform_clean_id, x_link, lr); - r.insert (configure_update_id, x_link, lr); - r.insert (perform_update_id, x_compile, cr); r.insert (perform_clean_id, x_compile, cr); r.insert (configure_update_id, x_compile, cr); + if (modules) + { + r.insert (perform_update_id, x_compile, cr); + r.insert (perform_clean_id, x_compile, cr); + r.insert (configure_update_id, x_compile, cr); + + r.insert (perform_update_id, x_compile, cr); + r.insert (perform_clean_id, x_compile, cr); + r.insert (configure_update_id, x_compile, cr); + + r.insert (perform_update_id, x_compile, cr); + r.insert (perform_clean_id, x_compile, cr); + r.insert (configure_update_id, x_compile, cr); + } + + r.insert (perform_update_id, x_link, lr); + r.insert (perform_clean_id, x_link, lr); + r.insert (configure_update_id, x_link, lr); + + r.insert (perform_update_id, x_link, lr); + r.insert (perform_clean_id, x_link, lr); + r.insert (configure_update_id, x_link, lr); + r.insert (perform_update_id, x_link, lr); r.insert (perform_clean_id, x_link, lr); r.insert (configure_update_id, x_link, lr); diff --git a/build2/cc/utility.hxx b/build2/cc/utility.hxx index e0529af..11abf90 100644 --- a/build2/cc/utility.hxx +++ b/build2/cc/utility.hxx @@ -22,7 +22,7 @@ namespace build2 // Compile/link output type. // otype - compile_type (const target&); + compile_type (const target&, bool module); otype link_type (const target&); diff --git a/build2/cc/utility.ixx b/build2/cc/utility.ixx index bc9cd05..b15791a 100644 --- a/build2/cc/utility.ixx +++ b/build2/cc/utility.ixx @@ -7,20 +7,24 @@ namespace build2 namespace cc { inline otype - compile_type (const target& t) + compile_type (const target& t, bool mod) { + using namespace bin; + return - t.is_a () ? otype::e : - t.is_a () ? otype::a : + t.is_a (mod ? bmie::static_type : obje::static_type) ? otype::e : + t.is_a (mod ? bmia::static_type : obja::static_type) ? otype::a : otype::s; } inline otype link_type (const target& t) { + using namespace bin; + return - t.is_a () ? otype::e : - t.is_a () ? otype::a : + t.is_a () ? otype::e : + t.is_a () ? otype::a : otype::s; } } diff --git a/build2/cxx/init.cxx b/build2/cxx/init.cxx index fde12e3..a5422d2 100644 --- a/build2/cxx/init.cxx +++ b/build2/cxx/init.cxx @@ -375,19 +375,21 @@ namespace build2 static const target_type* const hdr[] = { &hxx::static_type, + &h::static_type, &ixx::static_type, &txx::static_type, - &h::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, - &h::static_type, &c::static_type, nullptr }; @@ -414,6 +416,8 @@ namespace build2 if (!cast_false (rs["cxx.config.loaded"])) load_module (rs, rs, "cxx.config", loc, false, hints); + bool modules (cast (rs["cxx.features.modules"])); + config_module& cm (*rs.modules.lookup ("cxx.config")); cc::data d { @@ -433,13 +437,14 @@ namespace build2 cm.tstd, - cast (rs["cxx.features.modules"]), + modules, cast_null (rs["pkgconfig.path"]), cast (rs[cm.x_sys_lib_dirs]), cast (rs[cm.x_sys_inc_dirs]), cxx::static_type, + modules ? &mxx::static_type : nullptr, hdr, inc }; diff --git a/build2/cxx/target.cxx b/build2/cxx/target.cxx index 7fb9971..5641e7a 100644 --- a/build2/cxx/target.cxx +++ b/build2/cxx/target.cxx @@ -63,5 +63,18 @@ namespace build2 &file_search, false }; + + extern const char mxx_ext_def[] = "mxx"; + const target_type mxx::static_type + { + "mxx", + &cc::static_type, + &target_factory, + &target_extension_var, + &target_pattern_var, + nullptr, + &file_search, + false + }; } } diff --git a/build2/cxx/target.hxx b/build2/cxx/target.hxx index 4dd93e3..f5591d1 100644 --- a/build2/cxx/target.hxx +++ b/build2/cxx/target.hxx @@ -57,6 +57,21 @@ namespace build2 static const target_type static_type; virtual const target_type& dynamic_type () const {return static_type;} }; + + // The module interface unit is both like a header (e.g., we need to + // install it) and like a source (we need to compile it). Plus, to + // support dual use (modules/headers) it could actually be #include'd + // (and in both cases). + // + class mxx: public cc::cc + { + public: + using cc::cc; + + public: + static const target_type static_type; + virtual const target_type& dynamic_type () const {return static_type;} + }; } } -- cgit v1.1