aboutsummaryrefslogtreecommitdiff
path: root/build/cxx
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2015-07-20 10:38:02 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2015-07-20 10:38:02 +0200
commitc1981e367aafc22389ac0ab506b00e9657c8071c (patch)
tree528b0d00283411de2daf6d0e2163f72f2e741ee9 /build/cxx
parent243da3993c138d33063f633aa3996a8a710ea396 (diff)
Implement support for importing installed libraries
Diffstat (limited to 'build/cxx')
-rw-r--r--build/cxx/compile30
-rw-r--r--build/cxx/compile.cxx (renamed from build/cxx/rule.cxx)586
-rw-r--r--build/cxx/link46
-rw-r--r--build/cxx/link.cxx843
-rw-r--r--build/cxx/module.cxx3
-rw-r--r--build/cxx/rule60
-rw-r--r--build/cxx/utility37
-rw-r--r--build/cxx/utility.cxx29
-rw-r--r--build/cxx/utility.txx35
9 files changed, 1056 insertions, 613 deletions
diff --git a/build/cxx/compile b/build/cxx/compile
new file mode 100644
index 0000000..1cc91ab
--- /dev/null
+++ b/build/cxx/compile
@@ -0,0 +1,30 @@
+// file : build/cxx/compile -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_CXX_COMPILE
+#define BUILD_CXX_COMPILE
+
+#include <build/types>
+#include <build/rule>
+
+namespace build
+{
+ namespace cxx
+ {
+ class compile: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+ };
+ }
+}
+
+#endif // BUILD_CXX_COMPILE
diff --git a/build/cxx/rule.cxx b/build/cxx/compile.cxx
index 9228994..d0e6526 100644
--- a/build/cxx/rule.cxx
+++ b/build/cxx/compile.cxx
@@ -1,19 +1,18 @@
-// file : build/cxx/rule.cxx -*- C++ -*-
+// file : build/cxx/compile.cxx -*- C++ -*-
// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
// license : MIT; see accompanying LICENSE file
-#include <build/cxx/rule>
+#include <build/cxx/compile>
#include <map>
#include <string>
#include <cstddef> // size_t
-#include <cstdlib> // exit
+#include <cstdlib> // exit()
#include <utility> // move()
#include <butl/process>
#include <butl/utility> // reverse_iterate
#include <butl/fdstream>
-#include <butl/optional>
#include <butl/path-map>
#include <build/types>
@@ -26,7 +25,8 @@
#include <build/bin/target>
#include <build/cxx/target>
-#include <build/config/utility>
+#include <build/cxx/utility>
+#include <build/cxx/link>
using namespace std;
using namespace butl;
@@ -37,48 +37,6 @@ namespace build
{
using namespace bin;
- using config::append_options;
-
- static void
- append_std (cstrings& args, target& t, string& opt)
- {
- if (auto val = t["cxx.std"])
- {
- const string& v (val.as<const string&> ());
-
- // Translate 11 to 0x and 14 to 1y for compatibility with
- // older versions of the compiler.
- //
- opt = "-std=c++";
-
- if (v == "11")
- opt += "0x";
- else if (v == "14")
- opt += "1y";
- else
- opt += v;
-
- args.push_back (opt.c_str ());
- }
- }
-
- // Append library options from one of the cxx.export.* variables
- // recursively, prerequisite libraries first.
- //
- static void
- append_lib_options (cstrings& args, target& l, const char* var)
- {
- for (target* t: l.prerequisite_targets)
- {
- if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ())
- append_lib_options (args, *t, var);
- }
-
- append_options (args, l, var);
- }
-
- // compile
- //
match_result compile::
match (action a, target& t, const string&) const
{
@@ -133,31 +91,49 @@ namespace build
? nullptr
: &t.strong_scope ().path ());
+ link::search_paths_cache lib_paths; // Extract lazily.
+
for (prerequisite_member p: group_prerequisite_members (a, t))
{
- target& pt (p.search ());
-
- if (a.operation () == clean_id && !pt.dir.sub (*amlg))
- continue;
-
// A dependency on a library is there so that we can get its
// cxx.export.poptions. In particular, making sure it is
// executed before us will only restrict parallelism. But we
// do need to match it in order to get its prerequisite_targets
// populated; see append_lib_options() above.
//
- if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ())
+ if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ())
{
- // @@ The fact that we match but never execute messes up
- // the dependents count. This is a workaround, not a
- // solution.
- //
if (a.operation () == update_id)
+ {
+ // Handle imported libraries.
+ //
+ if (p.proj () != nullptr)
+ {
+ // We know that for such libraries we don't need to do
+ // match() in order to get options (if any, they would
+ // be set by search_library()).
+ //
+ if (link::search_library (lib_paths, p.prerequisite) != nullptr)
+ continue;
+ }
+
+ target& pt (p.search ());
+
+ // @@ The fact that we match but never execute messes up
+ // the dependents count. This is a workaround, not a
+ // solution.
+ //
build::match (a, pt);
+ }
continue;
}
+ target& pt (p.search ());
+
+ if (a.operation () == clean_id && !pt.dir.sub (*amlg))
+ continue;
+
build::match (a, pt);
t.prerequisite_targets.push_back (&pt);
}
@@ -493,7 +469,7 @@ namespace build
getline (is, l);
if (is.fail () && !is.eof ())
- fail << "io error while parsing g++ -M output";
+ fail << "error reading C++ compiler -M output";
size_t pos (0);
@@ -802,499 +778,5 @@ namespace build
throw failed ();
}
}
-
- // link
- //
- inline link::type link::
- link_type (target& t)
- {
- return t.is_a<exe> () ? type::e : (t.is_a<liba> () ? type::a : type::so);
- }
-
- link::order link::
- link_order (target& t)
- {
- const char* var;
-
- switch (link_type (t))
- {
- case type::e: var = "bin.exe.lib"; break;
- case type::a: var = "bin.liba.lib"; break;
- case type::so: var = "bin.libso.lib"; break;
- }
-
- const list_value& lv (t[var].as<const list_value&> ());
- return lv[0].value == "shared"
- ? lv.size () > 1 && lv[1].value == "static" ? order::so_a : order::so
- : lv.size () > 1 && lv[1].value == "shared" ? order::a_so : order::a;
- }
-
- match_result link::
- match (action a, target& t, const string& hint) const
- {
- tracer trace ("cxx::link::match");
-
- // @@ TODO:
- //
- // - check prerequisites: object files, libraries
- // - if path already assigned, verify extension?
- //
- // @@ Q:
- //
- // - if there is no .o, are we going to check if the one derived
- // from target exist or can be built? A: No.
- // What if there is a library. Probably ok if .a, not if .so.
- // (i.e., a utility library).
- //
-
- bool so (t.is_a<libso> ());
-
- // Scan prerequisites and see if we can work with what we've got.
- //
- bool seen_cxx (false), seen_c (false), seen_obj (false),
- seen_lib (false);
-
- for (prerequisite_member p: group_prerequisite_members (a, t))
- {
- if (p.is_a<cxx> ())
- {
- seen_cxx = seen_cxx || true;
- }
- else if (p.is_a<c> ())
- {
- seen_c = seen_c || true;
- }
- else if (p.is_a<obja> ())
- {
- if (so)
- fail << "shared library " << t << " prerequisite " << p
- << " is static object";
-
- seen_obj = seen_obj || true;
- }
- else if (p.is_a<objso> () ||
- p.is_a<obj> ())
- {
- seen_obj = seen_obj || true;
- }
- else if (p.is_a<liba> () ||
- p.is_a<libso> () ||
- p.is_a<lib> ())
- {
- seen_lib = seen_lib || true;
- }
- else if (p.is_a<h> () ||
- p.is_a<hxx> () ||
- p.is_a<ixx> () ||
- p.is_a<txx> () ||
- p.is_a<fsdir> ())
- ;
- else
- {
- level3 ([&]{trace << "unexpected prerequisite type " << p.type ();});
- return nullptr;
- }
- }
-
- // We will only chain a C source if there is also a C++ source or we
- // were explicitly told to.
- //
- if (seen_c && !seen_cxx && hint < "cxx")
- {
- level3 ([&]{trace << "c prerequisite(s) without c++ or hint";});
- return nullptr;
- }
-
- return seen_cxx || seen_c || seen_obj || seen_lib ? &t : nullptr;
- }
-
- recipe link::
- apply (action a, target& xt, const match_result&) const
- {
- tracer trace ("cxx::link::apply");
-
- path_target& t (static_cast<path_target&> (xt));
-
- type lt (link_type (t));
- bool so (lt == type::so);
- optional<order> lo; // Link-order.
-
- // Derive file name from target name.
- //
- if (t.path ().empty ())
- {
- switch (lt)
- {
- case type::e: t.derive_path ("" ); break;
- case type::a: t.derive_path ("a", "lib"); break;
- case type::so: t.derive_path ("so", "lib"); break;
- }
- }
-
- // Inject dependency on the output directory.
- //
- inject_parent_fsdir (a, t);
-
- // We may need the project roots for rule chaining (see below).
- // We will resolve them lazily only if needed.
- //
- scope* root (nullptr);
- const dir_path* out_root (nullptr);
- const dir_path* src_root (nullptr);
-
- // Process prerequisites: do rule chaining for C and C++ source
- // files as well as search and match.
- //
- // When cleaning, ignore prerequisites that are not in the same
- // or a subdirectory of our strong amalgamation.
- //
- const dir_path* amlg (
- a.operation () != clean_id
- ? nullptr
- : &t.strong_scope ().path ());
-
- for (prerequisite_member p: group_prerequisite_members (a, t))
- {
- bool group (!p.prerequisite.belongs (t)); // Group's prerequisite.
- target* pt (nullptr);
-
- if (!p.is_a<c> () && !p.is_a<cxx> ())
- {
- // The same basic logic as in search_and_match().
- //
- pt = &p.search ();
-
- if (a.operation () == clean_id && !pt->dir.sub (*amlg))
- continue; // Skip.
-
- // If this is the obj{} or lib{} target group, then pick the
- // appropriate member and make sure it is searched and matched.
- //
- if (obj* o = pt->is_a<obj> ())
- {
- pt = so ? static_cast<target*> (o->so) : o->a;
-
- if (pt == nullptr)
- pt = &search (so ? objso::static_type : obja::static_type,
- p.key ());
- }
- else if (lib* l = pt->is_a<lib> ())
- {
- // Determine the library type to link.
- //
- bool lso (true);
- const string& at ((*l)["bin.lib"].as<const string&> ());
-
- if (!lo)
- lo = link_order (t);
-
- switch (*lo)
- {
- case order::a:
- case order::a_so:
- lso = false; // Fall through.
- case order::so:
- case order::so_a:
- {
- if (lso ? at == "static" : at == "shared")
- {
- if (*lo == order::a_so || *lo == order::so_a)
- lso = !lso;
- else
- fail << (lso ? "shared" : "static") << " build of " << *l
- << " is not available";
- }
- }
- }
-
- pt = lso ? static_cast<target*> (l->so) : l->a;
-
- if (pt == nullptr)
- pt = &search (lso ? libso::static_type : liba::static_type,
- p.key ());
- }
-
- build::match (a, *pt);
- t.prerequisite_targets.push_back (pt);
- continue;
- }
-
- if (root == nullptr)
- {
- // Which scope shall we use to resolve the root? Unlikely,
- // but possible, the prerequisite is from a different project
- // altogether. So we are going to use the target's project.
- //
- root = &t.root_scope ();
- out_root = &root->path ();
- src_root = &root->src_path ();
- }
-
- const prerequisite_key& cp (p.key ()); // c(xx){} prerequisite key.
- const target_type& o_type (
- group
- ? obj::static_type
- : (so ? objso::static_type : obja::static_type));
-
- // Come up with the obj*{} target. The c(xx){} prerequisite
- // directory can be relative (to the scope) or absolute. If it is
- // relative, then use it as is. If it is absolute, then translate
- // it to the corresponding directory under out_root. While the
- // c(xx){} directory is most likely under src_root, it is also
- // possible it is under out_root (e.g., generated source).
- //
- dir_path d;
- {
- const dir_path& cpd (*cp.tk.dir);
-
- if (cpd.relative () || cpd.sub (*out_root))
- d = cpd;
- else
- {
- if (!cpd.sub (*src_root))
- fail << "out of project prerequisite " << cp <<
- info << "specify corresponding " << o_type.name << "{} "
- << "target explicitly";
-
- d = *out_root / cpd.leaf (*src_root);
- }
- }
-
- target& ot (search (o_type, d, *cp.tk.name, nullptr, cp.scope));
-
- // If we are cleaning, check that this target is in the same or
- // a subdirectory of our strong amalgamation.
- //
- if (a.operation () == clean_id && !ot.dir.sub (*amlg))
- {
- // If we shouldn't clean obj{}, then it is fair to assume
- // we shouldn't clean cxx{} either (generated source will
- // be in the same directory as obj{} and if not, well, go
- // find yourself another build system ;-)).
- //
- continue; // Skip.
- }
-
- // If we have created the obj{} target group, pick one of its
- // members; the rest would be primarily concerned with it.
- //
- if (group)
- {
- obj& o (static_cast<obj&> (ot));
- pt = so ? static_cast<target*> (o.so) : o.a;
-
- if (pt == nullptr)
- pt = &search (so ? objso::static_type : obja::static_type,
- o.dir, o.name, o.ext, nullptr);
- }
- else
- pt = &ot;
-
- // If this obj*{} target already exists, then it needs to be
- // "compatible" with what we are doing here.
- //
- // This gets a bit tricky. We need to make sure the source files
- // are the same which we can only do by comparing the targets to
- // which they resolve. But we cannot search the ot's prerequisites
- // -- only the rule that matches can. Note, however, that if all
- // this works out, then our next step is to match the obj*{}
- // target. If things don't work out, then we fail, in which case
- // searching and matching speculatively doesn't really hurt.
- //
- bool found (false);
- for (prerequisite_member p1:
- reverse_group_prerequisite_members (a, *pt))
- {
- // Ignore some known target types (fsdir, headers, libraries).
- //
- if (p1.is_a<fsdir> () ||
- p1.is_a<h> () ||
- (p.is_a<cxx> () && (p1.is_a<hxx> () ||
- p1.is_a<ixx> () ||
- p1.is_a<txx> ())) ||
- p1.is_a<lib> () ||
- p1.is_a<liba> () ||
- p1.is_a<libso> ())
- {
- continue;
- }
-
- if (!p1.is_a<cxx> ())
- fail << "synthesized target for prerequisite " << cp
- << " would be incompatible with existing target " << *pt <<
- info << "unexpected existing prerequisite type " << p1 <<
- info << "specify corresponding obj{} target explicitly";
-
- if (!found)
- {
- build::match (a, *pt); // Now p1 should be resolved.
-
- // Searching our own prerequisite is ok.
- //
- if (&p.search () != &p1.search ())
- fail << "synthesized target for prerequisite " << cp << " would "
- << "be incompatible with existing target " << *pt <<
- info << "existing prerequisite " << p1 << " does not match "
- << cp <<
- info << "specify corresponding " << o_type.name << "{} target "
- << "explicitly";
-
- found = true;
- // Check the rest of the prerequisites.
- }
- }
-
- if (!found)
- {
- // Note: add the source to the group, not the member.
- //
- ot.prerequisites.emplace_back (p.as_prerequisite (trace));
-
- // Add our lib*{} prerequisites to the object file (see
- // cxx.export.poptions above for details). Note: no need
- // to go into group members.
- //
- // Initially, we were only adding imported libraries, but
- // there is a problem with this approach: the non-imported
- // library might depend on the imported one(s) which we will
- // never "see" unless we start with this library.
- //
- for (prerequisite& p: group_prerequisites (t))
- {
- if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ())
- ot.prerequisites.emplace_back (p);
- }
-
- build::match (a, *pt);
- }
-
- t.prerequisite_targets.push_back (pt);
- }
-
- switch (a)
- {
- case perform_update_id: return &perform_update;
- case perform_clean_id: return &perform_clean;
- default: return default_recipe; // Forward to prerequisites.
- }
- }
-
- target_state link::
- perform_update (action a, target& xt)
- {
- path_target& t (static_cast<path_target&> (xt));
-
- type lt (link_type (t));
- bool so (lt == type::so);
-
- if (!execute_prerequisites (a, t, t.mtime ()))
- return target_state::unchanged;
-
- // Translate paths to relative (to working directory) ones. This
- // results in easier to read diagnostics.
- //
- path relt (relative (t.path ()));
-
- scope& rs (t.root_scope ());
- cstrings args;
- string storage1;
-
- if (lt == type::a)
- {
- //@@ ranlib
- //
- args.push_back ("ar");
- args.push_back ("-rc");
- args.push_back (relt.string ().c_str ());
- }
- else
- {
- args.push_back (rs["config.cxx"].as<const string&> ().c_str ());
-
- append_options (args, t, "cxx.coptions");
-
- append_std (args, t, storage1);
-
- if (so)
- args.push_back ("-shared");
-
- args.push_back ("-o");
- args.push_back (relt.string ().c_str ());
-
- append_options (args, t, "cxx.loptions");
- }
-
- // Reserve enough space so that we don't reallocate. Reallocating
- // means pointers to elements may no longer be valid.
- //
- paths relo;
- relo.reserve (t.prerequisite_targets.size ());
-
- for (target* pt: t.prerequisite_targets)
- {
- path_target* ppt;
-
- if ((ppt = pt->is_a<obja> ()))
- ;
- else if ((ppt = pt->is_a<objso> ()))
- ;
- else if ((ppt = pt->is_a<liba> ()))
- ;
- else if ((ppt = pt->is_a<libso> ()))
- {
- // Use absolute path for the shared libraries since that's
- // the path the runtime loader will use to try to find it.
- // This is probably temporary until we get into the whole
- // -soname/-rpath mess.
- //
- args.push_back (ppt->path ().string ().c_str ());
- continue;
- }
- else
- continue;
-
- relo.push_back (relative (ppt->path ()));
- args.push_back (relo.back ().string ().c_str ());
- }
-
- if (lt != type::a)
- append_options (args, t, "cxx.libs");
-
- args.push_back (nullptr);
-
- if (verb)
- print_process (args);
- else
- text << "ld " << t;
-
- try
- {
- process pr (args.data ());
-
- if (!pr.wait ())
- throw failed ();
-
- // 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)
- {
- error << "unable to execute " << args[0] << ": " << e.what ();
-
- // In a multi-threaded program that fork()'ed but did not exec(),
- // it is unwise to try to do any kind of cleanup (like unwinding
- // the stack and running destructors).
- //
- if (e.child ())
- exit (1);
-
- throw failed ();
- }
- }
}
}
diff --git a/build/cxx/link b/build/cxx/link
new file mode 100644
index 0000000..75223fc
--- /dev/null
+++ b/build/cxx/link
@@ -0,0 +1,46 @@
+// file : build/cxx/link -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_CXX_LINK
+#define BUILD_CXX_LINK
+
+#include <vector>
+
+#include <butl/optional>
+
+#include <build/types>
+#include <build/rule>
+
+namespace build
+{
+ namespace cxx
+ {
+ class link: public rule
+ {
+ public:
+ virtual match_result
+ match (action, target&, const std::string& hint) const;
+
+ virtual recipe
+ apply (action, target&, const match_result&) const;
+
+ static target_state
+ perform_update (action, target&);
+
+ private:
+ friend class compile;
+
+ using search_paths = std::vector<dir_path>;
+ using search_paths_cache = butl::optional<search_paths>;
+
+ static target*
+ search_library (search_paths_cache&, prerequisite&);
+
+ static search_paths
+ extract_library_paths (scope&);
+ };
+ }
+}
+
+#endif // BUILD_CXX_LINK
diff --git a/build/cxx/link.cxx b/build/cxx/link.cxx
new file mode 100644
index 0000000..f37ef58
--- /dev/null
+++ b/build/cxx/link.cxx
@@ -0,0 +1,843 @@
+// file : build/cxx/link.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build/cxx/link>
+
+#include <vector>
+#include <string>
+#include <cstddef> // size_t
+#include <cstdlib> // exit()
+#include <utility> // move()
+
+#include <butl/process>
+#include <butl/utility> // reverse_iterate
+#include <butl/fdstream>
+#include <butl/optional>
+#include <butl/path-map>
+#include <butl/filesystem>
+
+#include <build/types>
+#include <build/scope>
+#include <build/variable>
+#include <build/algorithm>
+#include <build/diagnostics>
+#include <build/context>
+
+#include <build/bin/target>
+#include <build/cxx/target>
+
+#include <build/cxx/utility>
+
+using namespace std;
+using namespace butl;
+
+namespace build
+{
+ namespace cxx
+ {
+ using namespace bin;
+
+ enum class type {e, a, so};
+ enum class order {a, so, a_so, so_a};
+
+ static inline type
+ link_type (target& t)
+ {
+ return t.is_a<exe> () ? type::e : (t.is_a<liba> () ? type::a : type::so);
+ }
+
+ static order
+ link_order (target& t)
+ {
+ const char* var;
+
+ switch (link_type (t))
+ {
+ case type::e: var = "bin.exe.lib"; break;
+ case type::a: var = "bin.liba.lib"; break;
+ case type::so: var = "bin.libso.lib"; break;
+ }
+
+ const list_value& lv (t[var].as<const list_value&> ());
+ return lv[0].value == "shared"
+ ? lv.size () > 1 && lv[1].value == "static" ? order::so_a : order::so
+ : lv.size () > 1 && lv[1].value == "shared" ? order::a_so : order::a;
+ }
+
+ link::search_paths link::
+ extract_library_paths (scope& bs)
+ {
+ search_paths r;
+ scope& rs (*bs.root_scope ());
+
+ // Extract user-supplied search paths (i.e., -L).
+ //
+ if (auto val = bs["cxx.loptions"])
+ {
+ const list_value& l (val.as<const list_value&> ());
+
+ for (auto i (l.begin ()), e (l.end ()); i != e; ++i)
+ {
+ if (!i->simple ())
+ continue;
+
+ // -L can either be in the -Lfoo or -L foo form.
+ //
+ dir_path d;
+ if (i->value == "-L")
+ {
+ if (++i == e)
+ break; // Let the compiler complain.
+
+ if (i->simple ())
+ d = dir_path (i->value);
+ else if (i->directory ())
+ d = i->dir;
+ else
+ break; // Let the compiler complain.
+ }
+ else if (i->value.compare (0, 2, "-L") == 0)
+ d = dir_path (i->value, 2, string::npos);
+ else
+ continue;
+
+ // Ignore relative paths. Or maybe we should warn?
+ //
+ if (!d.relative ())
+ r.push_back (move (d));
+ }
+ }
+
+ // Extract system search paths.
+ //
+ cstrings args;
+ string std_storage;
+
+ args.push_back (rs["config.cxx"].as<const string&> ().c_str ());
+ append_options (args, bs, "cxx.coptions");
+ append_std (args, bs, std_storage);
+ append_options (args, bs, "cxx.loptions");
+ args.push_back ("-print-search-dirs");
+ args.push_back (nullptr);
+
+ if (verb >= 5)
+ print_process (args);
+
+ string l;
+ try
+ {
+ process pr (args.data (), false, false, true);
+ ifdstream is (pr.in_ofd);
+
+ while (!is.eof ())
+ {
+ string s;
+ getline (is, s);
+
+ if (is.fail () && !is.eof ())
+ fail << "error reading C++ compiler -print-search-dirs output";
+
+ if (s.compare (0, 12, "libraries: =") == 0)
+ {
+ l.assign (s, 12, string::npos);
+ break;
+ }
+ }
+
+ is.close (); // Don't block.
+
+ if (!pr.wait ())
+ throw failed ();
+ }
+ catch (const process_error& e)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+
+ if (l.empty ())
+ fail << "unable to extract C++ compiler system library paths";
+
+ // Now the fun part: figuring out which delimiter is used.
+ // Normally it is ':' but on Windows it is ';' (or can be;
+ // who knows for sure). Also note that these paths are
+ // absolute (or should be). So here is what we are going
+ // to do: first look for ';'. If found, then that's the
+ // delimiter. If not found, then there are two cases:
+ // it is either a single Windows path or the delimiter
+ // is ':'. To distinguish these two cases we check if
+ // the path starts with a Windows drive.
+ //
+ char d (';');
+ string::size_type e (l.find (d));
+
+ if (e == string::npos &&
+ (l.size () < 2 || l[0] == '/' || l[1] != ':'))
+ {
+ d = ':';
+ e = l.find (d);
+ }
+
+ // Now chop it up. We already have the position of the
+ // first delimiter (if any).
+ //
+ for (string::size_type b (0);; e = l.find (d, (b = e + 1)))
+ {
+ r.emplace_back (l, b, (e != string::npos ? e - b : e));
+ r.back ().normalize ();
+
+ if (e == string::npos)
+ break;
+ }
+
+ return r;
+ }
+
+ target* link::
+ search_library (search_paths_cache& spc, prerequisite& p)
+ {
+ tracer trace ("cxx::link::search_library");
+
+ // First check the cache.
+ //
+ if (p.target != nullptr)
+ return p.target;
+
+ bool l (p.is_a<lib> ());
+ const string* ext (l ? nullptr : p.ext); // Only for liba/libso.
+
+ // Then figure out what we need to search for.
+ //
+
+ // liba
+ //
+ path an;
+ const string* ae;
+
+ if (l || p.is_a<liba> ())
+ {
+ an = path ("lib" + p.name);
+
+ // Note that p.scope should be the same as the target's for
+ // which we are looking for this library. The idea here is
+ // that we have to use the same "extension configuration" as
+ // the target's.
+ //
+ ae = ext == nullptr
+ ? &liba::static_type.extension (p.key ().tk, p.scope)
+ : ext;
+
+ if (!ae->empty ())
+ {
+ an += '.';
+ an += *ae;
+ }
+ }
+
+ // libso
+ //
+ path sn;
+ const string* se;
+
+ if (l || p.is_a<libso> ())
+ {
+ sn = path ("lib" + p.name);
+ se = ext == nullptr
+ ? &libso::static_type.extension (p.key ().tk, p.scope)
+ : ext;
+
+ if (!se->empty ())
+ {
+ sn += '.';
+ sn += *se;
+ }
+ }
+
+ // Now search.
+ //
+ if (!spc)
+ spc = extract_library_paths (p.scope);
+
+ liba* a (nullptr);
+ libso* s (nullptr);
+
+ path f; // Reuse the buffer.
+ const dir_path* pd;
+ for (const dir_path& d: *spc)
+ {
+ timestamp mt;
+
+ // liba
+ //
+ if (!an.empty ())
+ {
+ f = d;
+ f /= an;
+
+ text << "trying " << f;
+
+ if ((mt = file_mtime (f)) != timestamp_nonexistent)
+ {
+ // Enter the target. Note that because the search paths are
+ // normalized, the result is automatically normalized as well.
+ //
+ a = &targets.insert<liba> (d, p.name, ae, trace);
+
+ if (a->path ().empty ())
+ a->path (move (f));
+
+ a->mtime (mt);
+ }
+ }
+
+ // libso
+ //
+ if (!sn.empty ())
+ {
+ f = d;
+ f /= sn;
+
+ text << "trying " << f;
+
+ if ((mt = file_mtime (f)) != timestamp_nonexistent)
+ {
+ s = &targets.insert<libso> (d, p.name, se, trace);
+
+ if (s->path ().empty ())
+ s->path (move (f));
+
+ s->mtime (mt);
+ }
+ }
+
+ if (a != nullptr || s != nullptr)
+ {
+ pd = &d;
+ break;
+ }
+ }
+
+ if (a == nullptr && s == nullptr)
+ return nullptr;
+
+ if (l)
+ {
+ // Enter the target group.
+ //
+ lib& l (targets.insert<lib> (*pd, p.name, p.ext, trace));
+
+ // It should automatically link-up to the members we have found.
+ //
+ assert (l.a == a);
+ assert (l.so == s);
+
+ // Set the bin.lib variable to indicate what's available.
+ //
+ const char* bl (a != nullptr
+ ? (s != nullptr ? "both" : "static")
+ : "shared");
+ l.assign ("bin.lib") = bl;
+
+ p.target = &l;
+ }
+ else
+ p.target = p.is_a<liba> () ? static_cast<target*> (a) : s;
+
+ return p.target;
+ }
+
+ match_result link::
+ match (action a, target& t, const string& hint) const
+ {
+ tracer trace ("cxx::link::match");
+
+ // @@ TODO:
+ //
+ // - check prerequisites: object files, libraries
+ // - if path already assigned, verify extension?
+ //
+ // @@ Q:
+ //
+ // - if there is no .o, are we going to check if the one derived
+ // from target exist or can be built? A: No.
+ // What if there is a library. Probably ok if .a, not if .so.
+ // (i.e., a utility library).
+ //
+
+ bool so (t.is_a<libso> ());
+
+ // Scan prerequisites and see if we can work with what we've got.
+ //
+ bool seen_cxx (false), seen_c (false), seen_obj (false),
+ seen_lib (false);
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ if (p.is_a<cxx> ())
+ {
+ seen_cxx = seen_cxx || true;
+ }
+ else if (p.is_a<c> ())
+ {
+ seen_c = seen_c || true;
+ }
+ else if (p.is_a<obja> ())
+ {
+ if (so)
+ fail << "shared library " << t << " prerequisite " << p
+ << " is static object";
+
+ seen_obj = seen_obj || true;
+ }
+ else if (p.is_a<objso> () ||
+ p.is_a<obj> ())
+ {
+ seen_obj = seen_obj || true;
+ }
+ else if (p.is_a<liba> () ||
+ p.is_a<libso> () ||
+ p.is_a<lib> ())
+ {
+ seen_lib = seen_lib || true;
+ }
+ else if (p.is_a<h> () ||
+ p.is_a<hxx> () ||
+ p.is_a<ixx> () ||
+ p.is_a<txx> () ||
+ p.is_a<fsdir> ())
+ ;
+ else
+ {
+ level3 ([&]{trace << "unexpected prerequisite type " << p.type ();});
+ return nullptr;
+ }
+ }
+
+ // We will only chain a C source if there is also a C++ source or we
+ // were explicitly told to.
+ //
+ if (seen_c && !seen_cxx && hint < "cxx")
+ {
+ level3 ([&]{trace << "c prerequisite(s) without c++ or hint";});
+ return nullptr;
+ }
+
+ return seen_cxx || seen_c || seen_obj || seen_lib ? &t : nullptr;
+ }
+
+ recipe link::
+ apply (action a, target& xt, const match_result&) const
+ {
+ tracer trace ("cxx::link::apply");
+
+ path_target& t (static_cast<path_target&> (xt));
+
+ type lt (link_type (t));
+ bool so (lt == type::so);
+ optional<order> lo; // Link-order.
+
+ // Derive file name from target name.
+ //
+ if (t.path ().empty ())
+ {
+ switch (lt)
+ {
+ case type::e: t.derive_path ("" ); break;
+ case type::a: t.derive_path ("a", "lib"); break;
+ case type::so: t.derive_path ("so", "lib"); break;
+ }
+ }
+
+ // Inject dependency on the output directory.
+ //
+ inject_parent_fsdir (a, t);
+
+ // We may need the project roots for rule chaining (see below).
+ // We will resolve them lazily only if needed.
+ //
+ scope* root (nullptr);
+ const dir_path* out_root (nullptr);
+ const dir_path* src_root (nullptr);
+
+ search_paths_cache lib_paths; // Extract lazily.
+
+ // Process prerequisites: do rule chaining for C and C++ source
+ // files as well as search and match.
+ //
+ // When cleaning, ignore prerequisites that are not in the same
+ // or a subdirectory of our strong amalgamation.
+ //
+ const dir_path* amlg (
+ a.operation () != clean_id
+ ? nullptr
+ : &t.strong_scope ().path ());
+
+ for (prerequisite_member p: group_prerequisite_members (a, t))
+ {
+ bool group (!p.prerequisite.belongs (t)); // Group's prerequisite.
+ target* pt (nullptr);
+
+ if (!p.is_a<c> () && !p.is_a<cxx> ())
+ {
+ // Handle imported libraries. Essentially, we want to replicate
+ // the -lfoo functionality but as part of our import support.
+ //
+ if (p.proj () != nullptr &&
+ (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ()))
+ {
+ pt = search_library (lib_paths, p.prerequisite);
+
+ // We only need this target if we are updating (remember, like
+ // -lfoo). The question is why search in the first place? The
+ // reason is the "not found" situation, in which someone else
+ // (i.e., the import phase 2) could resolve it to something
+ // that, who knows, might need cleaning, for example.
+ //
+ if (pt != nullptr && a.operation () != update_id)
+ continue; // Skip.
+ }
+
+ // The rest is the same basic logic as in search_and_match().
+ //
+ if (pt == nullptr) // Could've been resolved by search_library().
+ pt = &p.search ();
+
+ if (a.operation () == clean_id && !pt->dir.sub (*amlg))
+ continue; // Skip.
+
+ // If this is the obj{} or lib{} target group, then pick the
+ // appropriate member and make sure it is searched and matched.
+ //
+ if (obj* o = pt->is_a<obj> ())
+ {
+ pt = so ? static_cast<target*> (o->so) : o->a;
+
+ if (pt == nullptr)
+ pt = &search (so ? objso::static_type : obja::static_type,
+ p.key ());
+ }
+ else if (lib* l = pt->is_a<lib> ())
+ {
+ // Determine the library type to link.
+ //
+ bool lso (true);
+ const string& at ((*l)["bin.lib"].as<const string&> ());
+
+ if (!lo)
+ lo = link_order (t);
+
+ switch (*lo)
+ {
+ case order::a:
+ case order::a_so:
+ lso = false; // Fall through.
+ case order::so:
+ case order::so_a:
+ {
+ if (lso ? at == "static" : at == "shared")
+ {
+ if (*lo == order::a_so || *lo == order::so_a)
+ lso = !lso;
+ else
+ fail << (lso ? "shared" : "static") << " build of " << *l
+ << " is not available";
+ }
+ }
+ }
+
+ pt = lso ? static_cast<target*> (l->so) : l->a;
+
+ if (pt == nullptr)
+ pt = &search (lso ? libso::static_type : liba::static_type,
+ p.key ());
+ }
+
+ build::match (a, *pt);
+ t.prerequisite_targets.push_back (pt);
+ continue;
+ }
+
+ if (root == nullptr)
+ {
+ // Which scope shall we use to resolve the root? Unlikely,
+ // but possible, the prerequisite is from a different project
+ // altogether. So we are going to use the target's project.
+ //
+ root = &t.root_scope ();
+ out_root = &root->path ();
+ src_root = &root->src_path ();
+ }
+
+ const prerequisite_key& cp (p.key ()); // c(xx){} prerequisite key.
+ const target_type& o_type (
+ group
+ ? obj::static_type
+ : (so ? objso::static_type : obja::static_type));
+
+ // Come up with the obj*{} target. The c(xx){} prerequisite
+ // directory can be relative (to the scope) or absolute. If it is
+ // relative, then use it as is. If it is absolute, then translate
+ // it to the corresponding directory under out_root. While the
+ // c(xx){} directory is most likely under src_root, it is also
+ // possible it is under out_root (e.g., generated source).
+ //
+ dir_path d;
+ {
+ const dir_path& cpd (*cp.tk.dir);
+
+ if (cpd.relative () || cpd.sub (*out_root))
+ d = cpd;
+ else
+ {
+ if (!cpd.sub (*src_root))
+ fail << "out of project prerequisite " << cp <<
+ info << "specify corresponding " << o_type.name << "{} "
+ << "target explicitly";
+
+ d = *out_root / cpd.leaf (*src_root);
+ }
+ }
+
+ target& ot (search (o_type, d, *cp.tk.name, nullptr, cp.scope));
+
+ // If we are cleaning, check that this target is in the same or
+ // a subdirectory of our strong amalgamation.
+ //
+ if (a.operation () == clean_id && !ot.dir.sub (*amlg))
+ {
+ // If we shouldn't clean obj{}, then it is fair to assume
+ // we shouldn't clean cxx{} either (generated source will
+ // be in the same directory as obj{} and if not, well, go
+ // find yourself another build system ;-)).
+ //
+ continue; // Skip.
+ }
+
+ // If we have created the obj{} target group, pick one of its
+ // members; the rest would be primarily concerned with it.
+ //
+ if (group)
+ {
+ obj& o (static_cast<obj&> (ot));
+ pt = so ? static_cast<target*> (o.so) : o.a;
+
+ if (pt == nullptr)
+ pt = &search (so ? objso::static_type : obja::static_type,
+ o.dir, o.name, o.ext, nullptr);
+ }
+ else
+ pt = &ot;
+
+ // If this obj*{} target already exists, then it needs to be
+ // "compatible" with what we are doing here.
+ //
+ // This gets a bit tricky. We need to make sure the source files
+ // are the same which we can only do by comparing the targets to
+ // which they resolve. But we cannot search the ot's prerequisites
+ // -- only the rule that matches can. Note, however, that if all
+ // this works out, then our next step is to match the obj*{}
+ // target. If things don't work out, then we fail, in which case
+ // searching and matching speculatively doesn't really hurt.
+ //
+ bool found (false);
+ for (prerequisite_member p1:
+ reverse_group_prerequisite_members (a, *pt))
+ {
+ // Ignore some known target types (fsdir, headers, libraries).
+ //
+ if (p1.is_a<fsdir> () ||
+ p1.is_a<h> () ||
+ (p.is_a<cxx> () && (p1.is_a<hxx> () ||
+ p1.is_a<ixx> () ||
+ p1.is_a<txx> ())) ||
+ p1.is_a<lib> () ||
+ p1.is_a<liba> () ||
+ p1.is_a<libso> ())
+ {
+ continue;
+ }
+
+ if (!p1.is_a<cxx> ())
+ fail << "synthesized target for prerequisite " << cp
+ << " would be incompatible with existing target " << *pt <<
+ info << "unexpected existing prerequisite type " << p1 <<
+ info << "specify corresponding obj{} target explicitly";
+
+ if (!found)
+ {
+ build::match (a, *pt); // Now p1 should be resolved.
+
+ // Searching our own prerequisite is ok.
+ //
+ if (&p.search () != &p1.search ())
+ fail << "synthesized target for prerequisite " << cp << " would "
+ << "be incompatible with existing target " << *pt <<
+ info << "existing prerequisite " << p1 << " does not match "
+ << cp <<
+ info << "specify corresponding " << o_type.name << "{} target "
+ << "explicitly";
+
+ found = true;
+ // Check the rest of the prerequisites.
+ }
+ }
+
+ if (!found)
+ {
+ // Note: add the source to the group, not the member.
+ //
+ ot.prerequisites.emplace_back (p.as_prerequisite (trace));
+
+ // Add our lib*{} prerequisites to the object file (see
+ // cxx.export.poptions above for details). Note: no need
+ // to go into group members.
+ //
+ // Initially, we were only adding imported libraries, but
+ // there is a problem with this approach: the non-imported
+ // library might depend on the imported one(s) which we will
+ // never "see" unless we start with this library.
+ //
+ for (prerequisite& p: group_prerequisites (t))
+ {
+ if (p.is_a<lib> () || p.is_a<liba> () || p.is_a<libso> ())
+ ot.prerequisites.emplace_back (p);
+ }
+
+ build::match (a, *pt);
+ }
+
+ t.prerequisite_targets.push_back (pt);
+ }
+
+ switch (a)
+ {
+ case perform_update_id: return &perform_update;
+ case perform_clean_id: return &perform_clean;
+ default: return default_recipe; // Forward to prerequisites.
+ }
+ }
+
+ target_state link::
+ perform_update (action a, target& xt)
+ {
+ path_target& t (static_cast<path_target&> (xt));
+
+ type lt (link_type (t));
+ bool so (lt == type::so);
+
+ if (!execute_prerequisites (a, t, t.mtime ()))
+ return target_state::unchanged;
+
+ // Translate paths to relative (to working directory) ones. This
+ // results in easier to read diagnostics.
+ //
+ path relt (relative (t.path ()));
+
+ scope& rs (t.root_scope ());
+ cstrings args;
+ string storage1;
+
+ if (lt == type::a)
+ {
+ //@@ ranlib
+ //
+ args.push_back ("ar");
+ args.push_back ("-rc");
+ args.push_back (relt.string ().c_str ());
+ }
+ else
+ {
+ args.push_back (rs["config.cxx"].as<const string&> ().c_str ());
+
+ append_options (args, t, "cxx.coptions");
+
+ append_std (args, t, storage1);
+
+ if (so)
+ args.push_back ("-shared");
+
+ args.push_back ("-o");
+ args.push_back (relt.string ().c_str ());
+
+ append_options (args, t, "cxx.loptions");
+ }
+
+ // Reserve enough space so that we don't reallocate. Reallocating
+ // means pointers to elements may no longer be valid.
+ //
+ paths relo;
+ relo.reserve (t.prerequisite_targets.size ());
+
+ for (target* pt: t.prerequisite_targets)
+ {
+ path_target* ppt;
+
+ if ((ppt = pt->is_a<obja> ()))
+ ;
+ else if ((ppt = pt->is_a<objso> ()))
+ ;
+ else if ((ppt = pt->is_a<liba> ()))
+ ;
+ else if ((ppt = pt->is_a<libso> ()))
+ {
+ // Use absolute path for the shared libraries since that's
+ // the path the runtime loader will use to try to find it.
+ // This is probably temporary until we get into the whole
+ // -soname/-rpath mess.
+ //
+ args.push_back (ppt->path ().string ().c_str ());
+ continue;
+ }
+ else
+ continue;
+
+ relo.push_back (relative (ppt->path ()));
+ args.push_back (relo.back ().string ().c_str ());
+ }
+
+ if (lt != type::a)
+ append_options (args, t, "cxx.libs");
+
+ args.push_back (nullptr);
+
+ if (verb)
+ print_process (args);
+ else
+ text << "ld " << t;
+
+ try
+ {
+ process pr (args.data ());
+
+ if (!pr.wait ())
+ throw failed ();
+
+ // 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)
+ {
+ error << "unable to execute " << args[0] << ": " << e.what ();
+
+ // In a multi-threaded program that fork()'ed but did not exec(),
+ // it is unwise to try to do any kind of cleanup (like unwinding
+ // the stack and running destructors).
+ //
+ if (e.child ())
+ exit (1);
+
+ throw failed ();
+ }
+ }
+ }
+}
diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx
index ce7c968..3975ac5 100644
--- a/build/cxx/module.cxx
+++ b/build/cxx/module.cxx
@@ -14,8 +14,9 @@
#include <build/bin/target>
-#include <build/cxx/rule>
#include <build/cxx/target>
+#include <build/cxx/compile>
+#include <build/cxx/link>
using namespace std;
using namespace butl;
diff --git a/build/cxx/rule b/build/cxx/rule
deleted file mode 100644
index 2bd344d..0000000
--- a/build/cxx/rule
+++ /dev/null
@@ -1,60 +0,0 @@
-// file : build/cxx/rule -*- C++ -*-
-// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#ifndef BUILD_CXX_RULE
-#define BUILD_CXX_RULE
-
-#include <build/types>
-#include <build/rule>
-
-namespace build
-{
- class scope;
-
- namespace cxx
- {
- class cxx;
-
- // @@ Can't we do match(obj&) and then registration code extracts
- // that. And no virtuals?
- //
- class compile: public rule
- {
- public:
- virtual match_result
- match (action, target&, const std::string& hint) const;
-
- virtual recipe
- apply (action, target&, const match_result&) const;
-
- static target_state
- perform_update (action, target&);
- };
-
- class link: public rule
- {
- public:
- virtual match_result
- match (action, target&, const std::string& hint) const;
-
- virtual recipe
- apply (action, target&, const match_result&) const;
-
- static target_state
- perform_update (action, target&);
-
- private:
- enum class type {e, a, so};
- enum class order {a, so, a_so, so_a};
-
- static type
- link_type (target&);
-
- static order
- link_order (target&);
- };
- }
-}
-
-#endif // BUILD_CXX_RULE
diff --git a/build/cxx/utility b/build/cxx/utility
new file mode 100644
index 0000000..53e80e1
--- /dev/null
+++ b/build/cxx/utility
@@ -0,0 +1,37 @@
+// file : build/cxx/utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUILD_CXX_UTILITY
+#define BUILD_CXX_UTILITY
+
+#include <string>
+
+#include <build/types>
+#include <build/target>
+
+#include <build/config/utility>
+
+namespace build
+{
+ namespace cxx
+ {
+ using config::append_options;
+
+ // T is either target or scope.
+ //
+ template <typename T>
+ void
+ append_std (cstrings& args, T&, std::string& storage);
+
+ // Append library options from one of the cxx.export.* variables
+ // recursively, prerequisite libraries first.
+ //
+ void
+ append_lib_options (cstrings& args, target&, const char* variable);
+ }
+}
+
+#include <build/cxx/utility.txx>
+
+#endif // BUILD_CXX_UTILITY
diff --git a/build/cxx/utility.cxx b/build/cxx/utility.cxx
new file mode 100644
index 0000000..830ee76
--- /dev/null
+++ b/build/cxx/utility.cxx
@@ -0,0 +1,29 @@
+// file : build/cxx/utility.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <build/cxx/utility>
+
+#include <build/bin/target>
+
+using namespace std;
+
+namespace build
+{
+ namespace cxx
+ {
+ void
+ append_lib_options (cstrings& args, target& l, const char* var)
+ {
+ using namespace bin;
+
+ for (target* t: l.prerequisite_targets)
+ {
+ if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ())
+ append_lib_options (args, *t, var);
+ }
+
+ append_options (args, l, var);
+ }
+ }
+}
diff --git a/build/cxx/utility.txx b/build/cxx/utility.txx
new file mode 100644
index 0000000..7d9d686
--- /dev/null
+++ b/build/cxx/utility.txx
@@ -0,0 +1,35 @@
+// file : build/cxx/utility.txx -*- C++ -*-
+// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+using namespace std;
+
+namespace build
+{
+ namespace cxx
+ {
+ template <typename T>
+ void
+ append_std (cstrings& args, T& t, std::string& s)
+ {
+ if (auto val = t["cxx.std"])
+ {
+ const std::string& v (val.template as<const string&> ());
+
+ // Translate 11 to 0x and 14 to 1y for compatibility with
+ // older versions of the compiler.
+ //
+ s = "-std=c++";
+
+ if (v == "11")
+ s += "0x";
+ else if (v == "14")
+ s += "1y";
+ else
+ s += v;
+
+ args.push_back (s.c_str ());
+ }
+ }
+ }
+}