From c1981e367aafc22389ac0ab506b00e9657c8071c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 20 Jul 2015 10:38:02 +0200 Subject: Implement support for importing installed libraries --- build/bin/target.cxx | 18 +- build/buildfile | 2 +- build/cxx/compile | 30 + build/cxx/compile.cxx | 782 ++++++++++++++++ build/cxx/link | 46 + build/cxx/link.cxx | 843 +++++++++++++++++ build/cxx/module.cxx | 3 +- build/cxx/rule | 60 -- build/cxx/rule.cxx | 1300 -------------------------- build/cxx/utility | 37 + build/cxx/utility.cxx | 29 + build/cxx/utility.txx | 35 + build/file.cxx | 2 +- build/target | 19 + tests/import/installed/build/bootstrap.build | 3 + tests/import/installed/buildfile | 8 + tests/import/installed/driver.cxx | 4 + 17 files changed, 1856 insertions(+), 1365 deletions(-) create mode 100644 build/cxx/compile create mode 100644 build/cxx/compile.cxx create mode 100644 build/cxx/link create mode 100644 build/cxx/link.cxx delete mode 100644 build/cxx/rule delete mode 100644 build/cxx/rule.cxx create mode 100644 build/cxx/utility create mode 100644 build/cxx/utility.cxx create mode 100644 build/cxx/utility.txx create mode 100644 tests/import/installed/build/bootstrap.build create mode 100644 tests/import/installed/buildfile create mode 100644 tests/import/installed/driver.cxx diff --git a/build/bin/target.cxx b/build/bin/target.cxx index 9dbcd83..4e48d16 100644 --- a/build/bin/target.cxx +++ b/build/bin/target.cxx @@ -106,13 +106,26 @@ namespace build return a; } + // @@ + // + // What extensions should we use? At the outset, this is platform- + // dependent. And if we consider cross-compilation, is it build or + // host-dependent? Feels like it should be host-dependent so that + // we can copy things between cross and native environments. So + // these will have to be determined based on what we are building. + // As if this is not complicated enough, the bin module doesn't + // know anything about building. So perhaps the extension should + // come from a variable that is set not by bin but by the module + // whose rule matched the target (e.g., cxx::link). + // + constexpr const char a_ext[] = "a"; const target_type liba::static_type { typeid (liba), "liba", &file::static_type, &liba_factory, - nullptr, + &target_extension_fix, &search_file, false }; @@ -129,13 +142,14 @@ namespace build return so; } + constexpr const char so_ext[] = "so"; const target_type libso::static_type { typeid (libso), "libso", &file::static_type, &libso_factory, - nullptr, + &target_extension_fix, &search_file, false }; diff --git a/build/buildfile b/build/buildfile index 5a5f9fe..90a8dc7 100644 --- a/build/buildfile +++ b/build/buildfile @@ -6,7 +6,7 @@ import libs += libbutl%lib{butl} config = config/{operation module utility} bin = bin/{target rule module} -cxx = cxx/{target rule module} +cxx = cxx/{target compile link module utility} cli = cli/{target rule module} exe{b}: cxx{b algorithm name operation spec scope variable target \ 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 +#include + +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/compile.cxx b/build/cxx/compile.cxx new file mode 100644 index 0000000..d0e6526 --- /dev/null +++ b/build/cxx/compile.cxx @@ -0,0 +1,782 @@ +// file : build/cxx/compile.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include +#include +#include // size_t +#include // exit() +#include // move() + +#include +#include // reverse_iterate +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace butl; + +namespace build +{ + namespace cxx + { + using namespace bin; + + match_result compile:: + match (action a, target& t, const string&) const + { + tracer trace ("cxx::compile::match"); + + // @@ TODO: + // + // - check prerequisites: single source file + // - check prerequisites: the rest are headers (other ignorable?) + // - if path already assigned, verify extension? + // + + // See if we have a C++ 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. + // + for (prerequisite_member p: reverse_group_prerequisite_members (a, t)) + { + if (p.is_a ()) + return p; + } + + level3 ([&]{trace << "no c++ source file for target " << t;}); + return nullptr; + } + + static void + inject_prerequisites (action, target&, cxx&, scope&); + + recipe compile:: + apply (action a, target& xt, const match_result& mr) const + { + path_target& t (static_cast (xt)); + + // Derive file name from target name. + // + if (t.path ().empty ()) + t.derive_path ("o", nullptr, (t.is_a () ? "-so" : nullptr)); + + // Inject dependency on the output directory. + // + inject_parent_fsdir (a, t); + + // Search and match all the existing prerequisites. The injection + // code (below) takes care of the ones it is adding. + // + // 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 ()); + + link::search_paths_cache lib_paths; // Extract lazily. + + for (prerequisite_member p: group_prerequisite_members (a, t)) + { + // 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 (p.is_a () || p.is_a () || p.is_a ()) + { + 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); + } + + // Inject additional prerequisites. We only do it for update + // since chances are we will have to update some of our + // prerequisites in the process (auto-generated source code). + // + if (a.operation () == update_id) + { + // The cached prerequisite target should be the same as what + // is in t.prerequisite_targets since we used standard + // search() and match() above. + // + // @@ Ugly. + // + cxx& st ( + dynamic_cast ( + mr.target != nullptr ? *mr.target : *mr.prerequisite->target)); + inject_prerequisites (a, t, st, mr.prerequisite->scope); + } + + switch (a) + { + case perform_update_id: return &perform_update; + case perform_clean_id: return &perform_clean; + default: return default_recipe; // Forward to prerequisites. + } + } + + // The strings used as the map key should be from the extension_pool. + // This way we can just compare pointers. + // + using ext_map = map; + + static ext_map + build_ext_map (scope& r) + { + ext_map m; + + if (auto val = r["h.ext"]) + m[&extension_pool.find (val.as ())] = &h::static_type; + + if (auto val = r["c.ext"]) + m[&extension_pool.find (val.as ())] = &c::static_type; + + if (auto val = r["hxx.ext"]) + m[&extension_pool.find (val.as ())] = &hxx::static_type; + + if (auto val = r["ixx.ext"]) + m[&extension_pool.find (val.as ())] = &ixx::static_type; + + if (auto val = r["txx.ext"]) + m[&extension_pool.find (val.as ())] = &txx::static_type; + + if (auto val = r["cxx.ext"]) + m[&extension_pool.find (val.as ())] = &cxx::static_type; + + return m; + } + + // Mapping of include prefixes (e.g., foo in ) for auto- + // generated headers to directories where they will be generated. + // + // We are using a prefix map of directories (dir_path_map) instead + // of just a map in order also cover sub-paths (e.g., + // if we continue with the example). Specifically, we need to make + // sure we don't treat foobar as a sub-directory of foo. + // + // @@ The keys should be canonicalized. + // + using prefix_map = dir_path_map; + + static void + append_prefixes (prefix_map& m, target& t, const char* var) + { + tracer trace ("cxx::append_prefixes"); + + const dir_path& out_base (t.dir); + const dir_path& out_root (t.root_scope ().path ()); + + if (auto val = t[var]) + { + const list_value& l (val.template as ()); + + // Assume the names have already been vetted by append_options(). + // + for (auto i (l.begin ()), e (l.end ()); i != e; ++i) + { + // -I can either be in the -Ifoo or -I foo form. + // + dir_path d; + if (i->value == "-I") + { + if (++i == e) + break; // Let the compiler complain. + + d = i->simple () ? dir_path (i->value) : i->dir; + } + else if (i->value.compare (0, 2, "-I") == 0) + d = dir_path (i->value, 2, string::npos); + else + continue; + + level5 ([&]{trace << "-I '" << d << "'";}); + + // If we are relative or not inside our project root, then + // ignore. + // + if (d.relative () || !d.sub (out_root)) + continue; + + // If the target directory is a sub-directory of the include + // directory, then the prefix is the difference between the + // two. Otherwise, leave it empty. + // + // The idea here is to make this "canonical" setup work auto- + // magically: + // + // 1. We include all files with a prefix, e.g., . + // 2. The library target is in the foo/ sub-directory, e.g., + // /tmp/foo/. + // 3. The poptions variable contains -I/tmp. + // + dir_path p (out_base.sub (d) ? out_base.leaf (d) : dir_path ()); + + auto j (m.find (p)); + + if (j != m.end ()) + { + if (j->second != d) + fail << "duplicate generated dependency prefix '" << p << "'" << + info << "old mapping to " << j->second << + info << "new mapping to " << d; + } + else + { + level5 ([&]{trace << "'" << p << "' = '" << d << "'";}); + m.emplace (move (p), move (d)); + } + } + } + } + + // Append library prefixes based on the cxx.export.poptions variables + // recursively, prerequisite libraries first. + // + static void + append_lib_prefixes (prefix_map& m, target& l) + { + for (target* t: l.prerequisite_targets) + { + if (t == nullptr) + continue; + + if (t->is_a () || t->is_a () || t->is_a ()) + append_lib_prefixes (m, *t); + } + + append_prefixes (m, l, "cxx.export.poptions"); + } + + static prefix_map + build_prefix_map (target& t) + { + prefix_map m; + + // First process the include directories from prerequisite + // libraries. Note that here we don't need to see group + // members (see apply()). + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_prefixes (m, pt); + } + + // Then process our own. + // + append_prefixes (m, t, "cxx.poptions"); + + return m; + } + + // Return the next make prerequisite starting from the specified + // position and update position to point to the start of the + // following prerequisite or l.size() if there are none left. + // + static string + next (const string& l, size_t& p) + { + size_t n (l.size ()); + + // Skip leading spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Lines containing multiple prerequisites are 80 characters max. + // + string r; + r.reserve (n); + + // Scan the next prerequisite while watching out for escape sequences. + // + for (; p != n && l[p] != ' '; p++) + { + char c (l[p]); + + if (c == '\\') + c = l[++p]; + + r += c; + } + + // Skip trailing spaces. + // + for (; p != n && l[p] == ' '; p++) ; + + // Skip final '\'. + // + if (p == n - 1 && l[p] == '\\') + p++; + + return r; + } + + static void + inject_prerequisites (action a, target& t, cxx& s, scope& ds) + { + tracer trace ("cxx::compile::inject_prerequisites"); + + scope& rs (t.root_scope ()); + const string& cxx (rs["config.cxx"].as ()); + + cstrings args {cxx.c_str ()}; + + // Add cxx.export.poptions from prerequisite libraries. Note + // that here we don't need to see group members (see apply()). + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_options (args, pt, "cxx.export.poptions"); + } + + append_options (args, t, "cxx.poptions"); + + // @@ Some C++ options (e.g., -std, -m) affect the preprocessor. + // Or maybe they are not C++ options? Common options? + // + append_options (args, t, "cxx.coptions"); + + string std; // Storage. + append_std (args, t, std); + + if (t.is_a ()) + args.push_back ("-fPIC"); + + args.push_back ("-M"); // Note: -MM -MG skips missing <>-included. + args.push_back ("-MG"); // Treat missing headers as generated. + args.push_back ("-MQ"); // Quoted target name. + args.push_back ("*"); // Old versions can't handle empty target name. + + // We are using absolute source file path in order to get absolute + // paths in the result. Any relative paths in the result are non- + // existent, potentially auto-generated headers. + // + // @@ We will also have to use absolute -I paths to guarantee + // that. Or just detect relative paths and error out? + // + args.push_back (s.path ().string ().c_str ()); + args.push_back (nullptr); + + level5 ([&]{trace << "target: " << t;}); + + // Build the prefix map lazily only if we have non-existent files. + // Also reuse it over restarts since it doesn't change. + // + prefix_map pm; + + // If any prerequisites that we have extracted changed, then we + // have to redo the whole thing. The reason for this is auto- + // generated headers: the updated header may now include a yet- + // non-existent header. Unless we discover this and generate it + // (which, BTW, will trigger another restart since that header, + // in turn, can also include auto-generated headers), we will + // end up with an error during compilation proper. + // + // One complication with this restart logic is that we will see + // a "prefix" of prerequisites that we have already processed + // (i.e., they are already in our prerequisite_targets list) and + // we don't want to keep redoing this over and over again. One + // thing to note, however, is that the prefix that we have seen + // on the previous run must appear exactly the same in the + // subsequent run. The reason for this is that none of the files + // that it can possibly be based on have changed and thus it + // should be exactly the same. To put it another way, the + // presence or absence of a file in the dependency output can + // only depend on the previous files (assuming the compiler + // outputs them as it encounters them and it is hard to think + // of a reason why would someone do otherwise). And we have + // already made sure that all those files are up to date. And + // here is the way we are going to exploit this: we are going + // to keep track of how many prerequisites we have processed so + // far and on restart skip right to the next one. + // + // Also, before we do all that, make sure the source file itself + // if up to date. + // + execute_direct (a, s); + + size_t skip_count (0); + for (bool restart (true); restart; ) + { + restart = false; + + if (verb >= 2) + print_process (args); + + try + { + process pr (args.data (), false, false, true); + ifdstream is (pr.in_ofd); + + size_t skip (skip_count); + for (bool first (true), second (true); !(restart || is.eof ()); ) + { + string l; + getline (is, l); + + if (is.fail () && !is.eof ()) + fail << "error reading C++ compiler -M output"; + + size_t pos (0); + + if (first) + { + // Empty output should mean the wait() call below will return + // false. + // + if (l.empty ()) + break; + + assert (l[0] == '*' && l[1] == ':' && l[2] == ' '); + + first = false; + + // While normally we would have the source file on the + // first line, if too long, it will be moved to the next + // line and all we will have on this line is "*: \". + // + if (l.size () == 4 && l[3] == '\\') + continue; + else + pos = 3; // Skip "*: ". + + // Fall through to the 'second' block. + } + + if (second) + { + second = false; + next (l, pos); // Skip the source file. + } + + // If things go wrong (and they often do in this area), give + // the user a bit extra context. + // + auto g ( + make_exception_guard ( + [](target& s) + { + info << "while extracting dependencies from " << s; + }, + s)); + + while (pos != l.size ()) + { + string fs (next (l, pos)); + + // Skip until where we left off. + // + if (skip != 0) + { + skip--; + continue; + } + + path f (move (fs)); + f.normalize (); + + if (!f.absolute ()) + { + // This is probably as often an error as an auto-generated + // file, so trace at level 3. + // + level3 ([&]{trace << "non-existent header '" << f << "'";}); + + // If we already did it and build_prefix_map() returned empty, + // then we would have failed below. + // + if (pm.empty ()) + pm = build_prefix_map (t); + + // First try the whole file. Then just the directory. + // + // @@ Has to be a separate map since the prefix can be + // the same as the file name. + // + // auto i (pm.find (f)); + + // Find the most qualified prefix of which we are a + // sub-path. + // + auto i (pm.end ()); + + if (!pm.empty ()) + { + const dir_path& d (f.directory ()); + i = pm.upper_bound (d); + --i; // Greatest less than. + + if (!d.sub (i->first)) // We might still not be a sub. + i = pm.end (); + } + + if (i == pm.end ()) + fail << "unable to map presumably auto-generated header '" + << f << "' to a project"; + + f = i->second / f; + } + + level5 ([&]{trace << "injecting " << f;}); + + // Split the name into its directory part, the name part, and + // extension. Here we can assume the name part is a valid + // filesystem name. + // + // Note that if the file has no extension, we record an empty + // extension rather than NULL (which would signify that the + // default extension should be added). + // + dir_path d (f.directory ()); + string n (f.leaf ().base ().string ()); + const char* es (f.extension ()); + const string* e (&extension_pool.find (es != nullptr ? es : "")); + + // Determine the target type. + // + const target_type* tt (nullptr); + + // See if this directory is part of any project out_root + // hierarchy. Note that this will miss all the headers + // that come from src_root (so they will be treated as + // generic C headers below). Generally, we don't have + // the ability to determine that some file belongs to + // src_root of some project. But that's not a problem + // for our purposes: it is only important for us to + // accurately determine target types for headers that + // could be auto-generated. + // + if (scope* r = scopes.find (d).root_scope ()) + { + // Get cached (or build) a map of the extensions for the + // C/C++ files this project is using. + // + const ext_map& m (build_ext_map (*r)); + + auto i (m.find (e)); + if (i != m.end ()) + tt = i->second; + } + + // If it is outside any project, or the project doesn't have + // such an extension, assume it is a plain old C header. + // + if (tt == nullptr) + tt = &h::static_type; + + // Find or insert target. + // + path_target& pt ( + static_cast (search (*tt, d, n, e, &ds))); + + // Assign path. + // + if (pt.path ().empty ()) + pt.path (move (f)); + + // Match to a rule. + // + build::match (a, pt); + + // Update it. + // + // There would normally be a lot of headers for every source + // file (think all the system headers) and this can get + // expensive. At the same time, most of these headers are + // existing files that we will never be updating (again, + // system headers, for example) and the rule that will match + // them is fallback file_rule. So we are going to do a little + // fast-path optimization by detecting this common case. + // + recipe_function* const* recipe ( + pt.recipe (a).target ()); + + if (recipe == nullptr || *recipe != &file_rule::perform_update) + { + // We only want to restart if our call to execute() actually + // caused an update. In particular, the target could already + // have been in target_state::changed because of a dependency + // extraction run for some other source file. + // + target_state os (pt.state ()); + target_state ns (execute_direct (a, pt)); + + if (ns != os && ns != target_state::unchanged) + { + level5 ([&]{trace << "updated " << pt << ", restarting";}); + restart = true; + } + } + + // Add to our prerequisite target list. + // + t.prerequisite_targets.push_back (&pt); + skip_count++; + } + } + + // We may not have read all the output (e.g., due to a restart), + // so close the file descriptor before waiting to avoid blocking + // the other end. + // + is.close (); + + // We assume the child process issued some diagnostics. + // + if (!pr.wait ()) + throw failed (); + } + 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 (); + } + } + } + + target_state compile:: + perform_update (action a, target& xt) + { + path_target& t (static_cast (xt)); + cxx* s (execute_prerequisites (a, t, t.mtime ())); + + if (s == nullptr) + return target_state::unchanged; + + // Translate paths to relative (to working directory) ones. This + // results in easier to read diagnostics. + // + path relo (relative (t.path ())); + path rels (relative (s->path ())); + + scope& rs (t.root_scope ()); + const string& cxx (rs["config.cxx"].as ()); + + cstrings args {cxx.c_str ()}; + + // Add cxx.export.poptions from prerequisite libraries. Note that + // here we don't need to see group members (see apply()). + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a () || pt.is_a () || pt.is_a ()) + append_lib_options (args, pt, "cxx.export.poptions"); + } + + append_options (args, t, "cxx.poptions"); + append_options (args, t, "cxx.coptions"); + + string std; // Storage. + append_std (args, t, std); + + if (t.is_a ()) + args.push_back ("-fPIC"); + + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + + args.push_back ("-c"); + args.push_back (rels.string ().c_str ()); + + args.push_back (nullptr); + + if (verb) + print_process (args); + else + text << "c++ " << *s; + + 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 + +#include + +#include +#include + +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; + using search_paths_cache = butl::optional; + + 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 + +#include +#include +#include // size_t +#include // exit() +#include // move() + +#include +#include // reverse_iterate +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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 () ? type::e : (t.is_a () ? 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 ()); + 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 ()); + + 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 ().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 ()); + 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 ()) + { + 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 ()) + { + 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 (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 (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 (*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 () ? static_cast (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 ()); + + // 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 ()) + { + seen_cxx = seen_cxx || true; + } + else if (p.is_a ()) + { + seen_c = seen_c || true; + } + else if (p.is_a ()) + { + if (so) + fail << "shared library " << t << " prerequisite " << p + << " is static object"; + + seen_obj = seen_obj || true; + } + else if (p.is_a () || + p.is_a ()) + { + seen_obj = seen_obj || true; + } + else if (p.is_a () || + p.is_a () || + p.is_a ()) + { + seen_lib = seen_lib || true; + } + else if (p.is_a () || + p.is_a () || + p.is_a () || + p.is_a () || + p.is_a ()) + ; + 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 (xt)); + + type lt (link_type (t)); + bool so (lt == type::so); + optional 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 () && !p.is_a ()) + { + // 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 () || p.is_a () || p.is_a ())) + { + 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 ()) + { + pt = so ? static_cast (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 ()) + { + // Determine the library type to link. + // + bool lso (true); + const string& at ((*l)["bin.lib"].as ()); + + 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 (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 (ot)); + pt = so ? static_cast (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 () || + p1.is_a () || + (p.is_a () && (p1.is_a () || + p1.is_a () || + p1.is_a ())) || + p1.is_a () || + p1.is_a () || + p1.is_a ()) + { + continue; + } + + if (!p1.is_a ()) + 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 () || p.is_a () || p.is_a ()) + 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 (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 ().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 ())) + ; + else if ((ppt = pt->is_a ())) + ; + else if ((ppt = pt->is_a ())) + ; + else if ((ppt = pt->is_a ())) + { + // 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 -#include #include +#include +#include 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 -#include - -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/rule.cxx b/build/cxx/rule.cxx deleted file mode 100644 index 9228994..0000000 --- a/build/cxx/rule.cxx +++ /dev/null @@ -1,1300 +0,0 @@ -// file : build/cxx/rule.cxx -*- C++ -*- -// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd -// license : MIT; see accompanying LICENSE file - -#include - -#include -#include -#include // size_t -#include // exit -#include // move() - -#include -#include // reverse_iterate -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -using namespace std; -using namespace butl; - -namespace build -{ - namespace cxx - { - 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 ()); - - // 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 () || t->is_a () || t->is_a ()) - append_lib_options (args, *t, var); - } - - append_options (args, l, var); - } - - // compile - // - match_result compile:: - match (action a, target& t, const string&) const - { - tracer trace ("cxx::compile::match"); - - // @@ TODO: - // - // - check prerequisites: single source file - // - check prerequisites: the rest are headers (other ignorable?) - // - if path already assigned, verify extension? - // - - // See if we have a C++ 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. - // - for (prerequisite_member p: reverse_group_prerequisite_members (a, t)) - { - if (p.is_a ()) - return p; - } - - level3 ([&]{trace << "no c++ source file for target " << t;}); - return nullptr; - } - - static void - inject_prerequisites (action, target&, cxx&, scope&); - - recipe compile:: - apply (action a, target& xt, const match_result& mr) const - { - path_target& t (static_cast (xt)); - - // Derive file name from target name. - // - if (t.path ().empty ()) - t.derive_path ("o", nullptr, (t.is_a () ? "-so" : nullptr)); - - // Inject dependency on the output directory. - // - inject_parent_fsdir (a, t); - - // Search and match all the existing prerequisites. The injection - // code (below) takes care of the ones it is adding. - // - // 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)) - { - 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 () || pt.is_a () || pt.is_a ()) - { - // @@ 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) - build::match (a, pt); - - continue; - } - - build::match (a, pt); - t.prerequisite_targets.push_back (&pt); - } - - // Inject additional prerequisites. We only do it for update - // since chances are we will have to update some of our - // prerequisites in the process (auto-generated source code). - // - if (a.operation () == update_id) - { - // The cached prerequisite target should be the same as what - // is in t.prerequisite_targets since we used standard - // search() and match() above. - // - // @@ Ugly. - // - cxx& st ( - dynamic_cast ( - mr.target != nullptr ? *mr.target : *mr.prerequisite->target)); - inject_prerequisites (a, t, st, mr.prerequisite->scope); - } - - switch (a) - { - case perform_update_id: return &perform_update; - case perform_clean_id: return &perform_clean; - default: return default_recipe; // Forward to prerequisites. - } - } - - // The strings used as the map key should be from the extension_pool. - // This way we can just compare pointers. - // - using ext_map = map; - - static ext_map - build_ext_map (scope& r) - { - ext_map m; - - if (auto val = r["h.ext"]) - m[&extension_pool.find (val.as ())] = &h::static_type; - - if (auto val = r["c.ext"]) - m[&extension_pool.find (val.as ())] = &c::static_type; - - if (auto val = r["hxx.ext"]) - m[&extension_pool.find (val.as ())] = &hxx::static_type; - - if (auto val = r["ixx.ext"]) - m[&extension_pool.find (val.as ())] = &ixx::static_type; - - if (auto val = r["txx.ext"]) - m[&extension_pool.find (val.as ())] = &txx::static_type; - - if (auto val = r["cxx.ext"]) - m[&extension_pool.find (val.as ())] = &cxx::static_type; - - return m; - } - - // Mapping of include prefixes (e.g., foo in ) for auto- - // generated headers to directories where they will be generated. - // - // We are using a prefix map of directories (dir_path_map) instead - // of just a map in order also cover sub-paths (e.g., - // if we continue with the example). Specifically, we need to make - // sure we don't treat foobar as a sub-directory of foo. - // - // @@ The keys should be canonicalized. - // - using prefix_map = dir_path_map; - - static void - append_prefixes (prefix_map& m, target& t, const char* var) - { - tracer trace ("cxx::append_prefixes"); - - const dir_path& out_base (t.dir); - const dir_path& out_root (t.root_scope ().path ()); - - if (auto val = t[var]) - { - const list_value& l (val.template as ()); - - // Assume the names have already been vetted by append_options(). - // - for (auto i (l.begin ()), e (l.end ()); i != e; ++i) - { - // -I can either be in the -Ifoo or -I foo form. - // - dir_path d; - if (i->value == "-I") - { - if (++i == e) - break; // Let the compiler complain. - - d = i->simple () ? dir_path (i->value) : i->dir; - } - else if (i->value.compare (0, 2, "-I") == 0) - d = dir_path (i->value, 2, string::npos); - else - continue; - - level5 ([&]{trace << "-I '" << d << "'";}); - - // If we are relative or not inside our project root, then - // ignore. - // - if (d.relative () || !d.sub (out_root)) - continue; - - // If the target directory is a sub-directory of the include - // directory, then the prefix is the difference between the - // two. Otherwise, leave it empty. - // - // The idea here is to make this "canonical" setup work auto- - // magically: - // - // 1. We include all files with a prefix, e.g., . - // 2. The library target is in the foo/ sub-directory, e.g., - // /tmp/foo/. - // 3. The poptions variable contains -I/tmp. - // - dir_path p (out_base.sub (d) ? out_base.leaf (d) : dir_path ()); - - auto j (m.find (p)); - - if (j != m.end ()) - { - if (j->second != d) - fail << "duplicate generated dependency prefix '" << p << "'" << - info << "old mapping to " << j->second << - info << "new mapping to " << d; - } - else - { - level5 ([&]{trace << "'" << p << "' = '" << d << "'";}); - m.emplace (move (p), move (d)); - } - } - } - } - - // Append library prefixes based on the cxx.export.poptions variables - // recursively, prerequisite libraries first. - // - static void - append_lib_prefixes (prefix_map& m, target& l) - { - for (target* t: l.prerequisite_targets) - { - if (t == nullptr) - continue; - - if (t->is_a () || t->is_a () || t->is_a ()) - append_lib_prefixes (m, *t); - } - - append_prefixes (m, l, "cxx.export.poptions"); - } - - static prefix_map - build_prefix_map (target& t) - { - prefix_map m; - - // First process the include directories from prerequisite - // libraries. Note that here we don't need to see group - // members (see apply()). - // - for (prerequisite& p: group_prerequisites (t)) - { - target& pt (*p.target); // Already searched and matched. - - if (pt.is_a () || pt.is_a () || pt.is_a ()) - append_lib_prefixes (m, pt); - } - - // Then process our own. - // - append_prefixes (m, t, "cxx.poptions"); - - return m; - } - - // Return the next make prerequisite starting from the specified - // position and update position to point to the start of the - // following prerequisite or l.size() if there are none left. - // - static string - next (const string& l, size_t& p) - { - size_t n (l.size ()); - - // Skip leading spaces. - // - for (; p != n && l[p] == ' '; p++) ; - - // Lines containing multiple prerequisites are 80 characters max. - // - string r; - r.reserve (n); - - // Scan the next prerequisite while watching out for escape sequences. - // - for (; p != n && l[p] != ' '; p++) - { - char c (l[p]); - - if (c == '\\') - c = l[++p]; - - r += c; - } - - // Skip trailing spaces. - // - for (; p != n && l[p] == ' '; p++) ; - - // Skip final '\'. - // - if (p == n - 1 && l[p] == '\\') - p++; - - return r; - } - - static void - inject_prerequisites (action a, target& t, cxx& s, scope& ds) - { - tracer trace ("cxx::compile::inject_prerequisites"); - - scope& rs (t.root_scope ()); - const string& cxx (rs["config.cxx"].as ()); - - cstrings args {cxx.c_str ()}; - - // Add cxx.export.poptions from prerequisite libraries. Note - // that here we don't need to see group members (see apply()). - // - for (prerequisite& p: group_prerequisites (t)) - { - target& pt (*p.target); // Already searched and matched. - - if (pt.is_a () || pt.is_a () || pt.is_a ()) - append_lib_options (args, pt, "cxx.export.poptions"); - } - - append_options (args, t, "cxx.poptions"); - - // @@ Some C++ options (e.g., -std, -m) affect the preprocessor. - // Or maybe they are not C++ options? Common options? - // - append_options (args, t, "cxx.coptions"); - - string std; // Storage. - append_std (args, t, std); - - if (t.is_a ()) - args.push_back ("-fPIC"); - - args.push_back ("-M"); // Note: -MM -MG skips missing <>-included. - args.push_back ("-MG"); // Treat missing headers as generated. - args.push_back ("-MQ"); // Quoted target name. - args.push_back ("*"); // Old versions can't handle empty target name. - - // We are using absolute source file path in order to get absolute - // paths in the result. Any relative paths in the result are non- - // existent, potentially auto-generated headers. - // - // @@ We will also have to use absolute -I paths to guarantee - // that. Or just detect relative paths and error out? - // - args.push_back (s.path ().string ().c_str ()); - args.push_back (nullptr); - - level5 ([&]{trace << "target: " << t;}); - - // Build the prefix map lazily only if we have non-existent files. - // Also reuse it over restarts since it doesn't change. - // - prefix_map pm; - - // If any prerequisites that we have extracted changed, then we - // have to redo the whole thing. The reason for this is auto- - // generated headers: the updated header may now include a yet- - // non-existent header. Unless we discover this and generate it - // (which, BTW, will trigger another restart since that header, - // in turn, can also include auto-generated headers), we will - // end up with an error during compilation proper. - // - // One complication with this restart logic is that we will see - // a "prefix" of prerequisites that we have already processed - // (i.e., they are already in our prerequisite_targets list) and - // we don't want to keep redoing this over and over again. One - // thing to note, however, is that the prefix that we have seen - // on the previous run must appear exactly the same in the - // subsequent run. The reason for this is that none of the files - // that it can possibly be based on have changed and thus it - // should be exactly the same. To put it another way, the - // presence or absence of a file in the dependency output can - // only depend on the previous files (assuming the compiler - // outputs them as it encounters them and it is hard to think - // of a reason why would someone do otherwise). And we have - // already made sure that all those files are up to date. And - // here is the way we are going to exploit this: we are going - // to keep track of how many prerequisites we have processed so - // far and on restart skip right to the next one. - // - // Also, before we do all that, make sure the source file itself - // if up to date. - // - execute_direct (a, s); - - size_t skip_count (0); - for (bool restart (true); restart; ) - { - restart = false; - - if (verb >= 2) - print_process (args); - - try - { - process pr (args.data (), false, false, true); - ifdstream is (pr.in_ofd); - - size_t skip (skip_count); - for (bool first (true), second (true); !(restart || is.eof ()); ) - { - string l; - getline (is, l); - - if (is.fail () && !is.eof ()) - fail << "io error while parsing g++ -M output"; - - size_t pos (0); - - if (first) - { - // Empty output should mean the wait() call below will return - // false. - // - if (l.empty ()) - break; - - assert (l[0] == '*' && l[1] == ':' && l[2] == ' '); - - first = false; - - // While normally we would have the source file on the - // first line, if too long, it will be moved to the next - // line and all we will have on this line is "*: \". - // - if (l.size () == 4 && l[3] == '\\') - continue; - else - pos = 3; // Skip "*: ". - - // Fall through to the 'second' block. - } - - if (second) - { - second = false; - next (l, pos); // Skip the source file. - } - - // If things go wrong (and they often do in this area), give - // the user a bit extra context. - // - auto g ( - make_exception_guard ( - [](target& s) - { - info << "while extracting dependencies from " << s; - }, - s)); - - while (pos != l.size ()) - { - string fs (next (l, pos)); - - // Skip until where we left off. - // - if (skip != 0) - { - skip--; - continue; - } - - path f (move (fs)); - f.normalize (); - - if (!f.absolute ()) - { - // This is probably as often an error as an auto-generated - // file, so trace at level 3. - // - level3 ([&]{trace << "non-existent header '" << f << "'";}); - - // If we already did it and build_prefix_map() returned empty, - // then we would have failed below. - // - if (pm.empty ()) - pm = build_prefix_map (t); - - // First try the whole file. Then just the directory. - // - // @@ Has to be a separate map since the prefix can be - // the same as the file name. - // - // auto i (pm.find (f)); - - // Find the most qualified prefix of which we are a - // sub-path. - // - auto i (pm.end ()); - - if (!pm.empty ()) - { - const dir_path& d (f.directory ()); - i = pm.upper_bound (d); - --i; // Greatest less than. - - if (!d.sub (i->first)) // We might still not be a sub. - i = pm.end (); - } - - if (i == pm.end ()) - fail << "unable to map presumably auto-generated header '" - << f << "' to a project"; - - f = i->second / f; - } - - level5 ([&]{trace << "injecting " << f;}); - - // Split the name into its directory part, the name part, and - // extension. Here we can assume the name part is a valid - // filesystem name. - // - // Note that if the file has no extension, we record an empty - // extension rather than NULL (which would signify that the - // default extension should be added). - // - dir_path d (f.directory ()); - string n (f.leaf ().base ().string ()); - const char* es (f.extension ()); - const string* e (&extension_pool.find (es != nullptr ? es : "")); - - // Determine the target type. - // - const target_type* tt (nullptr); - - // See if this directory is part of any project out_root - // hierarchy. Note that this will miss all the headers - // that come from src_root (so they will be treated as - // generic C headers below). Generally, we don't have - // the ability to determine that some file belongs to - // src_root of some project. But that's not a problem - // for our purposes: it is only important for us to - // accurately determine target types for headers that - // could be auto-generated. - // - if (scope* r = scopes.find (d).root_scope ()) - { - // Get cached (or build) a map of the extensions for the - // C/C++ files this project is using. - // - const ext_map& m (build_ext_map (*r)); - - auto i (m.find (e)); - if (i != m.end ()) - tt = i->second; - } - - // If it is outside any project, or the project doesn't have - // such an extension, assume it is a plain old C header. - // - if (tt == nullptr) - tt = &h::static_type; - - // Find or insert target. - // - path_target& pt ( - static_cast (search (*tt, d, n, e, &ds))); - - // Assign path. - // - if (pt.path ().empty ()) - pt.path (move (f)); - - // Match to a rule. - // - build::match (a, pt); - - // Update it. - // - // There would normally be a lot of headers for every source - // file (think all the system headers) and this can get - // expensive. At the same time, most of these headers are - // existing files that we will never be updating (again, - // system headers, for example) and the rule that will match - // them is fallback file_rule. So we are going to do a little - // fast-path optimization by detecting this common case. - // - recipe_function* const* recipe ( - pt.recipe (a).target ()); - - if (recipe == nullptr || *recipe != &file_rule::perform_update) - { - // We only want to restart if our call to execute() actually - // caused an update. In particular, the target could already - // have been in target_state::changed because of a dependency - // extraction run for some other source file. - // - target_state os (pt.state ()); - target_state ns (execute_direct (a, pt)); - - if (ns != os && ns != target_state::unchanged) - { - level5 ([&]{trace << "updated " << pt << ", restarting";}); - restart = true; - } - } - - // Add to our prerequisite target list. - // - t.prerequisite_targets.push_back (&pt); - skip_count++; - } - } - - // We may not have read all the output (e.g., due to a restart), - // so close the file descriptor before waiting to avoid blocking - // the other end. - // - is.close (); - - // We assume the child process issued some diagnostics. - // - if (!pr.wait ()) - throw failed (); - } - 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 (); - } - } - } - - target_state compile:: - perform_update (action a, target& xt) - { - path_target& t (static_cast (xt)); - cxx* s (execute_prerequisites (a, t, t.mtime ())); - - if (s == nullptr) - return target_state::unchanged; - - // Translate paths to relative (to working directory) ones. This - // results in easier to read diagnostics. - // - path relo (relative (t.path ())); - path rels (relative (s->path ())); - - scope& rs (t.root_scope ()); - const string& cxx (rs["config.cxx"].as ()); - - cstrings args {cxx.c_str ()}; - - // Add cxx.export.poptions from prerequisite libraries. Note that - // here we don't need to see group members (see apply()). - // - for (prerequisite& p: group_prerequisites (t)) - { - target& pt (*p.target); // Already searched and matched. - - if (pt.is_a () || pt.is_a () || pt.is_a ()) - append_lib_options (args, pt, "cxx.export.poptions"); - } - - append_options (args, t, "cxx.poptions"); - append_options (args, t, "cxx.coptions"); - - string std; // Storage. - append_std (args, t, std); - - if (t.is_a ()) - args.push_back ("-fPIC"); - - args.push_back ("-o"); - args.push_back (relo.string ().c_str ()); - - args.push_back ("-c"); - args.push_back (rels.string ().c_str ()); - - args.push_back (nullptr); - - if (verb) - print_process (args); - else - text << "c++ " << *s; - - 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 (); - } - } - - // link - // - inline link::type link:: - link_type (target& t) - { - return t.is_a () ? type::e : (t.is_a () ? 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 ()); - 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 ()); - - // 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 ()) - { - seen_cxx = seen_cxx || true; - } - else if (p.is_a ()) - { - seen_c = seen_c || true; - } - else if (p.is_a ()) - { - if (so) - fail << "shared library " << t << " prerequisite " << p - << " is static object"; - - seen_obj = seen_obj || true; - } - else if (p.is_a () || - p.is_a ()) - { - seen_obj = seen_obj || true; - } - else if (p.is_a () || - p.is_a () || - p.is_a ()) - { - seen_lib = seen_lib || true; - } - else if (p.is_a () || - p.is_a () || - p.is_a () || - p.is_a () || - p.is_a ()) - ; - 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 (xt)); - - type lt (link_type (t)); - bool so (lt == type::so); - optional 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 () && !p.is_a ()) - { - // 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 ()) - { - pt = so ? static_cast (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 ()) - { - // Determine the library type to link. - // - bool lso (true); - const string& at ((*l)["bin.lib"].as ()); - - 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 (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 (ot)); - pt = so ? static_cast (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 () || - p1.is_a () || - (p.is_a () && (p1.is_a () || - p1.is_a () || - p1.is_a ())) || - p1.is_a () || - p1.is_a () || - p1.is_a ()) - { - continue; - } - - if (!p1.is_a ()) - 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 () || p.is_a () || p.is_a ()) - 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 (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 ().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 ())) - ; - else if ((ppt = pt->is_a ())) - ; - else if ((ppt = pt->is_a ())) - ; - else if ((ppt = pt->is_a ())) - { - // 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/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 + +#include +#include + +#include + +namespace build +{ + namespace cxx + { + using config::append_options; + + // T is either target or scope. + // + template + 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 + +#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 + +#include + +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 () || t->is_a () || t->is_a ()) + 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 + void + append_std (cstrings& args, T& t, std::string& s) + { + if (auto val = t["cxx.std"]) + { + const std::string& v (val.template as ()); + + // 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 ()); + } + } + } +} diff --git a/build/file.cxx b/build/file.cxx index ef7ef78..305c13a 100644 --- a/build/file.cxx +++ b/build/file.cxx @@ -291,7 +291,7 @@ namespace build for (const dir_entry& de: dir_iterator (d)) { - if (de.ltype () != entry_type::directory) + if (de.type () != entry_type::directory) continue; dir_path sd (d / path_cast (de.path ())); diff --git a/build/target b/build/target index a1adbbd..ee61c7b 100644 --- a/build/target +++ b/build/target @@ -514,6 +514,14 @@ namespace build return target != nullptr ? target->name : prerequisite.get ().name; } + const std::string* + proj () const + { + // Target cannot be project-qualified. + // + return target != nullptr ? nullptr : prerequisite.get ().proj; + } + target_type& search () const { @@ -743,6 +751,17 @@ namespace build template T& + insert (const dir_path& dir, + const std::string& name, + const std::string* ext, + tracer& t) + { + return static_cast ( + insert (T::static_type, dir, name, ext, t).first); + } + + template + T& insert (const dir_path& dir, const std::string& name, tracer& t) { return static_cast ( diff --git a/tests/import/installed/build/bootstrap.build b/tests/import/installed/build/bootstrap.build new file mode 100644 index 0000000..97fd11f --- /dev/null +++ b/tests/import/installed/build/bootstrap.build @@ -0,0 +1,3 @@ +project = import-installed +amalgamation = # Disabled. +using config diff --git a/tests/import/installed/buildfile b/tests/import/installed/buildfile new file mode 100644 index 0000000..40c77ce --- /dev/null +++ b/tests/import/installed/buildfile @@ -0,0 +1,8 @@ +using cxx + +hxx.ext = hxx +cxx.ext = cxx + +import libs += lib{z} + +exe{driver}: cxx{driver} $libs diff --git a/tests/import/installed/driver.cxx b/tests/import/installed/driver.cxx new file mode 100644 index 0000000..70b4146 --- /dev/null +++ b/tests/import/installed/driver.cxx @@ -0,0 +1,4 @@ +int +main () +{ +} -- cgit v1.1