diff --git a/CMakeLists.txt b/CMakeLists.txt index b63f57d..eca0801 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,31 +15,40 @@ include(CheckCSourceCompiles) include(CheckIncludeFile) include(GNUInstallDirs) include(CMakePackageConfigHelpers) -include(CTest) -option(LIBOWFAT_BUILD_STATIC "Build static libowfat library" OFF) +option(LIBOWFAT_BUILD_STATIC "Build static libowfat/nwowfat library" OFF) option(LIBOWFAT_BUILD_SHARED "Build shared libowfat library" ON) -set(LIBOWFAT_LIBRARY_PREFIX "" CACHE STRING "Optional library basename prefix. Empty builds libowfat; nw builds libnwowfat") +set(LIBOWFAT_LIBRARY_PREFIX "" CACHE STRING "Optional library basename prefix, e.g. nw builds libnwowfat") option(LIBOWFAT_INSTALL "Install libraries and headers" ON) option(LIBOWFAT_GLIBC_COMPAT "Enable glibc-compatible feature-test defines" ON) -option(LIBOWFAT_BUILD_UNIT_TESTS "Build and register the UNITTEST sources used by GNUmakefile check" OFF) -if(LIBOWFAT_LIBRARY_PREFIX MATCHES "[^A-Za-z0-9_+-]") - message(FATAL_ERROR "LIBOWFAT_LIBRARY_PREFIX must be a simple library basename prefix, for example '' or 'nw'") +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + include(CTest) + set(_libowfat_testing_default ${BUILD_TESTING}) +else() + set(_libowfat_testing_default OFF) +endif() +option(LIBOWFAT_BUILD_TEST_PROGRAMS "Build the programs in the test directory" ${_libowfat_testing_default}) +option(LIBOWFAT_RUN_SELF_TESTS "Register non-interactive tests with CTest" ${_libowfat_testing_default}) +if(LIBOWFAT_RUN_SELF_TESTS) + enable_testing() endif() -set(LIBOWFAT_OUTPUT_NAME "${LIBOWFAT_LIBRARY_PREFIX}owfat") if(NOT LIBOWFAT_BUILD_STATIC AND NOT LIBOWFAT_BUILD_SHARED) message(FATAL_ERROR "At least one of LIBOWFAT_BUILD_STATIC or LIBOWFAT_BUILD_SHARED must be ON") endif() # Keep this CMake build close to the existing GNUmakefile, but make the checks -# out-of-tree friendly and avoid dietlibc-specific defaults. The feature -# checks use _GNU_SOURCE on Linux so glibc extension declarations stay visible. +# out-of-tree friendly and avoid dietlibc-specific defaults. glibc builds get +# _GNU_SOURCE so Linux/glibc extension declarations stay visible. set(LIBOWFAT_FEATURE_DEFINES _REENTRANT) if(LIBOWFAT_GLIBC_COMPAT AND CMAKE_SYSTEM_NAME STREQUAL "Linux") - list(APPEND LIBOWFAT_FEATURE_DEFINES _GNU_SOURCE) + check_c_source_compiles("#ifndef _GNU_SOURCE\n#error _GNU_SOURCE is not predefined\n#endif\nint main(void) { return 0; }" LIBOWFAT_GNU_SOURCE_PREDEFINED) + if(NOT LIBOWFAT_GNU_SOURCE_PREDEFINED) + list(APPEND LIBOWFAT_FEATURE_DEFINES _GNU_SOURCE) + endif() endif() +set(LIBOWFAT_OUTPUT_NAME "${LIBOWFAT_LIBRARY_PREFIX}owfat") set(CMAKE_REQUIRED_DEFINITIONS "") foreach(_def IN LISTS LIBOWFAT_FEATURE_DEFINES) @@ -73,11 +82,16 @@ set(_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") set(_generated_libowfat_dir "${_generated_dir}/libowfat") file(MAKE_DIRECTORY "${_generated_libowfat_dir}") +function(_write_generated_header name content) + file(WRITE "${_generated_dir}/${name}" "${content}") + file(WRITE "${_generated_libowfat_dir}/${name}" "${content}") +endfunction() + function(_write_feature_header name content_if_true) if(${ARGV2}) - file(WRITE "${_generated_dir}/${name}" "${content_if_true}\n") + _write_generated_header("${name}" "${content_if_true}\n") else() - file(WRITE "${_generated_dir}/${name}" "") + _write_generated_header("${name}" "") endif() endfunction() @@ -86,15 +100,15 @@ _write_feature_header(havescope.h "#define LIBC_HAS_SCOPE_ID" LIBC_HAS_SCOPE_ID) _write_feature_header(haven2i.h "#define HAVE_N2I" HAVE_N2I) _write_feature_header(haveinline.h "#define inline" NOT_HAVE_INLINE) if(HAVE_INLINE) - file(WRITE "${_generated_dir}/haveinline.h" "") + _write_generated_header(haveinline.h "") endif() _write_feature_header(havekqueue.h "#define HAVE_KQUEUE" HAVE_KQUEUE) _write_feature_header(havebsdsf.h "#define HAVE_BSDSENDFILE" HAVE_BSDSENDFILE) _write_feature_header(havesendfile.h "#define HAVE_SENDFILE" HAVE_SENDFILE) if(HAVE_EPOLL) - file(WRITE "${_generated_dir}/haveepoll.h" "#define HAVE_EPOLL 1\n") + _write_generated_header(haveepoll.h "#define HAVE_EPOLL 1\n") else() - file(WRITE "${_generated_dir}/haveepoll.h" "") + _write_generated_header(haveepoll.h "") endif() _write_feature_header(havedevpoll.h "#define HAVE_DEVPOLL" HAVE_DEVPOLL) _write_feature_header(havesigio.h "#define HAVE_SIGIO" HAVE_SIGIO) @@ -103,37 +117,39 @@ _write_feature_header(havepread.h "#define HAVE_PREAD" HAVE_PREAD) _write_feature_header(haveaccept4.h "#define HAVE_ACCEPT4" HAVE_ACCEPT4) if(HAVE_SOCKLEN_T) - file(WRITE "${_generated_dir}/havesl.h" "") + _write_generated_header(havesl.h "") else() - file(WRITE "${_generated_dir}/havesl.h" "typedef int socklen_t;\n") + _write_generated_header(havesl.h "typedef int socklen_t;\n") endif() -file(WRITE "${_generated_dir}/havealloca.h" "#include \n") +set(_havealloca_content "#include \n") if(HAVE_ALLOCA_H) - file(APPEND "${_generated_dir}/havealloca.h" "#include \n") + string(APPEND _havealloca_content "#include \n") endif() if(HAVE_MALLOC_H) - file(APPEND "${_generated_dir}/havealloca.h" "#include \n") + string(APPEND _havealloca_content "#include \n") endif() - +_write_generated_header(havealloca.h "${_havealloca_content}") if(HAVE_POLL) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/iopause.h2" "${_generated_dir}/iopause.h" COPYONLY) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/iopause.h2" "${_generated_libowfat_dir}/iopause.h" COPYONLY) else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/iopause.h1" "${_generated_dir}/iopause.h" COPYONLY) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/iopause.h1" "${_generated_libowfat_dir}/iopause.h" COPYONLY) endif() if(HAVE_SYS_SELECT_H) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/select.h2" "${_generated_dir}/select.h" COPYONLY) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/select.h2" "${_generated_libowfat_dir}/select.h" COPYONLY) else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/select.h1" "${_generated_dir}/select.h" COPYONLY) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/select.h1" "${_generated_libowfat_dir}/select.h" COPYONLY) endif() add_executable(libowfat_ent EXCLUDE_FROM_ALL "${CMAKE_CURRENT_SOURCE_DIR}/ent.c") target_include_directories(libowfat_ent PRIVATE "${_generated_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") target_compile_definitions(libowfat_ent PRIVATE _REENTRANT) add_custom_command( - OUTPUT - "${_generated_dir}/entities.h" - "${_generated_libowfat_dir}/entities.h" + OUTPUT "${_generated_dir}/entities.h" "${_generated_libowfat_dir}/entities.h" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/entities.json" "${_generated_dir}/entities.json" COMMAND libowfat_ent @@ -143,16 +159,7 @@ add_custom_command( DEPENDS libowfat_ent "${CMAKE_CURRENT_SOURCE_DIR}/entities.json" COMMENT "Generating entities.h" VERBATIM) -add_custom_target(libowfat_entities DEPENDS - "${_generated_dir}/entities.h" - "${_generated_libowfat_dir}/entities.h") - -set(LIBOWFAT_GENERATED_HEADERS - haveaccept4.h havealloca.h havebsdsf.h havedevpoll.h haveepoll.h - haveinline.h haveip6.h havekqueue.h haven2i.h havepread.h - havescope.h havesendfile.h havesigio.h havesl.h haveuint128.h - iopause.h select.h entities.h -) +add_custom_target(libowfat_entities DEPENDS "${_generated_dir}/entities.h" "${_generated_libowfat_dir}/entities.h") set(LIBOWFAT_PUBLIC_HEADERS buffer.h byte.h fmt.h ip4.h ip6.h mmap.h scan.h socket.h str.h stralloc.h @@ -170,12 +177,6 @@ foreach(_hdr IN LISTS LIBOWFAT_PUBLIC_HEADERS) endif() endforeach() -foreach(_hdr IN LISTS LIBOWFAT_GENERATED_HEADERS) - if(EXISTS "${_generated_dir}/${_hdr}") - configure_file("${_generated_dir}/${_hdr}" "${_generated_libowfat_dir}/${_hdr}" COPYONLY) - endif() -endforeach() - file(GLOB LIBOWFAT_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/array/*.c" "${CMAKE_CURRENT_SOURCE_DIR}/buffer/*.c" @@ -211,6 +212,7 @@ target_include_directories(libowfat_objects PUBLIC "$" "$" + "$" "$" "$") if(LIBOWFAT_BUILD_SHARED) @@ -243,6 +245,7 @@ foreach(_target IN LISTS _libowfat_targets) PUBLIC "$" "$" + "$" "$" "$") endforeach() @@ -253,47 +256,42 @@ elseif(LIBOWFAT_BUILD_STATIC) add_library(libowfat::libowfat ALIAS libowfat_static) endif() -if(BUILD_TESTING) - set(_libowfat_test_target libowfat_shared) - if(NOT LIBOWFAT_BUILD_SHARED) - set(_libowfat_test_target libowfat_static) - endif() - add_executable(t "${CMAKE_CURRENT_SOURCE_DIR}/t.c") - target_link_libraries(t PRIVATE ${_libowfat_test_target}) - target_include_directories(t PRIVATE "${_generated_dir}" "${_generated_libowfat_dir}" "${CMAKE_CURRENT_SOURCE_DIR}") - target_compile_definitions(t PRIVATE _REENTRANT) - find_package(Threads) - if(Threads_FOUND) - target_link_libraries(t PRIVATE Threads::Threads) - endif() - add_test(NAME libowfat_smoke COMMAND t) +if(LIBOWFAT_RUN_SELF_TESTS) + add_executable(libowfat_smoke_t "${CMAKE_CURRENT_SOURCE_DIR}/t.c") + target_link_libraries(libowfat_smoke_t PRIVATE libowfat::libowfat) + add_test(NAME libowfat_smoke_t COMMAND libowfat_smoke_t) + set_tests_properties(libowfat_smoke_t PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") +endif() - if(LIBOWFAT_BUILD_UNIT_TESTS) - file(GLOB_RECURSE LIBOWFAT_UNITTEST_SOURCES CONFIGURE_DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/*/*.c") - set(_libowfat_unittest_index 0) - foreach(_test_src IN LISTS LIBOWFAT_UNITTEST_SOURCES) - file(READ "${_test_src}" _test_content) - if(_test_content MATCHES "UNITTEST") - get_filename_component(_test_dir "${_test_src}" DIRECTORY) - get_filename_component(_test_dir_name "${_test_dir}" NAME) - get_filename_component(_test_name "${_test_src}" NAME_WE) - set(_test_target "libowfat_unittest_${_test_dir_name}_${_test_name}") - add_executable(${_test_target} "${_test_src}") - target_compile_definitions(${_test_target} PRIVATE UNITTEST _REENTRANT) - target_include_directories(${_test_target} PRIVATE - "${_generated_dir}" - "${_generated_libowfat_dir}" - "${CMAKE_CURRENT_SOURCE_DIR}") - add_dependencies(${_test_target} libowfat_entities) - list(APPEND _libowfat_unittest_targets ${_test_target}) - add_test(NAME "unittest/${_test_dir_name}/${_test_name}" COMMAND ${_test_target}) - set_tests_properties("unittest/${_test_dir_name}/${_test_name}" PROPERTIES LABELS libowfat-unit) - math(EXPR _libowfat_unittest_index "${_libowfat_unittest_index} + 1") +if(LIBOWFAT_BUILD_TEST_PROGRAMS) + file(GLOB LIBOWFAT_TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test/*.c") + foreach(_test_src IN LISTS LIBOWFAT_TEST_SOURCES) + get_filename_component(_test_name "${_test_src}" NAME_WE) + string(MAKE_C_IDENTIFIER "libowfat_test_${_test_name}" _test_target) + add_executable(${_test_target} "${_test_src}") + target_link_libraries(${_test_target} PRIVATE libowfat::libowfat) + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${_test_target} PRIVATE + -Wno-error=implicit-int + -Wno-error=incompatible-pointer-types + -Wno-error=implicit-function-declaration) + endif() + endforeach() + + if(LIBOWFAT_RUN_SELF_TESTS) + set(_libowfat_noarg_tests + array buffer_1 buffer_tosa byte_copy cas fmt fmt_httpdate fmt_human fmt_ip6 + fmt_iso8691 fmt_long fmt_longlong fmt_strm_alloca json marshal + mult netstring protobuf range scan scan_long stralloc_chomp textcode uint utf8 + ) + foreach(_test_name IN LISTS _libowfat_noarg_tests) + string(MAKE_C_IDENTIFIER "libowfat_test_${_test_name}" _test_target) + if(TARGET ${_test_target}) + add_test(NAME ${_test_target} COMMAND ${_test_target}) + set_tests_properties(${_test_target} PROPERTIES WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") endif() endforeach() - add_custom_target(libowfat-unit-tests DEPENDS ${_libowfat_unittest_targets}) endif() endif() diff --git a/README.md b/README.md index 653f280..8341f25 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,97 @@ # libowfat -This tree keeps the original GNUmakefile build unchanged and adds a standalone -CMake build for projects that want to consume libowfat directly or as a CMake -subdirectory. +This tree keeps the original GNUmakefile build untouched and adds an optional +CMake build for projects that want to consume libowfat directly with CMake. ## CMake build -Out-of-tree builds are supported and recommended: +Out-of-tree builds are supported: ```sh cmake -S . -B build cmake --build build +ctest --test-dir build --output-on-failure ``` -The CMake build reads the library version from the first line of `CHANGES`. -For example, with the current `CHANGES` this produces version `0.35`. +By default the CMake build produces the normal shared library name: -By default CMake builds the shared library. Static and shared builds can be -selected independently: +```text +libowfat.so +``` + +A prefix can be added to the library basename. For example, this builds +`libnwowfat.so` and, when static builds are enabled, `libnwowfat.a`: ```sh -cmake -S . -B build \ - -DLIBOWFAT_BUILD_SHARED=ON \ - -DLIBOWFAT_BUILD_STATIC=ON +cmake -S . -B build-nw \ + -DLIBOWFAT_LIBRARY_PREFIX=nw \ + -DLIBOWFAT_BUILD_STATIC=ON \ + -DLIBOWFAT_BUILD_SHARED=ON +cmake --build build-nw ``` -The installed library basename can be prefixed without changing the headers or -CMake package name. The default prefix is empty, so the output is `libowfat`. -For a NetWare-flavoured name, pass `nw` and the output becomes `libnwowfat`: +The library version is read from the first line of `CHANGES`. -```sh -cmake -S . -B build -DLIBOWFAT_LIBRARY_PREFIX=nw -``` +## Installation -## Install - -Installation uses CMake `GNUInstallDirs`, so the library directory follows the -target platform and install prefix (`lib`, `lib64`, etc.). Headers are installed -below `include/libowfat`. +CMake installation uses `GNUInstallDirs`, so the library directory follows the +platform and the configured prefix, for example `lib` or `lib64`: ```sh cmake --install build --prefix /usr ``` -Useful install options: +Headers are installed under: -```sh -cmake -S . -B build \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_INSTALL_LIBDIR=lib64 +```text +${prefix}/include/libowfat ``` -## Generated compatibility headers +Generated compatibility headers such as `haveip6.h`, `havesendfile.h`, +`haveaccept4.h`, `havesl.h`, `iopause.h`, `select.h`, and `entities.h` are +installed there too. This lets consumers include either `libowfat/foo.h` or, +for legacy code that relies on the target include path, plain `foo.h`. -The CMake build generates the same style of compatibility headers as the -GNUmakefile build, but places them in the build tree instead of the source tree. -Both the build-tree target and the installed package export include paths for: +## Using from another CMake project -- `/generated` -- `/generated/libowfat` -- `/include` -- `/include/libowfat` +As an installed package: -This lets normal includes such as `` work, and also keeps -internal or compatibility includes such as `"haveip6.h"`, `"havesendfile.h"`, -`"iopause.h"`, `"select.h"`, and `"entities.h"` available when the project is -used through `add_subdirectory()` or `find_package(libowfat CONFIG REQUIRED)`. +```cmake +find_package(libowfat CONFIG REQUIRED) +target_link_libraries(myapp PRIVATE libowfat::libowfat) +``` + +As a subdirectory or submodule: + +```cmake +add_subdirectory(extern/libowfat) +target_link_libraries(myapp PRIVATE libowfat::libowfat) +``` + +The exported build-tree target includes both the source include directory and +the generated header directories, so generated `have*.h` headers are available +without copying files into the source tree. ## Tests -The GNUmakefile `all` target builds the root `t` smoke-test program; CMake does -the same when `BUILD_TESTING` is enabled, and registers it with CTest: +When libowfat is built as the top-level CMake project, `BUILD_TESTING=ON` +registers `t.c` as a smoke test and builds the programs under `test/`: ```sh +cmake -S . -B build -DBUILD_TESTING=ON +cmake --build build ctest --test-dir build --output-on-failure ``` -The `UNITTEST` sources used by the GNUmakefile `check` target can also be built -and registered with CTest: +Only the non-interactive tests are registered with CTest. Network/server/demo +programs and tests that need command-line input are compiled but not run by +CTest. + +When libowfat is used through `add_subdirectory()`, tests are off by default so +that a parent project does not unexpectedly build the whole test tree. They can +be enabled explicitly: ```sh -cmake -S . -B build -DLIBOWFAT_BUILD_UNIT_TESTS=ON -cmake --build build -ctest --test-dir build --output-on-failure -L libowfat-unit +-DLIBOWFAT_BUILD_TEST_PROGRAMS=ON +-DLIBOWFAT_RUN_SELF_TESTS=ON ``` - -## glibc compatibility - -The CMake build uses the system C compiler and glibc-compatible feature checks -instead of dietlibc-specific assumptions. On Linux the configure checks use -`_GNU_SOURCE` so glibc extension declarations are visible; the library itself -still relies on the existing source-level feature defines where individual -files need them.