From ef54e197e99e48478d76034dfee25882e92075c4 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 1 Nov 2021 13:43:47 +0200 Subject: [PATCH] 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. --- README.md | 6 +- libbuild2-autoconf-tests/.gitignore | 24 ++ libbuild2-autoconf-tests/AUTHORS | 1 + libbuild2-autoconf-tests/LICENSE | 1 + libbuild2-autoconf-tests/README.md | 3 + libbuild2-autoconf-tests/basics/buildfile | 1 + libbuild2-autoconf-tests/basics/testscript | 208 ++++++++++ libbuild2-autoconf-tests/build/.gitignore | 4 + .../build/bootstrap.build | 6 + libbuild2-autoconf-tests/build/root.build | 12 + libbuild2-autoconf-tests/buildfile | 1 + libbuild2-autoconf-tests/manifest | 11 + libbuild2-autoconf/.gitignore | 24 ++ libbuild2-autoconf/AUTHORS | 1 + libbuild2-autoconf/LICENSE | 1 + libbuild2-autoconf/README.md | 1 + libbuild2-autoconf/build/.gitignore | 4 + libbuild2-autoconf/build/bootstrap.build | 7 + libbuild2-autoconf/build/export.build | 6 + libbuild2-autoconf/build/root.build | 16 + libbuild2-autoconf/buildfile | 1 + .../libbuild2/autoconf/.gitignore | 3 + .../libbuild2/autoconf/buildfile | 47 +++ .../libbuild2/autoconf/export.hxx | 38 ++ .../libbuild2/autoconf/init.cxx | 75 ++++ .../libbuild2/autoconf/init.hxx | 20 + .../libbuild2/autoconf/rule.cxx | 355 ++++++++++++++++++ .../libbuild2/autoconf/rule.hxx | 38 ++ libbuild2-autoconf/manifest | 13 + packages.manifest | 4 + 30 files changed, 929 insertions(+), 3 deletions(-) create mode 100644 libbuild2-autoconf-tests/.gitignore create mode 120000 libbuild2-autoconf-tests/AUTHORS create mode 120000 libbuild2-autoconf-tests/LICENSE create mode 100644 libbuild2-autoconf-tests/README.md create mode 100644 libbuild2-autoconf-tests/basics/buildfile create mode 100644 libbuild2-autoconf-tests/basics/testscript create mode 100644 libbuild2-autoconf-tests/build/.gitignore create mode 100644 libbuild2-autoconf-tests/build/bootstrap.build create mode 100644 libbuild2-autoconf-tests/build/root.build create mode 100644 libbuild2-autoconf-tests/buildfile create mode 100644 libbuild2-autoconf-tests/manifest create mode 100644 libbuild2-autoconf/.gitignore create mode 120000 libbuild2-autoconf/AUTHORS create mode 120000 libbuild2-autoconf/LICENSE create mode 120000 libbuild2-autoconf/README.md create mode 100644 libbuild2-autoconf/build/.gitignore create mode 100644 libbuild2-autoconf/build/bootstrap.build create mode 100644 libbuild2-autoconf/build/export.build create mode 100644 libbuild2-autoconf/build/root.build create mode 100644 libbuild2-autoconf/buildfile create mode 100644 libbuild2-autoconf/libbuild2/autoconf/.gitignore create mode 100644 libbuild2-autoconf/libbuild2/autoconf/buildfile create mode 100644 libbuild2-autoconf/libbuild2/autoconf/export.hxx create mode 100644 libbuild2-autoconf/libbuild2/autoconf/init.cxx create mode 100644 libbuild2-autoconf/libbuild2/autoconf/init.hxx create mode 100644 libbuild2-autoconf/libbuild2/autoconf/rule.cxx create mode 100644 libbuild2-autoconf/libbuild2/autoconf/rule.hxx create mode 100644 libbuild2-autoconf/manifest create mode 100644 packages.manifest diff --git a/README.md b/README.md index 1dd9bf3..d38b136 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,8 @@ CMake and Meson variants. Similar to Autoconf, the module provides built-in support for a number of common `HAVE_*` configuration options. However, the values of these options -are not discovered by probing, such as trying to compile a test program to -check if the header is present. Instead, they are set to the expected values -based on the platform/compiler macros. +are not discovered by dynamic probing, such as trying to compile a test +program to check if the header is present. Instead, they are set to static +expected values based on the platform/compiler macros. [module-in]: https://build2.org/build2/doc/build2-build-system-manual.xhtml#module-in diff --git a/libbuild2-autoconf-tests/.gitignore b/libbuild2-autoconf-tests/.gitignore new file mode 100644 index 0000000..6435b97 --- /dev/null +++ b/libbuild2-autoconf-tests/.gitignore @@ -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 diff --git a/libbuild2-autoconf-tests/AUTHORS b/libbuild2-autoconf-tests/AUTHORS new file mode 120000 index 0000000..9eadf71 --- /dev/null +++ b/libbuild2-autoconf-tests/AUTHORS @@ -0,0 +1 @@ +../AUTHORS \ No newline at end of file diff --git a/libbuild2-autoconf-tests/LICENSE b/libbuild2-autoconf-tests/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/libbuild2-autoconf-tests/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/libbuild2-autoconf-tests/README.md b/libbuild2-autoconf-tests/README.md new file mode 100644 index 0000000..8008d38 --- /dev/null +++ b/libbuild2-autoconf-tests/README.md @@ -0,0 +1,3 @@ +# libbuild2-autoconf-tests + +Tests package for the Autoconf emulation build system module for `build2`. diff --git a/libbuild2-autoconf-tests/basics/buildfile b/libbuild2-autoconf-tests/basics/buildfile new file mode 100644 index 0000000..328b6da --- /dev/null +++ b/libbuild2-autoconf-tests/basics/buildfile @@ -0,0 +1 @@ +./: testscript diff --git a/libbuild2-autoconf-tests/basics/testscript b/libbuild2-autoconf-tests/basics/testscript new file mode 100644 index 0000000..9cd90d8 --- /dev/null +++ b/libbuild2-autoconf-tests/basics/testscript @@ -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 <=bootstrap.build + project = basics + #amalgamation = + subprojects = + + version = 1.2.3 + EOI + ++cat <=root.build + using c + using autoconf + EOI + +: basics-autoconf +: +mkdir build; +ln -s ../../bootstrap.build ../../root.build build/; +cat <=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 +$* <>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 <=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 +$* <>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 <=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 +$* <>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 diff --git a/libbuild2-autoconf-tests/build/.gitignore b/libbuild2-autoconf-tests/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/libbuild2-autoconf-tests/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/libbuild2-autoconf-tests/build/bootstrap.build b/libbuild2-autoconf-tests/build/bootstrap.build new file mode 100644 index 0000000..248996d --- /dev/null +++ b/libbuild2-autoconf-tests/build/bootstrap.build @@ -0,0 +1,6 @@ +project = libbuild2-autoconf-tests + +using version +using config +using test +using dist diff --git a/libbuild2-autoconf-tests/build/root.build b/libbuild2-autoconf-tests/build/root.build new file mode 100644 index 0000000..82fe0cd --- /dev/null +++ b/libbuild2-autoconf-tests/build/root.build @@ -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) diff --git a/libbuild2-autoconf-tests/buildfile b/libbuild2-autoconf-tests/buildfile new file mode 100644 index 0000000..949c427 --- /dev/null +++ b/libbuild2-autoconf-tests/buildfile @@ -0,0 +1 @@ +./: {*/ -build/} doc{README.md} legal{LICENSE AUTHORS} manifest diff --git a/libbuild2-autoconf-tests/manifest b/libbuild2-autoconf-tests/manifest new file mode 100644 index 0000000..babc80c --- /dev/null +++ b/libbuild2-autoconf-tests/manifest @@ -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- diff --git a/libbuild2-autoconf/.gitignore b/libbuild2-autoconf/.gitignore new file mode 100644 index 0000000..6435b97 --- /dev/null +++ b/libbuild2-autoconf/.gitignore @@ -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 diff --git a/libbuild2-autoconf/AUTHORS b/libbuild2-autoconf/AUTHORS new file mode 120000 index 0000000..9eadf71 --- /dev/null +++ b/libbuild2-autoconf/AUTHORS @@ -0,0 +1 @@ +../AUTHORS \ No newline at end of file diff --git a/libbuild2-autoconf/LICENSE b/libbuild2-autoconf/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/libbuild2-autoconf/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/libbuild2-autoconf/README.md b/libbuild2-autoconf/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/libbuild2-autoconf/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/libbuild2-autoconf/build/.gitignore b/libbuild2-autoconf/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/libbuild2-autoconf/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/libbuild2-autoconf/build/bootstrap.build b/libbuild2-autoconf/build/bootstrap.build new file mode 100644 index 0000000..facc71b --- /dev/null +++ b/libbuild2-autoconf/build/bootstrap.build @@ -0,0 +1,7 @@ +project = libbuild2-autoconf + +using version +using config +using test +using install +using dist diff --git a/libbuild2-autoconf/build/export.build b/libbuild2-autoconf/build/export.build new file mode 100644 index 0000000..4c788d8 --- /dev/null +++ b/libbuild2-autoconf/build/export.build @@ -0,0 +1,6 @@ +$out_root/ +{ + include libbuild2/autoconf/ +} + +export $out_root/libbuild2/autoconf/$import.target diff --git a/libbuild2-autoconf/build/root.build b/libbuild2-autoconf/build/root.build new file mode 100644 index 0000000..00c8dca --- /dev/null +++ b/libbuild2-autoconf/build/root.build @@ -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 diff --git a/libbuild2-autoconf/buildfile b/libbuild2-autoconf/buildfile new file mode 100644 index 0000000..949c427 --- /dev/null +++ b/libbuild2-autoconf/buildfile @@ -0,0 +1 @@ +./: {*/ -build/} doc{README.md} legal{LICENSE AUTHORS} manifest diff --git a/libbuild2-autoconf/libbuild2/autoconf/.gitignore b/libbuild2-autoconf/libbuild2/autoconf/.gitignore new file mode 100644 index 0000000..0c9102a --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/.gitignore @@ -0,0 +1,3 @@ +# Generated version header. +# +version.hxx diff --git a/libbuild2-autoconf/libbuild2/autoconf/buildfile b/libbuild2-autoconf/libbuild2/autoconf/buildfile new file mode 100644 index 0000000..393c8c0 --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/buildfile @@ -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 +} diff --git a/libbuild2-autoconf/libbuild2/autoconf/export.hxx b/libbuild2-autoconf/libbuild2/autoconf/export.hxx new file mode 100644 index 0000000..39a38d7 --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/export.hxx @@ -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 diff --git a/libbuild2-autoconf/libbuild2/autoconf/init.cxx b/libbuild2-autoconf/libbuild2/autoconf/init.cxx new file mode 100644 index 0000000..b466c4a --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/init.cxx @@ -0,0 +1,75 @@ +#include + +#include +#include +#include + +#include + +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 ("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 (perform_update_id, "autoconf.in", rule_); + rs.insert_rule (perform_clean_id, "autoconf.in", rule_); + rs.insert_rule (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; + } + } +} diff --git a/libbuild2-autoconf/libbuild2/autoconf/init.hxx b/libbuild2-autoconf/libbuild2/autoconf/init.hxx new file mode 100644 index 0000000..9b778ae --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/init.hxx @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace build2 +{ + namespace autoconf + { + //- + // Module `autoconf` does not require bootstrapping. + //- + extern "C" LIBBUILD2_AUTOCONF_SYMEXPORT const module_functions* + build2_autoconf_load (); + } +} diff --git a/libbuild2-autoconf/libbuild2/autoconf/rule.cxx b/libbuild2-autoconf/libbuild2/autoconf/rule.cxx new file mode 100644 index 0000000..337fe8c --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/rule.cxx @@ -0,0 +1,355 @@ +#include + +#include +#include +#include + +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 (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& 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 ().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 + { + // 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 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 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 value; + if (s[i] != '\0') + value = string (s, i); + + optional 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); + } + } +} diff --git a/libbuild2-autoconf/libbuild2/autoconf/rule.hxx b/libbuild2-autoconf/libbuild2/autoconf/rule.hxx new file mode 100644 index 0000000..c87e2ef --- /dev/null +++ b/libbuild2-autoconf/libbuild2/autoconf/rule.hxx @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include + +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&) const override; + }; + } +} diff --git a/libbuild2-autoconf/manifest b/libbuild2-autoconf/manifest new file mode 100644 index 0000000..f846236 --- /dev/null +++ b/libbuild2-autoconf/manifest @@ -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 == $ diff --git a/packages.manifest b/packages.manifest new file mode 100644 index 0000000..eb0dab3 --- /dev/null +++ b/packages.manifest @@ -0,0 +1,4 @@ +: 1 +location: libbuild2-autoconf/ +: +location: libbuild2-autoconf-tests/