From 95239b7c5404965d4f5ef997b5b75bf542a25192 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 25 Jun 2015 13:41:19 +0200 Subject: Part one of dependency injection with auto-generation support --- build/algorithm.cxx | 4 +- build/config/utility | 2 +- build/cxx/module.cxx | 6 +- build/cxx/rule.cxx | 264 ++++++++++++++++++++++++---- build/rule | 2 +- build/target | 3 + build/target.cxx | 10 +- tests/cli/build/bootstrap.build | 2 - tests/cli/build/root.build | 3 - tests/cli/buildfile | 6 - tests/cli/driver.cpp | 4 - tests/cli/lib/libtest/build/bootstrap.build | 2 + tests/cli/lib/libtest/build/export.build | 6 + tests/cli/lib/libtest/build/root.build | 7 + tests/cli/lib/libtest/buildfile | 2 + tests/cli/lib/libtest/test/buildfile | 13 ++ tests/cli/lib/libtest/test/extra/test.cli | 11 ++ tests/cli/lib/libtest/test/test.cli | 8 + tests/cli/lib/libtest/test/utility.cpp | 6 + tests/cli/lib/test/build/bootstrap.build | 2 + tests/cli/lib/test/build/root.build | 7 + tests/cli/lib/test/buildfile | 6 + tests/cli/lib/test/driver.C | 8 + tests/cli/lib/test/test.cli | 5 + tests/cli/simple/build/bootstrap.build | 2 + tests/cli/simple/build/root.build | 3 + tests/cli/simple/buildfile | 8 + tests/cli/simple/driver.cpp | 6 + tests/cli/simple/test.cli | 5 + tests/cli/test.cli | 5 - 30 files changed, 352 insertions(+), 66 deletions(-) delete mode 100644 tests/cli/build/bootstrap.build delete mode 100644 tests/cli/build/root.build delete mode 100644 tests/cli/buildfile delete mode 100644 tests/cli/driver.cpp create mode 100644 tests/cli/lib/libtest/build/bootstrap.build create mode 100644 tests/cli/lib/libtest/build/export.build create mode 100644 tests/cli/lib/libtest/build/root.build create mode 100644 tests/cli/lib/libtest/buildfile create mode 100644 tests/cli/lib/libtest/test/buildfile create mode 100644 tests/cli/lib/libtest/test/extra/test.cli create mode 100644 tests/cli/lib/libtest/test/test.cli create mode 100644 tests/cli/lib/libtest/test/utility.cpp create mode 100644 tests/cli/lib/test/build/bootstrap.build create mode 100644 tests/cli/lib/test/build/root.build create mode 100644 tests/cli/lib/test/buildfile create mode 100644 tests/cli/lib/test/driver.C create mode 100644 tests/cli/lib/test/test.cli create mode 100644 tests/cli/simple/build/bootstrap.build create mode 100644 tests/cli/simple/build/root.build create mode 100644 tests/cli/simple/buildfile create mode 100644 tests/cli/simple/driver.cpp create mode 100644 tests/cli/simple/test.cli delete mode 100644 tests/cli/test.cli 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 ()) { - 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 // reverse_iterate #include #include +#include #include #include @@ -69,8 +70,10 @@ namespace build { for (target* t: l.prerequisite_targets) { - if (t != nullptr && - (t->is_a () || t->is_a () || t->is_a ())) + if (t == nullptr) + continue; + + if (t->is_a () || t->is_a () || t->is_a ()) 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; + + 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 = 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., . + // 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 prerequsite + // libraries. + // + 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. @@ -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; - - 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; - } - void compile:: inject_prerequisites (action a, target& t, const cxx& s, scope& ds) const { @@ -276,7 +403,7 @@ namespace build if (t.is_a ()) 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;}); diff --git a/build/rule b/build/rule index c7134e1..7a4d4d7 100644 --- a/build/rule +++ b/build/rule @@ -30,7 +30,7 @@ namespace build using target_rule_map = std::unordered_map< std::type_index, - butl::prefix_multimap, '.'>>; + butl::prefix_map, '.'>>; using operation_rule_map = std::unordered_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/build/root.build b/tests/cli/build/root.build deleted file mode 100644 index 8e910cf..0000000 --- a/tests/cli/build/root.build +++ /dev/null @@ -1,3 +0,0 @@ -using cxx -using cli - 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 + +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 +#include + +int +main () +{ +} diff --git a/tests/cli/lib/test/test.cli b/tests/cli/lib/test/test.cli new file mode 100644 index 0000000..db3cfb8 --- /dev/null +++ b/tests/cli/lib/test/test.cli @@ -0,0 +1,5 @@ +class options +{ + bool --help; + bool --version; +}; 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/simple/build/root.build b/tests/cli/simple/build/root.build new file mode 100644 index 0000000..8e910cf --- /dev/null +++ b/tests/cli/simple/build/root.build @@ -0,0 +1,3 @@ +using cxx +using cli + 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; +}; diff --git a/tests/cli/test.cli b/tests/cli/test.cli deleted file mode 100644 index db3cfb8..0000000 --- a/tests/cli/test.cli +++ /dev/null @@ -1,5 +0,0 @@ -class options -{ - bool --help; - bool --version; -}; -- cgit v1.1