Compare commits

..

1 Commits

Author SHA1 Message Date
Martchus f3b0be6620 WIP: Add CLI-wrapper for Windows
Starting the console from a GUI application is not working very
well - so let's just provide a 2nd executable for the CLI. It
will be a simple console application that merely invokes the main
application passing all standard I/O.
2023-05-06 22:48:27 +02:00
41 changed files with 401 additions and 1164 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
project(c++utilities)
@ -96,11 +96,17 @@ set(CMAKE_TEMPLATE_FILES
cmake/templates/global.h.in
cmake/templates/version.h.in
cmake/templates/template.pc.in)
if (WIN32)
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in cmake/templates/windows-cli-wrapper.rc.in
cmake/templates/cli-wrapper.cpp)
set(SCRIPT_FILES)
if (MINGW)
list(APPEND CMAKE_TEMPLATE_FILES
cmake/templates/windows.rc.in
cmake/templates/windows-cli-wrapper.rc.in
)
list(APPEND SCRIPT_FILES scripts/wine.sh)
endif ()
if (WIN32)
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/cli-wrapper.cpp)
endif ()
set(EXCLUDED_FILES cmake/templates/cli-wrapper.cpp)
set(DOC_FILES README.md doc/buildvariables.md doc/testapplication.md)
set(EXTRA_FILES tests/calculateoverallcoverage.awk coding-style.clang-format)
@ -116,15 +122,14 @@ set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "Useful C++ classes and routines such as argument parser, IO and conversion utilities")
set(META_VERSION_MAJOR 5)
set(META_VERSION_MINOR 24)
set(META_VERSION_PATCH 9)
set(META_VERSION_MINOR 23)
set(META_VERSION_PATCH 0)
# find required 3rd party libraries
include(3rdParty)
use_iconv(AUTO_LINKAGE REQUIRED)
# configure use of native file buffer and its backend implementation if enabled
set(REQUIRED_BOOST_COMPONENTS "")
set(USE_NATIVE_FILE_BUFFER_BY_DEFAULT OFF)
if (WIN32
OR ANDROID
@ -154,7 +159,7 @@ if (USE_NATIVE_FILE_BUFFER)
endforeach ()
else ()
message(STATUS "Using boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_sink> for NativeFileStream")
list(APPEND REQUIRED_BOOST_COMPONENTS iostreams)
use_package(TARGET_NAME Boost::iostreams PACKAGE_NAME Boost PACKAGE_ARGS "REQUIRED;COMPONENTS;iostreams")
foreach (NATIVE_FILE_STREAM_IMPL_FILE ${NATIVE_FILE_STREAM_IMPL_FILES})
set_property(
SOURCE ${NATIVE_FILE_STREAM_IMPL_FILE}
@ -166,26 +171,6 @@ else ()
message(STATUS "Using std::fstream for NativeFileStream")
endif ()
# configure use of Boost.Process for launching test applications on Windows
if (WIN32)
option(USE_BOOST_PROCESS "enables use of Boost.Process to launch test applications" ON)
if (USE_BOOST_PROCESS)
list(APPEND REQUIRED_BOOST_COMPONENTS filesystem)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_BOOST_PROCESS)
list(APPEND PRIVATE_LIBRARIES ws2_32) # needed by Boost.Asio
use_package(TARGET_NAME Threads::Threads PACKAGE_NAME Threads PACKAGE_ARGS REQUIRED)
endif ()
endif ()
# configure usage of Boost
if (REQUIRED_BOOST_COMPONENTS)
set(BOOST_ARGS REQUIRED COMPONENTS ${REQUIRED_BOOST_COMPONENTS})
use_package(TARGET_NAME Boost::boost PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
foreach (COMPONENT ${REQUIRED_BOOST_COMPONENTS})
use_package(TARGET_NAME Boost::${COMPONENT} PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}")
endforeach ()
endif ()
# configure required libraries for std::filesystem
option(USE_STANDARD_FILESYSTEM "uses std::filesystem; if disabled Bash completion for files and directories is not working"
ON)

View File

@ -21,27 +21,6 @@
"WEBVIEW_PROVIDER": {"type": "STRING", "value": "none"}
}
},
{
"name": "clang",
"inherits": "default",
"displayName": "Use clang/clang++",
"description": "Enforces use of clang/clang++ even when it is not the system default",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-clang",
"cacheVariables": {
"CMAKE_C_COMPILER": {"type": "STRING", "value": "clang"},
"CMAKE_CXX_COMPILER": {"type": "STRING", "value": "clang++"}
}
},
{
"name": "libc++",
"inherits": "clang",
"displayName": "Use clang/clang++ and libc++",
"description": "Enforces use of clang/clang++ and libc++ even when it is not the system default",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-clang-libc++",
"cacheVariables": {
"CMAKE_CXX_FLAGS": {"type": "STRING", "value": "$env{CXXFLAGS} -stdlib=libc++"}
}
},
{
"name": "no-kde",
"inherits": "default",
@ -90,23 +69,6 @@
"CONFIGURATION_TARGET_SUFFIX": {"type": "STRING", "value": "devel"}
}
},
{
"name": "devel-clang",
"inherits": ["devel", "clang"],
"displayName": "Development config using clang",
"description": "Combination of devel and libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-clang"
},
{
"name": "devel-libc++",
"inherits": ["devel", "libc++"],
"displayName": "Development config using libc++",
"description": "Combination of devel and libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++",
"cacheVariables": {
"ENABLE_CPP_UNIT": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "devel-qt6",
"inherits": ["qt6", "devel"],
@ -115,37 +77,9 @@
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-qt6",
"cacheVariables": {
"QT_PACKAGE_PREFIX": {"type": "STRING", "value": "Qt6"},
"QT_MAJOR_VERSION": {"type": "STRING", "value": "6"},
"KF_PACKAGE_PREFIX": {"type": "STRING", "value": "KF6"},
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"},
"NO_PLASMOID": {"type": "BOOL", "value": "ON"},
"NO_FILE_ITEM_ACTION_PLUGIN": {"type": "BOOL", "value": "ON"}
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-unity",
"inherits": ["devel-qt6"],
"displayName": "Development config creating a unity build using Qt 6",
"description": "Same as devel-qt6 but configures makes a unity build",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-unity",
"cacheVariables": {
"CMAKE_UNITY_BUILD": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-clang-qt6",
"inherits": ["qt6", "devel-clang"],
"displayName": "Development config using clang and Qt 6",
"description": "Combination of qt6 and devel-clang",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-clang-qt6"
},
{
"name": "devel-libc++-qt6",
"inherits": ["qt6", "devel-libc++"],
"displayName": "Development config using libc++ and Qt 6",
"description": "Combination of qt6 and devel-libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++-qt6"
},
{
"name": "debug",
"inherits": "devel",
@ -156,71 +90,9 @@
"CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Debug"}
}
},
{
"name": "debug-qt6",
"inherits": ["debug", "devel-qt6"],
"displayName": "Generic debug build with development config using Qt 6",
"description": "Same as devel-qt6 but creates a debug build",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/debug-qt6"
},
{
"name": "debug-kde",
"inherits": "debug-qt6",
"displayName": "Generic debug build with development config with KDE integrations enabled",
"description": "Same as debug-qt6 but with KDE integrations enabled",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/debug-kde",
"cacheVariables": {
"NO_PLASMOID": {"type": "BOOL", "value": "OFF"},
"NO_FILE_ITEM_ACTION_PLUGIN": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "debug-kde-custom",
"inherits": "debug-kde",
"displayName": "Generic debug build with development config using custom KDE build",
"description": "Same as debug-kde but with custom KDE installation from KDE_INSTALL_DIR",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/debug-kde-custom",
"cacheVariables": {
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{KDE_INSTALL_DIR}"},
"CMAKE_INSTALL_PREFIX": {"type": "PATH", "value": "$env{KDE_INSTALL_DIR}"}
}
},
{
"name": "arch-*-w64-mingw32",
"inherits": ["no-webview", "no-kde"],
"environment": {
"CPPFLAGS": "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS",
"CFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"CXXFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"LDFLAGS": "-Wl,-O1,--sort-common,--as-needed -fstack-protector"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"},
"VERSIONED_MINGW_LIBRARIES": {"type": "BOOL", "value": "ON"},
"ENABLE_TARGETS_FOR_MINGW_CROSS_PACKAGING": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-i686-w64-mingw32",
"inherits": "arch-*-w64-mingw32",
"displayName": "Target i686-w64-mingw32 using Arch Linux's mingw-w64 packaging",
"description": "Build targeting i686-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32",
"toolchainFile": "/usr/share/mingw/toolchain-i686-w64-mingw32.cmake",
"environment": {
"CROSS_TOOL_PREFIX": "i686-w64-mingw32-",
"CROSS_INSTALL_PREFIX": "/usr/i686-w64-mingw32",
"PATH": "$env{CROSS_INSTALL_PREFIX}/bin:$penv{PATH}"
},
"cacheVariables": {
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CROSSCOMPILING_EMULATOR": {"type": "PATH", "value": "/usr/bin/i686-w64-mingw32-wine"}
}
},
{
"name": "arch-x86_64-w64-mingw32",
"inherits": "arch-*-w64-mingw32",
"inherits": ["no-webview", "no-kde"],
"displayName": "Target x86_64-w64-mingw32 using Arch Linux's mingw-w64 packaging",
"description": "Build targeting x86_64-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32",
@ -228,28 +100,20 @@
"environment": {
"CROSS_TOOL_PREFIX": "x86_64-w64-mingw32-",
"CROSS_INSTALL_PREFIX": "/usr/x86_64-w64-mingw32",
"CPPFLAGS": "-D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS",
"CFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"CXXFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"LDFLAGS": "-Wl,-O1,--sort-common,--as-needed -fstack-protector",
"PATH": "$env{CROSS_INSTALL_PREFIX}/bin:$penv{PATH}"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"},
"VERSIONED_MINGW_LIBRARIES": {"type": "BOOL", "value": "ON"},
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CROSSCOMPILING_EMULATOR": {"type": "PATH", "value": "/usr/bin/x86_64-w64-mingw32-wine"}
}
},
{
"name": "arch-i686-w64-mingw32-static",
"inherits": "arch-i686-w64-mingw32",
"displayName": "Target i686-w64-mingw32 using Arch Linux's mingw-w64 packaging (static)",
"description": "Build targeting i686-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-static",
"toolchainFile": "/usr/share/mingw/toolchain-i686-w64-mingw32-static.cmake",
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_FIND_LIBRARY_SUFFIXES": {"type": "STRING", "value": ".a;.lib"},
"STATIC_LIBRARY_LINKAGE": {"type": "BOOL", "value": "ON"},
"STATIC_LINKAGE": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-x86_64-w64-mingw32-static",
"inherits": "arch-x86_64-w64-mingw32",
@ -258,19 +122,9 @@
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-static",
"toolchainFile": "/usr/share/mingw/toolchain-x86_64-w64-mingw32-static.cmake",
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_FIND_LIBRARY_SUFFIXES": {"type": "STRING", "value": ".a;.lib"},
"STATIC_LIBRARY_LINKAGE": {"type": "BOOL", "value": "ON"},
"STATIC_LINKAGE": {"type": "BOOL", "value": "ON"}
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "arch-i686-w64-mingw32-qt6",
"inherits": ["qt6", "arch-i686-w64-mingw32"],
"displayName": "Combination of qt6 and arch-i686-w64-mingw32",
"description": "See description of qt6 and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-qt6"
},
{
"name": "arch-x86_64-w64-mingw32-qt6",
"inherits": ["qt6", "arch-x86_64-w64-mingw32"],
@ -278,13 +132,6 @@
"description": "See description of qt6 and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-qt6"
},
{
"name": "arch-i686-w64-mingw32-static-qt6",
"inherits": ["qt6", "arch-i686-w64-mingw32-static"],
"displayName": "Combination of qt6 and arch-i686-w64-mingw32-static",
"description": "See description of qt6 and arch-i686-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-static-qt6"
},
{
"name": "arch-x86_64-w64-mingw32-static-qt6",
"inherits": ["qt6", "arch-x86_64-w64-mingw32-static"],
@ -292,13 +139,6 @@
"description": "See description of qt6 and arch-x86_64-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-static-qt6"
},
{
"name": "arch-i686-w64-mingw32-devel",
"inherits": ["devel", "arch-i686-w64-mingw32"],
"displayName": "Combination of devel and arch-i686-w64-mingw32",
"description": "See descriptions of devel and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-devel",
"inherits": ["devel", "arch-x86_64-w64-mingw32"],
@ -306,34 +146,6 @@
"description": "See descriptions of devel and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-i686-w64-mingw32-devel-qt6",
"inherits": ["qt6", "devel", "arch-i686-w64-mingw32"],
"displayName": "Combination of qt6, devel and arch-i686-w64-mingw32",
"description": "See descriptions of devel and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-devel-qt6",
"inherits": ["qt6", "devel", "arch-x86_64-w64-mingw32"],
"displayName": "Combination of qt6, devel and arch-x86_64-w64-mingw32",
"description": "See descriptions of devel and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-i686-w64-mingw32-static-devel-qt6",
"inherits": ["qt6", "devel", "arch-i686-w64-mingw32-static"],
"displayName": "Combination of qt6, devel and arch-i686-w64-mingw32-static",
"description": "See descriptions of devel and arch-i686-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-static-devel-qt6",
"inherits": ["qt6", "devel", "arch-x86_64-w64-mingw32-static"],
"displayName": "Combination of qt6, devel and arch-x86_64-w64-mingw32-static",
"description": "See descriptions of devel and arch-x86_64-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-static-compat",
"inherits": ["no-webview", "no-kde", "qt6"],
@ -358,14 +170,10 @@
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_INSTALL_PREFIX": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}"},
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}"},
"CMAKE_SKIP_BUILD_RPATH": {"type": "BOOL", "value": "ON"},
"CMAKE_SKIP_INSTALL_RPATH": {"type": "BOOL", "value": "ON"},
"CMAKE_DISABLE_FIND_PACKAGE_harfbuzz": {"type": "BOOL", "value": "ON"},
"Boost_USE_STATIC_RUNTIME": {"type": "BOOL", "value": "ON"},
"GLIB2_USE_PKG_CONFIG": {"type": "BOOL", "value": "ON"},
"WAYLAND_USE_PKG_CONFIG": {"type": "BOOL", "value": "ON"},
"OPENGL_glu_LIBRARY": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/lib/libGLU.a"},
"USE_BUNDLED_RTMIDI": {"type": "BOOL", "value": "ON"}
"WAYLAND_USE_PKG_CONFIG": {"type": "BOOL", "value": "ON"}
}
},
{
@ -379,13 +187,12 @@
"name": "win-x64-msvc-static",
"inherits": ["no-webview", "no-kde", "qt6"],
"displayName": "Target x64-windows-static on Windows",
"description": "Build on Windows targeting x64-windows-static using MSVC, Qt 6 (for Qt libs and CMake/Ninja), vcpkg (for other dependencies) and MSYS2 (for Perl, Go, ffmpeg and other tools)",
"description": "Build on Windows targeting x64-windows-static using MSVC, Qt 6 (for Qt libs and CMake/Ninja), vcpkg (for other dependencies) and MSYS2 (for Perl)",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static",
"environment": {
"INCLUDE": "$env{MSVC_ROOT}/include;$env{MSVC_ROOT}/ATLMFC/include;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}/ucrt;$env{WIN_KITS_ROOT}//include/$env{WIN_KITS_VERSION}//um;$env{WIN_KITS_ROOT}//include/$env{WIN_KITS_VERSION}//shared;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}//winrt;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}//cppwinrt",
"LIB": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}//um/x64",
"LIBPATH": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/um/x64",
"GOROOT": "$env{MSYS2_ROOT}/mingw64/lib/go"
"INCLUDE": "$env{MSVC_ROOT}/include;$env{MSVC_ROOT}/ATLMFC/include;$env{WIN_KITS_ROOT}/include/10.0.22000.0/ucrt;$env{WIN_KITS_ROOT}//include/10.0.22000.0//um;$env{WIN_KITS_ROOT}//include/10.0.22000.0//shared;$env{WIN_KITS_ROOT}/include/10.0.22000.0//winrt;$env{WIN_KITS_ROOT}/include/10.0.22000.0//cppwinrt",
"LIB": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0/ucrt/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0//um/x64",
"LIBPATH": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0/ucrt/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0/um/x64"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
@ -397,9 +204,9 @@
"CMAKE_AR_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/Hostx64/x64/lib.exe"},
"CMAKE_C_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/HostX64/x64/cl.exe"},
"CMAKE_CXX_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/HostX64/x64/cl.exe"},
"CMAKE_RC_COMPILER": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/$env{WIN_KITS_VERSION}/x64/rc.exe"},
"CMAKE_RC_COMPILER": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/10.0.22000.0/x64/rc.exe"},
"CMAKE_LINKER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/Hostx64/x64/link.exe"},
"CMAKE_MT": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/$env{WIN_KITS_VERSION}/x64/mt.exe"},
"CMAKE_MT": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/10.0.22000.0/x64/mt.exe"},
"CMAKE_MSVC_RUNTIME_LIBRARY": {"type": "STRING", "value": "MultiThreaded$<$<CONFIG:Debug>:Debug>"},
"CMAKE_CXX_FLAGS_DEBUG": {"type": "STRING", "value": "/MTd /Zi /Ob0 /Od /RTC1"},
"CMAKE_CXX_FLAGS_RELEASE": {"type": "STRING", "value": "/MT /O2 /Ob2 /DNDEBUG"},
@ -409,13 +216,11 @@
"PERL_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/usr/bin/perl.exe"},
"DOXYGEN_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/doxygen.exe"},
"CLANG_FORMAT_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/clang-format.exe"},
"GO_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/go.exe"},
"FFMPEG_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/ffmpeg.exe"},
"REALPATH_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/usr/bin/realpath.exe"},
"FORCE_EXTERNAL_ICONV": {"type": "BOOL", "value": "ON"},
"CPP_UNIT_LIB": {"type": "FILEPATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static/lib/cppunit.lib"},
"CPP_UNIT_INCLUDE_DIR": {"type": "PATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static/include"},
"Vulkan_INCLUDE_DIR": {"type": "PATH", "value": ""},
"QUICK_GUI": {"type": "BOOL", "value": "OFF"}
}
},
@ -446,33 +251,15 @@
],
"buildPresets": [
{"name": "default", "configurePreset": "default"},
{"name": "libc++", "configurePreset": "libc++"},
{"name": "qt6", "configurePreset": "qt6"},
{"name": "devel", "configurePreset": "devel"},
{"name": "devel-clang", "configurePreset": "devel-clang"},
{"name": "devel-libc++", "configurePreset": "devel-libc++"},
{"name": "devel-qt6", "configurePreset": "devel-qt6"},
{"name": "devel-unity", "configurePreset": "devel-unity"},
{"name": "devel-clang-qt6", "configurePreset": "devel-clang-qt6"},
{"name": "devel-libc++-qt6", "configurePreset": "devel-libc++-qt6"},
{"name": "debug", "configurePreset": "debug"},
{"name": "debug-qt6", "configurePreset": "debug-qt6"},
{"name": "debug-kde", "configurePreset": "debug-kde"},
{"name": "debug-kde-custom", "configurePreset": "debug-kde-custom"},
{"name": "arch-i686-w64-mingw32", "configurePreset": "arch-i686-w64-mingw32"},
{"name": "arch-x86_64-w64-mingw32", "configurePreset": "arch-x86_64-w64-mingw32"},
{"name": "arch-i686-w64-mingw32-static", "configurePreset": "arch-i686-w64-mingw32-static"},
{"name": "arch-x86_64-w64-mingw32-static", "configurePreset": "arch-x86_64-w64-mingw32-static"},
{"name": "arch-i686-w64-mingw32-qt6", "configurePreset": "arch-i686-w64-mingw32-qt6"},
{"name": "arch-x86_64-w64-mingw32-qt6", "configurePreset": "arch-x86_64-w64-mingw32-qt6"},
{"name": "arch-i686-w64-mingw32-static-qt6", "configurePreset": "arch-i686-w64-mingw32-static-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-qt6"},
{"name": "arch-i686-w64-mingw32-devel", "configurePreset": "arch-i686-w64-mingw32-devel"},
{"name": "arch-x86_64-w64-mingw32-devel", "configurePreset": "arch-x86_64-w64-mingw32-devel"},
{"name": "arch-i686-w64-mingw32-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-devel-qt6"},
{"name": "arch-i686-w64-mingw32-static-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-static-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-devel-qt6"},
{"name": "arch-static-compat", "configurePreset": "arch-static-compat"},
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"},
{"name": "win-x64-msvc-static", "configurePreset": "win-x64-msvc-static"},

160
README.md
View File

@ -61,11 +61,11 @@ These build instructions apply to `c++utilities` but also to my other projects u
* C++ compiler supporting C++17, tested with
- g++ to compile for GNU/Linux and Windows
- clang++ to compile for GNU/Linux and Android
* CMake (at least 3.17.0) and Ninja or GNU Make
* CMake (at least 3.3.0) and Ninja or GNU Make
* cppunit for unit tests (optional)
* Doxygen for API documentation (optional)
* Graphviz for diagrams in the API documentation (optional)
* clang-format and cmake-format for tidying (optional)
* clang-format for tidying (optional)
* llvm-profdata, llvm-cov and cppunit for source-based code coverage analysis (optional)
* [appstreamcli](https://www.freedesktop.org/wiki/Distributions/AppStream/) for validation
of generated AppStream files (optional)
@ -76,20 +76,19 @@ These build instructions apply to `c++utilities` but also to my other projects u
- libstdc++ under GNU/Linux and Windows
- libc++ under GNU/Linux and Android
* glibc with iconv support or standalone iconv library
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional, use `USE_NATIVE_FILE_BUFFER=OFF` to disable)
* Boost.Process for `execApp()` test helper under Windows (optional, use `USE_BOOST_PROCESS=OFF` to disable)
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional)
* My other projects have further dependencies such as Qt. Checkout the README of these
projects for further details.
### How to build
Generic example using Ninja:
Example using Ninja:
```
cmake -G Ninja \
-S "path/to/source/directory" \
-B "path/to/build/directory" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="/final/install/location"
# build the binaries
make # build the binaries
cmake --build "path/to/build/directory"
# format source files (optional, must be enabled via CLANG_FORMAT_ENABLED)
cmake --build "path/to/build/directory" --target tidy
@ -104,11 +103,10 @@ DESTDIR="/temporary/install/location" \
cmake --install "path/to/build/directory"
```
This example is rather generic. For a development build I recommended using CMakePresets as
documented in the "CMake presets" section below. It also contains more concrete instructions for
building on Windows.
#### General notes
* ```LIB_SUFFIX```, ```LIB_SUFFIX_32``` and ```LIB_SUFFIX_64``` can be set to specify a suffix
for the library directory, eg. lib*64* or lib*32*. The 32/64 variants are only used when building
for 32/64-bit architecture.
* By default the build system will *build* static libs. To *build* shared libraries *instead*, set
`BUILD_SHARED_LIBS=ON`.
* By default the build system will prefer *linking against* shared libraries. To force *linking against*
@ -117,15 +115,14 @@ building on Windows.
* If thread local storage is not supported by your compiler/platform (might be the case on MacOS), you can
disable making use of it via `ENABLE_THREAD_LOCAL=OFF`.
* To disable use of `std::filesystem`, set `USE_STANDARD_FILESYSTEM=OFF`. Note that the Bash completion will
not be able to suggest files and directories with `USE_STANDARD_FILESYSTEM=OFF`. Note that this will only
help with `c++utilities` itself. My other projects might use `std::filesystem` unconditionally.
not be able to suggest files and directories with `USE_STANDARD_FILESYSTEM=OFF`.
* To disable `NativeFileStream` (and make it just a regular `std::fstream`), set `USE_NATIVE_FILE_BUFFER=OFF`.
Note that handling paths with non-ASCII characters will then cease to work on Windows.
* The Qt-based applications support bundeling icon themes by specifying e.g.
`BUILTIN_ICON_THEMES=breeze;breeze-dark`.
* This variable must be set when building the application (not when building any of the libraries).
* The specified icon themes need to be installed in the usual location. Otherwise, use e.g.
`BUILTIN_ICON_THEMES_SEARCH_PATH=D:/programming/misc/breeze-icons/usr/share/icons` to specify the
`DBUILTIN_ICON_THEMES_SEARCH_PATH=D:/programming/misc/breeze-icons/usr/share/icons` to specify the
search path.
* For more detailed documentation, see the documentation about build variables (in
[directory doc](https://github.com/Martchus/cpp-utilities/blob/master/doc/buildvariables.md) and
@ -158,9 +155,7 @@ tagparser and tageditor) as one big project.
This can be easily achieved by using CMake's `add_subdirectory()` function. For project files see the repository
[subdirs](https://github.com/Martchus/subdirs). For an example, see
[build instructions for Syncthing Tray](https://github.com/Martchus/syncthingtray#building-this-straight) or
[build instructions for Tag Editor](https://github.com/Martchus/tageditor#building-this-straight). The `subdirs`
repository also contains the script `sync-all.sh` to clone all possibly relevant repositories and keep them
up-to-date later on.
[build instructions for Tag Editor](https://github.com/Martchus/tageditor#building-this-straight).
For a debug build, use `-DCMAKE_BUILD_TYPE=Debug`. To tweak various settings (e.g. warnings) for development,
use `-DENABLE_DEVEL_DEFAULTS=ON`.
@ -171,133 +166,46 @@ but also some specific to certain Arch Linux packaging found in the AUR and my P
Use `cmake --list-presets` to list all presets. All `cmake` commands need to be executed within the source
directory. Builds will be created within a sub-directory of the path specified via the environment variable
`BUILD_DIR`.
`BUILD_DIR`. Here is an example for creating a build with the `arch-static-compat-devel` preset and invoking
tests:
The most useful presets for development are likely `devel`, `devel-qt6` and `debug`. Note that the `devel`
preset (and all presets inheriting from it) use `ccache` which therefore needs to be installed.
```
export BUILD_DIR=$HOME/builds # set build directory via environment variable
cmake --preset arch-static-compat-devel # configure build
cmake --build --preset arch-static-compat-devel -- -v # conduct build
cmake --build --preset arch-static-compat-devel --target check # run tests
cmake --build --preset arch-static-compat-devel --target tidy # apply formatting
```
Here is a simple example to build with the `devel-qt6` preset:
```
export BUILD_DIR=$HOME/builds # set build directory via environment variable
cmake --preset devel-qt6 # configure build
cmake --build --preset devel-qt6 -- -v # conduct build
cmake --build --preset devel-qt6 --target check # run tests
cmake --build --preset devel-qt6 --target tidy # apply formatting
```
This preset is quite special (see [PKGBUILDs](https://github.com/Martchus/PKGBUILDs#static-gnulinux-libraries)
for details about it). The most useful presets for development are likely `devel`, `devel-qt6` and `debug`.
Note that these presets are supposed to cover all of my projects (so some of them aren't really making a
difference when just building `c++utilities` itself). To use presets in other projects, simply symlink the
file `CMakePresets.json` into the source directory of those projects. This is also done by the "subdirs"
projects mentioned in the previous section.
file `CMakePresets.json` into the source directory of those projects which works with the "subdirs" projects
mentioned in the previous section as well.
After invoking the configuration via the command-line, you can also open the project in Qt Creator and import
it as an existing build (instead of adding a new build configuration).
Note that the `devel` preset (and all presets inheriting from it) use `ccache` which therefore needs to be
installed.
##### Remarks for building on Windows
To create a development build on Windows, it is most straight forward to use the `devel-qt6` preset in a
MSYS2 mingw64 shell. Set the `BUILD_DIR` environment variable to specify the directory to store build
artefacts.
Run the following commands to build one of my applications and its `c++utilities`/`qtutilities` dependencies
in one go (in this example Syncthing Tray):
```
# install dependencies; you may strip down this list depending on the application and features to enable
pacman -Syu git perl-YAML mingw-w64-x86_64-gcc mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-cppunit mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-declarative mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-qt6-svg mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-go
# clone repositories as mentioned under "Building this straight" in the application's README file
cd /path/to/store/sources
...
git clone ...
...
# configure and invoke the build
cd subdirs/syncthingtray
cmake --preset devel-qt6
cmake --build "$BUILD_DIR/syncthingtray/devel-qt6" devel-qt6 -- -v
```
Run the following commands to build libraries individually (in this example `tagparser`) and
installing them in some directory (in this example `$BUILD_DIR/install`) for use in another
project:
```
# install dependencies
pacman -Syu git mingw-w64-x86_64-gcc mingw-w64-x86_64-ccache mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-cppunit
# clone relevant repositories, e.g. here just tagparser and its dependency c++utilities
cd /path/to/store/sources
git config core.symlinks true
git clone https://github.com/Martchus/cpp-utilities.git c++utilities
git clone https://github.com/Martchus/tagparser.git
# configure and invoke the build and installation of the projects individually
cmake --preset devel-qt6 -S c++utilities -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/install"
cmake --build "$BUILD_DIR/c++utilities/devel-qt6" --target install -- -v
ln -rs c++utilities/CMakePresets.json tagparser/CMakePresets.json
cmake --preset devel-qt6 -S tagparser -DCMAKE_INSTALL_PREFIX="$BUILD_DIR/install"
cmake --build "$BUILD_DIR/tagparser/devel-qt6" --target install -- -v
```
Note that:
* Not all those dependencies are required by all my projects and some are just optional.
* The second example to just build `c++utilities` and `tagparser` already shows a stripped-down list
of dependencies.
* Especially `mingw-w64-x86_64-go` is only required when building Syncthing Tray with built-in
Syncthing-library enabled. To build in an MSYS2 shell one needs to invoke `export GOROOT=/mingw64/lib/go`
so Go can find its root.
* All Qt-related dependencies are generally only required for building with Qt GUI, e.g. Tag Editor
and Password Manager can be built without Qt GUI. The libraries `c++utilities` and `tagparser` don't
require Qt at all.
* You can also easily install Qt Creator via MSYS2 using `pacman -S mingw-w64-x86_64-qt-creator`.
* You must *not* use the presets containing `mingw-w64` in their name as those are only intended for cross-compilation
on Arch Linux.
###### Building with MSVC
To build with MSVC you can use the `win-x64-msvc-static` preset. This preset (and all presets inheriting from it) need
various additional environment variables to be set and you need to install dependencies from various sources:
* `MSYS2_ROOT`: for Perl (only used by `qtforkawesome` so far), `clang-format`, Doxygen, FFmpeg and Go (only
used by `libsyncthing`) provided via MSYS2 packages; install the following packages:
The `win-x64-msvc-static` preset (and all presets inheriting from it) need various additional environment
variables to be set:
* `MSYS2_ROOT`: for Perl (only used by `qtforkawesome` so far), `clang-format`, Doxygen and FFmpeg provided
via MSYS2 packages; install the following packages:
```
pacman -Syu perl-YAML mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-go
pacman -Syu perl mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg
```
* `MSVC_ROOT`: for compiler and stdlib usually installed as part of Visual Studio setup, e.g.
`C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933`
* `WIN_KITS_ROOT`: for Windows platform headers/libraries usually installed as part of Visual Studio setup,
e.g. `C:/Program Files (x86)/Windows Kits/10`
* `WIN_KITS_VERSION`: the relevant subdirectory within `WIN_KITS_ROOT`, usually a version number like `10.0.22621.0`
* `QT_ROOT`: for Qt libraries provided by the official Qt installer, e.g. `D:/programming/qt/6.5.0/msvc2019_64`
* `QT_TOOLS`: for additional build tools provided by the official Qt installer, e.g. `D:/programming/qt/Tools`
* `QT_ROOT`: for Qt libraries provided by official Qt installer, e.g. `D:/programming/qt/6.5.0/msvc2019_64`
* `QT_TOOLS`: for additional build tools provided by official Qt installer, e.g. `D:/programming/qt/Tools`
* `VCPKG_ROOT`: directory of VCPKG checkout used for other dependencies; install the following packages:
```
vcpkg install boost-system:x64-windows-static boost-iostreams:x64-windows-static boost-filesystem:x64-windows-static boost-hana:x64-windows-static boost-process:x64-windows-static boost-asio:x64-windows-static libiconv:x64-windows-static zlib:x64-windows-static openssl:x64-windows-static cppunit:x64-windows-static
```
When building with MSVC, do *not* use any of the MSYS2 shells. The environment of those shells leads to
build problems. You can however use CMake and Ninja from MSYS2's mingw-w64 packaging (instead of the CMake
version from Qt's installer). Then you need to specify the Ninja executable manually so the CMake invocation
would become something like this:
```
`& "$Env:MSYS2_ROOT\mingw64\bin\cmake.exe" --preset win-x64-msvc-static -DCMAKE_MAKE_PROGRAM="$Env:MSYS2_ROOT\mingw64\bin\ninja.exe" .
```
To run the resulting binaries, you'll need to make sure the Qt libraries are in the search path, e.g. using
`$Env:PATH = "$Env:QT_ROOT\bin"`.
Note that you don't need to install all Visual Studio has to offer. A customized installation with just
C++ core features, MSVC x86/x64 build tools, Windows SDK and vpkg should be enough. In Qt's online installer
you can also uncheck everything except the MSVC build of Qt itself.
If the compilation of the resource file doesn't work you can use `-DWINDOWS_RC_FILE=OFF` to continue the
build regardless.
##### Remarks about special presets
The presets starting with `arch-` are for use under Arch Linux. Do *not* use them unless you know what you
are doing. When creating a normal build under Arch Linux it is recommended to still use e.g. `devel-qt6`.
Use the presets starting with `arch-*-w64-mingw32` to cross-compile for Windows using `mingw-w64` packages.
Use the presets starting with `arch-static-compat-devel` to create a self-contained executable that is also
usable under older GNU/Linux distributions using `static-compat` packages (see
[PKGBUILDs](https://github.com/Martchus/PKGBUILDs#static-gnulinux-libraries) for details about it).
### Packaging
The mentioned repositories contain packages for `c++utilities` itself but also for my other projects.
However, the README files of my other projects contain a more exhaustive list.
@ -321,6 +229,6 @@ Checkout [Case_Of's overlay](https://codeberg.org/Case_Of/gentoo-overlay)
or [perfect7gentleman's overlay](https://gitlab.com/Perfect_Gentleman/PG_Overlay).
## Copyright notice and license
Copyright © 2015-2024 Marius Kittler
Copyright © 2015-2023 Marius Kittler
All code is licensed under [GPL-2-or-later](LICENSE).

View File

@ -182,7 +182,7 @@ bool ArgumentReader::read(ArgumentVector &args)
Argument *lastArgInLevel = nullptr;
vector<const char *> *values = nullptr;
// iterate through all argument denotations; loop might exit earlier when a denotation is unknown
// iterate through all argument denotations; loop might exit earlier when an denotation is unknown
while (argv != end) {
// check whether there are still values to read
if (values && ((lastArgInLevel->requiredValueCount() != Argument::varValueCount) || (lastArgInLevel->flags() & Argument::Flags::Greedy))
@ -361,9 +361,6 @@ bool ArgumentReader::read(ArgumentVector &args)
++parser.m_actualArgc;
lastArg = lastArgInLevel = matchingArg;
argDenotation = nullptr;
if ((values->size() < matchingArg->requiredValueCount()) && (matchingArg->flags() & Argument::Flags::Greedy)) {
continue;
}
read(lastArg->m_subArgs);
argDenotation = nullptr;
continue;
@ -1323,25 +1320,6 @@ string ArgumentParser::findSuggestions(int argc, const char *const *argv, unsign
return suggestionStr;
}
/*!
* \brief Returns a copy of \a escaped with escaping characters removed.
*/
static std::string unescape(std::string_view escaped)
{
auto unescaped = std::string();
auto onEscaping = false;
unescaped.reserve(escaped.size());
for (const auto c : escaped) {
if (!onEscaping && c == '\\') {
onEscaping = true;
} else {
unescaped += c;
onEscaping = false;
}
}
return unescaped;
}
/*!
* \brief Prints the bash completion for the specified arguments and the specified \a lastPath.
* \remarks Arguments must have been parsed before with readSpecifiedArgs(). When calling this method, completionMode must
@ -1527,13 +1505,23 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
}
// -> completions for files and dirs
// -> if there's already an "opening", determine the dir part and the file part
auto actualDir = std::string(), actualFile = std::string();
auto haveFileOrDirCompletions = false;
string actualDir, actualFile;
bool haveFileOrDirCompletions = false;
if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && opening) {
// the "opening" might contain escaped characters which need to be unescaped first
const auto unescapedOpening = unescape(opening);
// the "opening" might contain escaped characters which need to be unescaped first (let's hope this covers all possible escapings)
string unescapedOpening(opening);
findAndReplace<string>(unescapedOpening, "\\ ", " ");
findAndReplace<string>(unescapedOpening, "\\,", ",");
findAndReplace<string>(unescapedOpening, "\\[", "[");
findAndReplace<string>(unescapedOpening, "\\]", "]");
findAndReplace<string>(unescapedOpening, "\\!", "!");
findAndReplace<string>(unescapedOpening, "\\#", "#");
findAndReplace<string>(unescapedOpening, "\\$", "$");
findAndReplace<string>(unescapedOpening, "\\'", "'");
findAndReplace<string>(unescapedOpening, "\\\"", "\"");
findAndReplace<string>(unescapedOpening, "\\\\", "\\");
// determine the "directory" part
auto dir = directory(unescapedOpening);
string dir = directory(unescapedOpening);
if (dir.empty()) {
actualDir = ".";
} else {
@ -1546,7 +1534,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
actualDir = std::move(dir);
}
// determine the "file" part
auto file = fileName(unescapedOpening);
string file = fileName(unescapedOpening);
if (file[0] == '\"' || file[0] == '\'') {
file.erase(0, 1);
}
@ -1799,7 +1787,7 @@ void ValueConversion::Helper::ArgumentValueConversionError::throwFailure(const s
throw ParseError(argumentPath.empty()
? argsToString("Conversion of top-level value \"", valueToConvert, "\" to type \"", targetTypeName, "\" failed: ", errorMessage)
: argsToString("Conversion of value \"", valueToConvert, "\" (for argument --", argumentPath.back()->name(), ") to type \"",
targetTypeName, "\" failed: ", errorMessage));
targetTypeName, "\" failed: ", errorMessage));
}
/*!
@ -1810,7 +1798,7 @@ void ArgumentOccurrence::throwNumberOfValuesNotSufficient(unsigned long valuesTo
throw ParseError(path.empty()
? argsToString("Expected ", valuesToConvert, " top-level values to be present but only ", values.size(), " have been specified.")
: argsToString("Expected ", valuesToConvert, " values for argument --", path.back()->name(), " to be present but only ", values.size(),
" have been specified."));
" have been specified."));
}
} // namespace CppUtilities

View File

@ -881,7 +881,7 @@ inline void Argument::setFlags(Argument::Flags flags, bool add)
{
m_flags = add ? (m_flags | flags)
: static_cast<Argument::Flags>(static_cast<std::underlying_type<Argument::Flags>::type>(m_flags)
& ~static_cast<std::underlying_type<Argument::Flags>::type>(flags));
& ~static_cast<std::underlying_type<Argument::Flags>::type>(flags));
}
/*!

View File

@ -10,7 +10,6 @@
#ifdef PLATFORM_WINDOWS
#include <cstring>
#include <io.h>
#include <tchar.h>
#include <windows.h>
#else
#include <sys/ioctl.h>
@ -91,19 +90,6 @@ TerminalSize determineTerminalSize()
}
#ifdef PLATFORM_WINDOWS
/*!
* \brief Returns whether Mintty is used.
*/
static bool isMintty()
{
static const auto mintty = [] {
const char *const msyscon = std::getenv("MSYSCON");
const char *const termprog = std::getenv("TERM_PROGRAM");
return (msyscon && std::strstr(msyscon, "mintty")) || (termprog && std::strstr(termprog, "mintty"));
}();
return mintty;
}
/*!
* \brief Enables virtual terminal processing (and thus processing of ANSI escape codes) of the console
* determined by the specified \a nStdHandle.
@ -133,11 +119,13 @@ bool handleVirtualTerminalProcessing()
if (enableVirtualTerminalProcessing(STD_OUTPUT_HANDLE) && enableVirtualTerminalProcessing(STD_ERROR_HANDLE)) {
return true;
}
// disable use of ANSI escape codes otherwise if it makes sense
if (isMintty()) {
// disable use on ANSI escape codes otherwise if it makes sense
const char *const msyscon = std::getenv("MSYSCON");
if (msyscon && std::strstr(msyscon, "mintty")) {
return false; // no need to disable escape codes if it is just mintty
}
if (const char *const term = std::getenv("TERM"); term && std::strstr(term, "xterm")) {
const char *const term = std::getenv("TERM");
if (term && std::strstr(term, "xterm")) {
return false; // no need to disable escape codes if it is some xterm-like terminal
}
return EscapeCodes::enabled = false;
@ -153,26 +141,20 @@ void stopConsole()
fclose(stdin);
fclose(stderr);
if (auto *const consoleWindow = GetConsoleWindow()) {
PostMessage(consoleWindow, WM_KEYUP, VK_RETURN, 0);
FreeConsole();
}
}
/*!
* \brief Ensure the process has a console attached and properly setup.
* \brief Ensure the process has a console attached and sets its output code page to UTF-8.
* \remarks
* - Only available (and required) under Windows where otherwise standard I/O is not possible via the console (unless
* when using Mintty).
* - Attaching a console breaks redirections/pipes so this needs to be opted-in by setting the environment variable
* `ENABLE_CONSOLE=1`.
* - Note that this is only useful to start a console from a GUI application. It is not necassary to call this function
* from a console application.
* - The console is automatically closed when the application exits.
* - This function alone does not provide good results. It still breaks redirections in PowerShell and other shells and
* after the application exists the command prompt is not displayed. A CLI-wrapper is required for proper behavior. The
* build system automatically generates one when the CMake variable BUILD_CLI_WRAPPER is set. Note that this CLI-wrapper
* still relies on this function (and thus sets `ENABLE_CONSOLE=1`). Without this standard I/O would still not be
* possible via the console. The part for skipping in case there's a redirection is still required. Otherwise
* redirections/pipes are broken when using the CLI-wrapper as well.
* - Only available (and required) under Windows where otherwise stdout/stderr is not printed to the console (at
* least when using `cmd.exe`).
* - Used to start a console from a GUI application. Does *not* create a new console if the process already has one.
* - Closes the console automatically when the application exits.
* - It breaks redirecting stdout/stderr so this can be opted-out by setting the environment
* variable `ENABLE_CONSOLE=0` and/or `ENABLE_CP_UTF8=0`.
* \sa
* - https://docs.microsoft.com/en-us/windows/console/AttachConsole
* - https://docs.microsoft.com/en-us/windows/console/AllocConsole
@ -181,101 +163,42 @@ void stopConsole()
*/
void startConsole()
{
// skip if ENABLE_CONSOLE is set to 0 or not set at all
if (const auto e = isEnvVariableSet("ENABLE_CONSOLE"); !e.has_value() || !e.value()) {
return;
}
// check whether there's a redirection; skip messing with any streams then to not break redirections/pipes
auto pos = std::fpos_t();
std::fgetpos(stdout, &pos);
const auto skipstdout = pos >= 0;
std::fgetpos(stderr, &pos);
const auto skipstderr = pos >= 0;
std::fgetpos(stdin, &pos);
const auto skipstdin = pos >= 0;
const auto skip = skipstdout || skipstderr || skipstdin;
// attach to the parent process' console or allocate a new console if that's not possible
if (!skip && (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())) {
FILE *fp;
#ifdef _MSC_VER
// take care of normal streams
if (!skipstdout) {
freopen_s(&fp, "CONOUT$", "w", stdout);
std::cout.clear();
std::clog.clear();
}
if (!skipstderr) {
freopen_s(&fp, "CONOUT$", "w", stderr);
std::cerr.clear();
}
if (!skipstdin) {
freopen_s(&fp, "CONIN$", "r", stdin);
std::cin.clear();
}
// take care of wide streams
auto hConOut = CreateFile(
_T("CONOUT$"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
auto hConIn = CreateFile(
_T("CONIN$"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (!skipstdout) {
SetStdHandle(STD_OUTPUT_HANDLE, hConOut);
std::wcout.clear();
std::wclog.clear();
}
if (!skipstderr) {
SetStdHandle(STD_ERROR_HANDLE, hConOut);
std::wcerr.clear();
}
if (!skipstdin) {
SetStdHandle(STD_INPUT_HANDLE, hConIn);
std::wcin.clear();
}
#else
const auto consoleEnabled = isEnvVariableSet("ENABLE_CONSOLE");
if ((!consoleEnabled.has_value() || consoleEnabled.value()) && (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())) {
// redirect stdout
auto stdHandle = std::intptr_t();
auto conHandle = int();
if (!skipstdout) {
stdHandle = reinterpret_cast<intptr_t>(GetStdHandle(STD_OUTPUT_HANDLE));
conHandle = _open_osfhandle(stdHandle, _O_TEXT);
fp = _fdopen(conHandle, "w");
*stdout = *fp;
setvbuf(stdout, nullptr, _IONBF, 0);
}
auto stdHandle = reinterpret_cast<intptr_t>(GetStdHandle(STD_OUTPUT_HANDLE));
auto conHandle = _open_osfhandle(stdHandle, _O_TEXT);
auto fp = _fdopen(conHandle, "w");
*stdout = *fp;
setvbuf(stdout, nullptr, _IONBF, 0);
// redirect stdin
if (!skipstdin) {
stdHandle = reinterpret_cast<intptr_t>(GetStdHandle(STD_INPUT_HANDLE));
conHandle = _open_osfhandle(stdHandle, _O_TEXT);
fp = _fdopen(conHandle, "r");
*stdin = *fp;
setvbuf(stdin, nullptr, _IONBF, 0);
}
stdHandle = reinterpret_cast<intptr_t>(GetStdHandle(STD_INPUT_HANDLE));
conHandle = _open_osfhandle(stdHandle, _O_TEXT);
fp = _fdopen(conHandle, "r");
*stdin = *fp;
setvbuf(stdin, nullptr, _IONBF, 0);
// redirect stderr
if (!skipstderr) {
stdHandle = reinterpret_cast<intptr_t>(GetStdHandle(STD_ERROR_HANDLE));
conHandle = _open_osfhandle(stdHandle, _O_TEXT);
fp = _fdopen(conHandle, "w");
*stderr = *fp;
setvbuf(stderr, nullptr, _IONBF, 0);
}
stdHandle = reinterpret_cast<intptr_t>(GetStdHandle(STD_ERROR_HANDLE));
conHandle = _open_osfhandle(stdHandle, _O_TEXT);
fp = _fdopen(conHandle, "w");
*stderr = *fp;
setvbuf(stderr, nullptr, _IONBF, 0);
// sync
ios::sync_with_stdio(true);
#endif
// ensure the console prompt is shown again when app terminates
std::atexit(stopConsole);
atexit(stopConsole);
}
// set console character set to UTF-8
if (const auto e = isEnvVariableSet("ENABLE_CP_UTF8"); !e.has_value() || e.value()) {
const auto utf8Enabled = isEnvVariableSet("ENABLE_CP_UTF8");
if (!utf8Enabled.has_value() || utf8Enabled.value()) {
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
}
// enable virtual terminal processing or disable ANSI-escape if that's not possible
if (const auto e = isEnvVariableSet("ENABLE_HANDLING_VIRTUAL_TERMINAL_PROCESSING"); !e.has_value() || e.value()) {
handleVirtualTerminalProcessing();
}
handleVirtualTerminalProcessing();
}
/*!
@ -307,7 +230,7 @@ pair<vector<unique_ptr<char[]>>, vector<char *>> convertArgsToUtf8()
}
res.second.emplace_back(argv.get());
res.first.emplace_back(std::move(argv));
res.first.emplace_back(move(argv));
}
LocalFree(argv_w);

View File

@ -14,7 +14,7 @@ namespace CppUtilities {
*/
FakeQtConfigArguments::FakeQtConfigArguments()
: m_qtWidgetsGuiArg(
"qt-widgets-gui", 'g', "shows a Qt widgets based graphical user interface (the application has not been built with Qt widgets support)")
"qt-widgets-gui", 'g', "shows a Qt widgets based graphical user interface (the application has not been built with Qt widgets support)")
, m_qtQuickGuiArg(
"qt-quick-gui", 'q', "shows a Qt quick based graphical user interface (the application has not been built with Qt quick support)")
{

View File

@ -47,6 +47,8 @@ template <typename num1, typename num2, typename num3> constexpr bool inRangeExc
* the time zone deltas are "baked into" the DateTime instance. For instance, the expression (DateTime::now() - DateTime::gmtNow())
* returns one hour in Germany during winter time (and *not* zero although both instances represent the current time).
* \todo
* - Add method for parsing custom string formats.
* - Add method for printing to custom string formats.
* - Allow to determine the date part for each component at once to prevent multiple
* invocations of getDatePart().
*/

View File

@ -2,52 +2,17 @@
#include "./timespan.h"
#include "../conversion/stringbuilder.h"
#include "../conversion/stringconversion.h"
#include <array>
#include <charconv>
#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <sstream>
#include <vector>
using namespace std;
namespace CppUtilities {
/// \cond
#if defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 10
enum class chars_format { scientific = 1, fixed = 2, hex = 4, general = fixed | scientific };
#else
using char_format = std::chars_format;
#endif
inline std::from_chars_result from_chars(const char *first, const char *last, double &value, chars_format fmt = chars_format::general) noexcept
{
#if defined(_LIBCPP_VERSION) || (defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 11)
// workaround std::from_chars() not being implemented for floating point numbers in libc++ and older libstdc++ versions
CPP_UTILITIES_UNUSED(fmt)
auto r = std::from_chars_result{ nullptr, std::errc() };
auto s = std::string(first, last);
auto l = s.data() + s.size();
auto d = std::strtod(s.data(), &l);
if (errno == ERANGE) {
r.ec = std::errc::result_out_of_range;
} else if (s.data() == l) {
r.ec = std::errc::invalid_argument;
} else {
value = d;
r.ptr = first + (l - s.data());
}
return r;
#else
return std::from_chars(first, last, value, fmt);
#endif
}
/// \endcond
/*!
* \class TimeSpan
* \brief Represents a time interval.
@ -57,19 +22,18 @@ inline std::from_chars_result from_chars(const char *first, const char *last, do
* and month. For that use case, use the Period class instead.
*
* \remarks Time values are measured in 100-nanosecond units called ticks.
* \todo
* - Add method for parsing custom string formats.
* - Add method for printing to custom string formats.
*/
/*!
* \brief Parses the given C-style string as TimeSpan.
* \throws Throws a ConversionException if the specified \a str does not match the expected format.
*
* The expected format is "days:hours:minutes:seconds", e.g. "5:31:4.521" for 5 hours, 31 minutes
* The expected format is "days:hours:minutes:seconds", eg. "5:31:4.521" for 5 hours, 31 minutes
* and 4.521 seconds. So parts at the front can be omitted and the parts can be fractions. The
* colon can be changed by specifying another \a separator. White-spaces before and after parts
* are ignored.
*
* It is also possible to specify one or more values with a unit, e.g. "2w 1d 5h 1m 0.5s".
* The units "w" (weeks), "d" (days), "h" (hours), "m" (minutes) and "s" (seconds) are supported.
* colon can be changed by specifying another \a separator.
*/
TimeSpan TimeSpan::fromString(const char *str, char separator)
{
@ -77,100 +41,34 @@ TimeSpan TimeSpan::fromString(const char *str, char separator)
return TimeSpan();
}
auto parts = std::array<double, 4>();
auto partsPresent = std::size_t();
auto specificationsWithUnits = TimeSpan();
vector<double> parts;
size_t partsSize = 1;
for (const char *i = str; *i; ++i) {
*i == separator && ++partsSize;
}
parts.reserve(partsSize);
for (const char *i = str;; ++i) {
// skip over white-spaces
if (*i == ' ' && i == str) {
str = i + 1;
continue;
}
// consider non-separator and non-terminator characters as part to be interpreted as number
if (*i != separator && *i != '\0') {
continue;
}
// allow only up to 4 parts (days, hours, minutes and seconds)
if (partsPresent == 4) {
throw ConversionException("too many separators/parts");
}
// parse value of the part
auto valuePart = 0.0;
auto valueWithUnit = TimeSpan();
if (str != i) {
// parse value of the part as double
const auto res = from_chars(str, i, valuePart);
if (res.ec != std::errc()) {
const auto part = std::string_view(str, static_cast<std::string_view::size_type>(i - str));
if (res.ec == std::errc::result_out_of_range) {
throw ConversionException(argsToString("part \"", part, "\" is too large"));
} else {
throw ConversionException(argsToString("part \"", part, "\" cannot be interpreted as floating point number"));
}
}
// handle remaining characters; detect a possibly present unit suffix
for (const char *suffix = res.ptr; suffix != i; ++suffix) {
if (*suffix == ' ') {
continue;
}
if (valueWithUnit.isNull()) {
switch (*suffix) {
case 'w':
valueWithUnit = TimeSpan::fromDays(7.0 * valuePart);
continue;
case 'd':
valueWithUnit = TimeSpan::fromDays(valuePart);
continue;
case 'h':
valueWithUnit = TimeSpan::fromHours(valuePart);
continue;
case 'm':
valueWithUnit = TimeSpan::fromMinutes(valuePart);
continue;
case 's':
valueWithUnit = TimeSpan::fromSeconds(valuePart);
continue;
default:;
}
}
if (*suffix >= '0' && *suffix <= '9') {
str = i = suffix;
break;
}
throw ConversionException(argsToString("unexpected character \"", *suffix, '\"'));
}
}
// set part value; add value with unit
if (valueWithUnit.isNull()) {
parts[partsPresent++] = valuePart;
} else {
specificationsWithUnits += valueWithUnit;
}
// expect next part starting after the separator or stop if terminator reached
for (const char *i = str;;) {
if (*i == separator) {
str = i + 1;
parts.emplace_back(stringToNumber<double>(string(str, i)));
str = ++i;
} else if (*i == '\0') {
parts.emplace_back(stringToNumber<double>(string(str, i)));
break;
} else {
++i;
}
}
// compute and return total value from specifications with units and parts
switch (partsPresent) {
switch (parts.size()) {
case 1:
return specificationsWithUnits + TimeSpan::fromSeconds(parts.front());
return TimeSpan::fromSeconds(parts.front());
case 2:
return specificationsWithUnits + TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
return TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
case 3:
return specificationsWithUnits + TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
return TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
default:
return specificationsWithUnits + TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2])
+ TimeSpan::fromSeconds(parts[3]);
return TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2]) + TimeSpan::fromSeconds(parts[3]);
}
}

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED THIRD_PARTY_MODULE_LOADED)
@ -157,10 +157,6 @@ macro (_cpp_utilities_use_openssl OPENSSL_TARGETS)
message(STATUS "Unable to find OpenSSL")
return()
endif ()
set(OPENSSL_IS_STATIC FALSE)
if (OPENSSL_CRYPTO_LIBRARY MATCHES ".*\\.a" OR OPENSSL_SSL_LIBRARY MATCHES ".*\\.a")
set(OPENSSL_IS_STATIC TRUE)
endif ()
foreach (OPENSSL_TARGET ${OPENSSL_TARGETS})
if (TARGET "${OPENSSL_TARGET}")
continue()
@ -178,14 +174,9 @@ macro (_cpp_utilities_use_openssl OPENSSL_TARGETS)
message(STATUS "Found required OpenSSL targets (${OPENSSL_TARGETS})")
set("${ARGS_LIBRARIES_VARIABLE}" "${${ARGS_LIBRARIES_VARIABLE}};${OPENSSL_TARGETS}")
# add transitive dependencies for static OpenSSL libs as the CMake find module does not do a good job
if (OPENSSL_USE_STATIC_LIBS OR OPENSSL_IS_STATIC)
if (WIN32)
set("${ARGS_LIBRARIES_VARIABLE}" "${${ARGS_LIBRARIES_VARIABLE}};-lws2_32;-lgdi32;-lcrypt32")
elseif (LINUX)
set("${ARGS_LIBRARIES_VARIABLE}" "${${ARGS_LIBRARIES_VARIABLE}};-ldl")
endif ()
if (WIN32 AND OPENSSL_USE_STATIC_LIBS)
# FIXME: preferably use pkg-config to cover this case without hardcoding OpenSSL's dependencies under Windows
set("${ARGS_LIBRARIES_VARIABLE}" "${${ARGS_LIBRARIES_VARIABLE}};-lws2_32;-lgdi32;-lcrypt32")
endif ()
set("${ARGS_PACKAGES_VARIABLE}"
"${${ARGS_PACKAGES_VARIABLE}};OpenSSL"

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
if (NOT BASIC_PROJECT_CONFIG_DONE)
message(FATAL_ERROR "Before including the AppTarget module, the BasicConfig module must be included.")
@ -111,17 +111,35 @@ if (GUI_TYPE STREQUAL "MACOSX_BUNDLE")
endif ()
# create CLI-wrapper to be able to use CLI in Windows-termial without hacks
if (GUI_TYPE STREQUAL "WIN32")
option(BUILD_CLI_WRAPPER "whether to build a CLI wrapper" ON)
endif ()
if (BUILD_CLI_WRAPPER)
set(CLI_WRAPPER_TARGET_NAME "${META_TARGET_NAME}-cli")
# add Boost::boost target which represents include directory for header-only deps and add Boost::filesystem as it is
# needed by Boost.Process
set(BOOST_ARGS 1.75 REQUIRED COMPONENTS filesystem)
set(USE_PACKAGE_ARGS
LIBRARIES_VARIABLE CLI_WRAPPER_TARGET_NAME_LIBS
PACKAGES_VARIABLE CLI_WRAPPER_TARGET_NAME_PKGS)
use_package(TARGET_NAME Boost::boost PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}" ${USE_PACKAGE_ARGS})
use_package(TARGET_NAME Boost::filesystem PACKAGE_NAME Boost PACKAGE_ARGS "${BOOST_ARGS}" ${USE_PACKAGE_ARGS})
# find source file
include(TemplateFinder)
find_template_file_full_name("cli-wrapper.cpp" CPP_UTILITIES CLI_WRAPPER_SRC_FILE)
# add and configure additional executable
set(CLI_WRAPPER_TARGET_NAME "${META_TARGET_NAME}-cli")
add_executable(${CLI_WRAPPER_TARGET_NAME} ${CLI_WRAPPER_RES_FILES} ${CLI_WRAPPER_SRC_FILE})
set_target_properties(${CLI_WRAPPER_TARGET_NAME} PROPERTIES CXX_STANDARD 17)
target_link_libraries(${CLI_WRAPPER_TARGET_NAME} PRIVATE ${STATIC_LINKAGE_LINKER_FLAGS})
target_compile_definitions(${CLI_WRAPPER_TARGET_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS=1)
target_link_libraries(${CLI_WRAPPER_TARGET_NAME} PRIVATE "${CLI_WRAPPER_TARGET_NAME_LIBS}")
if (MINGW)
# workaround https://github.com/boostorg/process/issues/96
target_compile_definitions(${CLI_WRAPPER_TARGET_NAME} PRIVATE BOOST_USE_WINDOWS_H WIN32_LEAN_AND_MEAN)
elseif (MSVC)
# prevent "Please define _WIN32_WINNT or _WIN32_WINDOWS appropriately."
target_compile_definitions(${CLI_WRAPPER_TARGET_NAME} PRIVATE _WIN32_WINNT=0x0601)
endif ()
endif ()
# add install targets

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED APPLICATION_UTILITIES_LOADED)
@ -21,9 +21,16 @@ function (add_custom_desktop_file)
endif ()
# parse arguments
set(ONE_VALUE_ARGS FILE_NAME DESKTOP_FILE_APP_NAME DESKTOP_FILE_GENERIC_NAME DESKTOP_FILE_DESCRIPTION DESKTOP_FILE_CMD
DESKTOP_FILE_ICON)
set(MULTI_VALUE_ARGS DESKTOP_FILE_CATEGORIES DESKTOP_FILE_ADDITIONAL_ENTRIES)
set(ONE_VALUE_ARGS
FILE_NAME
DESKTOP_FILE_APP_NAME
DESKTOP_FILE_GENERIC_NAME
DESKTOP_FILE_DESCRIPTION
DESKTOP_FILE_CATEGORIES
DESKTOP_FILE_CMD
DESKTOP_FILE_ICON
DESKTOP_FILE_ADDITIONAL_ENTRIES)
set(MULTI_VALUE_ARGS)
set(OPTIONAL_ARGS)
cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
if (NOT ARGS_FILE_NAME
@ -63,7 +70,7 @@ function (add_appstream_file)
endif ()
# create appstream desktop file from template
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.metainfo.xml")
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.appdata.xml")
configure_file("${APP_APPSTREAM_TEMPLATE_FILE}" "${APPSTREAM_FILE}" @ONLY)
# add install for the appstream file
@ -73,19 +80,11 @@ function (add_appstream_file)
COMPONENT appimage)
# add test
set(APPSTREAM_TESTS_ENABLED_DEFAULT OFF)
find_program(APPSTREAMCLI_BIN "appstreamcli")
if (ENABLE_DEVEL_DEFAULTS AND APPSTREAMCLI_BIN)
set(APPSTREAM_TESTS_ENABLED_DEFAULT ON)
endif ()
option(APPSTREAM_TESTS_ENABLED "enables tests for checking whether AppStream files" "${APPSTREAM_TESTS_ENABLED_DEFAULT}")
if (APPSTREAM_TESTS_ENABLED)
if (NOT APPSTREAMCLI_BIN)
message(FATAL_ERROR "Unable to validate appstreamcli files; appstreamcli not found")
else ()
add_test(NAME "${META_TARGET_NAME}_appstream_validation" COMMAND "${APPSTREAMCLI_BIN}" validate
"${APPSTREAM_FILE}")
endif ()
if (NOT APPSTREAMCLI_BIN)
message(STATUS "Could not find appstreamcli; won't add test/target to validate appstream files")
else ()
add_test(NAME "${META_TARGET_NAME}_appstream_validation" COMMAND "${APPSTREAMCLI_BIN}" validate "${APPSTREAM_FILE}")
endif ()
endfunction ()
@ -98,6 +97,7 @@ function (add_desktop_file)
endif ()
# compose actions
set(DESKTOP_FILE_ADDITIONAL_ENTRIES "")
foreach (ACTION_VAR ${META_APP_ACTIONS})
list(GET META_APP_ACTION_${ACTION_VAR} 0 ACTION_ID)
list(GET META_APP_ACTION_${ACTION_VAR} 1 ACTION_NAME)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# check whether the required project meta-data has been set before including this module
if (NOT META_PROJECT_NAME)
@ -191,39 +191,18 @@ if (NOT META_PROJECT_LICENSE)
endif ()
endif ()
# determine RDNS automatically from other meta-data and allow override
set(${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE
""
CACHE STRING "overrides the RDNS used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE)
set(META_PROJECT_RDNS ${${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE})
endif ()
set(${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE
""
CACHE STRING "overrides the developer ID used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
set(META_DEVELOPER_ID ${${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE})
endif ()
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE OR ${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
message(
WARNING
"Overriding the RDNS or developer ID is NOT recommended. This feature is only intended to ease "
"transitioning when a change is required and to create alternative packaging for development and private use.")
endif ()
if (NOT META_PROJECT_RDNS OR NOT META_DEVELOPER_ID)
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
# determine RDNS automatically from other meta-data
if (NOT META_PROJECT_RDNS)
if (NOT META_PROJECT_RDNS_BASE)
if (META_APP_URL MATCHES ".*github\\.(com|io).*")
if (META_APP_URL MATCHES ".*github\\.com.*")
set(META_PROJECT_RDNS_BASE "io.github") # assume GitHub pages
else ()
set(META_PROJECT_RDNS_BASE "org")
endif ()
endif ()
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
set(META_PROJECT_RDNS "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}.${META_PROJECT_NAME}${TARGET_SUFFIX}")
endif ()
if (NOT META_DEVELOPER_ID)
set(META_DEVELOPER_ID "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}")
endif ()
# provide variables for other projects built as part of the same subdirs project to access files from this project
get_directory_property(HAS_PARENT PARENT_DIRECTORY)
@ -281,9 +260,9 @@ endif ()
option(FORCE_OLD_ABI "specifies whether usage of libstdc++'s old ABI should be forced" OFF)
if (FORCE_OLD_ABI)
list(APPEND META_PRIVATE_COMPILE_DEFINITIONS _GLIBCXX_USE_CXX11_ABI=0)
message(STATUS "Forcing usage of old CXX11-ABI of libstdc++ (has no effect when a different standard library is used).")
message(STATUS "Forcing usage of old CXX11 ABI of libstdc++.")
else ()
message(STATUS "Using default CXX11-ABI (not forcing old CXX11-ABI of libstdc++).")
message(STATUS "Using default CXX11 ABI of libstdc++ (not forcing old CX11 ABI).")
endif ()
# enable debug-only code when doing a debug build
@ -414,24 +393,8 @@ endif ()
# allow user to configure creation of tidy targets unless the project disables this via META_NO_TIDY
if (NOT META_NO_TIDY)
find_program(CLANG_FORMAT_BIN clang-format)
find_program(CMAKE_FORMAT_BIN cmake-format)
set(CLANG_FORMAT_ENABLED_DEFAULT OFF)
set(CMAKE_FORMAT_ENABLED_DEFAULT OFF)
if (CLANG_FORMAT_BIN)
set(CLANG_FORMAT_ENABLED_DEFAULT ON)
endif ()
if (CMAKE_FORMAT_BIN)
set(CMAKE_FORMAT_ENABLED_DEFAULT ON)
endif ()
set(TIDY_TESTS_ENABLED_DEFAULT OFF)
if (ENABLE_DEVEL_DEFAULTS AND CLANG_FORMAT_ENABLED_DEFAULT)
set(TIDY_TESTS_ENABLED_DEFAULT ON)
endif ()
option(CLANG_FORMAT_ENABLED "enables creation of tidy target using clang-format" "${CLANG_FORMAT_ENABLED_DEFAULT}")
option(CMAKE_FORMAT_ENABLED "enables creation of tidy target using cmake-format" "${CMAKE_FORMAT_ENABLED_DEFAULT}")
option(TIDY_TESTS_ENABLED "enables tests for checking whether code is well-formatted using clang-format"
"${TIDY_TESTS_ENABLED_DEFAULT}")
option(CLANG_FORMAT_ENABLED "enables creation of tidy target using clang-format" "${ENABLE_DEVEL_DEFAULTS}")
option(CMAKE_FORMAT_ENABLED "enables creation of tidy target using cmake-format" "${ENABLE_DEVEL_DEFAULTS}")
endif ()
# add target for tidying with clang-format
@ -439,6 +402,7 @@ if (NOT META_NO_TIDY
AND CLANG_FORMAT_ENABLED
AND FORMATABLE_FILES
AND EXISTS "${CLANG_FORMAT_RULES}")
find_program(CLANG_FORMAT_BIN clang-format)
if (NOT CLANG_FORMAT_BIN)
message(FATAL_ERROR "Unable to add tidy target; clang-format not found")
endif ()
@ -454,22 +418,21 @@ if (NOT META_NO_TIDY
add_dependencies(tidy "${META_TARGET_NAME}_tidy")
# also add a test to verify whether sources are tidy
if (TIDY_TESTS_ENABLED)
add_test(
NAME "${META_TARGET_NAME}_tidy_test"
COMMAND "${CLANG_FORMAT_BIN}" -output-replacements-xml -style=file ${FORMATABLE_FILES}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
list(APPEND CHECK_TARGET_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
set_tests_properties(
"${META_TARGET_NAME}_tidy_test" PROPERTIES FAIL_REGULAR_EXPRESSION "<replacement.*>.*</replacement>"
REQUIRED_FILES "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
endif ()
add_test(
NAME "${META_TARGET_NAME}_tidy_test"
COMMAND "${CLANG_FORMAT_BIN}" -output-replacements-xml -style=file ${FORMATABLE_FILES}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
list(APPEND CHECK_TARGET_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
set_tests_properties(
"${META_TARGET_NAME}_tidy_test" PROPERTIES FAIL_REGULAR_EXPRESSION "<replacement.*>.*</replacement>" REQUIRED_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/.clang-format")
endif ()
# add target for tidying with cmake-format
if (NOT META_NO_TIDY
AND CMAKE_FORMAT_ENABLED
AND FORMATABLE_FILES_CMAKE)
find_program(CMAKE_FORMAT_BIN cmake-format)
if (NOT CMAKE_FORMAT_BIN)
message(FATAL_ERROR "Unable to add tidy target; cmake-format not found")
endif ()
@ -634,7 +597,7 @@ if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
endif ()
# determine library directory suffix - Applications might be built as libraries under some platforms (eg. Android). Hence
# this is part of BasicConfig and not LibraryTarget.
# this is part of BasicConfig and not LibraryConfig.
set(LIB_SUFFIX
""
CACHE STRING "specifies the general suffix for the library directory")
@ -727,10 +690,6 @@ else ()
endforeach ()
endif ()
# configure warnings
configure_development_warnings(APPEND_OUTPUT_VAR META_PRIVATE_COMPILE_OPTIONS)
if (MSVC)
list(APPEND META_PRIVATE_COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS=1)
endif ()
set(BASIC_PROJECT_CONFIG_DONE YES)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# before including this module, all relevant variables must be set just include this module as last one since nothing should
# depend on it

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED DEVEL_UTILITIES_LOADED)
@ -59,7 +59,7 @@ function (configure_development_warnings)
option(TREAT_WARNINGS_AS_ERRORS "adds additional compiler flag to treat warnings as errors" "${ENABLE_DEVEL_DEFAULTS}")
if (TREAT_WARNINGS_AS_ERRORS)
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror -Wno-error=address)
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror)
else ()
message(AUTHOR_WARNING "Treating warnings as errors is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
endif ()

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
if (NOT BASIC_PROJECT_CONFIG_DONE)
message(FATAL_ERROR "Before including the Doxygen module, the BasicConfig module must be included.")

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
if (NOT BASIC_PROJECT_CONFIG_DONE)
message(FATAL_ERROR "Before including the LibraryTarget module, the BasicConfig module must be included.")
@ -53,7 +53,7 @@ endif ()
# add global library-specific header
find_template_file("global.h" CPP_UTILITIES GLOBAL_H_TEMPLATE_FILE)
if ("${META_PROJECT_NAME}" STREQUAL "c++utilities")
set(GENERAL_GLOBAL_H_INCLUDE_PATH "\"application/global.h\"")
set(GENERAL_GLOBAL_H_INCLUDE_PATH "\"./application/global.h\"")
else ()
set(GENERAL_GLOBAL_H_INCLUDE_PATH "<c++utilities/application/global.h>")
endif ()
@ -121,37 +121,14 @@ endif ()
# add custom libraries
append_user_defined_additional_libraries()
# allow writing public compile definitions to a header file instead of just relying on CMake/pkg-config
option(USE_HEADER_FOR_PUBLIC_COMPILE_DEFINITIONS "writes public compile definitions to a header file" ON)
set(DEFS_FOR_HEADER "")
if (USE_HEADER_FOR_PUBLIC_COMPILE_DEFINITIONS)
foreach (DEF ${META_PUBLIC_COMPILE_DEFINITIONS})
if (DEF MATCHES "([A-Za-z0-9_]+)=([A-Za-z0-9_ ]+)")
set(DEF_NAME "${CMAKE_MATCH_1}")
set(DEF_VALUE " ${CMAKE_MATCH_2}")
elseif (DEF MATCHES "([A-Za-z0-9_]+)")
set(DEF_NAME "${CMAKE_MATCH_1}")
set(DEF_VALUE "")
endif ()
if (DEF_NAME)
set(DEFS_FOR_HEADER "${DEFS_FOR_HEADER}#ifndef ${DEF_NAME}\n#define ${DEF_NAME}${DEF_VALUE}\n#endif\n")
endif ()
endforeach ()
endif ()
set(TARGET_GENERATED_INCLUDE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include")
set(TARGET_DEFINITIONS_HEADER "${TARGET_GENERATED_INCLUDE_DIRECTORY}/${META_PROJECT_NAME}-definitions.h")
file(WRITE "${TARGET_DEFINITIONS_HEADER}" "${DEFS_FOR_HEADER}")
# add library to be created, set libs to link against, set version and C++ standard
if (META_HEADER_ONLY_LIB)
add_library(${META_TARGET_NAME} INTERFACE)
target_link_libraries(${META_TARGET_NAME} INTERFACE ${META_ADDITIONAL_LINK_FLAGS} "${PUBLIC_LIBRARIES}"
"${PRIVATE_LIBRARIES}")
target_include_directories(
${META_TARGET_NAME}
INTERFACE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}>
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS})
${META_TARGET_NAME} INTERFACE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS})
target_compile_definitions(${META_TARGET_NAME} INTERFACE "${META_PUBLIC_COMPILE_DEFINITIONS}"
"${META_PRIVATE_COMPILE_DEFINITIONS}")
target_compile_options(${META_TARGET_NAME} INTERFACE "${META_PUBLIC_COMPILE_OPTIONS}" "${META_PRIVATE_COMPILE_OPTIONS}")
@ -162,14 +139,12 @@ else ()
PUBLIC ${META_ADDITIONAL_LINK_FLAGS} "${PUBLIC_LIBRARIES}"
PRIVATE "${PRIVATE_LIBRARIES}")
if (META_IS_PLUGIN)
target_include_directories(
${META_TARGET_NAME} PRIVATE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}> "${PRIVATE_INCLUDE_DIRS}")
target_include_directories(${META_TARGET_NAME} PRIVATE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
"${PRIVATE_INCLUDE_DIRS}")
else ()
target_include_directories(
${META_TARGET_NAME}
PUBLIC $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}>
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS}
PRIVATE "${PRIVATE_INCLUDE_DIRS}")
endif ()
@ -220,10 +195,8 @@ else ()
if (NOT META_PLUGIN_CATEGORY)
add_library(${META_TARGET_NAME}-headers INTERFACE)
target_include_directories(
${META_TARGET_NAME}-headers
INTERFACE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}>
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS})
${META_TARGET_NAME}-headers INTERFACE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS})
target_compile_definitions(${META_TARGET_NAME}-headers INTERFACE "${META_PUBLIC_COMPILE_DEFINITIONS}"
"${META_PRIVATE_COMPILE_DEFINITIONS}")
target_compile_options(${META_TARGET_NAME}-headers INTERFACE "${META_PUBLIC_COMPILE_OPTIONS}"
@ -557,12 +530,9 @@ if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
COMPONENT cmake-config)
# allow checking for the export in subsequent sibling projects/directories
get_directory_property(HAS_PARENT_DIRECTORY PARENT_DIRECTORY)
if (HAS_PARENT_DIRECTORY)
set("EXPORT_${NAMESPACE_PREFIX}${META_PROJECT_NAME}${META_CONFIG_SUFFIX}"
ON
PARENT_SCOPE)
endif ()
set("EXPORT_${NAMESPACE_PREFIX}${META_PROJECT_NAME}${META_CONFIG_SUFFIX}"
ON
PARENT_SCOPE)
# add install target for header files
if (NOT META_IS_PLUGIN)
@ -584,10 +554,6 @@ if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
FILES "${VERSION_HEADER_FILE}"
DESTINATION "${INCLUDE_SUBDIR}/${META_PROJECT_NAME}"
COMPONENT header)
install(
FILES "${TARGET_DEFINITIONS_HEADER}"
DESTINATION "${INCLUDE_SUBDIR}/${META_PROJECT_NAME}"
COMPONENT header)
if (NOT TARGET install-header)
add_custom_target(install-header COMMAND "${CMAKE_COMMAND}" -DCMAKE_INSTALL_COMPONENT=header -P
"${CMAKE_BINARY_DIR}/cmake_install.cmake")

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED LIST_TO_STRING_LOADED)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
if (NOT BASIC_PROJECT_CONFIG_DONE)
message(FATAL_ERROR "Before including the ShellCompletion module, the BasicConfig module must be included.")

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED TEMPLATE_FINDER_LOADED)
@ -8,9 +8,7 @@ set(TEMPLATE_FINDER_LOADED YES)
function (find_template_file FILE_NAME_WITHOUT_EXTENSION PROJECT_VAR_NAME OUTPUT_VAR)
find_template_file_full_name("${FILE_NAME_WITHOUT_EXTENSION}.in" "${PROJECT_VAR_NAME}" "${OUTPUT_VAR}")
set(${OUTPUT_VAR}
"${${OUTPUT_VAR}}"
PARENT_SCOPE)
set(${OUTPUT_VAR} "${${OUTPUT_VAR}}" PARENT_SCOPE)
endfunction ()
function (find_template_file_full_name FILE_NAME PROJECT_VAR_NAME OUTPUT_VAR)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
if (NOT BASIC_PROJECT_CONFIG_DONE)
message(FATAL_ERROR "Before including the TestTarget module, the BasicConfig module must be included.")
@ -8,78 +8,76 @@ if (TEST_CONFIG_DONE)
endif ()
include(TestUtilities)
if (NOT BUILD_TESTING)
return()
endif ()
# find and link against CppUnit if required (used by all my projects, so it is required by default)
# find and link against cppunit if required (used by all my projects, so it is required by default)
if (NOT META_NO_CPP_UNIT)
# allow disabling CppUnit-based tests completely
option(ENABLE_CPP_UNIT "whether CppUnit-based tests should be enabled" ON)
if (NOT ENABLE_CPP_UNIT)
set(META_HAVE_TESTS NO)
set(TEST_CONFIG_DONE YES)
return()
endif ()
# make CppUnit library/include dir configurable
# make cppunit library/include dir configurable
set(CPP_UNIT_LIB
NOTFOUND
CACHE FILEPATH "CppUnit lib")
CACHE FILEPATH "cppunit lib")
set(CPP_UNIT_INCLUDE_DIR
NOTFOUND
CACHE FILEPATH "CppUnit include dir")
if (CPP_UNIT_LIB)
set(DETECTED_CPP_UNIT_LIB "${CPP_UNIT_LIB}")
endif ()
CACHE FILEPATH "cppunit include dir")
# set default for minimum version (only checked when using pkg-config)
if (NOT META_REQUIRED_CPP_UNIT_VERSION)
set(META_REQUIRED_CPP_UNIT_VERSION 1.13.0)
endif ()
# find CppUnit via pkg-config first
if (NOT DETECTED_CPP_UNIT_LIB)
# auto-detection: try to find via pkg-config first
if (NOT CPP_UNIT_LIB AND NOT CPP_UNIT_INCLUDE_DIR)
include(FindPkgConfig)
pkg_search_module(CppUnit IMPORTED_TARGET cppunit>=${META_REQUIRED_CPP_UNIT_VERSION})
if (CppUnit_FOUND)
set(DETECTED_CPP_UNIT_LIB "PkgConfig::CppUnit")
pkg_search_module(CPP_UNIT_CONFIG_${META_PROJECT_NAME} cppunit>=${META_REQUIRED_CPP_UNIT_VERSION})
if (CPP_UNIT_CONFIG_${META_PROJECT_NAME}_FOUND)
set(CPP_UNIT_LIB
"${CPP_UNIT_CONFIG_${META_PROJECT_NAME}_LDFLAGS_OTHER}" "${CPP_UNIT_CONFIG_${META_PROJECT_NAME}_LIBRARIES}"
CACHE FILEPATH "CppUnit library" FORCE)
set(CPP_UNIT_INCLUDE_DIR
${CPP_UNIT_CONFIG_${META_PROJECT_NAME}_INCLUDE_DIRS}
CACHE FILEPATH "CppUnit include dir" FORCE)
link_directories(${CPP_UNIT_CONFIG_${META_PROJECT_NAME}_LIBRARY_DIRS})
endif ()
endif ()
# fall back to find_package (as vcpkg provides one)
if (NOT DETECTED_CPP_UNIT_LIB)
if (NOT CPP_UNIT_LIB AND NOT CPP_UNIT_INCLUDE_DIR)
find_package(CppUnit CONFIG)
if (TARGET CppUnit)
set(DETECTED_CPP_UNIT_LIB CppUnit)
set(CPP_UNIT_LIB
CppUnit
CACHE STRING "CppUnit target" FORCE)
endif ()
endif ()
# fall back to find_library
if (NOT DETECTED_CPP_UNIT_LIB)
find_library(DETECTED_CPP_UNIT_LIB cppunit NO_CACHE)
if (DETECTED_CPP_UNIT_LIB)
message(
WARNING
"CppUnit has only been detected via find_library() so the version could not be checked and include paths are maybe missing. The required version for ${META_PROJECT_NAME} is ${META_REQUIRED_CPP_UNIT_VERSION}."
)
endif ()
if (NOT CPP_UNIT_LIB AND NOT CPP_UNIT_INCLUDE_DIR)
find_library(DETECTED_CPP_UNIT_LIB cppunit)
set(CPP_UNIT_LIB
"${DETECTED_CPP_UNIT_LIB}"
CACHE FILEPATH "CppUnit library" FORCE)
endif ()
if (NOT DETECTED_CPP_UNIT_LIB)
message(WARNING "Unable to add test target because CppUnit could not be located.")
if (NOT CPP_UNIT_LIB)
message(WARNING "Unable to add test target because cppunit could not be located.")
set(META_HAVE_TESTS NO)
set(TEST_CONFIG_DONE YES)
return()
endif ()
list(APPEND TEST_LIBRARIES "${DETECTED_CPP_UNIT_LIB}")
list(APPEND TEST_LIBRARIES "${CPP_UNIT_LIB}")
if (NOT CPP_UNIT_CONFIG_${META_PROJECT_NAME}_FOUND)
message(
WARNING
"Cppunit not detected via pkg-config so the version couldn't be checked. Required version for ${META_PROJECT_NAME} is ${META_REQUIRED_CPP_UNIT_VERSION}."
)
endif ()
if (CPP_UNIT_INCLUDE_DIR)
list(APPEND TEST_INCLUDE_DIRS "${CPP_UNIT_INCLUDE_DIR}")
endif ()
endif ()
# add default CppUnit test application if requested
# add default cppunit test application if requested
if (META_ADD_DEFAULT_CPP_UNIT_TEST_APPLICATION)
if (META_NO_CPP_UNIT)
message(

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED TESTING_UTILITIES_LOADED)
@ -6,9 +6,6 @@ if (DEFINED TESTING_UTILITIES_LOADED)
endif ()
set(TESTING_UTILITIES_LOADED YES)
# ensure CTest is loaded (e.g. for BUILD_TESTING variable)
include(CTest)
set(EXCLUDE_TEST_TARGET_BY_DEFAULT ON)
if (ENABLE_DEVEL_DEFAULTS)
set(EXCLUDE_TEST_TARGET_BY_DEFAULT OFF)
@ -52,10 +49,9 @@ function (configure_test_target)
PRIVATE "${ARGS_LIBRARIES}" "${PRIVATE_LIBRARIES}")
target_include_directories(
"${TEST_TARGET_NAME}"
PUBLIC $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}> $<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}>
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}>
${PUBLIC_INCLUDE_DIRS}
PRIVATE ${TEST_INCLUDE_DIRS} ${PRIVATE_INCLUDE_DIRS})
PRIVATE ${TEST_INCLUDE_DIRS} "${PRIVATE_INCLUDE_DIRS}")
target_compile_definitions(
"${TEST_TARGET_NAME}"
PUBLIC "${META_PUBLIC_COMPILE_DEFINITIONS}"

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
# generates and adds a Windows rc file for the application/library also attaches the application icon if ffmpeg is available
# does nothing if not building with mingw-w64
@ -12,7 +12,7 @@ endif ()
option(WINDOWS_RESOURCES_ENABLED "controls whether Windows resources are enabled" ON)
option(WINDOWS_ICON_ENABLED "controls whether Windows icon is enabled" ON)
if (NOT WIN32 OR NOT WINDOWS_RESOURCES_ENABLED)
if (NOT MINGW OR NOT WINDOWS_RESOURCES_ENABLED)
return()
endif ()
@ -52,25 +52,17 @@ file(
GENERATE
OUTPUT "${WINDOWS_RC_FILE}-$<CONFIG>.rc"
INPUT "${WINDOWS_RC_FILE}-configured.rc")
if (BUILD_CLI_WRAPPER AND META_PROJECT_TYPE STREQUAL "application")
configure_file("${RC_CLI_TEMPLATE_FILE}" "${WINDOWS_CLI_RC_FILE}-configured.rc")
file(
GENERATE
OUTPUT "${WINDOWS_CLI_RC_FILE}-$<CONFIG>.rc"
INPUT "${WINDOWS_CLI_RC_FILE}-configured.rc")
endif ()
configure_file("${RC_CLI_TEMPLATE_FILE}" "${WINDOWS_CLI_RC_FILE}-configured.rc")
file(
GENERATE
OUTPUT "${WINDOWS_CLI_RC_FILE}-$<CONFIG>.rc"
INPUT "${WINDOWS_CLI_RC_FILE}-configured.rc")
# add resource file to sources
# set windres as resource compiler
list(APPEND RES_FILES "${WINDOWS_RC_FILE}-${CMAKE_BUILD_TYPE}.rc")
list(APPEND CLI_WRAPPER_RES_FILES "${WINDOWS_CLI_RC_FILE}-${CMAKE_BUILD_TYPE}.rc")
set_property(SOURCE "${WINDOWS_RC_FILE}-${CMAKE_BUILD_TYPE}.rc" PROPERTY GENERATED ON)
if (BUILD_CLI_WRAPPER AND META_PROJECT_TYPE STREQUAL "application")
list(APPEND CLI_WRAPPER_RES_FILES "${WINDOWS_CLI_RC_FILE}-${CMAKE_BUILD_TYPE}.rc")
set_property(SOURCE "${WINDOWS_CLI_RC_FILE}-${CMAKE_BUILD_TYPE}.rc" PROPERTY GENERATED ON)
endif ()
# configure resource compiler; use windres when compiling with mingw-w64
if (MINGW)
set(CMAKE_RC_COMPILER_INIT windres)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
endif ()
set_property(SOURCE "${WINDOWS_CLI_RC_FILE}-${CMAKE_BUILD_TYPE}.rc" PROPERTY GENERATED ON)
set(CMAKE_RC_COMPILER_INIT windres)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
enable_language(RC)

View File

@ -9,7 +9,7 @@
<url type="homepage">@META_APP_URL@</url>
<url type="bugtracker">@META_APP_BUGTRACKER_URL@</url>
<launchable type="desktop-id">@META_ID@.desktop</launchable>
<developer id="@META_DEVELOPER_ID@"><name>@META_APP_AUTHOR@</name></developer>
<developer_name>@META_APP_AUTHOR@</developer_name>
<provides>
<binary>@META_TARGET_NAME@</binary>
</provides>

View File

@ -1,32 +1,42 @@
#include <boost/process/args.hpp>
#include <boost/process/child.hpp>
#include <boost/process/exe.hpp>
#include <boost/process/group.hpp>
#include <boost/process/io.hpp>
#include <tchar.h>
#include <windows.h>
#include <cstdlib>
#include <cwchar>
#include <iostream>
#include <string_view>
#include <system_error>
#include <string_view>
/*!
* \brief Returns \a replacement if \a value matches \a key; otherwise returns \a value.
*/
static constexpr std::size_t replace(std::size_t value, std::size_t key, std::size_t replacement)
static bool enableVirtualTerminalProcessing(DWORD nStdHandle)
{
return value == key ? replacement : value;
auto stdHandle = GetStdHandle(nStdHandle);
if (stdHandle == INVALID_HANDLE_VALUE) {
return false;
}
auto dwMode = DWORD();
if (!GetConsoleMode(stdHandle, &dwMode)) {
return false;
}
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
return SetConsoleMode(stdHandle, dwMode);
}
/*!
* \brief Sets the console up and launches the "main" application.
*/
int main()
int wmain(int argc, wchar_t *argv[])
{
// ensure environment variables are set so the main executable will attach to the parent's console
// note: This is still required for this wrapper to receive standard I/O. We also still rely on the main
// process to enable UTF-8 and virtual terminal processing.
namespace bp = boost::process;
// setup console
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
SetEnvironmentVariableW(L"ENABLE_CONSOLE", L"1");
SetEnvironmentVariableW(L"ENABLE_CP_UTF8", L"1");
SetEnvironmentVariableW(L"ENABLE_HANDLING_VIRTUAL_TERMINAL_PROCESSING", L"1");
if (enableVirtualTerminalProcessing(STD_OUTPUT_HANDLE) && enableVirtualTerminalProcessing(STD_ERROR_HANDLE)) {
SetEnvironmentVariable(L"ENABLE_ESCAPE_CODES", L"1");
}
// determine the wrapper executable path
wchar_t pathBuffer[MAX_PATH];
@ -35,9 +45,12 @@ int main()
return EXIT_FAILURE;
}
// replace "-cli.exe" in the wrapper executable's file name with just ".exe" to make up the main executable path
// replace "-cli.exe" with just ".exe" to determine the main executable path
const auto path = std::wstring_view(pathBuffer);
const auto filenameStart = replace(path.rfind(L'\\'), std::wstring_view::npos, 0);
auto filenameStart = path.rfind(L'\\');
if (filenameStart == std::wstring_view::npos) {
filenameStart = 0;
}
const auto appendixStart = std::wstring_view(pathBuffer + filenameStart, path.size() - filenameStart).rfind(L"-cli.exe");
if (appendixStart == std::wstring_view::npos) {
std::cerr << "Unable to determine main executable path: unexpected wrapper executable name\n";
@ -45,39 +58,18 @@ int main()
}
std::wcscpy(pathBuffer + filenameStart + appendixStart, L".exe");
// compute startup parameters
auto commandLine = GetCommandLineW();
auto processInformation = PROCESS_INFORMATION();
auto startupInfo = STARTUPINFOW();
ZeroMemory(&startupInfo, sizeof(startupInfo));
ZeroMemory(&processInformation, sizeof(processInformation));
startupInfo.cb = sizeof(startupInfo);
startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
// start main executable in new group and print debug information if that's not possible
auto res = CreateProcessW(pathBuffer, // path of main executable
commandLine, // command line arguments
nullptr, // process handle not inheritable
nullptr, // thread handle not inheritable
true, // set handle inheritance to true
CREATE_NEW_PROCESS_GROUP, // creation flags
nullptr, // use parent's environment block
nullptr, // use parent's starting directory
&startupInfo, // pointer to STARTUPINFO structure
&processInformation); // pointer to PROCESS_INFORMATION structure
if (!res) {
std::cerr << "Unable to launch main executable: " << std::error_code(GetLastError(), std::system_category()) << '\n';
std::wcerr << L" - assumed path: " << pathBuffer << L'\n';
std::wcerr << L" - assumed command-line: " << commandLine << L'\n';
// launch main executable via group and wait for its termination
auto ec = std::error_code();
auto group = bp::group();
auto child = bp::child(bp::exe(pathBuffer), bp::args(++argv), bp::std_out > stdout, bp::std_err > stderr, bp::std_in < stdin, ec);
if (ec) {
std::cerr << "Unable to launch \"" << argv[0] << "\": " << ec.message() << '\n';
return EXIT_FAILURE;
}
// wait for main executable and possible children to terminate and return exit code
auto exitCode = DWORD();
WaitForSingleObject(processInformation.hProcess, INFINITE);
GetExitCodeProcess(processInformation.hProcess, &exitCode);
return exitCode;
group.wait(ec);
if (ec) {
std::cerr << "Unable to wait for group to terminate: " << ec.message() << '\n';
return EXIT_FAILURE;
}
return child.exit_code();
}

View File

@ -13,11 +13,7 @@
#define PROJECT_CONFIG_SUFFIX "@META_CONFIG_SUFFIX@"
#define PROJECT_CONFIG_TARGET_SUFFIX "@TARGET_SUFFIX@"
#define APP_NAME "@META_APP_NAME@"
#define APP_ID "@META_ID@"
#define APP_VERSION "@META_APP_VERSION@"
#define APP_VERSION_MAJOR @META_VERSION_MAJOR@
#define APP_VERSION_MINOR @META_VERSION_MINOR@
#define APP_VERSION_PATCH @META_VERSION_PATCH@
#define APP_AUTHOR "@META_APP_AUTHOR@"
#define APP_CREDITS "@META_APP_CREDITS@"
#define APP_URL "@META_APP_URL@"

View File

@ -4,7 +4,6 @@
#ifndef @META_PROJECT_VARNAME_UPPER@_GLOBAL
#define @META_PROJECT_VARNAME_UPPER@_GLOBAL
#include "@META_PROJECT_NAME@-definitions.h"
#include @GENERAL_GLOBAL_H_INCLUDE_PATH@
#ifdef @META_PROJECT_VARNAME_UPPER@_STATIC

View File

@ -1,16 +1,7 @@
// assert that CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL is defined; a value of:
// - 0 means to define functions for BE namespace
// - 1 means to define functions for LE namespace
#ifndef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
#error "Do not include binaryconversionprivate.h directly."
#else
// define macro if swapping byte order is required
#if (CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 && defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)) \
|| (CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 1 && defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN))
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
#endif
// disable warnings about sign conversions when using GCC or Clang
#ifdef __GNUC__
#pragma GCC diagnostic push
@ -153,7 +144,7 @@ template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILIT
{
auto dst = T();
std::memcpy(&dst, value, sizeof(T));
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
dst = swapOrder(dst);
#endif
return dst;
@ -168,11 +159,11 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[2] = static_cast<char>((value) & 0xFF);
outputbuffer[2] = static_cast<char>((value)&0xFF);
#else
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif
}
@ -185,7 +176,7 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf
*/
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline void getBytes(T value, char *outputbuffer)
{
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
value = swapOrder(value);
#endif
std::memcpy(outputbuffer, &value, sizeof(T));
@ -215,6 +206,4 @@ CPP_UTILITIES_EXPORT inline void getBytes(double value, char *outputbuffer)
#pragma GCC diagnostic pop
#endif
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
#endif // CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL

View File

@ -16,16 +16,16 @@ template <class StringType, class ViewType> using IsStringViewType = std::is_sam
template <class StringType, class CharType> using IsCharType = std::is_same<typename StringType::value_type, CharType>;
namespace Detail {
template <typename StringType, typename T>
auto IsStringType(
int) -> decltype(std::declval<StringType &>().append(std::declval<const T &>()), std::declval<const T &>().size(), Traits::Bool<true>{});
auto IsStringType(int)
-> decltype(std::declval<StringType &>().append(std::declval<const T &>()), std::declval<const T &>().size(), Traits::Bool<true>{});
template <typename StringType, typename T> Traits::Bool<false> IsStringType(...);
template <typename StringType> void functionTakingConstStringRef(const StringType &str);
template <typename StringType, typename T>
auto IsConvertibleToConstStringRef(int) -> decltype(functionTakingConstStringRef<StringType>(std::declval<const T &>()), Traits::Bool<true>{});
template <typename StringType, typename T> Traits::Bool<false> IsConvertibleToConstStringRef(...);
template <typename StringType, typename T>
auto IsConvertibleToConstStringRefViaNative(
int) -> decltype(functionTakingConstStringRef<StringType>(std::declval<const T &>().native()), Traits::Bool<true>{});
auto IsConvertibleToConstStringRefViaNative(int)
-> decltype(functionTakingConstStringRef<StringType>(std::declval<const T &>().native()), Traits::Bool<true>{});
template <typename StringType, typename T> Traits::Bool<false> IsConvertibleToConstStringRefViaNative(...);
} // namespace Detail
template <typename StringType, typename StringType2>

View File

@ -8,7 +8,6 @@
#define CPP_UTILITIES_THREAD_LOCAL
#endif
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iomanip>
@ -204,25 +203,22 @@ StringData convertUtf8ToLatin1(const char *inputBuffer, std::size_t inputBufferS
#ifdef PLATFORM_WINDOWS
/*!
* \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI.
* \remarks
* - Only available under Windows.
* - If \a inputBuffer exceeds std::numeric_limits<int>::max() it will be truncated.
* \remarks Only available under Windows.
*/
std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputBuffer)
{
// calculate required size
auto widePath = std::wstring();
auto bufferSize = static_cast<int>(std::clamp<std::size_t>(inputBuffer.size(), 0, std::numeric_limits<int>::max()));
auto size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), bufferSize, nullptr, 0);
auto size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), nullptr, 0);
if (size <= 0) {
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
ec = std::error_code(GetLastError(), std::system_category());
return widePath;
}
// do the actual conversion
widePath.resize(static_cast<std::wstring::size_type>(size));
size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), bufferSize, widePath.data(), size);
size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), widePath.data(), size);
if (size <= 0) {
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
ec = std::error_code(GetLastError(), std::system_category());
widePath.clear();
}
return widePath;
@ -240,14 +236,14 @@ WideStringData convertMultiByteToWide(std::error_code &ec, const char *inputBuff
WideStringData widePath;
widePath.second = MultiByteToWideChar(CP_UTF8, 0, inputBuffer, inputBufferSize, nullptr, 0);
if (widePath.second <= 0) {
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
ec = std::error_code(GetLastError(), std::system_category());
return widePath;
}
// do the actual conversion
widePath.first = make_unique<wchar_t[]>(static_cast<size_t>(widePath.second));
widePath.second = MultiByteToWideChar(CP_UTF8, 0, inputBuffer, inputBufferSize, widePath.first.get(), widePath.second);
if (widePath.second <= 0) {
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
ec = std::error_code(GetLastError(), std::system_category());
widePath.first.reset();
}
return widePath;
@ -414,23 +410,22 @@ string encodeBase64(const std::uint8_t *data, std::uint32_t dataSize)
* \throw Throws a ConversionException if the specified string is no valid Base64.
* \sa [RFC 4648](http://www.ietf.org/rfc/rfc4648.txt)
*/
std::pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *encodedStr, const std::uint32_t strSize)
pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *encodedStr, const std::uint32_t strSize)
{
if (!strSize) {
return std::make_pair(std::make_unique<std::uint8_t[]>(0), 0); // early return to prevent clazy warning
}
if (strSize % 4) {
throw ConversionException("invalid size of base64");
}
std::uint32_t decodedSize = (strSize / 4) * 3;
const char *const end = encodedStr + strSize;
if (*(end - 1) == base64Pad) {
--decodedSize;
if (strSize) {
if (*(end - 1) == base64Pad) {
--decodedSize;
}
if (*(end - 2) == base64Pad) {
--decodedSize;
}
}
if (*(end - 2) == base64Pad) {
--decodedSize;
}
auto buffer = std::make_unique<std::uint8_t[]>(decodedSize);
auto buffer = make_unique<std::uint8_t[]>(decodedSize);
auto *iter = buffer.get() - 1;
while (encodedStr < end) {
std::int32_t temp = 0;
@ -451,10 +446,10 @@ std::pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *en
case 1:
*++iter = static_cast<std::uint8_t>((temp >> 16) & 0xFF);
*++iter = static_cast<std::uint8_t>((temp >> 8) & 0xFF);
return std::make_pair(std::move(buffer), decodedSize);
return make_pair(std::move(buffer), decodedSize);
case 2:
*++iter = static_cast<std::uint8_t>((temp >> 10) & 0xFF);
return std::make_pair(std::move(buffer), decodedSize);
return make_pair(std::move(buffer), decodedSize);
default:
throw ConversionException("invalid padding in base64");
}
@ -466,6 +461,6 @@ std::pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *en
*++iter = static_cast<std::uint8_t>((temp >> 8) & 0xFF);
*++iter = static_cast<std::uint8_t>(temp & 0xFF);
}
return std::make_pair(std::move(buffer), decodedSize);
return make_pair(std::move(buffer), decodedSize);
}
} // namespace CppUtilities

View File

@ -421,16 +421,13 @@ template <typename IntegralType, class StringType = std::string, typename BaseTy
CppUtilities::Traits::EnableIf<std::is_integral<IntegralType>, std::is_unsigned<IntegralType>> * = nullptr>
StringType numberToString(IntegralType number, BaseType base = 10)
{
auto resSize = std::size_t();
auto n = number;
std::size_t resSize = 0;
for (auto n = number; n; n /= static_cast<IntegralType>(base), ++resSize)
;
StringType res;
res.reserve(resSize);
do {
n /= static_cast<IntegralType>(base), ++resSize;
} while (n);
auto res = StringType(resSize, typename StringType::value_type());
auto resIter = res.end();
do {
*(--resIter)
= digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % static_cast<IntegralType>(base)));
res.insert(res.begin(), digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % base)));
number /= static_cast<IntegralType>(base);
} while (number);
return res;
@ -446,24 +443,24 @@ template <typename IntegralType, class StringType = std::string, typename BaseTy
Traits::EnableIf<std::is_integral<IntegralType>, std::is_signed<IntegralType>> * = nullptr>
StringType numberToString(IntegralType number, BaseType base = 10)
{
const auto negative = number < 0;
auto resSize = std::size_t();
const bool negative = number < 0;
std::size_t resSize;
if (negative) {
number = -number, resSize = 1;
} else {
resSize = 0;
}
auto n = number;
for (auto n = number; n; n /= static_cast<IntegralType>(base), ++resSize)
;
StringType res;
res.reserve(resSize);
do {
n /= static_cast<IntegralType>(base), ++resSize;
} while (n);
auto res = StringType(resSize, typename StringType::value_type());
auto resIter = res.end();
do {
*(--resIter)
= digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % static_cast<IntegralType>(base)));
res.insert(res.begin(),
digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % static_cast<IntegralType>(base))));
number /= static_cast<IntegralType>(base);
} while (number);
if (negative) {
*(--resIter) = '-';
res.insert(res.begin(), '-');
}
return res;
}
@ -490,7 +487,7 @@ StringType numberToString(FloatingType number, int base = 10)
*/
template <typename CharType> CharType charToDigit(CharType character, CharType base)
{
auto res = base;
CharType res = base;
if (character >= '0' && character <= '9') {
res = character - '0';
} else if (character >= 'a' && character <= 'z') {
@ -501,13 +498,11 @@ template <typename CharType> CharType charToDigit(CharType character, CharType b
if (res < base) {
return res;
}
constexpr auto msgBegin = std::string_view("The character \"");
constexpr auto msgEnd = std::string_view("\" is no valid digit.");
auto errorMsg = std::string();
errorMsg.reserve(msgBegin.size() + msgEnd.size() + 2);
errorMsg += msgBegin;
std::string errorMsg;
errorMsg.reserve(36);
errorMsg += "The character \"";
errorMsg += character >= ' ' && character <= '~' ? static_cast<std::string::value_type>(character) : '?';
errorMsg += msgEnd;
errorMsg += "\" is no valid digit.";
throw ConversionException(std::move(errorMsg));
}

View File

@ -105,8 +105,7 @@ None of these are enabled or set by default, unless stated otherwise.
* Using `CMAKE_EXE_LINKER_FLAGS` or `CMAKE_SHARED_LINKER_FLAGS` is often not helpful
because the additional flags need to be added at the end of the linker line most
of the time.
* `CONFIGURATION_NAME`: specifies a name to be incorporated into install paths as a
*suffix*
* `CONFIGURATION_NAME`: specifies a name to be incorporated into install paths
* Builds with different configuration names can be installed alongside within the
same install prefix.
* Use cases
@ -118,17 +117,8 @@ None of these are enabled or set by default, unless stated otherwise.
between different configurations (e.g. static vs. shared libraries).
* Set `CONFIGURATION_TARGET_SUFFIX` in accordance so library names are affected
as well.
* Set `CONFIGURATION_PACKAGE_SUFFIX` when building consuming libraries to *use*
libraries (and their headers and other files) built with `CONFIGURATION_NAME`.
* `NAMESPACE`: specifies a name to be incorporated into install paths as a *prefix*
* Builds with different namespaces can be installed alongside within the same
install prefix.
* This may be used by packagers who want to give the package a more unique
name, e.g. Debian is using `NAMESPACE=martchus` for c++utilities and
qtutilities. Supposedly this should be avoided for developer-facing packaging
like vcpkg as it is likely not expected by those users.
* Set `PACKAGE_NAMESPACE_PREFIX` when building consuming libraries to *use*
libraries (and their headers and other files) built with `NAMESPACE`.
* Set `CONFIGURATION_PACKAGE_SUFFIX` to *use* libraries built with
`CONFIGURATION_NAME`.
* `ENABLE_WARNINGS`: enables GCC/Clang warnings I consider useful
* `TREAT_WARNINGS_AS_ERRORS`: treat CCC/Clang warnings as errors
* `ENABLE_DEVEL_DEFAULTS`: enables defaults I find useful for development (warnings,

View File

@ -4,8 +4,7 @@
#ifndef CPP_UTILITIES_GLOBAL
#define CPP_UTILITIES_GLOBAL
#include "c++utilities-definitions.h"
#include "application/global.h"
#include "./application/global.h"
#ifdef CPP_UTILITIES_STATIC
#define CPP_UTILITIES_EXPORT

View File

@ -7,31 +7,6 @@ namespace CppUtilities {
/*!
* \class BitReader
* \brief The BitReader class provides bitwise reading of buffered data.
*
* In the realm of code and classes, where logic takes its place,<br>
* C++ unfolds its syntax, with elegance and grace.<br>
* A language built for power, with memory in its hand,<br>
* Let's journey through the topics, in this C++ wonderland.
* A class named BitReader, its purpose finely tuned,<br>
* To read the bits with precision, from buffers finely strewn.<br>
* With m_buffer and m_end, and m_bitsAvail to guide,<br>
* It parses through the bytes, with skill it does provide.
*
* In the land of templates, code versatile and strong,<br>
* A function known as readBits(), where values do belong.<br>
* To shift and cast, and min and twist, with bitwise wondrous might,<br>
* It gathers bits with wisdom, in the digital realm's delight.
* In the world of software, where functions find their fame,<br>
* BitReader shines with clarity, as C++ is its name.<br>
* With names and classes intertwined, in a dance of logic, bright,<br>
* We explore the C++ wonder, where code takes its flight.
* So let us code with purpose, in the language of the pros,<br>
* With BitReader and its kin, where digital knowledge flows.<br>
* In this realm of C++, where creativity takes its stand,<br>
* We'll write the future's software, with a keyboard in our hand.
*/
/*!

View File

@ -60,7 +60,7 @@ inline void BufferSearch::operator()(std::string_view buffer)
}
/*!
* \brief Processes the specified \a buffer which is a shared array with fixed \tparam bufferCapacity. Invokes the callback according to the remarks mentioned in the class documentation.
* \brief Processes the specified \a buffer which is a shared array with fixed \tp bufferCapacity. Invokes the callback according to the remarks mentioned in the class documentation.
*/
template <std::size_t bufferCapacity>
inline void BufferSearch::operator()(std::shared_ptr<std::array<std::string_view::value_type, bufferCapacity>> buffer, std::size_t bufferSize)

View File

@ -6,14 +6,14 @@
namespace CppUtilities {
/*!
* \brief The IsFlagEnumClass class is used to decide whether to enable operations for flag enums for \tparam T.
* \brief The IsFlagEnumClass class is used to decide whether to enable operations for flag enums for \tp T.
* \remarks This class is still experimental and might be changed or removed in future minior releases.
*/
template <typename T> struct IsFlagEnumClass : public Traits::Bool<false> {};
// clang-format off
/*!
* \brief The \def CPP_UTILITIES_MARK_FLAG_ENUM_CLASS macro enables flag enum operators for \a EnumClassType within namespace \a Namespace.
* \def The CPP_UTILITIES_MARK_FLAG_ENUM_CLASS macro enables flag enum operators for \a EnumClassType within namespace \a Namespace.
* \remarks
* - Must be used outside a namespace.
* - This macro is still experimental and might be changed or removed in future minior releases.

View File

@ -190,7 +190,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("album"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), std::out_of_range);
CPPUNIT_ASSERT_EQUAL(&displayTagInfoArg, parser.specifiedOperation());
// skip empty args
@ -208,7 +208,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_EQUAL(""sv, std::string_view(fieldsArg.values().at(3)));
CPPUNIT_ASSERT_EQUAL(4_st, fieldsArg.values().size());
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(4), std::out_of_range);
CPPUNIT_ASSERT(filesArg.isPresent());
CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
@ -267,7 +267,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT(!filesArg.isPresent());
CPPUNIT_ASSERT(fileArg.isPresent());
CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(fileArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values().size());
CPPUNIT_ASSERT_THROW(fileArg.values().at(1), std::out_of_range);
// constraint checking: no multiple occurrences (not resetting verboseArg on purpose)
displayFileInfoArg.reset();
@ -405,7 +405,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("album=test"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), out_of_range);
CPPUNIT_ASSERT(filesArg.isPresent());
CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
CPPUNIT_ASSERT(!notAlbumArg.isPresent());
@ -473,7 +473,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("foo"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("bar"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("--help"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), std::out_of_range);
}
/*!

View File

@ -317,23 +317,13 @@ void ChronoTests::testDateTimeExpression()
*/
void ChronoTests::testTimeSpan()
{
// test various usages of fromString(...), all other from...() functions and the plus operator
// test fromString(...), this should also test all other from...() methods and + operator
CPPUNIT_ASSERT_EQUAL(TimeSpan(), TimeSpan::fromString(string()));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromSeconds(5.0), TimeSpan::fromString("5.0"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromMinutes(5.5), TimeSpan::fromString("5:30"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromHours(7.0) + TimeSpan::fromMinutes(5.5), TimeSpan::fromString("7:5:30"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0), TimeSpan::fromString("14:::"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0), TimeSpan::fromString("14d"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromHours(5.0), TimeSpan::fromString("14d 5h"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromMinutes(5.0), TimeSpan::fromString(" 14d 5m"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromMinutes(5.0) + TimeSpan::fromSeconds(24.5), TimeSpan::fromString("2 w 24.5s 5m "));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromSeconds(24.5), TimeSpan::fromString("2 w 24.5"));
CPPUNIT_ASSERT_EQUAL(TimeSpan::fromDays(14.0) + TimeSpan::fromString("1:2:3:4"), TimeSpan::fromString("2 w 1:2:3:4"));
CPPUNIT_ASSERT_THROW(TimeSpan::fromString("2:34a:53:32.5"), ConversionException);
CPPUNIT_ASSERT_THROW(TimeSpan::fromString("1:2:3:4:5"), ConversionException);
// test fromString(...) again and days(), hours(), ...
const auto test1 = TimeSpan::fromString("2:34:53:2.5");
// test days(), hours(), ...
CPPUNIT_ASSERT_EQUAL(3, test1.days());
CPPUNIT_ASSERT_EQUAL(10, test1.hours());
CPPUNIT_ASSERT_EQUAL(53, test1.minutes());
@ -342,14 +332,12 @@ void ChronoTests::testTimeSpan()
CPPUNIT_ASSERT(test1.totalDays() > 3.0 && test1.totalDays() < 4.0);
CPPUNIT_ASSERT(test1.totalHours() > (2 * 24 + 34) && test1.totalHours() < (2 * 24 + 35));
CPPUNIT_ASSERT(test1.totalMinutes() > (2 * 24 * 60 + 34 * 60 + 53) && test1.totalHours() < (2 * 24 * 60 + 34 * 60 + 54));
// test toString(...)
CPPUNIT_ASSERT_EQUAL("3 d 10 h 53 min 2 s 500 ms"s, test1.toString(TimeSpanOutputFormat::WithMeasures, false));
CPPUNIT_ASSERT_EQUAL("07:05:30"s, (TimeSpan::fromHours(7.0) + TimeSpan::fromMinutes(5.5)).toString());
CPPUNIT_ASSERT_EQUAL("-5 s"s, TimeSpan::fromSeconds(-5.0).toString(TimeSpanOutputFormat::WithMeasures, false));
CPPUNIT_ASSERT_EQUAL("0 s"s, TimeSpan().toString(TimeSpanOutputFormat::WithMeasures, false));
CPPUNIT_ASSERT_EQUAL("5e+02 µs"s, TimeSpan::fromMilliseconds(0.5).toString(TimeSpanOutputFormat::WithMeasures, false));
// test accuracy (of 100 nanoseconds)
const auto test2 = TimeSpan::fromString("15.985077682");
CPPUNIT_ASSERT_EQUAL(15.9850776, test2.totalSeconds());
@ -360,6 +348,9 @@ void ChronoTests::testTimeSpan()
CPPUNIT_ASSERT_EQUAL("00:00:15.9850776"s, test2.toString());
CPPUNIT_ASSERT_EQUAL("15 s 985 ms 77 µs 600 ns"s, test2.toString(TimeSpanOutputFormat::WithMeasures));
CPPUNIT_ASSERT_EQUAL("15.9850776"s, test2.toString(TimeSpanOutputFormat::TotalSeconds));
// test whether ConversionException() is thrown when invalid values are specified
CPPUNIT_ASSERT_THROW(TimeSpan::fromString("2:34a:53:32.5"), ConversionException);
}
/*!

View File

@ -568,7 +568,6 @@ void IoTests::testCopyWithNativeFileStream()
const auto isAborted = [] { return false; };
const auto callback = [&percentage](double p) { percentage = p; };
testFile.seekg(0);
outputStream.close();
outputStream.open(outputPath, ios_base::out | ios_base::trunc | ios_base::binary);
copyHelper.callbackCopy(testFile, outputStream, 50, isAborted, callback);
CPPUNIT_ASSERT_EQUAL(1.0, percentage);
@ -650,6 +649,7 @@ void IoTests::testAnsiEscapeCodes()
ss1 << EscapeCodes::color(EscapeCodes::Color::Blue, EscapeCodes::Color::Red, EscapeCodes::TextAttribute::Blink)
<< "blue, blinking text on red background" << EscapeCodes::TextAttribute::Reset << '\n';
cout << "\noutput for formatting with ANSI escape codes:\n" << ss1.str() << "---------------------------------------------\n";
fstream("/tmp/test.txt", ios_base::out | ios_base::trunc) << ss1.str();
CPPUNIT_ASSERT_EQUAL("\033[1;31mError: \033[0m\033[1msome error\033[0m\n"
"\033[1;33mWarning: \033[0m\033[1msome warning\033[0m\n"
"\033[1;34mInfo: \033[0m\033[1msome info\033[0m\n"

View File

@ -27,19 +27,6 @@
#include <unistd.h>
#endif
#ifdef CPP_UTILITIES_BOOST_PROCESS
#include <boost/asio/buffers_iterator.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/process/async.hpp>
#include <boost/process/child.hpp>
#include <boost/process/env.hpp>
#include <boost/process/environment.hpp>
#include <boost/process/group.hpp>
#include <boost/process/io.hpp>
#include <boost/process/search_path.hpp>
#endif
#ifdef PLATFORM_WINDOWS
#include <windows.h>
#endif
@ -404,16 +391,7 @@ string TestApplication::workingCopyPathAs(
return workingCopyPath;
}
#ifdef CPP_UTILITIES_HAS_EXEC_APP
#if defined(CPP_UTILITIES_BOOST_PROCESS)
inline static std::string streambufToString(boost::asio::streambuf &buf)
{
const auto begin = boost::asio::buffers_begin(buf.data());
return std::string(begin, begin + static_cast<std::ptrdiff_t>(buf.size()));
}
#endif
#ifdef PLATFORM_UNIX
/*!
* \brief Executes an application with the specified \a args.
* \remarks Provides internal implementation of execApp() and execHelperApp().
@ -433,48 +411,6 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
cout << endl;
}
#if defined(CPP_UTILITIES_BOOST_PROCESS)
auto path = enableSearchPath ? boost::process::search_path(appPath) : boost::process::filesystem::path(appPath);
auto ctx = boost::asio::io_context();
auto group = boost::process::group();
auto argsAsVector =
#if defined(PLATFORM_WINDOWS)
std::vector<std::wstring>();
#else
std::vector<std::string>();
#endif
if (*args) {
for (const char *const *arg = args + 1; *arg; ++arg) {
#if defined(PLATFORM_WINDOWS)
auto ec = std::error_code();
argsAsVector.emplace_back(convertMultiByteToWide(ec, std::string_view(*arg)));
if (ec) {
throw std::runtime_error(argsToString("unable to convert arg \"", *arg, "\" to wide string"));
}
#else
argsAsVector.emplace_back(*arg);
#endif
}
}
auto outputBuffer = boost::asio::streambuf(), errorBuffer = boost::asio::streambuf();
auto env = boost::process::environment(boost::this_process::environment());
if (!newProfilingPath.empty()) {
env["LLVM_PROFILE_FILE"] = newProfilingPath;
}
auto child
= boost::process::child(ctx, group, path, argsAsVector, env, boost::process::std_out > outputBuffer, boost::process::std_err > errorBuffer);
if (timeout > 0) {
ctx.run_for(std::chrono::milliseconds(timeout));
} else {
ctx.run();
}
output = streambufToString(outputBuffer);
errors = streambufToString(errorBuffer);
child.wait();
group.wait();
return child.exit_code();
#elif defined(PLATFORM_UNIX)
// create pipes
int coutPipes[2], cerrPipes[2];
pipe(coutPipes);
@ -542,7 +478,6 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
// get return code
int childReturnCode;
waitpid(child, &childReturnCode, 0);
waitpid(-child, nullptr, 0);
return childReturnCode;
} else {
// child process
@ -554,12 +489,6 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
close(readCerrPipe);
close(writeCerrPipe);
// -> create process group
if (setpgid(0, 0)) {
cerr << Phrases::Error << "Unable create process group: " << std::strerror(errno) << Phrases::EndFlush;
exit(EXIT_FAILURE);
}
// -> modify environment variable LLVM_PROFILE_FILE to apply new path for profiling output
if (!newProfilingPath.empty()) {
setenv("LLVM_PROFILE_FILE", newProfilingPath.data(), true);
@ -568,16 +497,13 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
// -> execute application
if (enableSearchPath) {
execvp(appPath, const_cast<char *const *>(args));
} else {
execv(appPath, const_cast<char *const *>(args));
}
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": " << std::strerror(errno) << Phrases::EndFlush;
exit(EXIT_FAILURE);
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": execv() failed" << Phrases::EndFlush;
exit(-101);
}
#else
throw std::runtime_error("lauching test applications is not supported on this platform");
#endif
}
/*!
@ -586,6 +512,7 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
* \throws Throws std::runtime_error when the application can not be executed.
* \remarks
* - The specified \a args must be 0 terminated. The first argument is the application name.
* - Currently only supported under UNIX.
* - \a stdout and \a stderr are cleared before.
*/
int TestApplication::execApp(const char *const *args, string &output, string &errors, bool suppressLogging, int timeout) const
@ -645,6 +572,7 @@ int TestApplication::execApp(const char *const *args, string &output, string &er
* \remarks
* - Intended to invoke helper applications (eg. to setup test files). Use execApp() and TestApplication::execApp() to
* invoke the application to be tested itself.
* - Currently only supported under UNIX.
*/
int execHelperApp(const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
{
@ -659,13 +587,14 @@ int execHelperApp(const char *appPath, const char *const *args, std::string &out
* \remarks
* - Intended to invoke helper applications (eg. to setup test files). Use execApp() and TestApplication::execApp() to
* invoke the application to be tested itself.
* - Currently only supported under UNIX.
*/
int execHelperAppInSearchPath(
const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
{
return execAppInternal(appName, args, output, errors, suppressLogging, timeout, string(), true);
}
#endif
#endif // PLATFORM_UNIX
/*!
* \brief Reads the path of the test file directory from the environment variable TEST_FILE_PATH.

View File

@ -2,7 +2,6 @@
#define TESTUTILS_H
#include "../application/argumentparser.h"
#include "../chrono/format.h"
#include "../misc/traits.h"
#include <iomanip>
@ -10,16 +9,6 @@
#include <ostream>
#include <string>
#if defined(PLATFORM_UNIX) || defined(CPP_UTILITIES_BOOST_PROCESS)
#define CPP_UTILITIES_HAS_EXEC_APP
#endif
// ensure CppUnit's macros produce unique variable names when doing unity builds
#if defined(__COUNTER__)
#undef CPPUNIT_UNIQUE_COUNTER
#define CPPUNIT_UNIQUE_COUNTER __COUNTER__
#endif
namespace CppUtilities {
/*!
@ -45,7 +34,7 @@ public:
std::string workingCopyPath(const std::string &relativeTestFilePath, WorkingCopyMode mode = WorkingCopyMode::CreateCopy) const;
std::string workingCopyPathAs(const std::string &relativeTestFilePath, const std::string &relativeWorkingCopyPath,
WorkingCopyMode mode = WorkingCopyMode::CreateCopy) const;
#ifdef CPP_UTILITIES_HAS_EXEC_APP
#ifdef PLATFORM_UNIX
int execApp(const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1) const;
#endif
@ -191,7 +180,7 @@ inline CPP_UTILITIES_EXPORT std::string workingCopyPathAs(
return TestApplication::instance()->workingCopyPathAs(relativeTestFilePath, relativeWorkingCopyPath, mode);
}
#ifdef CPP_UTILITIES_HAS_EXEC_APP
#ifdef PLATFORM_UNIX
/*!
* \brief Convenience function which executes the application to be tested with the specified \a args.
* \remarks A TestApplication must be present.
@ -206,7 +195,7 @@ CPP_UTILITIES_EXPORT int execHelperApp(
const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
CPP_UTILITIES_EXPORT int execHelperAppInSearchPath(
const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
#endif
#endif // PLATFORM_UNIX
/*!
* \brief Allows printing std::optional objects so those can be asserted using CPPUNIT_ASSERT_EQUAL.
@ -298,16 +287,6 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
*
* \remarks Requires cppunit.
*/
#ifdef CPP_UTILITIES_BOOST_PROCESS
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto status = execApp(args, stdout, stderr); \
if (status != expectedExitStatus) { \
CPPUNIT_FAIL(::CppUtilities::argsToString( \
"app exited with status ", status, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#else
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto status = execApp(args, stdout, stderr); \
@ -319,7 +298,6 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
"app exited with status ", exitStatus, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#endif
/*!
* \brief Asserts whether the specified \a string matches the specified \a regex.