aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-06-01 15:18:08 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-06-01 15:18:08 +0200
commitf36d3e02033ff2f9d14cb20a6d338c8bb09a3962 (patch)
tree94d610b8f7a344aa200f1e9165f5ec18fb7c1959
parenta8b6e14fdce2c950a42cc007e0413c1635161fce (diff)
Implement module interface unit compilation for Clang and VC
-rw-r--r--build2/algorithm.cxx27
-rw-r--r--build2/algorithm.hxx8
-rw-r--r--build2/cc/compile.cxx247
-rw-r--r--build2/cc/link.cxx16
-rw-r--r--build2/target.hxx2
5 files changed, 221 insertions, 79 deletions
diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx
index 156a209..8f555dd 100644
--- a/build2/algorithm.cxx
+++ b/build2/algorithm.cxx
@@ -244,6 +244,33 @@ namespace build2
sched.resume (t.task_count);
}
+ target_lock
+ add_adhoc_member (action a, target& t, const target_type& tt, const char* s)
+ {
+ string n (t.name);
+ if (s != nullptr)
+ {
+ n += '.';
+ n += s;
+ }
+
+ const target& m (t.member != nullptr // Might already be there.
+ ? *t.member
+ : search (t, tt, t.dir, t.out, n));
+
+ target_lock l (lock (a, m));
+ assert (l.target != nullptr); // Someone messing with ad hoc members?
+
+ if (t.member == nullptr)
+ t.member = l.target;
+ else
+ // Basic sanity check.
+ //
+ assert (t.member->type () == tt && t.member->name == n);
+
+ return l;
+ };
+
// Return the matching rule and the recipe action.
//
pair<const pair<const string, reference_wrapper<const rule>>*, action>
diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx
index 36e5a92..d002e23 100644
--- a/build2/algorithm.hxx
+++ b/build2/algorithm.hxx
@@ -120,6 +120,14 @@ namespace build2
target_lock
lock (action, const target&);
+ // Add an ad hoc member. If the suffix is specified, it is added (as an
+ // extension) to the member's target name. Return the locked member target.
+ //
+ target_lock
+ add_adhoc_member (action, target&,
+ const target_type&,
+ const char* suffix = nullptr);
+
// Match and apply a rule to the action/target with ambiguity detection.
// Increment the target's dependents count, which means that you should call
// this function with the intent to also call execute(). Return the target
diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx
index b0b7568..d5566b8 100644
--- a/build2/cc/compile.cxx
+++ b/build2/cc/compile.cxx
@@ -393,65 +393,89 @@ namespace build2
// Derive file name from target name.
//
- string e; // In case of a module, this is the object file extension.
-
- if (tsys == "win32-msvc")
+ string e; // Primary target extension (module or object).
{
- switch (ct)
+ const char* o ("o"); // Object extension (.o or .obj).
+
+ if (tsys == "win32-msvc")
{
- case otype::e: e = "exe."; break;
- case otype::a: e = "lib."; break;
- case otype::s: e = "dll."; break;
+ switch (ct)
+ {
+ case otype::e: e = "exe."; break;
+ case otype::a: e = "lib."; break;
+ case otype::s: e = "dll."; break;
+ }
+ o = "obj";
}
- }
- else if (tsys == "mingw32")
- {
- switch (ct)
+ else if (tsys == "mingw32")
{
- case otype::e: e = "exe."; break;
- case otype::a: e = "a."; break;
- case otype::s: e = "dll."; break;
+ switch (ct)
+ {
+ case otype::e: e = "exe."; break;
+ case otype::a: e = "a."; break;
+ case otype::s: e = "dll."; break;
+ }
}
- }
- else if (tsys == "darwin")
- {
- switch (ct)
+ else if (tsys == "darwin")
{
- case otype::e: e = ""; break;
- case otype::a: e = "a."; break;
- case otype::s: e = "dylib."; break;
+ switch (ct)
+ {
+ case otype::e: e = ""; break;
+ case otype::a: e = "a."; break;
+ case otype::s: e = "dylib."; break;
+ }
}
- }
- else
- {
- switch (ct)
+ else
{
- case otype::e: e = ""; break;
- case otype::a: e = "a."; break;
- case otype::s: e = "so."; break;
+ switch (ct)
+ {
+ 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:
+ switch (cid)
{
- if (mod) e += "pcm.";
- e += "o";
- break;
+ case compiler_id::gcc:
+ {
+ e += mod ? "nms" : o;
+ break;
+ }
+ case compiler_id::clang:
+ {
+ e += mod ? "pcm" : o;
+ break;
+ }
+ case compiler_id::msvc:
+ case compiler_id::icc:
+ {
+ e += mod ? "ifc" : o;
+ break;
+ }
}
- case compiler_id::msvc:
- case compiler_id::icc:
+
+ // If we are compiling a module, then the obj*{} is an ad hoc member
+ // of bmi*{}.
+ //
+ if (mod)
{
- if (mod) e += "ifc.";
- e += (tsys == "win32-msvc" ? "obj" : "o");
- break;
+ const target_type* tt (nullptr);
+
+ switch (ct)
+ {
+ case otype::e: tt = &obje::static_type; break;
+ case otype::a: tt = &obja::static_type; break;
+ case otype::s: tt = &objs::static_type; break;
+ }
+
+ // The module interface unit can be the same as an implementation
+ // (e.g., foo.mxx and foo.cxx) which means obj*{} targets could
+ // collide. So we add the module extension to the target name.
+ //
+ target_lock obj (add_adhoc_member (act, t, *tt, e.c_str ()));
+ obj.target->as<file> ().derive_path (o);
+ match_recipe (obj, group_recipe); // Set recipe and unlock.
}
}
@@ -2335,7 +2359,6 @@ namespace build2
perform_update (action act, const target& xt) const
{
const file& t (xt.as<file> ());
- const path& tp (t.path ());
match_data md (move (t.data<match_data> ()));
bool mod (md.mod);
@@ -2366,9 +2389,22 @@ namespace build2
// Translate paths to relative (to working directory) ones. This results
// in easier to read diagnostics.
//
- path relo (relative (tp));
path rels (relative (s.path ()));
+ // If we are building a module, then the target is bmi*{} and its ad hoc
+ // member is obj*{}.
+ //
+ path relo, relm;
+ if (mod)
+ {
+ relm = relative (t.path ());
+ relo = relative (t.member->is_a<file> ()->path ());
+ }
+ else
+ relo = relative (t.path ());
+
+ // Build command line.
+ //
if (md.pp != preprocessed::all)
{
append_options (args, t, c_poptions);
@@ -2392,6 +2428,7 @@ namespace build2
append_options (args, tstd);
string out, out1; // Storage.
+ size_t out_i (0); // Index of the -o option.
if (cid == compiler_id::msvc)
{
@@ -2476,6 +2513,13 @@ namespace build2
args.push_back (out.c_str ());
}
+ if (mod)
+ {
+ args.push_back ("/module:interface");
+ args.push_back ("/module:output");
+ args.push_back (relm.string ().c_str ());
+ }
+
// Note: no way to indicate that the source if already preprocessed.
args.push_back ("/c"); // Compile only.
@@ -2492,13 +2536,46 @@ namespace build2
args.push_back ("-fPIC");
}
- args.push_back ("-o");
- args.push_back (relo.string ().c_str ());
-
- args.push_back ("-c");
-
// Note: the order of the following options is relied upon below.
//
+ out_i = args.size (); // Index of the -o option.
+
+ if (mod)
+ {
+ switch (cid)
+ {
+ case compiler_id::gcc:
+ {
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
+ //@@ MOD: specify module output file.
+ args.push_back ("-c");
+ break;
+ }
+ case compiler_id::clang:
+ {
+ args.push_back ("-o");
+ args.push_back (relm.string ().c_str ());
+ args.push_back ("--precompile");
+
+ // These should become the default at some point.
+ //
+ args.push_back ("-Xclang"); args.push_back ("-fmodules-codegen");
+ args.push_back ("-Xclang"); args.push_back ("-fmodules-debuginfo");
+ break;
+ }
+ case compiler_id::msvc:
+ case compiler_id::icc:
+ assert (false);
+ }
+ }
+ else
+ {
+ args.push_back ("-o");
+ args.push_back (relo.string ().c_str ());
+ args.push_back ("-c");
+ }
+
args.push_back ("-x");
args.push_back (langopt (md));
@@ -2640,17 +2717,6 @@ namespace build2
if (!pr.wait ())
throw failed ();
-
- if (psrc && verb >= 3)
- md.psrc = auto_rmfile (move (rels));
-
- // Should we go to the filesystem and get the new mtime? We
- // know the file has been modified, so instead just use the
- // current clock time. It has the advantage of having the
- // subseconds precision.
- //
- t.mtime (system_clock::now ());
- return target_state::changed;
}
catch (const process_error& e)
{
@@ -2665,6 +2731,59 @@ namespace build2
throw failed ();
}
+
+ if (psrc && verb >= 3)
+ md.psrc = auto_rmfile (move (rels));
+
+ // Clang's module compilation requires two separate compiler
+ // invocations.
+ //
+ if (mod && cid == compiler_id::clang)
+ {
+ // Remove the target file if this fails. If we don't do that, we will
+ // end up with a broken build that is up-to-date.
+ //
+ auto_rmfile rm (relm);
+
+ // Adjust the command line. First discard everything after -o then
+ // build the new "tail".
+ //
+ args.resize (out_i + 1);
+ args.push_back (relo.string ().c_str ()); // Produce .o.
+ args.push_back ("-c"); // By compiling .pcm.
+ args.push_back ("-Wno-unused-command-line-argument"); //@@ MOD (-I).
+ args.push_back (relm.string ().c_str ());
+ args.push_back (nullptr);
+
+ if (verb >= 2)
+ print_process (args);
+
+ try
+ {
+ process pr (cpath, args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e;
+
+ if (e.child)
+ exit (1);
+
+ throw failed ();
+ }
+
+ rm.cancel ();
+ }
+
+ // Should we go to the filesystem and get the new mtime? We know the
+ // file has been modified, so instead just use the current clock time.
+ // It has the advantage of having the subseconds precision.
+ //
+ t.mtime (system_clock::now ());
+ return target_state::changed;
}
target_state compile::
diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx
index 0d2a14f..9bbe677 100644
--- a/build2/cc/link.cxx
+++ b/build2/cc/link.cxx
@@ -314,21 +314,7 @@ namespace build2
//
auto add_adhoc = [act, &bs] (target& t, const char* type) -> target_lock
{
- const target_type& tt (*bs.find_target_type (type));
-
- const target& m (t.member != nullptr // Might already be there.
- ? *t.member
- : search (t, tt, t.dir, t.out, t.name));
-
- target_lock l (lock (act, m));
- assert (l.target != nullptr); // Someone messing with adhoc members?
-
- if (t.member == nullptr)
- t.member = l.target;
- else
- assert (t.member->type () == tt);
-
- return l;
+ return add_adhoc_member (act, t, *bs.find_target_type (type));
};
{
diff --git a/build2/target.hxx b/build2/target.hxx
index 19c8946..d4def6e 100644
--- a/build2/target.hxx
+++ b/build2/target.hxx
@@ -228,6 +228,8 @@ namespace build2
// - Member variable lookup skips the ad hoc group (since the group is
// the first member, this is normally what we want).
//
+ // Use add_adhoc_member() from algorithms to add an ad hoc member.
+ //
const_ptr<target> member = nullptr;
bool