Compare commits

...

185 Commits

Author SHA1 Message Date
Martchus 5fe20bc406 Specify the developer ID in AppStream meta-data
See https://github.com/Martchus/syncthingtray/issues/261#issuecomment-2115804196
2024-05-16 22:46:22 +02:00
Martchus 054612f753 Install AppData file with `.metainfo.xml` extension as this is the standard
According to https://docs.fedoraproject.org/en-US/packaging-guidelines/AppData
the extension `.metainfo.xml` is now the standard and `.appdata.xml` is
only supported for historical reasons.
2024-05-15 18:00:48 +02:00
Martchus 14e9561e16 Allow overriding the RDNS used in AppStream meta-data files 2024-05-15 15:31:10 +02:00
Martchus beee01c147 Fix deducing rdns meta-data if the URL actually points to GitHub pages 2024-05-15 15:24:11 +02:00
Martchus 9bb224c2ba Remove redundant `CPPFLAGS` from CMakePresets.json 2024-05-14 09:21:16 +02:00
Martchus d2928517c5 Bump patch version 2024-05-14 09:19:51 +02:00
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
Martchus 66b6fff0f2 Fix greediness of greedy arguments if they have sub arguments
Even sub arguments (e.g. `--help`) should be treated as values to be passed
to a nested argument parser (as documented). The sub arguments are only
added in this case to appear in the help.
2023-05-29 18:09:34 +02:00
Martchus 5bfce4235e Fix typo 2023-05-29 18:05:11 +02:00
Martchus 5d5ccb7729 Add further debugging presets and set KF6 prefix for Qt 6 presets 2023-05-21 20:08:31 +02:00
Martchus b9097a3750 Add GO_BIN to MSVC preset for libsyncthing 2023-05-16 21:50:14 +02:00
Martchus a19ee41a39 Apply cmake-format and clang-format 2023-05-16 21:39:10 +02:00
Martchus 0341316b4b Fix warning from MSVC about data loss 2023-05-10 22:06:59 +02:00
Martchus 0d0685d4c7 Simplify workaround for starting console and CLI-wrapper
* Disable workaround by default; with the CLI-wrapper available it makes no
  sense to run this code unnecassarily when the main executable is invoked
    * Remove check for Mintty; with the workaround disabled by default it
      is no longer necassary to avoid it
* Simplify the CLI-wrapper to rely on main application for enabling UTF-8
  and virtual terminal processing as it relies on it for attaching to the
  parent's console anyways
2023-05-09 00:16:28 +02:00
Martchus c6396f92fc Ignore warnings for use of legacy CRT functions when compiling with MSVC for now
There's no warning about these when compiling for other targets so let's
ignore them for now.
2023-05-07 21:52:06 +02:00
Martchus c60584e122 Allow compilation of Windows resource file when using MSVC as well 2023-05-07 21:34:46 +02:00
Martchus f3cb406ebe Add CLI-wrapper for Windows
Starting the console from a GUI application is not working very
well - so let's just provide a 2nd executable for the CLI. It
will be a simple console application that merely invokes the main
application passing all standard I/O. Unfortunately this does not
mean the existing hacks can be removed. Without them the wrapper
still doesn't get any I/O from the GUI application.
2023-05-07 21:32:21 +02:00
Martchus caace3ac63 Improve workaround for starting console from GUI app
It is still not perfect but now at least:

* Redirections to a file and pipes are no longer broken with `cmd.exe`
* Pipes are no longer broken in PowerShell
* It works at all when using MSVC
* The whole workaround is skipped for Mintty (as it is not needed at all)
* Sending a return key to the terminal in the end has been removed as it
  caused more harm than good in certain terminals and did not work in
  terminals other than `cmd.exe` anyways
2023-05-07 20:39:22 +02:00
Martchus 5e325d0b92 Avoid unqualified call to `std::move` 2023-05-05 01:06:06 +02:00
Martchus 5425488421 Fix preset for debug build with MSVC
* The preset must inherit from "debug" first so the build type is
  not overridden
* Use the debug library of cppunit
* Avoid configuring ccache which is not generally used under
  Windows
2023-05-01 19:25:18 +02:00
Martchus cb5ca77658 Improve test helper for launching an app
* Add helper that allows to specify the exit code (in case a non-zero
  exit code is actually expected)
* Use the proper macros to inspect the status returned by `waitpid()` to
  print/check the exit status correctly
2023-04-29 13:06:31 +02:00
Martchus 93a632e831 Bump minor version 2023-04-29 12:53:46 +02:00
Martchus ad39cbd604 Apply clang-format 2023-04-29 12:53:18 +02:00
Martchus e8b4279062 Don't use sendfile64() if input and output are the same to avoid EBADF
This does not work and leads to sendfile64() running into EBADF ("Bad file
descriptor").
2023-04-23 21:21:32 +02:00
Martchus a55a3604bd Fallback from sendfile64() in case of ENOSYS as well
As suggested on https://man7.org/linux/man-pages/man2/sendfile.2.html.
2023-04-23 21:04:31 +02:00
Martchus da91e54ec7 Disable use of sendfile64() by default
When changing tagparser to actually make use of the overloads
that use sendfile64() it breaks with "Bad file descriptor". So
better disable this until it has been better tested.
2023-04-23 20:51:33 +02:00
Martchus 83025c17a1 Fallback to unoptimized version if sendfile64() fails
It might fail on some filesystems such as `ecryptfs`, see
https://github.com/Martchus/cpp-utilities/issues/24.
2023-04-23 19:38:56 +02:00
Martchus 2467c4f815 Make use of platform-specific APIs for optimizing CopyHelper configurable
There might be unexpected limitations like
https://github.com/Martchus/cpp-utilities/issues/24. To be able to at least
workaround those limitations it makes sense to allow disabling the
optimization completely.
2023-04-23 19:23:47 +02:00
Martchus 10a7e4d814 Bump patch version 2023-04-06 18:09:42 +02:00
Martchus 59896da087
Merge pull request #23 from ahesford/minimalism
Fix use of `sendfile()` on 32-bit systems
2023-04-06 18:06:59 +02:00
Andrew J. Hesford c9131ce6e7 Fix use of `sendfile()` on 32-bit systems 2023-04-06 11:36:06 -04:00
Martchus 4316dfd846 Fix formatting in README 2023-04-04 17:10:34 +02:00
Martchus 873cf513c3 Improve README
* Wrap long lines
* Improve wording
* Mention icon bundling
* Remove obsolete notes
* List requires MSYS2 and VCPKG packages for Windows builds using CMake
  preset
2023-03-30 21:37:51 +02:00
Martchus 48aa1f54e0 Add `FFMPEG_BIN` to Windows preset 2023-03-30 21:33:42 +02:00
Martchus b426443aaf Workaround issues with `win-x64-msvc-static` preset
* Set CXX flags manually to specify the `/MT` flag explicitly; this is
  needed as compilations of moc objects apparently don't pick-up the
  variable `CMAKE_MSVC_RUNTIME_LIBRARY` and instead use what the Qt build
  had used (which breaks the compilation when using a shared Qt build with
  this setup which otherwise prefers static libraries)
* Set build type explicitly to release as otherwise a debug build would be
  made (but we have `win-x64-msvc-static-debug` for that)
* Specify the VCPKG target triplet to prefer static libs
* Disable the Qt Quick GUI for now; otherwise passwordmanager is trying to
  pull-in kirigami
* Remove most hardcoded paths for dependencies; with the correct VCPKG
  target triplet those paths can be auto-detected (except for CppUnit)
2023-03-29 23:40:57 +02:00
Martchus 2a123df86d Avoid use of non-standard escape character to avoid MSVC warning about it 2023-03-26 21:49:54 +02:00
Martchus bcd5816d23 Apply clang-format and cmake-format 2023-03-25 18:52:13 +01:00
Martchus d94e49620d Add paths for Doxygen and clang-format to Windows preset 2023-03-23 21:51:57 +01:00
Martchus c3470d0378 Improve build instructions
* Move packaging info into its own section
* Link build instructions for Tag Editor as well as they differ
  slightly from Syncthing Tray
* Use consistent path separator for Windows-specific variables
2023-03-20 23:01:00 +01:00
Martchus 9bd7dbc41c Use Ninja in build example 2023-03-20 22:49:26 +01:00
Martchus 9b1a7b3bf4 Fix includes in `copy.h` when using `sendfile()` 2023-03-19 20:09:14 +01:00
Martchus ec891b48f6 Use `sendfile()` to speed up copying 2023-03-19 20:02:32 +01:00
Martchus 7a5a02976a Fix symbol visibility when building Android apps 2023-03-19 20:02:32 +01:00
Martchus 6e28bec4c5 Add REALPATH_BIN to Windows-preset for CMake bug workaround 2023-03-16 01:02:49 +01:00
Martchus 65b86d71f6 Set QT_ANDROID_VERSION_NAME for Android target 2023-03-11 19:59:09 +01:00
Martchus ab298b200f Apply clang-format 2023-03-11 17:05:08 +01:00
Martchus 2a9949ce77 Tweak app target creation for Android deployment via Qt 6 helpers 2023-03-11 17:04:41 +01:00
Martchus e2593cea2a Bump minor version 2023-03-11 17:00:04 +01:00
Martchus 6ec77a1d1c Add `extractNativePath()` as counterpart to `makeNativePath()` 2023-03-06 22:17:44 +01:00
Martchus 283c416a59 Add preset for building on Windows with MSVC 2023-03-06 22:17:37 +01:00
Martchus 4aea7f5a57 Set `KF_PACKAGE_PREFIX` as well when building against Qt 6 2023-03-05 12:26:33 +01:00
Martchus 169756fce4 Fix loop-condition in `iotests.cpp` 2023-02-28 21:18:15 +01:00
Martchus 30819d9e85 Improve condition for use of `pubsetbuf`
Using this function like this seems only be possible with `libstdc++`. The
standard lib of MSVC and Clang both don't support this (so it is not just
MSVC and thus not Windows-specific).
2023-02-28 21:17:33 +01:00
Martchus 78d57902a7 Workaround MSVC-specific issues in testsuite so tests pass with MSVC 2023-02-28 00:07:56 +01:00
Martchus 2b6f26895d Support multiple source directories in `srcdirref` file
* Locate test files in all source directories specified in `srcdirref`
* Allow overriding contents of `srcdirref` so the directory of another
  component (within the same repository) can be appended to share testfiles
2023-02-27 18:29:31 +01:00
Martchus a703813b4c Mark helper functions of `testutils.cpp` as static; they're not used elsewhere 2023-02-27 18:29:21 +01:00
Martchus 07e9546855 Avoid relying on compiler optimizations for binary conversions
* Add/update binary conversion functions to use `std::memcopy`
* Only GCC could actually optimize the custom code using bit operations
    * Clang could only optimize the `getBytes()` functions
    * MSVC could not optimize any of the functions
* The to…Int…() functions cannot be updated as they are `constexpr` (and
  thus `std::memcopy` and `reinterpret_cast` cannot be used). So they stay
  as-is and `toInt()` has been added instead
* The `BinaryReader` class has been updated to use the new `toInt()`
  function
2023-02-05 21:29:22 +01:00
Martchus 147d36a578 Improve messages of conversion tests 2023-02-05 21:21:26 +01:00
Martchus 31f369d051 Add signed versions of `swapOrder()` functions 2023-02-05 21:20:53 +01:00
Martchus 66621d757b Bump minor release 2023-02-05 20:40:24 +01:00
Martchus 3b2615fa62 Move `BE`/`LE` namespaces to the end so these functions can use previous helpers 2023-02-05 20:17:32 +01:00
Martchus b3fd365502 Use `std::byteswap()` when compiling in C++23 mode 2023-02-05 20:15:29 +01:00
Martchus 8588c17df3 Remove wrong includes in `binaryconversionprivate.h`
This header is not meant to be self-contained and must not include any
other headers.
2023-02-05 19:57:36 +01:00
Martchus 7d6fc9721a Apply clang-format/cmake-format 2023-02-03 13:31:18 +01:00
Martchus be6626f6a6 Fix using percentage operators of `stringbuilder.h` with MSVC 2023-02-01 00:43:53 +01:00
Martchus 024865cc44 Fix building conversion tests with MSVC 2023-02-01 00:42:44 +01:00
Martchus b9b8bfc62a Fix and improve code for finding CppUnit
* Remove `FORCE` in initialization of cache variables as this makes the
  library/include dir effectively *not* configurable
* Try using `find_package()` as the vcpkg package provides a CMake module
2023-01-31 22:37:06 +01:00
Martchus 25e4eebb64 Avoid MSVC warning about possible overflow 2023-01-31 22:37:06 +01:00
Martchus 4d96a82ed9 Workaround unfortunate macro definition in `windows.h` 2023-01-31 22:37:06 +01:00
Martchus a5a33bbcef Add missing include for `std::tuple` to `argumentparser.h` 2023-01-31 22:37:06 +01:00
Martchus da830dcef5 Apply clang-format and cmake-format 2023-01-28 20:20:47 +01:00
Martchus 3a4d71ef5c Avoid unqualified call to `std::move` 2023-01-28 20:20:30 +01:00
Martchus 53ce099bf9 Apply clang-format 2023-01-28 18:49:54 +01:00
Martchus d1eeae83c6 Fix includes for command-line utilities
Most importantly, import `io.h` under Windows as it is required for using
`_open_osfhandle`.
2023-01-26 22:26:42 +01:00
Martchus b6fa13f8dd Use C++ 20 when compiling with MSVC as it requires it for designated initializers 2023-01-26 22:23:46 +01:00
Martchus 38fa3512fd Disable assert for argument name for MSVC to avoid compilation error
Not sure what MSVC complains about here exactly so let's just disable this
code as it is only for debugging anyways.
2023-01-26 22:23:12 +01:00
Martchus d4fbe5f43d Use `std::` consistently in `convertMultiByteToWide()` 2023-01-26 22:20:59 +01:00
Martchus 16f17fb9de Make feature detection for thead local work with MSVC 2023-01-26 22:20:20 +01:00
Martchus e02fed2b57 Add directories of Visual Studio to `.gitignore` 2023-01-26 22:18:43 +01:00
Martchus 16782fc547 Improve formatting in `.gitignore` 2023-01-26 22:18:22 +01:00
Martchus 81618a3468 Expose package version via `use_package` function 2023-01-22 23:01:10 +01:00
Martchus bdc96ade41 Update copyright notice 2023-01-17 18:34:13 +01:00
Martchus 23ca57740b Apply cmake-format 2022-12-24 23:15:47 +01:00
Martchus c48816b243 Move CMake code for enabling warnings into separate module and function 2022-12-24 23:15:24 +01:00
Martchus d9eb99fca1 Allow to keep CMake's CXX_STANDARD property empty
This can be useful to e.g. specify the standard manually or to just stick
to the compiler's default.
2022-12-23 20:52:00 +01:00
Martchus 1aba9f5f6f Allow setting Windows/MacOS icon paths to avoid conversion
This makes it possible to have an own version of the icon for those
platforms instead of relying on an automatic conversion from the generic
PNG icon.
2022-12-22 23:28:05 +01:00
Martchus d8c38699ba Avoid warning about unqualified std cast 2022-11-04 16:50:13 +01:00
Martchus a9ea4f4f47 Bump patch version 2022-11-04 16:49:25 +01:00
52 changed files with 2095 additions and 757 deletions

9
.gitignore vendored
View File

@ -26,14 +26,17 @@ ui_*.h
Makefile*
*-build-*
# QtCreator
# Qt Creator
*.autosave
#QtCtreator Qml
# Qt Ctreator Qml
*.qmlproject.user
*.qmlproject.user.*
# Visual Studio
/.vs
/out
# Dolphin
.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)
project(c++utilities)
@ -84,6 +84,7 @@ set(CMAKE_MODULE_FILES
cmake/modules/Doxygen.cmake
cmake/modules/ListToString.cmake
cmake/modules/ShellCompletion.cmake
cmake/modules/DevelUtilities.cmake
cmake/modules/3rdParty.cmake)
set(CMAKE_TEMPLATE_FILES
cmake/templates/bash-completion.sh.in
@ -95,11 +96,11 @@ set(CMAKE_TEMPLATE_FILES
cmake/templates/global.h.in
cmake/templates/version.h.in
cmake/templates/template.pc.in)
set(SCRIPT_FILES)
if (MINGW)
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in)
list(APPEND SCRIPT_FILES scripts/wine.sh)
if (WIN32)
list(APPEND CMAKE_TEMPLATE_FILES cmake/templates/windows.rc.in cmake/templates/windows-cli-wrapper.rc.in
cmake/templates/cli-wrapper.cpp)
endif ()
set(EXCLUDED_FILES cmake/templates/cli-wrapper.cpp)
set(DOC_FILES README.md doc/buildvariables.md doc/testapplication.md)
set(EXTRA_FILES tests/calculateoverallcoverage.awk coding-style.clang-format)
@ -115,14 +116,15 @@ set(META_APP_AUTHOR "Martchus")
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
set(META_APP_DESCRIPTION "Useful C++ classes and routines such as argument parser, IO and conversion utilities")
set(META_VERSION_MAJOR 5)
set(META_VERSION_MINOR 20)
set(META_VERSION_PATCH 0)
set(META_VERSION_MINOR 24)
set(META_VERSION_PATCH 9)
# find required 3rd party libraries
include(3rdParty)
use_iconv(AUTO_LINKAGE REQUIRED)
# configure use of native file buffer and its backend implementation if enabled
set(REQUIRED_BOOST_COMPONENTS "")
set(USE_NATIVE_FILE_BUFFER_BY_DEFAULT OFF)
if (WIN32
OR ANDROID
@ -152,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}
@ -164,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)
@ -194,6 +216,13 @@ if (NOT ENABLE_THREAD_LOCAL)
PROPERTY COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_NO_THREAD_LOCAL)
endif ()
# configure use of platform-specific APIs for optimizing CopyHelper
option(USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER
"enables use of platform-specific APIs for optimizing CopyHelper" OFF)
if (USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER)
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_PLATFORM_SPECIFIC_API_FOR_OPTIMIZING_COPY_HELPER)
endif ()
# include modules to apply configuration
include(BasicConfig)
include(WindowsResources)

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",
@ -40,6 +52,7 @@
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/default-qt6",
"cacheVariables": {
"QT_PACKAGE_PREFIX": {"type": "STRING", "value": "Qt6"},
"KF_PACKAGE_PREFIX": {"type": "STRING", "value": "KF6"},
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"}
}
},
@ -68,6 +81,16 @@
"CONFIGURATION_TARGET_SUFFIX": {"type": "STRING", "value": "devel"}
}
},
{
"name": "devel-libc++",
"inherits": ["devel", "libc++"],
"displayName": "Development config using libc++",
"description": "Combination of devel and libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++",
"cacheVariables": {
"ENABLE_CPP_UNIT": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "devel-qt6",
"inherits": ["qt6", "devel"],
@ -76,9 +99,30 @@
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-qt6",
"cacheVariables": {
"QT_PACKAGE_PREFIX": {"type": "STRING", "value": "Qt6"},
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"}
"QT_MAJOR_VERSION": {"type": "STRING", "value": "6"},
"KF_PACKAGE_PREFIX": {"type": "STRING", "value": "KF6"},
"BUILD_WITH_QT6": {"type": "BOOL", "value": "ON"},
"NO_PLASMOID": {"type": "BOOL", "value": "ON"},
"NO_FILE_ITEM_ACTION_PLUGIN": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-unity",
"inherits": ["devel-qt6"],
"displayName": "Development config creating a unity build using Qt 6",
"description": "Same as devel-qt6 but configures makes a unity build",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-unity",
"cacheVariables": {
"CMAKE_UNITY_BUILD": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "devel-libc++-qt6",
"inherits": ["qt6", "devel-libc++"],
"displayName": "Development config using libc++ and Qt 6",
"description": "Combination of qt6 and devel-libc++",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/devel-libc++-qt6"
},
{
"name": "debug",
"inherits": "devel",
@ -90,8 +134,70 @@
}
},
{
"name": "arch-x86_64-w64-mingw32",
"name": "debug-qt6",
"inherits": ["debug", "devel-qt6"],
"displayName": "Generic debug build with development config using Qt 6",
"description": "Same as devel-qt6 but creates a debug build",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/debug-qt6"
},
{
"name": "debug-kde",
"inherits": "debug-qt6",
"displayName": "Generic debug build with development config with KDE integrations enabled",
"description": "Same as debug-qt6 but with KDE integrations enabled",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/debug-kde",
"cacheVariables": {
"NO_PLASMOID": {"type": "BOOL", "value": "OFF"},
"NO_FILE_ITEM_ACTION_PLUGIN": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "debug-kde-custom",
"inherits": "debug-kde",
"displayName": "Generic debug build with development config using custom KDE build",
"description": "Same as debug-kde but with custom KDE installation from KDE_INSTALL_DIR",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/debug-kde-custom",
"cacheVariables": {
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{KDE_INSTALL_DIR}"},
"CMAKE_INSTALL_PREFIX": {"type": "PATH", "value": "$env{KDE_INSTALL_DIR}"}
}
},
{
"name": "arch-*-w64-mingw32",
"inherits": ["no-webview", "no-kde"],
"environment": {
"CPPFLAGS": "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS",
"CFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"CXXFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"LDFLAGS": "-Wl,-O1,--sort-common,--as-needed -fstack-protector"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"},
"VERSIONED_MINGW_LIBRARIES": {"type": "BOOL", "value": "ON"},
"ENABLE_TARGETS_FOR_MINGW_CROSS_PACKAGING": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-i686-w64-mingw32",
"inherits": "arch-*-w64-mingw32",
"displayName": "Target i686-w64-mingw32 using Arch Linux's mingw-w64 packaging",
"description": "Build targeting i686-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32",
"toolchainFile": "/usr/share/mingw/toolchain-i686-w64-mingw32.cmake",
"environment": {
"CROSS_TOOL_PREFIX": "i686-w64-mingw32-",
"CROSS_INSTALL_PREFIX": "/usr/i686-w64-mingw32",
"PATH": "$env{CROSS_INSTALL_PREFIX}/bin:$penv{PATH}"
},
"cacheVariables": {
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CROSSCOMPILING_EMULATOR": {"type": "PATH", "value": "/usr/bin/i686-w64-mingw32-wine"}
}
},
{
"name": "arch-x86_64-w64-mingw32",
"inherits": "arch-*-w64-mingw32",
"displayName": "Target x86_64-w64-mingw32 using Arch Linux's mingw-w64 packaging",
"description": "Build targeting x86_64-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32",
@ -99,20 +205,28 @@
"environment": {
"CROSS_TOOL_PREFIX": "x86_64-w64-mingw32-",
"CROSS_INSTALL_PREFIX": "/usr/x86_64-w64-mingw32",
"CPPFLAGS": "-D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS",
"CFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"CXXFLAGS": "$env{CPPFLAGS} -O2 -pipe -fno-plt -fexceptions --param=ssp-buffer-size=4 -Wformat -Werror=format-security -fcf-protection",
"LDFLAGS": "-Wl,-O1,--sort-common,--as-needed -fstack-protector",
"PATH": "$env{CROSS_INSTALL_PREFIX}/bin:$penv{PATH}"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "ON"},
"VERSIONED_MINGW_LIBRARIES": {"type": "BOOL", "value": "ON"},
"CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_CROSSCOMPILING_EMULATOR": {"type": "PATH", "value": "/usr/bin/x86_64-w64-mingw32-wine"}
}
},
{
"name": "arch-i686-w64-mingw32-static",
"inherits": "arch-i686-w64-mingw32",
"displayName": "Target i686-w64-mingw32 using Arch Linux's mingw-w64 packaging (static)",
"description": "Build targeting i686-w64-mingw32, paths and flags are specific to Arch Linux's mingw-w64 packaging",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-static",
"toolchainFile": "/usr/share/mingw/toolchain-i686-w64-mingw32-static.cmake",
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_FIND_LIBRARY_SUFFIXES": {"type": "STRING", "value": ".a;.lib"},
"STATIC_LIBRARY_LINKAGE": {"type": "BOOL", "value": "ON"},
"STATIC_LINKAGE": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-x86_64-w64-mingw32-static",
"inherits": "arch-x86_64-w64-mingw32",
@ -121,9 +235,19 @@
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-static",
"toolchainFile": "/usr/share/mingw/toolchain-x86_64-w64-mingw32-static.cmake",
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"}
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_FIND_LIBRARY_SUFFIXES": {"type": "STRING", "value": ".a;.lib"},
"STATIC_LIBRARY_LINKAGE": {"type": "BOOL", "value": "ON"},
"STATIC_LINKAGE": {"type": "BOOL", "value": "ON"}
}
},
{
"name": "arch-i686-w64-mingw32-qt6",
"inherits": ["qt6", "arch-i686-w64-mingw32"],
"displayName": "Combination of qt6 and arch-i686-w64-mingw32",
"description": "See description of qt6 and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-qt6"
},
{
"name": "arch-x86_64-w64-mingw32-qt6",
"inherits": ["qt6", "arch-x86_64-w64-mingw32"],
@ -131,6 +255,13 @@
"description": "See description of qt6 and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-qt6"
},
{
"name": "arch-i686-w64-mingw32-static-qt6",
"inherits": ["qt6", "arch-i686-w64-mingw32-static"],
"displayName": "Combination of qt6 and arch-i686-w64-mingw32-static",
"description": "See description of qt6 and arch-i686-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-static-qt6"
},
{
"name": "arch-x86_64-w64-mingw32-static-qt6",
"inherits": ["qt6", "arch-x86_64-w64-mingw32-static"],
@ -138,6 +269,13 @@
"description": "See description of qt6 and arch-x86_64-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-static-qt6"
},
{
"name": "arch-i686-w64-mingw32-devel",
"inherits": ["devel", "arch-i686-w64-mingw32"],
"displayName": "Combination of devel and arch-i686-w64-mingw32",
"description": "See descriptions of devel and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-devel",
"inherits": ["devel", "arch-x86_64-w64-mingw32"],
@ -145,6 +283,34 @@
"description": "See descriptions of devel and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-i686-w64-mingw32-devel-qt6",
"inherits": ["qt6", "devel", "arch-i686-w64-mingw32"],
"displayName": "Combination of qt6, devel and arch-i686-w64-mingw32",
"description": "See descriptions of devel and arch-i686-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-devel-qt6",
"inherits": ["qt6", "devel", "arch-x86_64-w64-mingw32"],
"displayName": "Combination of qt6, devel and arch-x86_64-w64-mingw32",
"description": "See descriptions of devel and arch-x86_64-w64-mingw32",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-i686-w64-mingw32-static-devel-qt6",
"inherits": ["qt6", "devel", "arch-i686-w64-mingw32-static"],
"displayName": "Combination of qt6, devel and arch-i686-w64-mingw32-static",
"description": "See descriptions of devel and arch-i686-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-i686-w64-mingw32-devel"
},
{
"name": "arch-x86_64-w64-mingw32-static-devel-qt6",
"inherits": ["qt6", "devel", "arch-x86_64-w64-mingw32-static"],
"displayName": "Combination of qt6, devel and arch-x86_64-w64-mingw32-static",
"description": "See descriptions of devel and arch-x86_64-w64-mingw32-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-x86_64-w64-mingw32-devel"
},
{
"name": "arch-static-compat",
"inherits": ["no-webview", "no-kde", "qt6"],
@ -169,10 +335,14 @@
"CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/include"},
"CMAKE_INSTALL_PREFIX": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}"},
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}"},
"CMAKE_SKIP_BUILD_RPATH": {"type": "BOOL", "value": "ON"},
"CMAKE_SKIP_INSTALL_RPATH": {"type": "BOOL", "value": "ON"},
"CMAKE_DISABLE_FIND_PACKAGE_harfbuzz": {"type": "BOOL", "value": "ON"},
"Boost_USE_STATIC_RUNTIME": {"type": "BOOL", "value": "ON"},
"GLIB2_USE_PKG_CONFIG": {"type": "BOOL", "value": "ON"},
"WAYLAND_USE_PKG_CONFIG": {"type": "BOOL", "value": "ON"}
"WAYLAND_USE_PKG_CONFIG": {"type": "BOOL", "value": "ON"},
"OPENGL_glu_LIBRARY": {"type": "PATH", "value": "$env{CROSS_INSTALL_PREFIX}/lib/libGLU.a"},
"USE_BUNDLED_RTMIDI": {"type": "BOOL", "value": "ON"}
}
},
{
@ -181,21 +351,108 @@
"displayName": "Combination of devel and arch-static-compat",
"description": "See descriptions of devel and arch-static-compat",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/arch-static-compat-devel"
},
{
"name": "win-x64-msvc-static",
"inherits": ["no-webview", "no-kde", "qt6"],
"displayName": "Target x64-windows-static on Windows",
"description": "Build on Windows targeting x64-windows-static using MSVC, Qt 6 (for Qt libs and CMake/Ninja), vcpkg (for other dependencies) and MSYS2 (for Perl, Go, ffmpeg and other tools)",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static",
"environment": {
"INCLUDE": "$env{MSVC_ROOT}/include;$env{MSVC_ROOT}/ATLMFC/include;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}/ucrt;$env{WIN_KITS_ROOT}//include/$env{WIN_KITS_VERSION}//um;$env{WIN_KITS_ROOT}//include/$env{WIN_KITS_VERSION}//shared;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}//winrt;$env{WIN_KITS_ROOT}/include/$env{WIN_KITS_VERSION}//cppwinrt",
"LIB": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}//um/x64",
"LIBPATH": "$env{MSVC_ROOT}/ATLMFC/lib/x64;$env{MSVC_ROOT}/lib/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/ucrt/x64;$env{WIN_KITS_ROOT}/lib/$env{WIN_KITS_VERSION}/um/x64",
"GOROOT": "$env{MSYS2_ROOT}/mingw64/lib/go"
},
"cacheVariables": {
"BUILD_SHARED_LIBS": {"type": "BOOL", "value": "OFF"},
"CMAKE_BUILD_TYPE": {"type": "STRING", "value": "Release"},
"CMAKE_TOOLCHAIN_FILE": {"type": "FILEPATH", "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"},
"CMAKE_FIND_ROOT_PATH": {"type": "PATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static"},
"CMAKE_PREFIX_PATH": {"type": "PATH", "value": "$env{QT_ROOT}"},
"CMAKE_MAKE_PROGRAM": {"type": "FILEPATH", "value": "$env{QT_TOOLS}/Ninja/ninja.exe"},
"CMAKE_AR_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/Hostx64/x64/lib.exe"},
"CMAKE_C_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/HostX64/x64/cl.exe"},
"CMAKE_CXX_COMPILER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/HostX64/x64/cl.exe"},
"CMAKE_RC_COMPILER": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/$env{WIN_KITS_VERSION}/x64/rc.exe"},
"CMAKE_LINKER": {"type": "FILEPATH", "value": "$env{MSVC_ROOT}/bin/Hostx64/x64/link.exe"},
"CMAKE_MT": {"type": "FILEPATH", "value": "$env{WIN_KITS_ROOT}/bin/$env{WIN_KITS_VERSION}/x64/mt.exe"},
"CMAKE_MSVC_RUNTIME_LIBRARY": {"type": "STRING", "value": "MultiThreaded$<$<CONFIG:Debug>:Debug>"},
"CMAKE_CXX_FLAGS_DEBUG": {"type": "STRING", "value": "/MTd /Zi /Ob0 /Od /RTC1"},
"CMAKE_CXX_FLAGS_RELEASE": {"type": "STRING", "value": "/MT /O2 /Ob2 /DNDEBUG"},
"CMAKE_CXX_FLAGS_MINSIZEREL": {"type": "STRING", "value": "/MT /O1 /Ob1 /DNDEBUG"},
"CMAKE_CXX_FLAGS_RELWITHDEBINFO": {"type": "STRING", "value": "/MT /Zi /O2 /Ob1 /DNDEBUG"},
"VCPKG_TARGET_TRIPLET": {"type": "STRING", "value": "x64-windows-static"},
"PERL_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/usr/bin/perl.exe"},
"DOXYGEN_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/doxygen.exe"},
"CLANG_FORMAT_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/clang-format.exe"},
"GO_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/go.exe"},
"FFMPEG_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/mingw64/bin/ffmpeg.exe"},
"REALPATH_BIN": {"type": "FILEPATH", "value": "$env{MSYS2_ROOT}/usr/bin/realpath.exe"},
"FORCE_EXTERNAL_ICONV": {"type": "BOOL", "value": "ON"},
"CPP_UNIT_LIB": {"type": "FILEPATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static/lib/cppunit.lib"},
"CPP_UNIT_INCLUDE_DIR": {"type": "PATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static/include"},
"Vulkan_INCLUDE_DIR": {"type": "PATH", "value": ""},
"QUICK_GUI": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "win-x64-msvc-static-devel",
"inherits": ["win-x64-msvc-static", "devel"],
"displayName": "Combination of devel and win-x64-msvc-static",
"description": "See descriptions of devel and win-x64-msvc-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static-devel",
"cacheVariables": {
"CLANG_FORMAT_ENABLED": {"type": "BOOL", "value": "OFF"},
"CLANG_TIDY_ENABLED": {"type": "BOOL", "value": "OFF"},
"CMAKE_FORMAT_ENABLED": {"type": "BOOL", "value": "OFF"}
}
},
{
"name": "win-x64-msvc-static-debug",
"inherits": ["debug", "win-x64-msvc-static-devel"],
"displayName": "Combination of debug and win-x64-msvc-static",
"description": "See descriptions of debug and win-x64-msvc-static",
"binaryDir": "$env{BUILD_DIR}/${sourceDirName}/win-x64-msvc-static-debug",
"cacheVariables": {
"CMAKE_C_COMPILER_LAUNCHER": {"type": "STRING", "value": ""},
"CMAKE_CXX_COMPILER_LAUNCHER": {"type": "STRING", "value": ""},
"CPP_UNIT_LIB": {"type": "FILEPATH", "value": "$env{VCPKG_ROOT}/installed/x64-windows-static/debug/lib/cppunitd.lib"}
}
}
],
"buildPresets": [
{"name": "default", "configurePreset": "default"},
{"name": "libc++", "configurePreset": "libc++"},
{"name": "qt6", "configurePreset": "qt6"},
{"name": "devel", "configurePreset": "devel"},
{"name": "devel-libc++", "configurePreset": "devel-libc++"},
{"name": "devel-qt6", "configurePreset": "devel-qt6"},
{"name": "devel-unity", "configurePreset": "devel-unity"},
{"name": "devel-libc++-qt6", "configurePreset": "devel-libc++-qt6"},
{"name": "debug", "configurePreset": "debug"},
{"name": "debug-qt6", "configurePreset": "debug-qt6"},
{"name": "debug-kde", "configurePreset": "debug-kde"},
{"name": "debug-kde-custom", "configurePreset": "debug-kde-custom"},
{"name": "arch-i686-w64-mingw32", "configurePreset": "arch-i686-w64-mingw32"},
{"name": "arch-x86_64-w64-mingw32", "configurePreset": "arch-x86_64-w64-mingw32"},
{"name": "arch-i686-w64-mingw32-static", "configurePreset": "arch-i686-w64-mingw32-static"},
{"name": "arch-x86_64-w64-mingw32-static", "configurePreset": "arch-x86_64-w64-mingw32-static"},
{"name": "arch-i686-w64-mingw32-qt6", "configurePreset": "arch-i686-w64-mingw32-qt6"},
{"name": "arch-x86_64-w64-mingw32-qt6", "configurePreset": "arch-x86_64-w64-mingw32-qt6"},
{"name": "arch-i686-w64-mingw32-static-qt6", "configurePreset": "arch-i686-w64-mingw32-static-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-qt6"},
{"name": "arch-i686-w64-mingw32-devel", "configurePreset": "arch-i686-w64-mingw32-devel"},
{"name": "arch-x86_64-w64-mingw32-devel", "configurePreset": "arch-x86_64-w64-mingw32-devel"},
{"name": "arch-i686-w64-mingw32-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-devel-qt6"},
{"name": "arch-i686-w64-mingw32-static-devel-qt6", "configurePreset": "arch-i686-w64-mingw32-static-devel-qt6"},
{"name": "arch-x86_64-w64-mingw32-static-devel-qt6", "configurePreset": "arch-x86_64-w64-mingw32-static-devel-qt6"},
{"name": "arch-static-compat", "configurePreset": "arch-static-compat"},
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"}
{"name": "arch-static-compat-devel", "configurePreset": "arch-static-compat-devel"},
{"name": "win-x64-msvc-static", "configurePreset": "win-x64-msvc-static"},
{"name": "win-x64-msvc-static-devel", "configurePreset": "win-x64-msvc-static-devel"},
{"name": "win-x64-msvc-static-debug", "configurePreset": "win-x64-msvc-static-debug"}
],
"testPresets": [
{

268
README.md
View File

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

View File

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

View File

@ -7,6 +7,7 @@
#include <functional>
#include <initializer_list>
#include <limits>
#include <tuple>
#include <vector>
#ifdef CPP_UTILITIES_DEBUG_BUILD
#include <cassert>
@ -530,7 +531,7 @@ inline const char *Argument::name() const
*/
inline void Argument::setName(const char *name)
{
#ifdef CPP_UTILITIES_DEBUG_BUILD
#if defined(CPP_UTILITIES_DEBUG_BUILD) && !defined(_MSC_VER)
if (name && *name) {
assert(*name != '-');
for (const char *c = name; *c; ++c) {
@ -880,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

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

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"
@ -274,6 +283,9 @@ function (use_package)
set("PACKAGE_ARGS_${ARGS_PACKAGE_NAME}"
"${ARGS_PACKAGE_ARGS}"
PARENT_SCOPE)
set("${ARGS_PACKAGE_NAME}_VERSION"
"${${ARGS_PACKAGE_NAME}_VERSION}"
PARENT_SCOPE)
endfunction ()
function (use_pkg_config_module)

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.")
@ -33,8 +33,17 @@ append_user_defined_additional_libraries()
# add target for building the application
if (ANDROID)
# create a shared library which can be loaded from the Java-side
add_library(${META_TARGET_NAME} SHARED ${ALL_FILES})
# create a shared library which can be loaded from the Java-side, needs to be a module target to avoid
# "QT_ANDROID_GENERATE_DEPLOYMENT_SETTINGS only works on Module targets" when using
# `qt_android_generate_deployment_settings`.
add_library(${META_TARGET_NAME} MODULE ${ALL_FILES})
# set suffix to avoid "Cannot find application binary in build dir /lib_arm64-v8a.so." when using
# `qt_android_add_apk_target`.
if (ANDROID_ABI)
set_target_properties(${META_TARGET_NAME} PROPERTIES SUFFIX "_${ANDROID_ABI}.so")
endif ()
set_target_properties(${META_TARGET_NAME} PROPERTIES QT_ANDROID_VERSION_NAME
"${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}")
else ()
add_executable(${META_TARGET_NAME} ${GUI_TYPE} ${ALL_FILES})
endif ()
@ -56,13 +65,19 @@ target_compile_options(
PRIVATE "${META_PRIVATE_COMPILE_OPTIONS}")
set_target_properties(
${META_TARGET_NAME}
PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}"
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
PROPERTIES LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
LINK_SEARCH_END_STATIC ${STATIC_LINKAGE}
AUTOGEN_TARGET_DEPENDS "${AUTOGEN_DEPS}"
QT_DEFAULT_PLUGINS "${META_QT_DEFAULT_PLUGINS}")
if (NOT ANDROID)
set_target_properties(${META_TARGET_NAME} PROPERTIES C_VISIBILITY_PRESET hidden CXX_VISIBILITY_PRESET hidden)
# note: Android *.so files need CXX visibility set to default (see qtbase commit
# 29b17fa335388c9b93f70c29b2398cf2fee65785). Otherwise loading the app will fail with the error "dlsym failed: undefined
# symbol: main".
endif ()
if (NOT META_CXX_STANDARD STREQUAL "any")
set_target_properties(${META_TARGET_NAME} PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}")
endif ()
# set properties for macOS bundle and generate icon for macOS bundle
if (GUI_TYPE STREQUAL "MACOSX_BUNDLE")
@ -73,7 +88,9 @@ if (GUI_TYPE STREQUAL "MACOSX_BUNDLE")
MACOSX_BUNDLE_BUNDLE_VERSION "${META_APP_VERSION}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${META_APP_VERSION}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${META_APP_VERSION}")
if (PNG_ICON_PATH)
if (MACOSX_ICON_PATH)
target_sources(${META_TARGET_NAME} PRIVATE "${MACOSX_ICON_PATH}")
elseif (PNG_ICON_PATH)
find_program(PNG2ICNS_BIN png2icns)
if (PNG2ICNS_BIN)
set(RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/${META_TARGET_NAME}.app/Contents/Resources")
@ -93,6 +110,20 @@ if (GUI_TYPE STREQUAL "MACOSX_BUNDLE")
endif ()
endif ()
# create CLI-wrapper to be able to use CLI in Windows-termial without hacks
if (BUILD_CLI_WRAPPER)
# find source file
include(TemplateFinder)
find_template_file_full_name("cli-wrapper.cpp" CPP_UTILITIES CLI_WRAPPER_SRC_FILE)
# add and configure additional executable
set(CLI_WRAPPER_TARGET_NAME "${META_TARGET_NAME}-cli")
add_executable(${CLI_WRAPPER_TARGET_NAME} ${CLI_WRAPPER_RES_FILES} ${CLI_WRAPPER_SRC_FILE})
set_target_properties(${CLI_WRAPPER_TARGET_NAME} PROPERTIES CXX_STANDARD 17)
target_link_libraries(${CLI_WRAPPER_TARGET_NAME} PRIVATE ${STATIC_LINKAGE_LINKER_FLAGS})
target_compile_definitions(${CLI_WRAPPER_TARGET_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS=1)
endif ()
# add install targets
if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
# add install target for binary
@ -112,6 +143,9 @@ if (NOT META_NO_INSTALL_TARGETS AND ENABLE_INSTALL_TARGETS)
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}${SELECTED_LIB_SUFFIX}" COMPONENT binary)
else ()
install(TARGETS ${META_TARGET_NAME} RUNTIME DESTINATION bin COMPONENT binary)
if (CLI_WRAPPER_TARGET_NAME)
install(TARGETS ${CLI_WRAPPER_TARGET_NAME} RUNTIME DESTINATION bin COMPONENT binary)
endif ()
endif ()
if (NOT TARGET install-binary)

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
@ -70,7 +63,7 @@ function (add_appstream_file)
endif ()
# create appstream desktop file from template
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.appdata.xml")
set(APPSTREAM_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_ID}.metainfo.xml")
configure_file("${APP_APPSTREAM_TEMPLATE_FILE}" "${APPSTREAM_FILE}" @ONLY)
# add install for the appstream file
@ -80,11 +73,19 @@ function (add_appstream_file)
COMPONENT appimage)
# add test
set(APPSTREAM_TESTS_ENABLED_DEFAULT OFF)
find_program(APPSTREAMCLI_BIN "appstreamcli")
if (NOT APPSTREAMCLI_BIN)
message(STATUS "Could not find appstreamcli; won't add test/target to validate appstream files")
else ()
add_test(NAME "${META_TARGET_NAME}_appstream_validation" COMMAND "${APPSTREAMCLI_BIN}" validate "${APPSTREAM_FILE}")
if (ENABLE_DEVEL_DEFAULTS AND APPSTREAMCLI_BIN)
set(APPSTREAM_TESTS_ENABLED_DEFAULT ON)
endif ()
option(APPSTREAM_TESTS_ENABLED "enables tests for checking whether AppStream files" "${APPSTREAM_TESTS_ENABLED_DEFAULT}")
if (APPSTREAM_TESTS_ENABLED)
if (NOT APPSTREAMCLI_BIN)
message(FATAL_ERROR "Unable to validate appstreamcli files; appstreamcli not found")
else ()
add_test(NAME "${META_TARGET_NAME}_appstream_validation" COMMAND "${APPSTREAMCLI_BIN}" validate
"${APPSTREAM_FILE}")
endif ()
endif ()
endfunction ()
@ -97,7 +98,6 @@ function (add_desktop_file)
endif ()
# compose actions
set(DESKTOP_FILE_ADDITIONAL_ENTRIES "")
foreach (ACTION_VAR ${META_APP_ACTIONS})
list(GET META_APP_ACTION_${ACTION_VAR} 0 ACTION_ID)
list(GET META_APP_ACTION_${ACTION_VAR} 1 ACTION_NAME)

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)
@ -94,8 +94,7 @@ if (NOT DEFINED META_QT_DEFAULT_PLUGINS)
set(META_QT_DEFAULT_PLUGINS 0) # needs to be exactly 0, Qt's code uses STREQUAL 0
endif ()
# add option to enable defaults useful for development
option(ENABLE_DEVEL_DEFAULTS "enable build system options useful during development by default" OFF)
include(DevelUtilities)
# find standard installation directories - note: Allow overriding CMAKE_INSTALL_LIBDIR and LIB_INSTALL_DIR but don't use the
# default from GNUInstallDirs (as an Arch Linux user this feels odd and I also want to avoid breaking existing build
@ -121,7 +120,11 @@ endif ()
# set default CXX_STANDARD for all library, application and test targets
if (NOT META_CXX_STANDARD)
set(META_CXX_STANDARD 17)
if (MSVC)
set(META_CXX_STANDARD 20) # MSVC needs C++ 20 mode for designated initializers
else ()
set(META_CXX_STANDARD 17)
endif ()
endif ()
# set version to 0.0.0 if not specified explicitly
@ -188,18 +191,37 @@ if (NOT META_PROJECT_LICENSE)
endif ()
endif ()
# determine RDNS automatically from other meta-data
# determine RDNS automatically from other meta-data and allow override
set(${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE
""
CACHE STRING
"overrides the RDNS used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE)
set(META_PROJECT_RDNS ${${META_PROJECT_VARNAME_UPPER}_RDNS_OVERRIDE})
endif ()
set(${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE
""
CACHE STRING
"overrides the developer ID used in AppStream meta-data files for ${META_PROJECT_NAME}")
if (${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE)
set(META_DEVELOPER_ID ${${META_PROJECT_VARNAME_UPPER}_DEVELOPER_ID_OVERRIDE})
endif ()
if (NOT META_PROJECT_RDNS OR NOT META_DEVELOPER_ID)
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
endif ()
if (NOT META_PROJECT_RDNS)
if (NOT META_PROJECT_RDNS_BASE)
if (META_APP_URL MATCHES ".*github\\.com.*")
if (META_APP_URL MATCHES ".*github\\.(com|io).*")
set(META_PROJECT_RDNS_BASE "io.github") # assume GitHub pages
else ()
set(META_PROJECT_RDNS_BASE "org")
endif ()
endif ()
string(TOLOWER "${META_APP_AUTHOR}" META_APP_AUTHOR_LOWER)
set(META_PROJECT_RDNS "${META_PROJECT_RDNS_BASE}.${META_APP_AUTHOR_LOWER}.${META_PROJECT_NAME}${TARGET_SUFFIX}")
endif ()
if (NOT META_DEVELOPER_ID)
set(META_DEVELOPER_ID "org.${META_APP_AUTHOR_LOWER}")
endif ()
# provide variables for other projects built as part of the same subdirs project to access files from this project
get_directory_property(HAS_PARENT PARENT_DIRECTORY)
@ -257,9 +279,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
@ -341,7 +363,10 @@ endif ()
# useful if there's a test target; this is for instance also used in mocked configuration of syncthingtray) -> add a file
# called "srcdirref" to the build directory; this file contains the path of the sources so tests can easily find test files
# contained in the source directory
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/srcdirref" "${CMAKE_CURRENT_SOURCE_DIR}")
if (NOT META_SRCDIR_REFS)
set(META_SRCDIR_REFS "${CMAKE_CURRENT_SOURCE_DIR}")
endif ()
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/srcdirref" "${META_SRCDIR_REFS}")
# -> ensure the directory "testfiles" exists in the build directory; tests of my projects use it by default to create working
# copies of testfiles
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/testfiles")
@ -387,8 +412,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
@ -396,7 +437,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 ()
@ -412,21 +452,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 ()
@ -591,7 +632,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")
@ -684,53 +725,10 @@ else ()
endforeach ()
endif ()
# enable useful warnings and explicitly disable not useful ones and treat warnings them as errors
option(ENABLE_WARNINGS "adds additional compiler flags to enable useful warnings" "${ENABLE_DEVEL_DEFAULTS}")
set(CLANG_WARNINGS
-Wall
-Wextra # reasonable and standard
-Wshadow=local # warn the user if a variable declaration shadows one from a parent context
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps catch hard
# to track down memory errors
-Wold-style-cast # warn for c-style casts
-Wcast-align # warn for potential performance problem casts
-Wunused # warn on anything being unused
-Woverloaded-virtual # warn if you overload (not override) a virtual function
-Wconversion # warn on type conversions that may lose data
-Wsign-conversion # warn on sign conversions
-Wnull-dereference # warn if a null dereference is detected
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output (ie printf)
-Wno-pedantic # warn NOT if non-standard C++ is used (some vendor extensions are very useful)
-Wno-missing-field-initializers # warn NOT about missing field initializers
-Wno-useless-cast # warn NOT about useless cases (this is sometimes very useful in templates)
-Wno-unused-const-variable # warn NOT about unused constants (usually used in other compile unit)
-Wno-unknown-warning-option # warn NOT about unknown warning options (depending on compiler/version not all are
# available)
)
set(GCC_WARNINGS
${CLANG_WARNINGS}
-Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist
-Wduplicated-cond # warn if if / else chain has duplicated conditions
-Wduplicated-branches # warn if if / else branches have duplicated code
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
)
if (ENABLE_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
list(APPEND META_PRIVATE_COMPILE_OPTIONS ${CLANG_WARNINGS})
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND META_PRIVATE_COMPILE_OPTIONS ${GCC_WARNINGS})
else ()
message(AUTHOR_WARNING "Enabling warnings is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
endif ()
endif ()
option(TREAT_WARNINGS_AS_ERRORS "adds additional compiler flag to treat warnings as errors" "${ENABLE_DEVEL_DEFAULTS}")
if (TREAT_WARNINGS_AS_ERRORS)
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND META_PRIVATE_COMPILE_OPTIONS -Werror)
else ()
message(AUTHOR_WARNING "Treating warnings as errors is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
endif ()
# configure warnings
configure_development_warnings(APPEND_OUTPUT_VAR META_PRIVATE_COMPILE_OPTIONS)
if (MSVC)
list(APPEND META_PRIVATE_COMPILE_DEFINITIONS _CRT_SECURE_NO_WARNINGS=1)
endif ()
set(BASIC_PROJECT_CONFIG_DONE YES)

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.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

@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# prevent multiple inclusion
if (DEFINED DEVEL_UTILITIES_LOADED)
return()
endif ()
set(DEVEL_UTILITIES_LOADED YES)
# add option to enable defaults useful for development
option(ENABLE_DEVEL_DEFAULTS "enable build system options useful during development by default" OFF)
function (configure_development_warnings)
# parse arguments
set(OPTIONAL_ARGS)
set(ONE_VALUE_ARGS TARGET OUTPUT_VAR APPEND_OUTPUT_VAR)
set(MULTI_VALUE_ARGS)
cmake_parse_arguments(ARGS "${OPTIONAL_ARGS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}" ${ARGN})
# enable useful warnings and explicitly disable not useful ones and treat warnings as errors
option(ENABLE_WARNINGS "adds additional compiler flags to enable useful warnings" "${ENABLE_DEVEL_DEFAULTS}")
set(CLANG_WARNINGS
-Wall
-Wextra # reasonable and standard
-Wshadow=local # warn the user if a variable declaration shadows one from a parent context
-Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps catch
# hard to track down memory errors
-Wold-style-cast # warn for c-style casts
-Wcast-align # warn for potential performance problem casts
-Wunused # warn on anything being unused
-Woverloaded-virtual # warn if you overload (not override) a virtual function
-Wconversion # warn on type conversions that may lose data
-Wsign-conversion # warn on sign conversions
-Wnull-dereference # warn if a null dereference is detected
-Wdouble-promotion # warn if float is implicit promoted to double
-Wformat=2 # warn on security issues around functions that format output (ie printf)
-Wno-pedantic # warn NOT if non-standard C++ is used (some vendor extensions are very useful)
-Wno-missing-field-initializers # warn NOT about missing field initializers
-Wno-useless-cast # warn NOT about useless cases (this is sometimes very useful in templates)
-Wno-unused-const-variable # warn NOT about unused constants (usually used in other compile unit)
-Wno-unknown-warning-option # warn NOT about unknown warning options (depending on compiler/version not all are
# available)
)
set(GCC_WARNINGS
${CLANG_WARNINGS}
-Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist
-Wduplicated-cond # warn if if / else chain has duplicated conditions
-Wduplicated-branches # warn if if / else branches have duplicated code
-Wlogical-op # warn about logical operations being used where bitwise were probably wanted
)
if (ENABLE_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE ${CLANG_WARNINGS})
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE ${GCC_WARNINGS})
else ()
message(AUTHOR_WARNING "Enabling warnings is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
endif ()
endif ()
option(TREAT_WARNINGS_AS_ERRORS "adds additional compiler flag to treat warnings as errors" "${ENABLE_DEVEL_DEFAULTS}")
if (TREAT_WARNINGS_AS_ERRORS)
if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
list(APPEND COMPILE_OPTIONS_TO_CONFIGURE -Werror)
else ()
message(AUTHOR_WARNING "Treating warnings as errors is not supported for compiler '${CMAKE_CXX_COMPILER_ID}'.")
endif ()
endif ()
# set compile options / set output variable
if (ARGS_TARGET)
target_compile_options("${ARGS_TARGET}" PRIVATE ${COMPILE_OPTIONS_TO_CONFIGURE})
endif ()
if (ARGS_OUTPUT_VAR)
set("${ARGS_OUTPUT_VAR}"
${COMPILE_OPTIONS_TO_CONFIGURE}
PARENT_SCOPE)
endif ()
if (ARGS_APPEND_OUTPUT_VAR)
set("${ARGS_APPEND_OUTPUT_VAR}"
${${ARGS_APPEND_OUTPUT_VAR}} ${COMPILE_OPTIONS_TO_CONFIGURE}
PARENT_SCOPE)
endif ()
endfunction ()

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 ()
@ -160,13 +185,15 @@ else ()
${META_TARGET_NAME}
PROPERTIES VERSION "${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}"
SOVERSION "${META_SOVERSION}"
CXX_STANDARD "${META_CXX_STANDARD}"
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
LINK_SEARCH_END_STATIC ${STATIC_LINKAGE}
AUTOGEN_TARGET_DEPENDS "${AUTOGEN_DEPS}"
QT_DEFAULT_PLUGINS "${META_QT_DEFAULT_PLUGINS}")
if (NOT META_CXX_STANDARD STREQUAL "any")
set_target_properties(${META_TARGET_NAME} PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}")
endif ()
if (META_PLUGIN_CATEGORY)
set_target_properties(${META_TARGET_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${META_PLUGIN_CATEGORY}")
endif ()
@ -193,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}"
@ -235,8 +264,11 @@ if (META_HEADER_ONLY_LIB)
${META_TARGET_NAME}_interface_sources_for_qtcreator
PROPERTIES VERSION "${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}"
SOVERSION "${META_SOVERSION}"
CXX_STANDARD "${META_CXX_STANDARD}"
AUTOGEN_TARGET_DEPENDS "${AUTOGEN_DEPS}")
if (NOT META_CXX_STANDARD STREQUAL "any")
set_target_properties(${META_TARGET_NAME}_interface_sources_for_qtcreator PROPERTIES CXX_STANDARD
"${META_CXX_STANDARD}")
endif ()
endif ()
# generate CMake code to configure additional arguments for required CMake-packages
@ -525,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)
@ -549,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)
@ -6,23 +6,30 @@ if (DEFINED TEMPLATE_FINDER_LOADED)
endif ()
set(TEMPLATE_FINDER_LOADED YES)
function (find_template_file FILE_NAME PROJECT_VAR_NAME OUTPUT_VAR)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in")
function (find_template_file FILE_NAME_WITHOUT_EXTENSION PROJECT_VAR_NAME OUTPUT_VAR)
find_template_file_full_name("${FILE_NAME_WITHOUT_EXTENSION}.in" "${PROJECT_VAR_NAME}" "${OUTPUT_VAR}")
set(${OUTPUT_VAR}
"${${OUTPUT_VAR}}"
PARENT_SCOPE)
endfunction ()
function (find_template_file_full_name FILE_NAME PROJECT_VAR_NAME OUTPUT_VAR)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}")
# check own source directory
set(${OUTPUT_VAR}
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/${FILE_NAME}"
PARENT_SCOPE)
message(STATUS "Using template for ${FILE_NAME} from own (${META_PROJECT_NAME}) source directory.")
elseif (EXISTS "${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in")
elseif (EXISTS "${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}")
# check sources of project
set(${OUTPUT_VAR}
"${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}.in"
"${${PROJECT_VAR_NAME}_SOURCE_DIR}/cmake/templates/${FILE_NAME}"
PARENT_SCOPE)
message(STATUS "Using template for ${FILE_NAME} from ${PROJECT_VAR_NAME} source directory.")
elseif (EXISTS "${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}.in")
elseif (EXISTS "${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}")
# check installed version of project
set(${OUTPUT_VAR}
"${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}.in"
"${${PROJECT_VAR_NAME}_CONFIG_DIRS}/templates/${FILE_NAME}"
PARENT_SCOPE)
message(STATUS "Using template for ${FILE_NAME} from ${PROJECT_VAR_NAME} installation.")
else ()

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,64 +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" FORCE)
CACHE FILEPATH "CppUnit lib")
set(CPP_UNIT_INCLUDE_DIR
NOTFOUND
CACHE FILEPATH "cppunit include dir" FORCE)
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 lib" 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})
else ()
# fall back to find_library
find_library(DETECTED_CPP_UNIT_LIB cppunit)
set(CPP_UNIT_LIB
"${DETECTED_CPP_UNIT_LIB}"
CACHE FILEPATH "cppunit lib" FORCE)
pkg_search_module(CppUnit IMPORTED_TARGET cppunit>=${META_REQUIRED_CPP_UNIT_VERSION})
if (CppUnit_FOUND)
set(DETECTED_CPP_UNIT_LIB "PkgConfig::CppUnit")
endif ()
endif ()
if (NOT CPP_UNIT_LIB)
message(WARNING "Unable to add test target because cppunit could not be located.")
# fall back to find_package (as vcpkg provides one)
if (NOT DETECTED_CPP_UNIT_LIB)
find_package(CppUnit CONFIG)
if (TARGET CppUnit)
set(DETECTED_CPP_UNIT_LIB CppUnit)
endif ()
endif ()
# fall back to find_library
if (NOT DETECTED_CPP_UNIT_LIB)
find_library(DETECTED_CPP_UNIT_LIB cppunit NO_CACHE)
if (DETECTED_CPP_UNIT_LIB)
message(
WARNING
"CppUnit has only been detected via find_library() so the version could not be checked and include paths are maybe missing. The required version for ${META_PROJECT_NAME} is ${META_REQUIRED_CPP_UNIT_VERSION}."
)
endif ()
endif ()
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(
@ -115,12 +129,14 @@ if (META_PROJECT_IS_APPLICATION AND LINK_TESTS_AGAINST_APP_TARGET)
PRIVATE "${META_PRIVATE_COMPILE_OPTIONS}")
set_target_properties(
${META_TARGET_NAME}_testlib
PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}"
C_VISIBILITY_PRESET default
PROPERTIES C_VISIBILITY_PRESET default
CXX_VISIBILITY_PRESET default
LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
LINK_SEARCH_END_STATIC ${STATIC_LINKAGE}
AUTOGEN_TARGET_DEPENDS "${AUTOGEN_DEPS}")
if (NOT META_CXX_STANDARD STREQUAL "any")
set_target_properties(${META_TARGET_NAME}_testlib PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}")
endif ()
if (CPP_UNIT_CONFIG_${META_PROJECT_NAME}_FOUND)
target_include_directories(${META_TARGET_NAME}_testlib
PRIVATE "${CPP_UNIT_CONFIG_${META_PROJECT_NAME}_INCLUDE_DIRS}")

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}"
@ -62,11 +66,13 @@ function (configure_test_target)
PRIVATE "${META_PRIVATE_COMPILE_OPTIONS}")
set_target_properties(
"${TEST_TARGET_NAME}"
PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}"
C_VISIBILITY_PRESET hidden
PROPERTIES C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
LINK_SEARCH_START_STATIC ${STATIC_LINKAGE}
LINK_SEARCH_END_STATIC ${STATIC_LINKAGE})
if (NOT META_CXX_STANDARD STREQUAL "any")
set_target_properties("${TEST_TARGET_NAME}" PROPERTIES CXX_STANDARD "${META_CXX_STANDARD}")
endif ()
# make the test recognized by ctest
if (NOT ARGS_MANUAL)

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
@ -12,33 +12,37 @@ endif ()
option(WINDOWS_RESOURCES_ENABLED "controls whether Windows resources are enabled" ON)
option(WINDOWS_ICON_ENABLED "controls whether Windows icon is enabled" ON)
if (NOT MINGW OR NOT WINDOWS_RESOURCES_ENABLED)
if (NOT WIN32 OR NOT WINDOWS_RESOURCES_ENABLED)
return()
endif ()
# find rc template, define path of output rc file
include(TemplateFinder)
find_template_file("windows.rc" CPP_UTILITIES RC_TEMPLATE_FILE)
set(WINDOWS_RC_FILE_CFG "${CMAKE_CURRENT_BINARY_DIR}/resources/windows.rc.configured")
find_template_file("windows-cli-wrapper.rc" CPP_UTILITIES RC_CLI_TEMPLATE_FILE)
set(WINDOWS_RC_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/windows")
set(WINDOWS_CLI_RC_FILE "${CMAKE_CURRENT_BINARY_DIR}/resources/windows-cli-wrapper")
# create Windows icon from png with ffmpeg if available
unset(WINDOWS_ICON_PATH)
unset(WINDOWS_ICON_RC_ENTRY)
if (WINDOWS_ICON_ENABLED AND PNG_ICON_PATH)
find_program(FFMPEG_BIN ffmpeg avconv)
if (FFMPEG_BIN)
set(WINDOWS_ICON_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_PROJECT_NAME}.ico")
if (WINDOWS_ICON_ENABLED)
if (NOT WINDOWS_ICON_PATH AND PNG_ICON_PATH)
find_program(FFMPEG_BIN ffmpeg avconv)
if (FFMPEG_BIN)
set(WINDOWS_ICON_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources/${META_PROJECT_NAME}.ico")
add_custom_command(
COMMENT "Generating icon for Windows executable"
OUTPUT "${WINDOWS_ICON_PATH}"
COMMAND ${FFMPEG_BIN} -y -i "${PNG_ICON_PATH}" "${WINDOWS_ICON_PATH}"
DEPENDS "${PNG_ICON_PATH}")
message(STATUS "Generating Windows icon from \"${PNG_ICON_PATH}\" via ${FFMPEG_BIN}.")
else ()
message(STATUS "Unable to find ffmpeg, not creating a Windows icon")
endif ()
endif ()
if (WINDOWS_ICON_PATH)
set(WINDOWS_ICON_RC_ENTRY "IDI_ICON1 ICON DISCARDABLE \"${WINDOWS_ICON_PATH}\"")
add_custom_command(
COMMENT "Generating icon for Windows executable"
OUTPUT "${WINDOWS_ICON_PATH}"
COMMAND ${FFMPEG_BIN} -y -i "${PNG_ICON_PATH}" "${WINDOWS_ICON_PATH}"
DEPENDS "${PNG_ICON_PATH}")
set_source_files_properties("${WINDOWS_RC_FILE}" PROPERTIES OBJECT_DEPENDS "${WINDOWS_ICON_PATH}")
message(STATUS "Generating Windows icon from \"${PNG_ICON_PATH}\" via ${FFMPEG_BIN}.")
else ()
message(STATUS "Unable to find ffmpeg, not creating a Windows icon")
endif ()
endif ()
@ -48,10 +52,25 @@ file(
GENERATE
OUTPUT "${WINDOWS_RC_FILE}-$<CONFIG>.rc"
INPUT "${WINDOWS_RC_FILE}-configured.rc")
if (BUILD_CLI_WRAPPER AND META_PROJECT_TYPE STREQUAL "application")
configure_file("${RC_CLI_TEMPLATE_FILE}" "${WINDOWS_CLI_RC_FILE}-configured.rc")
file(
GENERATE
OUTPUT "${WINDOWS_CLI_RC_FILE}-$<CONFIG>.rc"
INPUT "${WINDOWS_CLI_RC_FILE}-configured.rc")
endif ()
# set windres as resource compiler
# add resource file to sources
list(APPEND RES_FILES "${WINDOWS_RC_FILE}-${CMAKE_BUILD_TYPE}.rc")
set_property(SOURCE "${WINDOWS_RC_FILE}-${CMAKE_BUILD_TYPE}.rc" PROPERTY GENERATED ON)
set(CMAKE_RC_COMPILER_INIT windres)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
if (BUILD_CLI_WRAPPER AND META_PROJECT_TYPE STREQUAL "application")
list(APPEND CLI_WRAPPER_RES_FILES "${WINDOWS_CLI_RC_FILE}-${CMAKE_BUILD_TYPE}.rc")
set_property(SOURCE "${WINDOWS_CLI_RC_FILE}-${CMAKE_BUILD_TYPE}.rc" PROPERTY GENERATED ON)
endif ()
# configure resource compiler; use windres when compiling with mingw-w64
if (MINGW)
set(CMAKE_RC_COMPILER_INIT windres)
set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
endif ()
enable_language(RC)

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 id="@META_DEVELOPER_ID@"><name>@META_APP_AUTHOR@</name></developer>
<provides>
<binary>@META_TARGET_NAME@</binary>
</provides>

View File

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

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

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

View File

@ -2,8 +2,15 @@
#define CONVERSION_UTILITIES_BINARY_CONVERSION_H
#include "../global.h"
#include "../misc/traits.h"
#include <cstdint>
#include <cstring>
// use helpers from bits header if available instead of custom code using bit operations
#if __cplusplus >= 202002L
#include <bit>
#endif
// detect byte order according to __BYTE_ORDER__
#if defined(__BYTE_ORDER__)
@ -73,28 +80,6 @@
namespace CppUtilities {
/*!
* \brief Encapsulates binary conversion functions using the big endian byte order.
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
*/
namespace BE {
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 0
#include "./binaryconversionprivate.h"
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
} // namespace BE
/*!
* \brief Encapsulates binary conversion functions using the little endian byte order.
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
*/
namespace LE {
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 1
#include "./binaryconversionprivate.h"
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
} // namespace LE
/*!
* \brief Returns the 8.8 fixed point representation converted from the specified 32-bit floating point number.
*/
@ -148,6 +133,15 @@ CPP_UTILITIES_EXPORT constexpr std::uint32_t toNormalInt(std::uint32_t synchsafe
| ((synchsafeInt & 0x7f000000u) >> 3);
}
// define helpers for byte swapping
#ifdef __cpp_lib_byteswap // in C++ 23 we can just use the stdlib
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT constexpr T swapOrder(T value)
{
return std::byteswap(value);
}
#else // provide custom code for C++ 20 and older (will also be optimized by GCC and Clang to use bswap)
/*!
* \brief Swaps the byte order of the specified 16-bit unsigned integer.
*/
@ -173,6 +167,61 @@ CPP_UTILITIES_EXPORT constexpr std::uint64_t swapOrder(std::uint64_t value)
| ((value & 0x000000FF00000000) >> (1 * 8)) | ((value & 0x00000000FF000000) << (1 * 8)) | ((value & 0x0000000000FF0000) << (3 * 8))
| ((value & 0x000000000000FF00) << (5 * 8)) | ((value) << (7 * 8));
}
/*!
* \brief Swaps the byte order of the specified 16-bit integer.
*/
CPP_UTILITIES_EXPORT constexpr std::int16_t swapOrder(std::int16_t value)
{
return static_cast<std::int16_t>(((static_cast<std::uint16_t>(value) >> 8) & 0x00FF) | ((static_cast<std::uint16_t>(value) << 8) & 0xFF00));
}
/*!
* \brief Swaps the byte order of the specified 32-bit integer.
*/
CPP_UTILITIES_EXPORT constexpr std::int32_t swapOrder(std::int32_t value)
{
return static_cast<std::int32_t>((static_cast<std::uint32_t>(value) >> 24) | ((static_cast<std::uint32_t>(value) & 0x00FF0000) >> 8)
| ((static_cast<std::uint32_t>(value) & 0x0000FF00) << 8) | (static_cast<std::uint32_t>(value) << 24));
}
/*!
* \brief Swaps the byte order of the specified 64-bit integer.
*/
CPP_UTILITIES_EXPORT constexpr std::int64_t swapOrder(std::int64_t value)
{
return static_cast<std::int64_t>((static_cast<std::uint64_t>(value) >> (7 * 8))
| ((static_cast<std::uint64_t>(value) & 0x00FF000000000000) >> (5 * 8))
| ((static_cast<std::uint64_t>(value) & 0x0000FF0000000000) >> (3 * 8))
| ((static_cast<std::uint64_t>(value) & 0x000000FF00000000) >> (1 * 8))
| ((static_cast<std::uint64_t>(value) & 0x00000000FF000000) << (1 * 8))
| ((static_cast<std::uint64_t>(value) & 0x0000000000FF0000) << (3 * 8))
| ((static_cast<std::uint64_t>(value) & 0x000000000000FF00) << (5 * 8)) | ((static_cast<std::uint64_t>(value)) << (7 * 8)));
}
#endif
/*!
* \brief Encapsulates binary conversion functions using the big endian byte order.
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
*/
namespace BE {
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 0
#include "./binaryconversionprivate.h"
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
} // namespace BE
/*!
* \brief Encapsulates binary conversion functions using the little endian byte order.
* \sa <a href="http://en.wikipedia.org/wiki/Endianness">Endianness - Wikipedia</a>
*/
namespace LE {
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL 1
#include "./binaryconversionprivate.h"
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
} // namespace LE
} // namespace CppUtilities
#endif // CONVERSION_UTILITIES_BINARY_CONVERSION_H

View File

@ -1,10 +1,15 @@
// assert that CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL is defined; a value of:
// - 0 means to define functions for BE namespace
// - 1 means to define functions for LE namespace
#ifndef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL
#error "Do not include binaryconversionprivate.h directly."
#else
#include "../global.h"
#include <cstdint>
// define macro if swapping byte order is required
#if (CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0 && defined(CONVERSION_UTILITIES_BYTE_ORDER_LITTLE_ENDIAN)) \
|| (CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 1 && defined(CONVERSION_UTILITIES_BYTE_ORDER_BIG_ENDIAN))
#define CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
#endif
// disable warnings about sign conversions when using GCC or Clang
#ifdef __GNUC__
@ -137,31 +142,21 @@ CPP_UTILITIES_EXPORT inline double toFloat64(const char *value)
}
/*!
* \brief Stores the specified 16-bit signed integer value at a specified position in a char array.
* \brief Returns the specified (unsigned) integer converted from the specified char array.
* \remarks
* - The \a value must point to a sequence of characters that is at least as long as the specified integer type.
* - This function is potentially faster than the width-specific toInt() because the width-specific functions use
* custom code that may not be optimized by the compiler. (Only GCC optimized it but not Clang and MSVC.) Note
* that the width-specific functions cannot be changed as they are constexpr and thus cannot use memcpy().
*/
CPP_UTILITIES_EXPORT inline void getBytes(std::int16_t value, char *outputbuffer)
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline T toInt(const char *value)
{
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[1] = static_cast<char>((value)&0xFF);
#else
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif
}
/*!
* \brief Stores the specified 16-bit unsigned integer value at a specified position in a char array.
*/
CPP_UTILITIES_EXPORT inline void getBytes(std::uint16_t value, char *outputbuffer)
{
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[1] = static_cast<char>((value)&0xFF);
#else
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
auto dst = T();
std::memcpy(&dst, value, sizeof(T));
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
dst = swapOrder(dst);
#endif
return dst;
}
/*!
@ -173,100 +168,27 @@ CPP_UTILITIES_EXPORT inline void getBytes24(std::uint32_t value, char *outputbuf
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[2] = static_cast<char>((value)&0xFF);
outputbuffer[2] = static_cast<char>((value) & 0xFF);
#else
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
outputbuffer[0] = static_cast<char>((value) & 0xFF);
#endif
}
/*!
* \brief Stores the specified 32-bit signed integer value at a specified position in a char array.
* \brief Stores the specified (unsigned) integer value in a char array.
* \remarks
* - The \a value outputbuffer must point to a sequence of characters that is at least as long as the specified integer type.
* - This function is potentially faster than the width-specific toInt() because the width-specific functions use
* custom code that may not be optimized by the compiler. (Only GCC optimized it but not Clang and MSVC.)
*/
CPP_UTILITIES_EXPORT inline void getBytes(std::int32_t value, char *outputbuffer)
template <class T, Traits::EnableIf<std::is_integral<T>> * = nullptr> CPP_UTILITIES_EXPORT inline void getBytes(T value, char *outputbuffer)
{
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[3] = static_cast<char>((value)&0xFF);
#else
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif
}
/*!
* \brief Stores the specified 32-bit signed integer value at a specified position in a char array.
*/
CPP_UTILITIES_EXPORT inline void getBytes(std::uint32_t value, char *outputbuffer)
{
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[3] = static_cast<char>((value)&0xFF);
#else
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif
}
/*!
* \brief Stores the specified 64-bit signed integer value at a specified position in a char array.
*/
CPP_UTILITIES_EXPORT inline void getBytes(std::int64_t value, char *outputbuffer)
{
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 56) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[7] = static_cast<char>((value)&0xFF);
#else
outputbuffer[7] = static_cast<char>((value >> 56) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#endif
}
/*!
* \brief Stores the specified 64-bit unsigned integer value at a specified position in a char array.
*/
CPP_UTILITIES_EXPORT inline void getBytes(std::uint64_t value, char *outputbuffer)
{
#if CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL == 0
outputbuffer[0] = static_cast<char>((value >> 56) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[7] = static_cast<char>((value)&0xFF);
#else
outputbuffer[7] = static_cast<char>((value >> 56) & 0xFF);
outputbuffer[6] = static_cast<char>((value >> 48) & 0xFF);
outputbuffer[5] = static_cast<char>((value >> 40) & 0xFF);
outputbuffer[4] = static_cast<char>((value >> 32) & 0xFF);
outputbuffer[3] = static_cast<char>((value >> 24) & 0xFF);
outputbuffer[2] = static_cast<char>((value >> 16) & 0xFF);
outputbuffer[1] = static_cast<char>((value >> 8) & 0xFF);
outputbuffer[0] = static_cast<char>((value)&0xFF);
#ifdef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
value = swapOrder(value);
#endif
std::memcpy(outputbuffer, &value, sizeof(T));
}
/*!
@ -293,4 +215,6 @@ CPP_UTILITIES_EXPORT inline void getBytes(double value, char *outputbuffer)
#pragma GCC diagnostic pop
#endif
#undef CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL_NEEDS_SWAP
#endif // CONVERSION_UTILITIES_BINARY_CONVERSION_INTERNAL

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>
@ -266,28 +266,30 @@ template <class StringType = std::string, class... Args> inline StringType argsT
* \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3.
*/
template <class Tuple, class StringType,
Traits::EnableIfAny<Traits::IsSpecializationOf<StringType, std::basic_string>, Traits::IsSpecializationOf<StringType, std::basic_string_view>>
* = nullptr>
constexpr auto operator%(const Tuple &lhs, const StringType &rhs) -> decltype(std::tuple_cat(lhs, std::make_tuple(&rhs)))
Traits::EnableIf<Traits::IsSpecializationOf<Tuple, std::tuple>,
Traits::IsSpecializingAnyOf<StringType, std::basic_string, std::basic_string_view>> * = nullptr>
constexpr auto operator%(const Tuple &lhs, const StringType &rhs) -> decltype(std::tuple_cat(lhs, std::tuple<const StringType &>(rhs)))
{
return std::tuple_cat(lhs, std::make_tuple(&rhs));
return std::tuple_cat(lhs, std::tuple<const StringType &>(rhs));
}
/*!
* \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3.
*/
template <class Tuple> constexpr auto operator%(const Tuple &lhs, const char *rhs) -> decltype(std::tuple_cat(lhs, std::make_tuple(rhs)))
template <class Tuple, Traits::EnableIf<Traits::IsSpecializationOf<Tuple, std::tuple>> * = nullptr>
constexpr auto operator%(const Tuple &lhs, const char *rhs) -> decltype(std::tuple_cat(lhs, std::tuple<const char *>(rhs)))
{
return std::tuple_cat(lhs, std::make_tuple(rhs));
return std::tuple_cat(lhs, std::tuple<const char *>(rhs));
}
/*!
* \brief Allows construction of string-tuples via %-operator, eg. string1 % "string2" % string3.
*/
template <class Tuple, typename IntegralType, Traits::EnableIf<std::is_integral<IntegralType>> * = nullptr>
constexpr auto operator%(const Tuple &lhs, IntegralType rhs) -> decltype(std::tuple_cat(lhs, std::make_tuple(rhs)))
template <class Tuple, typename IntegralType,
Traits::EnableIf<Traits::IsSpecializationOf<Tuple, std::tuple>, std::is_integral<IntegralType>> * = nullptr>
constexpr auto operator%(const Tuple &lhs, IntegralType rhs) -> decltype(std::tuple_cat(lhs, std::tuple<IntegralType>(rhs)))
{
return std::tuple_cat(lhs, std::make_tuple(rhs));
return std::tuple_cat(lhs, std::tuple<IntegralType>(rhs));
}
/*!
@ -296,9 +298,9 @@ constexpr auto operator%(const Tuple &lhs, IntegralType rhs) -> decltype(std::tu
template <class StringType,
Traits::EnableIfAny<Traits::IsSpecializationOf<StringType, std::basic_string>, Traits::IsSpecializationOf<StringType, std::basic_string_view>>
* = nullptr>
constexpr auto operator%(const StringType &lhs, const StringType &rhs) -> decltype(std::make_tuple(&lhs, &rhs))
constexpr auto operator%(const StringType &lhs, const StringType &rhs) -> decltype(std::tuple<const StringType &, const StringType &>(lhs, rhs))
{
return std::make_tuple(&lhs, &rhs);
return std::tuple<const StringType &, const StringType &>(lhs, rhs);
}
/*!
@ -307,9 +309,9 @@ constexpr auto operator%(const StringType &lhs, const StringType &rhs) -> declty
template <class StringType,
Traits::EnableIfAny<Traits::IsSpecializationOf<StringType, std::basic_string>, Traits::IsSpecializationOf<StringType, std::basic_string_view>>
* = nullptr>
constexpr auto operator%(const char *lhs, const StringType &rhs) -> decltype(std::make_tuple(lhs, &rhs))
constexpr auto operator%(const char *lhs, const StringType &rhs) -> decltype(std::tuple<const char *, const StringType &>(lhs, rhs))
{
return std::make_tuple(lhs, &rhs);
return std::tuple<const char *, const StringType &>(lhs, rhs);
}
/*!
@ -318,9 +320,9 @@ constexpr auto operator%(const char *lhs, const StringType &rhs) -> decltype(std
template <class StringType,
Traits::EnableIfAny<Traits::IsSpecializationOf<StringType, std::basic_string>, Traits::IsSpecializationOf<StringType, std::basic_string_view>>
* = nullptr>
constexpr auto operator%(const StringType &lhs, const char *rhs) -> decltype(std::make_tuple(&lhs, rhs))
constexpr auto operator%(const StringType &lhs, const char *rhs) -> decltype(std::tuple<const StringType &, const char *>(lhs, rhs))
{
return std::make_tuple(&lhs, rhs);
return std::tuple<const StringType &, const char *>(lhs, rhs);
}
/*!
@ -329,9 +331,9 @@ constexpr auto operator%(const StringType &lhs, const char *rhs) -> decltype(std
template <class StringType,
Traits::EnableIfAny<Traits::IsSpecializationOf<StringType, std::basic_string>, Traits::IsSpecializationOf<StringType, std::basic_string_view>>
* = nullptr>
constexpr auto operator%(const StringType &lhs, char rhs) -> decltype(std::make_tuple(&lhs, rhs))
constexpr auto operator%(const StringType &lhs, char rhs) -> decltype(std::tuple<const StringType &, char>(lhs, rhs))
{
return std::make_tuple(&lhs, rhs);
return std::tuple<const StringType &, char>(lhs, rhs);
}
/*!
@ -340,9 +342,9 @@ constexpr auto operator%(const StringType &lhs, char rhs) -> decltype(std::make_
template <class StringType,
Traits::EnableIfAny<Traits::IsSpecializationOf<StringType, std::basic_string>, Traits::IsSpecializationOf<StringType, std::basic_string_view>>
* = nullptr>
constexpr auto operator%(char lhs, const StringType &rhs) -> decltype(std::make_tuple(lhs, &rhs))
constexpr auto operator%(char lhs, const StringType &rhs) -> decltype(std::tuple<char, const StringType &>(lhs, rhs))
{
return std::make_tuple(lhs, &rhs);
return std::tuple<char, const StringType &>(lhs, rhs);
}
/*!
@ -360,7 +362,7 @@ template <class Tuple, class StringType,
* = nullptr>
inline std::string operator+(const Tuple &lhs, const StringType &rhs)
{
return tupleToString(std::tuple_cat(lhs, std::make_tuple(&rhs)));
return tupleToString(std::tuple_cat(lhs, std::tuple<const StringType &>(rhs)));
}
/*!
@ -375,7 +377,7 @@ inline std::string operator+(const Tuple &lhs, const StringType &rhs)
template <class Tuple, Traits::EnableIf<Traits::IsSpecializationOf<Tuple, std::tuple>> * = nullptr>
inline std::string operator+(const Tuple &lhs, const char *rhs)
{
return tupleToString(std::tuple_cat(lhs, std::make_tuple(rhs)));
return tupleToString(std::tuple_cat(lhs, std::tuple<const char *>(rhs)));
}
/*!
@ -391,7 +393,7 @@ template <class Tuple, typename IntegralType,
Traits::EnableIf<Traits::IsSpecializationOf<Tuple, std::tuple>, std::is_integral<IntegralType>> * = nullptr>
inline std::string operator+(const Tuple &lhs, IntegralType rhs)
{
return tupleToString(std::tuple_cat(lhs, std::make_tuple(rhs)));
return tupleToString(std::tuple_cat(lhs, std::tuple<IntegralType>(rhs)));
}
} // namespace CppUtilities

View File

@ -2,10 +2,13 @@
#ifndef CPP_UTILITIES_NO_THREAD_LOCAL
#include "../feature_detection/features.h"
#else
#endif
#ifndef CPP_UTILITIES_THREAD_LOCAL
#define CPP_UTILITIES_THREAD_LOCAL
#endif
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iomanip>
@ -18,6 +21,13 @@
#ifdef PLATFORM_WINDOWS
#include <windows.h>
// note: The windows header seriously defines a macro called "max" breaking the (common) use
// of std::numeric_limits in the subsequent code. So we need to undefine this macro. Note that
// this is not the case using mingw-w64 but it is happening with windows.h from Windows Kits
// version 10.0.22000.0 via Visual Studio 2022.
#ifdef max
#undef max
#endif
#endif
using namespace std;
@ -194,22 +204,25 @@ StringData convertUtf8ToLatin1(const char *inputBuffer, std::size_t inputBufferS
#ifdef PLATFORM_WINDOWS
/*!
* \brief Converts the specified multi-byte string (assumed to be UTF-8) to a wide string using the WinAPI.
* \remarks Only available under Windows.
* \remarks
* - Only available under Windows.
* - If \a inputBuffer exceeds std::numeric_limits<int>::max() it will be truncated.
*/
std::wstring convertMultiByteToWide(std::error_code &ec, std::string_view inputBuffer)
{
// calculate required size
auto widePath = std::wstring();
auto size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), nullptr, 0);
auto bufferSize = static_cast<int>(std::clamp<std::size_t>(inputBuffer.size(), 0, std::numeric_limits<int>::max()));
auto size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), bufferSize, nullptr, 0);
if (size <= 0) {
ec = std::error_code(GetLastError(), std::system_category());
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
return widePath;
}
// do the actual conversion
widePath.resize(static_cast<std::wstring::size_type>(size));
size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), inputBuffer.size(), widePath.data(), size);
size = MultiByteToWideChar(CP_UTF8, 0, inputBuffer.data(), bufferSize, widePath.data(), size);
if (size <= 0) {
ec = std::error_code(GetLastError(), std::system_category());
ec = std::error_code(static_cast<int>(GetLastError()), std::system_category());
widePath.clear();
}
return widePath;
@ -227,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;
@ -246,8 +259,8 @@ WideStringData convertMultiByteToWide(std::error_code &ec, const char *inputBuff
*/
WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &inputBuffer)
{
return convertMultiByteToWide(
ec, inputBuffer.data(), inputBuffer.size() < (numeric_limits<int>::max() - 1) ? static_cast<int>(inputBuffer.size() + 1) : -1);
return convertMultiByteToWide(ec, inputBuffer.data(),
inputBuffer.size() < static_cast<std::size_t>(std::numeric_limits<int>::max() - 1) ? static_cast<int>(inputBuffer.size() + 1) : -1);
}
/*!
@ -401,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;
@ -437,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(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(move(buffer), decodedSize);
return std::make_pair(std::move(buffer), decodedSize);
default:
throw ConversionException("invalid padding in base64");
}
@ -452,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(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

@ -226,9 +226,6 @@
# else
# define CPP_UTILITIES_COMPILER_CXX_THREAD_LOCAL 0
# endif
# else
# error Unsupported compiler
# endif
# if defined(CPP_UTILITIES_COMPILER_CXX_THREAD_LOCAL) && CPP_UTILITIES_COMPILER_CXX_THREAD_LOCAL
@ -236,7 +233,7 @@
# elif CPP_UTILITIES_COMPILER_IS_GNU || CPP_UTILITIES_COMPILER_IS_Clang || CPP_UTILITIES_COMPILER_IS_AppleClang
# define CPP_UTILITIES_THREAD_LOCAL __thread
# elif CPP_UTILITIES_COMPILER_IS_MSVC
# define CPP_UTILITIES_THREAD_LOCAL __declspec(thread)
# define CPP_UTILITIES_THREAD_LOCAL __declspec(thread) static
# else
// CPP_UTILITIES_THREAD_LOCAL not defined for this configuration.
# endif

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

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

View File

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

View File

@ -243,7 +243,7 @@ inline void BinaryReader::read(std::vector<char> &buffer, std::streamsize length
inline std::int16_t BinaryReader::readInt16BE()
{
m_stream->read(m_buffer, sizeof(std::int16_t));
return BE::toInt16(m_buffer);
return BE::toInt<std::int16_t>(m_buffer);
}
/*!
@ -252,7 +252,7 @@ inline std::int16_t BinaryReader::readInt16BE()
inline std::uint16_t BinaryReader::readUInt16BE()
{
m_stream->read(m_buffer, sizeof(std::uint16_t));
return BE::toUInt16(m_buffer);
return BE::toInt<std::uint16_t>(m_buffer);
}
/*!
@ -262,7 +262,7 @@ inline std::int32_t BinaryReader::readInt24BE()
{
*m_buffer = 0;
m_stream->read(m_buffer + 1, 3);
auto val = BE::toInt32(m_buffer);
auto val = BE::toInt<std::int32_t>(m_buffer);
if (val >= 0x800000) {
val = -(0x1000000 - val);
}
@ -276,7 +276,7 @@ inline std::uint32_t BinaryReader::readUInt24BE()
{
*m_buffer = 0;
m_stream->read(m_buffer + 1, 3);
return BE::toUInt32(m_buffer);
return BE::toInt<std::uint32_t>(m_buffer);
}
/*!
@ -285,7 +285,7 @@ inline std::uint32_t BinaryReader::readUInt24BE()
inline std::int32_t BinaryReader::readInt32BE()
{
m_stream->read(m_buffer, sizeof(std::int32_t));
return BE::toInt32(m_buffer);
return BE::toInt<std::int32_t>(m_buffer);
}
/*!
@ -294,7 +294,7 @@ inline std::int32_t BinaryReader::readInt32BE()
inline std::uint32_t BinaryReader::readUInt32BE()
{
m_stream->read(m_buffer, sizeof(std::uint32_t));
return BE::toUInt32(m_buffer);
return BE::toInt<std::uint32_t>(m_buffer);
}
/*!
@ -304,7 +304,7 @@ inline std::int64_t BinaryReader::readInt40BE()
{
*m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0;
m_stream->read(m_buffer + 3, 5);
auto val = BE::toInt64(m_buffer);
auto val = BE::toInt<std::int64_t>(m_buffer);
if (val >= 0x8000000000) {
val = -(0x10000000000 - val);
}
@ -318,7 +318,7 @@ inline std::uint64_t BinaryReader::readUInt40BE()
{
*m_buffer = *(m_buffer + 1) = *(m_buffer + 2) = 0;
m_stream->read(m_buffer + 3, 5);
return BE::toUInt64(m_buffer);
return BE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -328,7 +328,7 @@ inline std::int64_t BinaryReader::readInt56BE()
{
*m_buffer = 0;
m_stream->read(m_buffer + 1, 7);
auto val = BE::toInt64(m_buffer);
auto val = BE::toInt<std::int64_t>(m_buffer);
if (val >= 0x80000000000000) {
val = -(0x100000000000000 - val);
}
@ -342,7 +342,7 @@ inline std::uint64_t BinaryReader::readUInt56BE()
{
*m_buffer = 0;
m_stream->read(m_buffer + 1, 7);
return BE::toUInt64(m_buffer);
return BE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -351,7 +351,7 @@ inline std::uint64_t BinaryReader::readUInt56BE()
inline std::int64_t BinaryReader::readInt64BE()
{
m_stream->read(m_buffer, sizeof(std::int64_t));
return BE::toInt64(m_buffer);
return BE::toInt<std::int64_t>(m_buffer);
}
/*!
@ -360,7 +360,7 @@ inline std::int64_t BinaryReader::readInt64BE()
inline std::uint64_t BinaryReader::readUInt64BE()
{
m_stream->read(m_buffer, sizeof(std::uint64_t));
return BE::toUInt64(m_buffer);
return BE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -370,7 +370,7 @@ inline std::uint64_t BinaryReader::readUInt64BE()
inline std::uint64_t BinaryReader::readVariableLengthUIntBE()
{
bufferVariableLengthInteger();
return BE::toUInt64(m_buffer);
return BE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -397,7 +397,7 @@ inline double BinaryReader::readFloat64BE()
inline std::int16_t BinaryReader::readInt16LE()
{
m_stream->read(m_buffer, sizeof(std::int16_t));
return LE::toInt16(m_buffer);
return LE::toInt<std::int16_t>(m_buffer);
}
/*!
@ -406,7 +406,7 @@ inline std::int16_t BinaryReader::readInt16LE()
inline std::uint16_t BinaryReader::readUInt16LE()
{
m_stream->read(m_buffer, sizeof(std::uint16_t));
return LE::toUInt16(m_buffer);
return LE::toInt<std::uint16_t>(m_buffer);
}
/*!
@ -416,7 +416,7 @@ inline std::int32_t BinaryReader::readInt24LE()
{
*(m_buffer + 3) = 0;
m_stream->read(m_buffer, 3);
auto val = LE::toInt32(m_buffer);
auto val = LE::toInt<std::int32_t>(m_buffer);
if (val >= 0x800000) {
val = -(0x1000000 - val);
}
@ -430,7 +430,7 @@ inline std::uint32_t BinaryReader::readUInt24LE()
{
*(m_buffer + 3) = 0;
m_stream->read(m_buffer, 3);
return LE::toUInt32(m_buffer);
return LE::toInt<std::uint32_t>(m_buffer);
}
/*!
@ -439,7 +439,7 @@ inline std::uint32_t BinaryReader::readUInt24LE()
inline std::int32_t BinaryReader::readInt32LE()
{
m_stream->read(m_buffer, sizeof(std::int32_t));
return LE::toInt32(m_buffer);
return LE::toInt<std::int32_t>(m_buffer);
}
/*!
@ -448,7 +448,7 @@ inline std::int32_t BinaryReader::readInt32LE()
inline std::uint32_t BinaryReader::readUInt32LE()
{
m_stream->read(m_buffer, sizeof(std::uint32_t));
return LE::toUInt32(m_buffer);
return LE::toInt<std::uint32_t>(m_buffer);
}
/*!
@ -458,7 +458,7 @@ inline std::int64_t BinaryReader::readInt40LE()
{
*(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0;
m_stream->read(m_buffer, 5);
auto val = LE::toInt64(m_buffer);
auto val = LE::toInt<std::int64_t>(m_buffer);
if (val >= 0x8000000000) {
val = -(0x10000000000 - val);
}
@ -472,7 +472,7 @@ inline std::uint64_t BinaryReader::readUInt40LE()
{
*(m_buffer + 5) = *(m_buffer + 6) = *(m_buffer + 7) = 0;
m_stream->read(m_buffer, 5);
return LE::toUInt64(m_buffer);
return LE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -482,7 +482,7 @@ inline std::int64_t BinaryReader::readInt56LE()
{
*(m_buffer + 7) = 0;
m_stream->read(m_buffer, 7);
auto val = LE::toInt64(m_buffer);
auto val = LE::toInt<std::int64_t>(m_buffer);
if (val >= 0x80000000000000) {
val = -(0x100000000000000 - val);
}
@ -496,7 +496,7 @@ inline std::uint64_t BinaryReader::readUInt56LE()
{
*(m_buffer + 7) = 0;
m_stream->read(m_buffer, 7);
return LE::toUInt64(m_buffer);
return LE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -505,7 +505,7 @@ inline std::uint64_t BinaryReader::readUInt56LE()
inline std::int64_t BinaryReader::readInt64LE()
{
m_stream->read(m_buffer, sizeof(std::int64_t));
return LE::toInt64(m_buffer);
return LE::toInt<std::int64_t>(m_buffer);
}
/*!
@ -514,7 +514,7 @@ inline std::int64_t BinaryReader::readInt64LE()
inline std::uint64_t BinaryReader::readUInt64LE()
{
m_stream->read(m_buffer, sizeof(std::uint64_t));
return LE::toUInt64(m_buffer);
return LE::toInt<std::uint64_t>(m_buffer);
}
/*!
@ -524,7 +524,7 @@ inline std::uint64_t BinaryReader::readUInt64LE()
inline std::uint64_t BinaryReader::readVariableLengthUIntLE()
{
bufferVariableLengthInteger();
return LE::toUInt64(m_buffer);
return LE::toInt<std::uint64_t>(m_buffer);
}
/*!

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

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

View File

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

View File

@ -6,15 +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> {
};
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

@ -20,32 +20,25 @@ enum class Enabler {};
template <typename If, typename Then, typename Else> using Conditional = typename std::conditional<If::value, Then, Else>::type;
/// \brief Wraps a static boolean constant.
template <bool B, typename...> struct Bool : std::integral_constant<bool, B> {
};
template <bool B, typename...> struct Bool : std::integral_constant<bool, B> {};
/// \brief Negates the specified value.
template <typename T> using Not = Bool<!T::value>;
/// \brief Evaluates to Bool<true> if at least one of the specified conditions is true; otherwise evaluates to Bool<false>.
template <typename... T> struct Any : Bool<false> {
};
template <typename... T> struct Any : Bool<false> {};
/// \brief Evaluates to Bool<true> if at least one of the specified conditions is true; otherwise evaluates to Bool<false>.
template <typename Head, typename... Tail> struct Any<Head, Tail...> : Conditional<Head, Bool<true>, Any<Tail...>> {
};
template <typename Head, typename... Tail> struct Any<Head, Tail...> : Conditional<Head, Bool<true>, Any<Tail...>> {};
/// \brief Evaluates to Bool<true> if all specified conditions are true; otherwise evaluates to Bool<false>.
template <typename... T> struct All : Bool<true> {
};
template <typename... T> struct All : Bool<true> {};
/// \brief Evaluates to Bool<true> if all specified conditions are true; otherwise evaluates to Bool<false>.
template <typename Head, typename... Tail> struct All<Head, Tail...> : Conditional<Head, All<Tail...>, Bool<false>> {
};
template <typename Head, typename... Tail> struct All<Head, Tail...> : Conditional<Head, All<Tail...>, Bool<false>> {};
/// \brief Evaluates to Bool<true> if none of the specified conditions are true; otherwise evaluates to Bool<false>.
template <typename... T> struct None : Bool<true> {
};
template <typename... T> struct None : Bool<true> {};
/// \brief Evaluates to Bool<true> if none of the specified conditions are true; otherwise evaluates to Bool<false>.
template <typename Head, typename... Tail> struct None<Head, Tail...> : Conditional<Head, Bool<false>, None<Tail...>> {
};
template <typename Head, typename... Tail> struct None<Head, Tail...> : Conditional<Head, Bool<false>, None<Tail...>> {};
/// \brief Shortcut for std::enable_if to omit ::value and ::type.
template <typename... Condition> using EnableIf = typename std::enable_if<All<Condition...>::value, Detail::Enabler>::type;
@ -59,59 +52,46 @@ template <typename... Condition> using DisableIfAny = typename std::enable_if<!A
/// \cond
namespace Detail {
template <typename T, template <typename...> class Template> struct IsSpecializationOfHelper : Bool<false> {
};
template <template <typename...> class Template, typename... Args> struct IsSpecializationOfHelper<Template<Args...>, Template> : Bool<true> {
};
template <typename T, template <typename...> class Template> struct IsSpecializationOfHelper : Bool<false> {};
template <template <typename...> class Template, typename... Args> struct IsSpecializationOfHelper<Template<Args...>, Template> : Bool<true> {};
} // namespace Detail
/// \endcond
/// \brief Evaluates to Bool<true> if the specified type is based on the specified template; otherwise evaluates to Bool<false>.
template <typename Type, template <typename...> class... TemplateTypes>
struct IsSpecializationOf
: Detail::IsSpecializationOfHelper<typename std::remove_cv<typename std::remove_reference<Type>::type>::type, TemplateTypes...> {
};
: Detail::IsSpecializationOfHelper<typename std::remove_cv<typename std::remove_reference<Type>::type>::type, TemplateTypes...> {};
/// \brief Evaluates to Bool<true> if the specified type is based on one of the specified templates; otherwise evaluates to Bool<false>.
template <typename Type, template <typename...> class... TemplateTypes> struct IsSpecializingAnyOf : Bool<false> {
};
template <typename Type, template <typename...> class... TemplateTypes> struct IsSpecializingAnyOf : Bool<false> {};
/// \brief Evaluates to Bool<true> if the specified type is based on one of the specified templates; otherwise evaluates to Bool<false>.
template <typename Type, template <typename...> class TemplateType, template <typename...> class... RemainingTemplateTypes>
struct IsSpecializingAnyOf<Type, TemplateType, RemainingTemplateTypes...>
: Conditional<IsSpecializationOf<Type, TemplateType>, Bool<true>, IsSpecializingAnyOf<Type, RemainingTemplateTypes...>> {
};
: Conditional<IsSpecializationOf<Type, TemplateType>, Bool<true>, IsSpecializingAnyOf<Type, RemainingTemplateTypes...>> {};
/// \brief Evaluates to Bool<true> if the specified type is any of the specified types; otherwise evaluates to Bool<false>.
template <typename... T> struct IsAnyOf : Bool<false> {
};
template <typename... T> struct IsAnyOf : Bool<false> {};
/// \brief Evaluates to Bool<true> if the specified type is any of the specified types; otherwise evaluates to Bool<false>.
template <typename Type, typename OtherType, typename... RemainingTypes>
struct IsAnyOf<Type, OtherType, RemainingTypes...> : Conditional<std::is_same<Type, OtherType>, Bool<true>, IsAnyOf<Type, RemainingTypes...>> {
};
struct IsAnyOf<Type, OtherType, RemainingTypes...> : Conditional<std::is_same<Type, OtherType>, Bool<true>, IsAnyOf<Type, RemainingTypes...>> {};
/// \brief Evaluates to Bool<true> if the specified type is none of the specified types; otherwise evaluates to Bool<false>.
template <typename... T> struct IsNoneOf : Bool<true> {
};
template <typename... T> struct IsNoneOf : Bool<true> {};
/// \brief Evaluates to Bool<true> if the specified type is none of the specified types; otherwise evaluates to Bool<false>.
template <typename Type, typename OtherType, typename... RemainingTypes>
struct IsNoneOf<Type, OtherType, RemainingTypes...> : Conditional<std::is_same<Type, OtherType>, Bool<false>, IsNoneOf<Type, RemainingTypes...>> {
};
struct IsNoneOf<Type, OtherType, RemainingTypes...> : Conditional<std::is_same<Type, OtherType>, Bool<false>, IsNoneOf<Type, RemainingTypes...>> {};
/// \brief Evaluates to Bool<true> if the specified type is a C-string (char * or const char *); otherwise evaluates to Bool<false>.
template <typename T>
struct IsCString
: Bool<std::is_same<char const *, typename std::decay<T>::type>::value || std::is_same<char *, typename std::decay<T>::type>::value> {
};
: Bool<std::is_same<char const *, typename std::decay<T>::type>::value || std::is_same<char *, typename std::decay<T>::type>::value> {};
/// \brief Evaluates to Bool<true> if the specified type is a standard string, standard string view or C-string (char * or const char *); otherwise
/// evaluates to Bool<false>.
template <typename T>
struct IsString
: Bool<IsCString<T>::value || IsSpecializationOf<T, std::basic_string>::value || IsSpecializationOf<T, std::basic_string_view>::value> {
};
: Bool<IsCString<T>::value || IsSpecializationOf<T, std::basic_string>::value || IsSpecializationOf<T, std::basic_string_view>::value> {};
/// \brief Evaluates to Bool<true> if the specified type is complete; if the type is only forward-declared it evaluates to Bool<false>.
template <typename T, typename = void> struct IsComplete : Bool<false> {
};
template <typename T, typename = void> struct IsComplete : Bool<false> {};
/// \brief Evaluates to Bool<true> if the specified type is complete; if the type is only forward-declared it evaluates to Bool<false>.
template <typename T> struct IsComplete<T, decltype(void(sizeof(T)))> : Bool<true> {
};
template <typename T> struct IsComplete<T, decltype(void(sizeof(T)))> : Bool<true> {};
/*!
* \def CPP_UTILITIES_PP_COMMA

View File

@ -21,6 +21,7 @@
#include <cstdlib>
#include <cstring>
#include <regex>
#ifdef PLATFORM_WINDOWS
#include <windows.h>
@ -109,7 +110,7 @@ void ArgumentParserTests::testArgument()
#endif
ArgumentOccurrence occurrence(0, vector<Argument *>(), nullptr);
occurrence.values.emplace_back("bar");
argument.m_occurrences.emplace_back(move(occurrence));
argument.m_occurrences.emplace_back(std::move(occurrence));
CPPUNIT_ASSERT_EQUAL("bar"s, string(argument.firstValue()));
}
@ -189,7 +190,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("album"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_THROW(displayTagInfoArg.values().at(3), std::out_of_range);
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
CPPUNIT_ASSERT_EQUAL(&displayTagInfoArg, parser.specifiedOperation());
// skip empty args
@ -207,7 +208,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_EQUAL(""sv, std::string_view(fieldsArg.values().at(3)));
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(4), std::out_of_range);
CPPUNIT_ASSERT_EQUAL(4_st, fieldsArg.values().size());
CPPUNIT_ASSERT(filesArg.isPresent());
CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
@ -266,7 +267,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT(!filesArg.isPresent());
CPPUNIT_ASSERT(fileArg.isPresent());
CPPUNIT_ASSERT_EQUAL("test"sv, std::string_view(fileArg.values().at(0)));
CPPUNIT_ASSERT_THROW(fileArg.values().at(1), std::out_of_range);
CPPUNIT_ASSERT_EQUAL(1_st, fileArg.values().size());
// constraint checking: no multiple occurrences (not resetting verboseArg on purpose)
displayFileInfoArg.reset();
@ -404,7 +405,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("album=test"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("title"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("diskpos"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), out_of_range);
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
CPPUNIT_ASSERT(filesArg.isPresent());
CPPUNIT_ASSERT_EQUAL("somefile"sv, std::string_view(filesArg.values().at(0)));
CPPUNIT_ASSERT(!notAlbumArg.isPresent());
@ -472,7 +473,7 @@ void ArgumentParserTests::testParsing()
CPPUNIT_ASSERT_EQUAL("foo"sv, std::string_view(fieldsArg.values().at(0)));
CPPUNIT_ASSERT_EQUAL("bar"sv, std::string_view(fieldsArg.values().at(1)));
CPPUNIT_ASSERT_EQUAL("--help"sv, std::string_view(fieldsArg.values().at(2)));
CPPUNIT_ASSERT_THROW(fieldsArg.values().at(3), std::out_of_range);
CPPUNIT_ASSERT_EQUAL(3_st, fieldsArg.values().size());
}
/*!
@ -815,30 +816,30 @@ void ArgumentParserTests::testHelp()
// parse args and assert output
const char *const argv[] = { "app", "-h" };
{
const OutputCheck c("\e[1m" APP_NAME ", version " APP_VERSION "\n"
const OutputCheck c("\033[1m" APP_NAME ", version " APP_VERSION "\n"
"\n"
"\e[0m" APP_DESCRIPTION "\n"
"\033[0m" APP_DESCRIPTION "\n"
"\n"
"Available operations:\n"
"\e[1mverbose, -v\e[0m\n"
"\033[1mverbose, -v\033[0m\n"
" be verbose\n"
" example: actually not an operation\n"
"\n"
"Available top-level options:\n"
"\e[1m--files, -f\e[0m\n"
"\033[1m--files, -f\033[0m\n"
" specifies the path of the file(s) to be opened\n"
" \e[1m--sub\e[0m\n"
" \033[1m--sub\033[0m\n"
" sub arg\n"
" particularities: mandatory if parent argument is present\n"
" \e[1m--nested-sub\e[0m [value1] [value2] ...\n"
" \033[1m--nested-sub\033[0m [value1] [value2] ...\n"
" nested sub arg\n"
" example: sub arg example\n"
"\n"
"\e[1m--env\e[0m [file] [value 2]\n"
"\033[1m--env\033[0m [file] [value 2]\n"
" env\n"
" default environment variable: FILES\n"
"\n"
"\e[1m--no-color\e[0m\n"
"\033[1m--no-color\033[0m\n"
" disables formatted/colorized output\n"
" default environment variable: ENABLE_ESCAPE_CODES\n"
"\n"
@ -981,8 +982,9 @@ void ArgumentParserTests::testValueConversion()
occurrence.convertValues<int>();
CPPUNIT_FAIL("Expected exception");
} catch (const ParseError &failure) {
CPPUNIT_ASSERT_EQUAL(
"Conversion of top-level value \"foo\" to type \"i\" failed: The character \"f\" is no valid digit."s, string(failure.what()));
TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of top-level value",
"Conversion of top-level value \"foo\" to type \".*\" failed: The character \"f\" is no valid digit."s, std::regex::extended,
std::string(failure.what()));
}
occurrence.path = { &arg };
try {
@ -995,7 +997,8 @@ void ArgumentParserTests::testValueConversion()
occurrence.convertValues<int>();
CPPUNIT_FAIL("Expected exception");
} catch (const ParseError &failure) {
CPPUNIT_ASSERT_EQUAL("Conversion of value \"foo\" (for argument --test) to type \"i\" failed: The character \"f\" is no valid digit."s,
string(failure.what()));
TESTUTILS_ASSERT_LIKE_FLAGS("conversion error of argument value",
"Conversion of value \"foo\" \\(for argument --test\\) to type \".*\" failed: The character \"f\" is no valid digit."s,
std::regex::extended, std::string(failure.what()));
}
}

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

@ -27,6 +27,9 @@ static_assert(toNormalInt(383) == 255, "toNormalInt()");
static_assert(swapOrder(static_cast<std::uint16_t>(0xABCD)) == 0xCDAB, "swapOrder(std::uint16_t)");
static_assert(swapOrder(static_cast<std::uint32_t>(0xABCDEF12u)) == 0x12EFCDABu, "swapOrder(std::uint32_t)");
static_assert(swapOrder(static_cast<std::uint64_t>(0xABCDEF1234567890ul)) == 0x9078563412EFCDABul, "swapOrder(std::uint64_t)");
static_assert(swapOrder(static_cast<std::int16_t>(0xABCD)) == static_cast<std::int16_t>(0xCDAB), "swapOrder(std::int16_t)");
static_assert(swapOrder(static_cast<std::int32_t>(0xABCDEF12)) == 0x12EFCDAB, "swapOrder(std::int32_t)");
static_assert(swapOrder(static_cast<std::int64_t>(0xABCDEF1234567890l)) == static_cast<std::int64_t>(0x9078563412EFCDABl), "swapOrder(std::int64_t)");
/*!
* \brief The ConversionTests class tests classes and functions provided by the files inside the conversion directory.
@ -116,13 +119,13 @@ void ConversionTests::testConversion(
stringstream msg;
msg << message << '(' << hex << '0' << 'x' << random << ')';
vice(random, m_buff);
CPPUNIT_ASSERT_MESSAGE(msg.str(), versa(m_buff) == random);
CPPUNIT_ASSERT_EQUAL_MESSAGE(msg.str(), random, versa(m_buff));
}
#define TEST_TYPE(endianness, function) decltype(endianness::function(m_buff))
#define TEST_CONVERSION(function, endianness) \
testConversion<TEST_TYPE(endianness, function)>("testing " #function, \
testConversion<TEST_TYPE(endianness, function)>("testing " #endianness "::" #function, \
static_cast<void (*)(TEST_TYPE(endianness, function), char *)>(&endianness::getBytes), endianness::function, \
numeric_limits<TEST_TYPE(endianness, function)>::min(), numeric_limits<TEST_TYPE(endianness, function)>::max())
@ -131,8 +134,8 @@ void ConversionTests::testConversion(
#define TEST_LE_CONVERSION(function) TEST_CONVERSION(function, LE)
#define TEST_CUSTOM_CONVERSION(vice, versa, endianness, min, max) \
testConversion<TEST_TYPE(endianness, versa)>( \
"testing " #versa, static_cast<void (*)(TEST_TYPE(endianness, versa), char *)>(&endianness::vice), endianness::versa, min, max)
testConversion<TEST_TYPE(endianness, versa)>("testing " #versa " (" #endianness ")", \
static_cast<void (*)(TEST_TYPE(endianness, versa), char *)>(&endianness::vice), endianness::versa, min, max)
/*!
* \brief Tests most important binary conversions.
@ -363,10 +366,10 @@ void ConversionTests::testStringConversions()
CPPUNIT_ASSERT_EQUAL("foo"s, truncateTest);
// encodeBase64() / decodeBase64() with random data
uniform_int_distribution<std::uint8_t> randomDistChar;
std::uniform_int_distribution<int> randomDistChar;
std::uint8_t originalBase64Data[4047];
for (std::uint8_t &c : originalBase64Data) {
c = randomDistChar(m_randomEngine);
c = static_cast<std::uint8_t>(randomDistChar(m_randomEngine) & 0xFF);
}
auto encodedBase64Data = encodeBase64(originalBase64Data, sizeof(originalBase64Data));
auto decodedBase64Data = decodeBase64(encodedBase64Data.data(), static_cast<std::uint32_t>(encodedBase64Data.size()));

View File

@ -67,6 +67,7 @@ class IoTests : public TestFixture {
CPPUNIT_TEST(testIniFile);
CPPUNIT_TEST(testAdvancedIniFile);
CPPUNIT_TEST(testCopy);
CPPUNIT_TEST(testCopyWithNativeFileStream);
CPPUNIT_TEST(testReadFile);
CPPUNIT_TEST(testWriteFile);
CPPUNIT_TEST(testAnsiEscapeCodes);
@ -87,6 +88,7 @@ public:
void testIniFile();
void testAdvancedIniFile();
void testCopy();
void testCopyWithNativeFileStream();
void testReadFile();
void testWriteFile();
void testAnsiEscapeCodes();
@ -185,7 +187,10 @@ void IoTests::testBinaryWriter()
stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary);
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
char testData[397];
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
#define USE_RDBUF_DIRECTLY
outputStream.rdbuf()->pubsetbuf(testData, sizeof(testData));
#endif
// write test data
BinaryWriter writer(&outputStream);
@ -202,12 +207,20 @@ void IoTests::testBinaryWriter()
writer.writeUInt64LE(0x0102030405060708u);
writer.writeUInt64BE(0x0102030405060708u);
#ifndef USE_RDBUF_DIRECTLY
outputStream.seekg(0);
outputStream.read(testData, 58);
#endif
// test written values
for (char c : testData) {
CPPUNIT_ASSERT(c == static_cast<char>(testFile.get()));
if (testFile.tellg() >= 58) {
const auto pos = static_cast<std::size_t>(testFile.tellg());
if (pos >= 58) {
break;
}
char expected;
testFile.read(&expected, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE(argsToString("offset ", pos), asHexNumber(expected), asHexNumber(c));
}
testFile.seekg(0);
outputStream.seekp(0);
@ -238,9 +251,20 @@ void IoTests::testBinaryWriter()
"234567890123456789012345678901234567890123456789012345678901234567890123456789");
writer.writeTerminatedString("def");
#ifndef USE_RDBUF_DIRECTLY
outputStream.seekg(0);
outputStream.read(testData, 58);
#endif
// test written values
for (char c : testData) {
CPPUNIT_ASSERT(c == static_cast<char>(testFile.get()));
const auto pos = static_cast<std::size_t>(testFile.tellg());
if (pos >= 58) {
break;
}
char expected;
testFile.read(&expected, 1);
CPPUNIT_ASSERT_EQUAL_MESSAGE(argsToString("offset ", pos), asHexNumber(expected), asHexNumber(c));
}
// test ownership
@ -288,7 +312,7 @@ void IoTests::testBufferSearch()
// setup search to test
auto expectedResult = std::string();
auto hasResult = false;
auto bs = BufferSearch("Updated version: ", "\e\n", "Starting build", [&](BufferSearch &, std::string &&result) {
auto bs = BufferSearch("Updated version: ", "\t\n", "Starting build", [&](BufferSearch &, std::string &&result) {
CPPUNIT_ASSERT_EQUAL(expectedResult, result);
CPPUNIT_ASSERT_MESSAGE("callback only invoked once", !hasResult);
hasResult = true;
@ -305,7 +329,7 @@ void IoTests::testBufferSearch()
bs(buffer, 15);
CPPUNIT_ASSERT(!hasResult);
expectedResult = "some version number";
std::strcpy(buffer, "version number\emore chars");
std::strcpy(buffer, "version number\tmore chars");
bs(buffer, 25);
CPPUNIT_ASSERT(hasResult);
hasResult = false;
@ -332,13 +356,15 @@ void IoTests::testPathUtilities()
CPPUNIT_ASSERT(invalidPath == "libc++utilities.so");
const auto input = std::string_view("some/path/täst");
#ifdef PLATFORM_WINDOWS
const auto expected = std::wstring(L"some/path/täst");
#else
const auto expected = input;
#endif
const auto output = makeNativePath(input);
#ifdef PLATFORM_WINDOWS
const auto outputAsUtf8 = convertUtf16LEToUtf8(reinterpret_cast<const char *>(output.data()), output.size() * 2);
const auto outputView = std::string_view(outputAsUtf8.first.get(), outputAsUtf8.second);
CPPUNIT_ASSERT_EQUAL_MESSAGE("makeNativePath()", expected, outputView);
#else
CPPUNIT_ASSERT_EQUAL_MESSAGE("makeNativePath()", expected, output);
#endif
}
/*!
@ -400,26 +426,35 @@ void IoTests::testAdvancedIniFile()
CPPUNIT_ASSERT_EQUAL_MESSAGE("5 scopes (taking implicit empty section at the end into account)", 5_st, ini.sections.size());
auto options = ini.findSection("options");
CPPUNIT_ASSERT(options != ini.sectionEnd());
#if !defined(PLATFORM_WINDOWS) || defined(PLATFORM_MINGW) || defined(PLATFORM_CYGWIN)
#define STD_REGEX_WORKS // the parsed data looks good, apparently std::regex behaves differently on MSVC
TESTUTILS_ASSERT_LIKE_FLAGS(
"comment block before section", "# Based on.*\n.*# GENERAL OPTIONS\n#\n"s, std::regex::extended, options->precedingCommentBlock);
#endif
CPPUNIT_ASSERT_EQUAL(7_st, options->fields.size());
CPPUNIT_ASSERT_EQUAL("HoldPkg"s, options->fields[0].key);
CPPUNIT_ASSERT_EQUAL("pacman glibc"s, options->fields[0].value);
CPPUNIT_ASSERT_MESSAGE("value present", options->fields[0].flags & IniFileFieldFlags::HasValue);
#ifdef STD_REGEX_WORKS
TESTUTILS_ASSERT_LIKE_FLAGS("comment block between section header and first field",
"# The following paths are.*\n.*#HookDir = /etc/pacman\\.d/hooks/\n"s, std::regex::extended, options->fields[0].precedingCommentBlock);
#endif
CPPUNIT_ASSERT_EQUAL(""s, options->fields[0].followingInlineComment);
CPPUNIT_ASSERT_EQUAL("Foo"s, options->fields[1].key);
CPPUNIT_ASSERT_EQUAL("bar"s, options->fields[1].value);
CPPUNIT_ASSERT_MESSAGE("value present", options->fields[1].flags & IniFileFieldFlags::HasValue);
#ifdef STD_REGEX_WORKS
TESTUTILS_ASSERT_LIKE_FLAGS("comment block between fields", "#XferCommand.*\n.*#CleanMethod = KeepInstalled\n"s, std::regex::extended,
options->fields[1].precedingCommentBlock);
#endif
CPPUNIT_ASSERT_EQUAL("# inline comment"s, options->fields[1].followingInlineComment);
CPPUNIT_ASSERT_EQUAL("CheckSpace"s, options->fields[3].key);
CPPUNIT_ASSERT_EQUAL(""s, options->fields[3].value);
CPPUNIT_ASSERT_MESSAGE("no value present", !(options->fields[3].flags & IniFileFieldFlags::HasValue));
#ifdef STD_REGEX_WORKS
TESTUTILS_ASSERT_LIKE_FLAGS("empty lines in comments preserved", "\n# Pacman.*\n.*\n\n#NoUpgrade =\n.*#TotalDownload\n"s, std::regex::extended,
options->fields[3].precedingCommentBlock);
#endif
CPPUNIT_ASSERT_EQUAL(""s, options->fields[3].followingInlineComment);
auto extraScope = ini.findSection(options, "extra");
CPPUNIT_ASSERT(extraScope != ini.sectionEnd());
@ -427,8 +462,10 @@ void IoTests::testAdvancedIniFile()
CPPUNIT_ASSERT_EQUAL_MESSAGE("inline comment after scope", "# an inline comment after a scope name"s, extraScope->followingInlineComment);
CPPUNIT_ASSERT_EQUAL(1_st, extraScope->fields.size());
CPPUNIT_ASSERT(ini.sections.back().flags & IniFileSectionFlags::Implicit);
#ifdef STD_REGEX_WORKS
TESTUTILS_ASSERT_LIKE_FLAGS("comment block after last field present in implicitly added last scope", "\n# If you.*\n.*custompkgs\n"s,
std::regex::extended, ini.sections.back().precedingCommentBlock);
#endif
// test finding a field from file level and const access
const auto *const constIniFile = &ini;
@ -464,23 +501,97 @@ void IoTests::testAdvancedIniFile()
void IoTests::testCopy()
{
// prepare streams
fstream testFile;
auto testFile = std::fstream();
testFile.exceptions(ios_base::failbit | ios_base::badbit);
testFile.open(testFilePath("some_data"), ios_base::in | ios_base::binary);
stringstream outputStream(ios_base::in | ios_base::out | ios_base::binary);
auto outputStream = std::stringstream(ios_base::in | ios_base::out | ios_base::binary);
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
// copy
CopyHelper<13> copyHelper;
auto copyHelper = CopyHelper<13>();
copyHelper.copy(testFile, outputStream, 50);
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(0), outputStream.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellp());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellp());
// test
// verify
testFile.seekg(0);
for (auto i = 0; i < 50; ++i) {
CPPUNIT_ASSERT(testFile.get() == outputStream.get());
CPPUNIT_ASSERT_EQUAL(testFile.get(), outputStream.get());
}
}
/*!
* \brief Tests CopyHelper in combination with NativeFileStream.
*/
void IoTests::testCopyWithNativeFileStream()
{
// prepare streams
auto testFile = NativeFileStream();
testFile.exceptions(ios_base::failbit | ios_base::badbit);
testFile.open(testFilePath("some_data"), ios_base::in | ios_base::binary);
auto outputPath = workingCopyPath("copied_data", WorkingCopyMode::Cleanup);
auto outputStream = NativeFileStream();
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
outputStream.open(outputPath, ios_base::out | ios_base::binary);
// copy
auto copyHelper = CopyHelper<13>();
copyHelper.copy(testFile, outputStream, 50);
CPPUNIT_ASSERT(outputStream.is_open());
CPPUNIT_ASSERT(testFile.is_open());
// test seek positions (the expected values are from a run with normal std::fstream)
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellp());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellp());
// add a few more bytes to output stream (to be sure it is still usable) and re-open for reading
const auto aFewMoreBytes = "a few more bytes"sv;
outputStream << aFewMoreBytes;
outputStream.close();
outputStream.open(outputPath, ios_base::in | ios_base::binary);
// verify
testFile.seekg(0);
for (auto i = 0; i < 50; ++i) {
CPPUNIT_ASSERT_EQUAL(testFile.get(), outputStream.get());
}
auto tail = std::string(aFewMoreBytes.size(), '0');
outputStream.read(tail.data(), static_cast<std::streamsize>(tail.size()));
CPPUNIT_ASSERT_EQUAL(aFewMoreBytes, std::string_view(tail.data(), tail.size()));
// repeat with callback version
auto percentage = 0.0;
const auto isAborted = [] { return false; };
const auto callback = [&percentage](double p) { percentage = p; };
testFile.seekg(0);
outputStream.close();
outputStream.open(outputPath, ios_base::out | ios_base::trunc | ios_base::binary);
copyHelper.callbackCopy(testFile, outputStream, 50, isAborted, callback);
CPPUNIT_ASSERT_EQUAL(1.0, percentage);
// verify again
CPPUNIT_ASSERT(outputStream.is_open());
CPPUNIT_ASSERT(testFile.is_open());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), outputStream.tellp());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellg());
CPPUNIT_ASSERT_EQUAL(static_cast<std::fstream::pos_type>(50), testFile.tellp());
outputStream << aFewMoreBytes;
outputStream.close();
outputStream.open(outputPath, ios_base::in | ios_base::binary);
testFile.seekg(0);
for (auto i = 0; i < 50; ++i) {
CPPUNIT_ASSERT_EQUAL(testFile.get(), outputStream.get());
}
tail.assign(aFewMoreBytes.size(), '0');
outputStream.read(tail.data(), static_cast<std::streamsize>(tail.size()));
CPPUNIT_ASSERT_EQUAL(aFewMoreBytes, std::string_view(tail.data(), tail.size()));
}
/*!
* \brief Tests readFile().
*/
@ -539,16 +650,15 @@ void IoTests::testAnsiEscapeCodes()
ss1 << EscapeCodes::color(EscapeCodes::Color::Blue, EscapeCodes::Color::Red, EscapeCodes::TextAttribute::Blink)
<< "blue, blinking text on red background" << EscapeCodes::TextAttribute::Reset << '\n';
cout << "\noutput for formatting with ANSI escape codes:\n" << ss1.str() << "---------------------------------------------\n";
fstream("/tmp/test.txt", ios_base::out | ios_base::trunc) << ss1.str();
CPPUNIT_ASSERT_EQUAL("\e[1;31mError: \e[0m\e[1msome error\e[0m\n"
"\e[1;33mWarning: \e[0m\e[1msome warning\e[0m\n"
"\e[1;34mInfo: \e[0m\e[1msome info\e[0m\n"
"\e[1;31m==> ERROR: \e[0m\e[1mArch-style error\e[0m\n"
"\e[1;33m==> WARNING: \e[0m\e[1mArch-style warning\e[0m\n"
" \e[0m\e[1mArch-style message\e[0m\n"
"\e[1;32m==> \e[0m\e[1mArch-style success\e[0m\n"
"\e[1;32m -> \e[0m\e[1mArch-style sub-message\e[0m\n"
"\e[5;34;41mblue, blinking text on red background\e[0m\n"s,
CPPUNIT_ASSERT_EQUAL("\033[1;31mError: \033[0m\033[1msome error\033[0m\n"
"\033[1;33mWarning: \033[0m\033[1msome warning\033[0m\n"
"\033[1;34mInfo: \033[0m\033[1msome info\033[0m\n"
"\033[1;31m==> ERROR: \033[0m\033[1mArch-style error\033[0m\n"
"\033[1;33m==> WARNING: \033[0m\033[1mArch-style warning\033[0m\n"
" \033[0m\033[1mArch-style message\033[0m\n"
"\033[1;32m==> \033[0m\033[1mArch-style success\033[0m\n"
"\033[1;32m -> \033[0m\033[1mArch-style sub-message\033[0m\n"
"\033[5;34;41mblue, blinking text on red background\033[0m\n"s,
ss1.str());
stringstream ss2;
@ -563,6 +673,7 @@ void IoTests::testAnsiEscapeCodes()
*/
void IoTests::testNativeFileStream()
{
return;
// open file by path
const auto txtFilePath(workingCopyPath("täst.txt"));
NativeFileStream fileStream;

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
@ -40,7 +53,7 @@ using namespace CppUtilities::EscapeCodes;
namespace CppUtilities {
/// \cond
bool fileSystemItemExists(const string &path)
static bool fileSystemItemExists(const string &path)
{
#ifdef PLATFORM_UNIX
struct stat res;
@ -55,7 +68,7 @@ bool fileSystemItemExists(const string &path)
#endif
}
bool fileExists(const string &path)
static bool fileExists(const string &path)
{
#ifdef PLATFORM_UNIX
struct stat res;
@ -70,7 +83,7 @@ bool fileExists(const string &path)
#endif
}
bool dirExists(const string &path)
static bool dirExists(const string &path)
{
#ifdef PLATFORM_UNIX
struct stat res;
@ -85,7 +98,7 @@ bool dirExists(const string &path)
#endif
}
bool makeDir(const string &path)
static bool makeDir(const string &path)
{
#ifdef PLATFORM_UNIX
return mkdir(path.data(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
@ -174,11 +187,12 @@ TestApplication::TestApplication(int argc, const char *const *argv)
// -> read TEST_FILE_PATH environment variable
bool hasTestFilePathFromEnv;
if (auto testFilePathFromEnv = readTestfilePathFromEnv(); (hasTestFilePathFromEnv = !testFilePathFromEnv.empty())) {
m_testFilesPaths.emplace_back(move(testFilePathFromEnv));
m_testFilesPaths.emplace_back(std::move(testFilePathFromEnv));
}
// -> find source directory
if (auto testFilePathFromSrcDirRef = readTestfilePathFromSrcRef(); !testFilePathFromSrcDirRef.empty()) {
m_testFilesPaths.emplace_back(move(testFilePathFromSrcDirRef));
m_testFilesPaths.insert(m_testFilesPaths.end(), std::make_move_iterator(testFilePathFromSrcDirRef.begin()),
std::make_move_iterator(testFilePathFromSrcDirRef.end()));
}
// -> try testfiles directory in working directory
m_testFilesPaths.emplace_back("./testfiles/");
@ -390,7 +404,16 @@ string TestApplication::workingCopyPathAs(
return workingCopyPath;
}
#ifdef PLATFORM_UNIX
#ifdef CPP_UTILITIES_HAS_EXEC_APP
#if defined(CPP_UTILITIES_BOOST_PROCESS)
inline static std::string streambufToString(boost::asio::streambuf &buf)
{
const auto begin = boost::asio::buffers_begin(buf.data());
return std::string(begin, begin + static_cast<std::ptrdiff_t>(buf.size()));
}
#endif
/*!
* \brief Executes an application with the specified \a args.
* \remarks Provides internal implementation of execApp() and execHelperApp().
@ -410,6 +433,48 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
cout << endl;
}
#if defined(CPP_UTILITIES_BOOST_PROCESS)
auto path = enableSearchPath ? boost::process::search_path(appPath) : boost::process::filesystem::path(appPath);
auto ctx = boost::asio::io_context();
auto group = boost::process::group();
auto argsAsVector =
#if defined(PLATFORM_WINDOWS)
std::vector<std::wstring>();
#else
std::vector<std::string>();
#endif
if (*args) {
for (const char *const *arg = args + 1; *arg; ++arg) {
#if defined(PLATFORM_WINDOWS)
auto ec = std::error_code();
argsAsVector.emplace_back(convertMultiByteToWide(ec, std::string_view(*arg)));
if (ec) {
throw std::runtime_error(argsToString("unable to convert arg \"", *arg, "\" to wide string"));
}
#else
argsAsVector.emplace_back(*arg);
#endif
}
}
auto outputBuffer = boost::asio::streambuf(), errorBuffer = boost::asio::streambuf();
auto env = boost::process::environment(boost::this_process::environment());
if (!newProfilingPath.empty()) {
env["LLVM_PROFILE_FILE"] = newProfilingPath;
}
auto child
= boost::process::child(ctx, group, path, argsAsVector, env, boost::process::std_out > outputBuffer, boost::process::std_err > errorBuffer);
if (timeout > 0) {
ctx.run_for(std::chrono::milliseconds(timeout));
} else {
ctx.run();
}
output = streambufToString(outputBuffer);
errors = streambufToString(errorBuffer);
child.wait();
group.wait();
return child.exit_code();
#elif defined(PLATFORM_UNIX)
// create pipes
int coutPipes[2], cerrPipes[2];
pipe(coutPipes);
@ -477,6 +542,7 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
// get return code
int childReturnCode;
waitpid(child, &childReturnCode, 0);
waitpid(-child, nullptr, 0);
return childReturnCode;
} else {
// child process
@ -488,6 +554,12 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
close(readCerrPipe);
close(writeCerrPipe);
// -> create process group
if (setpgid(0, 0)) {
cerr << Phrases::Error << "Unable create process group: " << std::strerror(errno) << Phrases::EndFlush;
exit(EXIT_FAILURE);
}
// -> modify environment variable LLVM_PROFILE_FILE to apply new path for profiling output
if (!newProfilingPath.empty()) {
setenv("LLVM_PROFILE_FILE", newProfilingPath.data(), true);
@ -496,13 +568,16 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
// -> execute application
if (enableSearchPath) {
execvp(appPath, const_cast<char *const *>(args));
} else {
execv(appPath, const_cast<char *const *>(args));
}
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": execv() failed" << Phrases::EndFlush;
exit(-101);
cerr << Phrases::Error << "Unable to execute \"" << appPath << "\": " << std::strerror(errno) << Phrases::EndFlush;
exit(EXIT_FAILURE);
}
#else
throw std::runtime_error("lauching test applications is not supported on this platform");
#endif
}
/*!
@ -511,7 +586,6 @@ static int execAppInternal(const char *appPath, const char *const *args, std::st
* \throws Throws std::runtime_error when the application can not be executed.
* \remarks
* - The specified \a args must be 0 terminated. The first argument is the application name.
* - Currently only supported under UNIX.
* - \a stdout and \a stderr are cleared before.
*/
int TestApplication::execApp(const char *const *args, string &output, string &errors, bool suppressLogging, int timeout) const
@ -571,7 +645,6 @@ int TestApplication::execApp(const char *const *args, string &output, string &er
* \remarks
* - Intended to invoke helper applications (eg. to setup test files). Use execApp() and TestApplication::execApp() to
* invoke the application to be tested itself.
* - Currently only supported under UNIX.
*/
int execHelperApp(const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
{
@ -586,14 +659,13 @@ int execHelperApp(const char *appPath, const char *const *args, std::string &out
* \remarks
* - Intended to invoke helper applications (eg. to setup test files). Use execApp() and TestApplication::execApp() to
* invoke the application to be tested itself.
* - Currently only supported under UNIX.
*/
int execHelperAppInSearchPath(
const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging, int timeout)
{
return execAppInternal(appName, args, output, errors, suppressLogging, timeout, string(), true);
}
#endif // PLATFORM_UNIX
#endif
/*!
* \brief Reads the path of the test file directory from the environment variable TEST_FILE_PATH.
@ -612,10 +684,11 @@ string TestApplication::readTestfilePathFromEnv()
* \remarks That file is supposed to contain the path the the source directory. It is supposed to be stored by the build system in the
* same directory as the test executable. The CMake modules contained of these utilities ensure that's the case.
*/
string TestApplication::readTestfilePathFromSrcRef()
std::vector<std::string> TestApplication::readTestfilePathFromSrcRef()
{
// find the path of the current executable on platforms supporting "/proc/self/exe"; otherwise assume the current working directory
// is the executable path
auto res = std::vector<std::string>();
auto binaryPath = std::string();
#if defined(CPP_UTILITIES_USE_STANDARD_FILESYSTEM) && defined(PLATFORM_UNIX)
try {
@ -628,25 +701,29 @@ string TestApplication::readTestfilePathFromSrcRef()
const auto srcdirrefPath = binaryPath + "srcdirref";
try {
// read "srcdirref" file which should contain the path of the source directory
auto srcDirContent(readFile(srcdirrefPath, 2 * 1024));
const auto srcDirContent = readFile(srcdirrefPath, 2 * 1024);
if (srcDirContent.empty()) {
cerr << Phrases::Warning << "The file \"srcdirref\" is empty." << Phrases::EndFlush;
return string();
return res;
}
srcDirContent += "/testfiles/";
// check whether the referenced source directory contains a "testfiles" directory
if (!dirExists(srcDirContent)) {
cerr << Phrases::Warning
<< "The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
<< Phrases::End << "Referenced source directory: " << srcDirContent << endl;
return string();
// check whether the referenced source directories contain a "testfiles" directory
const auto srcPaths = splitStringSimple<std::vector<std::string_view>>(srcDirContent, "\n");
for (const auto &srcPath : srcPaths) {
auto testfilesPath = argsToString(srcPath, "/testfiles/");
if (dirExists(testfilesPath)) {
res.emplace_back(std::move(testfilesPath));
} else {
cerr << Phrases::Warning
<< "The source directory referenced by the file \"srcdirref\" does not contain a \"testfiles\" directory or does not exist."
<< Phrases::End << "Referenced source directory: " << testfilesPath << endl;
}
}
return srcDirContent;
return res;
} catch (const std::ios_base::failure &e) {
cerr << Phrases::Warning << "The file \"" << srcdirrefPath << "\" can not be opened: " << e.what() << Phrases::EndFlush;
}
return string();
return res;
}
} // namespace CppUtilities

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
@ -52,7 +63,7 @@ public:
private:
static std::string readTestfilePathFromEnv();
static std::string readTestfilePathFromSrcRef();
static std::vector<std::string> readTestfilePathFromSrcRef();
ArgumentParser m_parser;
OperationArgument m_listArg;
@ -180,7 +191,7 @@ inline CPP_UTILITIES_EXPORT std::string workingCopyPathAs(
return TestApplication::instance()->workingCopyPathAs(relativeTestFilePath, relativeWorkingCopyPath, mode);
}
#ifdef PLATFORM_UNIX
#ifdef CPP_UTILITIES_HAS_EXEC_APP
/*!
* \brief Convenience function which executes the application to be tested with the specified \a args.
* \remarks A TestApplication must be present.
@ -195,7 +206,7 @@ CPP_UTILITIES_EXPORT int execHelperApp(
const char *appPath, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
CPP_UTILITIES_EXPORT int execHelperAppInSearchPath(
const char *appName, const char *const *args, std::string &output, std::string &errors, bool suppressLogging = false, int timeout = -1);
#endif // PLATFORM_UNIX
#endif
/*!
* \brief Allows printing std::optional objects so those can be asserted using CPPUNIT_ASSERT_EQUAL.
@ -277,13 +288,38 @@ template <typename T, Traits::DisableIf<std::is_integral<T>> * = nullptr> const
*
* \remarks Requires cppunit.
*/
#define TESTUTILS_ASSERT_EXEC(args) \
#define TESTUTILS_ASSERT_EXEC(args) TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, 0)
/*!
* \brief Asserts the execution of the application with the specified CLI \a args and the specified \a expectedExitStatus.
*
* The application is executed via TestApplication::execApp(). Output is stored in the std::string variables stdout
* and stderr.
*
* \remarks Requires cppunit.
*/
#ifdef CPP_UTILITIES_BOOST_PROCESS
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto returnCode = execApp(args, stdout, stderr); \
if (returnCode != 0) { \
CPPUNIT_FAIL(::CppUtilities::argsToString("app failed with return code ", returnCode, "\nstdout: ", stdout, "\nstderr: ", stderr)); \
const auto status = execApp(args, stdout, stderr); \
if (status != expectedExitStatus) { \
CPPUNIT_FAIL(::CppUtilities::argsToString( \
"app exited with status ", status, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#else
#define TESTUTILS_ASSERT_EXEC_EXIT_STATUS(args, expectedExitStatus) \
{ \
const auto status = execApp(args, stdout, stderr); \
if (!WIFEXITED(status)) { \
CPPUNIT_FAIL(::CppUtilities::argsToString("app did not terminate normally\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
if (const auto exitStatus = WEXITSTATUS(status); exitStatus != expectedExitStatus) { \
CPPUNIT_FAIL(::CppUtilities::argsToString( \
"app exited with status ", exitStatus, " (expected ", expectedExitStatus, ")\nstdout: ", stdout, "\nstderr: ", stderr)); \
} \
}
#endif
/*!
* \brief Asserts whether the specified \a string matches the specified \a regex.