Initial module implementation

At this stage it handles Autoconf (#undef), CMake (#cmakedefine), and Meson
(#mesondefine) special lines but does not have any built-in tests.
This commit is contained in:
Boris Kolpackov 2021-11-01 13:43:47 +02:00
parent 19fcde9ddc
commit ef54e197e9
30 changed files with 929 additions and 3 deletions

View File

@ -8,8 +8,8 @@ CMake and Meson variants.
Similar to Autoconf, the module provides built-in support for a number of Similar to Autoconf, the module provides built-in support for a number of
common `HAVE_*` configuration options. However, the values of these options common `HAVE_*` configuration options. However, the values of these options
are not discovered by probing, such as trying to compile a test program to are not discovered by dynamic probing, such as trying to compile a test
check if the header is present. Instead, they are set to the expected values program to check if the header is present. Instead, they are set to static
based on the platform/compiler macros. expected values based on the platform/compiler macros.
[module-in]: https://build2.org/build2/doc/build2-build-system-manual.xhtml#module-in [module-in]: https://build2.org/build2/doc/build2-build-system-manual.xhtml#module-in

24
libbuild2-autoconf-tests/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

View File

@ -0,0 +1 @@
../AUTHORS

View File

@ -0,0 +1 @@
../LICENSE

View File

@ -0,0 +1,3 @@
# libbuild2-autoconf-tests
Tests package for the Autoconf emulation build system module for `build2`.

View File

@ -0,0 +1 @@
./: testscript

View File

@ -0,0 +1,208 @@
# Note: --silent rather than --quiet to suppress module update diagnostics.
#
test.options += --no-default-options --serial-stop --silent --buildfile -
# We disable bdep auto-synchronization since we will potentially be updating
# the module from multiple parallel tests. Note that we've made sure it is
# up-to-date by pre-loading it in the tests project (see root.build for
# details).
#
+export BDEP_SYNC=0
# Note that we are using the libbuild2-autoconf-tests project as our
# amalgamation in order to get the module import location and config.c.
#
+cat <<EOI >=bootstrap.build
project = basics
#amalgamation =
subprojects =
version = 1.2.3
EOI
+cat <<EOI >=root.build
using c
using autoconf
EOI
: basics-autoconf
:
mkdir build;
ln -s ../../bootstrap.build ../../root.build build/;
cat <<EOI >=config.h.in;
#define VERSION "@version@"
#undef TRUE
#undef FALSE
#undef ONE
#undef ZERO
#undef VALUE
# undef TRUE
#undef CUSTOM_LINE
#undef CUSTOM_BLOCK
EOI
$* <<EOI &config.h &config.h.d;
./: h{config}: in{config}
{
TRUE = true
FALSE = [bool] false
ONE = 1
ZERO = [uint64] 000
VALUE = [uint64] 0123
CUSTOM_LINE = '#define CUSTOM 123'
CUSTOM_BLOCK = \
'
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
'
}
EOI
cat config.h >>EOO
#define VERSION "1.2.3"
#define TRUE 1
#undef FALSE
#define ONE 1
#undef ZERO
#define VALUE 123
#define TRUE 1
#define CUSTOM 123
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
EOO
: basics-cmake
:
mkdir build;
ln -s ../../bootstrap.build ../../root.build build/;
cat <<EOI >=config.h.in;
#define VERSION "@version@"
#cmakedefine TRUE
#cmakedefine FALSE
#cmakedefine ONE
#cmakedefine ZERO
#cmakedefine VALUE
# cmakedefine TRUE
#cmakedefine CUSTOM_LINE
#cmakedefine CUSTOM_BLOCK
#cmakedefine CUSTOM_BLOCK @version@
#cmakedefine TRUE true
#cmakedefine FALSE false
#cmakedefine VALUE @VALUE@ /* @version@ */
EOI
$* <<EOI &config.h &config.h.d;
./: h{config}: in{config}
{
autoconf.flavor = cmake
TRUE = true
FALSE = [bool] false
ONE = 1
ZERO = [uint64] 000
VALUE = [uint64] 0123
CUSTOM_LINE = '#define CUSTOM 123'
CUSTOM_BLOCK = \
'
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
'
}
EOI
cat config.h >>EOO
#define VERSION "1.2.3"
#define TRUE 1
#undef FALSE
#define ONE 1
#undef ZERO
#define VALUE 123
#define TRUE 1
#define CUSTOM 123
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
#define TRUE true
#undef FALSE
#define VALUE 123 /* 1.2.3 */
EOO
: basics-meson
:
mkdir build;
ln -s ../../bootstrap.build ../../root.build build/;
cat <<EOI >=config.h.in;
#define VERSION "@version@"
#mesondefine TRUE
#mesondefine FALSE
#mesondefine ONE
#mesondefine ZERO
#mesondefine VALUE
# mesondefine TRUE
#mesondefine CUSTOM_LINE
#mesondefine CUSTOM_BLOCK
EOI
$* <<EOI &config.h &config.h.d;
./: h{config}: in{config}
{
autoconf.flavor = meson
TRUE = true
FALSE = [bool] false
ONE = 1
ZERO = [uint64] 000
VALUE = [uint64] 0123
CUSTOM_LINE = '#define CUSTOM 123'
CUSTOM_BLOCK = \
'
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
'
}
EOI
cat config.h >>EOO
#define VERSION "1.2.3"
#define TRUE 1
#undef FALSE
#define ONE 1
#undef ZERO
#define VALUE 123
#define TRUE 1
#define CUSTOM 123
/* Make sure we do not redefine CUSTOM. */
#ifndef CUSTOM
# define CUSTOM 123
#endif
EOO

View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@ -0,0 +1,6 @@
project = libbuild2-autoconf-tests
using version
using config
using test
using dist

View File

@ -0,0 +1,12 @@
# Load the module to make sure it is up-to-date before we start running the
# tests. Failed that, we may try to update it from multiple tests in parallel.
# Also, our tests use this project as their amalgamation in order to get the
# module import location (as well as the C module configuration).
#
using autoconf
using c
# @@ Should we be able to do just test = $build.path?
#
testscript{*}: test = $recall($build.path)

View File

@ -0,0 +1 @@
./: {*/ -build/} doc{README.md} legal{LICENSE AUTHORS} manifest

View File

@ -0,0 +1,11 @@
: 1
name: libbuild2-autoconf-tests
version: 0.1.0-a.0.z
project: build2
summary: Tests for the Autoconf emulation build system module for build2
license: MIT ; MIT License.
description-file: README.md
url: https://github.com/build2/libbuild2-autoconf
email: users@build2.org
depends: * build2 >= 0.15.0-
depends: * bpkg >= 0.15.0-

24
libbuild2-autoconf/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

1
libbuild2-autoconf/AUTHORS Symbolic link
View File

@ -0,0 +1 @@
../AUTHORS

1
libbuild2-autoconf/LICENSE Symbolic link
View File

@ -0,0 +1 @@
../LICENSE

View File

@ -0,0 +1 @@
../README.md

4
libbuild2-autoconf/build/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@ -0,0 +1,7 @@
project = libbuild2-autoconf
using version
using config
using test
using install
using dist

View File

@ -0,0 +1,6 @@
$out_root/
{
include libbuild2/autoconf/
}
export $out_root/libbuild2/autoconf/$import.target

View File

@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
if ($cxx.target.system == 'win32-msvc')
cc.poptions += -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS
if ($cxx.class == 'msvc')
cc.coptions += /wd4251 /wd4275 /wd4800
elif ($cxx.id == 'gcc')
cxx.coptions += -Wno-maybe-uninitialized -Wno-free-nonheap-object # libbutl

View File

@ -0,0 +1 @@
./: {*/ -build/} doc{README.md} legal{LICENSE AUTHORS} manifest

View File

@ -0,0 +1,3 @@
# Generated version header.
#
version.hxx

View File

@ -0,0 +1,47 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import impl_libs += build2%lib{build2} # Implied interface dependency.
import impl_libs += build2%lib{build2-in}
lib{build2-autoconf}: {hxx ixx txx cxx}{**} $impl_libs $intf_libs
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
obja{*}: cxx.poptions += -DLIBBUILD2_AUTOCONF_STATIC_BUILD
objs{*}: cxx.poptions += -DLIBBUILD2_AUTOCONF_SHARED_BUILD
# Export options.
#
lib{build2-autoconf}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
liba{build2-autoconf}: cxx.export.poptions += -DLIBBUILD2_AUTOCONF_STATIC
libs{build2-autoconf}: cxx.export.poptions += -DLIBBUILD2_AUTOCONF_SHARED
# For pre-releases use the complete version to make sure they cannot be used
# in place of another pre-release or the final version. See the version module
# for details on the version.* variable values.
#
if $version.pre_release
lib{build2-autoconf}: bin.lib.version = "-$version.project_id"
else
lib{build2-autoconf}: bin.lib.version = "-$version.major.$version.minor"
# Embed the build system core version as our load suffix.
#
libs{build2-autoconf}: bin.lib.load_suffix = "-$build.version.interface"
# Install into the libbuild2/autoconf/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/libbuild2/autoconf/
install.subdirs = true
}

View File

@ -0,0 +1,38 @@
#pragma once
// Normally we don't export class templates (but do complete specializations),
// inline functions, and classes with only inline member functions. Exporting
// classes that inherit from non-exported/imported bases (e.g., std::string)
// will end up badly. The only known workarounds are to not inherit or to not
// export. Also, MinGW GCC doesn't like seeing non-exported functions being
// used before their inline definition. The workaround is to reorder code. In
// the end it's all trial and error.
#if defined(LIBBUILD2_AUTOCONF_STATIC) // Using static.
# define LIBBUILD2_AUTOCONF_SYMEXPORT
#elif defined(LIBBUILD2_AUTOCONF_STATIC_BUILD) // Building static.
# define LIBBUILD2_AUTOCONF_SYMEXPORT
#elif defined(LIBBUILD2_AUTOCONF_SHARED) // Using shared.
# ifdef _WIN32
# define LIBBUILD2_AUTOCONF_SYMEXPORT __declspec(dllimport)
# else
# define LIBBUILD2_AUTOCONF_SYMEXPORT
# endif
#elif defined(LIBBUILD2_AUTOCONF_SHARED_BUILD) // Building shared.
# ifdef _WIN32
# define LIBBUILD2_AUTOCONF_SYMEXPORT __declspec(dllexport)
# else
# define LIBBUILD2_AUTOCONF_SYMEXPORT
# endif
#else
// If none of the above macros are defined, then we assume we are being used
// by some third-party build system that cannot/doesn't signal the library
// type. Note that this fallback works for both static and shared libraries
// provided the library only exports functions (in other words, no global
// exported data) and for the shared case the result will be sub-optimal
// compared to having dllimport. If, however, your library does export data,
// then you will probably want to replace the fallback with the (commented
// out) error since it won't work for the shared case.
//
# define LIBBUILD2_AUTOCONF_SYMEXPORT // Using static or shared.
#endif

View File

@ -0,0 +1,75 @@
#include <libbuild2/autoconf/init.hxx>
#include <libbuild2/scope.hxx>
#include <libbuild2/variable.hxx>
#include <libbuild2/diagnostics.hxx>
#include <libbuild2/autoconf/rule.hxx>
namespace build2
{
namespace autoconf
{
static const rule rule_;
//-
// The `autoconf` module.
//-
bool
init (scope& rs,
scope& bs,
const location& l,
bool,
bool,
module_init_extra&)
{
tracer trace ("autoconf::init");
l5 ([&]{trace << "for " << bs;});
// Load in.base (in.* variables, in{} target type).
//
load_module (rs, rs, "in.base", l);
// Enter variables.
//
{
auto& vp (rs.var_pool ());
// Configuration file flavor. Valid values are `autoconf` (default),
// `cmake`, and `meson`.
//
vp.insert<string> ("autoconf.flavor");
}
// Register the rule.
//
// @@ TODO: this will be ambiguous, for example, version.in rule. Note
// also that if we register it for cc{}, then it will always take
// precedence over version.in, which is probably something we don't
// want. In fact, we would have liked it to be of lower precedence
// since version.in will only match if there is dependency on
// manifest.
//
rs.insert_rule<file> (perform_update_id, "autoconf.in", rule_);
rs.insert_rule<file> (perform_clean_id, "autoconf.in", rule_);
rs.insert_rule<file> (configure_update_id, "autoconf.in", rule_);
return true;
}
static const module_functions mod_functions[] =
{
// NOTE: don't forget to also update the documentation in init.hxx if
// changing anything here.
{"autoconf", nullptr, init},
{nullptr, nullptr, nullptr}
};
const module_functions*
build2_autoconf_load ()
{
return mod_functions;
}
}
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
#include <libbuild2/module.hxx>
#include <libbuild2/autoconf/export.hxx>
namespace build2
{
namespace autoconf
{
//-
// Module `autoconf` does not require bootstrapping.
//-
extern "C" LIBBUILD2_AUTOCONF_SYMEXPORT const module_functions*
build2_autoconf_load ();
}
}

View File

@ -0,0 +1,355 @@
#include <libbuild2/autoconf/rule.hxx>
#include <libbuild2/target.hxx>
#include <libbuild2/algorithm.hxx>
#include <libbuild2/diagnostics.hxx>
namespace build2
{
namespace autoconf
{
enum class flavor {autoconf, cmake, meson};
struct match_data
{
autoconf::flavor flavor;
};
static_assert (sizeof (match_data) <= target::data_size,
"insufficient space");
rule::
rule ()
: in::rule ("autoconf.in 1",
"autoconf.in",
'@' /* symbol */,
false /* strict */)
{
}
recipe rule::
apply (action a, target& t) const
{
// Determine and cache the configuration file flavor.
//
flavor f (flavor::autoconf);
if (const string* s = cast_null<string> (t["autoconf.flavor"]))
{
if (*s == "cmake")
f = flavor::cmake;
else if (*s == "meson")
f = flavor::meson;
else if (*s != "autoconf")
fail << "invalid configuration file flavor '" << *s << "'";
}
t.data (match_data {f});
return in::rule::apply (a, t);
}
void rule::
process (const location& l,
action a, const target& t,
depdb& dd, size_t dd_skip,
string& s, size_t b,
const char* nl,
char sym,
bool strict,
const optional<string>& null) const
{
auto ws = [] (char c)
{
return c == ' ' || c == '\t';
};
auto skip_ws = [&ws] (const string& s, size_t& b)
{
for (; ws (s[b]); ++b) ;
};
// Scan a C identifier returning its size. Return 0 if it's not a valid
// identifier or is followed by a non-whitespace.
//
auto read_id = [&ws] (const string& s, size_t b) -> size_t
{
size_t i (b);
char c;
for (; (c = s[i]) == '_' || (i == b ? alpha (c) : alnum (c)); ++i) ;
return i != b && (c == '\0' || ws (c)) ? i - b : 0;
};
size_t i (b);
flavor f (t.data<match_data> ().flavor);
// Substitute special #undef/#cmakedfine/#mesondefine line. If value is
// false, then do not append the value to #define.
//
// Return true if it was substituted with #define, false if with #undef,
// and nullopt if with a custom block of preprocessor directives.
//
auto substitute_special = [&skip_ws, this,
&l,
a, &t,
&dd, &dd_skip,
nl, strict, &null,
&s] (const string& name,
bool value = true) -> optional<bool>
{
// Note that we must be careful here with going too ad hoc since there
// is a parallel debdb validation logic in in::rule which calls
// substitute().
//
optional<string> ov (
substitute (l, a, t, dd, dd_skip, name, strict, null));
assert (ov); // C identifier is a valid variable name.
string& v (*ov);
// As an extension, we allow replacing the entire line with a
// potentially multi-line block of preprocessor directives. To detect
// this, we look for a line that starts with `#` after whitespaces.
//
size_t p (0);
for (;; ++p)
{
skip_ws (v, p);
if (v[p] == '#')
break;
p = v.find ('\n', p);
if (p == string::npos)
break;
}
optional<bool> r;
if (p != string::npos)
{
s.clear ();
// Skip leading and trailing newlines.
//
for (; v.back () == '\n'; v.pop_back ()) ;
for (p = 0; v[p] == '\n'; ++p) ;
for (;; ++p)
{
size_t b (p);
p = v.find ('\n', p);
s.append (v, b, p - b);
if (p == string::npos)
break;
s.append (nl); // Last line is added by our caller.
}
}
// Otherwise translate false/0 to #undef and true to 1.
//
else if (v == "false" || v == "0")
{
s = "#undef ";
s += name;
r = false;
}
else
{
if (v == "true")
v = "1";
s = "#define ";
s += name;
if (value)
{
s += ' ';
s += v;
}
r = true;
}
return r;
};
// Deal with special lines of each flavor. Return if the line has has
// been handled and fall through to use the normal substitution logic.
//
switch (f)
{
case flavor::autoconf:
{
// Autoconf recognizes two forms of special lines:
//
// #undef NAME
// #define NAME 0
//
// However, the latter form as well as comments after NAME in the
// former form are "strongly discouraged". So for now we only
// recognize #undef, especially seeing that it's the dominant form
// in the wild.
//
skip_ws (s, i);
if (s[i] == '#')
{
++i;
skip_ws (s, i);
if (s.compare (i, 5, "undef") == 0 && ws (s[i + 5]))
{
i += 5;
skip_ws (s, i);
size_t n (read_id (s, i));
if (n == 0)
fail (l) << "expected identifier after #undef";
string name (s, i, n);
i += n;
skip_ws (s, i);
// Let's not bother with detecting comments seeing that it's
// strongly discouraged.
//
if (s[i] != '\0')
fail (l) << "junk after identifier " << name;
substitute_special (name);
return;
}
}
break;
}
case flavor::cmake:
{
// CMake recognizes two forms of special lines:
//
// #cmakedefine NAME [VALUE]
// #cmakedefine01 NAME
//
// The first variant is replaced with #define if NAME is "set to any
// value not considered false" and to (commented out) #undef
// otherwise. In the former case, if VALUE is present, then it is
// processed normally with @-substitutions. So in a sense there are
// two entities at play: variable NAME controls #define/#undef while
// VALUE -- the value in case of #define. The following patterns are
// fairly common:
//
// #cmakedefine HAVE_MEMORY_H
// #cmakedefine HAVE_MEMORY_H 1
// #cmakedefine SIZEOF_LONG @SIZEOF_LONG@
//
// But also:
//
// #cmakedefine const
// #cmakedefine const @HAVE_CONST@
// #cmakedefine size_t @SIZE_T@
//
// The #cmakedefine01 variant is always replaced with #define,
// either with value 1 if NAME is true and 0 otherwise. It's doesn't
// appear to be used much in practice so we are not going to bother
// with it.
//
skip_ws (s, i);
if (s[i] == '#')
{
++i;
skip_ws (s, i);
if (s.compare (i, 11, "cmakedefine") == 0 && ws (s[i + 11]))
{
i += 11;
skip_ws (s, i);
size_t n (read_id (s, i));
if (n == 0)
fail (l) << "expected identifier after #cmakedefine";
string name (s, i, n);
i += n;
skip_ws (s, i);
// If there is value, then save it for later (see below).
//
optional<string> value;
if (s[i] != '\0')
value = string (s, i);
optional<bool> r (substitute_special (name, !value));
if (!value || !r || !*r)
return;
// Append the value and fall through to the normal substitution
// logic.
//
s += ' ';
b = s.size (); // Start substituting from here.
s += *value;
}
else if (s.compare (i, 13, "cmakedefine01") == 0 && ws (s[i + 13]))
fail(l) << "#cmakedefine01 is not yet supported";
}
break;
}
case flavor::meson:
{
// Meson recognizes only one special form:
//
// #mesondefine NAME
//
// It is replaced with commented out #undef if NAME is not set, with
// #undef if NAME is set to boolean false, with #define without a
// value if it is set to boolean true, and to #define with the value
// of NAME if it is set to integer or string.
//
// Since we don't do commented out #undef, we will treat the first
// two cases the same. We will also map the third case to value 1,
// similar to Autoconf.
//
skip_ws (s, i);
if (s[i] == '#')
{
++i;
skip_ws (s, i);
if (s.compare (i, 11, "mesondefine") == 0 && ws (s[i + 11]))
{
i += 11;
skip_ws (s, i);
size_t n (read_id (s, i));
if (n == 0)
fail (l) << "expected identifier after #mesondefine";
string name (s, i, n);
i += n;
skip_ws (s, i);
if (s[i] != '\0')
fail (l) << "junk after identifier " << name;
substitute_special (name);
return;
}
}
break;
}
}
in::rule::process (l, a, t, dd, dd_skip, s, b, nl, sym, strict, null);
}
}
}

View File

@ -0,0 +1,38 @@
#pragma once
#include <libbuild2/types.hxx>
#include <libbuild2/utility.hxx>
#include <libbuild2/in/rule.hxx>
namespace build2
{
namespace autoconf
{
// Process a config.h.in file.
//
// Note that to be usable as a drop-in replacement we make the default
// substitution symbol '@' and the mode -- lax. The user, however, is
// still able to override both of these choices with the corresponding
// in.* variables.
//
class rule: public in::rule
{
public:
rule ();
virtual recipe
apply (action, target&) const override;
virtual void
process (const location&,
action, const target&,
depdb&, size_t,
string&, size_t,
const char*,
char,
bool,
const optional<string>&) const override;
};
}
}

View File

@ -0,0 +1,13 @@
: 1
name: libbuild2-autoconf
version: 0.1.0-a.0.z
project: build2
summary: Autoconf emulation build system module for build2
license: MIT ; MIT License.
description-file: README.md
url: https://github.com/build2/libbuild2-autoconf
email: users@build2.org
build-warning-email: builds@build2.org
depends: * build2 >= 0.15.0-
depends: * bpkg >= 0.15.0-
tests: * libbuild2-autoconf-tests == $

4
packages.manifest Normal file
View File

@ -0,0 +1,4 @@
: 1
location: libbuild2-autoconf/
:
location: libbuild2-autoconf-tests/