Skip to content

Commit

Permalink
Generate feature test, in two stages.
Browse files Browse the repository at this point in the history
This is yet another attempt at #732: go back to writing all feature test
macros to a config header, so that we can build libpqxx itself and the
client application against the same version of the language — even if
the user actually builds them with different options that may affect the
feature set.

Things I wanted for this work:
1. Less of that repetitive configuration.  It's tedious and error-prone.
2. Reduced risk of inconsistency between autoconf and CMake.
3. Portability.
4. Simple way to add a feature check based on a C++ feature test macro.
5. Reasonable simplifity.
6. Fast build configuration.

I think this solution satisfies most of these.  A new configuration file
`cxx_features.txt` lets me define features that we detect simply by
checking a C++ feature test macro.  (We'll still need to enter each
libpqxx feature macro that we want to expose into `configitems`.)

A first Python script, `generate_cxx_checks.py` generates the C++ code
snippets that check for each of the C++ feature check macros.  It just
adds those to `config-tests/`.

The C++ files inside `config-tests/` now have names that correspond
_directly_ to the libpqxx feature macros.  It eliminates the pointless
separate file name, but also, it lets me generate autoconf and CMake
config based on just the file names!

Then, a second script `generate_check_config.py` produces pieces of
autoconf and CMake configuration to actually run these checks.  The
respective main configurations use include directives to incorporate
these configs.

These pieces of configuration will reference both the pre-existing,
hand-written feature test snippets, and the ones we just generated from
C++ feature test macros.  At this stage there's no difference between
the two kinds.

I believe this is as portable as it ever was, because nothing really
changes structurally.  Yes, the scripts are tailored for recent Python
language versions, but I think that's about it.  Of course the C++
feature test macros were introduced in C++20 so they're not going to
do much good for C++17.  But that should stop mattering soon, as we
transition to C++20 as the minimum language version.

I believe the new system is not the simplest thing, but is easier to
manage than all those manual compilation checks across two build
systems.  That was just painful.  And who knows, some day this new
approach may support adding yet another build system.

The only thing that hasn't improved is build configuration speed.  The
only positive thing I can say there is that the new mechanism generates
all the extra files during `autogen.sh`, so that part shouldn't affect
build configuration speeds.  But it's not going to be as fast as my
last plan for this work.  (That experiment checked all the C++ feature
test macros by running a single piece of generated C++ code through the
preprocessor, and generating a new config header from it.)
  • Loading branch information
jtv committed Nov 12, 2023
1 parent 7586846 commit 2663057
Show file tree
Hide file tree
Showing 24 changed files with 279 additions and 261 deletions.
6 changes: 6 additions & 0 deletions autogen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ expand_templates $(find . -name \*.template)
substitute include/pqxx/version.hxx.template >include/pqxx/version.hxx
substitute include/pqxx/doc/mainpage.md.template >include/pqxx/doc/mainpage.md

# Generate feature test snippets for C++ features that we simply detect by
# checking a C++ feature test macro.
./tools/generate_cxx_checks.py

# Generate autoconf and CMake configuration for our feature test snippets.
./tools/generate_check_config.py

autoheader
libtoolize --force --automake --copy
Expand Down
75 changes: 6 additions & 69 deletions cmake/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -51,80 +51,17 @@ endif()
set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_CXX${CMAKE_CXX_STANDARD}_STANDARD_COMPILE_OPTION})
set(CMAKE_REQUIRED_QUIET ON)

try_compile(
PQXX_HAVE_GCC_PURE
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/gcc_pure.cxx)
try_compile(
PQXX_HAVE_GCC_VISIBILITY
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/gcc_visibility.cxx)
# C++20: Assume support.
try_compile(
PQXX_HAVE_LIKELY
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/likely.cxx)
try_compile(
PQXX_HAVE_CXA_DEMANGLE
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/cxa_demangle.cxx)
# C++20: Assume support.
try_compile(
PQXX_HAVE_CONCEPTS
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/concepts.cxx)
# C++20: Assume support.
try_compile(
PQXX_HAVE_SPAN
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/span.cxx)
try_compile(
PQXX_HAVE_CHARCONV_FLOAT
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/charconv_float.cxx)
try_compile(
PQXX_HAVE_CHARCONV_INT
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/charconv_int.cxx)
try_compile(
PQXX_HAVE_PATH
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/fs.cxx)
try_compile(
PQXX_HAVE_THREAD_LOCAL
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/thread_local.cxx)
try_compile(
PQXX_HAVE_SLEEP_FOR
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/sleep_for.cxx)
try_compile(
PQXX_HAVE_STRERROR_R
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/strerror_r.cxx)
try_compile(
PQXX_HAVE_STRERROR_S
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/strerror_s.cxx)
try_compile(
PQXX_HAVE_YEAR_MONTH_DAY
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/year_month_day.cxx)
# C++20: Assume support.
try_compile(
PQXX_HAVE_CMP
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/cmp.cxx)

try_compile(
need_fslib
${PROJECT_BINARY_DIR}
SOURCES ${PROJECT_SOURCE_DIR}/config-tests/need_fslib.cxx)
if(!need_fslib)
# Incorporate feature checks based on C++ feature test mac
include(pqxx_cxx_feature_checks)

# This variable is set by one of the config-tests.
if(!no_need_fslib)
# TODO: This may work for gcc 8, but some clang versions may need -lc++fs.
link_libraries(stdc++fs)
endif()


# check_cxx_source_compiles requires CMAKE_REQUIRED_DEFINITIONS to specify
# compiling arguments.
# Workaround: Pop CMAKE_REQUIRED_DEFINITIONS
Expand Down
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions config-tests/cmp.cxx → config-tests/PQXX_HAVE_CMP.cxx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Test for C++20 std::cmp_greater etc. support.
// C++20: Assume support.
#include <utility>


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Test for concepts support. Not just the language feature; also headers.
// C++20: Assume support.
#include <iostream>
#include <ranges>
#include <vector>
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Test for C++20 [[likely]] and [[unlikely]] attributes.
// C++20: Assume support.

int main(int argc, char **)
{
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions config-tests/span.cxx → config-tests/PQXX_HAVE_SPAN.cxx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Test for std::span.
// C++20: Assume support.
#include <span>

int main(int argc, char **argv)
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Test for std::to_string/std::from_string for floating-point types.
// TODO: Probably no longer needed once we always have float std::charconv.
#include <iostream>
#include <sstream>

Expand Down
File renamed without changes.
30 changes: 17 additions & 13 deletions config-tests/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
Configuration tests
===================

Libpqxx comes with support for different build systems: the GNU autotools,
CMake, Visual Studio's "nmake", and raw GNU "make" on Windows.
Libpqxx comes with support for two different build systems: the GNU autotools,
and CMake.

For several of these build systems, we need to test things like "does this
compiler environment support `std::to_chars` for floating-point types?"
We need to teach both of these to test things like "does this compiler
environment support `std::to_chars` for floating-point types?"

We test these things by trying to compile a particular snippet of code, and
seeing whether that succeeds.
In both build systems we test these things by trying to compile a particular
snippet of code, found in this directry, and seeing whether that succeeds.

To avoid duplicating those snippets for multiple build systems, we put them
here. Both the autotools configuration and the CMake configuration can refer to
them that way.
them that way. We generate autoconf and cmake configuration automatically to
inject those checks, avoiding tedious repetition.

It took a bit of nasty magic to read a C++ source file into m4 and treat it as
a string literal, without macro expansion. There is every chance that I missed
something, so be prepared for tests failing for unexpected reasons! Some C++
syntax may end up having an unforeseen meaning in m4, and screw up the handling
of the code snippet. Re-configure, and read your logs carefully after editing
these snippets.
Some of the checks are based on C++20 feature test macros. We generate those
automatically using `tools/generate_cxx_checks.py`.

It took a bit of nasty magic to read a C++ source file into m4 for the autoconf
side and treat it as a string literal, without macro expansion. There is every
chance that I missed something, so be prepared for tests failing for unexpected
reasons! Some C++ syntax may end up having an unforeseen meaning in m4, and
screw up the handling of the code snippet. Re-configure, and read your logs
carefully after editing these snippets.
File renamed without changes.
4 changes: 4 additions & 0 deletions configitems
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ PQXX_HAVE_CONCEPTS public compiler
PQXX_HAVE_CXA_DEMANGLE internal compiler
PQXX_HAVE_GCC_PURE public compiler
PQXX_HAVE_GCC_VISIBILITY public compiler
PQXX_HAVE_MULTIDIM public compiler
PQXX_HAVE_LIKELY public compiler
PQXX_HAVE_PATH public compiler
PQXX_HAVE_POLL internal compiler
PQXX_HAVE_SLEEP_FOR internal compiler
PQXX_HAVE_SOURCE_LOCATION public compiler
PQXX_HAVE_SPAN public compiler
PQXX_HAVE_SSIZE public compiler
PQXX_HAVE_STRERROR_R public compiler
PQXX_HAVE_STRERROR_S public compiler
PQXX_HAVE_THREAD_LOCAL internal compiler
PQXX_HAVE_UNREACHABLE public compiler
PQXX_HAVE_YEAR_MONTH_DAY public compiler
VERSION internal autotools
186 changes: 7 additions & 179 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -250,193 +250,21 @@ then
-Wsuggest-final-types \
-Wsuggest-final-methods
fi
fi # End of gcc-specific part.

AC_MSG_CHECKING([g++ visibility attribute])
gcc_visibility=yes
SAVE_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="$CXXFLAGS -Werror"
AC_COMPILE_IFELSE(
[read_test(gcc_visibility.cxx)],
AC_DEFINE(
[PQXX_HAVE_GCC_VISIBILITY],
1,
[Define if g++ supports visibility attribute.]),
gcc_visibility=no)
AC_MSG_RESULT($gcc_visibility)
CXXFLAGS="$SAVE_CXXFLAGS"
if test "$gcc_visibility" = "yes"

m4_include([pqxx_cxx_feature_checks.ac])


# One of the generated checks sets this variable.
if test "$PQXX_HAVE_GCC_VISIBILITY" = "yes"
then
# Make internal definitions accessible only to the library itself.
# Only definitions marked PQXX_LIBEXPORT will be accessible.
add_compiler_opts -fvisibility=hidden
add_compiler_opts -fvisibility-inlines-hidden
fi

AC_MSG_CHECKING([g++ pure attribute])
gcc_pure=yes
AC_COMPILE_IFELSE(
[read_test(gcc_pure.cxx)],
AC_DEFINE(
[PQXX_HAVE_GCC_PURE],
1,
[Define if g++ supports pure attribute]),
gcc_pure=no)
AC_MSG_RESULT($gcc_pure)

fi # End of gcc-specific part.


# Check for __cxa_demangle.
AC_MSG_CHECKING([__cxa_demangle])
cxa_demangle=yes
AC_COMPILE_IFELSE(
[read_test(cxa_demangle.cxx)],
AC_DEFINE(
[PQXX_HAVE_CXA_DEMANGLE],
1,
[Define if compiler supports __cxa_demangle]),
cxa_demangle=no)
AC_MSG_RESULT($cxa_demangle)


# C++20: Assume support.
# Check for sufficient Concepts support, introduced with C++20.
AC_MSG_CHECKING([concepts])
concepts=yes
AC_COMPILE_IFELSE(
[read_test(concepts.cxx)],
AC_DEFINE(
[PQXX_HAVE_CONCEPTS],
1,
[Define if compiler supports Concepts and <ranges> header.]),
concepts=no)
AC_MSG_RESULT($concepts)


# C++20: Assume support.
# Check for C++20 std::span.
AC_MSG_CHECKING([std::span])
span=yes
AC_COMPILE_IFELSE(
[read_test(span.cxx)],
AC_DEFINE([PQXX_HAVE_SPAN], 1, [Define if compiler has std::span.]),
span=no)
AC_MSG_RESULT($span)


AC_MSG_CHECKING([for strerror_r()])
strerror_r=yes
AC_LINK_IFELSE(
[read_test(strerror_r.cxx)],
AC_DEFINE(
[PQXX_HAVE_STRERROR_R],
1,
[Define if strerror_r() is available.]),
strerror_r=no)
AC_MSG_RESULT($strerror_r)



AC_MSG_CHECKING([for strerror_s()])
strerror_s=yes
AC_LINK_IFELSE(
[read_test(strerror_s.cxx)],
AC_DEFINE(
[PQXX_HAVE_STRERROR_S],
1,
[Define if strerror_s() is available.]),
strerror_s=no)
AC_MSG_RESULT($strerror_s)


AC_MSG_CHECKING([for std::chrono::year_month_day etc])
year_month_day=yes
AC_LINK_IFELSE(
[read_test(year_month_day.cxx)],
AC_DEFINE(
[PQXX_HAVE_YEAR_MONTH_DAY],
1,
[Define if std::chrono has year_month_day etc.]),
year_month_day=no)
AC_MSG_RESULT($year_month_day)


# C++20: Assume support.
# Check for [[likely]] and [[unlikely]] attributes.
AC_MSG_CHECKING([attributes "likely" and "unlikely".])
likely=yes
AC_COMPILE_IFELSE(
[read_test(likely.cxx)],
AC_DEFINE([PQXX_HAVE_LIKELY], 1, [Define if likely & unlikely work.]),
likely=no)
AC_MSG_RESULT($likely)


# It's mid-2019, and gcc's charconv supports integers but not yet floats.
# So for now, we test for int and float conversion... separately.
#
# It's worse for older clang versions, which compile the integer conversions
# but then fail at link time because of a missing symbol "__muloti4" with the
# "long long" version. I couldn't resolve that symbol by adding -lm either.
# So don't just compile these tests; link them as well.
AC_MSG_CHECKING([for C++17 charconv integer conversion])
have_charconv_int=yes
AC_LINK_IFELSE(
[read_test(charconv_int.cxx)],
AC_DEFINE(
[PQXX_HAVE_CHARCONV_INT],
1,
[Define if <charconv> supports integer conversion.]),
have_charconv_int=no)
AC_MSG_RESULT($have_charconv_int)

AC_MSG_CHECKING([for C++17 charconv floating-point conversion])
have_charconv_float=yes
AC_LINK_IFELSE(
[read_test(charconv_float.cxx)],
AC_DEFINE(
[PQXX_HAVE_CHARCONV_FLOAT],
1,
[Define if <charconv> supports floating-point conversion.]),
have_charconv_float=no)
AC_MSG_RESULT($have_charconv_float)

# As per #262, clang with libcxxrt does not support thread_local on non-POD
# objects. Luckily we can live without those, it's just less efficient.
AC_MSG_CHECKING([for full thread_local support])
have_thread_local=yes
AC_LINK_IFELSE(
[read_test(thread_local.cxx)],
AC_DEFINE(
[PQXX_HAVE_THREAD_LOCAL],
1,
[Define if thread_local is fully supported.]),
have_thread_local=no)
AC_MSG_RESULT($have_thread_local)

AC_MSG_CHECKING([for std::this_thread::sleep_for])
have_sleep_for=yes
AC_LINK_IFELSE(
[read_test(sleep_for.cxx)],
AC_DEFINE(
[PQXX_HAVE_SLEEP_FOR],
1,
[Define if std::this_thread::sleep_for works.]),
have_sleep_for=no)
AC_MSG_RESULT($have_sleep_for)


# C++20: Assume support.
AC_MSG_CHECKING([for std::cmp_greater, std::cmp_less_equal, etc])
have_cmp=yes
AC_COMPILE_IFELSE(
[read_test(cmp.cxx)],
AC_DEFINE(
[PQXX_HAVE_CMP],
1,
[Define if compiler has C++20 std::cmp_greater etc.]),
have_cmp=no)
AC_MSG_RESULT($have_cmp)


# Doing my own check for poll(). There's one built into autoconf-archive, but
Expand Down
Loading

0 comments on commit 2663057

Please sign in to comment.