diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2015-06-25 13:41:19 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2015-06-25 13:41:19 +0200 |
commit | 95239b7c5404965d4f5ef997b5b75bf542a25192 (patch) | |
tree | 1b4fc6229723babffa7c5d56ee0c2eae24215535 | |
parent | f56b5a42b9aaaeb0c4e7dee894dea9686599b88c (diff) |
Part one of dependency injection with auto-generation support
28 files changed, 344 insertions, 58 deletions
diff --git a/build/algorithm.cxx b/build/algorithm.cxx index c438fb8..f4da3bd 100644 --- a/build/algorithm.cxx +++ b/build/algorithm.cxx @@ -213,9 +213,9 @@ namespace build search_and_match (action a, target& t) { group_prerequisites gp (t); - t.prerequisite_targets.resize (gp.size ()); + size_t i (t.prerequisite_targets.size ()); + t.prerequisite_targets.resize (gp.size () + i); - size_t i (0); for (prerequisite& p: gp) { target& pt (search (p)); diff --git a/build/config/utility b/build/config/utility index 3da990f..403a91e 100644 --- a/build/config/utility +++ b/build/config/utility @@ -53,7 +53,7 @@ namespace build { for (const name& n: val.template as<const list_value&> ()) { - if (!n.type.empty () || !n.dir.empty ()) + if (!n.simple ()) fail << "expected option instead of " << n << info << "in variable " << var; diff --git a/build/cxx/module.cxx b/build/cxx/module.cxx index 495819d..7e6a211 100644 --- a/build/cxx/module.cxx +++ b/build/cxx/module.cxx @@ -71,11 +71,11 @@ namespace build bool r (getline (is, ver)); - if (!pr.wait ()) - throw failed (); - if (!r) fail << "unexpected output from " << cxx; + + if (!pr.wait ()) + throw failed (); } catch (const process_error& e) { diff --git a/build/cxx/rule.cxx b/build/cxx/rule.cxx index 79b6072..47f925d 100644 --- a/build/cxx/rule.cxx +++ b/build/cxx/rule.cxx @@ -15,6 +15,7 @@ #include <butl/utility> // reverse_iterate #include <butl/fdstream> #include <butl/optional> +#include <butl/path-map> #include <build/scope> #include <build/variable> @@ -69,8 +70,10 @@ namespace build { for (target* t: l.prerequisite_targets) { - if (t != nullptr && - (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ())) + if (t == nullptr) + continue; + + if (t->is_a<lib> () || t->is_a<liba> () || t->is_a<libso> ()) append_lib_options (args, *t, var); } @@ -170,6 +173,161 @@ namespace build } } + // The strings used as the map key should be from the extension_pool. + // This way we can just compare pointers. + // + using ext_map = map<const string*, const target_type*>; + + static ext_map + build_ext_map (scope& r) + { + ext_map m; + + if (auto val = r["h.ext"]) + m[&extension_pool.find (val.as<const string&> ())] = &h::static_type; + + if (auto val = r["c.ext"]) + m[&extension_pool.find (val.as<const string&> ())] = &c::static_type; + + if (auto val = r["hxx.ext"]) + m[&extension_pool.find (val.as<const string&> ())] = &hxx::static_type; + + if (auto val = r["ixx.ext"]) + m[&extension_pool.find (val.as<const string&> ())] = &ixx::static_type; + + if (auto val = r["txx.ext"]) + m[&extension_pool.find (val.as<const string&> ())] = &txx::static_type; + + if (auto val = r["cxx.ext"]) + m[&extension_pool.find (val.as<const string&> ())] = &cxx::static_type; + + return m; + } + + // Mapping of include prefixes (e.g., foo in <foo/bar>) 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., <foo/more/bar> + // 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<dir_path>; + + 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<const list_value&> ()); + + // 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 = dir_path (i->value); + } + 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., <foo/bar>. + // 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<lib> () || t->is_a<liba> () || t->is_a<libso> ()) + 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 prerequsite + // libraries. + // + for (prerequisite& p: group_prerequisites (t)) + { + target& pt (*p.target); // Already searched and matched. + + if (pt.is_a<lib> () || pt.is_a<liba> () || pt.is_a<libso> ()) + 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. @@ -212,37 +370,6 @@ namespace build return r; } - // The strings used as the map key should be from the extension_pool. - // This way we can just compare pointers. - // - using ext_map = map<const string*, const target_type*>; - - static ext_map - build_ext_map (scope& r) - { - ext_map m; - - if (auto val = r["h.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &h::static_type; - - if (auto val = r["c.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &c::static_type; - - if (auto val = r["hxx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &hxx::static_type; - - if (auto val = r["ixx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &ixx::static_type; - - if (auto val = r["txx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &txx::static_type; - - if (auto val = r["cxx.ext"]) - m[&extension_pool.find (val.as<const string&> ())] = &cxx::static_type; - - return m; - } - void compile:: inject_prerequisites (action a, target& t, const cxx& s, scope& ds) const { @@ -276,7 +403,7 @@ namespace build if (t.is_a<objso> ()) args.push_back ("-fPIC"); - args.push_back ("-MM"); // @@ Change to -M + 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. @@ -301,8 +428,9 @@ namespace build { process pr (args.data (), false, false, true); ifdstream is (pr.in_ofd); + prefix_map pm; // Build it lazily. - for (bool first (true); !is.eof (); ) + for (bool first (true), second (true); !is.eof (); ) { string l; getline (is, l); @@ -321,11 +449,35 @@ namespace build break; assert (l[0] == '*' && l[1] == ':' && l[2] == ' '); - next (l, (pos = 3)); // Skip the source file. first = false; + + // While normally we would have the source file on the + // first line, if too long, it will be move 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. + } + + auto g ( + make_exception_guard ( + [](const target& s) + { + info << "while extracting dependencies from " << s; + }, + s)); + while (pos != l.size ()) { path f (next (l, pos)); @@ -333,8 +485,44 @@ namespace build if (!f.absolute ()) { - level5 ([&]{trace << "skipping generated/non-existent " << f;}); - continue; + // 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;}); @@ -30,7 +30,7 @@ namespace build using target_rule_map = std::unordered_map< std::type_index, - butl::prefix_multimap<std::string, std::reference_wrapper<rule>, '.'>>; + butl::prefix_map<std::string, std::reference_wrapper<rule>, '.'>>; using operation_rule_map = std::unordered_map<operation_id, target_rule_map>; diff --git a/build/target b/build/target index ddf5a2e..1de521e 100644 --- a/build/target +++ b/build/target @@ -552,6 +552,9 @@ namespace build // the directory. Similarly, if name_suffix is not NULL, add it after // the name part and before the extension. // + // Finally, if the path was already assigned to this target, then + // this function verifies that the two are the same. + // void derive_path (const char* default_ext = nullptr, const char* name_prefix = nullptr, diff --git a/build/target.cxx b/build/target.cxx index 87194fb..3b030ee 100644 --- a/build/target.cxx +++ b/build/target.cxx @@ -285,7 +285,15 @@ namespace build n += *ext; } - path (dir / path_type (move (n))); + path_type p (dir / path_type (move (n))); + const path_type& ep (path ()); + + if (ep.empty ()) + path (p); + else if (p != ep) + fail << "path mismatch for target " << *this << + info << "assigned '" << ep << "'" << + info << "derived '" << p << "'"; } // file_target diff --git a/tests/cli/build/bootstrap.build b/tests/cli/build/bootstrap.build deleted file mode 100644 index 9e91c9a..0000000 --- a/tests/cli/build/bootstrap.build +++ /dev/null @@ -1,2 +0,0 @@ -project = cli-test -using config diff --git a/tests/cli/buildfile b/tests/cli/buildfile deleted file mode 100644 index d71a677..0000000 --- a/tests/cli/buildfile +++ /dev/null @@ -1,6 +0,0 @@ -hxx.ext = -cxx.ext = cpp -ixx.ext = ipp - -exe{driver}: cxx{driver test} -cxx{test}: cli{test} diff --git a/tests/cli/driver.cpp b/tests/cli/driver.cpp deleted file mode 100644 index 70b4146..0000000 --- a/tests/cli/driver.cpp +++ /dev/null @@ -1,4 +0,0 @@ -int -main () -{ -} diff --git a/tests/cli/lib/libtest/build/bootstrap.build b/tests/cli/lib/libtest/build/bootstrap.build new file mode 100644 index 0000000..67a5f1a --- /dev/null +++ b/tests/cli/lib/libtest/build/bootstrap.build @@ -0,0 +1,2 @@ +project = cli-lib-libtest +using config diff --git a/tests/cli/lib/libtest/build/export.build b/tests/cli/lib/libtest/build/export.build new file mode 100644 index 0000000..e8b12b3 --- /dev/null +++ b/tests/cli/lib/libtest/build/export.build @@ -0,0 +1,6 @@ +$out_root/: +{ + include test/ +} + +export $out_root/test/lib{test} diff --git a/tests/cli/lib/libtest/build/root.build b/tests/cli/lib/libtest/build/root.build new file mode 100644 index 0000000..f2f5ca6 --- /dev/null +++ b/tests/cli/lib/libtest/build/root.build @@ -0,0 +1,7 @@ +using cxx + +hxx.ext = +ixx.ext = ipp +cxx.ext = cpp + +using cli diff --git a/tests/cli/lib/libtest/buildfile b/tests/cli/lib/libtest/buildfile new file mode 100644 index 0000000..d00a137 --- /dev/null +++ b/tests/cli/lib/libtest/buildfile @@ -0,0 +1,2 @@ +.: test/ +include test/ diff --git a/tests/cli/lib/libtest/test/buildfile b/tests/cli/lib/libtest/test/buildfile new file mode 100644 index 0000000..5325f14 --- /dev/null +++ b/tests/cli/lib/libtest/test/buildfile @@ -0,0 +1,13 @@ +lib{test}: cxx{utility} cxx{test} extra/cxx{test} +cxx{test} hxx{test}: cli{test} + +extra/: +{ + cxx{test} hxx{test}: cli{test} + cli.options += --cli-namespace test::extra::cli +} + +cxx.poptions += -I$out_root -I$src_root +lib{test}: cxx.export.poptions = -I$out_root -I$src_root + +cli.options += --cli-namespace test::cli diff --git a/tests/cli/lib/libtest/test/extra/test.cli b/tests/cli/lib/libtest/test/extra/test.cli new file mode 100644 index 0000000..8408402 --- /dev/null +++ b/tests/cli/lib/libtest/test/extra/test.cli @@ -0,0 +1,11 @@ +namespace test +{ + namespace extra + { + class options + { + bool --help; + bool --version; + }; + } +} diff --git a/tests/cli/lib/libtest/test/test.cli b/tests/cli/lib/libtest/test/test.cli new file mode 100644 index 0000000..8bc51e8 --- /dev/null +++ b/tests/cli/lib/libtest/test/test.cli @@ -0,0 +1,8 @@ +namespace test +{ + class options + { + bool --help; + bool --version; + }; +} diff --git a/tests/cli/lib/libtest/test/utility.cpp b/tests/cli/lib/libtest/test/utility.cpp new file mode 100644 index 0000000..7c59218 --- /dev/null +++ b/tests/cli/lib/libtest/test/utility.cpp @@ -0,0 +1,6 @@ +#include <test/test> + +void +f () +{ +} diff --git a/tests/cli/lib/test/build/bootstrap.build b/tests/cli/lib/test/build/bootstrap.build new file mode 100644 index 0000000..e83e189 --- /dev/null +++ b/tests/cli/lib/test/build/bootstrap.build @@ -0,0 +1,2 @@ +project = cli-lib-test +using config diff --git a/tests/cli/lib/test/build/root.build b/tests/cli/lib/test/build/root.build new file mode 100644 index 0000000..2dda614 --- /dev/null +++ b/tests/cli/lib/test/build/root.build @@ -0,0 +1,7 @@ +using cxx + +hxx.ext = h +ixx.ext = inl +cxx.ext = C + +using cli diff --git a/tests/cli/lib/test/buildfile b/tests/cli/lib/test/buildfile new file mode 100644 index 0000000..fc0e552 --- /dev/null +++ b/tests/cli/lib/test/buildfile @@ -0,0 +1,6 @@ +import libs += cli-lib-libtest + +exe{driver}: cxx{driver} cxx{test} $libs +cxx{test} hxx{test}: cli{test} + +cxx.poptions = -I$out_root diff --git a/tests/cli/lib/test/driver.C b/tests/cli/lib/test/driver.C new file mode 100644 index 0000000..ee2171f --- /dev/null +++ b/tests/cli/lib/test/driver.C @@ -0,0 +1,8 @@ +#include "test.h" +#include <test/test> +#include <test/extra/test> + +int +main () +{ +} diff --git a/tests/cli/test.cli b/tests/cli/lib/test/test.cli index db3cfb8..db3cfb8 100644 --- a/tests/cli/test.cli +++ b/tests/cli/lib/test/test.cli diff --git a/tests/cli/simple/build/bootstrap.build b/tests/cli/simple/build/bootstrap.build new file mode 100644 index 0000000..e2fd93f --- /dev/null +++ b/tests/cli/simple/build/bootstrap.build @@ -0,0 +1,2 @@ +project = cli-simple +using config diff --git a/tests/cli/build/root.build b/tests/cli/simple/build/root.build index 8e910cf..8e910cf 100644 --- a/tests/cli/build/root.build +++ b/tests/cli/simple/build/root.build diff --git a/tests/cli/simple/buildfile b/tests/cli/simple/buildfile new file mode 100644 index 0000000..5a940ce --- /dev/null +++ b/tests/cli/simple/buildfile @@ -0,0 +1,8 @@ +hxx.ext = +cxx.ext = cpp +ixx.ext = ipp + +cxx.poptions = -I$out_root + +exe{driver}: cxx{driver} cxx{test} +cxx{test} hxx{test}: cli{test} diff --git a/tests/cli/simple/driver.cpp b/tests/cli/simple/driver.cpp new file mode 100644 index 0000000..ef9cc60 --- /dev/null +++ b/tests/cli/simple/driver.cpp @@ -0,0 +1,6 @@ +#include "test" + +int +main () +{ +} diff --git a/tests/cli/simple/test.cli b/tests/cli/simple/test.cli new file mode 100644 index 0000000..db3cfb8 --- /dev/null +++ b/tests/cli/simple/test.cli @@ -0,0 +1,5 @@ +class options +{ + bool --help; + bool --version; +}; |