Compare commits
134 Commits
Author | SHA1 | Date |
---|---|---|
|
ad2ea47d36 | |
|
f50e615ee0 | |
|
2664affadc | |
|
684e0b0344 | |
|
d180667293 | |
|
13dfb81ff2 | |
|
5fe20bc406 | |
|
054612f753 | |
|
14e9561e16 | |
|
beee01c147 | |
|
9bb224c2ba | |
|
d2928517c5 | |
|
0c652a774e | |
|
9c687bd723 | |
|
65ffed8151 | |
|
957c044e63 | |
|
a4c18017b7 | |
|
73a837962d | |
|
8502d1bc2a | |
|
afc3413e9c | |
|
ae908283a0 | |
|
d31092b7d9 | |
|
dfbf300c65 | |
|
909346c199 | |
|
c9cd44ceee | |
|
a43affa81a | |
|
d8e144d312 | |
|
a337452179 | |
|
6cb0e63921 | |
|
a4625b8e34 | |
|
a4be8a56d1 | |
|
ce31de2c6f | |
|
bc00bdcdc9 | |
|
57579f0164 | |
|
a7fdc1af1b | |
|
27043d2be0 | |
|
b526d79eaf | |
|
995c315377 | |
|
d08794b11d | |
|
1a0c4fbce0 | |
|
c25a3c9c9a | |
|
c9dea06cfe | |
|
ad686a1be7 | |
|
85c76708c9 | |
|
c17c8e7815 | |
|
f97320816a | |
|
6f924da4f0 | |
|
b3b7166812 | |
|
8bffc93316 | |
|
456bbfc54e | |
|
b0be8817ad | |
|
d8605b50b0 | |
|
1264a7e9c5 | |
|
b8dff49c01 | |
|
dd0ea1d348 | |
|
21f32d318b | |
|
8d460380f1 | |
|
fbb1d73779 | |
|
35f56d486c | |
|
9d8f897972 | |
|
55146db7e1 | |
|
fdfe8f154c | |
|
b1f89d78c3 | |
|
3649782c8c | |
|
905f81c8b7 | |
|
7bed9cd38c | |
|
11de58141b | |
|
a425363eac | |
|
8fb7de6fe0 | |
|
a1bed55eda | |
|
05570c5c71 | |
|
cc6576c417 | |
|
ac35a5fad1 | |
|
762131acf9 | |
|
cded82a00b | |
|
a2f9748c1a | |
|
938e441336 | |
|
b2d4d0be01 | |
|
ff33454bf1 | |
|
aa298772c9 | |
|
7dff72d0bd | |
|
938da48da0 | |
|
c71f835ad0 | |
|
053ac7e1ad | |
|
035a448da0 | |
|
8d28ab70b3 | |
|
132d25fbb1 | |
|
0349037711 | |
|
af200403de | |
|
18d92fee40 | |
|
3bca5c224d | |
|
6660ff7eca | |
|
2137568ad8 | |
|
6a8431da0a | |
|
c4024ce00e | |
|
7bcc66be0d | |
|
5ebbd0eb3f | |
|
04682d4601 | |
|
6826546196 | |
|
08a1c5c2eb | |
|
9191117120 | |
|
30cefc2fd3 | |
|
38541f4c60 | |
|
831c083e5f | |
|
dd95310c73 | |
|
fc651c71ff | |
|
3bec473775 | |
|
c111d9f374 | |
|
0057e49a0d | |
|
cfe67a1078 | |
|
66b6fff0f2 | |
|
5bfce4235e | |
|
5d5ccb7729 | |
|
b9097a3750 | |
|
a19ee41a39 | |
|
0341316b4b | |
|
0d0685d4c7 | |
|
c6396f92fc | |
|
c60584e122 | |
|
f3cb406ebe | |
|
caace3ac63 | |
|
5e325d0b92 | |
|
5425488421 | |
|
cb5ca77658 | |
|
93a632e831 | |
|
ad39cbd604 | |
|
e8b4279062 | |
|
a55a3604bd | |
|
da91e54ec7 | |
|
83025c17a1 | |
|
2467c4f815 | |
|
10a7e4d814 | |
|
59896da087 | |
|
c9131ce6e7 |
|
@ -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 22)
|
||||
set(META_VERSION_PATCH 0)
|
||||
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,27 @@
|
|||
"WEBVIEW_PROVIDER": {"type": "STRING", "value": "none"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "clang",
|
||||
"inherits": "default",
|
||||
"displayName": "Use clang/clang++",
|
||||
"description": "Enforces use of clang/clang++ even when it is not the system default",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-clang",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": {"type": "STRING", "value": "clang"},
|
||||
"CMAKE_CXX_COMPILER": {"type": "STRING", "value": "clang++"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libc++",
|
||||
"inherits": "clang",
|
||||
"displayName": "Use clang/clang++ and libc++",
|
||||
"description": "Enforces use of clang/clang++ and libc++ even when it is not the system default",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-clang-libc++",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_FLAGS": {"type": "STRING", "value": "$env{CXXFLAGS} -stdlib=libc++"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "no-kde",
|
||||
"inherits": "default",
|
||||
|
@ -69,6 +90,23 @@
|
|||
"CONFIGURATION_TARGET_SUFFIX": {"type": "STRING", "value": "devel"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "devel-clang",
|
||||
"inherits": ["devel", "clang"],
|
||||
"displayName": "Development config using clang",
|
||||
"description": "Combination of devel and libc++",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-clang"
|
||||
},
|
||||
{
|
||||
"name": "devel-libc++",
|
||||
"inherits": ["devel", "libc++"],
|
||||
"displayName": "Development config using libc++",
|
||||
"description": "Combination of devel and libc++",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++",
|
||||
"cacheVariables": {
|
||||
"ENABLE_CPP_UNIT": {"type": "BOOL", "value": "OFF"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "devel-qt6",
|
||||
"inherits": ["qt6", "devel"],
|
||||
|
@ -77,9 +115,37 @@
|
|||
"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-clang-qt6",
|
||||
"inherits": ["qt6", "devel-clang"],
|
||||
"displayName": "Development config using clang and Qt 6",
|
||||
"description": "Combination of qt6 and devel-clang",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-clang-qt6"
|
||||
},
|
||||
{
|
||||
"name": "devel-libc++-qt6",
|
||||
"inherits": ["qt6", "devel-libc++"],
|
||||
"displayName": "Development config using libc++ and Qt 6",
|
||||
"description": "Combination of qt6 and devel-libc++",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++-qt6"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"inherits": "devel",
|
||||
|
@ -91,8 +157,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",
|
||||
|
@ -100,20 +228,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",
|
||||
|
@ -122,9 +258,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"],
|
||||
|
@ -132,6 +278,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"],
|
||||
|
@ -139,6 +292,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"],
|
||||
|
@ -146,6 +306,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"],
|
||||
|
@ -170,10 +358,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"}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -183,16 +375,49 @@
|
|||
"description": "See descriptions of devel and arch-static-compat",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-static-compat-devel"
|
||||
},
|
||||
{
|
||||
"name": "arch-android",
|
||||
"inherits": ["no-webview", "no-kde", "qt6"],
|
||||
"displayName": "Target *-linux-android using Arch Linux's Android packaging (invoke e.g. \"source android-env aarch64\" before invoking CMake)",
|
||||
"description": "Build targeting Android, paths and flags are specific to Arch Linux's Android packaging, invoke e.g. \"source android-env aarch64\" for required environment variables",
|
||||
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-android-$env{ANDROID_ABI}",
|
||||
"toolchainFile": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake",
|
||||
"environment": {
|
||||
"PATH": "$env{ANDROID_PREFIX}/bin:$penv{PATH}"
|
||||
},
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": {"type": "STRING", "value": "$env{ANDROID_ABI}"},
|
||||
"ANDROID_SDK_ROOT": {"type": "STRING", "value": "$env{ANDROID_HOME}"},
|
||||
"ANDROID_NDK": {"type": "STRING", "value": "$env{ANDROID_NDK_HOME}"},
|
||||
"ANDROID_NATIVE_API_LEVEL": {"type": "STRING", "value": "$env{ANDROID_MINIMUM_PLATFORM}"},
|
||||
"ANDROID_TOOLCHAIN": {"type": "STRING", "value": "clang"},
|
||||
"CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"},
|
||||
"CMAKE_C_FLAGS_RELEASE": {"type": "STRING", "value": "$env{ANDROID_CFLAGS}"},
|
||||
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{ANDROID_PREFIX_INCLUDE}"},
|
||||
"CMAKE_CXX_FLAGS_RELEASE": {"type": "STRING", "value": "$env{ANDROID_CXXFLAGS}"},
|
||||
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{ANDROID_PREFIX_INCLUDE}"},
|
||||
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": {"type": "STRING", "value": "$env{ANDROID_LDFLAGS}"},
|
||||
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{ANDROID_PREFIX}"},
|
||||
"CMAKE_INSTALL_PREFIX": {"type": "PATH", "value": "$env{ANDROID_PREFIX}"},
|
||||
"CMAKE_INSTALL_LIBDIR": {"type": "PATH", "value": "lib"},
|
||||
"INCLUDE_INSTALL_DIR": {"type": "PATH", "value": "$env{ANDROID_PREFIX_INCLUDE}"},
|
||||
"LIB_INSTALL_DIR": {"type": "PATH", "value": "$env{ANDROID_PREFIX_LIB}"},
|
||||
"SYSCONF_INSTALL_DIR": {"type": "PATH", "value": "$env{ANDROID_PREFIX_ETC}"},
|
||||
"SHARE_INSTALL_DIR": {"type": "PATH", "value": "$env{ANDROID_PREFIX_SHARE}"},
|
||||
"PKG_CONFIG_EXECUTABLE": {"type": "FILEPATH", "value": "/usr/bin/$env{ANDROID_PKGCONFIG}"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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)",
|
||||
"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/10.0.22000.0/ucrt;$env{WIN_KITS_ROOT}//include/10.0.22000.0//um;$env{WIN_KITS_ROOT}//include/10.0.22000.0//shared;$env{WIN_KITS_ROOT}/include/10.0.22000.0//winrt;$env{WIN_KITS_ROOT}/include/10.0.22000.0//cppwinrt",
|
||||
"LIB": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0/ucrt/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0//um/x64",
|
||||
"LIBPATH": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0/ucrt/x64;$env{WIN_KITS_ROOT}/lib/10.0.22000.0/um/x64"
|
||||
"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"},
|
||||
|
@ -204,9 +429,9 @@
|
|||
"CMAKE_AR_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/Hostx64/x64/lib.exe"},
|
||||
"CMAKE_C_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/HostX64/x64/cl.exe"},
|
||||
"CMAKE_CXX_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/HostX64/x64/cl.exe"},
|
||||
"CMAKE_RC_COMPILER": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/10.0.22000.0/x64/rc.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/10.0.22000.0/x64/mt.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"},
|
||||
|
@ -216,11 +441,13 @@
|
|||
"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"}
|
||||
}
|
||||
},
|
||||
|
@ -238,25 +465,49 @@
|
|||
},
|
||||
{
|
||||
"name": "win-x64-msvc-static-debug",
|
||||
"inherits": ["win-x64-msvc-static-devel", "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"
|
||||
"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-clang", "configurePreset": "devel-clang"},
|
||||
{"name": "devel-libc++", "configurePreset": "devel-libc++"},
|
||||
{"name": "devel-qt6", "configurePreset": "devel-qt6"},
|
||||
{"name": "devel-unity", "configurePreset": "devel-unity"},
|
||||
{"name": "devel-clang-qt6", "configurePreset": "devel-clang-qt6"},
|
||||
{"name": "devel-libc++-qt6", "configurePreset": "devel-libc++-qt6"},
|
||||
{"name": "debug", "configurePreset": "debug"},
|
||||
{"name": "debug-qt6", "configurePreset": "debug-qt6"},
|
||||
{"name": "debug-kde", "configurePreset": "debug-kde"},
|
||||
{"name": "debug-kde-custom", "configurePreset": "debug-kde-custom"},
|
||||
{"name": "arch-i686-w64-mingw32", "configurePreset": "arch-i686-w64-mingw32"},
|
||||
{"name": "arch-x86_64-w64-mingw32", "configurePreset": "arch-x86_64-w64-mingw32"},
|
||||
{"name": "arch-i686-w64-mingw32-static", "configurePreset": "arch-i686-w64-mingw32-static"},
|
||||
{"name": "arch-x86_64-w64-mingw32-static", "configurePreset": "arch-x86_64-w64-mingw32-static"},
|
||||
{"name": "arch-i686-w64-mingw32-qt6", "configurePreset": "arch-i686-w64-mingw32-qt6"},
|
||||
{"name": "arch-x86_64-w64-mingw32-qt6", "configurePreset": "arch-x86_64-w64-mingw32-qt6"},
|
||||
{"name": "arch-i686-w64-mingw32-static-qt6", "configurePreset": "arch-i686-w64-mingw32-static-qt6"},
|
||||
{"name": "arch-x86_64-w64-mingw32-static-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-qt6"},
|
||||
{"name": "arch-i686-w64-mingw32-devel", "configurePreset": "arch-i686-w64-mingw32-devel"},
|
||||
{"name": "arch-x86_64-w64-mingw32-devel", "configurePreset": "arch-x86_64-w64-mingw32-devel"},
|
||||
{"name": "arch-i686-w64-mingw32-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-devel-qt6"},
|
||||
{"name": "arch-x86_64-w64-mingw32-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-devel-qt6"},
|
||||
{"name": "arch-i686-w64-mingw32-static-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-static-devel-qt6"},
|
||||
{"name": "arch-x86_64-w64-mingw32-static-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-devel-qt6"},
|
||||
{"name": "arch-static-compat", "configurePreset": "arch-static-compat"},
|
||||
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"},
|
||||
{"name": "arch-android", "configurePreset": "arch-android"},
|
||||
{"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"}
|
||||
|
|
160
README.md
160
README.md
|
@ -61,11 +61,11 @@ These build instructions apply to `c++utilities` but also to my other projects u
|
|||
* C++ compiler supporting C++17, tested with
|
||||
- g++ to compile for GNU/Linux and Windows
|
||||
- clang++ to compile for GNU/Linux and Android
|
||||
* CMake (at least 3.3.0) and Ninja or GNU Make
|
||||
* 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)
|
||||
|
@ -76,19 +76,20 @@ These build instructions apply to `c++utilities` but also to my other projects u
|
|||
- libstdc++ under GNU/Linux and Windows
|
||||
- libc++ under GNU/Linux and Android
|
||||
* glibc with iconv support or standalone iconv library
|
||||
* libstdc++ or Boost.Iostreams for `NativeFileStream` (optional)
|
||||
* 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 Ninja:
|
||||
Generic example using Ninja:
|
||||
```
|
||||
cmake -G Ninja \
|
||||
-S "path/to/source/directory" \
|
||||
-B "path/to/build/directory" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX="/final/install/location"
|
||||
make # build the binaries
|
||||
# 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
|
||||
|
@ -103,10 +104,11 @@ DESTDIR="/temporary/install/location" \
|
|||
cmake --install "path/to/build/directory"
|
||||
```
|
||||
|
||||
This example is rather generic. For a development build I recommended using CMakePresets as
|
||||
documented in the "CMake presets" section below. It also contains more concrete instructions for
|
||||
building on Windows.
|
||||
|
||||
#### General notes
|
||||
* ```LIB_SUFFIX```, ```LIB_SUFFIX_32``` and ```LIB_SUFFIX_64``` can be set to specify a suffix
|
||||
for the library directory, eg. lib*64* or lib*32*. The 32/64 variants are only used when building
|
||||
for 32/64-bit architecture.
|
||||
* By default the build system will *build* static libs. To *build* shared libraries *instead*, set
|
||||
`BUILD_SHARED_LIBS=ON`.
|
||||
* By default the build system will prefer *linking against* shared libraries. To force *linking against*
|
||||
|
@ -115,14 +117,15 @@ DESTDIR="/temporary/install/location" \
|
|||
* 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`.
|
||||
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.
|
||||
`DBUILTIN_ICON_THEMES_SEARCH_PATH=D:/programming/misc/breeze-icons/usr/share/icons` to specify the
|
||||
`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
|
||||
|
@ -155,7 +158,9 @@ tagparser and tageditor) as one big project.
|
|||
This can be easily achieved by using CMake's `add_subdirectory()` function. For project files see the repository
|
||||
[subdirs](https://github.com/Martchus/subdirs). For an example, see
|
||||
[build instructions for Syncthing Tray](https://github.com/Martchus/syncthingtray#building-this-straight) or
|
||||
[build instructions for Tag Editor](https://github.com/Martchus/tageditor#building-this-straight).
|
||||
[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`. To tweak various settings (e.g. warnings) for development,
|
||||
use `-DENABLE_DEVEL_DEFAULTS=ON`.
|
||||
|
@ -166,46 +171,133 @@ 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.
|
||||
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) use `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).
|
||||
|
||||
The `win-x64-msvc-static` preset (and all presets inheriting from it) need various additional environment
|
||||
variables to be set:
|
||||
* `MSYS2_ROOT`: for Perl (only used by `qtforkawesome` so far), `clang-format`, Doxygen and FFmpeg provided
|
||||
via MSYS2 packages; install the following packages:
|
||||
##### 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 mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg
|
||||
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`
|
||||
* `QT_ROOT`: for Qt libraries provided by official Qt installer, e.g. `D:/programming/qt/6.5.0/msvc2019_64`
|
||||
* `QT_TOOLS`: for additional build tools provided by official Qt installer, e.g. `D:/programming/qt/Tools`
|
||||
* `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.
|
||||
|
@ -229,6 +321,6 @@ Checkout [Case_Of's overlay](https://codeberg.org/Case_Of/gentoo-overlay)
|
|||
or [perfect7gentleman's overlay](https://gitlab.com/Perfect_Gentleman/PG_Overlay).
|
||||
|
||||
## Copyright notice and license
|
||||
Copyright © 2015-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.")
|
||||
|
@ -110,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
|
||||
|
@ -129,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,39 @@ 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 (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE OR ${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
|
||||
message(
|
||||
WARNING
|
||||
"Overriding the RDNS or developer ID is NOT recommended. This feature is only intended to ease "
|
||||
"transitioning when a change is required and to create alternative packaging for development and private use.")
|
||||
endif ()
|
||||
if (NOT META_PROJECT_RDNS OR NOT META_DEVELOPER_ID)
|
||||
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
|
||||
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 +281,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
|
||||
|
@ -393,8 +414,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
|
||||
|
@ -402,7 +439,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 ()
|
||||
|
@ -418,21 +454,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 ()
|
||||
|
@ -597,7 +634,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")
|
||||
|
@ -690,6 +727,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)
|
||||
|
@ -59,7 +59,7 @@ function (configure_development_warnings)
|
|||
option(TREAT_WARNINGS_AS_ERRORS "adds additional compiler flag to treat warnings as errors" "${ENABLE_DEVEL_DEFAULTS}")
|
||||
if (TREAT_WARNINGS_AS_ERRORS)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror)
|
||||
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror -Wno-error=address)
|
||||
else ()
|
||||
message(AUTHOR_WARNING "Treating warnings as errors is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
|
||||
endif ()
|
||||
|
|
|
@ -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 */
|
|
@ -1,7 +1,16 @@
|
|||
// assert that CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL is defined; a value of:
|
||||
// - 0 means to define functions for BE namespace
|
||||
// - 1 means to define functions for LE namespace
|
||||
#ifndef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
|
||||
#error "Do not include binaryconversionprivate.h directly."
|
||||
#else
|
||||
|
||||
// define macro if swapping byte order is required
|
||||
#if (CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 && defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)) \
|
||||
|| (CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 1 && defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN))
|
||||
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
|
||||
#endif
|
||||
|
||||
// disable warnings about sign conversions when using GCC or Clang
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
|
@ -144,7 +153,7 @@ template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILIT
|
|||
{
|
||||
auto dst = T();
|
||||
std::memcpy(&dst, value, sizeof(T));
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
|
||||
dst = swapOrder(dst);
|
||||
#endif
|
||||
return dst;
|
||||
|
@ -159,11 +168,11 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf
|
|||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
outputbuffer[0] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value)&0xFF);
|
||||
outputbuffer[2] = static_cast<char>((value) & 0xFF);
|
||||
#else
|
||||
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
|
||||
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value)&0xFF);
|
||||
outputbuffer[0] = static_cast<char>((value) & 0xFF);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -176,7 +185,7 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf
|
|||
*/
|
||||
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline void getBytes(T value, char *outputbuffer)
|
||||
{
|
||||
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
|
||||
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
|
||||
value = swapOrder(value);
|
||||
#endif
|
||||
std::memcpy(outputbuffer, &value, sizeof(T));
|
||||
|
@ -206,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
|
||||
|
|
|
@ -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)
|
||||
|
|
22
io/copy.h
22
io/copy.h
|
@ -2,7 +2,8 @@
|
|||
#define IOUTILITIES_COPY_H
|
||||
|
||||
#include "./nativefilestream.h"
|
||||
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && defined(PLATFORM_LINUX)
|
||||
#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
|
||||
|
@ -103,7 +104,7 @@ void CopyHelper<bufferSize>::callbackCopy(std::istream &input, std::ostream &out
|
|||
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) {
|
||||
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();
|
||||
|
@ -114,9 +115,13 @@ template <std::size_t bufferSize> void CopyHelper<bufferSize>::copy(NativeFileSt
|
|||
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::size_t>(bytesCopied);
|
||||
count -= static_cast<std::uint64_t>(bytesCopied);
|
||||
}
|
||||
input.sync();
|
||||
output.sync();
|
||||
|
@ -126,6 +131,7 @@ template <std::size_t bufferSize> void CopyHelper<bufferSize>::copy(NativeFileSt
|
|||
input.seekp(inputTellp + totalBytes);
|
||||
return;
|
||||
}
|
||||
unoptimized_version:
|
||||
#endif
|
||||
copy(static_cast<std::istream &>(input), static_cast<std::ostream &>(output), count);
|
||||
}
|
||||
|
@ -146,7 +152,7 @@ void CopyHelper<bufferSize>::callbackCopy(NativeFileStream &input, NativeFileStr
|
|||
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) {
|
||||
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();
|
||||
|
@ -155,8 +161,13 @@ void CopyHelper<bufferSize>::callbackCopy(NativeFileStream &input, NativeFileStr
|
|||
output.flush();
|
||||
const auto totalBytes = static_cast<std::streamoff>(count);
|
||||
while (count) {
|
||||
const auto bytesCopied = ::sendfile64(output.fileDescriptor(), input.fileDescriptor(), nullptr, std::min(count, bufferSize));
|
||||
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);
|
||||
|
@ -174,6 +185,7 @@ void CopyHelper<bufferSize>::callbackCopy(NativeFileStream &input, NativeFileStr
|
|||
callback(1.0);
|
||||
return;
|
||||
}
|
||||
unoptimized_version:
|
||||
#endif
|
||||
callbackCopy(static_cast<std::istream &>(input), static_cast<std::ostream &>(output), count, isAborted, callback);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -190,7 +190,7 @@ void ArgumentParserTests::testParsing()
|
|||
CPPUNIT_ASSERT_EQUAL("album"sv, std::string_view(fieldsArg.values().at(0)));
|
||||
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
|
||||
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
|
||||
CPPUNIT_ASSERT_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
|
||||
|
@ -208,7 +208,7 @@ void ArgumentParserTests::testParsing()
|
|||
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
|
||||
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
|
||||
CPPUNIT_ASSERT_EQUAL(""sv, std::string_view(fieldsArg.values().at(3)));
|
||||
CPPUNIT_ASSERT_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)));
|
||||
|
||||
|
@ -267,7 +267,7 @@ void ArgumentParserTests::testParsing()
|
|||
CPPUNIT_ASSERT(!filesArg.isPresent());
|
||||
CPPUNIT_ASSERT(fileArg.isPresent());
|
||||
CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(fileArg.values().at(0)));
|
||||
CPPUNIT_ASSERT_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();
|
||||
|
@ -405,7 +405,7 @@ void ArgumentParserTests::testParsing()
|
|||
CPPUNIT_ASSERT_EQUAL("album=test"sv, std::string_view(fieldsArg.values().at(0)));
|
||||
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
|
||||
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
|
||||
CPPUNIT_ASSERT_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());
|
||||
|
@ -473,7 +473,7 @@ void ArgumentParserTests::testParsing()
|
|||
CPPUNIT_ASSERT_EQUAL("foo"sv, std::string_view(fieldsArg.values().at(0)));
|
||||
CPPUNIT_ASSERT_EQUAL("bar"sv, std::string_view(fieldsArg.values().at(1)));
|
||||
CPPUNIT_ASSERT_EQUAL("--help"sv, std::string_view(fieldsArg.values().at(2)));
|
||||
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), std::out_of_range);
|
||||
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -568,6 +568,7 @@ void IoTests::testCopyWithNativeFileStream()
|
|||
const auto isAborted = [] { return false; };
|
||||
const auto callback = [&percentage](double p) { percentage = p; };
|
||||
testFile.seekg(0);
|
||||
outputStream.close();
|
||||
outputStream.open(outputPath, ios_base::out | ios_base::trunc | ios_base::binary);
|
||||
copyHelper.callbackCopy(testFile, outputStream, 50, isAborted, callback);
|
||||
CPPUNIT_ASSERT_EQUAL(1.0, percentage);
|
||||
|
@ -649,7 +650,6 @@ void IoTests::testAnsiEscapeCodes()
|
|||
ss1 << EscapeCodes::color(EscapeCodes::Color::Blue, EscapeCodes::Color::Red, EscapeCodes::TextAttribute::Blink)
|
||||
<< "blue, blinking text on red background" << EscapeCodes::TextAttribute::Reset << '\n';
|
||||
cout << "\noutput for formatting with ANSI escape codes:\n" << ss1.str() << "---------------------------------------------\n";
|
||||
fstream("/tmp/test.txt", ios_base::out | ios_base::trunc) << ss1.str();
|
||||
CPPUNIT_ASSERT_EQUAL("\033[1;31mError: \033[0m\033[1msome error\033[0m\n"
|
||||
"\033[1;33mWarning: \033[0m\033[1msome warning\033[0m\n"
|
||||
"\033[1;34mInfo: \033[0m\033[1msome info\033[0m\n"
|
||||
|
|
|
@ -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
|
||||
|
@ -391,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().
|
||||
|
@ -411,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);
|
||||
|
@ -478,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
|
||||
|
@ -489,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);
|
||||
|
@ -497,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
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -512,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
|
||||
|
@ -572,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)
|
||||
{
|
||||
|
@ -587,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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -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