Compare commits

...

98 Commits

Author SHA1 Message Date
Martchus 0c652a774e Fix definition of GOROOT in presets
It needs to be an env variable; not a CMake variable.
2024-05-01 23:20:19 +02:00
Martchus 9c687bd723 Fix include directories of test targets after a7fdc1af1
The include directories need to be set for test targets as well; otherwise
they cannot compile unless tests link against the main target as well.
2024-04-26 22:55:58 +02:00
Martchus 65ffed8151 Remove reference to non-existent script 2024-04-13 17:06:41 +02:00
Martchus 957c044e63 Enable targets for mingw-w64 cross-packaging in mingw-w64 CMake presets 2024-04-13 17:02:54 +02:00
Martchus a4c18017b7 Avoid duplications in mingw-w64 CMake presets 2024-04-13 17:00:04 +02:00
Martchus 73a837962d Adapt the `arch-…-w64-mingw32-static` presets
Not sure what has changed but it seems that these extra variables are now
required to avoid linking against certain shared libraries.
2024-04-12 00:43:41 +02:00
Martchus 8502d1bc2a Add `arch-…-w64-mingw32…-devel-qt6` presets 2024-04-11 23:35:33 +02:00
Martchus afc3413e9c Add `arch-i686-w64-mingw32…`-presets 2024-04-11 23:28:19 +02:00
Martchus ae908283a0 Bump patch version 2024-04-08 12:34:27 +02:00
Martchus d31092b7d9 Apply clang-format 2024-04-08 12:33:57 +02:00
Martchus dfbf300c65 Expose name of default desktop file via config header 2024-04-08 12:33:40 +02:00
Martchus 909346c199 Fix reserving size for error message in `charToDigit()` 2024-02-27 02:16:52 +01:00
Martchus c9cd44ceee Apply clang-format 2024-02-22 19:41:29 +01:00
Martchus a43affa81a Avoid `global.h` changing depending on target prefix/suffix
Just using the project name should be sufficient and this avoid `global.h`
from changing when a different target prefix/suffix is used.
2024-02-22 19:41:15 +01:00
Martchus d8e144d312 Optimize `numberToString()`
This function is slower than it needs to be due to the expensive inserts at
the front of the string. The new version is 38 times faster for a 9-digit
number using GCC 13.2 with full optimizations according to Quick Bench.
2024-02-21 21:21:11 +01:00
Martchus a337452179 Fix typo in README 2024-02-16 17:42:15 +01:00
Martchus 6cb0e63921 Avoid warnings about unused return values by MSVC 2024-02-15 18:40:44 +01:00
Martchus a4625b8e34 Bump patch version 2024-02-15 18:40:11 +01:00
Martchus a4be8a56d1 Avoid problems with CppUnit's macros when doing unity builds 2024-02-04 20:56:27 +01:00
Martchus ce31de2c6f Fix tests when making a unity build
The formatting for chrono types needs to be included before CppUnit
headers. This change makes sure of that by simply including that header via
`tests/testutils.h`. The `chrono/format.h` header is not big (including the
header it includes) so this should not be a big deal.
2024-01-30 23:08:14 +01:00
Martchus bc00bdcdc9 Apply cmake-format 2024-01-30 22:38:52 +01:00
Martchus 57579f0164 Add preset for unity builds
This is not working for most of my projects due to conflicting macros.
2024-01-30 22:38:39 +01:00
Martchus a7fdc1af1b Allow writing public compile definitions to a header file
This is useful as it makes consuming libraries less dependent on using the
CMake module or pkg-config file correctly. This should especially decrease
the likelihood of running into linker errors when consuming a static build
of the libraries where e.g. `CPP_UTILITIES_STATIC` needs to be defined.
2024-01-30 22:13:25 +01:00
Martchus 27043d2be0 Fix typo in comment 2024-01-30 00:44:54 +01:00
Martchus b526d79eaf Always use a process group in helper for involing test applications
So far only the implementation using Boost.Process was using a process
group; with this change also the implementation using POSIX APIs uses a
process group. This way the code can wait until all sub processes have
terminated.
2024-01-28 21:55:13 +01:00
Martchus 995c315377 Allow semicolons in categories and additional entries for desktop file
Pass these variables as multi-value arguments to get more than just the
part before the first semicolon.
2024-01-27 02:54:27 +01:00
Martchus d08794b11d Allow setting `DESKTOP_FILE_ADDITIONAL_ENTRIES` manually 2024-01-27 02:51:41 +01:00
Martchus 1a0c4fbce0 Update copyright date 2024-01-23 00:25:55 +01:00
Martchus c25a3c9c9a Improve error handling when launching test process
* Use `EXIT_FAILURE` instead of an arbitrary exit status
* Print the error message
2024-01-20 17:38:14 +01:00
Martchus c9dea06cfe Remove outdated remarks about `execApp()` and similar test helpers
These functions are supported also on other platforms via Boost.Process.
2024-01-20 17:35:17 +01:00
Martchus ad686a1be7 Bump patch version 2024-01-20 17:33:47 +01:00
Martchus 85c76708c9 Mention setting GOROOT for building with MSYS2 mingw-w64 packaging 2024-01-02 15:32:14 +01:00
Martchus c17c8e7815 Set `GOROOT` for `x64-windows-static` preset in accordance with `GO_BIN`
Unfortunately the final linking still doesn't work due to conflicting
symbols (and using the ucrt64 version doesn't change that).
2023-12-30 19:34:22 +01:00
Martchus f97320816a Change Bash completion code for dirs/files to new coding style 2023-12-29 16:34:26 +01:00
Martchus 6f924da4f0 Make code for processing escaping in Bash completion more generic 2023-12-29 16:30:55 +01:00
Martchus b3b7166812 Fix bash completion when path contains round brackets 2023-12-29 16:09:55 +01:00
Martchus 8bffc93316 Improve instructions about building on Windows 2023-12-25 00:56:50 +01:00
Martchus 456bbfc54e Avoid hardcoding the versioned subdirectory within `WIN_KITS_ROOT` 2023-12-25 00:56:50 +01:00
Martchus b0be8817ad Bump patch version 2023-12-25 00:56:50 +01:00
Martchus d8605b50b0 Ensure `chars_format` is defined for older versions of libstdc++
Versions older than 10 don't even define this enum class. Note that libc++
always seems to define it even though it doesn't implement the FP overloads
at all at this point.

By the way, this means the minimum GCC version is 8 and the minimum libc++
version is 7 because for this code to work the `charconv` header still
needs to be present at all.
2023-12-11 19:59:46 +01:00
Martchus 1264a7e9c5 Remove no longer used include in `chrono/timespan.cpp` 2023-12-08 16:24:04 +01:00
Martchus b8dff49c01 Add missing include for `std::array`
See https://github.com/Martchus/cpp-utilities/issues/29
2023-12-07 10:44:27 +01:00
Martchus dd0ea1d348 Use workaround for unavailable `std::from_chars()` also under older libstdc++ versions 2023-12-06 22:34:33 +01:00
Martchus 21f32d318b Bump patch version 2023-12-06 22:32:28 +01:00
Martchus 8d460380f1 Apply cmake-format 2023-12-05 11:42:42 +01:00
Martchus fbb1d73779 Apply clang-format 2023-12-05 11:42:26 +01:00
Martchus 35f56d486c Fix enabling AppStream tests 2023-11-30 19:44:08 +01:00
Martchus 9d8f897972 Add presets for compiling with clang++ and libc++ 2023-11-25 23:18:50 +01:00
Martchus 55146db7e1 Add workaround for missing FP overload for `std::from_chars()` in libc++ 2023-11-25 23:18:30 +01:00
Martchus fdfe8f154c Fix warnings in code of `TimeSpan::fromString()` 2023-11-25 23:15:29 +01:00
Martchus b1f89d78c3 Allow building without CppUnit-based tests 2023-11-25 23:14:36 +01:00
Martchus 3649782c8c Update README section about dependencies 2023-11-23 20:29:43 +01:00
Martchus 905f81c8b7 Improve remark in README about disabling native file buffer 2023-11-23 20:26:14 +01:00
Martchus 7bed9cd38c Remove TODOs about parsing/printing custom formats
It makes likely more sense to implement that entirely in user code. The
parsing/printing functions for common formats have also been improved so
this is likely not very relevant anymore anyways.
2023-11-23 20:24:44 +01:00
Martchus 11de58141b Support units in `TimeSpan::fromString()` 2023-11-23 20:20:07 +01:00
Martchus a425363eac Improve `TimeSpan::fromString()`
* Avoid heap allocation and counting separators
* Avoid code repetition
* Throw an exception if too many parts are present (as the documentation
  suggests this function would do)
* Allow emptry separators as this seems useful
2023-11-23 18:48:48 +01:00
Martchus 8fb7de6fe0 Enable AppStream tests only by default via `ENABLE_DEVEL_DEFAULTS`
This test already fails on deprecations so it is likely not a good idea to
run it in general as it would cause needless failures.
2023-11-22 12:59:40 +01:00
Martchus a1bed55eda Enable tidy tests only by default via `ENABLE_DEVEL_DEFAULTS`
This test is only relevant for development. Additionally, the behavior of
clang-format differs slightly between versions so this can really cause
needlessly failures.
2023-11-22 12:57:59 +01:00
Martchus 05570c5c71 Improve status messages about CXX11-ABI 2023-11-22 12:54:04 +01:00
Martchus cc6576c417 Bump patch version 2023-11-22 12:51:07 +01:00
Martchus ac35a5fad1 Fix a few Doxygen warnings 2023-11-18 22:32:04 +01:00
Martchus 762131acf9 Fix AppStream validation tests 2023-11-14 02:14:26 +01:00
Martchus cded82a00b Move use of `KDE_INSTALL_DIR` from `debug-kde` into new preset `debug-kde-custom` 2023-11-14 00:02:50 +01:00
Martchus a2f9748c1a Apply clang-format 2023-11-14 00:01:27 +01:00
Martchus 938e441336 Extend documentation of the BitReader class
I asked ChatGPT to write documentation for this class and got this. It is
too hilarious to not copy it here verbatimly.
2023-11-02 16:48:00 +01:00
Martchus b2d4d0be01 Enable _FORTIFY_SOURCE=3 in mingw-w64 preset
In accordance with the mingw-w64-environment package
2023-10-30 20:22:02 +01:00
Martchus ff33454bf1 Skip rpath in arch-static-compat presets
It should not be required to load dynamic libraries that
are not already in standard paths with these builds and
loading libraries from the static-compat prefix should be
avoided.
2023-10-27 20:46:19 +02:00
Martchus aa298772c9 Adjust arch-static-compat preset for PianoBooster 2023-10-27 20:20:45 +02:00
Martchus 7dff72d0bd Apply cmake-format 2023-10-15 16:57:29 +02:00
Martchus 938da48da0 Skip configuration of tests unless `BUILD_TESTING` is set
See https://github.com/Martchus/tagparser/issues/26
and https://cmake.org/cmake/help/latest/module/CTest.html
2023-09-16 22:01:32 +02:00
Martchus c71f835ad0 Bump patch version 2023-09-16 22:01:32 +02:00
Martchus 053ac7e1ad Disable KDE integrations in `devel-qt6` preset, enable then only in `debug-kde`
Since the required KDE packages haven't been released yet it makes sense to
avoid enabling them for now in the generic preset for Qt 6.
2023-09-04 20:29:10 +02:00
Martchus 035a448da0 Avoid clazy warning about `decodeBase64`
The warning is about invalid memory usage within the loop in
case `strSize` is zero. It is supposedly not correct because
then the loop would never be entered anyways in that case.
However, it is likely nevertheless a good idea to silence the
warning.
2023-09-01 17:21:18 +02:00
Martchus 8d28ab70b3 Use `std::` consistently in `decodeBase64` 2023-09-01 17:18:39 +02:00
Martchus 132d25fbb1 Apply clang-format 2023-08-20 20:29:54 +02:00
Martchus 0349037711 Fix passing non-ASCII arguments in `execApp()` under Windows 2023-08-19 00:11:06 +02:00
Martchus af200403de Support `execApp()` test helper under Windows as well via Boost.Process 2023-08-18 22:57:54 +02:00
Martchus 18d92fee40 Fix typo in CLI wrapper code 2023-08-18 22:55:42 +02:00
Martchus 3bca5c224d Document how to build/install projects individually under Windows 2023-08-18 12:04:26 +02:00
Martchus 6660ff7eca Improve section about building on Windows via MSYS2
* Make it clear where to find the "Building this straight" section
* Give more details about reducing the list of dependencies to be
  installed
2023-08-17 00:15:22 +02:00
Martchus 2137568ad8 Remove debugging leftover in IO tests 2023-08-09 01:26:23 +02:00
Martchus 6a8431da0a Fix missing closing bracket in README 2023-08-09 01:25:55 +02:00
Martchus c4024ce00e Avoid CMake deprecation warning by bumping version 2023-07-23 21:18:25 +02:00
Martchus 7bcc66be0d Avoid warning "current scope has no parent" 2023-07-06 00:50:45 +02:00
Martchus 5ebbd0eb3f Improve logic for finding CppUnit
* Avoid forcefully setting cache variables; use a normal variable
  instead
* Use the imported target generated from the pkg-config which hopefully
  works better than using the variables directly
* Avoid warning when the find module was used; this is the case for
  vcpkg and there the provided CppUnit library is good enough
2023-07-05 14:18:32 +02:00
Martchus 04682d4601 Bump patch version 2023-07-05 14:17:59 +02:00
Martchus 6826546196 Improve documentation for namespace/config build variables
See https://github.com/Martchus/cpp-utilities/issues/25
2023-06-21 12:16:13 +02:00
Martchus 08a1c5c2eb Close output stream in IO tests before re-opening 2023-06-20 14:03:36 +02:00
Martchus 9191117120 Fix binary conversion functions for big endian systems
The code was broken on big endian systems by
07e9546855. When doing an explicit swap
one must distinguish the endianness the code runs on.
2023-06-20 14:03:36 +02:00
Martchus 30cefc2fd3 Add version details to config header 2023-06-17 22:53:48 +02:00
Martchus 38541f4c60 Bump minor version 2023-06-17 22:46:55 +02:00
Martchus 831c083e5f Add flags for static linkage when building CLI wrapper as well
Otherwise the wrapper might depend on `libgcc` or `libstdc++` on builds
that link against these libraries otherwise statically. Not sure why this
is only an issue on 32-bit builds. (The different exeption handling can
only explain `libgcc` but not `libstdc++`.)
2023-06-10 18:29:18 +02:00
Martchus dd95310c73 Suppress warning about implicit sign conversions
The conversion from unsigned int to int should be ok here.
2023-06-10 18:14:06 +02:00
Martchus fc651c71ff Improve build instructions further
* Avoid mentioning setting for library suffix as this most likely does not
  need manual tweaking anymore anyways
* Add a few additional remarks
* Use a simpler example for CMake presets and document special presets only
  in a further section
* Mention how to build on Windows via MSYS2 mingw64 (and not *only* via
  MSVC which is definitely more complicated)
2023-06-10 18:12:28 +02:00
Martchus 3bec473775 Enable clang-format and cmake-format only by default if present
Enabling both depending on `ENABLE_DEVEL_DEFAULTS` limits the usefulness of
`ENABLE_DEVEL_DEFAULTS` because it can then only be used of both tools are
installed (and especially `cmake-format` might not be installed). It makes
more sense to simply enable those targets depending on whether the tools
are installed or not. If they are explicitly enabled it is still a hard
error if the tools cannot be found.
2023-06-10 16:41:07 +02:00
Martchus c111d9f374 Clear Vulkan path in MSVC preset
I don't need it currently for my projects and it gets accidentally set to
the mingw-w64 include path which is wrong for a MSVC build.
2023-06-08 14:24:05 +02:00
Martchus 0057e49a0d Fix linking against static OpenSSL on GNU/Linux
Judging by the code the CMake find module actually attempts to cover this
case but it doesn't seem to work in practice - at least not when there are
only static libs and thus we find those static libs without explicitly
specifying `OPENSSL_USE_STATIC_LIBS`.
2023-06-07 23:58:49 +02:00
Martchus cfe67a1078 Bump patch version 2023-06-07 21:50:41 +02:00
40 changed files with 885 additions and 275 deletions

View File

@ -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,10 +96,6 @@ 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 SCRIPT_FILES scripts/wine.sh)
endif ()
if (WIN32)
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in cmake/templates/windows-cli-wrapper.rc.in
cmake/templates/cli-wrapper.cpp)
@ -120,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 23)
set(META_VERSION_PATCH 0)
set(META_VERSION_MINOR 24)
set(META_VERSION_PATCH 8)
# 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
@ -157,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}
@ -169,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)

View File

@ -21,6 +21,18 @@
"WEBVIEW_PROVIDER": {"type": "STRING", "value": "none"}
}
},
{
"name": "libc++",
"inherits": "default",
"displayName": "Use clang++ and libc++",
"description": "Enforces use of clang++ and libc++ even when it is not the system default",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-no-webview",
"cacheVariables": {
"CMAKE_C_COMPILER": {"type": "STRING", "value": "clang"},
"CMAKE_CXX_COMPILER": {"type": "STRING", "value": "clang++"},
"CMAKE_CXX_FLAGS": {"type": "STRING", "value": "$env{CXXFLAGS} -stdlib=libc++"}
}
},
{
"name": "no-kde",
"inherits": "default",
@ -69,6 +81,16 @@
"CONFIGURATION_TARGET_SUFFIX": {"type": "STRING", "value": "devel"}
}
},
{
"name": "devel-libc++",
"inherits": ["devel", "libc++"],
"displayName": "Development config using libc++",
"description": "Combination of devel and libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++",
"cacheVariables": {
"ENABLE_CPP_UNIT": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "devel-qt6",
"inherits": ["qt6", "devel"],
@ -79,9 +101,28 @@
"QT_PACKAGE_PREFIX": {"type": "STRING", "value": "Qt6"},
"QT_MAJOR_VERSION": {"type": "STRING", "value": "6"},
"KF_PACKAGE_PREFIX": {"type": "STRING", "value": "KF6"},
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"}
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"},
"NO_PLASMOID": {"type": "BOOL", "value": "ON"},
"NO_FILE_ITEM_ACTION_PLUGIN": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-unity",
"inherits": ["devel-qt6"],
"displayName": "Development config creating a unity build using Qt 6",
"description": "Same as devel-qt6 but configures makes a unity build",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-unity",
"cacheVariables": {
"CMAKE_UNITY_BUILD": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-libc++-qt6",
"inherits": ["qt6", "devel-libc++"],
"displayName": "Development config using libc++ and Qt 6",
"description": "Combination of qt6 and devel-libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++-qt6"
},
{
"name": "debug",
"inherits": "devel",
@ -102,17 +143,61 @@
{
"name": "debug-kde",
"inherits": "debug-qt6",
"displayName": "Generic debug build with development config using custom KDE build",
"description": "Same as devel but creates a debug build",
"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-x86_64-w64-mingw32",
"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",
@ -120,20 +205,29 @@
"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",
"CPPFLAGS": "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS",
"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",
@ -142,9 +236,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"],
@ -152,6 +256,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"],
@ -159,6 +270,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"],
@ -166,6 +284,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"],
@ -190,10 +336,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"}
}
},
{
@ -210,9 +360,10 @@
"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"},
@ -224,9 +375,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"},
@ -242,6 +393,7 @@
"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"}
}
},
@ -272,17 +424,31 @@
],
"buildPresets": [
{"name": "default", "configurePreset": "default"},
{"name": "libc++", "configurePreset": "libc++"},
{"name": "qt6", "configurePreset": "qt6"},
{"name": "devel", "configurePreset": "devel"},
{"name": "devel-libc++", "configurePreset": "devel-libc++"},
{"name": "devel-qt6", "configurePreset": "devel-qt6"},
{"name": "devel-unity", "configurePreset": "devel-unity"},
{"name": "devel-libc++-qt6", "configurePreset": "devel-libc++-qt6"},
{"name": "debug", "configurePreset": "debug"},
{"name": "debug-qt6", "configurePreset": "debug-qt6"},
{"name": "debug-kde", "configurePreset": "debug-kde"},
{"name": "debug-kde-custom", "configurePreset": "debug-kde-custom"},
{"name": "arch-i686-w64-mingw32", "configurePreset": "arch-i686-w64-mingw32"},
{"name": "arch-x86_64-w64-mingw32", "configurePreset": "arch-x86_64-w64-mingw32"},
{"name": "arch-i686-w64-mingw32-static", "configurePreset": "arch-i686-w64-mingw32-static"},
{"name": "arch-x86_64-w64-mingw32-static", "configurePreset": "arch-x86_64-w64-mingw32-static"},
{"name": "arch-i686-w64-mingw32-qt6", "configurePreset": "arch-i686-w64-mingw32-qt6"},
{"name": "arch-x86_64-w64-mingw32-qt6", "configurePreset": "arch-x86_64-w64-mingw32-qt6"},
{"name": "arch-i686-w64-mingw32-static-qt6", "configurePreset": "arch-i686-w64-mingw32-static-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-qt6"},
{"name": "arch-i686-w64-mingw32-devel", "configurePreset": "arch-i686-w64-mingw32-devel"},
{"name": "arch-x86_64-w64-mingw32-devel", "configurePreset": "arch-x86_64-w64-mingw32-devel"},
{"name": "arch-i686-w64-mingw32-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-devel-qt6"},
{"name": "arch-i686-w64-mingw32-static-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-static-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-devel-qt6"},
{"name": "arch-static-compat", "configurePreset": "arch-static-compat"},
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"},
{"name": "win-x64-msvc-static", "configurePreset": "win-x64-msvc-static"},

152
README.md
View File

@ -61,11 +61,11 @@ These build instructions apply to `c++utilities` but also to my other projects u
* C++ compiler supporting C++17, tested with
- g++ to compile for GNU/Linux and Windows
- clang++ to compile for GNU/Linux and Android
* CMake (at least 3.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,39 +171,99 @@ 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:
##### 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 mingw-w64-x86_64-go
pacman -Syu perl-YAML mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-doxygen mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-go
```
* `MSVC_ROOT`: for compiler and stdlib usually installed as part of Visual Studio setup, e.g.
`C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933`
* `WIN_KITS_ROOT`: for Windows platform headers/libraries usually installed as part of Visual Studio setup,
e.g. `C:/Program Files (x86)/Windows Kits/10`
* `WIN_KITS_VERSION`: the relevant subdirectory within `WIN_KITS_ROOT`, usually a version number like `10.0.22621.0`
* `QT_ROOT`: for Qt libraries provided by the official Qt installer, e.g. `D:/programming/qt/6.5.0/msvc2019_64`
* `QT_TOOLS`: for additional build tools provided by the official Qt installer, e.g. `D:/programming/qt/Tools`
* `VCPKG_ROOT`: directory of VCPKG checkout used for other dependencies; install the following packages:
@ -206,6 +271,33 @@ variables to be set:
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).

View File

@ -1323,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
@ -1508,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 {
@ -1537,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);
}
@ -1790,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));
}
/*!
@ -1801,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

View File

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

View File

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

View File

@ -47,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().
*/

View File

@ -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]);
}
}

View File

@ -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"

View File

@ -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.")
@ -120,6 +120,7 @@ if (BUILD_CLI_WRAPPER)
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 ()

View File

@ -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
@ -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)

View File

@ -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)
@ -260,9 +260,9 @@ endif ()
option(FORCE_OLD_ABI "specifies whether usage of libstdc++'s old ABI should be forced" OFF)
if (FORCE_OLD_ABI)
list(APPEND META_PRIVATE_COMPILE_DEFINITIONS _GLIBCXX_USE_CXX11_ABI=0)
message(STATUS "Forcing usage of old CXX11 ABI of libstdc++.")
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 +393,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 +418,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 +433,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 +613,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")

View File

@ -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

View File

@ -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)

View File

@ -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.")

View File

@ -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")

View File

@ -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)

View File

@ -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.")

View File

@ -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)

View File

@ -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(

View File

@ -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}"

View File

@ -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

View File

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

View File

@ -46,7 +46,7 @@ int main()
std::wcscpy(pathBuffer + filenameStart + appendixStart, L".exe");
// compute startup parameters
auto commadLine = GetCommandLineW();
auto commandLine = GetCommandLineW();
auto processInformation = PROCESS_INFORMATION();
auto startupInfo = STARTUPINFOW();
ZeroMemory(&startupInfo, sizeof(startupInfo));
@ -59,7 +59,7 @@ int main()
// start main executable in new group and print debug information if that's not possible
auto res = CreateProcessW(pathBuffer, // path of main executable
commadLine, // command line arguments
commandLine, // command line arguments
nullptr, // process handle not inheritable
nullptr, // thread handle not inheritable
true, // set handle inheritance to true
@ -71,7 +71,7 @@ int main()
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: " << commadLine << L'\n';
std::wcerr << L" - assumed command-line: " << commandLine << L'\n';
return EXIT_FAILURE;
}

View File

@ -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@"

View File

@ -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

View File

@ -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

View File

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

View File

@ -215,14 +215,14 @@ std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputB
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(), 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;
@ -240,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;
@ -414,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;
@ -450,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");
}
@ -465,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

View File

@ -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));
}

View File

@ -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,

View File

@ -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

View File

@ -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.
*/
/*!

View File

@ -60,7 +60,7 @@ inline void BufferSearch::operator()(std::string_view buffer)
}
/*!
* \brief Processes the specified \a buffer which is a shared array with fixed \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)

View File

@ -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.

View File

@ -190,7 +190,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("album"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_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());
}
/*!

View File

@ -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);
}
/*!

View File

@ -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"

View File

@ -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.

View File

@ -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.
@ -287,6 +298,16 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
*
* \remarks Requires cppunit.
*/
#ifdef CPP_UTILITIES_BOOST_PROCESS
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto status = execApp(args, stdout, stderr); \
if (status != expectedExitStatus) { \
CPPUNIT_FAIL(::CppUtilities::argsToString( \
"app exited with status ", status, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#else
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto status = execApp(args, stdout, stderr); \
@ -298,6 +319,7 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
"app exited with status ", exitStatus, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#endif
/*!
* \brief Asserts whether the specified \a string matches the specified \a regex.