Search code examples
configureautotoolsautoconfm4

Autoconf loop but stop after first success?


I've got this snippet to detect and require the highest usable C++ standard. It works exactly the way I want it to - checks highest first, and stop searching after the first success. It's also extremely ugly and hard to maintain.

# Require highest supported C++ standard
AC_LANG(C++)
AX_CHECK_COMPILE_FLAG([-std=c++20], [CXXFLAGS="$CXXFLAGS -std=c++20"], [
 AX_CHECK_COMPILE_FLAG([-std=c++2a], [CXXFLAGS="$CXXFLAGS -std=c++2a"], [
  AX_CHECK_COMPILE_FLAG([-std=c++17], [CXXFLAGS="$CXXFLAGS -std=c++17"], [
   AX_CHECK_COMPILE_FLAG([-std=c++1z], [CXXFLAGS="$CXXFLAGS -std=c++1z"], [
    AX_CHECK_COMPILE_FLAG([-std=c++14], [CXXFLAGS="$CXXFLAGS -std=c++14"], [
     AX_CHECK_COMPILE_FLAG([-std=c++1y], [CXXFLAGS="$CXXFLAGS -std=c++1y"], [
      AC_MSG_ERROR([Could not enable at least C++1y (C++14) - upgrade your compiler])
     ])
    ])
   ])
  ])
 ])
])

Is there a way to turn it into a single list? What I ideally want is something like following pseudocode, so that to add/remove a specific C++ variant is simply changing the list instead of the whole tree:

foreach([-std=c++20, -std=c++2a, -std=c++17, etc],
[if(AX_CHECK_COMPILE_FLAG($1, [CXXFLAGS+=$1]), break)])

From what I can find, m4 simply cannot loop. It can recurse, but breaking out of a recursion is the kind of m4 black magic that I apparently can't get to work.

And no, changing to another build system is not an option. This is trivial in CMake, but this project is autotools.


Solution

  • From what I can find, m4 simply cannot loop. It can recurse, but breaking out of a recursion is the kind of m4 black magic that I apparently can't get to work.

    M4 absolutely can loop, but you don't need that for your purposes, and you probably don't want it. In fact, one usually doesn't want to directly leverage M4 features in one's Autoconf code.

    Remember always that M4 runs when you build the build system, not when you configure the project. It produces configure, a shell script, and for configure-time control flow you need configure to leverage shell features. Autoconf provides macros around some such shell features, but you are not obligated to use them, and you are certainly not obligated to avoid shell features just because there isn't an Autoconf macro wrapping them. The main caveat is that you should be careful to write portable shell code. Indeed, it is in service to that objective that Autoconf provides any macros at all for shell flow-control.

    With that in mind, here's a way to use a shell loop to implement your check more cleanly:

    # language versions to test, in priority order:
    for version in 20 2a 17 1z 14 1y; do
      version_flag="-std=c++${version}"
      AX_CHECK_COMPILE_FLAG([${version_flag}], [
        break
      ], [
        version_flag=none
      ])
    done
    AS_IF([test "$version_flag" == none], [
      AC_MSG_ERROR([Could not enable at least C++1y (C++14) - upgrade your compiler])
    ])
    CXXFLAGS="$CXXFLAGS ${version_flag}"
    

    If you want to change the versions that are tested or your priority for which to ones to prefer, then you can just change the list in the for statement.


    HOWEVER, I offer a frame challenge: what's the point? If -std=c++1y is sufficient for your program's purposes, then what is gained by conditionally selecting a more recent version?

    Perhaps there indeed is a point -- for instance, maybe the source contains preprocessor conditionals to enable additional features when built to a more recent standard. In that particular case, however, a better Autoconf idiom would be to add one or more --enable flags for the conditional features, and to select the required C++ version flag based on which, if any, of those are chosen. There's no need then to test any other options, and it's less magical for the builder who wants influence over what features are in fact enabled.