Add 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. See the README for details.
This commit is contained in:
parent
3a324ad757
commit
367c4529d2
116
README.md
116
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, `<sys/endian.h>` 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, `<sys/endian.h>` 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 `<NAME>` simply create the
|
||||
|
@ -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 <<EOI >=config.h.in;
|
||||
#undef TEST_DUMMY1_H
|
||||
|
||||
#undef TEST_DUMMY2_H
|
||||
|
||||
#undef TEST_DUMMY3_H
|
||||
EOI
|
||||
$* <<EOI &config.h &config.h.d;
|
||||
./: h{config}: in{config}
|
||||
{
|
||||
autoconf.aliases = TEST_DUMMY1_H@zzz_TEST_DUMMY1_H
|
||||
autoconf.aliases += TEST_DUMMY2_H@zzz_TEST_DUMMY2_H
|
||||
autoconf.aliases += TEST_DUMMY3_H@zzz_TEST_DUMMY3_H
|
||||
|
||||
zzz_TEST_DUMMY2_H = '#define zzz_TEST_DUMMY2_H 20'
|
||||
}
|
||||
EOI
|
||||
cat config.h >>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 <<EOI >=config.h.in;
|
||||
#undef PREFIX_TEST_DUMMY1_H
|
||||
|
||||
#undef PREFIX_TEST_DUMMY3_H
|
||||
EOI
|
||||
$* <<EOI &config.h &config.h.d;
|
||||
./: h{config}: in{config}
|
||||
{
|
||||
autoconf.prefix = PREFIX_
|
||||
|
||||
autoconf.aliases = PREFIX_TEST_DUMMY1_H@PREFIX_zzz_TEST_DUMMY1_H
|
||||
autoconf.aliases += PREFIX_TEST_DUMMY3_H@zzz_TEST_DUMMY3_H
|
||||
}
|
||||
EOI
|
||||
cat config.h >>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
|
||||
|
@ -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<map<string, string>> ("autoconf.aliases");
|
||||
}
|
||||
|
||||
// Register the rule.
|
||||
|
@ -19,6 +19,8 @@ namespace build2
|
||||
{
|
||||
enum class flavor {autoconf, cmake, meson};
|
||||
|
||||
using alias_map = map<string, string>;
|
||||
|
||||
// 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,6 +31,7 @@ namespace build2
|
||||
{
|
||||
autoconf::flavor flavor;
|
||||
string prefix;
|
||||
const alias_map* aliases;
|
||||
map<string, string> 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<string> (t["autoconf.prefix"]));
|
||||
const alias_map* a (cast_null<alias_map> (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.
|
||||
|
||||
auto deprefix = [&md] (const string& n) -> const char*
|
||||
{
|
||||
const string& p (md.prefix);
|
||||
|
||||
if (!p.empty ())
|
||||
{
|
||||
// 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 ();
|
||||
return (n.size () > p.size () && n.compare (0, p.size (), p) == 0
|
||||
? n.c_str () + p.size ()
|
||||
: nullptr);
|
||||
}
|
||||
else
|
||||
pn = n.c_str ();
|
||||
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<string, 1> 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,6 +719,69 @@ namespace build2
|
||||
}
|
||||
};
|
||||
|
||||
// Return true if the built-in check is prefix-compatible with the
|
||||
// substitution.
|
||||
//
|
||||
auto prefix_compatible = [&md] (const check* c, const char* pn)
|
||||
{
|
||||
return (md.prefix.empty () || // No prefix.
|
||||
strchr (c->modifier, '!') == nullptr || // Prefixable.
|
||||
pn == nullptr); // Unprefixed.
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -718,7 +789,7 @@ namespace build2
|
||||
//
|
||||
append (c->value);
|
||||
|
||||
if (!p.empty ())
|
||||
if (!md.prefix.empty ())
|
||||
{
|
||||
if (pn != nullptr) // Not unprefixable.
|
||||
ns.push_back (pn);
|
||||
@ -729,6 +800,7 @@ namespace build2
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return in::rule::lookup (l, a, t, n, nullopt, smap, null);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user