# This is free and unencumbered software released into the public domain. # # Anyone is free to copy, modify, publish, use, compile, sell, or # distribute this software, either in source code form or as a compiled # binary, for any purpose, commercial or non-commercial, and by any # means. # # In jurisdictions that recognize copyright laws, the author or authors # of this software dedicate any and all copyright interest in the # software to the public domain. We make this dedication for the benefit # of the public at large and to the detriment of our heirs and # successors. We intend this dedication to be an overt act of # relinquishment in perpetuity of all present and future rights to this # software under copyright law. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # For more information, please refer to cmake_minimum_required(VERSION 3.15) project(subprocess) include(CTest) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) # Strict -std=c99/-std=c11/-std=c++XX modes on glibc hide POSIX/GNU # declarations used by subprocess.h unless feature test macros are enabled. if(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(_GNU_SOURCE) endif() set(SUBPROCESS_HELPER_SOURCES process_return_zero.c process_return_fortytwo.c process_return_argc.c process_return_argv.c process_return_special_argv.c process_return_lpcmdline.c process_return_stdin.c process_return_stdin_count.c process_stdout_argc.c process_stdout_argv.c process_stderr_argc.c process_stderr_argv.c process_combined_stdout_stderr.c process_inherit_environment.c process_fail_divzero.c process_fail_stackoverflow.c process_hung.c process_stdout_data.c process_stdout_poll.c process_stdout_poll_wait_first.c process_stderr_poll.c process_stderr_poll_wait_first.c process_stdout_large.c process_call_return_argc.c process_cwd.c ) foreach(SUBPROCESS_HELPER_SOURCE ${SUBPROCESS_HELPER_SOURCES}) get_filename_component(SUBPROCESS_HELPER_TARGET ${SUBPROCESS_HELPER_SOURCE} NAME_WE) add_executable(${SUBPROCESS_HELPER_TARGET} ${SUBPROCESS_HELPER_SOURCE}) endforeach() # Base warning flags and whether -std= flags are supported. # MSVC and clang-cl use /std: and don't accept -std=, so we skip # the standard flag for those compilers. set(SUBPROCESS_MSVC_WARNINGS "/Wall /WX /wd4514 /wd4668 /wd4710 /wd4711 /wd4865 /wd5039 /wd5045") if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") set(SUBPROCESS_C_WARNINGS "-Wall -Wextra -Werror") set(SUBPROCESS_C_USE_STD_FLAG TRUE) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") if(CMAKE_C_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") set(SUBPROCESS_C_WARNINGS "${SUBPROCESS_MSVC_WARNINGS}") set(SUBPROCESS_C_USE_STD_FLAG FALSE) else() set(SUBPROCESS_C_WARNINGS "-Wall -Wextra -Weverything -Werror -Wno-poison-system-directories") set(SUBPROCESS_C_USE_STD_FLAG TRUE) endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set(SUBPROCESS_C_WARNINGS "${SUBPROCESS_MSVC_WARNINGS}") set(SUBPROCESS_C_USE_STD_FLAG FALSE) else() message(WARNING "Unknown C compiler '${CMAKE_C_COMPILER_ID}'!") set(SUBPROCESS_C_WARNINGS "") set(SUBPROCESS_C_USE_STD_FLAG FALSE) endif() if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(SUBPROCESS_CXX_WARNINGS "-Wall -Wextra -Werror -Wpedantic -Wno-variadic-macros") set(SUBPROCESS_CXX_USE_STD_FLAG TRUE) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") set(SUBPROCESS_CXX_WARNINGS "${SUBPROCESS_MSVC_WARNINGS} /clang:-Wno-format /clang:-Wno-old-style-cast /clang:-Wno-zero-as-null-pointer-constant /clang:-Wno-c++98-compat /clang:-Wno-c++98-compat-pedantic /clang:-Wno-c++11-compat-deprecated-writable-strings /clang:-Wno-null-arithmetic /clang:-Wno-writable-strings /clang:-Wno-undefined-func-template /clang:-Wno-gnu-anonymous-struct /clang:-Wno-nested-anon-types") set(SUBPROCESS_CXX_USE_STD_FLAG FALSE) else() set(SUBPROCESS_CXX_WARNINGS "-Wall -Wextra -Weverything -Werror -Wpedantic -Wno-poison-system-directories -Wno-format -Wno-old-style-cast -Wno-zero-as-null-pointer-constant -Wno-c++11-compat-deprecated-writable-strings -Wno-null-arithmetic -Wno-writable-strings -Wno-undefined-func-template") set(SUBPROCESS_CXX_USE_STD_FLAG TRUE) endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set(SUBPROCESS_CXX_WARNINGS "${SUBPROCESS_MSVC_WARNINGS}") set(SUBPROCESS_CXX_USE_STD_FLAG FALSE) else() message(WARNING "Unknown C++ compiler '${CMAKE_CXX_COMPILER_ID}'!") set(SUBPROCESS_CXX_WARNINGS "") set(SUBPROCESS_CXX_USE_STD_FLAG FALSE) endif() # Helper: set compile flags for a C source file (warnings + standard). function(subprocess_set_c_flags FILE STANDARD) if(SUBPROCESS_C_USE_STD_FLAG) set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "${SUBPROCESS_C_WARNINGS} ${STANDARD}") else() set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "${SUBPROCESS_C_WARNINGS}") endif() endfunction() # Helper: set compile flags for a C++ source file (warnings + standard + extras). function(subprocess_set_cxx_flags FILE STANDARD) set(EXTRA_FLAGS ${ARGN}) if(SUBPROCESS_CXX_USE_STD_FLAG) set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "${SUBPROCESS_CXX_WARNINGS} ${STANDARD} ${EXTRA_FLAGS}") else() set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "${SUBPROCESS_CXX_WARNINGS}") endif() endfunction() # C files — each compiled under a specific C standard. subprocess_set_c_flags(main.c "-std=gnu89") subprocess_set_c_flags(test.c "-std=gnu89") subprocess_set_c_flags(test99.c "-std=c99") list(FIND CMAKE_C_COMPILE_FEATURES c_std_11 IDX) if(${IDX} GREATER -1) subprocess_set_c_flags(test11.c "-std=c11") endif() # C++ files — each compiled under a specific C++ standard. subprocess_set_cxx_flags(test.cpp "" "-Wno-c++98-compat") subprocess_set_cxx_flags(test98.cpp "-std=gnu++98") subprocess_set_cxx_flags(test11.cpp "-std=c++11" "-Wno-c++98-compat") subprocess_set_cxx_flags(test14.cpp "-std=c++14" "-Wno-c++98-compat") subprocess_set_cxx_flags(test17.cpp "-std=c++17" "-Wno-c++98-compat") subprocess_set_cxx_flags(test20.cpp "-std=c++20" "-Wno-c++98-compat") subprocess_set_cxx_flags(pre_windows.cpp "" "-Wno-c++98-compat") subprocess_set_cxx_flags(post_windows.cpp "" "-Wno-c++98-compat") set(SUBPROCESS_TEST_SOURCES ../subprocess.h utest.h test_shared.h main.c test.c test99.c test11.c test.cpp test98.cpp test11.cpp test14.cpp test17.cpp test20.cpp pre_windows.cpp post_windows.cpp ) # Helper: add a test executable and register it with CTest. function(subprocess_add_test NAME) add_executable(${NAME} ${SUBPROCESS_TEST_SOURCES}) add_test(NAME ${NAME} COMMAND $) set_tests_properties(${NAME} PROPERTIES WORKING_DIRECTORY $) endfunction() subprocess_add_test(subprocess_test) if(MSVC) # Build a second test binary linked against the static CRT (/MT) # to ensure we work with both static and dynamic libc linkage. set_property(TARGET subprocess_test PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") subprocess_add_test(subprocess_mt_test) set_property(TARGET subprocess_mt_test PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() # Some sanitizer runtimes can't initialise on every host even when # the toolchain links them fine — e.g. clang's TSan and MSan on # aarch64 only ship shadow-memory layouts for 39-, 42- and 48-bit # user VMAs, but Raspberry Pi OS' kernel is configured with # CONFIG_ARM64_VA_BITS=47. The binary builds, but aborts before # main with "FATAL: ThreadSanitizer: unsupported VMA range". Probe # each candidate sanitizer with try_run and skip the ones whose # runtime can't start. Result is cached so reconfigure is cheap; # delete CMakeCache.txt to re-probe (e.g. after changing kernels). function(subprocess_check_sanitizer_runs SAN OUT_VAR) set(CACHE_VAR "SUBPROCESS_SAN_${SAN}_RUNS") if(DEFINED ${CACHE_VAR}) set(${OUT_VAR} ${${CACHE_VAR}} PARENT_SCOPE) return() endif() set(PROBE_SRC "${CMAKE_BINARY_DIR}/subprocess_san_probe_${SAN}.c") file(WRITE "${PROBE_SRC}" "int main(void) { return 0; }\n") try_run(RUN_RESULT COMPILE_RESULT "${CMAKE_BINARY_DIR}/subprocess_san_probe_${SAN}" "${PROBE_SRC}" COMPILE_DEFINITIONS "-fsanitize=${SAN}" "-fno-omit-frame-pointer" LINK_OPTIONS "-fsanitize=${SAN}" ) if(COMPILE_RESULT AND "${RUN_RESULT}" STREQUAL "0") set(RESULT TRUE) message(STATUS "Sanitizer ${SAN}: usable") else() set(RESULT FALSE) message(STATUS "Sanitizer ${SAN}: skipping " "(compile=${COMPILE_RESULT}, run=${RUN_RESULT})") endif() set(${CACHE_VAR} ${RESULT} CACHE INTERNAL "Whether the ${SAN} sanitizer runtime starts on this host") set(${OUT_VAR} ${RESULT} PARENT_SCOPE) endfunction() if(("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") AND NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") set(SUBPROCESS_SANITIZER_CANDIDATES address undefined thread ) if(NOT "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") set(SUBPROCESS_SANITIZER_CANDIDATES ${SUBPROCESS_SANITIZER_CANDIDATES} memory) endif() set(SUBPROCESS_SANITIZERS) foreach(SUBPROCESS_SANITIZER ${SUBPROCESS_SANITIZER_CANDIDATES}) if(CMAKE_CROSSCOMPILING) list(APPEND SUBPROCESS_SANITIZERS ${SUBPROCESS_SANITIZER}) else() subprocess_check_sanitizer_runs(${SUBPROCESS_SANITIZER} SUBPROCESS_SAN_RUNS) if(SUBPROCESS_SAN_RUNS) list(APPEND SUBPROCESS_SANITIZERS ${SUBPROCESS_SANITIZER}) endif() endif() endforeach() foreach(SUBPROCESS_SANITIZER ${SUBPROCESS_SANITIZERS}) subprocess_add_test(subprocess_${SUBPROCESS_SANITIZER}_test) target_compile_options(subprocess_${SUBPROCESS_SANITIZER}_test PRIVATE -fsanitize=${SUBPROCESS_SANITIZER} -fno-omit-frame-pointer) target_link_options(subprocess_${SUBPROCESS_SANITIZER}_test PRIVATE -fsanitize=${SUBPROCESS_SANITIZER}) endforeach() endif()