2

I am curious if asprintf is available. Some libc implementations provide it under specific Feature Test Macros. Namely if you #define _GNU_SOURCE or #define _BSD_SOURCE you can get asprintf and a few other nice nonstandard extensions when you #include <stdio.h>.

There are a decent number of libc implementation. glibc obviously has asprintf; but so do a number of others.

How do I test if the libc implementation supports _BSD_SOURCE or _GNU_SOURCE?

My current test is bad, as it's just a kernel check not a compiler + libc + version check:

#if defined(__linux) || defined(__linux__) || defined(linux)
#define _GNU_SOURCE
#elif defined(BSD) || defined(__FreeBSD__) || defined(__FreeBSD__) ||          \
    defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) ||        \
    defined(__DragonFly__)
#define _BSD_SOURCE
#endif
#include <stdio.h>

PS: I'm also using CMake and considered a check_symbol_exists(asprintf "stdio.h" HAVE_ASPRINTF) but not sure that'd suffice.

Samuel Marks
  • 1,611
  • 1
  • 20
  • 25
  • 2
    You don't need to check for the macros. Use `check_symbol_exists` to check for the function and optionally (cuz e.g., `gcc` has `_GNU_SOURCE` pre-defined) provide `CMAKE_REQUIRED_DEFINITIONS` (e.g., in a `foreach` loop try w/o macro, and then add one repeating the check) – zaufi Feb 19 '23 at 22:56
  • 1
    Since these macros have no effect on systems that don't support them, why not simply define all of them, unconditionally? – zwol Feb 20 '23 at 01:38
  • @zwol Because that won't tell me if `asprintf` is available. – Samuel Marks Feb 20 '23 at 02:15

1 Answers1

0

For the sake of a fully working example, we'll start with:

cmake_minimum_required(VERSION 3.25)
project(example)

It probably doesn't need 3.25, but that's what I tested it on, so YMMV.

Now, here's a fairly robust function you can use to try several different preprocessor definitions in service of ensuring a target can see a symbol:

include(CheckSymbolExists)

function(ensure_symbol)
  cmake_parse_arguments(PARSE_ARGV 0 ARG "PUBLIC" "TARGET;SYMBOL" "INCLUDES;DEFINITIONS")

  # Determine visibility for symbols that need to be added
  get_property(type TARGET "${ARG_TARGET}" PROPERTY TYPE)
  if (type STREQUAL "INTERFACE_LIBRARY")
    set(visibility INTERFACE)
  elseif (ARG_PUBLIC)
    set(visibility PUBLIC)
  else ()
    set(visibility PRIVATE)
  endif ()

  # The base name of the cache variables to create
  string(TOUPPER "HAVE_${ARG_SYMBOL}" var_name)

  # Check the base version
  check_symbol_exists("${ARG_SYMBOL}" "${ARG_INCLUDES}" "${var_name}")

  if (${var_name})
    return ()
  endif ()

  # Otherwise, start trying alternatives
  foreach (def IN LISTS ARG_DEFINITIONS)
    set(local_name "${var_name}${def}")

    set(CMAKE_REQUIRED_DEFINITIONS "-D${def}")
    check_symbol_exists("${ARG_SYMBOL}" "${ARG_INCLUDES}" "${local_name}")

    if (${local_name})
      target_compile_definitions("${ARG_TARGET}" "${visibility}" "${def}")
      return()
    endif ()
  endforeach ()

  message(FATAL_ERROR "Could not ensure ${ARG_TARGET} can use ${ARG_SYMBOL}")
endfunction()

It uses check_symbol_exists in a loop over candidate symbols to define in order to make the symbol available. Whichever one works, it attaches to the target via target_compile_definitions.

The main complicating feature is that it gives users control over the visibility of the definition to linkees. It's always INTERFACE for interface libraries (which is required), but for other target types, it defaults to PRIVATE. The user may set it to PUBLIC by passing the appropriate option to ensure_symbol.

Finally, using it is pretty simple:

add_executable(myapp main.cpp)
ensure_symbol(
  TARGET myapp
  SYMBOL asprintf
  INCLUDES stdio.h
  DEFINITIONS _GNU_SOURCE _BSD_SOURCE
)

I tested it locally, and it adds _GNU_SOURCE on my machine, which can be seen at the bottom of the following output:

$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Looking for asprintf
-- Looking for asprintf - not found
-- Looking for asprintf
-- Looking for asprintf - found
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
$ cmake --build build/ --verbose
[1/2] /usr/bin/c++ -D_GNU_SOURCE  -O3 -DNDEBUG -MD -MT CMakeFiles/myapp.dir/main.cpp.o -MF CMakeFiles/myapp.dir/main.cpp.o.d -o CMakeFiles/myapp.dir/main.cpp.o -c /home/alex/test/main.cpp
[2/2] : && /usr/bin/c++ -O3 -DNDEBUG  CMakeFiles/myapp.dir/main.cpp.o -o myapp   && :
Alex Reinking
  • 16,724
  • 5
  • 52
  • 86