Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

undefined reference to pqxx::argument_error::argument_error #732

Closed
Neofitum opened this issue Sep 17, 2023 · 47 comments
Closed

undefined reference to pqxx::argument_error::argument_error #732

Neofitum opened this issue Sep 17, 2023 · 47 comments

Comments

@Neofitum
Copy link

Hello there! I have got some linking error:

/usr/bin/ld: /tmp/ccadqyKa.o: in function `pqxx::internal::(anonymous namespace)::throw_for_encoding_error(char const*, char const*, unsigned long, unsigned long)': db.cpp:(.text+0x1dc): undefined reference to `pqxx::argument_error::argument_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::source_location)' /usr/bin/ld: /tmp/ccpFKSJv.o: in function `pqxx::internal::(anonymous namespace)::throw_for_encoding_error(char const*, char const*, unsigned long, unsigned long)': echo.cpp:(.text+0x1dc): undefined reference to `pqxx::argument_error::argument_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::source_location)' /usr/bin/ld: /tmp/ccJw3CDF.o: in function `pqxx::internal::(anonymous namespace)::throw_for_encoding_error(char const*, char const*, unsigned long, unsigned long)': main.cpp:(.text+0x1dc): undefined reference to `pqxx::argument_error::argument_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::source_location)' collect2: error: ld returned 1 exit status

As I see my source doesn't matter. It comes from the header <pqxx/pqxx>
Also both variants give the same error:
-lpqxx -lpq
/usr/local/lib/libpqxx.a -lpq

g++ (Debian 13.2.0-1) 13.2.0
psql (PostgreSQL) 15.4 (Debian 15.4-1)

Any advice please?

@Neofitum
Copy link
Author

btw, I did standard building of the lib:

./configure --disable-shared
make
sudo make install

@tt4g
Copy link
Contributor

tt4g commented Sep 18, 2023

There are several possible causes of link errors.
For example, different library versions, different C++ versions, or different linked standard libraries.

If you built libpqxx by autotools (configure script, make) but you are building the project by other methods, it is possible that the compiler is different.

@Neofitum
Copy link
Author

I have just one version of libpqxx - 7.8.2
My machine has two versions of g++ 10 and 13, but I have set up g++13 by default through "alternatives".

different linked standard libraries

It compiles without "-std=c++20" if I switch off std::chrono::year_month_day variables.
The app compiles separately with -std=c++20 for std::chrono::year_month_day exclusive of pqxx header and separately with -lpqxx for pqxx header exclusive of std=c++20 and std::chrono::year_month_day, but not all together. So what the conflict of libs is there? Does libpqxx fully(as it is possible for the current compilers) support c++20?

@tt4g
Copy link
Contributor

tt4g commented Sep 19, 2023

Do you mean mixing multiple C++ versions? It will not work.
The symbols will not match because STL has different features provided by different C++ versions.

support c++20?
Yes.

@Neofitum
Copy link
Author

Do you mean mixing multiple C++ versions? It will not work.

No. I mean my C++ refers to g++13 as default version of the compiler.
c++ -> /usr/bin/g++
g++ -> g++-13
So, there is no mixing. I specify which STL the compiler must use "-std=c++20". And it's works with g++13.

@tt4g
Copy link
Contributor

tt4g commented Sep 19, 2023

make uses the compiler specified by the environment variable CXX (https://stackoverflow.com/questions/2969222/make-gnu-make-use-a-different-compiler).
Do you mean that you had set CXX=/usr/bin/g++?

And did you specify the environment variable CXXFLAGS="-std=c++20" when you built by make?
If not, libpqxx is built with C++17 (by default g++13).

Also, any of the environment variables will be referenced when running . /configure command.

See also: https://github.com/jtv/libpqxx/blob/7.8.1/BUILDING-configure.md#cheat-sheet

@jtv
Copy link
Owner

jtv commented Sep 19, 2023

My guess is that it's an inconsistency in detected std::source_location support. Probably either the headers are detecting it's there and the source file does not, or the other way around; or there's a mistake that doesn't show up with my compiler version. I'm trying to reproduce the problem, but I've only got gcc 11 here, and that may be making a difference.

@jtv
Copy link
Owner

jtv commented Sep 19, 2023

By the way, note that it's best to set CXXFLAGS when running configure, not when running make.

@jtv
Copy link
Owner

jtv commented Sep 20, 2023

@Neofitum I can't reproduce the problem with current Debian unstable (gcc 13).

Is it possible that the location where make install installs the library by default, simply isn't in your link path?

@Enhex
Copy link

Enhex commented Sep 20, 2023

I'm getting this error when building pqxx library with -std=c++17 and the project that uses it with -std=c++20.
when the standard versions match the error is gone.

using GCC 13.2.1

@KayEss
Copy link
Contributor

KayEss commented Sep 20, 2023

I'm getting this error when building pqxx library with -std=c++17 and the project that uses it with -std=c++20.

This smells like an ODR violation (IFNDR). Can you compile both with the same version?

@Enhex
Copy link

Enhex commented Sep 20, 2023

when they use the same version (either both using 17 or both using 20) it successfully compiles

@jtv
Copy link
Owner

jtv commented Sep 20, 2023

when they use the same version (either both using 17 or both using 20) it successfully compiles

That's what I'd expect. Question is: did a difference in versions sneak into @Neofitum's build?

That can happen, for instance, when setting CXXFLAGS during the make instead of just the configure.

@Enhex
Copy link

Enhex commented Sep 20, 2023

I don't know what his build does, I got this error independently from him.

@Neofitum
Copy link
Author

Guys, sorry for the delay with answer.
I had to rebuild the lib with the following flags:
./configure --disable-shared CXX=g++ CXXFLAGS=-std=c++20

Now, my source compiles together with <pqxx/pqxx>, std::chrono inside and with -std=c++20 flag.

So, it means the library building must be made with -std=c++20 flag if your compiler gcc version is 13 and\or your compiler supports c++20.

Thank you all and @tt4g

@jtv
Copy link
Owner

jtv commented Sep 21, 2023

Right. By default you should get whatever your compiler decides. (I'm a little surprised to be honest to see that that's still C++17 in gcc 13.)

@FloopCZ
Copy link

FloopCZ commented Sep 25, 2023

For those who came here because the latest Arch linux package with libpqxx 7.8.1+ suffers from this issue and need a quick fix, here is an updated PKGBUILD with C++20 flag enabled, although it is hardly a permanent solution.

@acwn1976
Copy link

acwn1976 commented Oct 5, 2023

Hi,
I can confirm the error on Debian testing. The library has been updated to version 7.8.1 (before 6.4.5) some days ago.

I have a very simple code example:

#include <pqxx/pqxx>
int main (int argc, const char* argv[])
{
}

and compile this file with

g++ -o test-pqxx test-pqxx.cpp -lpqxx -std=c++17

and the compilation succeeds.

When I compile the code with

g++ -o test-pqxx test-pqxx.cpp -lpqxx -std=c++20

I get this error

/usr/bin/ld: /tmp/ccCf0EO0.o: in function `pqxx::internal::(anonymous namespace)::throw_for_encoding_error(char const*, char const*, unsigned long, unsigned long)':
test-pqxx.cpp:(.text+0x1dc): undefined reference to `pqxx::argument_error::argument_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::source_location)'
collect2: error: ld returned 1 exit status`

My compiler is:

g++ (Debian 13.2.0-4) 13.2.0

I need C++20 for variuos reasons in the project, so compiling with C++17 is not an option for me.

@tt4g
Copy link
Contributor

tt4g commented Oct 5, 2023

@acwn1976 Do not get the package from Debian, build libpqxx manually.

@jtv
Copy link
Owner

jtv commented Oct 6, 2023

I agree with what @tt4g said. The problem with using a prepackaged library in C++ is that you're bound to have different compile settings than those that were used to build the packaged library.

And there's just not much that can be done about it. It'd be easy to say "just use the compiler's defaults," but those will change with some major compiler upgrades. And then of course, the defaults may not even be what you need.

@acwn1976
Copy link

acwn1976 commented Oct 9, 2023

I understand the point you are making, but it worked before with the old version. And I am using other C++ libraries (all default Debian packages) and I don't have the problem with them.

@jtv
Copy link
Owner

jtv commented Oct 11, 2023

Alright then. I'll have to go back to writing more config into a header at configure time.

I'd like to avoid duplicating checks between build systems, of course. Perhaps I can run the compiler in a way that tells me all defined macros, and sort it out from there in a single python script.

@jtv jtv changed the title undefined reference to `pqxx::argument_error::argument_error undefined reference to pqxx::argument_error::argument_error Oct 21, 2023
@jtv
Copy link
Owner

jtv commented Oct 21, 2023

I'm experimenting with an approach where a python script generates C++ code which in turn, when compiled and run, spits out a C++ configuration header. It'll be faster and easier to scale than maintaining all those feature tests, but it will also complicates cross compilation.

jtv added a commit that referenced this issue Oct 28, 2023
We're getting bug reports (e.g. #732) for situations where people have a
libpqxx compiled as C++17 but an application compiled as C++20, or _vice
versa._

Generally it's probably not wise to link C++17-compiled code to code
compiled as C++20.  But two things I did exacerbate the problem:
1. I moved a bunch of C++ feature checks to compile time, using C++20's
   feature test macros.  It looked like a much cleaner, easier way
   forward with reduced maintenance overhead.
2. Usage of C++20's `std::source_location` _when present_ affects the
   ABI.  So effectively there's one ABI with, and one without.  I see
   that mostly as the price of doing libraries in C++ — it's generally
   dangerous to package library binaries, unless they've been designed
   to be compatible, or they come as a range of binaries for various
   compilers, versions, and configurations.

And the real problem is that _these two changes interacted._  The
detection of support for `std::source_location` happened at compile
time.  And so if you compile libpqxx in C++17 (without this feature)
and the application in C++20 (with this feature), the two will not be
link-compatible!

In this first commit I'm prototyping a new approach that I hope will
combine the ease of maintenance of feature test macros with the ABI
stability of a configuration header.  Configuration speed should lie
somewhere inbetween: no more compiling separate little checks for
every individual C++ feature.

But it's not easy.  There are 2 orthogonal binary choices, leaving me
with 4 scenarios to support:
* Autoconf vs. CMake
* Cross-compiled vs. native.

How does cross compilation factor into it?  It works like this: I need
to check C++ feature test macros.  I check them in C++.  But I don't
want to keep editing that C++ code for every feature — there's a
repetitive preprocessor dance that I don't think I could simplify to one
simple line of code, because I'd need to pass macro names as parameters
to macros.  So, I write a minimal config file and run it through a
Python script that generates the C++ code for me.  Then I have the build
configuration machinery compile _and run_ that C++ code, and generate
the configuration header.

Yes, alright, but how does cross compilation factor into it?  First, if
you're cross-compiling, it's not a given that you can _run_ the binary
you produce on the same system!  The whole point is that the two systems
are different.  And two, you'll have a native compilation environment
but there's no guarantee that it will resemble the cross compilation
environment at all.  So if you compile a binary to run locally, you may
get very different results.

So for cross-compilation, the Python script just generates a minimal
configuration header that just has all features disabled.  And in this
first commit I've got that working for autoconf.  But I'm still
struggling with CMake (thanks @KayEss for helping with this).

If it gets too difficult, I may go a different route: generate C++ code
from Python, but only run it through the preprocessor.  (I believe
autoconf has a standard, portable way of running the preprocessor; let's
hope CMake has one as well.)  The output will still C++ code, but now
it's been preprocessed, so hopefully it'll be possible to tell portably
which features are present.  And ironically, I think I'd then have to
have another Python script to _postprocess_ the preprocessed code and
turn it into a ready-to-use config header.
@jtv
Copy link
Owner

jtv commented Oct 28, 2023

It's harder than I thought. It would be relatively easy to generate a C++ file that checks the feature flags; run it through the preprocessor; and then write a config header based on the output. Should work fine even for cross compilers.

But:

  • Autoconf's AC_PREPROC_IFELSE does not pass $CXXFLAGS, defeating the whole point — preprocessing will happen without knowledge of C++ dialect options.
  • CMake seems to have no notion of the preprocessor at all, except for actual source files in the project, only when using "make" as a builder.

Tempted to define my own "the option to turn the compiler into a preprocessor" variable...

I'll continue pushing the other solution I've been working on: generate some C++ code that checks the feature flags,compile and run it, and have that spit out a config header. Does mean defaulting to all features disabled when cross-compiling, and I have yet to make the thing work for CMake.

@tt4g
Copy link
Contributor

tt4g commented Oct 28, 2023

@jtv CMake does not include a preprocessor, but you can use other features to do something similar.
It is possible to use an external command as a preprocessor using add_custom_target() or add_custom_command().

See: https://stackoverflow.com/a/37862193

It is important to note that CMake's add_custom_command() (add_custom_target()) executes the command at build time.
If you want to generate source code by command before building pqxx libraries, as in this case, the command to generate source code must be executed before pqxx build, but CMake does not know that.
If the order in which the commands are executed is important, a dependency must be set up between the CMake targets.
CMake will automatically add dependencies for targets that use files generated by a particular target.

For example, look at the following code
include/pqxx/cxx_features.hxx is generated with add_custom_command(), and include/pqxx/cxx_features.hxx is specified in the source code with add_library(pqxx).
This ensures that include/pqxx/cxx_features.hxx is generated before the pqxx target is built.

set(pqxx_cxx_features_header include/pqxx/cxx_features.hxx)
add_custom_command(
  OUTPUT ${pqxx_cxx_features_header}
  COMMAND python tools/generate_checks.py cxx_features.txt --dummy
  DEPENDS tools/generate_checks.py cxx_features.txt
  WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
add_library(pqxx ${SOURCE_FILES} ${pqxx_cxx_features_header})

There are other options such as configure_file() and file(GENERATE), but they would not match this use case.

@hlmodi
Copy link

hlmodi commented Oct 31, 2023

I'm currently using GCC 13.1, and I'm building with vcpkg.

I encountered a similar error related to pqxx::conversion_overrun::conversion_overrun. The std::source_location parameter was enabled in the header but not in the compiled library.

As a quick and dirty solution (I don't care about std::source_location info), I modified the internal/cxx-features.hxx file by setting #define pqxx_have_source_location to 0 in all cases. My project then linked correctly.

Hope that helps!

@jtv
Copy link
Owner

jtv commented Nov 1, 2023

@tt4g thanks, I have it down now to the point where it works with autotools, and I just need to get it to work with CMake.

For that, I'll need to mirror what I did for autotools:

  • Run a piece of C++ (included in the source tree) through the C++ preprocessor.
  • Allow the user to override the preprocessor command — it really ought to be a compiler invocation.
  • Pass $CXXFLAGS to the preprocessor (mainly to select a C++ version).
  • Run a custom python script to post-process the pre-processed piece of C++ code to generate the config header.
  • Ensure that the generated config header be installed along with the regular headers.

@jtv
Copy link
Owner

jtv commented Nov 1, 2023

I pretty much don't know how to do any of that with CMake. Any pointers you could give me? I know add_custom_command is supposed to be relevant, but I really don't know how it fits in.

The preprocessor run and the post-processing need to happen when the user configures a build. I hope it will help that all this now only happens during that one stage.

@tt4g
Copy link
Contributor

tt4g commented Nov 1, 2023

@jtv Generating files in the CMake configuration step is a tricky task (compared to autotools).

First, I would like to explain the CMake steps, as I think it is important to familiarize yourself with each step of CMake.
CMake has three main steps: configure-step, generate-step and build-step.
Autotools users would like to think that . /configure and make, but unfortunately this is not the case.

configure-step: parses CMakeLists.txt to check the project structure and defined variables.
generate-step: generates project files for native build tools based on the information obtained by configure-step.
build-step: invokes the native build tool to build the artifact from the generated project file.

Tip
The following sites explain each step in more detail: https://cgold.readthedocs.io/en/latest/tutorials/cmake-stages.html

It is important to note that CMake generates project files that are buildable by native build tools, but leaves the building to the native build tools.
And since CMake is a cross-platform tool, it does not support tasks that can only be performed with specific build tools.

In autotools, . /configure can generate additional files, but the equivalent functionality is not supported by other build tools (for example, Visual Studio IDE does not have the ability to automatically generate files when a project is opened.).
Since all native build tools can generate files, the build commands executed by CMake build-step, add_custom_command() and add_custom_target(), which generate files in build-step, are commonly used in CMake file generation.

The add_custom_command() and add_custom_target(), which are commonly used in CMake for file generation, work on all platforms because they utilize the functionality of the native build tools.
However, since they are executed by CMake build-step, they will not work in this case.

The best match for this case would be execute_process().
The execute_process invokes a command (external process) with CMake configure-step.
The external process can generate files.

Note
CMake support for the external process is not very good.
This is because the external process is launched while CMake is reading the project configuration.

The community recommends add_custom_command over execute_process: https://discourse.cmake.org/t/execute-process-vs-add-custom-command/3286

Another solution that exists in CMake is try_compile.
try_compile is used in https://github.com/jtv/libpqxx/blob/7.8.1/cmake/config.cmake
try_compile can be used in combination with configure_file to record whether a feature is supported.
The user can control the contents of the file generated by configure_file by predefining variables to be set by try_compile.

@jtv
Copy link
Owner

jtv commented Nov 4, 2023

@tt4g might it be possible then to use try_compile but not for one feature, and instead somehow make it all a script to generate the header file as a side effect? It's a bit of a hack, but at the same time pretty close to what try_compile was intended to do, in terms of when it runs etc.

@tt4g
Copy link
Contributor

tt4g commented Nov 4, 2023

@jtv One method is to set the macro to a variable by try_compile() and replace it by configure_file().
This method is used in cmake/config.cmake.

  • try_compile(
    PQXX_HAVE_GCC_PURE
    ${PROJECT_BINARY_DIR}
    SOURCES ${PROJECT_SOURCE_DIR}/config-tests/gcc_pure.cxx)
  • /* Define if g++ supports pure attribute */
    #undef PQXX_HAVE_GCC_PURE
  • libpqxx/cmake/config.cmake

    Lines 128 to 153 in 21afb0d

    # check_cxx_source_compiles requires CMAKE_REQUIRED_DEFINITIONS to specify
    # compiling arguments.
    # Workaround: Pop CMAKE_REQUIRED_DEFINITIONS
    if(def)
    set(CMAKE_REQUIRED_DEFINITIONS ${def})
    unset(def CACHE)
    else()
    unset(CMAKE_REQUIRED_DEFINITIONS CACHE)
    endif()
    set(CMAKE_REQUIRED_QUIET OFF)
    set(AC_CONFIG_H_IN "${PROJECT_SOURCE_DIR}/include/pqxx/config.h.in")
    set(CM_CONFIG_H_IN "${PROJECT_BINARY_DIR}/include/pqxx/config_cmake.h.in")
    set(CM_CONFIG_PUB "${PROJECT_BINARY_DIR}/include/pqxx/config-public-compiler.h")
    set(CM_CONFIG_INT "${PROJECT_BINARY_DIR}/include/pqxx/config-internal-compiler.h")
    set(CM_CONFIG_PQ "${PROJECT_BINARY_DIR}/include/pqxx/config-internal-libpq.h")
    message(STATUS "Generating config.h")
    file(WRITE "${CM_CONFIG_H_IN}" "")
    file(STRINGS "${AC_CONFIG_H_IN}" lines)
    foreach(line ${lines})
    string(REGEX REPLACE "^#undef" "#cmakedefine" l "${line}")
    file(APPEND "${CM_CONFIG_H_IN}" "${l}\n")
    endforeach()
    configure_file("${CM_CONFIG_H_IN}" "${CM_CONFIG_INT}" @ONLY)
    configure_file("${CM_CONFIG_H_IN}" "${CM_CONFIG_PUB}" @ONLY)
    configure_file("${CM_CONFIG_H_IN}" "${CM_CONFIG_PQ}" @ONLY)

Tip
Header files to be processed by CMake from include/pqxx/config.h.in are generated by combining file(WRITE) and file(STRINGS)

See also: https://stackoverflow.com/questions/70113597/does-filegenerate-resolve-variables

@jtv
Copy link
Owner

jtv commented Nov 6, 2023

@jtv One method is to set the macro to a variable by try_compile() and replace it by configure_file(). This method is used in cmake/config.cmake.

But will that work for all of these checks in one go? I'd like to get out of compiling a separate check for each individual feature.

@tt4g
Copy link
Contributor

tt4g commented Nov 6, 2023

Since try_compile() only records build success or failure in one variable, it cannot check all of them in one build.

If compilation errors do not occur, it may be possible to determine this by outputting a message with #pragma message and grep.
But try_compile() does not seem to capture STDOUT at compile time, you will have to use execute_process().

Example:

compile_check.cpp:

#if defined(__cpp_lib_ssize) || __cpp_lib_ssize
#pragma message "__cpp_lib_ssize"
#endif

int main()
{
    return 0;
}

CMakeLists.txt:

execute_process(
    COMMAND "<BUILD_COMMAND>"
    RESULT_VARIABLE BUILD_RETURN_CODE
    OUTPUT_VARIABLE BUILD_STDOUT
)

if (BUILD_SUCCESS_OR_FAILURE

if(BUILD_RETURN_CODE AND NOT BUILD_RETURN_CODE EQUAL 0)
  message(FATAL_ERROR "Build failed")
endif()

if(BUILD_STDOUT MATCHES "__cpp_lib_ssize")
    set(SUPPORT_SSIZE TRUE)
end

@jtv
Copy link
Owner

jtv commented Nov 10, 2023

I was thinking more along the lines of generating the header as a side effect. But I'm giving up on the idea. I think I'll just have to go with generating individual checks when I run autogen.sh. :-(

Presumably there's a way that I can make config.cmake include a generated piece of configuration?

@tt4g
Copy link
Contributor

tt4g commented Nov 10, 2023

Do you mean generate CMake configuration?
That is possible since CMake is a scripting language.
You can write CMake scripts to a file and then import them with include().

@jtv
Copy link
Owner

jtv commented Nov 10, 2023

Thanks @tt4g — yes that's what I meant. I'll use include() then!

jtv added a commit that referenced this issue Nov 12, 2023
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.)
@jtv
Copy link
Owner

jtv commented Nov 12, 2023

I think I finally have something working for this: #747.

@jtv
Copy link
Owner

jtv commented Nov 14, 2023

I'm pretty satisfied with #747 so... last chance to voice any concerns about it. :-)

It's both a conservative and a radical approach:

Conservative — produces the same config header that we used to have before we had this problem.

Radical — streamlines maintenance of the build config logic and does make it very easy to add new configuration items based on C++ feature test macros.

@jtv
Copy link
Owner

jtv commented Nov 18, 2023

@tt4g, @KayEss — I think this PR solves the problem: #747. Lots of changes, but it boils down to: the old way of working, but with a bunch of configuration in autoconf/cmake automated, and a new config file so I can easily define feature macros in the config header based on the C++ standard feature test macros.

@tt4g
Copy link
Contributor

tt4g commented Nov 18, 2023

@jtv I had checked the PR changes several times.
I think we can control the activation C++ features.

jtv added a commit that referenced this issue Nov 21, 2023
Generate feature tests, in two stages.

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.)
@jtv
Copy link
Owner

jtv commented Nov 21, 2023

Thank you! I just merged it. Once we get #726 out of the way I think we'll be ready to release 7.9.0.

@jtv
Copy link
Owner

jtv commented Feb 10, 2024

@Neofitum @Enhex @tt4g @FloopCZ @acwn1976 @KayEss Some blockers for the next release have cleared up in recent days, so hoping to release 7.9.0 soon, and hopefully the problem won't happen with that.

However in the grander scale, we're still stuck with the problem that all package managers encourage us to link things together that may or may not actually be compatible, because of different language versions etc.

@tt4g
Copy link
Contributor

tt4g commented Feb 10, 2024

@jtv I think that compilation options for libraries provided by repositories should be devised in such a way that repository administrators can easily refer to them, if they are supposed to.
It is a well-known fact that C++, unlike C, breaks compatibility due to differences in compile options.
There is a limit to avoiding this problem completely by devising library design.

@jtv
Copy link
Owner

jtv commented Feb 10, 2024

That would certainly help @tt4g! But even then I think we'd need more. Because what can you actually do when the builds in your project don't match up? You'd need multiple builds of every single library. And the matrix of ABI-sensitive build options that would drive the list of builds could be large, irregular, and variable.

The only way out that I see is to "go Gentoo" and just recompile everything every time.

@tt4g
Copy link
Contributor

tt4g commented Feb 10, 2024

@jtv What I wanted to convey was "I think repository administrators should also provide tips on how to avoid problems like this one, since the library's efforts alone are limited".
If the repository does not tell us the C++ version of the library it provides when it is built, there will be no end to the number of users who encounter errors when using a different C++ version of the project than the library.

Not all users who retrieve libraries from repositories are familiar with building C++, so I feel a little bad asking them to build libraries they are unfamiliar with themselves to isolate the cause.

@jtv
Copy link
Owner

jtv commented Feb 10, 2024

Good point @tt4g. Makes me wonder what we could do to make this easier on people. ISTR CMake and configure both leave some traces of the build config, but it'd be nice to have a standardised, uniform, concise place to look this up.

@tt4g
Copy link
Contributor

tt4g commented Feb 10, 2024

@jtv As far as I know, the most famous way to check build options is phpinfo() (PHP).
It seems that phpinfo() records variables such as CXX and CXX_FLAGS referenced at build time without the make command.
This is not an easy method to provide, as the library provider manually records variables using the preprocessor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants