diff --git a/README.md b/README.md index 8659cf4..14e976c 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ h{config}: in{config} } ``` -As key-value pairs in the `autoconf.substitutions` map (which is an alias for -the `in.substitutions` variable; see the [`in`][module-in] module for +Or as key-value pairs in the `autoconf.substitutions` map (which is an alias +for the `in.substitutions` variable; see the [`in`][module-in] module for details): ``` @@ -113,10 +113,74 @@ h{config}: in{config} } ``` -Note that an implementation of a check may depend on another check. As a -result, substitutions should not be conditional at the preprocessor level -(unless all the checks are part of the same condition). Nor should the -results of checks be adjusted until after the last check. For example: +While this module provides widely used aliases for some checks, it doesn't +attempt to cover every project's idiosyncrasies. Instead, it provides a +mechanism for creating project-specific aliases for built-in +checks. Specifically, the desired aliases can be specified as key-value pairs +in the `autoconf.aliases` map with key being the new name and the value -- +old/existing. For example: + +``` +/* config.h.in */ + +#undef HAVE_AF_UNIX_H +#undef MY_SSIZE_T +``` + +``` +h{config}: in{config} +{ + autoconf.aliases = HAVE_AF_UNIX_H@HAVE_AFUNIX_H + autoconf.aliases += MY_SSIZE_T@ssize_t +} +``` + +The built-in checks can be prefixed in order to avoid clashes with similarly +named macros in other headers. This is an especially good idea if the +resulting header is public. To enable this, we specify the prefix with +the `autoconf.prefix` variable and then use the prefixed versions of +the options in the `config.h.in` file. For example: + +``` +/* config.h.in */ + +#undef LIBFOO_HAVE_STRLCPY +#undef LIBFOO_HAVE_STRLCAT +``` + +``` +h{config}: in{config} +{ + autoconf.prefix = LIBFOO_ +} +``` + +Note that `autoconf.prefix` only affects the lookup of the built-in checks. +Custom substitutions and overrides of built-in checks must include the +prefix. Similarly, both names in `autoconf.aliases` must be specified +with the prefix (unless unprefixable; see below). For example: + +``` +h{config}: in{config} +{ + autoconf.prefix = LIBFOO_ + + LIBFOO_HAVE_STRLCPY = true + + autoconf.aliases = LIBFOO_SSIZE_T@ssize_t +} +``` + +Note also that some built-in check names are *unprefixable*, usually because +they are standard macro names (for example, `BYTE_ORDER`) that on some +platforms come from system headers (for example, `` on FreeBSD). +Such checks have `!` after their names on the first line of their +implementation files (for example, `// BYTE_ORDER!`). + +An implementation of a check may depend on another check. As a result, +substitutions should not be conditional at the preprocessor level (unless all +the checks are part of the same condition). Nor should the results of checks +be adjusted until after the last check. For example: ``` #ifndef _WIN32 @@ -152,46 +216,6 @@ Below is the correct way to achieve the above semantics: #endif ``` -The built-in checks can be prefixed in order to avoid clashes with similarly -named macros in other headers. This is an especially good idea if the -resulting header is public. To enable this, we specify the prefix with -the `autoconf.prefix` variable and then use the prefixed versions of -the options in the `config.h.in` file. For example: - -``` -/* config.h.in */ - -#undef LIBFOO_HAVE_STRLCPY -#undef LIBFOO_HAVE_STRLCAT -``` - -``` -h{config}: in{config} -{ - autoconf.prefix = LIBFOO_ -} -``` - -Note that `autoconf.prefix` only affects the lookup of the built-in checks. -Custom substitutions and overrides of built-in checks must include the -prefix. For example: - -``` -h{config}: in{config} -{ - autoconf.prefix = LIBFOO_ - - LIBFOO_HAVE_STRLCPY = true -} -``` - -Note also that some built-in check names are *unprefixable*, usually because -they are standard macro names (for example, `BYTE_ORDER`) that on some -platforms come from system headers (for example, `` on FreeBSD). -Such checks have `!` after their names on the first line of their -implementation files (for example, `// BYTE_ORDER!`). - - ## Adding new checks To add a check for a new configuration option `` simply create the diff --git a/libbuild2-autoconf-tests/module/testscript b/libbuild2-autoconf-tests/module/testscript index 6e2af25..e666e58 100644 --- a/libbuild2-autoconf-tests/module/testscript +++ b/libbuild2-autoconf-tests/module/testscript @@ -424,3 +424,77 @@ cat config.h >>EOO #define _POSIX_SOURCE 1 #define FOO 1 EOO + +: alias-map +: +mkdir build; +ln -s ../../bootstrap.build ../../root.build build/; +cat <=config.h.in; + #undef TEST_DUMMY1_H + + #undef TEST_DUMMY2_H + + #undef TEST_DUMMY3_H + EOI +$* <>EOO + #define zzz_TEST_DUMMY1_H 1 + + #undef TEST_DUMMY1_H + #ifdef zzz_TEST_DUMMY1_H + # define TEST_DUMMY1_H zzz_TEST_DUMMY1_H + #endif + + #define zzz_TEST_DUMMY2_H 20 + + #undef TEST_DUMMY2_H + #ifdef zzz_TEST_DUMMY2_H + # define TEST_DUMMY2_H zzz_TEST_DUMMY2_H + #endif + + #define zzz_TEST_DUMMY3_H 1 + + #undef TEST_DUMMY3_H + #define TEST_DUMMY3_H zzz_TEST_DUMMY3_H + EOO + +: alias-map-prefix +: +mkdir build; +ln -s ../../bootstrap.build ../../root.build build/; +cat <=config.h.in; + #undef PREFIX_TEST_DUMMY1_H + + #undef PREFIX_TEST_DUMMY3_H + EOI +$* <>EOO + #define PREFIX_zzz_TEST_DUMMY1_H 1 + + #undef PREFIX_TEST_DUMMY1_H + #ifdef PREFIX_zzz_TEST_DUMMY1_H + # define PREFIX_TEST_DUMMY1_H PREFIX_zzz_TEST_DUMMY1_H + #endif + + #define zzz_TEST_DUMMY3_H 1 + + #undef PREFIX_TEST_DUMMY3_H + #define PREFIX_TEST_DUMMY3_H zzz_TEST_DUMMY3_H + EOO diff --git a/libbuild2-autoconf/libbuild2/autoconf/init.cxx b/libbuild2-autoconf/libbuild2/autoconf/init.cxx index 6ccd7b0..56d4397 100644 --- a/libbuild2-autoconf/libbuild2/autoconf/init.cxx +++ b/libbuild2-autoconf/libbuild2/autoconf/init.cxx @@ -48,6 +48,14 @@ namespace build2 // vp.insert_alias (*vp.find ("in.substitutions"), "autoconf.substitutions"); + + // Alias map. The key is the new name and the value is the aliased + // (old) name. + // + // Note that this map is only consulted when resolving build-in checks + // and the names should include the prefix, if any. + // + vp.insert> ("autoconf.aliases"); } // Register the rule. diff --git a/libbuild2-autoconf/libbuild2/autoconf/rule.cxx b/libbuild2-autoconf/libbuild2/autoconf/rule.cxx index ded2244..d9c19e6 100644 --- a/libbuild2-autoconf/libbuild2/autoconf/rule.cxx +++ b/libbuild2-autoconf/libbuild2/autoconf/rule.cxx @@ -19,6 +19,8 @@ namespace build2 { enum class flavor {autoconf, cmake, meson}; + using alias_map = map; + // Wrap the in::rule's perform_update recipe into a data-carrying recipe. // // To optimize this a bit further we will call in::rule::perform_update() @@ -29,7 +31,8 @@ namespace build2 { autoconf::flavor flavor; string prefix; - map checks; // Checks already seen. + const alias_map* aliases; + map checks; // Checks already seen. const autoconf::rule& rule; @@ -89,11 +92,12 @@ namespace build2 } } - // Get the prefix if any. + // Get the prefix and aliases, if any. // string p (cast_empty (t["autoconf.prefix"])); + const alias_map* a (cast_null (t["autoconf.aliases"])); - return match_data {f, move (p), {}, *this}; + return match_data {f, move (p), a, {}, *this}; } return r; @@ -502,10 +506,7 @@ namespace build2 // looking it up. So we store prefixless. Actually, it's convenient // to store both. // - // 2. Look in the catalog and fall through if not found. - // - // 3. If found, then check for a custom value falling through if - // found. + // 2. Check for a custom value falling through if found. // // Here things get a bit tricky: while a stray HAVE_* buildfile // variable is unlikely, something like const or volatile is @@ -515,22 +516,29 @@ namespace build2 // While this clashes with the in.null semantics, it's just as // easy to set the variable to the real default value as to null. // + // + // 3. Look in the catalog and fall through if not found. + // // 4. Return the build-in value from the catalog. // - const char* pn (nullptr); // Prefixless name. - - const string& p (md.prefix); - if (!p.empty ()) + auto deprefix = [&md] (const string& n) -> const char* { - // Note that if there is no prefix, then we only look for an - // unprefixable built-in check. - // - if (n.size () > p.size () && n.compare (0, p.size (), p) == 0) - pn = n.c_str () + p.size (); - } - else - pn = n.c_str (); + const string& p (md.prefix); + if (!p.empty ()) + { + return (n.size () > p.size () && n.compare (0, p.size (), p) == 0 + ? n.c_str () + p.size () + : nullptr); + } + else + return n.c_str (); + }; + + // Note that if there is no prefix in the name, then we only look for + // an unprefixable built-in check. + // + const char* pn (deprefix (n)); // Prefixless name. const char* en (pn != nullptr ? pn : n.c_str ()); // Effective name. // Note: this line must be recognizable by substitute_special(). @@ -579,13 +587,11 @@ namespace build2 // Note: original name in the custom substitution lookup. // - const check* c (find (en, pn == nullptr)); - - if (c != nullptr && !custom (n)) + if (!custom (n)) { - // The plan is as follows: keep adding base checks (suppressing - // duplicates) followed by the main check while prefixing all the - // already seen names (unless unprefixable). + // The overall plan is as follows: add checks (to r; suppressing + // duplicates) while prefixing all the already seen names (in ns; + // unless unprefixable). // string r; small_vector ns; @@ -605,10 +611,12 @@ namespace build2 r += v; }; - auto prefix = [&p, &ns, &r, b = size_t (0)] () mutable + auto prefix = [&md, &ns, &r, b = size_t (0)] () mutable { auto sep = [] (char c) { return !alnum (c) && c != '_'; }; + const string& p (md.prefix); + for (const string& n: ns) { size_t m (n.size ()); // Prefix-less name length. @@ -627,7 +635,7 @@ namespace build2 b = r.size (); }; - // Base checks. + // Append base checks. // // @@ TODO: detect cycles (currently we just prune, like an // include guard). @@ -640,16 +648,16 @@ namespace build2 // auto base = [this, &l, &t, a, smap, &null, - &md, &p, &ns, + &md, &ns, &find, &custom, - &append, &prefix] (const string& n, + &append, &prefix] (const string& dn, const char* bs, const auto& base) -> void { auto df = make_diag_frame ( - [&n] (const diag_record& dr) + [&dn] (const diag_record& dr) { - dr << info << "while resolving base options for " << n; + dr << info << "while resolving base options for " << dn; }); for (size_t b (0), e (0); next_word (bs, b, e); ) @@ -686,7 +694,7 @@ namespace build2 // bool up (strchr (c->modifier, '!') != nullptr); - string n ((up ? string () : p) + pn); + string n ((up ? string () : md.prefix) + pn); md.checks.emplace (pn, n); @@ -701,7 +709,7 @@ namespace build2 append (c->value); } - if (!p.empty ()) + if (!md.prefix.empty ()) { if (!up) ns.push_back (move (pn)); @@ -711,22 +719,86 @@ namespace build2 } }; - if (*c->base != '\0') - base (n, c->base, base); - - // Main check. + // Return true if the built-in check is prefix-compatible with the + // substitution. // - append (c->value); - - if (!p.empty ()) + auto prefix_compatible = [&md] (const check* c, const char* pn) { - if (pn != nullptr) // Not unprefixable. - ns.push_back (pn); + return (md.prefix.empty () || // No prefix. + strchr (c->modifier, '!') == nullptr || // Prefixable. + pn == nullptr); // Unprefixed. + }; - prefix (); + // See if this is an alias. + // + if (md.aliases != nullptr) + { + auto i (md.aliases->find (n)); + if (i != md.aliases->end ()) + { + // Reduce this to the base with a synthesized "derived" check + // (pretty much how we would implement it if it were a built-in + // check). + // + const string& an (i->second); + + pn = deprefix (an); + en = pn != nullptr ? pn : an.c_str (); + + const check* c (find (en, pn == nullptr)); + if (c == nullptr || !prefix_compatible (c, pn)) + fail (l) << "unknown aliased option " << en << + info << "while resolving alias " << n; + + base (n, en, base); + + // As a heuristics, for an unprefixable check we do a straight + // #define since it may not be a macro (see ssize_t). + // + // Note also that all the names are already prefixed, if + // necessary. + // + if (strchr (c->modifier, '!') != nullptr) + { + append (("#undef " + n + '\n' + + "#define " + n + ' ' + an + '\n').c_str ()); + } + else + { + append (("#undef " + n + '\n' + + "#ifdef " + an + '\n' + + "# define " + n + ' ' + an + '\n' + + "#endif\n").c_str ()); + } + + return r; + } } - return r; + const check* c (find (en, pn == nullptr)); + if (c != nullptr && prefix_compatible (c, pn)) + { + // The plan is as follows: keep adding base checks (suppressing + // duplicates) followed by the main check while prefixing all the + // already seen names (unless unprefixable). + // + if (*c->base != '\0') + base (n, c->base, base); + + // Main check. + // + append (c->value); + + if (!md.prefix.empty ()) + { + if (pn != nullptr) // Not unprefixable. + ns.push_back (pn); + + prefix (); + } + + return r; + } } }