Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ jobs:
exe_path: build/bin/Release/ASWLogTests.exe
# cl takes -MP as well as /MP; Git Bash would turn "/MP" into a path when it starts CMake.
cxxflags: -MP
# A multi-config generator, so the package check builds and installs both into the same prefix (the Debug
# library is ASWLogd)
package_configs: Debug Release
sanitize: ""
- name: Windows (MinGW)
os: windows-latest
Expand Down Expand Up @@ -166,3 +169,36 @@ jobs:
name: test-results-${{ matrix.name }}
path: junit-results.xml
if-no-files-found: error

- name: Check the installed CMake package
# Builds and installs the library (the root project) to a temporary prefix, then builds tests/package against
# it with find_package() and runs it, which checks the install rules and the package files. Not in the
# sanitizer jobs: they build the same CMake files, and the package would pass their flags on to the consumer.
if: ${{ !matrix.sanitize }}
shell: bash
run: |
if [ -n "${{ matrix.cc }}" ]; then
export CC="${{ matrix.cc }}"
export CXX="${{ matrix.cxx }}"
fi
if [ -n "${{ matrix.cxxflags }}" ]; then
export CXXFLAGS="${{ matrix.cxxflags }}"
fi
generatorArgs=()
if [ -n "${{ matrix.generator }}" ]; then
generatorArgs=(-G "${{ matrix.generator }}")
fi
# Forward slashes, which CMake takes on Windows too (RUNNER_TEMP has backslashes there)
prefix="${RUNNER_TEMP//\\//}/aswlog"
configs="${{ matrix.package_configs || 'Release' }}"
cmake -S . -B build-install "${generatorArgs[@]}" -DCMAKE_BUILD_TYPE=Release
for config in $configs; do
cmake --build build-install --config "$config"
cmake --install build-install --config "$config" --prefix "$prefix"
done
cmake -S tests/package -B build-package "${generatorArgs[@]}" -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$prefix"
for config in $configs; do
cmake --build build-package --config "$config"
ctest --test-dir build-package -C "$config" --output-on-failure
done
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ These instructions apply to the entire repository unless a more specific `AGENTS
ASWLog is a thread-safe light-weight C++ logging tool for Windows and Linux projects.

- `ASWLog/` contains the framework implementation.
- `tests/` contains the unit tests: test modules (`Test_ASWLog_<Unit>`, the tests of `ASWLog_<Unit>`) and shared test helpers (`UT_Helper_<Topic>`, e.g. `UT_Helper_DateTime`) in `tests/tests/`, their CMake project in `tests/cmake/`, their RAD Studio console project in `tests/rad370/`, and their RAD Studio VCL GUI runner project in `tests/vcl/gui/rad370/` (built from the submodule's `vcl/gui/src`). `tests/compile_checks/` holds "must not compile" checks, which `tests/cmake/CMakeLists.txt` runs with `try_compile` when configuring (a failed check stops the configure).
- `tests/` contains the unit tests: test modules (`Test_ASWLog_<Unit>`, the tests of `ASWLog_<Unit>`) and shared test helpers (`UT_Helper_<Topic>`, e.g. `UT_Helper_DateTime`) in `tests/tests/`, their CMake project in `tests/cmake/`, their RAD Studio console project in `tests/rad370/`, and their RAD Studio VCL GUI runner project in `tests/vcl/gui/rad370/` (built from the submodule's `vcl/gui/src`). `tests/compile_checks/` holds "must not compile" checks, which `tests/cmake/CMakeLists.txt` runs with `try_compile` when configuring (a failed check stops the configure). `tests/package/` is a project that builds against an installed ASWLog with `find_package` (CI installs the library and runs it).
- `CMakeLists.txt` (the root) defines the `ASWLog::ASWLog` library target and its install rules; `cmake/ASWLogConfig.cmake.in` is the template of the installed package file. The project version is read from `ASWLog/ASWLog_Version.h`.
- `third_party/asw-unit-tests/` is the ASWUnitTests framework, a git submodule pinned to a release tag. Never edit files in it; test modules self-register with `ASW_REGISTER_TEST_GROUP`, so adding a test only touches `tests/` and its build files.
- `.github/workflows/ci.yml` builds and runs the unit tests on Windows (MSVC, MinGW) and Linux (GCC, Clang), plus Linux Clang builds with ThreadSanitizer and with AddressSanitizer + UBSan (`ASWLOG_SANITIZE`).
- `.github/workflows/ci.yml` builds and runs the unit tests on Windows (MSVC, MinGW) and Linux (GCC, Clang), plus Linux Clang builds with ThreadSanitizer and with AddressSanitizer + UBSan (`ASWLOG_SANITIZE`); the non-sanitizer jobs also check the installed CMake package (`tests/package/`).
- `example/` contains example app that uses the logger.
- `example/rad370/` contains the RAD Studio 13.1 project and Windows build scripts.
- `README.md` contains repo details.
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,16 @@ see [0.26.1](#0261---2026-09-17) for the initial versioned baseline.
Windows and UTF-32 on Linux); invalid input becomes U+FFFD. `std::format`
has no `char16_t` format strings, so `char16_t` text goes in as a message
or through `Wide()`.
- CMake install and package files: `cmake --install` on the repository root
installs the static library, the headers (to `include/ASWLog`, so code
includes them as before, e.g. `#include "ASWLog_FileLog.h"`) and
`ASWLogConfig.cmake`, after which `find_package(ASWLog CONFIG)` gives the
`ASWLog::ASWLog` target. A requested version is met by any later release
with the same major version. The install rules are on by default only when
ASWLog is the top-level project (option `ASWLOG_INSTALL`). A Debug build of
the library is now named `ASWLogd` (e.g. `ASWLogd.lib`), so Debug and
Release can share an install prefix; nothing changes for code that links to
the `ASWLog::ASWLog` target.

### Changed

Expand Down
69 changes: 66 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
cmake_minimum_required(VERSION 3.20)

project(ASWLog LANGUAGES CXX)
# The version lives only in ASWLog/ASWLog_Version.h, so the project version comes from its ASWLOG_VERSION_MAJOR,
# _MINOR, and _PATCH macros (a CMake version has no pre-release part).
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/ASWLog/ASWLog_Version.h" ASWLOG_VERSION_DEFINES
REGEX "^#define ASWLOG_VERSION_(MAJOR|MINOR|PATCH) ")
foreach(versionPart MAJOR MINOR PATCH)
if(NOT ASWLOG_VERSION_DEFINES MATCHES "#define ASWLOG_VERSION_${versionPart} ([0-9]+)")
message(FATAL_ERROR "ASWLog/ASWLog_Version.h doesn't define ASWLOG_VERSION_${versionPart} as a number")
endif()
set(ASWLOG_VERSION_${versionPart} "${CMAKE_MATCH_1}")
endforeach()

project(ASWLog
VERSION ${ASWLOG_VERSION_MAJOR}.${ASWLOG_VERSION_MINOR}.${ASWLOG_VERSION_PATCH}
LANGUAGES CXX
)

# Building ASWLog itself (cmake -S .) rather than consuming it through add_subdirectory() or FetchContent.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
Expand All @@ -11,6 +25,8 @@ endif()

option(ASWLOG_BUILD_EXAMPLE "Build the ASWLog example (example/cmake)" OFF)
option(ASWLOG_BUILD_TESTS "Build the ASWLog unit tests (tests/cmake; needs the asw-unit-tests submodule)" OFF)
option(ASWLOG_INSTALL "Generate the install rules for the ASWLog library, headers, and CMake package files"
${ASWLOG_IS_TOP_LEVEL})
option(ASWLOG_WARNINGS "Compile the ASWLog sources with extra warnings" ${ASWLOG_IS_TOP_LEVEL})

set(ASWLOG_SANITIZE "" CACHE STRING
Expand Down Expand Up @@ -64,9 +80,14 @@ add_library(ASWLog STATIC
add_library(ASWLog::ASWLog ALIAS ASWLog)

target_compile_features(ASWLog PUBLIC cxx_std_20)
set_target_properties(ASWLog PROPERTIES CXX_EXTENSIONS OFF)
# A Debug build is named ASWLogd, so Debug and Release can be installed to the same prefix.
set_target_properties(ASWLog PROPERTIES
CXX_EXTENSIONS OFF
DEBUG_POSTFIX d
)

target_include_directories(ASWLog PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/ASWLog")
# The installed target's include folder comes from install(TARGETS) below.
target_include_directories(ASWLog PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ASWLog>")

if(WIN32)
# ASWLog_Utils.cpp uses the process memory APIs from psapi, and ASWLog_WindowsEventLog.cpp the Event Log and
Expand Down Expand Up @@ -108,6 +129,48 @@ if(ASWLOG_SANITIZE)
target_link_options(ASWLog PUBLIC ${ASWLOG_SANITIZE_FLAGS})
endif()

# After `cmake --install`, find_package(ASWLog CONFIG) gives the ASWLog::ASWLog target. The headers are installed to
# <prefix>/include/ASWLog, the installed target's include folder, so code includes them the same way as from the
# sources (e.g. #include "ASWLog_FileLog.h").
if(ASWLOG_INSTALL)
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)

set(ASWLOG_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/ASWLog")

install(TARGETS ASWLog
EXPORT ASWLogTargets
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ASWLog"
)

# Every header is public.
get_target_property(ASWLOG_HEADERS ASWLog SOURCES)
list(FILTER ASWLOG_HEADERS INCLUDE REGEX "\\.h$")
install(FILES ${ASWLOG_HEADERS} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ASWLog")

install(EXPORT ASWLogTargets
NAMESPACE ASWLog::
DESTINATION "${ASWLOG_INSTALL_CMAKEDIR}"
)

configure_package_config_file(cmake/ASWLogConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/ASWLogConfig.cmake"
INSTALL_DESTINATION "${ASWLOG_INSTALL_CMAKEDIR}"
)

# As in Semantic Versioning, find_package(ASWLog 1.2) accepts version 1.2.0 or any later 1.x, but not 2.0.
write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/ASWLogConfigVersion.cmake"
VERSION "${ASWLog_VERSION}"
COMPATIBILITY SameMajorVersion
)

install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/ASWLogConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/ASWLogConfigVersion.cmake"
DESTINATION "${ASWLOG_INSTALL_CMAKEDIR}"
)
endif()

if(ASWLOG_BUILD_EXAMPLE)
add_subdirectory(example/cmake example)
endif()
Expand Down
3 changes: 3 additions & 0 deletions Deploy.bat
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ if errorlevel 1 exit /b 1
call :CopyFile "README.md" "%TARGET_DIR%\README.md"
if errorlevel 1 exit /b 1

call :CopyFile "cmake\ASWLogConfig.cmake.in" "%TARGET_DIR%\cmake\ASWLogConfig.cmake.in"
if errorlevel 1 exit /b 1

call :CopyFile "ASWLog\ASWLog_Base.cpp" "%TARGET_DIR%\ASWLog\ASWLog_Base.cpp"
if errorlevel 1 exit /b 1
call :CopyFile "ASWLog\ASWLog_Base.h" "%TARGET_DIR%\ASWLog\ASWLog_Base.h"
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ Either way, only the library is built. When ASWLog is the top-level project, `-D
`-DASWLOG_BUILD_TESTS=ON` also build the example and the unit tests, and `ASWLOG_WARNINGS` (on by default only there)
compiles the library with extra warnings.

To use a prebuilt copy instead, build and install the library from the repository root (a Debug build is named
`ASWLogd`, so Debug and Release can share one install prefix):

```
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
cmake --install build --config Release --prefix C:/Libraries/ASWLog
```

and find it with `find_package` (pass the prefix in `CMAKE_PREFIX_PATH`):

```
find_package(ASWLog 1.0 CONFIG REQUIRED)
target_link_libraries(MyApp PRIVATE ASWLog::ASWLog)
```

The headers are installed to `<prefix>/include/ASWLog`, the target's include folder, so code includes them as it does
from the sources (`#include "ASWLog_FileLog.h"`). A requested version is met by any later release with the same major
version. The install rules are on by default only when ASWLog is the top-level project; set `ASWLOG_INSTALL` to change
that. `tests/package` is a small project that builds against an installed copy; CI runs it.

The `example/cmake` folder contains a portable CMake project for building the example with CMake, JetBrains CLion,
Visual Studio, Clang, or MinGW. From the repository root, configure and build it with:

Expand Down
14 changes: 14 additions & 0 deletions cmake/ASWLogConfig.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# The CMake package file of an installed ASWLog (see the root CMakeLists.txt). After
# find_package(ASWLog CONFIG REQUIRED)
# link to the ASWLog::ASWLog target.

@PACKAGE_INIT@

include(CMakeFindDependencyMacro)

# ASWLog::ASWLog links to Threads::Threads (TASWTextLogBase runs a std::thread).
find_dependency(Threads)

include("${CMAKE_CURRENT_LIST_DIR}/ASWLogTargets.cmake")

check_required_components(ASWLog)
31 changes: 31 additions & 0 deletions tests/package/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.20)

project(ASWLogPackageCheck LANGUAGES CXX)

# Checks the installed CMake package: builds Check_Package.cpp against an ASWLog installed with `cmake --install`
# (point CMAKE_PREFIX_PATH at the install prefix), and `ctest` runs it. See the README's CMake section.
find_package(ASWLog CONFIG REQUIRED)

add_executable(ASWLogPackageCheck
"${CMAKE_CURRENT_SOURCE_DIR}/Check_Package.cpp"
)

target_link_libraries(ASWLogPackageCheck PRIVATE ASWLog::ASWLog)

# The version the package's version file reported, which the check compares with the installed headers' version
target_compile_definitions(ASWLogPackageCheck PRIVATE "ASWLOG_PACKAGE_VERSION=\"${ASWLog_VERSION}\"")

if(MSVC)
target_compile_options(ASWLogPackageCheck PRIVATE /W4)
else()
target_compile_options(ASWLogPackageCheck PRIVATE -Wall -Wextra -Wpedantic)
endif()

if(MINGW)
# Statically link the MinGW runtime so the executable launches without MinGW's bin directory on PATH (e.g. on a
# CI runner).
target_link_options(ASWLogPackageCheck PRIVATE -static-libgcc -static-libstdc++ -static)
endif()

enable_testing()
add_test(NAME ASWLogPackageCheck COMMAND ASWLogPackageCheck)
70 changes: 70 additions & 0 deletions tests/package/Check_Package.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* **************************************************************************
Check_Package.cpp
Author: Anthony S. West - ASW Software

Check of the installed CMake package: tests/package/CMakeLists.txt builds this file against an ASWLog installed with
`cmake --install` and found with find_package(), and running it returns 0 when the package works.

Copyright 2026 ASW Software

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

************************************************************************** */

#include <format>
#include <iostream>
#include <string>
#include <string_view>
//---------------------------------------------------------------------------
#include "ASWLog_MemoryLog.h"
#include "ASWLog_Version.h"
//---------------------------------------------------------------------------

#if !defined(ASWLOG_PACKAGE_VERSION)
#error Define ASWLOG_PACKAGE_VERSION
#endif

int main()
{
// The version find_package() found (from ASWLogConfigVersion.cmake) must be the installed headers' version
const std::string headerVersion = std::format("{}.{}.{}", ASWLog::VersionMajor, ASWLog::VersionMinor,
ASWLog::VersionPatch);
if (headerVersion != ASWLOG_PACKAGE_VERSION)
{
std::cerr << std::format("The package version {} isn't the headers' version {}\n", ASWLOG_PACKAGE_VERSION,
headerVersion);
return 1;
}

// The default configuration writes the startup lines, which use the system libraries the installed target links to
// (e.g. the memory usage)
constexpr std::string_view message = "Logged through the installed ASWLog package";
ASWLog::TASWMemoryLog log;
if (!log.Initialize(ASWLog::TASWLogConfig()))
{
std::cerr << "TASWMemoryLog::Initialize() failed\n";
return 1;
}

log.LogInfo(message);
const auto lines = log.GetLines();
if (lines.size() < 2 || !lines.back().ends_with(message))
{
std::cerr << "The memory logger should have kept the startup lines and the message\n";
return 1;
}

std::cout << "ASWLog " << ASWLog::Version << " package works: " << lines.size() << " lines logged\n";

return 0;
}
Loading