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
82 changes: 82 additions & 0 deletions .github/workflows/bazel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Bazel and C++20 modules

on:
push:
branches: [master]
pull_request:
branches: [master]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
bazel:
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
bazel: [8.7.0, 9.2.0]
env:
USE_BAZEL_VERSION: ${{ matrix.bazel }}
# rules_cc looks for clang-scan-deps beside the selected compiler.
CC: /usr/lib/llvm-18/bin/clang
CXX: /usr/lib/llvm-18/bin/clang++
steps:
- uses: actions/checkout@v5
- uses: bazel-contrib/setup-bazel@0.19.0
- name: Install Clang module tools
if: matrix.bazel == '9.2.0'
run: |
sudo apt-get update
sudo apt-get install -y clang-18 clang-tools-18
"$CC" --version
"${CC%/*}/clang-scan-deps" --version
- name: Load all targets without enabling modules
run: bazel query //... --lockfile_mode=off
- name: Test the C++17 header targets
run: >-
bazel test //test/bazel:header_logging_test //test/bazel:fmtquill_test
--cxxopt=-std=c++17 --lockfile_mode=off --test_output=errors
- name: Check that modules require an explicit opt-in
run: |
if bazel build //:quill_module --lockfile_mode=off > module-disabled.log 2>&1; then
cat module-disabled.log
exit 1
fi
cat module-disabled.log
if [[ "$USE_BAZEL_VERSION" == 9.* ]]; then
grep -q 'requires --experimental_cpp_modules' module-disabled.log
else
grep -q 'requires Bazel 9 or newer' module-disabled.log
fi
- name: Test optimized C++20 module imports
if: matrix.bazel == '9.2.0'
run: >-
bazel test //test/bazel:module_logging_test --compilation_mode=opt
--cxxopt=-std=c++20 --cxxopt=-Werror --experimental_cpp_modules
--lockfile_mode=off --test_output=errors

cmake-module:
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
no_exceptions: ['OFF', 'ON']
steps:
- uses: actions/checkout@v5
- name: Configure the module examples
run: >-
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
-DCMAKE_CXX_STANDARD=20 -DQUILL_BUILD_MODULE=ON
-DQUILL_BUILD_TESTS=ON -DQUILL_BUILD_EXAMPLES=ON
-DQUILL_NO_EXCEPTIONS=${{ matrix.no_exceptions }}
- name: Build the module examples
run: >-
cmake --build build --target quill_docs_module
quill_example_console_logging_module --parallel 2
- name: Test logging and automatic shutdown
run: ctest --test-dir build -R '^quill_module_(logging|shutdown)_test$' --output-on-failure
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
/docs/Doxyfile.rtd
/docs/build/
/docs/_build*/
/bazel-*
/MODULE.bazel.lock
19 changes: 19 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load(":quill_module.bzl", "quill_module")

package(default_visibility = ["//visibility:public"])

Expand Down Expand Up @@ -28,3 +29,21 @@ cc_library(
hdrs = glob(["include/quill/bundled/fmt/*.h"]),
includes = ["include"],
)

quill_module(
name = "quill_module",
copts = select({
"@rules_cc//cc/compiler:gcc": ["-Wno-gnu-zero-variadic-macro-arguments"],
"@rules_cc//cc/compiler:clang": ["-Wno-gnu-zero-variadic-macro-arguments"],
"//conditions:default": [],
}),
# rules_cc 0.2.22 uses the same module metadata path for PIC and non-PIC outputs.
features = [
"cpp_modules",
"prefer_pic_for_opt_binaries",
],
module_interfaces = ["src/quill.cc"],
# Prevent users without --experimental_cpp_modules support from accidentally building this
tags = ["manual"],
deps = [":quill"],
)
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@

## v13.1.0

- Added the experimental `quill_module` target for Bazel 9+ consumers using C++20 and
`--experimental_cpp_modules`, while preserving ordinary header-only builds on older Bazel versions.
- Fixed C++20 module imports using separate logger state across translation units, which could
prevent logger lookup and crash automatic backend shutdown.
- CMake, Meson, and Bazel consumers can use a separate `fmtquill` dependency for the bundled
formatter without inheriting Quill's logging compile options, definitions, or link dependencies.
- Fixed `FileSink::open_file()` retaining the old filename when a subclass reopens a different path,
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,13 @@ if (QUILL_BUILD_MODULE)
target_link_libraries(quill_module PUBLIC ${TARGET_NAME})
target_compile_features(quill_module PUBLIC cxx_std_20)
set_target_properties(quill_module PROPERTIES CXX_SCAN_FOR_MODULES ON)

if (QUILL_NO_EXCEPTIONS)
# The module and its importers must agree on exception and RTTI support.
target_compile_options(quill_module PUBLIC
$<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>:-fno-exceptions;-fno-rtti>
$<$<CXX_COMPILER_ID:MSVC>:/GR-;/EHs-c-;/D_HAS_EXCEPTIONS=0>)
endif ()
endif ()

if (QUILL_BUILD_EXAMPLES)
Expand Down
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ module(

bazel_dep(name = "platforms", version = "1.1.0")
bazel_dep(name = "rules_cc", version = "0.2.22")
bazel_dep(name = "bazel_features", version = "1.50.0")
47 changes: 47 additions & 0 deletions docs/installing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,53 @@ Include ``quill/bundled/fmt/format.h`` and use ``fmtquill::format`` or specializ
``fmtquill::formatter``. These header-only dependencies use Quill's bundled, patched fmt in the
``fmtquill`` namespace; its version follows the Quill release.

Using the C++20 Module
----------------------

Quill provides an experimental C++20 named module interface (``src/quill.cc``).
It is opt-in; normal header-only use continues to require only C++17.

With CMake 3.28 or newer and a compiler and generator supporting C++20 modules, set
``CMAKE_CXX_STANDARD=20`` and ``QUILL_BUILD_MODULE=ON`` when adding Quill through
``add_subdirectory`` or ``FetchContent``. Link against ``quill::quill_module``:

.. code:: cmake

target_link_libraries(my_project PRIVATE quill::quill_module)

The module target is currently available from the source build, not from an installed
``find_package(quill)`` package.

With Bazel 9 or newer, the declared ``rules_cc`` 0.2.22 dependency, and a toolchain supporting
C++20 modules, add ``@quill//:quill_module`` to the target's ``deps``. Enable C++20 for the
whole build and pass ``--experimental_cpp_modules`` (for Clang, use
``--cxxopt=-std=c++20 --experimental_cpp_modules``):

.. code:: python

cc_binary(
name = "my_project",
srcs = ["main.cpp"],
features = ["cpp_modules", "prefer_pic_for_opt_binaries"],
deps = ["@quill//:quill_module"],
)

The selected Clang toolchain must also provide a matching ``clang-scan-deps`` beside the
compiler executable. On Ubuntu 24.04, install ``clang-18`` and ``clang-tools-18``, and set
``CC=/usr/lib/llvm-18/bin/clang`` and ``CXX=/usr/lib/llvm-18/bin/clang++``.

The module target is excluded from wildcard builds. Older Bazel versions can still use
``@quill//:quill`` and ``@quill//:fmtquill`` without enabling modules.

``prefer_pic_for_opt_binaries`` keeps module producers and consumers in PIC mode, avoiding
conflicting module metadata outputs in optimized builds with ``rules_cc`` 0.2.22.

Import Quill and define ``QUILL_USE_MODULE`` before including ``quill/LogMacros.h``.
Modules do not export macros, so this definition is required for the logging macros:

.. literalinclude:: snippets/quill_docs_module.cpp
:language: cpp

Next Steps
----------

Expand Down
14 changes: 14 additions & 0 deletions docs/snippets/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,19 @@ foreach (example_target ${EXAMPLE_TARGETS})
target_link_libraries(${example_target} quill)
endforeach ()

if (TARGET quill_module)
add_executable(quill_docs_module quill_docs_module.cpp)
set_common_compile_options(quill_docs_module)
target_link_libraries(quill_docs_module PRIVATE quill::quill_module)
set_target_properties(quill_docs_module PROPERTIES CXX_SCAN_FOR_MODULES ON)
list(APPEND EXAMPLE_TARGETS quill_docs_module)

if (QUILL_BUILD_TESTS)
add_test(NAME quill_module_logging_test COMMAND quill_docs_module)
set_tests_properties(quill_module_logging_test PROPERTIES
PASS_REGULAR_EXPRESSION "Logging through the Quill module: 42")
endif ()
endif ()

install(TARGETS ${EXAMPLE_TARGETS}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
22 changes: 22 additions & 0 deletions docs/snippets/quill_docs_module.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// GCC/libstdc++ needs these declarations before the module import.
#include <new>
#include <typeinfo>
#include <utility>

import quill;

#define QUILL_USE_MODULE
#include "quill/LogMacros.h"

int main()
{
quill::Backend::start();

auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("module_docs_sink");
quill::Logger* logger =
quill::Frontend::create_or_get_logger("module_docs_logger", std::move(console_sink));

LOG_INFO(logger, "Logging through the Quill module: {}", 42);
logger->flush_log();
quill::Backend::stop();
}
6 changes: 6 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ if (TARGET quill_module)
target_link_libraries(quill_example_console_logging_module PRIVATE quill_module)

list(APPEND EXAMPLE_TARGETS quill_example_console_logging_module)

if (QUILL_BUILD_TESTS)
add_test(NAME quill_module_shutdown_test COMMAND quill_example_console_logging_module)
set_tests_properties(quill_module_shutdown_test PROPERTIES
PASS_REGULAR_EXPRESSION "StringRef logging via module import")
endif ()
endif ()

install(TARGETS ${EXAMPLE_TARGETS}
Expand Down
2 changes: 1 addition & 1 deletion include/quill/backend/BackendManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class BackendManager
{
public:
/***/
QUILL_EXPORT static BackendManager& instance() noexcept
QUILL_EXPORT static inline BackendManager& instance() noexcept
{
static BackendManager instance;
return instance;
Expand Down
2 changes: 1 addition & 1 deletion include/quill/backend/RdtscClock.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class RdtscClock
class RdtscTicks
{
public:
QUILL_NODISCARD QUILL_EXPORT static RdtscTicks& instance()
QUILL_NODISCARD QUILL_EXPORT static inline RdtscTicks& instance()
{
static RdtscTicks inst;
return inst;
Expand Down
2 changes: 1 addition & 1 deletion include/quill/backend/SignalHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class SignalHandlerContext
SignalHandlerContext& operator=(SignalHandlerContext const&) = delete;

/***/
QUILL_EXPORT static SignalHandlerContext& instance() noexcept
QUILL_EXPORT static inline SignalHandlerContext& instance() noexcept
{
static SignalHandlerContext instance;
return instance;
Expand Down
2 changes: 1 addition & 1 deletion include/quill/core/LoggerBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class LoggerBase
}

// The exported accessor keeps its thread-local state shared with DLL consumers.
QUILL_EXPORT static bool& _backend_thread_flag() noexcept
QUILL_EXPORT static inline bool& _backend_thread_flag() noexcept
{
static QUILL_THREAD_LOCAL bool is_backend_thread{false};
return is_backend_thread;
Expand Down
3 changes: 2 additions & 1 deletion include/quill/core/LoggerManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class LoggerManager
LoggerManager& operator=(LoggerManager const&) = delete;

/***/
QUILL_EXPORT static LoggerManager& instance() noexcept
// Named modules do not give in-class definitions implicit inline linkage.
QUILL_EXPORT static inline LoggerManager& instance() noexcept
{
static LoggerManager instance;
return instance;
Expand Down
2 changes: 1 addition & 1 deletion include/quill/core/MetricManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class MetricManager
MetricManager& operator=(MetricManager const&) = delete;

/***/
QUILL_EXPORT static MetricManager& instance() noexcept
QUILL_EXPORT static inline MetricManager& instance() noexcept
{
static MetricManager instance;
return instance;
Expand Down
2 changes: 1 addition & 1 deletion include/quill/core/SinkManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class SinkManager
SinkManager& operator=(SinkManager const&) = delete;

/***/
QUILL_EXPORT static SinkManager& instance() noexcept
QUILL_EXPORT static inline SinkManager& instance() noexcept
{
static SinkManager instance;
return instance;
Expand Down
2 changes: 1 addition & 1 deletion include/quill/core/ThreadContextManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ class ThreadContextManager
{
public:
/***/
QUILL_EXPORT static ThreadContextManager& instance() noexcept
QUILL_EXPORT static inline ThreadContextManager& instance() noexcept
{
static ThreadContextManager instance;
return instance;
Expand Down
25 changes: 25 additions & 0 deletions quill_module.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Keep the experimental module optional on older Bazel versions."""

load("@bazel_features//:features.bzl", "bazel_features")
load("@rules_cc//cc:cc_library.bzl", "cc_library")

def _unsupported_module_impl(ctx):
fail("{} requires Bazel 9 or newer for C++20 modules; use //:quill for header-only logging.".format(ctx.label))

_unsupported_module = rule(implementation = _unsupported_module_impl)

def quill_module(name, **kwargs):
"""Declare the module without passing unsupported attributes to older Bazel.

Args:
name: Name of the module target.
**kwargs: Attributes forwarded to cc_library when modules are supported.
"""

# Bazel 7 rejects module_interfaces; Bazel 8 accepts it but does not compile it.
# Modules require the Starlark C++ implementation used by rules_cc on Bazel 9+.
if not bazel_features.cc.cc_common_is_in_rules_cc:
_unsupported_module(name = name, tags = kwargs.get("tags", []))
return

cc_library(name = name, **kwargs)
Loading
Loading