Compare commits
161 Commits
optimize-b
...
master
Author | SHA1 | Date |
---|---|---|
Martchus | 13dfb81ff2 | |
Martchus | 5fe20bc406 | |
Martchus | 054612f753 | |
Martchus | 14e9561e16 | |
Martchus | beee01c147 | |
Martchus | 9bb224c2ba | |
Martchus | d2928517c5 | |
Martchus | 0c652a774e | |
Martchus | 9c687bd723 | |
Martchus | 65ffed8151 | |
Martchus | 957c044e63 | |
Martchus | a4c18017b7 | |
Martchus | 73a837962d | |
Martchus | 8502d1bc2a | |
Martchus | afc3413e9c | |
Martchus | ae908283a0 | |
Martchus | d31092b7d9 | |
Martchus | dfbf300c65 | |
Martchus | 909346c199 | |
Martchus | c9cd44ceee | |
Martchus | a43affa81a | |
Martchus | d8e144d312 | |
Martchus | a337452179 | |
Martchus | 6cb0e63921 | |
Martchus | a4625b8e34 | |
Martchus | a4be8a56d1 | |
Martchus | ce31de2c6f | |
Martchus | bc00bdcdc9 | |
Martchus | 57579f0164 | |
Martchus | a7fdc1af1b | |
Martchus | 27043d2be0 | |
Martchus | b526d79eaf | |
Martchus | 995c315377 | |
Martchus | d08794b11d | |
Martchus | 1a0c4fbce0 | |
Martchus | c25a3c9c9a | |
Martchus | c9dea06cfe | |
Martchus | ad686a1be7 | |
Martchus | 85c76708c9 | |
Martchus | c17c8e7815 | |
Martchus | f97320816a | |
Martchus | 6f924da4f0 | |
Martchus | b3b7166812 | |
Martchus | 8bffc93316 | |
Martchus | 456bbfc54e | |
Martchus | b0be8817ad | |
Martchus | d8605b50b0 | |
Martchus | 1264a7e9c5 | |
Martchus | b8dff49c01 | |
Martchus | dd0ea1d348 | |
Martchus | 21f32d318b | |
Martchus | 8d460380f1 | |
Martchus | fbb1d73779 | |
Martchus | 35f56d486c | |
Martchus | 9d8f897972 | |
Martchus | 55146db7e1 | |
Martchus | fdfe8f154c | |
Martchus | b1f89d78c3 | |
Martchus | 3649782c8c | |
Martchus | 905f81c8b7 | |
Martchus | 7bed9cd38c | |
Martchus | 11de58141b | |
Martchus | a425363eac | |
Martchus | 8fb7de6fe0 | |
Martchus | a1bed55eda | |
Martchus | 05570c5c71 | |
Martchus | cc6576c417 | |
Martchus | ac35a5fad1 | |
Martchus | 762131acf9 | |
Martchus | cded82a00b | |
Martchus | a2f9748c1a | |
Martchus | 938e441336 | |
Martchus | b2d4d0be01 | |
Martchus | ff33454bf1 | |
Martchus | aa298772c9 | |
Martchus | 7dff72d0bd | |
Martchus | 938da48da0 | |
Martchus | c71f835ad0 | |
Martchus | 053ac7e1ad | |
Martchus | 035a448da0 | |
Martchus | 8d28ab70b3 | |
Martchus | 132d25fbb1 | |
Martchus | 0349037711 | |
Martchus | af200403de | |
Martchus | 18d92fee40 | |
Martchus | 3bca5c224d | |
Martchus | 6660ff7eca | |
Martchus | 2137568ad8 | |
Martchus | 6a8431da0a | |
Martchus | c4024ce00e | |
Martchus | 7bcc66be0d | |
Martchus | 5ebbd0eb3f | |
Martchus | 04682d4601 | |
Martchus | 6826546196 | |
Martchus | 08a1c5c2eb | |
Martchus | 9191117120 | |
Martchus | 30cefc2fd3 | |
Martchus | 38541f4c60 | |
Martchus | 831c083e5f | |
Martchus | dd95310c73 | |
Martchus | fc651c71ff | |
Martchus | 3bec473775 | |
Martchus | c111d9f374 | |
Martchus | 0057e49a0d | |
Martchus | cfe67a1078 | |
Martchus | 66b6fff0f2 | |
Martchus | 5bfce4235e | |
Martchus | 5d5ccb7729 | |
Martchus | b9097a3750 | |
Martchus | a19ee41a39 | |
Martchus | 0341316b4b | |
Martchus | 0d0685d4c7 | |
Martchus | c6396f92fc | |
Martchus | c60584e122 | |
Martchus | f3cb406ebe | |
Martchus | caace3ac63 | |
Martchus | 5e325d0b92 | |
Martchus | 5425488421 | |
Martchus | cb5ca77658 | |
Martchus | 93a632e831 | |
Martchus | ad39cbd604 | |
Martchus | e8b4279062 | |
Martchus | a55a3604bd | |
Martchus | da91e54ec7 | |
Martchus | 83025c17a1 | |
Martchus | 2467c4f815 | |
Martchus | 10a7e4d814 | |
Martchus | 59896da087 | |
Andrew J. Hesford | c9131ce6e7 | |
Martchus | 4316dfd846 | |
Martchus | 873cf513c3 | |
Martchus | 48aa1f54e0 | |
Martchus | b426443aaf | |
Martchus | 2a123df86d | |
Martchus | bcd5816d23 | |
Martchus | d94e49620d | |
Martchus | c3470d0378 | |
Martchus | 9bd7dbc41c | |
Martchus | 9b1a7b3bf4 | |
Martchus | ec891b48f6 | |
Martchus | 7a5a02976a | |
Martchus | 6e28bec4c5 | |
Martchus | 65b86d71f6 | |
Martchus | ab298b200f | |
Martchus | 2a9949ce77 | |
Martchus | e2593cea2a | |
Martchus | 6ec77a1d1c | |
Martchus | 283c416a59 | |
Martchus | 4aea7f5a57 | |
Martchus | 169756fce4 | |
Martchus | 30819d9e85 | |
Martchus | 78d57902a7 | |
Martchus | 2b6f26895d | |
Martchus | a703813b4c | |
Martchus | 07e9546855 | |
Martchus | 147d36a578 | |
Martchus | 31f369d051 | |
Martchus | 66621d757b | |
Martchus | 3b2615fa62 | |
Martchus | b3fd365502 | |
Martchus | 8588c17df3 |
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.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)
|
||||
set(SCRIPT_FILES)
|
||||
if (MINGW)
|
||||
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in)
|
||||
list(APPEND SCRIPT_FILES scripts/wine.sh)
|
||||
if (WIN32)
|
||||
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in cmake/templates/windows-cli-wrapper.rc.in
|
||||
cmake/templates/cli-wrapper.cpp)
|
||||
endif ()
|
||||
set(EXCLUDED_FILES cmake/templates/cli-wrapper.cpp)
|
||||
|
||||
set(DOC_FILES README.md doc/buildvariables.md doc/testapplication.md)
|
||||
set(EXTRA_FILES tests/calculateoverallcoverage.awk coding-style.clang-format)
|
||||
|
@ -116,14 +116,15 @@ 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 20)
|
||||
set(META_VERSION_PATCH 1)
|
||||
set(META_VERSION_MINOR 24)
|
||||
set(META_VERSION_PATCH 9)
|
||||
|
||||
# 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
|
||||
|
@ -153,7 +154,7 @@ if (USE_NATIVE_FILE_BUFFER)
|
|||
endforeach ()
|
||||
else ()
|
||||
message(STATUS "Using boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_sink> for NativeFileStream")
|
||||
use_package(TARGET_NAME Boost::iostreams PACKAGE_NAME Boost PACKAGE_ARGS "REQUIRED;COMPONENTS;iostreams")
|
||||
list(APPEND REQUIRED_BOOST_COMPONENTS iostreams)
|
||||
foreach (NATIVE_FILE_STREAM_IMPL_FILE ${NATIVE_FILE_STREAM_IMPL_FILES})
|
||||
set_property(
|
||||
SOURCE ${NATIVE_FILE_STREAM_IMPL_FILE}
|
||||
|
@ -165,6 +166,26 @@ 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)
|
||||
|
@ -195,6 +216,13 @@ 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)
|
||||
|
|
|
@ -21,6 +21,18 @@
|
|||
"WEBVIEW_PROVIDER": {"type": "STRING", "value": "none"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libc++",
|
||||
"inherits": "default",
|
||||
"displayName": "Use clang++ and libc++",
|
||||
"description": "Enforces use of clang++ and libc++ even when it is not the system default",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-no-webview",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": {"type": "STRING", "value": "clang"},
|
||||
"CMAKE_CXX_COMPILER": {"type": "STRING", "value": "clang++"},
|
||||
"CMAKE_CXX_FLAGS": {"type": "STRING", "value": "$env{CXXFLAGS} -stdlib=libc++"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "no-kde",
|
||||
"inherits": "default",
|
||||
|
@ -40,6 +52,7 @@
|
|||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-qt6",
|
||||
"cacheVariables": {
|
||||
"QT_PACKAGE_PREFIX": {"type": "STRING", "value": "Qt6"},
|
||||
"KF_PACKAGE_PREFIX": {"type": "STRING", "value": "KF6"},
|
||||
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"}
|
||||
}
|
||||
},
|
||||
|
@ -68,6 +81,16 @@
|
|||
"CONFIGURATION_TARGET_SUFFIX": {"type": "STRING", "value": "devel"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"],
|
||||
|
@ -76,9 +99,30 @@
|
|||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-qt6",
|
||||
"cacheVariables": {
|
||||
"QT_PACKAGE_PREFIX": {"type": "STRING", "value": "Qt6"},
|
||||
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"}
|
||||
"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"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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-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",
|
||||
|
@ -90,8 +134,70 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"name": "arch-x86_64-w64-mingw32",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -99,20 +205,28 @@
|
|||
"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",
|
||||
|
@ -121,9 +235,19 @@
|
|||
"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"}
|
||||
"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-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"],
|
||||
|
@ -131,6 +255,13 @@
|
|||
"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"],
|
||||
|
@ -138,6 +269,13 @@
|
|||
"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"],
|
||||
|
@ -145,6 +283,34 @@
|
|||
"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"],
|
||||
|
@ -169,10 +335,14 @@
|
|||
"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"}
|
||||
"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"}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -181,21 +351,108 @@
|
|||
"displayName": "Combination of devel and arch-static-compat",
|
||||
"description": "See descriptions of devel and arch-static-compat",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-static-compat-devel"
|
||||
},
|
||||
{
|
||||
"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)",
|
||||
"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_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"},
|
||||
"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"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-x64-msvc-static-devel",
|
||||
"inherits": ["win-x64-msvc-static", "devel"],
|
||||
"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"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-x64-msvc-static-debug",
|
||||
"inherits": ["debug", "win-x64-msvc-static-devel"],
|
||||
"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"}
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{"name": "default", "configurePreset": "default"},
|
||||
{"name": "libc++", "configurePreset": "libc++"},
|
||||
{"name": "qt6", "configurePreset": "qt6"},
|
||||
{"name": "devel", "configurePreset": "devel"},
|
||||
{"name": "devel-libc++", "configurePreset": "devel-libc++"},
|
||||
{"name": "devel-qt6", "configurePreset": "devel-qt6"},
|
||||
{"name": "devel-unity", "configurePreset": "devel-unity"},
|
||||
{"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": "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"}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
|
|
268
README.md
268
README.md
|
@ -54,83 +54,116 @@ 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
|
||||
- clang++ to compile for GNU/Linux and Android
|
||||
- g++ to compile for GNU/Linux and Windows
|
||||
* CMake (at least 3.3.0)
|
||||
- clang++ to compile for GNU/Linux and Android
|
||||
* CMake (at least 3.17.0) and Ninja or GNU Make
|
||||
* cppunit for unit tests (optional)
|
||||
* Doxygen for API documentation (optional)
|
||||
* Graphviz for diagrams in the API documentation (optional)
|
||||
* clang-format for tidying (optional)
|
||||
* clang-format and cmake-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)
|
||||
* For dependencies of my other projects check the README.md of these projects.
|
||||
* 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.
|
||||
|
||||
### How to build
|
||||
Example using `make`:
|
||||
Generic example using Ninja:
|
||||
```
|
||||
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
|
||||
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"
|
||||
```
|
||||
|
||||
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
|
||||
* 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.
|
||||
* 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.
|
||||
* 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 only conducted using mingw-w64/GCC. Using MSVC has never been tested.
|
||||
* 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.
|
||||
|
||||
#### 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).
|
||||
[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.
|
||||
|
||||
For a debug build, use `-DCMAKE_BUILD_TYPE=Debug`.
|
||||
For a debug build, use `-DCMAKE_BUILD_TYPE=Debug`. To tweak various settings (e.g. warnings) for development,
|
||||
use `-DENABLE_DEVEL_DEFAULTS=ON`.
|
||||
|
||||
#### CMake presets
|
||||
There are some generic [presets](https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html) available
|
||||
|
@ -138,45 +171,156 @@ 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`. Here is an example for creating a build with the `arch-static-compat-devel` preset and invoking
|
||||
tests:
|
||||
`BUILD_DIR`.
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
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.
|
||||
|
||||
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`.
|
||||
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
|
||||
```
|
||||
|
||||
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 which works with the "subdirs" projects
|
||||
mentioned in the previous section as well.
|
||||
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.
|
||||
|
||||
Note that the devel preset (and all presets inheriting from it) uses ccache which therefore needs to be
|
||||
installed.
|
||||
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.
|
||||
|
||||
#### 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-2023 Marius Kittler
|
||||
Copyright © 2015-2024 Marius Kittler
|
||||
|
||||
All code is licensed under [GPL-2-or-later](LICENSE).
|
||||
|
|
|
@ -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 an denotation is unknown
|
||||
// iterate through all argument denotations; loop might exit earlier when a 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,6 +361,9 @@ 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;
|
||||
|
@ -1320,6 +1323,25 @@ 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
|
||||
|
@ -1505,23 +1527,13 @@ 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
|
||||
string actualDir, actualFile;
|
||||
bool haveFileOrDirCompletions = false;
|
||||
auto actualDir = std::string(), actualFile = std::string();
|
||||
auto haveFileOrDirCompletions = false;
|
||||
if (argc && currentWordIndex == completionInfo.lastSpecifiedArgIndex && 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, "\\\\", "\\");
|
||||
// the "opening" might contain escaped characters which need to be unescaped first
|
||||
const auto unescapedOpening = unescape(opening);
|
||||
// determine the "directory" part
|
||||
string dir = directory(unescapedOpening);
|
||||
auto dir = directory(unescapedOpening);
|
||||
if (dir.empty()) {
|
||||
actualDir = ".";
|
||||
} else {
|
||||
|
@ -1534,7 +1546,7 @@ void ArgumentParser::printBashCompletion(int argc, const char *const *argv, unsi
|
|||
actualDir = std::move(dir);
|
||||
}
|
||||
// determine the "file" part
|
||||
string file = fileName(unescapedOpening);
|
||||
auto file = fileName(unescapedOpening);
|
||||
if (file[0] == '\"' || file[0] == '\'') {
|
||||
file.erase(0, 1);
|
||||
}
|
||||
|
@ -1787,7 +1799,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));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -1798,7 +1810,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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#ifdef PLATFORM_WINDOWS
|
||||
#include <cstring>
|
||||
#include <io.h>
|
||||
#include <tchar.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/ioctl.h>
|
||||
|
@ -90,6 +91,19 @@ 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.
|
||||
|
@ -119,13 +133,11 @@ bool handleVirtualTerminalProcessing()
|
|||
if (enableVirtualTerminalProcessing(STD_OUTPUT_HANDLE) && enableVirtualTerminalProcessing(STD_ERROR_HANDLE)) {
|
||||
return true;
|
||||
}
|
||||
// disable use on ANSI escape codes otherwise if it makes sense
|
||||
const char *const msyscon = std::getenv("MSYSCON");
|
||||
if (msyscon && std::strstr(msyscon, "mintty")) {
|
||||
// disable use of ANSI escape codes otherwise if it makes sense
|
||||
if (isMintty()) {
|
||||
return false; // no need to disable escape codes if it is just mintty
|
||||
}
|
||||
const char *const term = std::getenv("TERM");
|
||||
if (term && std::strstr(term, "xterm")) {
|
||||
if (const char *const term = std::getenv("TERM"); term && std::strstr(term, "xterm")) {
|
||||
return false; // no need to disable escape codes if it is some xterm-like terminal
|
||||
}
|
||||
return EscapeCodes::enabled = false;
|
||||
|
@ -141,20 +153,26 @@ 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 sets its output code page to UTF-8.
|
||||
* \brief Ensure the process has a console attached and properly setup.
|
||||
* \remarks
|
||||
* - 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`.
|
||||
* - 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.
|
||||
* \sa
|
||||
* - https://docs.microsoft.com/en-us/windows/console/AttachConsole
|
||||
* - https://docs.microsoft.com/en-us/windows/console/AllocConsole
|
||||
|
@ -163,42 +181,101 @@ 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
|
||||
const auto consoleEnabled = isEnvVariableSet("ENABLE_CONSOLE");
|
||||
if ((!consoleEnabled.has_value() || consoleEnabled.value()) && (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole())) {
|
||||
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
|
||||
// redirect stdout
|
||||
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);
|
||||
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);
|
||||
}
|
||||
// redirect stdin
|
||||
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);
|
||||
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);
|
||||
}
|
||||
// redirect stderr
|
||||
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);
|
||||
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);
|
||||
}
|
||||
// sync
|
||||
ios::sync_with_stdio(true);
|
||||
#endif
|
||||
// ensure the console prompt is shown again when app terminates
|
||||
atexit(stopConsole);
|
||||
std::atexit(stopConsole);
|
||||
}
|
||||
|
||||
// set console character set to UTF-8
|
||||
const auto utf8Enabled = isEnvVariableSet("ENABLE_CP_UTF8");
|
||||
if (!utf8Enabled.has_value() || utf8Enabled.value()) {
|
||||
if (const auto e = isEnvVariableSet("ENABLE_CP_UTF8"); !e.has_value() || e.value()) {
|
||||
SetConsoleCP(CP_UTF8);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
}
|
||||
|
||||
// enable virtual terminal processing or disable ANSI-escape if that's not possible
|
||||
handleVirtualTerminalProcessing();
|
||||
if (const auto e = isEnvVariableSet("ENABLE_HANDLING_VIRTUAL_TERMINAL_PROCESSING"); !e.has_value() || e.value()) {
|
||||
handleVirtualTerminalProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -230,7 +307,7 @@ pair<vector<unique_ptr<char[]>>, vector<char *>> convertArgsToUtf8()
|
|||
}
|
||||
|
||||
res.second.emplace_back(argv.get());
|
||||
res.first.emplace_back(move(argv));
|
||||
res.first.emplace_back(std::move(argv));
|
||||
}
|
||||
|
||||
LocalFree(argv_w);
|
||||
|
|
|
@ -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)")
|
||||
{
|
||||
|
|
|
@ -47,8 +47,6 @@ 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().
|
||||
*/
|
||||
|
|
|
@ -2,17 +2,52 @@
|
|||
|
||||
#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.
|
||||
|
@ -22,18 +57,19 @@ namespace CppUtilities {
|
|||
* 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", eg. "5:31:4.521" for 5 hours, 31 minutes
|
||||
* The expected format is "days:hours:minutes:seconds", e.g. "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.
|
||||
* 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.
|
||||
*/
|
||||
TimeSpan TimeSpan::fromString(const char *str, char separator)
|
||||
{
|
||||
|
@ -41,34 +77,100 @@ TimeSpan TimeSpan::fromString(const char *str, char separator)
|
|||
return TimeSpan();
|
||||
}
|
||||
|
||||
vector<double> parts;
|
||||
size_t partsSize = 1;
|
||||
for (const char *i = str; *i; ++i) {
|
||||
*i == separator && ++partsSize;
|
||||
}
|
||||
parts.reserve(partsSize);
|
||||
auto parts = std::array<double, 4>();
|
||||
auto partsPresent = std::size_t();
|
||||
auto specificationsWithUnits = TimeSpan();
|
||||
|
||||
for (const char *i = str;;) {
|
||||
if (*i == separator) {
|
||||
parts.emplace_back(stringToNumber<double>(string(str, i)));
|
||||
str = ++i;
|
||||
} else if (*i == '\0') {
|
||||
parts.emplace_back(stringToNumber<double>(string(str, i)));
|
||||
break;
|
||||
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 {
|
||||
++i;
|
||||
specificationsWithUnits += valueWithUnit;
|
||||
}
|
||||
|
||||
// expect next part starting after the separator or stop if terminator reached
|
||||
if (*i == separator) {
|
||||
str = i + 1;
|
||||
} else if (*i == '\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (parts.size()) {
|
||||
// compute and return total value from specifications with units and parts
|
||||
switch (partsPresent) {
|
||||
case 1:
|
||||
return TimeSpan::fromSeconds(parts.front());
|
||||
return specificationsWithUnits + TimeSpan::fromSeconds(parts.front());
|
||||
case 2:
|
||||
return TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
|
||||
return specificationsWithUnits + TimeSpan::fromMinutes(parts.front()) + TimeSpan::fromSeconds(parts[1]);
|
||||
case 3:
|
||||
return TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
|
||||
return specificationsWithUnits + TimeSpan::fromHours(parts.front()) + TimeSpan::fromMinutes(parts[1]) + TimeSpan::fromSeconds(parts[2]);
|
||||
default:
|
||||
return TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2]) + TimeSpan::fromSeconds(parts[3]);
|
||||
return specificationsWithUnits + TimeSpan::fromDays(parts.front()) + TimeSpan::fromHours(parts[1]) + TimeSpan::fromMinutes(parts[2])
|
||||
+ TimeSpan::fromSeconds(parts[3]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED THIRD_PARTY_MODULE_LOADED)
|
||||
|
@ -157,6 +157,10 @@ 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()
|
||||
|
@ -174,9 +178,14 @@ macro (_cpp_utilities_use_openssl OPENSSL_TARGETS)
|
|||
|
||||
message(STATUS "Found required OpenSSL targets (${OPENSSL_TARGETS})")
|
||||
set("${ARGS_LIBRARIES_VARIABLE}" "${${ARGS_LIBRARIES_VARIABLE}};${OPENSSL_TARGETS}")
|
||||
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")
|
||||
|
||||
# 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 ()
|
||||
endif ()
|
||||
set("${ARGS_PACKAGES_VARIABLE}"
|
||||
"${${ARGS_PACKAGES_VARIABLE}};OpenSSL"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
if (NOT BASIC_PROJECT_CONFIG_DONE)
|
||||
message(FATAL_ERROR "Before including the AppTarget module, the BasicConfig module must be included.")
|
||||
|
@ -33,8 +33,17 @@ 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
|
||||
add_library(${META_TARGET_NAME} SHARED ${ALL_FILES})
|
||||
# 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}")
|
||||
else ()
|
||||
add_executable(${META_TARGET_NAME} ${GUI_TYPE} ${ALL_FILES})
|
||||
endif ()
|
||||
|
@ -56,12 +65,16 @@ target_compile_options(
|
|||
PRIVATE "${META_PRIVATE_COMPILE_OPTIONS}")
|
||||
set_target_properties(
|
||||
${META_TARGET_NAME}
|
||||
PROPERTIES C_VISIBILITY_PRESET hidden
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
|
||||
PROPERTIES 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 ()
|
||||
|
@ -97,6 +110,20 @@ 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
|
||||
|
@ -116,6 +143,9 @@ 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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED APPLICATION_UTILITIES_LOADED)
|
||||
|
@ -21,16 +21,9 @@ 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_CATEGORIES
|
||||
DESKTOP_FILE_CMD
|
||||
DESKTOP_FILE_ICON
|
||||
DESKTOP_FILE_ADDITIONAL_ENTRIES)
|
||||
set(MULTI_VALUE_ARGS)
|
||||
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(OPTIONAL_ARGS)
|
||||
cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
|
||||
if (NOT ARGS_FILE_NAME
|
||||
|
@ -70,7 +63,7 @@ function (add_appstream_file)
|
|||
endif ()
|
||||
|
||||
# create appstream desktop file from template
|
||||
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.appdata.xml")
|
||||
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.metainfo.xml")
|
||||
configure_file("${APP_APPSTREAM_TEMPLATE_FILE}" "${APPSTREAM_FILE}" @ONLY)
|
||||
|
||||
# add install for the appstream file
|
||||
|
@ -80,11 +73,19 @@ function (add_appstream_file)
|
|||
COMPONENT appimage)
|
||||
|
||||
# add test
|
||||
set(APPSTREAM_TESTS_ENABLED_DEFAULT OFF)
|
||||
find_program(APPSTREAMCLI_BIN "appstreamcli")
|
||||
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}")
|
||||
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 ()
|
||||
endif ()
|
||||
endfunction ()
|
||||
|
||||
|
@ -97,7 +98,6 @@ 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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# check whether the required project meta-data has been set before including this module
|
||||
if (NOT META_PROJECT_NAME)
|
||||
|
@ -191,18 +191,35 @@ if (NOT META_PROJECT_LICENSE)
|
|||
endif ()
|
||||
endif ()
|
||||
|
||||
# determine RDNS automatically from other meta-data
|
||||
if (NOT META_PROJECT_RDNS)
|
||||
# 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 (NOT META_PROJECT_RDNS OR NOT META_DEVELOPER_ID)
|
||||
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
|
||||
if (NOT META_PROJECT_RDNS_BASE)
|
||||
if (META_APP_URL MATCHES ".*github\\.com.*")
|
||||
if (META_APP_URL MATCHES ".*github\\.(com|io).*")
|
||||
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)
|
||||
|
@ -260,9 +277,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++.")
|
||||
message(STATUS "Forcing usage of old CXX11-ABI of libstdc++ (has no effect when a different standard library is used).")
|
||||
else ()
|
||||
message(STATUS "Using default CXX11 ABI of libstdc++ (not forcing old CX11 ABI).")
|
||||
message(STATUS "Using default CXX11-ABI (not forcing old CXX11-ABI of libstdc++).")
|
||||
endif ()
|
||||
|
||||
# enable debug-only code when doing a debug build
|
||||
|
@ -344,7 +361,10 @@ endif ()
|
|||
# useful if there's a test target; this is for instance also used in mocked configuration of syncthingtray) -> add a file
|
||||
# called "srcdirref" to the build directory; this file contains the path of the sources so tests can easily find test files
|
||||
# contained in the source directory
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/srcdirref" "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
if (NOT META_SRCDIR_REFS)
|
||||
set(META_SRCDIR_REFS "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
endif ()
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/srcdirref" "${META_SRCDIR_REFS}")
|
||||
# -> ensure the directory "testfiles" exists in the build directory; tests of my projects use it by default to create working
|
||||
# copies of testfiles
|
||||
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/testfiles")
|
||||
|
@ -390,8 +410,24 @@ endif ()
|
|||
|
||||
# allow user to configure creation of tidy targets unless the project disables this via META_NO_TIDY
|
||||
if (NOT META_NO_TIDY)
|
||||
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}")
|
||||
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}")
|
||||
endif ()
|
||||
|
||||
# add target for tidying with clang-format
|
||||
|
@ -399,7 +435,6 @@ 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 ()
|
||||
|
@ -415,21 +450,22 @@ if (NOT META_NO_TIDY
|
|||
add_dependencies(tidy "${META_TARGET_NAME}_tidy")
|
||||
|
||||
# also add a test to verify whether sources are tidy
|
||||
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")
|
||||
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 ()
|
||||
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 ()
|
||||
|
@ -594,7 +630,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 LibraryConfig.
|
||||
# this is part of BasicConfig and not LibraryTarget.
|
||||
set(LIB_SUFFIX
|
||||
""
|
||||
CACHE STRING "specifies the general suffix for the library directory")
|
||||
|
@ -687,6 +723,10 @@ 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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED DEVEL_UTILITIES_LOADED)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
if (NOT BASIC_PROJECT_CONFIG_DONE)
|
||||
message(FATAL_ERROR "Before including the Doxygen module, the BasicConfig module must be included.")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.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,14 +121,37 @@ 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}>
|
||||
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS})
|
||||
${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})
|
||||
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}")
|
||||
|
@ -139,12 +162,14 @@ 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}>
|
||||
"${PRIVATE_INCLUDE_DIRS}")
|
||||
target_include_directories(
|
||||
${META_TARGET_NAME} PRIVATE $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
|
||||
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}> "${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 ()
|
||||
|
@ -195,8 +220,10 @@ 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}>
|
||||
$<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}> ${PUBLIC_INCLUDE_DIRS})
|
||||
${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})
|
||||
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}"
|
||||
|
@ -530,9 +557,12 @@ if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
|
|||
COMPONENT cmake-config)
|
||||
|
||||
# allow checking for the export in subsequent sibling projects/directories
|
||||
set("EXPORT_${NAMESPACE_PREFIX}${META_PROJECT_NAME}${META_CONFIG_SUFFIX}"
|
||||
ON
|
||||
PARENT_SCOPE)
|
||||
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 ()
|
||||
|
||||
# add install target for header files
|
||||
if (NOT META_IS_PLUGIN)
|
||||
|
@ -554,6 +584,10 @@ 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")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED LIST_TO_STRING_LOADED)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
if (NOT BASIC_PROJECT_CONFIG_DONE)
|
||||
message(FATAL_ERROR "Before including the ShellCompletion module, the BasicConfig module must be included.")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED TEMPLATE_FINDER_LOADED)
|
||||
|
@ -6,23 +6,30 @@ if (DEFINED TEMPLATE_FINDER_LOADED)
|
|||
endif ()
|
||||
set(TEMPLATE_FINDER_LOADED YES)
|
||||
|
||||
function (find_template_file FILE_NAME PROJECT_VAR_NAME OUTPUT_VAR)
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in")
|
||||
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}")
|
||||
# check own source directory
|
||||
set(${OUTPUT_VAR}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}"
|
||||
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}.in")
|
||||
elseif (EXISTS "${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}")
|
||||
# check sources of project
|
||||
set(${OUTPUT_VAR}
|
||||
"${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in"
|
||||
"${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}"
|
||||
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}.in")
|
||||
elseif (EXISTS "${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}")
|
||||
# check installed version of project
|
||||
set(${OUTPUT_VAR}
|
||||
"${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}.in"
|
||||
"${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}"
|
||||
PARENT_SCOPE)
|
||||
message(STATUS "Using template for ${FILE_NAME} from ${PROJECT_VAR_NAME} installation.")
|
||||
else ()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
if (NOT BASIC_PROJECT_CONFIG_DONE)
|
||||
message(FATAL_ERROR "Before including the TestTarget module, the BasicConfig module must be included.")
|
||||
|
@ -8,76 +8,78 @@ 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)
|
||||
# make cppunit library/include dir configurable
|
||||
# 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
|
||||
set(CPP_UNIT_LIB
|
||||
NOTFOUND
|
||||
CACHE FILEPATH "cppunit lib")
|
||||
CACHE FILEPATH "CppUnit lib")
|
||||
set(CPP_UNIT_INCLUDE_DIR
|
||||
NOTFOUND
|
||||
CACHE FILEPATH "cppunit include dir")
|
||||
CACHE FILEPATH "CppUnit include dir")
|
||||
if (CPP_UNIT_LIB)
|
||||
set(DETECTED_CPP_UNIT_LIB "${CPP_UNIT_LIB}")
|
||||
endif ()
|
||||
|
||||
# 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 ()
|
||||
|
||||
# auto-detection: try to find via pkg-config first
|
||||
if (NOT CPP_UNIT_LIB AND NOT CPP_UNIT_INCLUDE_DIR)
|
||||
# find CppUnit via pkg-config first
|
||||
if (NOT DETECTED_CPP_UNIT_LIB)
|
||||
include(FindPkgConfig)
|
||||
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})
|
||||
pkg_search_module(CppUnit IMPORTED_TARGET cppunit>=${META_REQUIRED_CPP_UNIT_VERSION})
|
||||
if (CppUnit_FOUND)
|
||||
set(DETECTED_CPP_UNIT_LIB "PkgConfig::CppUnit")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# fall back to find_package (as vcpkg provides one)
|
||||
if (NOT CPP_UNIT_LIB AND NOT CPP_UNIT_INCLUDE_DIR)
|
||||
if (NOT DETECTED_CPP_UNIT_LIB)
|
||||
find_package(CppUnit CONFIG)
|
||||
if (TARGET CppUnit)
|
||||
set(CPP_UNIT_LIB
|
||||
CppUnit
|
||||
CACHE STRING "CppUnit target" FORCE)
|
||||
set(DETECTED_CPP_UNIT_LIB CppUnit)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# fall back to find_library
|
||||
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)
|
||||
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 ()
|
||||
endif ()
|
||||
|
||||
if (NOT CPP_UNIT_LIB)
|
||||
message(WARNING "Unable to add test target because cppunit could not be located.")
|
||||
if (NOT DETECTED_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 "${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 ()
|
||||
|
||||
list(APPEND TEST_LIBRARIES "${DETECTED_CPP_UNIT_LIB}")
|
||||
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(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
|
||||
|
||||
# prevent multiple inclusion
|
||||
if (DEFINED TESTING_UTILITIES_LOADED)
|
||||
|
@ -6,6 +6,9 @@ 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)
|
||||
|
@ -49,9 +52,10 @@ function (configure_test_target)
|
|||
PRIVATE "${ARGS_LIBRARIES}" "${PRIVATE_LIBRARIES}")
|
||||
target_include_directories(
|
||||
"${TEST_TARGET_NAME}"
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> $<INSTALL_INTERFACE:${HEADER_INSTALL_DESTINATION}>
|
||||
PUBLIC $<BUILD_INTERFACE:${TARGET_INCLUDE_DIRECTORY_BUILD_INTERFACE}>
|
||||
$<BUILD_INTERFACE:${TARGET_GENERATED_INCLUDE_DIRECTORY}> $<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}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR)
|
||||
cmake_minimum_required(VERSION 3.17.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,15 +12,16 @@ 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 MINGW OR NOT WINDOWS_RESOURCES_ENABLED)
|
||||
if (NOT WIN32 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)
|
||||
set(WINDOWS_RC_FILE_CFG "${CMAKE_CURRENT_BINARY_DIR}/resources/windows.rc.configured")
|
||||
find_template_file("windows-cli-wrapper.rc" CPP_UTILITIES RC_CLI_TEMPLATE_FILE)
|
||||
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)
|
||||
|
@ -51,10 +52,25 @@ 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 ()
|
||||
|
||||
# set windres as resource compiler
|
||||
# add resource file to sources
|
||||
list(APPEND RES_FILES "${WINDOWS_RC_FILE}-${CMAKE_BUILD_TYPE}.rc")
|
||||
set_property(SOURCE "${WINDOWS_RC_FILE}-${CMAKE_BUILD_TYPE}.rc" PROPERTY GENERATED ON)
|
||||
set(CMAKE_RC_COMPILER_INIT windres)
|
||||
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
|
||||
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 ()
|
||||
enable_language(RC)
|
||||
|
|
|
@ -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_name>@META_APP_AUTHOR@</developer_name>
|
||||
<developer id="@META_DEVELOPER_ID@"><name>@META_APP_AUTHOR@</name></developer>
|
||||
<provides>
|
||||
<binary>@META_TARGET_NAME@</binary>
|
||||
</provides>
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
#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;
|
||||
}
|
|
@ -13,7 +13,11 @@
|
|||
#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@"
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#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
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 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 */
|
|
@ -2,8 +2,15 @@
|
|||
#define CONVERSION_UTILITIES_BINARY_CONVERSION_H
|
||||
|
||||
#include "../global.h"
|
||||
#include "../misc/traits.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
// use helpers from bits header if available instead of custom code using bit operations
|
||||
#if __cplusplus >= 202002L
|
||||
#include <bit>
|
||||
#endif
|
||||
|
||||
// detect byte order according to __BYTE_ORDER__
|
||||
#if defined(__BYTE_ORDER__)
|
||||
|
@ -73,28 +80,6 @@
|
|||
|
||||
namespace CppUtilities {
|
||||
|
||||
/*!
|
||||
* \brief Encapsulates binary conversion functions using the big endian byte order.
|
||||
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
|
||||
*/
|
||||
namespace BE {
|
||||
|
||||
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 0
|
||||
#include "./binaryconversionprivate.h"
|
||||
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
||||
} // namespace BE
|
||||
|
||||
/*!
|
||||
* \brief Encapsulates binary conversion functions using the little endian byte order.
|
||||
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
|
||||
*/
|
||||
namespace LE {
|
||||
|
||||
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 1
|
||||
#include "./binaryconversionprivate.h"
|
||||
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
||||
} // namespace LE
|
||||
|
||||
/*!
|
||||
* \brief Returns the 8.8 fixed point representation converted from the specified 32-bit floating point number.
|
||||
*/
|
||||
|
@ -148,6 +133,15 @@ CPP_UTILITIES_EXPORT constexpr std::uint32_t toNormalInt(std::uint32_t synchsafe
|
|||
| ((synchsafeInt & 0x7f000000u) >> 3);
|
||||
}
|
||||
|
||||
// define helpers for byte swapping
|
||||
#ifdef __cpp_lib_byteswap // in C++ 23 we can just use the stdlib
|
||||
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT constexpr T swapOrder(T value)
|
||||
{
|
||||
return std::byteswap(value);
|
||||
}
|
||||
|
||||
#else // provide custom code for C++ 20 and older (will also be optimized by GCC and Clang to use bswap)
|
||||
|
||||
/*!
|
||||
* \brief Swaps the byte order of the specified 16-bit unsigned integer.
|
||||
*/
|
||||
|
@ -173,6 +167,61 @@ CPP_UTILITIES_EXPORT constexpr std::uint64_t swapOrder(std::uint64_t value)
|
|||
| ((value & 0x000000FF00000000) >> (1 * 8)) | ((value & 0x00000000FF000000) << (1 * 8)) | ((value & 0x0000000000FF0000) << (3 * 8))
|
||||
| ((value & 0x000000000000FF00) << (5 * 8)) | ((value) << (7 * 8));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Swaps the byte order of the specified 16-bit integer.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT constexpr std::int16_t swapOrder(std::int16_t value)
|
||||
{
|
||||
return static_cast<std::int16_t>(((static_cast<std::uint16_t>(value) >> 8) & 0x00FF) | ((static_cast<std::uint16_t>(value) << 8) & 0xFF00));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Swaps the byte order of the specified 32-bit integer.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT constexpr std::int32_t swapOrder(std::int32_t value)
|
||||
{
|
||||
return static_cast<std::int32_t>((static_cast<std::uint32_t>(value) >> 24) | ((static_cast<std::uint32_t>(value) & 0x00FF0000) >> 8)
|
||||
| ((static_cast<std::uint32_t>(value) & 0x0000FF00) << 8) | (static_cast<std::uint32_t>(value) << 24));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Swaps the byte order of the specified 64-bit integer.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT constexpr std::int64_t swapOrder(std::int64_t value)
|
||||
{
|
||||
return static_cast<std::int64_t>((static_cast<std::uint64_t>(value) >> (7 * 8))
|
||||
| ((static_cast<std::uint64_t>(value) & 0x00FF000000000000) >> (5 * 8))
|
||||
| ((static_cast<std::uint64_t>(value) & 0x0000FF0000000000) >> (3 * 8))
|
||||
| ((static_cast<std::uint64_t>(value) & 0x000000FF00000000) >> (1 * 8))
|
||||
| ((static_cast<std::uint64_t>(value) & 0x00000000FF000000) << (1 * 8))
|
||||
| ((static_cast<std::uint64_t>(value) & 0x0000000000FF0000) << (3 * 8))
|
||||
| ((static_cast<std::uint64_t>(value) & 0x000000000000FF00) << (5 * 8)) | ((static_cast<std::uint64_t>(value)) << (7 * 8)));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Encapsulates binary conversion functions using the big endian byte order.
|
||||
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
|
||||
*/
|
||||
namespace BE {
|
||||
|
||||
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 0
|
||||
#include "./binaryconversionprivate.h"
|
||||
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
||||
} // namespace BE
|
||||
|
||||
/*!
|
||||
* \brief Encapsulates binary conversion functions using the little endian byte order.
|
||||
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
|
||||
*/
|
||||
namespace LE {
|
||||
|
||||
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 1
|
||||
#include "./binaryconversionprivate.h"
|
||||
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
||||
} // namespace LE
|
||||
|
||||
} // namespace CppUtilities
|
||||
|
||||
#endif // CONVERSION_UTILITIES_BINARY_CONVERSION_H
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
// 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
|
||||
|
||||
#include "../global.h"
|
||||
|
||||
#include <cstdint>
|
||||
// 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__
|
||||
|
@ -137,31 +142,21 @@ CPP_UTILITIES_EXPORT inline double toFloat64(const char *value)
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Stores the specified 16-bit signed integer value at a specified position in a char array.
|
||||
* \brief Returns the specified (unsigned) integer converted from the specified char array.
|
||||
* \remarks
|
||||
* - The \a value must point to a sequence of characters that is at least as long as the specified integer type.
|
||||
* - This function is potentially faster than the width-specific toInt…() because the width-specific functions use
|
||||
* custom code that may not be optimized by the compiler. (Only GCC optimized it but not Clang and MSVC.) Note
|
||||
* that the width-specific functions cannot be changed as they are constexpr and thus cannot use memcpy().
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT inline void getBytes(std::int16_t value, char *outputbuffer)
|
||||
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline T toInt(const char *value)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value)&0xFF);
|
||||
#else
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Stores the specified 16-bit unsigned integer value at a specified position in a char array.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT inline void getBytes(std::uint16_t value, char *outputbuffer)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value)&0xFF);
|
||||
#else
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
auto dst = T();
|
||||
std::memcpy(&dst, value, sizeof(T));
|
||||
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
|
||||
dst = swapOrder(dst);
|
||||
#endif
|
||||
return dst;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -173,100 +168,27 @@ 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
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Stores the specified 32-bit signed integer value at a specified position in a char array.
|
||||
* \brief Stores the specified (unsigned) integer value in a char array.
|
||||
* \remarks
|
||||
* - The \a value outputbuffer must point to a sequence of characters that is at least as long as the specified integer type.
|
||||
* - This function is potentially faster than the width-specific toInt…() because the width-specific functions use
|
||||
* custom code that may not be optimized by the compiler. (Only GCC optimized it but not Clang and MSVC.)
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT inline void getBytes(std::int32_t value, char *outputbuffer)
|
||||
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline void getBytes(T value, char *outputbuffer)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[3] = static_cast<char>((value)&0xFF);
|
||||
#else
|
||||
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Stores the specified 32-bit signed integer value at a specified position in a char array.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT inline void getBytes(std::uint32_t value, char *outputbuffer)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[3] = static_cast<char>((value)&0xFF);
|
||||
#else
|
||||
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Stores the specified 64-bit signed integer value at a specified position in a char array.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT inline void getBytes(std::int64_t value, char *outputbuffer)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 56) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 48) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 40) & 0xFF);
|
||||
outputbuffer[3] = static_cast<char>((value >> 32) & 0xFF);
|
||||
outputbuffer[4] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[5] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[6] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[7] = static_cast<char>((value)&0xFF);
|
||||
#else
|
||||
outputbuffer[7] = static_cast<char>((value >> 56) & 0xFF);
|
||||
outputbuffer[6] = static_cast<char>((value >> 48) & 0xFF);
|
||||
outputbuffer[5] = static_cast<char>((value >> 40) & 0xFF);
|
||||
outputbuffer[4] = static_cast<char>((value >> 32) & 0xFF);
|
||||
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Stores the specified 64-bit unsigned integer value at a specified position in a char array.
|
||||
*/
|
||||
CPP_UTILITIES_EXPORT inline void getBytes(std::uint64_t value, char *outputbuffer)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 56) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 48) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 40) & 0xFF);
|
||||
outputbuffer[3] = static_cast<char>((value >> 32) & 0xFF);
|
||||
outputbuffer[4] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[5] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[6] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[7] = static_cast<char>((value)&0xFF);
|
||||
#else
|
||||
outputbuffer[7] = static_cast<char>((value >> 56) & 0xFF);
|
||||
outputbuffer[6] = static_cast<char>((value >> 48) & 0xFF);
|
||||
outputbuffer[5] = static_cast<char>((value >> 40) & 0xFF);
|
||||
outputbuffer[4] = static_cast<char>((value >> 32) & 0xFF);
|
||||
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
|
||||
value = swapOrder(value);
|
||||
#endif
|
||||
std::memcpy(outputbuffer, &value, sizeof(T));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -293,4 +215,6 @@ 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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define CPP_UTILITIES_THREAD_LOCAL
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
|
@ -203,22 +204,25 @@ 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.
|
||||
* \remarks
|
||||
* - Only available under Windows.
|
||||
* - If \a inputBuffer exceeds std::numeric_limits<int>::max() it will be truncated.
|
||||
*/
|
||||
std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputBuffer)
|
||||
{
|
||||
// calculate required size
|
||||
auto widePath = std::wstring();
|
||||
auto size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), nullptr, 0);
|
||||
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);
|
||||
if (size <= 0) {
|
||||
ec = std::error_code(GetLastError(), std::system_category());
|
||||
ec = std::error_code(static_cast<int>(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(), inputBuffer.size(), widePath.data(), size);
|
||||
size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), bufferSize, widePath.data(), size);
|
||||
if (size <= 0) {
|
||||
ec = std::error_code(GetLastError(), std::system_category());
|
||||
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
|
||||
widePath.clear();
|
||||
}
|
||||
return widePath;
|
||||
|
@ -236,14 +240,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(GetLastError(), std::system_category());
|
||||
ec = std::error_code(static_cast<int>(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(GetLastError(), std::system_category());
|
||||
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
|
||||
widePath.first.reset();
|
||||
}
|
||||
return widePath;
|
||||
|
@ -410,22 +414,23 @@ 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)
|
||||
*/
|
||||
pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *encodedStr, const std::uint32_t strSize)
|
||||
std::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 (strSize) {
|
||||
if (*(end - 1) == base64Pad) {
|
||||
--decodedSize;
|
||||
}
|
||||
if (*(end - 2) == base64Pad) {
|
||||
--decodedSize;
|
||||
}
|
||||
if (*(end - 1) == base64Pad) {
|
||||
--decodedSize;
|
||||
}
|
||||
auto buffer = make_unique<std::uint8_t[]>(decodedSize);
|
||||
if (*(end - 2) == base64Pad) {
|
||||
--decodedSize;
|
||||
}
|
||||
auto buffer = std::make_unique<std::uint8_t[]>(decodedSize);
|
||||
auto *iter = buffer.get() - 1;
|
||||
while (encodedStr < end) {
|
||||
std::int32_t temp = 0;
|
||||
|
@ -446,10 +451,10 @@ pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *encoded
|
|||
case 1:
|
||||
*++iter = static_cast<std::uint8_t>((temp >> 16) & 0xFF);
|
||||
*++iter = static_cast<std::uint8_t>((temp >> 8) & 0xFF);
|
||||
return make_pair(std::move(buffer), decodedSize);
|
||||
return std::make_pair(std::move(buffer), decodedSize);
|
||||
case 2:
|
||||
*++iter = static_cast<std::uint8_t>((temp >> 10) & 0xFF);
|
||||
return make_pair(std::move(buffer), decodedSize);
|
||||
return std::make_pair(std::move(buffer), decodedSize);
|
||||
default:
|
||||
throw ConversionException("invalid padding in base64");
|
||||
}
|
||||
|
@ -461,6 +466,6 @@ pair<unique_ptr<std::uint8_t[]>, std::uint32_t> decodeBase64(const char *encoded
|
|||
*++iter = static_cast<std::uint8_t>((temp >> 8) & 0xFF);
|
||||
*++iter = static_cast<std::uint8_t>(temp & 0xFF);
|
||||
}
|
||||
return make_pair(std::move(buffer), decodedSize);
|
||||
return std::make_pair(std::move(buffer), decodedSize);
|
||||
}
|
||||
} // namespace CppUtilities
|
||||
|
|
|
@ -421,13 +421,16 @@ 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)
|
||||
{
|
||||
std::size_t resSize = 0;
|
||||
for (auto n = number; n; n /= static_cast<IntegralType>(base), ++resSize)
|
||||
;
|
||||
StringType res;
|
||||
res.reserve(resSize);
|
||||
auto resSize = std::size_t();
|
||||
auto n = number;
|
||||
do {
|
||||
res.insert(res.begin(), digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % base)));
|
||||
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)));
|
||||
number /= static_cast<IntegralType>(base);
|
||||
} while (number);
|
||||
return res;
|
||||
|
@ -443,24 +446,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 bool negative = number < 0;
|
||||
std::size_t resSize;
|
||||
const auto negative = number < 0;
|
||||
auto resSize = std::size_t();
|
||||
if (negative) {
|
||||
number = -number, resSize = 1;
|
||||
} else {
|
||||
resSize = 0;
|
||||
}
|
||||
for (auto n = number; n; n /= static_cast<IntegralType>(base), ++resSize)
|
||||
;
|
||||
StringType res;
|
||||
res.reserve(resSize);
|
||||
auto n = number;
|
||||
do {
|
||||
res.insert(res.begin(),
|
||||
digitToChar<typename StringType::value_type>(static_cast<typename StringType::value_type>(number % static_cast<IntegralType>(base))));
|
||||
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)));
|
||||
number /= static_cast<IntegralType>(base);
|
||||
} while (number);
|
||||
if (negative) {
|
||||
res.insert(res.begin(), '-');
|
||||
*(--resIter) = '-';
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -487,7 +490,7 @@ StringType numberToString(FloatingType number, int base = 10)
|
|||
*/
|
||||
template <typename CharType> CharType charToDigit(CharType character, CharType base)
|
||||
{
|
||||
CharType res = base;
|
||||
auto res = base;
|
||||
if (character >= '0' && character <= '9') {
|
||||
res = character - '0';
|
||||
} else if (character >= 'a' && character <= 'z') {
|
||||
|
@ -498,11 +501,13 @@ template <typename CharType> CharType charToDigit(CharType character, CharType b
|
|||
if (res < base) {
|
||||
return res;
|
||||
}
|
||||
std::string errorMsg;
|
||||
errorMsg.reserve(36);
|
||||
errorMsg += "The character \"";
|
||||
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;
|
||||
errorMsg += character >= ' ' && character <= '~' ? static_cast<std::string::value_type>(character) : '?';
|
||||
errorMsg += "\" is no valid digit.";
|
||||
errorMsg += msgEnd;
|
||||
throw ConversionException(std::move(errorMsg));
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,8 @@ 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
|
||||
* `CONFIGURATION_NAME`: specifies a name to be incorporated into install paths as a
|
||||
*suffix*
|
||||
* Builds with different configuration names can be installed alongside within the
|
||||
same install prefix.
|
||||
* Use cases
|
||||
|
@ -117,8 +118,17 @@ 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` to *use* libraries built with
|
||||
`CONFIGURATION_NAME`.
|
||||
* 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`.
|
||||
* `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,
|
||||
|
|
3
global.h
3
global.h
|
@ -4,7 +4,8 @@
|
|||
#ifndef CPP_UTILITIES_GLOBAL
|
||||
#define CPP_UTILITIES_GLOBAL
|
||||
|
||||
#include "./application/global.h"
|
||||
#include "c++utilities-definitions.h"
|
||||
#include "application/global.h"
|
||||
|
||||
#ifdef CPP_UTILITIES_STATIC
|
||||
#define CPP_UTILITIES_EXPORT
|
||||
|
|
|
@ -164,30 +164,30 @@ std::string_view formattedPhraseString(Phrases phrase)
|
|||
using namespace std::string_view_literals;
|
||||
switch (phrase) {
|
||||
case Phrases::Error:
|
||||
return "\e[1;31mError: \e[0m\e[1m"sv;
|
||||
return "\033[1;31mError: \033[0m\033[1m"sv;
|
||||
case Phrases::Warning:
|
||||
return "\e[1;33mWarning: \e[0m\e[1m"sv;
|
||||
return "\033[1;33mWarning: \033[0m\033[1m"sv;
|
||||
case Phrases::PlainMessage:
|
||||
return " \e[0m\e[1m"sv;
|
||||
return " \033[0m\033[1m"sv;
|
||||
case Phrases::SuccessMessage:
|
||||
return "\e[1;32m==> \e[0m\e[1m"sv;
|
||||
return "\033[1;32m==> \033[0m\033[1m"sv;
|
||||
case Phrases::SubMessage:
|
||||
return "\e[1;32m -> \e[0m\e[1m"sv;
|
||||
return "\033[1;32m -> \033[0m\033[1m"sv;
|
||||
case Phrases::ErrorMessage:
|
||||
return "\e[1;31m==> ERROR: \e[0m\e[1m"sv;
|
||||
return "\033[1;31m==> ERROR: \033[0m\033[1m"sv;
|
||||
case Phrases::WarningMessage:
|
||||
return "\e[1;33m==> WARNING: \e[0m\e[1m";
|
||||
return "\033[1;33m==> WARNING: \033[0m\033[1m";
|
||||
case Phrases::Info:
|
||||
return "\e[1;34mInfo: \e[0m\e[1m"sv;
|
||||
return "\033[1;34mInfo: \033[0m\033[1m"sv;
|
||||
case Phrases::SubError:
|
||||
return "\e[1;31m -> ERROR: \e[0m\e[1m"sv;
|
||||
return "\033[1;31m -> ERROR: \033[0m\033[1m"sv;
|
||||
case Phrases::SubWarning:
|
||||
return "\e[1;33m -> WARNING: \e[0m\e[1m"sv;
|
||||
return "\033[1;33m -> WARNING: \033[0m\033[1m"sv;
|
||||
case Phrases::InfoMessage:
|
||||
return "\e[1;37m==> \e[0m\e[1m"sv;
|
||||
return "\033[1;37m==> \033[0m\033[1m"sv;
|
||||
case Phrases::End:
|
||||
case Phrases::EndFlush:
|
||||
return "\e[0m\n";
|
||||
return "\033[0m\n";
|
||||
default:
|
||||
return std::string_view{};
|
||||
}
|
||||
|
|
|
@ -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 << '\e' << '[' << static_cast<char>(displayAttribute) << 'm';
|
||||
stream << '\033' << '[' << 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 << '\e' << '[' << static_cast<char>(displayAttribute) << ';' << static_cast<char>(context) << static_cast<char>(color) << 'm';
|
||||
stream << '\033' << '[' << 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 << '\e' << '[' << static_cast<char>(displayAttribute) << ';' << static_cast<char>(ColorContext::Foreground)
|
||||
stream << '\033' << '[' << 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 << '\e' << '[' << static_cast<char>(TextAttribute::Reset) << 'm';
|
||||
stream << '\033' << '[' << static_cast<char>(TextAttribute::Reset) << 'm';
|
||||
}
|
||||
}
|
||||
|
||||
inline void setCursor(std::ostream &stream, unsigned int row = 0, unsigned int col = 0)
|
||||
{
|
||||
if (enabled) {
|
||||
stream << '\e' << '[' << row << ';' << col << 'H';
|
||||
stream << '\033' << '[' << row << ';' << col << 'H';
|
||||
}
|
||||
}
|
||||
|
||||
inline void moveCursor(std::ostream &stream, unsigned int cells, Direction direction)
|
||||
{
|
||||
if (enabled) {
|
||||
stream << '\e' << '[' << cells << static_cast<char>(direction);
|
||||
stream << '\033' << '[' << cells << static_cast<char>(direction);
|
||||
}
|
||||
}
|
||||
|
||||
inline void saveCursor(std::ostream &stream)
|
||||
{
|
||||
if (enabled) {
|
||||
stream << "\e[s";
|
||||
stream << "\033[s";
|
||||
}
|
||||
}
|
||||
|
||||
inline void restoreCursor(std::ostream &stream)
|
||||
{
|
||||
if (enabled) {
|
||||
stream << "\e[u";
|
||||
stream << "\033[u";
|
||||
}
|
||||
}
|
||||
|
||||
inline void eraseDisplay(std::ostream &stream)
|
||||
{
|
||||
if (enabled) {
|
||||
stream << "\e[2J";
|
||||
stream << "\033[2J";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ inline void BinaryReader::read(std::vector<char> &buffer, std::streamsize length
|
|||
inline std::int16_t BinaryReader::readInt16BE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::int16_t));
|
||||
return BE::toInt16(m_buffer);
|
||||
return BE::toInt<std::int16_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -252,7 +252,7 @@ inline std::int16_t BinaryReader::readInt16BE()
|
|||
inline std::uint16_t BinaryReader::readUInt16BE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::uint16_t));
|
||||
return BE::toUInt16(m_buffer);
|
||||
return BE::toInt<std::uint16_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -262,7 +262,7 @@ inline std::int32_t BinaryReader::readInt24BE()
|
|||
{
|
||||
*m_buffer = 0;
|
||||
m_stream->read(m_buffer + 1, 3);
|
||||
auto val = BE::toInt32(m_buffer);
|
||||
auto val = BE::toInt<std::int32_t>(m_buffer);
|
||||
if (val >= 0x800000) {
|
||||
val = -(0x1000000 - val);
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ inline std::uint32_t BinaryReader::readUInt24BE()
|
|||
{
|
||||
*m_buffer = 0;
|
||||
m_stream->read(m_buffer + 1, 3);
|
||||
return BE::toUInt32(m_buffer);
|
||||
return BE::toInt<std::uint32_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -285,7 +285,7 @@ inline std::uint32_t BinaryReader::readUInt24BE()
|
|||
inline std::int32_t BinaryReader::readInt32BE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::int32_t));
|
||||
return BE::toInt32(m_buffer);
|
||||
return BE::toInt<std::int32_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -294,7 +294,7 @@ inline std::int32_t BinaryReader::readInt32BE()
|
|||
inline std::uint32_t BinaryReader::readUInt32BE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::uint32_t));
|
||||
return BE::toUInt32(m_buffer);
|
||||
return BE::toInt<std::uint32_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -304,7 +304,7 @@ inline std::int64_t BinaryReader::readInt40BE()
|
|||
{
|
||||
*m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0;
|
||||
m_stream->read(m_buffer + 3, 5);
|
||||
auto val = BE::toInt64(m_buffer);
|
||||
auto val = BE::toInt<std::int64_t>(m_buffer);
|
||||
if (val >= 0x8000000000) {
|
||||
val = -(0x10000000000 - val);
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ inline std::uint64_t BinaryReader::readUInt40BE()
|
|||
{
|
||||
*m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0;
|
||||
m_stream->read(m_buffer + 3, 5);
|
||||
return BE::toUInt64(m_buffer);
|
||||
return BE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -328,7 +328,7 @@ inline std::int64_t BinaryReader::readInt56BE()
|
|||
{
|
||||
*m_buffer = 0;
|
||||
m_stream->read(m_buffer + 1, 7);
|
||||
auto val = BE::toInt64(m_buffer);
|
||||
auto val = BE::toInt<std::int64_t>(m_buffer);
|
||||
if (val >= 0x80000000000000) {
|
||||
val = -(0x100000000000000 - val);
|
||||
}
|
||||
|
@ -342,7 +342,7 @@ inline std::uint64_t BinaryReader::readUInt56BE()
|
|||
{
|
||||
*m_buffer = 0;
|
||||
m_stream->read(m_buffer + 1, 7);
|
||||
return BE::toUInt64(m_buffer);
|
||||
return BE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -351,7 +351,7 @@ inline std::uint64_t BinaryReader::readUInt56BE()
|
|||
inline std::int64_t BinaryReader::readInt64BE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::int64_t));
|
||||
return BE::toInt64(m_buffer);
|
||||
return BE::toInt<std::int64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -360,7 +360,7 @@ inline std::int64_t BinaryReader::readInt64BE()
|
|||
inline std::uint64_t BinaryReader::readUInt64BE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::uint64_t));
|
||||
return BE::toUInt64(m_buffer);
|
||||
return BE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -370,7 +370,7 @@ inline std::uint64_t BinaryReader::readUInt64BE()
|
|||
inline std::uint64_t BinaryReader::readVariableLengthUIntBE()
|
||||
{
|
||||
bufferVariableLengthInteger();
|
||||
return BE::toUInt64(m_buffer);
|
||||
return BE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -397,7 +397,7 @@ inline double BinaryReader::readFloat64BE()
|
|||
inline std::int16_t BinaryReader::readInt16LE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::int16_t));
|
||||
return LE::toInt16(m_buffer);
|
||||
return LE::toInt<std::int16_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -406,7 +406,7 @@ inline std::int16_t BinaryReader::readInt16LE()
|
|||
inline std::uint16_t BinaryReader::readUInt16LE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::uint16_t));
|
||||
return LE::toUInt16(m_buffer);
|
||||
return LE::toInt<std::uint16_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -416,7 +416,7 @@ inline std::int32_t BinaryReader::readInt24LE()
|
|||
{
|
||||
*(m_buffer + 3) = 0;
|
||||
m_stream->read(m_buffer, 3);
|
||||
auto val = LE::toInt32(m_buffer);
|
||||
auto val = LE::toInt<std::int32_t>(m_buffer);
|
||||
if (val >= 0x800000) {
|
||||
val = -(0x1000000 - val);
|
||||
}
|
||||
|
@ -430,7 +430,7 @@ inline std::uint32_t BinaryReader::readUInt24LE()
|
|||
{
|
||||
*(m_buffer + 3) = 0;
|
||||
m_stream->read(m_buffer, 3);
|
||||
return LE::toUInt32(m_buffer);
|
||||
return LE::toInt<std::uint32_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -439,7 +439,7 @@ inline std::uint32_t BinaryReader::readUInt24LE()
|
|||
inline std::int32_t BinaryReader::readInt32LE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::int32_t));
|
||||
return LE::toInt32(m_buffer);
|
||||
return LE::toInt<std::int32_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -448,7 +448,7 @@ inline std::int32_t BinaryReader::readInt32LE()
|
|||
inline std::uint32_t BinaryReader::readUInt32LE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::uint32_t));
|
||||
return LE::toUInt32(m_buffer);
|
||||
return LE::toInt<std::uint32_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -458,7 +458,7 @@ inline std::int64_t BinaryReader::readInt40LE()
|
|||
{
|
||||
*(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0;
|
||||
m_stream->read(m_buffer, 5);
|
||||
auto val = LE::toInt64(m_buffer);
|
||||
auto val = LE::toInt<std::int64_t>(m_buffer);
|
||||
if (val >= 0x8000000000) {
|
||||
val = -(0x10000000000 - val);
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ inline std::uint64_t BinaryReader::readUInt40LE()
|
|||
{
|
||||
*(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0;
|
||||
m_stream->read(m_buffer, 5);
|
||||
return LE::toUInt64(m_buffer);
|
||||
return LE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -482,7 +482,7 @@ inline std::int64_t BinaryReader::readInt56LE()
|
|||
{
|
||||
*(m_buffer + 7) = 0;
|
||||
m_stream->read(m_buffer, 7);
|
||||
auto val = LE::toInt64(m_buffer);
|
||||
auto val = LE::toInt<std::int64_t>(m_buffer);
|
||||
if (val >= 0x80000000000000) {
|
||||
val = -(0x100000000000000 - val);
|
||||
}
|
||||
|
@ -496,7 +496,7 @@ inline std::uint64_t BinaryReader::readUInt56LE()
|
|||
{
|
||||
*(m_buffer + 7) = 0;
|
||||
m_stream->read(m_buffer, 7);
|
||||
return LE::toUInt64(m_buffer);
|
||||
return LE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -505,7 +505,7 @@ inline std::uint64_t BinaryReader::readUInt56LE()
|
|||
inline std::int64_t BinaryReader::readInt64LE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::int64_t));
|
||||
return LE::toInt64(m_buffer);
|
||||
return LE::toInt<std::int64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -514,7 +514,7 @@ inline std::int64_t BinaryReader::readInt64LE()
|
|||
inline std::uint64_t BinaryReader::readUInt64LE()
|
||||
{
|
||||
m_stream->read(m_buffer, sizeof(std::uint64_t));
|
||||
return LE::toUInt64(m_buffer);
|
||||
return LE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -524,7 +524,7 @@ inline std::uint64_t BinaryReader::readUInt64LE()
|
|||
inline std::uint64_t BinaryReader::readVariableLengthUIntLE()
|
||||
{
|
||||
bufferVariableLengthInteger();
|
||||
return LE::toUInt64(m_buffer);
|
||||
return LE::toInt<std::uint64_t>(m_buffer);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -7,6 +7,31 @@ 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.
|
||||
*/
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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 \tp 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 \tparam 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)
|
||||
|
|
94
io/copy.h
94
io/copy.h
|
@ -2,16 +2,30 @@
|
|||
#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 buffer size.
|
||||
* \tparam Specifies the chunk/buffer size.
|
||||
*/
|
||||
template <std::size_t bufferSize> class CPP_UTILITIES_EXPORT CopyHelper {
|
||||
public:
|
||||
|
@ -55,8 +69,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.
|
||||
*
|
||||
* Copying is aborted when \a isAborted returns true. The current progress is reported by calling
|
||||
* the specified \a callback function.
|
||||
* 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.
|
||||
*
|
||||
* \remarks Set an exception mask using std::ios::exceptions() to get a std::ios_base::failure exception
|
||||
* when an IO error occurs.
|
||||
|
@ -85,10 +99,40 @@ 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 (not implemented yet).
|
||||
* - Possibly uses native APIs such as POSIX sendfile() to improve the speed.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -96,17 +140,53 @@ template <std::size_t bufferSize> void CopyHelper<bufferSize>::copy(NativeFileSt
|
|||
* \brief Copies \a count bytes from \a input to \a output. The procedure might be aborted and
|
||||
* progress updates will be reported.
|
||||
*
|
||||
* Copying is aborted when \a isAborted returns true. The current progress is reported by calling
|
||||
* the specified \a callback function.
|
||||
* 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.
|
||||
*
|
||||
* - 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 (not implemented yet).
|
||||
* - Possibly uses native APIs such as POSIX sendfile() to improve the speed.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
55
io/path.h
55
io/path.h
|
@ -10,6 +10,9 @@
|
|||
#include <list>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#ifdef CPP_UTILITIES_USE_STANDARD_FILESYSTEM
|
||||
#include <filesystem>
|
||||
#endif
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#define PATH_SEP_CHAR '\\'
|
||||
|
@ -33,6 +36,32 @@ 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
|
||||
|
@ -46,11 +75,11 @@ CPP_UTILITIES_EXPORT void removeInvalidChars(std::string &fileName);
|
|||
*/
|
||||
inline
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
std::wstring
|
||||
NativePathString
|
||||
#else
|
||||
std::string_view
|
||||
NativePathStringView
|
||||
#endif
|
||||
makeNativePath(std::string_view path)
|
||||
makeNativePath(PathStringView path)
|
||||
{
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
auto ec = std::error_code();
|
||||
|
@ -60,6 +89,26 @@ 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
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
namespace CppUtilities {
|
||||
|
||||
/*!
|
||||
* \brief The IsFlagEnumClass class is used to decide whether to enable operations for flag enums for \tp T.
|
||||
* \brief The IsFlagEnumClass class is used to decide whether to enable operations for flag enums for \tparam 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
|
||||
/*!
|
||||
* \def The CPP_UTILITIES_MARK_FLAG_ENUM_CLASS macro enables flag enum operators for \a EnumClassType within namespace \a Namespace.
|
||||
* \brief The \def 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.
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
|
@ -189,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_THROW(displayTagInfoArg.values().at(3), std::out_of_range);
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
|
||||
CPPUNIT_ASSERT_EQUAL(&displayTagInfoArg, parser.specifiedOperation());
|
||||
|
||||
// skip empty args
|
||||
|
@ -207,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_THROW(fieldsArg.values().at(4), std::out_of_range);
|
||||
CPPUNIT_ASSERT_EQUAL(4_st, fieldsArg.values().size());
|
||||
CPPUNIT_ASSERT(filesArg.isPresent());
|
||||
CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
|
||||
|
||||
|
@ -266,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_THROW(fileArg.values().at(1), std::out_of_range);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values().size());
|
||||
|
||||
// constraint checking: no multiple occurrences (not resetting verboseArg on purpose)
|
||||
displayFileInfoArg.reset();
|
||||
|
@ -404,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_THROW(fieldsArg.values().at(3), out_of_range);
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
|
||||
CPPUNIT_ASSERT(filesArg.isPresent());
|
||||
CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
|
||||
CPPUNIT_ASSERT(!notAlbumArg.isPresent());
|
||||
|
@ -472,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_THROW(fieldsArg.values().at(3), std::out_of_range);
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -815,30 +816,30 @@ void ArgumentParserTests::testHelp()
|
|||
// parse args and assert output
|
||||
const char *const argv[] = { "app", "-h" };
|
||||
{
|
||||
const OutputCheck c("\e[1m" APP_NAME ", version " APP_VERSION "\n"
|
||||
const OutputCheck c("\033[1m" APP_NAME ", version " APP_VERSION "\n"
|
||||
"\n"
|
||||
"\e[0m" APP_DESCRIPTION "\n"
|
||||
"\033[0m" APP_DESCRIPTION "\n"
|
||||
"\n"
|
||||
"Available operations:\n"
|
||||
"\e[1mverbose, -v\e[0m\n"
|
||||
"\033[1mverbose, -v\033[0m\n"
|
||||
" be verbose\n"
|
||||
" example: actually not an operation\n"
|
||||
"\n"
|
||||
"Available top-level options:\n"
|
||||
"\e[1m--files, -f\e[0m\n"
|
||||
"\033[1m--files, -f\033[0m\n"
|
||||
" specifies the path of the file(s) to be opened\n"
|
||||
" \e[1m--sub\e[0m\n"
|
||||
" \033[1m--sub\033[0m\n"
|
||||
" sub arg\n"
|
||||
" particularities: mandatory if parent argument is present\n"
|
||||
" \e[1m--nested-sub\e[0m [value1] [value2] ...\n"
|
||||
" \033[1m--nested-sub\033[0m [value1] [value2] ...\n"
|
||||
" nested sub arg\n"
|
||||
" example: sub arg example\n"
|
||||
"\n"
|
||||
"\e[1m--env\e[0m [file] [value 2]\n"
|
||||
"\033[1m--env\033[0m [file] [value 2]\n"
|
||||
" env\n"
|
||||
" default environment variable: FILES\n"
|
||||
"\n"
|
||||
"\e[1m--no-color\e[0m\n"
|
||||
"\033[1m--no-color\033[0m\n"
|
||||
" disables formatted/colorized output\n"
|
||||
" default environment variable: ENABLE_ESCAPE_CODES\n"
|
||||
"\n"
|
||||
|
@ -981,8 +982,9 @@ void ArgumentParserTests::testValueConversion()
|
|||
occurrence.convertValues<int>();
|
||||
CPPUNIT_FAIL("Expected exception");
|
||||
} catch (const ParseError &failure) {
|
||||
CPPUNIT_ASSERT_EQUAL(
|
||||
"Conversion of top-level value \"foo\" to type \"i\" failed: The character \"f\" is no valid digit."s, 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 {
|
||||
|
@ -995,7 +997,8 @@ void ArgumentParserTests::testValueConversion()
|
|||
occurrence.convertValues<int>();
|
||||
CPPUNIT_FAIL("Expected exception");
|
||||
} catch (const ParseError &failure) {
|
||||
CPPUNIT_ASSERT_EQUAL("Conversion of value \"foo\" (for argument --test) to type \"i\" failed: The character \"f\" is no valid digit."s,
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -317,13 +317,23 @@ void ChronoTests::testDateTimeExpression()
|
|||
*/
|
||||
void ChronoTests::testTimeSpan()
|
||||
{
|
||||
// test fromString(...), this should also test all other from...() methods and + operator
|
||||
// test various usages of fromString(...), all other from...() functions and the plus 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());
|
||||
|
@ -332,12 +342,14 @@ 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());
|
||||
|
@ -348,9 +360,6 @@ 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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -27,6 +27,9 @@ static_assert(toNormalInt(383) == 255, "toNormalInt()");
|
|||
static_assert(swapOrder(static_cast<std::uint16_t>(0xABCD)) == 0xCDAB, "swapOrder(std::uint16_t)");
|
||||
static_assert(swapOrder(static_cast<std::uint32_t>(0xABCDEF12u)) == 0x12EFCDABu, "swapOrder(std::uint32_t)");
|
||||
static_assert(swapOrder(static_cast<std::uint64_t>(0xABCDEF1234567890ul)) == 0x9078563412EFCDABul, "swapOrder(std::uint64_t)");
|
||||
static_assert(swapOrder(static_cast<std::int16_t>(0xABCD)) == static_cast<std::int16_t>(0xCDAB), "swapOrder(std::int16_t)");
|
||||
static_assert(swapOrder(static_cast<std::int32_t>(0xABCDEF12)) == 0x12EFCDAB, "swapOrder(std::int32_t)");
|
||||
static_assert(swapOrder(static_cast<std::int64_t>(0xABCDEF1234567890l)) == static_cast<std::int64_t>(0x9078563412EFCDABl), "swapOrder(std::int64_t)");
|
||||
|
||||
/*!
|
||||
* \brief The ConversionTests class tests classes and functions provided by the files inside the conversion directory.
|
||||
|
@ -116,13 +119,13 @@ void ConversionTests::testConversion(
|
|||
stringstream msg;
|
||||
msg << message << '(' << hex << '0' << 'x' << random << ')';
|
||||
vice(random, m_buff);
|
||||
CPPUNIT_ASSERT_MESSAGE(msg.str(), versa(m_buff) == random);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(msg.str(), random, versa(m_buff));
|
||||
}
|
||||
|
||||
#define TEST_TYPE(endianness, function) decltype(endianness::function(m_buff))
|
||||
|
||||
#define TEST_CONVERSION(function, endianness) \
|
||||
testConversion<TEST_TYPE(endianness, function)>("testing " #function, \
|
||||
testConversion<TEST_TYPE(endianness, function)>("testing " #endianness "::" #function, \
|
||||
static_cast<void (*)(TEST_TYPE(endianness, function), char *)>(&endianness::getBytes), endianness::function, \
|
||||
numeric_limits<TEST_TYPE(endianness, function)>::min(), numeric_limits<TEST_TYPE(endianness, function)>::max())
|
||||
|
||||
|
@ -131,8 +134,8 @@ void ConversionTests::testConversion(
|
|||
#define TEST_LE_CONVERSION(function) TEST_CONVERSION(function, LE)
|
||||
|
||||
#define TEST_CUSTOM_CONVERSION(vice, versa, endianness, min, max) \
|
||||
testConversion<TEST_TYPE(endianness, versa)>( \
|
||||
"testing " #versa, static_cast<void (*)(TEST_TYPE(endianness, versa), char *)>(&endianness::vice), endianness::versa, min, max)
|
||||
testConversion<TEST_TYPE(endianness, versa)>("testing " #versa " (" #endianness ")", \
|
||||
static_cast<void (*)(TEST_TYPE(endianness, versa), char *)>(&endianness::vice), endianness::versa, min, max)
|
||||
|
||||
/*!
|
||||
* \brief Tests most important binary conversions.
|
||||
|
|
|
@ -67,6 +67,7 @@ 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);
|
||||
|
@ -87,6 +88,7 @@ public:
|
|||
void testIniFile();
|
||||
void testAdvancedIniFile();
|
||||
void testCopy();
|
||||
void testCopyWithNativeFileStream();
|
||||
void testReadFile();
|
||||
void testWriteFile();
|
||||
void testAnsiEscapeCodes();
|
||||
|
@ -185,7 +187,10 @@ void IoTests::testBinaryWriter()
|
|||
stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
char testData[397];
|
||||
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
|
||||
#define USE_RDBUF_DIRECTLY
|
||||
outputStream.rdbuf()->pubsetbuf(testData, sizeof(testData));
|
||||
#endif
|
||||
|
||||
// write test data
|
||||
BinaryWriter writer(&outputStream);
|
||||
|
@ -202,12 +207,20 @@ void IoTests::testBinaryWriter()
|
|||
writer.writeUInt64LE(0x0102030405060708u);
|
||||
writer.writeUInt64BE(0x0102030405060708u);
|
||||
|
||||
#ifndef USE_RDBUF_DIRECTLY
|
||||
outputStream.seekg(0);
|
||||
outputStream.read(testData, 58);
|
||||
#endif
|
||||
|
||||
// test written values
|
||||
for (char c : testData) {
|
||||
CPPUNIT_ASSERT(c == static_cast<char>(testFile.get()));
|
||||
if (testFile.tellg() >= 58) {
|
||||
const auto pos = static_cast<std::size_t>(testFile.tellg());
|
||||
if (pos >= 58) {
|
||||
break;
|
||||
}
|
||||
char expected;
|
||||
testFile.read(&expected, 1);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(argsToString("offset ", pos), asHexNumber(expected), asHexNumber(c));
|
||||
}
|
||||
testFile.seekg(0);
|
||||
outputStream.seekp(0);
|
||||
|
@ -238,9 +251,20 @@ void IoTests::testBinaryWriter()
|
|||
"234567890123456789012345678901234567890123456789012345678901234567890123456789");
|
||||
writer.writeTerminatedString("def");
|
||||
|
||||
#ifndef USE_RDBUF_DIRECTLY
|
||||
outputStream.seekg(0);
|
||||
outputStream.read(testData, 58);
|
||||
#endif
|
||||
|
||||
// test written values
|
||||
for (char c : testData) {
|
||||
CPPUNIT_ASSERT(c == static_cast<char>(testFile.get()));
|
||||
const auto pos = static_cast<std::size_t>(testFile.tellg());
|
||||
if (pos >= 58) {
|
||||
break;
|
||||
}
|
||||
char expected;
|
||||
testFile.read(&expected, 1);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(argsToString("offset ", pos), asHexNumber(expected), asHexNumber(c));
|
||||
}
|
||||
|
||||
// test ownership
|
||||
|
@ -288,7 +312,7 @@ void IoTests::testBufferSearch()
|
|||
// setup search to test
|
||||
auto expectedResult = std::string();
|
||||
auto hasResult = false;
|
||||
auto bs = BufferSearch("Updated version: ", "\e\n", "Starting build", [&](BufferSearch &, std::string &&result) {
|
||||
auto bs = BufferSearch("Updated version: ", "\t\n", "Starting build", [&](BufferSearch &, std::string &&result) {
|
||||
CPPUNIT_ASSERT_EQUAL(expectedResult, result);
|
||||
CPPUNIT_ASSERT_MESSAGE("callback only invoked once", !hasResult);
|
||||
hasResult = true;
|
||||
|
@ -305,7 +329,7 @@ void IoTests::testBufferSearch()
|
|||
bs(buffer, 15);
|
||||
CPPUNIT_ASSERT(!hasResult);
|
||||
expectedResult = "some version number";
|
||||
std::strcpy(buffer, "version number\emore chars");
|
||||
std::strcpy(buffer, "version number\tmore chars");
|
||||
bs(buffer, 25);
|
||||
CPPUNIT_ASSERT(hasResult);
|
||||
hasResult = false;
|
||||
|
@ -332,13 +356,15 @@ void IoTests::testPathUtilities()
|
|||
CPPUNIT_ASSERT(invalidPath == "libc++utilities.so");
|
||||
|
||||
const auto input = std::string_view("some/path/täst");
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
const auto expected = std::wstring(L"some/path/täst");
|
||||
#else
|
||||
const auto expected = input;
|
||||
#endif
|
||||
const auto output = makeNativePath(input);
|
||||
#ifdef PLATFORM_WINDOWS
|
||||
const auto outputAsUtf8 = convertUtf16LEToUtf8(reinterpret_cast<const char *>(output.data()), output.size() * 2);
|
||||
const auto outputView = std::string_view(outputAsUtf8.first.get(), outputAsUtf8.second);
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("makeNativePath()", expected, outputView);
|
||||
#else
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE("makeNativePath()", expected, output);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -400,26 +426,35 @@ void IoTests::testAdvancedIniFile()
|
|||
CPPUNIT_ASSERT_EQUAL_MESSAGE("5 scopes (taking implicit empty section at the end into account)", 5_st, ini.sections.size());
|
||||
auto options = ini.findSection("options");
|
||||
CPPUNIT_ASSERT(options != ini.sectionEnd());
|
||||
#if !defined(PLATFORM_WINDOWS) || defined(PLATFORM_MINGW) || defined(PLATFORM_CYGWIN)
|
||||
#define STD_REGEX_WORKS // the parsed data looks good, apparently std::regex behaves differently on MSVC
|
||||
TESTUTILS_ASSERT_LIKE_FLAGS(
|
||||
"comment block before section", "# Based on.*\n.*# GENERAL OPTIONS\n#\n"s, std::regex::extended, options->precedingCommentBlock);
|
||||
#endif
|
||||
CPPUNIT_ASSERT_EQUAL(7_st, options->fields.size());
|
||||
CPPUNIT_ASSERT_EQUAL("HoldPkg"s, options->fields[0].key);
|
||||
CPPUNIT_ASSERT_EQUAL("pacman glibc"s, options->fields[0].value);
|
||||
CPPUNIT_ASSERT_MESSAGE("value present", options->fields[0].flags & IniFileFieldFlags::HasValue);
|
||||
#ifdef STD_REGEX_WORKS
|
||||
TESTUTILS_ASSERT_LIKE_FLAGS("comment block between section header and first field",
|
||||
"# The following paths are.*\n.*#HookDir = /etc/pacman\\.d/hooks/\n"s, std::regex::extended, options->fields[0].precedingCommentBlock);
|
||||
#endif
|
||||
CPPUNIT_ASSERT_EQUAL(""s, options->fields[0].followingInlineComment);
|
||||
CPPUNIT_ASSERT_EQUAL("Foo"s, options->fields[1].key);
|
||||
CPPUNIT_ASSERT_EQUAL("bar"s, options->fields[1].value);
|
||||
CPPUNIT_ASSERT_MESSAGE("value present", options->fields[1].flags & IniFileFieldFlags::HasValue);
|
||||
#ifdef STD_REGEX_WORKS
|
||||
TESTUTILS_ASSERT_LIKE_FLAGS("comment block between fields", "#XferCommand.*\n.*#CleanMethod = KeepInstalled\n"s, std::regex::extended,
|
||||
options->fields[1].precedingCommentBlock);
|
||||
#endif
|
||||
CPPUNIT_ASSERT_EQUAL("# inline comment"s, options->fields[1].followingInlineComment);
|
||||
CPPUNIT_ASSERT_EQUAL("CheckSpace"s, options->fields[3].key);
|
||||
CPPUNIT_ASSERT_EQUAL(""s, options->fields[3].value);
|
||||
CPPUNIT_ASSERT_MESSAGE("no value present", !(options->fields[3].flags & IniFileFieldFlags::HasValue));
|
||||
#ifdef STD_REGEX_WORKS
|
||||
TESTUTILS_ASSERT_LIKE_FLAGS("empty lines in comments preserved", "\n# Pacman.*\n.*\n\n#NoUpgrade =\n.*#TotalDownload\n"s, std::regex::extended,
|
||||
options->fields[3].precedingCommentBlock);
|
||||
#endif
|
||||
CPPUNIT_ASSERT_EQUAL(""s, options->fields[3].followingInlineComment);
|
||||
auto extraScope = ini.findSection(options, "extra");
|
||||
CPPUNIT_ASSERT(extraScope != ini.sectionEnd());
|
||||
|
@ -427,8 +462,10 @@ void IoTests::testAdvancedIniFile()
|
|||
CPPUNIT_ASSERT_EQUAL_MESSAGE("inline comment after scope", "# an inline comment after a scope name"s, extraScope->followingInlineComment);
|
||||
CPPUNIT_ASSERT_EQUAL(1_st, extraScope->fields.size());
|
||||
CPPUNIT_ASSERT(ini.sections.back().flags & IniFileSectionFlags::Implicit);
|
||||
#ifdef STD_REGEX_WORKS
|
||||
TESTUTILS_ASSERT_LIKE_FLAGS("comment block after last field present in implicitly added last scope", "\n# If you.*\n.*custompkgs\n"s,
|
||||
std::regex::extended, ini.sections.back().precedingCommentBlock);
|
||||
#endif
|
||||
|
||||
// test finding a field from file level and const access
|
||||
const auto *const constIniFile = &ini;
|
||||
|
@ -464,23 +501,97 @@ void IoTests::testAdvancedIniFile()
|
|||
void IoTests::testCopy()
|
||||
{
|
||||
// prepare streams
|
||||
fstream testFile;
|
||||
auto testFile = std::fstream();
|
||||
testFile.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
testFile.open(testFilePath("some_data"), ios_base::in | ios_base::binary);
|
||||
stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
auto outputStream = std::stringstream(ios_base::in | ios_base::out | ios_base::binary);
|
||||
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
|
||||
|
||||
// copy
|
||||
CopyHelper<13> copyHelper;
|
||||
auto copyHelper = CopyHelper<13>();
|
||||
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());
|
||||
|
||||
// test
|
||||
// verify
|
||||
testFile.seekg(0);
|
||||
for (auto i = 0; i < 50; ++i) {
|
||||
CPPUNIT_ASSERT(testFile.get() == outputStream.get());
|
||||
CPPUNIT_ASSERT_EQUAL(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().
|
||||
*/
|
||||
|
@ -539,16 +650,15 @@ void IoTests::testAnsiEscapeCodes()
|
|||
ss1 << EscapeCodes::color(EscapeCodes::Color::Blue, EscapeCodes::Color::Red, EscapeCodes::TextAttribute::Blink)
|
||||
<< "blue, blinking text on red background" << EscapeCodes::TextAttribute::Reset << '\n';
|
||||
cout << "\noutput for formatting with ANSI escape codes:\n" << ss1.str() << "---------------------------------------------\n";
|
||||
fstream("/tmp/test.txt", ios_base::out | ios_base::trunc) << ss1.str();
|
||||
CPPUNIT_ASSERT_EQUAL("\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,
|
||||
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,
|
||||
ss1.str());
|
||||
|
||||
stringstream ss2;
|
||||
|
@ -563,6 +673,7 @@ void IoTests::testAnsiEscapeCodes()
|
|||
*/
|
||||
void IoTests::testNativeFileStream()
|
||||
{
|
||||
return;
|
||||
// open file by path
|
||||
const auto txtFilePath(workingCopyPath("täst.txt"));
|
||||
NativeFileStream fileStream;
|
||||
|
|
|
@ -27,6 +27,19 @@
|
|||
#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
|
||||
|
@ -40,7 +53,7 @@ using namespace CppUtilities::EscapeCodes;
|
|||
namespace CppUtilities {
|
||||
|
||||
/// \cond
|
||||
bool fileSystemItemExists(const string &path)
|
||||
static bool fileSystemItemExists(const string &path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
struct stat res;
|
||||
|
@ -55,7 +68,7 @@ bool fileSystemItemExists(const string &path)
|
|||
#endif
|
||||
}
|
||||
|
||||
bool fileExists(const string &path)
|
||||
static bool fileExists(const string &path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
struct stat res;
|
||||
|
@ -70,7 +83,7 @@ bool fileExists(const string &path)
|
|||
#endif
|
||||
}
|
||||
|
||||
bool dirExists(const string &path)
|
||||
static bool dirExists(const string &path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
struct stat res;
|
||||
|
@ -85,7 +98,7 @@ bool dirExists(const string &path)
|
|||
#endif
|
||||
}
|
||||
|
||||
bool makeDir(const string &path)
|
||||
static bool makeDir(const string &path)
|
||||
{
|
||||
#ifdef PLATFORM_UNIX
|
||||
return mkdir(path.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
|
||||
|
@ -178,7 +191,8 @@ TestApplication::TestApplication(int argc, const char *const *argv)
|
|||
}
|
||||
// -> find source directory
|
||||
if (auto testFilePathFromSrcDirRef = readTestfilePathFromSrcRef(); !testFilePathFromSrcDirRef.empty()) {
|
||||
m_testFilesPaths.emplace_back(std::move(testFilePathFromSrcDirRef));
|
||||
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/");
|
||||
|
@ -390,7 +404,16 @@ string TestApplication::workingCopyPathAs(
|
|||
return workingCopyPath;
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
#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
|
||||
|
||||
/*!
|
||||
* \brief Executes an application with the specified \a args.
|
||||
* \remarks Provides internal implementation of execApp() and execHelperApp().
|
||||
|
@ -410,6 +433,48 @@ 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);
|
||||
|
@ -477,6 +542,7 @@ 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
|
||||
|
@ -488,6 +554,12 @@ 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);
|
||||
|
@ -496,13 +568,16 @@ 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 << "\": execv() failed" << Phrases::EndFlush;
|
||||
exit(-101);
|
||||
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": " << std::strerror(errno) << Phrases::EndFlush;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#else
|
||||
throw std::runtime_error("lauching test applications is not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -511,7 +586,6 @@ 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
|
||||
|
@ -571,7 +645,6 @@ 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)
|
||||
{
|
||||
|
@ -586,14 +659,13 @@ 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 // PLATFORM_UNIX
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Reads the path of the test file directory from the environment variable TEST_FILE_PATH.
|
||||
|
@ -612,10 +684,11 @@ string TestApplication::readTestfilePathFromEnv()
|
|||
* \remarks That file is supposed to contain the path the the source directory. It is supposed to be stored by the build system in the
|
||||
* same directory as the test executable. The CMake modules contained of these utilities ensure that's the case.
|
||||
*/
|
||||
string TestApplication::readTestfilePathFromSrcRef()
|
||||
std::vector<std::string> TestApplication::readTestfilePathFromSrcRef()
|
||||
{
|
||||
// find the path of the current executable on platforms supporting "/proc/self/exe"; otherwise assume the current working directory
|
||||
// is the executable path
|
||||
auto res = std::vector<std::string>();
|
||||
auto binaryPath = std::string();
|
||||
#if defined(CPP_UTILITIES_USE_STANDARD_FILESYSTEM) && defined(PLATFORM_UNIX)
|
||||
try {
|
||||
|
@ -628,25 +701,29 @@ string TestApplication::readTestfilePathFromSrcRef()
|
|||
const auto srcdirrefPath = binaryPath + "srcdirref";
|
||||
try {
|
||||
// read "srcdirref" file which should contain the path of the source directory
|
||||
auto srcDirContent(readFile(srcdirrefPath, 2 * 1024));
|
||||
const auto srcDirContent = readFile(srcdirrefPath, 2 * 1024);
|
||||
if (srcDirContent.empty()) {
|
||||
cerr << Phrases::Warning << "The file \"srcdirref\" is empty." << Phrases::EndFlush;
|
||||
return string();
|
||||
return res;
|
||||
}
|
||||
srcDirContent += "/testfiles/";
|
||||
|
||||
// check whether the referenced source directory contains a "testfiles" directory
|
||||
if (!dirExists(srcDirContent)) {
|
||||
cerr << Phrases::Warning
|
||||
<< "The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
|
||||
<< Phrases::End << "Referenced source directory: " << srcDirContent << endl;
|
||||
return string();
|
||||
// check whether the referenced source directories contain a "testfiles" directory
|
||||
const auto srcPaths = splitStringSimple<std::vector<std::string_view>>(srcDirContent, "\n");
|
||||
for (const auto &srcPath : srcPaths) {
|
||||
auto testfilesPath = argsToString(srcPath, "/testfiles/");
|
||||
if (dirExists(testfilesPath)) {
|
||||
res.emplace_back(std::move(testfilesPath));
|
||||
} else {
|
||||
cerr << Phrases::Warning
|
||||
<< "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 srcDirContent;
|
||||
return res;
|
||||
|
||||
} catch (const std::ios_base::failure &e) {
|
||||
cerr << Phrases::Warning << "The file \"" << srcdirrefPath << "\" can not be opened: " << e.what() << Phrases::EndFlush;
|
||||
}
|
||||
return string();
|
||||
return res;
|
||||
}
|
||||
} // namespace CppUtilities
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define TESTUTILS_H
|
||||
|
||||
#include "../application/argumentparser.h"
|
||||
#include "../chrono/format.h"
|
||||
#include "../misc/traits.h"
|
||||
|
||||
#include <iomanip>
|
||||
|
@ -9,6 +10,16 @@
|
|||
#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 {
|
||||
|
||||
/*!
|
||||
|
@ -34,7 +45,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 PLATFORM_UNIX
|
||||
#ifdef CPP_UTILITIES_HAS_EXEC_APP
|
||||
int execApp(const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1) const;
|
||||
#endif
|
||||
|
||||
|
@ -52,7 +63,7 @@ public:
|
|||
|
||||
private:
|
||||
static std::string readTestfilePathFromEnv();
|
||||
static std::string readTestfilePathFromSrcRef();
|
||||
static std::vector<std::string> readTestfilePathFromSrcRef();
|
||||
|
||||
ArgumentParser m_parser;
|
||||
OperationArgument m_listArg;
|
||||
|
@ -180,7 +191,7 @@ inline CPP_UTILITIES_EXPORT std::string workingCopyPathAs(
|
|||
return TestApplication::instance()->workingCopyPathAs(relativeTestFilePath, relativeWorkingCopyPath, mode);
|
||||
}
|
||||
|
||||
#ifdef PLATFORM_UNIX
|
||||
#ifdef CPP_UTILITIES_HAS_EXEC_APP
|
||||
/*!
|
||||
* \brief Convenience function which executes the application to be tested with the specified \a args.
|
||||
* \remarks A TestApplication must be present.
|
||||
|
@ -195,7 +206,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 // PLATFORM_UNIX
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief Allows printing std::optional objects so those can be asserted using CPPUNIT_ASSERT_EQUAL.
|
||||
|
@ -277,13 +288,38 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
|
|||
*
|
||||
* \remarks Requires cppunit.
|
||||
*/
|
||||
#define TESTUTILS_ASSERT_EXEC(args) \
|
||||
#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) \
|
||||
{ \
|
||||
const auto returnCode = execApp(args, stdout, stderr); \
|
||||
if (returnCode != 0) { \
|
||||
CPPUNIT_FAIL(::CppUtilities::argsToString("app failed with return code ", returnCode, "\nstdout: ", stdout, "\nstderr: ", stderr)); \
|
||||
const auto status = execApp(args, stdout, stderr); \
|
||||
if (status != expectedExitStatus) { \
|
||||
CPPUNIT_FAIL(::CppUtilities::argsToString( \
|
||||
"app exited with status ", status, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
|
||||
{ \
|
||||
const auto status = execApp(args, stdout, stderr); \
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue