Compare commits

..

1 Commits

Author SHA1 Message Date
Martchus b9c8f795d4 Add preset for building on Windows with MSVC 2023-03-05 12:27:06 +01:00
46 changed files with 435 additions and 1690 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,11 @@ 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)
list(APPEND SCRIPT_FILES scripts/wine.sh)
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 +116,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 21)
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 +153,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 +165,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)
@ -216,13 +195,6 @@ if (NOT ENABLE_THREAD_LOCAL)
PROPERTY COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_NO_THREAD_LOCAL)
endif ()
# configure use of platform-specific APIs for optimizing CopyHelper
option(USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER
"enables use of platform-specific APIs for optimizing CopyHelper" OFF)
if (USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER)
endif ()
# include modules to apply configuration
include(BasicConfig)
include(WindowsResources)

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,105 +187,53 @@
"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 Qt 6, static packages from vcpkg and Perl from MSYS2",
"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"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"},
"CMAKE_TOOLCHAIN_FILE": {"type": "FILEPATH", "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"},
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static"},
"CMAKE_PREFIX_PATH": {"type": "PATH", "value": "$env{QT_ROOT}"},
"CMAKE_MAKE_PROGRAM": {"type": "FILEPATH", "value": "$env{QT_TOOLS}/Ninja/ninja.exe"},
"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_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_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static"},
"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"},
"CMAKE_CXX_FLAGS_MINSIZEREL": {"type": "STRING", "value": "/MT /O1 /Ob1 /DNDEBUG"},
"CMAKE_CXX_FLAGS_RELWITHDEBINFO": {"type": "STRING", "value": "/MT /Zi /O2 /Ob1 /DNDEBUG"},
"VCPKG_TARGET_TRIPLET": {"type": "STRING", "value": "x64-windows-static"},
"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"},
"PERL_BIN": {"type": "PATH", "value": "$env{MSYS2_PATH}/usr/bin/perl.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"}
"Iconv_LIBRARY": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/lib/iconv.lib"},
"Iconv_INCLUDE_DIR": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/include"},
"Boost_INCLUDE_DIR": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/include"},
"Boost_USE_STATIC_RUNTIME": {"type": "BOOL", "value": "ON"},
"CPP_UNIT_LIB": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/lib/cppunit.lib"},
"CPP_UNIT_INCLUDE_DIR": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/include"},
"ZLIB_INCLUDE_DIR": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/include"},
"ZLIB_LIBRARY_DEBUG": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/lib/zlib.lib"},
"ZLIB_LIBRARY_RELEASE": {"type": "PATH", "value": "$env{VCPKG_PATH}/installed/x64-windows-static/lib/zlib.lib"}
}
},
{
"name": "win-x64-msvc-static-devel",
"inherits": ["win-x64-msvc-static", "devel"],
"inherits": ["devel", "win-x64-msvc-static"],
"displayName": "Combination of devel and win-x64-msvc-static",
"description": "See descriptions of devel and win-x64-msvc-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static-devel",
"cacheVariables": {
"CLANG_FORMAT_ENABLED": {"type": "BOOL", "value": "OFF"},
"CLANG_TIDY_ENABLED": {"type": "BOOL", "value": "OFF"},
"CMAKE_FORMAT_ENABLED": {"type": "BOOL", "value": "OFF"}
}
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static-devel"
},
{
"name": "win-x64-msvc-static-debug",
"inherits": ["debug", "win-x64-msvc-static-devel"],
"inherits": ["debug", "win-x64-msvc-static"],
"displayName": "Combination of debug and win-x64-msvc-static",
"description": "See descriptions of debug and win-x64-msvc-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static-debug",
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": {"type": "STRING", "value": ""},
"CMAKE_CXX_COMPILER_LAUNCHER": {"type": "STRING", "value": ""},
"CPP_UNIT_LIB": {"type": "FILEPATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static/debug/lib/cppunitd.lib"}
}
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static-debug"
}
],
"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"},
{"name": "win-x64-msvc-static-devel", "configurePreset": "win-x64-msvc-static-devel"},
{"name": "win-x64-msvc-static-debug", "configurePreset": "win-x64-msvc-static-debug"}
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"}
],
"testPresets": [
{

268
README.md
View File

@ -54,116 +54,83 @@ The following counts for `c++utilities` and my other libraries unless stated oth
removed in the next minor or patch release.
## Build instructions
These build instructions apply to `c++utilities` but also to my other projects using it.
### Requirements
#### Build-only dependencies
* 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
- g++ to compile for GNU/Linux and Windows
* CMake (at least 3.3.0)
* 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)
#### Runtime dependencies
* The `c++utilities` library itself only needs
* The c++utilities library itself only needs
* C++ standard library supporting C++17, tested with
- 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)
* My other projects have further dependencies such as Qt. Checkout the README of these
projects for further details.
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional)
* For dependencies of my other projects check the README.md of these projects.
### How to build
Generic example using Ninja:
Example using `make`:
```
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
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
# build and run tests (optional)
cmake --build "path/to/build/directory" --target check
# build and run tests measuring test coverage (optional, must be enabled via CLANG_SOURCE_BASED_COVERAGE_ENABLED)
cmake --build "path/to/build/directory" --target coverage
# build API documentation (optional)
cmake --build "path/to/build/directory" --target apidoc
# install binaries, headers and additional files
DESTDIR="/temporary/install/location" \
cmake --install "path/to/build/directory"
cd "path/to/build/directory"
cmake -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="/final/install/location" \
"path/to/projectdirectory"
make tidy # format source files (optional, must be enabled via CLANG_FORMAT_ENABLED)
make # build the binaries
make check # build and run tests (optional)
make coverage # build and run tests measuring test coverage (optional, must be enabled via CLANG_SOURCE_BASED_COVERAGE_ENABLED)
make apidoc # build API documentation (optional)
make DESTDIR="/temporary/install/location" install # install binaries, headers and additional files
```
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
* 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*
static libraries set `STATIC_LINKAGE=ON`. However, this will only affect applications. To force linking
statically when building shared libraries set `STATIC_LIBRARY_LINKAGE=ON`.
* 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.
* 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
search path.
* The make option ```-j``` can be used for concurrent compilation.
* ```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* static libraries set `STATIC_LINKAGE=ON`.
However, this will only affect applications. To force linking statically when building shared libraries set `STATIC_LIBRARY_LINKAGE=ON`.
* 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`. This is required when building for MacOS and Android at the time of
writing this documentation. Note that the Bash completion will 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.
* 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
in Doxygen version accessible via "Related Pages").
* The repository [PKGBUILDs](https://github.com/Martchus/PKGBUILDs) contains build scripts for GNU/Linux,
Android, Windows and MacOS X in form of Arch Linux packages using `ninja`. These scripts can be used as an
example also when building under/for other platforms.
* The repository [PKGBUILDs](https://github.com/Martchus/PKGBUILDs) contains build scripts for GNU/Linux, Android, Windows and
MacOS X in form of Arch Linux packages using `ninja`. These scripts can be used as an example also when building under/for other platforms.
#### Windows-specific notes
* To create application icons the tool `ffmpeg`/`avconv` is required.
* Windows builds are mainly conducted using mingw-w64/GCC so using them is recommended. Building with MSVC
should be possible as well but it is not as well tested.
* When using `BUILTIN_ICON_THEMES`, the icon theme still needs to be installed as if it was installed on a
GNU/Linux system. So simply grab e.g. the Arch Linux package `breeze-icons` and extract it somewhere. Do
*not* use the package from MSYS2 or what comes with builds from KDE's binary factory.
* Windows builds are only conducted using mingw-w64/GCC. Using MSVC has never been tested.
#### MacOS-specific notes
* To create application icons the tool `png2icns` is required.
* Building for MacOS X under GNU/Linux is possible using [osxcross](https://github.com/tpoechtrager/osxcross).
* MacOS X builds are not tested regularly but should generally work (maybe with minor tweaks necassary).
* There is a [Homebrew formula](https://gist.github.com/rakkesh/0b13b8fca5dd1d57d98537ef1dd2e0dd) to
build Tag Editor (without GUI).
* There are [MacPorts packages](https://www.macports.org/ports.php?by=name&substr=syncthingtray-devel)
to build Syncthing Tray.
* MacOS X builds are not tested regularly but should generally work (maybe with minor tweaks necassary)
* There is a [Homebrew formula](https://gist.github.com/rakkesh/0b13b8fca5dd1d57d98537ef1dd2e0dd) to build Tag Editor (without GUI)
* There are [MacPorts packages](https://www.macports.org/ports.php?by=name&substr=syncthingtray-devel) to build Syncthing Tray
#### Development builds
During development I find it useful to build all required projects (for instance c++utilities, qtutilities,
tagparser and tageditor) as one big project.
During development I find it useful to build all required projects (for instance c++utilities, qtutilities, 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 Syncthing Tray](https://github.com/Martchus/syncthingtray#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`.
For a debug build, use `-DCMAKE_BUILD_TYPE=Debug`.
#### CMake presets
There are some generic [presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) available
@ -171,156 +138,45 @@ 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.
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 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).
##### 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:
```
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
```
* `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`
* `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.
Note that the devel preset (and all presets inheriting from it) uses ccache which therefore needs to be
installed.
#### Arch Linux package
The repository [PKGBUILDs](https://github.com/Martchus/PKGBUILDs) contains files for building Arch Linux
packages of the latest release and the Git master.
The repository [PKGBUILDs](https://github.com/Martchus/PKGBUILDs) contains files for building Arch Linux packages of the latest release and
the Git master.
PKGBUILDs to cross compile for Android, Windows (using mingw-w64) and for MacOS X (using osxcross) are
included as well.
PKGBUILDs to cross compile for Android, Windows (using mingw-w64) and for MacOS X (using osxcross) are included as well.
#### RPM packages for openSUSE and Fedora
RPM \*.spec files can be found at [openSUSE Build Servide](https://build.opensuse.org/project/show/home:mkittler).
Packages are available for several architectures.
RPM \*.spec files can be found at [openSUSE Build Servide](https://build.opensuse.org/project/show/home:mkittler). Packages are available for
several architectures.
There is also a [sub project](https://build.opensuse.org/project/show/home:mkittler:vcs) containing the builds
from the Git master branch.
There is also a [sub project](https://build.opensuse.org/project/show/home:mkittler:vcs) containing the builds from the Git master branch.
#### Gentoo
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.")
@ -33,17 +33,8 @@ append_user_defined_additional_libraries()
# add target for building the application
if (ANDROID)
# create a shared library which can be loaded from the Java-side, needs to be a module target to avoid
# "QT_ANDROID_GENERATE_DEPLOYMENT_SETTINGS only works on Module targets" when using
# `qt_android_generate_deployment_settings`.
add_library(${META_TARGET_NAME} MODULE ${ALL_FILES})
# set suffix to avoid "Cannot find application binary in build dir /lib_arm64-v8a.so." when using
# `qt_android_add_apk_target`.
if (ANDROID_ABI)
set_target_properties(${META_TARGET_NAME} PROPERTIES SUFFIX "_${ANDROID_ABI}.so")
endif ()
set_target_properties(${META_TARGET_NAME} PROPERTIES QT_ANDROID_VERSION_NAME
"${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}")
# create a shared library which can be loaded from the Java-side
add_library(${META_TARGET_NAME} SHARED ${ALL_FILES})
else ()
add_executable(${META_TARGET_NAME} ${GUI_TYPE} ${ALL_FILES})
endif ()
@ -65,16 +56,12 @@ target_compile_options(
PRIVATE "${META_PRIVATE_COMPILE_OPTIONS}")
set_target_properties(
${META_TARGET_NAME}
PROPERTIES LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
PROPERTIES C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
LINK_SEARCH_END_STATIC ${STATIC_LINKAGE}
AUTOGEN_TARGET_DEPENDS "${AUTOGEN_DEPS}"
QT_DEFAULT_PLUGINS "${META_QT_DEFAULT_PLUGINS}")
if (NOT ANDROID)
set_target_properties(${META_TARGET_NAME} PROPERTIES C_VISIBILITY_PRESET hidden CXX_VISIBILITY_PRESET hidden)
# note: Android *.so files need CXX visibility set to default (see qtbase commit
# 29b17fa335388c9b93f70c29b2398cf2fee65785). Otherwise loading the app will fail with the error "dlsym failed: undefined
# symbol: main".
endif ()
if (NOT META_CXX_STANDARD STREQUAL "any")
set_target_properties(${META_TARGET_NAME} PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}")
endif ()
@ -110,20 +97,6 @@ if (GUI_TYPE STREQUAL "MACOSX_BUNDLE")
endif ()
endif ()
# create CLI-wrapper to be able to use CLI in Windows-termial without hacks
if (BUILD_CLI_WRAPPER)
# 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)
endif ()
# add install targets
if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
# add install target for binary
@ -143,9 +116,6 @@ if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}${SELECTED_LIB_SUFFIX}" COMPONENT binary)
else ()
install(TARGETS ${META_TARGET_NAME} RUNTIME DESTINATION bin COMPONENT binary)
if (CLI_WRAPPER_TARGET_NAME)
install(TARGETS ${CLI_WRAPPER_TARGET_NAME} RUNTIME DESTINATION bin COMPONENT binary)
endif ()
endif ()
if (NOT TARGET install-binary)

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)
@ -6,30 +6,23 @@ if (DEFINED TEMPLATE_FINDER_LOADED)
endif ()
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)
endfunction ()
function (find_template_file_full_name FILE_NAME PROJECT_VAR_NAME OUTPUT_VAR)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}")
function (find_template_file FILE_NAME PROJECT_VAR_NAME OUTPUT_VAR)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in")
# check own source directory
set(${OUTPUT_VAR}
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in"
PARENT_SCOPE)
message(STATUS "Using template for ${FILE_NAME} from own (${META_PROJECT_NAME}) source directory.")
elseif (EXISTS "${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}")
elseif (EXISTS "${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in")
# check sources of project
set(${OUTPUT_VAR}
"${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}"
"${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in"
PARENT_SCOPE)
message(STATUS "Using template for ${FILE_NAME} from ${PROJECT_VAR_NAME} source directory.")
elseif (EXISTS "${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}")
elseif (EXISTS "${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}.in")
# check installed version of project
set(${OUTPUT_VAR}
"${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}"
"${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}.in"
PARENT_SCOPE)
message(STATUS "Using template for ${FILE_NAME} from ${PROJECT_VAR_NAME} installation.")
else ()

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,16 +12,15 @@ 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 ()
# find rc template, define path of output rc file
include(TemplateFinder)
find_template_file("windows.rc" CPP_UTILITIES RC_TEMPLATE_FILE)
find_template_file("windows-cli-wrapper.rc" CPP_UTILITIES RC_CLI_TEMPLATE_FILE)
set(WINDOWS_RC_FILE_CFG "${CMAKE_CURRENT_BINARY_DIR}/resources/windows.rc.configured")
set(WINDOWS_RC_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/windows")
set(WINDOWS_CLI_RC_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/windows-cli-wrapper")
# create Windows icon from png with ffmpeg if available
unset(WINDOWS_ICON_RC_ENTRY)
@ -52,25 +51,10 @@ 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 ()
# add resource file to sources
# set windres as resource compiler
list(APPEND RES_FILES "${WINDOWS_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(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,83 +0,0 @@
#include <windows.h>
#include <cstdlib>
#include <cwchar>
#include <iostream>
#include <string_view>
#include <system_error>
/*!
* \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)
{
return value == key ? replacement : value;
}
/*!
* \brief Sets the console up and launches the "main" application.
*/
int main()
{
// 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.
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");
// determine the wrapper executable path
wchar_t pathBuffer[MAX_PATH];
if (!GetModuleFileNameW(nullptr, pathBuffer, MAX_PATH)) {
std::cerr << "Unable to determine wrapper executable path: " << std::error_code(GetLastError(), std::system_category()) << '\n';
return EXIT_FAILURE;
}
// replace "-cli.exe" in the wrapper executable's file name with just ".exe" to make up the main executable path
const auto path = std::wstring_view(pathBuffer);
const auto filenameStart = replace(path.rfind(L'\\'), std::wstring_view::npos, 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";
return EXIT_FAILURE;
}
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';
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;
}

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,38 +0,0 @@
# if defined(UNDER_CE)
# include <winbase.h>
# else
# include <windows.h>
# endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION @META_VERSION_MAJOR@,@META_VERSION_MINOR@,@META_VERSION_PATCH@,0
PRODUCTVERSION @META_VERSION_MAJOR@,@META_VERSION_MINOR@,@META_VERSION_PATCH@,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "@META_APP_AUTHOR@\0"
VALUE "FileDescription", "@META_APP_DESCRIPTION@ - CLI wrapper\0"
VALUE "FileVersion", "@META_APP_VERSION@\0"
VALUE "LegalCopyright", "by @META_APP_AUTHOR@\0"
VALUE "OriginalFilename", "$<TARGET_FILE_NAME:@TARGET_PREFIX@@META_PROJECT_NAME@@TARGET_SUFFIX@-cli>\0"
VALUE "ProductName", "@META_APP_NAME@\0"
VALUE "ProductVersion", "@META_APP_VERSION@\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0409, 1200
END
END
/* End of Version info */

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

@ -164,30 +164,30 @@ std::string_view formattedPhraseString(Phrases phrase)
using namespace std::string_view_literals;
switch (phrase) {
case Phrases::Error:
return "\033[1;31mError: \033[0m\033[1m"sv;
return "\e[1;31mError: \e[0m\e[1m"sv;
case Phrases::Warning:
return "\033[1;33mWarning: \033[0m\033[1m"sv;
return "\e[1;33mWarning: \e[0m\e[1m"sv;
case Phrases::PlainMessage:
return " \033[0m\033[1m"sv;
return " \e[0m\e[1m"sv;
case Phrases::SuccessMessage:
return "\033[1;32m==> \033[0m\033[1m"sv;
return "\e[1;32m==> \e[0m\e[1m"sv;
case Phrases::SubMessage:
return "\033[1;32m -> \033[0m\033[1m"sv;
return "\e[1;32m -> \e[0m\e[1m"sv;
case Phrases::ErrorMessage:
return "\033[1;31m==> ERROR: \033[0m\033[1m"sv;
return "\e[1;31m==> ERROR: \e[0m\e[1m"sv;
case Phrases::WarningMessage:
return "\033[1;33m==> WARNING: \033[0m\033[1m";
return "\e[1;33m==> WARNING: \e[0m\e[1m";
case Phrases::Info:
return "\033[1;34mInfo: \033[0m\033[1m"sv;
return "\e[1;34mInfo: \e[0m\e[1m"sv;
case Phrases::SubError:
return "\033[1;31m -> ERROR: \033[0m\033[1m"sv;
return "\e[1;31m -> ERROR: \e[0m\e[1m"sv;
case Phrases::SubWarning:
return "\033[1;33m -> WARNING: \033[0m\033[1m"sv;
return "\e[1;33m -> WARNING: \e[0m\e[1m"sv;
case Phrases::InfoMessage:
return "\033[1;37m==> \033[0m\033[1m"sv;
return "\e[1;37m==> \e[0m\e[1m"sv;
case Phrases::End:
case Phrases::EndFlush:
return "\033[0m\n";
return "\e[0m\n";
default:
return std::string_view{};
}

View File

@ -34,7 +34,7 @@ enum class Direction : char { Up = 'A', Down = 'B', Forward = 'C', Backward = 'D
inline void setStyle(std::ostream &stream, TextAttribute displayAttribute = TextAttribute::Reset)
{
if (enabled) {
stream << '\033' << '[' << static_cast<char>(displayAttribute) << 'm';
stream << '\e' << '[' << static_cast<char>(displayAttribute) << 'm';
}
}
@ -42,14 +42,14 @@ inline void setStyle(
std::ostream &stream, Color color, ColorContext context = ColorContext::Foreground, TextAttribute displayAttribute = TextAttribute::Reset)
{
if (enabled) {
stream << '\033' << '[' << static_cast<char>(displayAttribute) << ';' << static_cast<char>(context) << static_cast<char>(color) << 'm';
stream << '\e' << '[' << static_cast<char>(displayAttribute) << ';' << static_cast<char>(context) << static_cast<char>(color) << 'm';
}
}
inline void setStyle(std::ostream &stream, Color foregroundColor, Color backgroundColor, TextAttribute displayAttribute = TextAttribute::Reset)
{
if (enabled) {
stream << '\033' << '[' << static_cast<char>(displayAttribute) << ';' << static_cast<char>(ColorContext::Foreground)
stream << '\e' << '[' << static_cast<char>(displayAttribute) << ';' << static_cast<char>(ColorContext::Foreground)
<< static_cast<char>(foregroundColor) << ';' << static_cast<char>(ColorContext::Background) << static_cast<char>(backgroundColor)
<< 'm';
}
@ -58,42 +58,42 @@ inline void setStyle(std::ostream &stream, Color foregroundColor, Color backgrou
inline void resetStyle(std::ostream &stream)
{
if (enabled) {
stream << '\033' << '[' << static_cast<char>(TextAttribute::Reset) << 'm';
stream << '\e' << '[' << static_cast<char>(TextAttribute::Reset) << 'm';
}
}
inline void setCursor(std::ostream &stream, unsigned int row = 0, unsigned int col = 0)
{
if (enabled) {
stream << '\033' << '[' << row << ';' << col << 'H';
stream << '\e' << '[' << row << ';' << col << 'H';
}
}
inline void moveCursor(std::ostream &stream, unsigned int cells, Direction direction)
{
if (enabled) {
stream << '\033' << '[' << cells << static_cast<char>(direction);
stream << '\e' << '[' << cells << static_cast<char>(direction);
}
}
inline void saveCursor(std::ostream &stream)
{
if (enabled) {
stream << "\033[s";
stream << "\e[s";
}
}
inline void restoreCursor(std::ostream &stream)
{
if (enabled) {
stream << "\033[u";
stream << "\e[u";
}
}
inline void eraseDisplay(std::ostream &stream)
{
if (enabled) {
stream << "\033[2J";
stream << "\e[2J";
}
}

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

@ -2,30 +2,16 @@
#define IOUTILITIES_COPY_H
#include "./nativefilestream.h"
#if defined(CPP_UTILITIES_USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER) && defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) \
&& defined(PLATFORM_LINUX)
#define CPP_UTILITIES_USE_SEND_FILE
#include "../conversion/stringbuilder.h"
#endif
#ifdef CPP_UTILITIES_USE_SEND_FILE
#include <errno.h>
#include <sys/sendfile.h>
#endif
#include <functional>
#include <iostream>
#ifdef CPP_UTILITIES_USE_SEND_FILE
#include <algorithm>
#include <cstring>
#endif
namespace CppUtilities {
/*!
* \class CopyHelper
* \brief The CopyHelper class helps to copy bytes from one stream to another.
* \tparam Specifies the chunk/buffer size.
* \tparam Specifies the buffer size.
*/
template <std::size_t bufferSize> class CPP_UTILITIES_EXPORT CopyHelper {
public:
@ -69,8 +55,8 @@ template <std::size_t bufferSize> void CopyHelper<bufferSize>::copy(std::istream
* \brief Copies \a count bytes from \a input to \a output. The procedure might be aborted and
* progress updates will be reported.
*
* Before processing the next chunk \a isAborted is checked and the copying aborted if it returns true. Before processing the next chunk
* \a callback is invoked to report the current progress.
* Copying is aborted when \a isAborted returns true. The current progress is reported by calling
* the specified \a callback function.
*
* \remarks Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception
* when an IO error occurs.
@ -99,40 +85,10 @@ void CopyHelper<bufferSize>::callbackCopy(std::istream &input, std::ostream &out
* \remarks
* - Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception
* when an IO error occurs.
* - Possibly uses native APIs such as POSIX sendfile() to improve the speed.
* - Possibly uses native APIs such as POSIX sendfile() to improve the speed (not implemented yet).
*/
template <std::size_t bufferSize> void CopyHelper<bufferSize>::copy(NativeFileStream &input, NativeFileStream &output, std::uint64_t count)
{
#ifdef CPP_UTILITIES_USE_SEND_FILE
if (output.fileDescriptor() != -1 && input.fileDescriptor() != -1 && output.fileDescriptor() != input.fileDescriptor()) {
const auto inputTellg = output.tellg();
const auto inputTellp = output.tellp();
const auto outputTellg = output.tellg();
const auto outputTellp = output.tellp();
input.flush();
output.flush();
const auto totalBytes = static_cast<std::streamoff>(count);
while (count) {
const auto bytesCopied = ::sendfile64(output.fileDescriptor(), input.fileDescriptor(), nullptr, count);
if (bytesCopied < 0) {
if ((errno == EINVAL || errno == ENOSYS) && static_cast<std::uint64_t>(totalBytes) == count) {
// try again the unoptimized version, maybe the filesystem doesn't support sendfile
goto unoptimized_version;
}
throw std::ios_base::failure(argsToString("sendfile64() failed: ", std::strerror(errno)));
}
count -= static_cast<std::uint64_t>(bytesCopied);
}
input.sync();
output.sync();
output.seekg(outputTellg + totalBytes);
output.seekp(outputTellp + totalBytes);
input.seekg(inputTellg + totalBytes);
input.seekp(inputTellp + totalBytes);
return;
}
unoptimized_version:
#endif
copy(static_cast<std::istream &>(input), static_cast<std::ostream &>(output), count);
}
@ -140,53 +96,17 @@ unoptimized_version:
* \brief Copies \a count bytes from \a input to \a output. The procedure might be aborted and
* progress updates will be reported.
*
* Before processing the next chunk \a isAborted is checked and the copying aborted if it returns true. Before processing the next chunk
* \a callback is invoked to report the current progress.
* Copying is aborted when \a isAborted returns true. The current progress is reported by calling
* the specified \a callback function.
*
* - Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception
* when an IO error occurs.
* - Possibly uses native APIs such as POSIX sendfile() to improve the speed.
* - Possibly uses native APIs such as POSIX sendfile() to improve the speed (not implemented yet).
*/
template <std::size_t bufferSize>
void CopyHelper<bufferSize>::callbackCopy(NativeFileStream &input, NativeFileStream &output, std::uint64_t count,
const std::function<bool(void)> &isAborted, const std::function<void(double)> &callback)
{
#ifdef CPP_UTILITIES_USE_SEND_FILE
if (output.fileDescriptor() != -1 && input.fileDescriptor() != -1 && output.fileDescriptor() != input.fileDescriptor()) {
const auto inputTellg = output.tellg();
const auto inputTellp = output.tellp();
const auto outputTellg = output.tellg();
const auto outputTellp = output.tellp();
input.flush();
output.flush();
const auto totalBytes = static_cast<std::streamoff>(count);
while (count) {
const auto bytesToCopy = static_cast<std::size_t>(std::min(count, static_cast<std::uint64_t>(bufferSize)));
const auto bytesCopied = ::sendfile64(output.fileDescriptor(), input.fileDescriptor(), nullptr, bytesToCopy);
if (bytesCopied < 0) {
if ((errno == EINVAL || errno == ENOSYS) && static_cast<std::uint64_t>(totalBytes) == count) {
// try again the unoptimized version, maybe the filesystem doesn't support sendfile
goto unoptimized_version;
}
throw std::ios_base::failure(argsToString("sendfile64() failed: ", std::strerror(errno)));
}
count -= static_cast<std::uint64_t>(bytesCopied);
if (isAborted()) {
return;
}
callback(static_cast<double>(totalBytes - static_cast<std::streamoff>(count)) / static_cast<double>(totalBytes));
}
input.sync();
output.sync();
output.seekg(outputTellg + totalBytes);
output.seekp(outputTellp + totalBytes);
input.seekg(inputTellg + totalBytes);
input.seekp(inputTellp + totalBytes);
callback(1.0);
return;
}
unoptimized_version:
#endif
callbackCopy(static_cast<std::istream &>(input), static_cast<std::ostream &>(output), count, isAborted, callback);
}

View File

@ -10,9 +10,6 @@
#include <list>
#include <string>
#include <string_view>
#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
#include <filesystem>
#endif
#ifdef PLATFORM_WINDOWS
#define PATH_SEP_CHAR '\\'
@ -36,32 +33,6 @@ CPP_UTILITIES_EXPORT std::string_view directory(std::string_view path);
#endif
CPP_UTILITIES_EXPORT void removeInvalidChars(std::string &fileName);
/// \brief The native type used by std::filesystem:path.
/// \remarks The current implementation requires this to be always wchar_t on Windows and char otherwise.
using PathValueType =
#ifdef PLATFORM_WINDOWS
wchar_t
#else
char
#endif
;
#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
static_assert(std::is_same_v<typename std::filesystem::path::value_type, PathValueType>);
#endif
/// \brief The string type used to store paths in the native encoding.
/// \remarks This type is used to store paths when interfacing with native APIs.
using NativePathString = std::basic_string<PathValueType>;
/// \brief The string view type used to pass paths in the native encoding.
/// \remarks This type is used to pass paths when interfacing with native APIs.
using NativePathStringView = std::basic_string_view<PathValueType>;
/// \brief The string type used to store paths UTF-8 encoded.
/// \remarks This type is used to store paths everywhere except when interfacing directly with native APIs.
using PathString = std::string;
/// \brief The string view type used to pass paths UTF-8 encoded.
/// \remarks This type is used to pass paths everywhere except when interfacing directly with native APIs.
using PathStringView = std::string_view;
/*!
* \brief Returns \a path in the platform's native encoding.
* \remarks
@ -75,11 +46,11 @@ using PathStringView = std::string_view;
*/
inline
#ifdef PLATFORM_WINDOWS
NativePathString
std::wstring
#else
NativePathStringView
std::string_view
#endif
makeNativePath(PathStringView path)
makeNativePath(std::string_view path)
{
#ifdef PLATFORM_WINDOWS
auto ec = std::error_code();
@ -89,26 +60,6 @@ inline
#endif
}
/*!
* \brief Returns \a path as UTF-8 string or string view.
* \sa This is the opposite of makeNativePath() so checkout remarks of that function for details.
*/
inline
#ifdef PLATFORM_WINDOWS
PathString
#else
PathStringView
#endif
extractNativePath(NativePathStringView path)
{
#ifdef PLATFORM_WINDOWS
auto res = convertUtf16LEToUtf8(reinterpret_cast<const char *>(path.data()), path.size() * 2);
return std::string(res.first.get(), res.second);
#else
return path;
#endif
}
} // namespace CppUtilities
#endif // IOUTILITIES_PATHHELPER_H

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);
}
/*!
@ -816,30 +816,30 @@ void ArgumentParserTests::testHelp()
// parse args and assert output
const char *const argv[] = { "app", "-h" };
{
const OutputCheck c("\033[1m" APP_NAME ", version " APP_VERSION "\n"
const OutputCheck c("\e[1m" APP_NAME ", version " APP_VERSION "\n"
"\n"
"\033[0m" APP_DESCRIPTION "\n"
"\e[0m" APP_DESCRIPTION "\n"
"\n"
"Available operations:\n"
"\033[1mverbose, -v\033[0m\n"
"\e[1mverbose, -v\e[0m\n"
" be verbose\n"
" example: actually not an operation\n"
"\n"
"Available top-level options:\n"
"\033[1m--files, -f\033[0m\n"
"\e[1m--files, -f\e[0m\n"
" specifies the path of the file(s) to be opened\n"
" \033[1m--sub\033[0m\n"
" \e[1m--sub\e[0m\n"
" sub arg\n"
" particularities: mandatory if parent argument is present\n"
" \033[1m--nested-sub\033[0m [value1] [value2] ...\n"
" \e[1m--nested-sub\e[0m [value1] [value2] ...\n"
" nested sub arg\n"
" example: sub arg example\n"
"\n"
"\033[1m--env\033[0m [file] [value 2]\n"
"\e[1m--env\e[0m [file] [value 2]\n"
" env\n"
" default environment variable: FILES\n"
"\n"
"\033[1m--no-color\033[0m\n"
"\e[1m--no-color\e[0m\n"
" disables formatted/colorized output\n"
" default environment variable: ENABLE_ESCAPE_CODES\n"
"\n"
@ -982,9 +982,7 @@ void ArgumentParserTests::testValueConversion()
occurrence.convertValues<int>();
CPPUNIT_FAIL("Expected exception");
} catch (const ParseError &failure) {
TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of top-level value",
"Conversion of top-level value \"foo\" to type \".*\" failed: The character \"f\" is no valid digit."s, std::regex::extended,
std::string(failure.what()));
TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of top-level value", "Conversion of top-level value \"foo\" to type \".*\" failed: The character \"f\" is no valid digit."s, std::regex::extended, std::string(failure.what()));
}
occurrence.path = { &arg };
try {
@ -997,8 +995,6 @@ void ArgumentParserTests::testValueConversion()
occurrence.convertValues<int>();
CPPUNIT_FAIL("Expected exception");
} catch (const ParseError &failure) {
TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of argument value",
"Conversion of value \"foo\" \\(for argument --test\\) to type \".*\" failed: The character \"f\" is no valid digit."s,
std::regex::extended, std::string(failure.what()));
TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of argument value", "Conversion of value \"foo\" \\(for argument --test\\) to type \".*\" failed: The character \"f\" is no valid digit."s, std::regex::extended, std::string(failure.what()));
}
}

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

@ -67,7 +67,6 @@ class IoTests : public TestFixture {
CPPUNIT_TEST(testIniFile);
CPPUNIT_TEST(testAdvancedIniFile);
CPPUNIT_TEST(testCopy);
CPPUNIT_TEST(testCopyWithNativeFileStream);
CPPUNIT_TEST(testReadFile);
CPPUNIT_TEST(testWriteFile);
CPPUNIT_TEST(testAnsiEscapeCodes);
@ -88,7 +87,6 @@ public:
void testIniFile();
void testAdvancedIniFile();
void testCopy();
void testCopyWithNativeFileStream();
void testReadFile();
void testWriteFile();
void testAnsiEscapeCodes();
@ -501,97 +499,23 @@ void IoTests::testAdvancedIniFile()
void IoTests::testCopy()
{
// prepare streams
auto testFile = std::fstream();
fstream testFile;
testFile.exceptions(ios_base::failbit | ios_base::badbit);
testFile.open(testFilePath("some_data"), ios_base::in | ios_base::binary);
auto outputStream = std::stringstream(ios_base::in | ios_base::out | ios_base::binary);
stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary);
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
// copy
auto copyHelper = CopyHelper<13>();
CopyHelper<13> copyHelper;
copyHelper.copy(testFile, outputStream, 50);
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(0), outputStream.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellp());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellp());
// verify
// test
testFile.seekg(0);
for (auto i = 0; i < 50; ++i) {
CPPUNIT_ASSERT_EQUAL(testFile.get(), outputStream.get());
CPPUNIT_ASSERT(testFile.get() == outputStream.get());
}
}
/*!
* \brief Tests CopyHelper in combination with NativeFileStream.
*/
void IoTests::testCopyWithNativeFileStream()
{
// prepare streams
auto testFile = NativeFileStream();
testFile.exceptions(ios_base::failbit | ios_base::badbit);
testFile.open(testFilePath("some_data"), ios_base::in | ios_base::binary);
auto outputPath = workingCopyPath("copied_data", WorkingCopyMode::Cleanup);
auto outputStream = NativeFileStream();
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
outputStream.open(outputPath, ios_base::out | ios_base::binary);
// copy
auto copyHelper = CopyHelper<13>();
copyHelper.copy(testFile, outputStream, 50);
CPPUNIT_ASSERT(outputStream.is_open());
CPPUNIT_ASSERT(testFile.is_open());
// test seek positions (the expected values are from a run with normal std::fstream)
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellp());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellp());
// add a few more bytes to output stream (to be sure it is still usable) and re-open for reading
const auto aFewMoreBytes = "a few more bytes"sv;
outputStream << aFewMoreBytes;
outputStream.close();
outputStream.open(outputPath, ios_base::in | ios_base::binary);
// verify
testFile.seekg(0);
for (auto i = 0; i < 50; ++i) {
CPPUNIT_ASSERT_EQUAL(testFile.get(), outputStream.get());
}
auto tail = std::string(aFewMoreBytes.size(), '0');
outputStream.read(tail.data(), static_cast<std::streamsize>(tail.size()));
CPPUNIT_ASSERT_EQUAL(aFewMoreBytes, std::string_view(tail.data(), tail.size()));
// repeat with callback version
auto percentage = 0.0;
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);
// verify again
CPPUNIT_ASSERT(outputStream.is_open());
CPPUNIT_ASSERT(testFile.is_open());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellp());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellp());
outputStream << aFewMoreBytes;
outputStream.close();
outputStream.open(outputPath, ios_base::in | ios_base::binary);
testFile.seekg(0);
for (auto i = 0; i < 50; ++i) {
CPPUNIT_ASSERT_EQUAL(testFile.get(), outputStream.get());
}
tail.assign(aFewMoreBytes.size(), '0');
outputStream.read(tail.data(), static_cast<std::streamsize>(tail.size()));
CPPUNIT_ASSERT_EQUAL(aFewMoreBytes, std::string_view(tail.data(), tail.size()));
}
/*!
* \brief Tests readFile().
*/
@ -650,15 +574,16 @@ 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";
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"
"\033[1;31m==> ERROR: \033[0m\033[1mArch-style error\033[0m\n"
"\033[1;33m==> WARNING: \033[0m\033[1mArch-style warning\033[0m\n"
" \033[0m\033[1mArch-style message\033[0m\n"
"\033[1;32m==> \033[0m\033[1mArch-style success\033[0m\n"
"\033[1;32m -> \033[0m\033[1mArch-style sub-message\033[0m\n"
"\033[5;34;41mblue, blinking text on red background\033[0m\n"s,
fstream("/tmp/test.txt", ios_base::out | ios_base::trunc) << ss1.str();
CPPUNIT_ASSERT_EQUAL("\e[1;31mError: \e[0m\e[1msome error\e[0m\n"
"\e[1;33mWarning: \e[0m\e[1msome warning\e[0m\n"
"\e[1;34mInfo: \e[0m\e[1msome info\e[0m\n"
"\e[1;31m==> ERROR: \e[0m\e[1mArch-style error\e[0m\n"
"\e[1;33m==> WARNING: \e[0m\e[1mArch-style warning\e[0m\n"
" \e[0m\e[1mArch-style message\e[0m\n"
"\e[1;32m==> \e[0m\e[1mArch-style success\e[0m\n"
"\e[1;32m -> \e[0m\e[1mArch-style sub-message\e[0m\n"
"\e[5;34;41mblue, blinking text on red background\e[0m\n"s,
ss1.str());
stringstream ss2;

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
@ -191,8 +178,7 @@ TestApplication::TestApplication(int argc, const char *const *argv)
}
// -> find source directory
if (auto testFilePathFromSrcDirRef = readTestfilePathFromSrcRef(); !testFilePathFromSrcDirRef.empty()) {
m_testFilesPaths.insert(m_testFilesPaths.end(), std::make_move_iterator(testFilePathFromSrcDirRef.begin()),
std::make_move_iterator(testFilePathFromSrcDirRef.end()));
m_testFilesPaths.insert(m_testFilesPaths.end(), std::make_move_iterator(testFilePathFromSrcDirRef.begin()), std::make_move_iterator(testFilePathFromSrcDirRef.end()));
}
// -> try testfiles directory in working directory
m_testFilesPaths.emplace_back("./testfiles/");
@ -404,16 +390,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 +410,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 +477,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 +488,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 +496,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 +511,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 +571,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 +586,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.
@ -718,6 +646,7 @@ std::vector<std::string> TestApplication::readTestfilePathFromSrcRef()
<< "The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
<< Phrases::End << "Referenced source directory: " << testfilesPath << endl;
}
}
return res;

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.
@ -288,38 +277,13 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
*
* \remarks Requires cppunit.
*/
#define TESTUTILS_ASSERT_EXEC(args) TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, 0)
/*!
* \brief Asserts the execution of the application with the specified CLI \a args and the specified \a expectedExitStatus.
*
* The application is executed via TestApplication::execApp(). Output is stored in the std::string variables stdout
* and stderr.
*
* \remarks Requires cppunit.
*/
#ifdef CPP_UTILITIES_BOOST_PROCESS
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
#define TESTUTILS_ASSERT_EXEC(args) \
{ \
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)); \
const auto returnCode = execApp(args, stdout, stderr); \
if (returnCode != 0) { \
CPPUNIT_FAIL(::CppUtilities::argsToString("app failed with return code ", returnCode, "\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#else
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto status = execApp(args, stdout, stderr); \
if (!WIFEXITED(status)) { \
CPPUNIT_FAIL(::CppUtilities::argsToString("app did not terminate normally\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
if (const auto exitStatus = WEXITSTATUS(status); exitStatus != expectedExitStatus) { \
CPPUNIT_FAIL(::CppUtilities::argsToString( \
"app exited with status ", exitStatus, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#endif
/*!
* \brief Asserts whether the specified \a string matches the specified \a regex.