# Outcome cmake
# (C) 2016-2025 Niall Douglas
# File Created: June 2016
#
#
# 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 in the accompanying file
# Licence.txt or at
#
# http://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.
#
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file Licence.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
set(OUTCOME_DEPENDENCY_QUICKCPPLIB_GIT_TAG "master" CACHE STRING "Which git tag to use for the QuickCppLib dependency")
if(NOT OUTCOME_DEPENDENCY_QUICKCPPLIB_GIT_TAG STREQUAL "master")
# Don't reuse the bootstrapped git clone as the dependency itself
if(CMAKE_BINARY_DIR)
set(CTEST_QUICKCPPLIB_CLONE_DIR "${CMAKE_BINARY_DIR}/quickcpplib-bootstrap")
else()
set(CTEST_QUICKCPPLIB_CLONE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/quickcpplib-bootstrap")
endif()
endif()
set(OUTCOME_DEPENDENCY_STATUS_CODE_GIT_TAG "master" CACHE STRING "Which git tag to use for the status-code dependency")
option(OUTCOME_BUNDLE_EMBEDDED_STATUS_CODE "Whether to bundle an embedded copy of SG14 status-code with Outcome. Used by various package managers such as vcpkg." ON)
include(cmake/QuickCppLibBootstrap.cmake)
include(QuickCppLibRequireOutOfSourceBuild)
include(QuickCppLibUtils)
include(QuickCppLibPolicies)
# Parse the version we tell cmake directly from the version header file
ParseProjectVersionFromHpp("${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/detail/version.hpp" VERSIONSTRING)
# Sets the usual PROJECT_NAME etc
project(outcome VERSION ${VERSIONSTRING} LANGUAGES C CXX)
# Also set a *cmake* namespace for this project
set(PROJECT_NAMESPACE)
# Setup this cmake environment for this project
include(QuickCppLibSetupProject)
option(OUTCOME_BUNDLE_EMBEDDED_QUICKCPPLIB "Whether to bundle an embedded copy of QuickCppLib with Outcome. Used by various package managers such as vcpkg." OFF)
option(OUTCOME_ENABLE_DEPENDENCY_SMOKE_TEST "Whether to build executables which are smoke tests that Outcome is fully working. Used by various package managers such as vcpkg." OFF)
option(OUTCOME_ENABLE_CXX_MODULES "Whether to enable the building of an Outcome C++ module" OFF)
set(UNIT_TESTS_CXX_VERSION "latest" CACHE STRING "The version of C++ to use in the unit tests")
if(NOT outcome_IS_DEPENDENCY)
# This file should be updated with the last git SHA next commit
UpdateRevisionHppFromGit("${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/detail/revision.hpp")
# Need to also possibly update the .natvis file
# file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/detail/revision.hpp" namespace_permuter REGEX "OUTCOME_PREVIOUS_COMMIT_UNIQUE (.+)")
# if(NOT namespace_permuter MATCHES "OUTCOME_PREVIOUS_COMMIT_UNIQUE (.+)")
# indented_message(FATAL_ERROR "FATAL: ${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/detail/revision.hpp was not parseable")
# endif()
# set(namespace_permuter "${CMAKE_MATCH_1}")
# file(READ "${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/outcome.natvis" natvis_contents)
# string(REGEX REPLACE "outcome_v2_([0-9a-f]+)" "outcome_v2_${namespace_permuter}" new_natvis_contents "${natvis_contents}")
# if(NOT natvis_contents STREQUAL new_natvis_contents)
# file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/outcome.natvis" "${new_natvis_contents}")
# endif()
endif()
# Find my library dependencies
if(OUTCOME_BUNDLE_EMBEDDED_QUICKCPPLIB AND CTEST_QUICKCPPLIB_CLONE_DIR)
file(COPY "${CTEST_QUICKCPPLIB_CLONE_DIR}/repo/" DESTINATION "${CMAKE_BINARY_DIR}/quickcpplib")
find_quickcpplib_library(quickcpplib
GIT_REPOSITORY "https://github.com/ned14/quickcpplib.git"
REQUIRED
IS_HEADER_ONLY
INBUILD
INCLUDE_ALL
)
else()
find_quickcpplib_library(quickcpplib
GIT_REPOSITORY "https://github.com/ned14/quickcpplib.git"
GIT_TAG "${OUTCOME_DEPENDENCY_QUICKCPPLIB_GIT_TAG}"
REQUIRED
IS_HEADER_ONLY
)
endif()
if(OUTCOME_BUNDLE_EMBEDDED_STATUS_CODE)
ensure_git_subrepo("${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/experimental/status-code/include" "https://github.com/ned14/status-code.git")
else()
find_quickcpplib_library(status-code
GIT_REPOSITORY "https://github.com/ned14/status-code.git"
GIT_TAG "${OUTCOME_DEPENDENCY_STATUS_CODE_GIT_TAG}"
REQUIRED
IS_HEADER_ONLY
)
list_filter(${PROJECT_NAME}_HEADERS EXCLUDE REGEX include/outcome/experimental/status-code/)
endif()
# Make an interface only library so dependent CMakeLists can bring in this header-only library
include(QuickCppLibMakeHeaderOnlyLibrary)
# If we have standard concepts, enable those for both myself and all inclusions
apply_cxx_concepts_to(INTERFACE outcome_hl REQUIRE_STD_CONCEPTS)
# Make preprocessed edition of this library target
if(NOT outcome_IS_DEPENDENCY OR OUTCOME_FORCE_ENABLE_PP_TARGETS)
if(NOT PYTHONINTERP_FOUND)
indented_message(WARNING "NOT rebuilding preprocessed edition of library due to python not being installed")
else()
function(make_single_header target name)
add_partial_preprocess(${target}
"${name}"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/detail/revision.hpp"
${ARGN}
-I ../quickcpplib/include
--passthru-defines --passthru-unfound-includes --passthru-unknown-exprs
--passthru-comments --line-directive --compress # --debug
-U __cpp_modules
-U QUICKCPPLIB_ENABLE_VALGRIND
-U DOXYGEN_SHOULD_SKIP_THIS -U DOXYGEN_IS_IN_THE_HOUSE
-U STANDARDESE_IS_IN_THE_HOUSE
-U OUTCOME_UNSTABLE_VERSION -U OUTCOME_ENABLE_ABI_PERMUTATION
-D QUICKCPPLIB_DISABLE_ABI_PERMUTATION=1
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
if(NOT CMAKE_VERSION VERSION_LESS 3.3)
add_dependencies(outcome_hl ${target})
endif()
endfunction()
make_single_header(outcome_hl-pp-std
"${CMAKE_CURRENT_SOURCE_DIR}/single-header/outcome.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome.hpp")
make_single_header(outcome_hl-pp-basic
"${CMAKE_CURRENT_SOURCE_DIR}/single-header/outcome-basic.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/basic_outcome.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/try.hpp")
make_single_header(outcome_hl-pp-experimental
"${CMAKE_CURRENT_SOURCE_DIR}/single-header/outcome-experimental.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/experimental/status_outcome.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/experimental/status-code/include/status-code/nested_status_code.hpp"
"${CMAKE_CURRENT_SOURCE_DIR}/include/outcome/try.hpp")
endif()
endif()
# Set the standard definitions for these libraries and bring in the all_* helper functions
include(QuickCppLibApplyDefaultDefinitions)
# Set the C++ features this library requires
all_compile_features(PUBLIC
cxx_alias_templates
cxx_variadic_templates
cxx_noexcept
cxx_constexpr
cxx_lambda_init_captures
cxx_attributes
cxx_generic_lambdas
)
if(NOT MSVC OR CMAKE_VERSION VERSION_GREATER 3.59)
all_compile_features(PUBLIC
cxx_variable_templates
)
endif()
# Set the library dependencies this library has
if(TARGET quickcpplib::hl)
target_link_libraries(outcome_hl INTERFACE quickcpplib::hl)
endif()
if(TARGET status-code::hl)
target_link_libraries(outcome_hl INTERFACE status-code::hl)
all_compile_definitions(PUBLIC OUTCOME_USE_SYSTEM_STATUS_CODE=1)
endif()
if(OUTCOME_ENABLE_CXX_MODULES)
# Right now this is very hacky, as cmake doesn't support building C++ Modules yet
add_custom_target(outcome_module
COMMAND "${CMAKE_CXX_COMPILER}" /std:c++latest /EHsc $<$:/MDd> $<$>:/MD> /fp:precise /c "${CMAKE_CURRENT_SOURCE_DIR}/include/outcome.ixx" "-I${CMAKE_CURRENT_SOURCE_DIR}/../quickcpplib/include" "-I${CMAKE_BINARY_DIR}/quickcpplib/include"
)
all_compile_definitions(PUBLIC
OUTCOME_ENABLE_CXX_MODULES=1
)
endif()
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test" AND NOT outcome_IS_DEPENDENCY AND (NOT DEFINED BUILD_TESTING OR BUILD_TESTING))
# For all possible configurations of this library, add each test
list_filter(outcome_TESTS EXCLUDE REGEX "constexprs|link")
set(outcome_TESTS_DISABLE_PRECOMPILE_HEADERS
"outcome_hl--coroutine-support"
"outcome_hl--core-result"
"outcome_hl--fileopen"
"outcome_hl--hooks"
"outcome_hl--outcome-int-int-1"
"outcome_hl--result-int-int-1"
"outcome_hl--result-int-int-2"
)
include(QuickCppLibMakeStandardTests)
# Enable Coroutines for the coroutines support test
foreach(target ${outcome_TEST_TARGETS})
if(${target} MATCHES "coroutine-support")
apply_cxx_coroutines_to(PRIVATE ${target})
endif()
# MSVC's concepts implementation blow up unless permissive is off
if(MSVC AND NOT CLANG)
target_compile_options(${target} PRIVATE /permissive-)
endif()
endforeach()
# Duplicate all tests into C++ exceptions disabled forms
set(noexcept_tests)
set(first_test_target_noexcept)
set(first_test_target_permissive)
foreach(testsource ${outcome_TESTS})
if(testsource MATCHES ".+/(.+)[.](c|cpp|cxx)$")
set(testname ${CMAKE_MATCH_1})
if(NOT testname MATCHES "expected-pass")
set(target_name "outcome_hl--${testname}-noexcept")
add_executable(${target_name} "${testsource}")
if(NOT first_test_target_noexcept)
set(first_test_target_noexcept ${target_name})
elseif(${target_name} MATCHES "coroutine-support|fileopen|hooks|core-result")
set_target_properties(${target_name} PROPERTIES DISABLE_PRECOMPILE_HEADERS On)
elseif(COMMAND target_precompile_headers)
target_precompile_headers(${target_name} REUSE_FROM ${first_test_target_noexcept})
endif()
add_dependencies(_hl ${target_name})
list(APPEND noexcept_tests ${target_name})
if(MSVC AND NOT CLANG)
# Disable warnings "C++ exception handler used" and "noexcept used with no exception handling"
target_compile_options(${target_name} PRIVATE /wd4530 /wd4577)
# target_compile_options(${target_name} PRIVATE /permissive-) # test bug report #142
endif()
target_compile_definitions(${target_name} PRIVATE SYSTEM_ERROR2_NOT_POSIX=1 "SYSTEM_ERROR2_FATAL=::abort()")
target_link_libraries(${target_name} PRIVATE outcome::hl)
if(${target_name} MATCHES "coroutine-support")
apply_cxx_coroutines_to(PRIVATE ${target_name})
endif()
set_target_properties(${target_name} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
POSITION_INDEPENDENT_CODE ON
CXX_EXCEPTIONS OFF
CXX_RTTI OFF
)
add_test(NAME ${target_name} CONFIGURATIONS Debug Release RelWithDebInfo MinSizeRel
COMMAND $ --reporter junit --out $.junit.xml
)
if(MSVC AND NOT CLANG)
set(target_name "outcome_hl--${testname}-permissive")
add_executable(${target_name} "${testsource}")
if(NOT first_test_target_permissive)
set(first_test_target_permissive ${target_name})
elseif(${target_name} MATCHES "coroutine-support|fileopen|core-result")
set_target_properties(${target_name} PROPERTIES DISABLE_PRECOMPILE_HEADERS On)
elseif(COMMAND target_precompile_headers)
target_precompile_headers(${target_name} REUSE_FROM ${first_test_target_permissive})
endif()
add_dependencies(_hl ${target_name})
target_link_libraries(${target_name} PRIVATE outcome::hl)
target_compile_options(${target_name} PRIVATE /permissive)
if(${target_name} MATCHES "coroutine-support")
apply_cxx_coroutines_to(PRIVATE ${target_name})
endif()
set_target_properties(${target_name} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
POSITION_INDEPENDENT_CODE ON
)
if(OUTCOME_ENABLE_CXX_MODULES)
set(target_name "outcome_hl--${testname}-modules")
add_executable(${target_name} "${testsource}")
add_dependencies(${target_name} outcome_module)
target_link_libraries(${target_name} PRIVATE outcome::hl)
target_compile_options(${target_name} PRIVATE /experimental:module /std:c++latest)
target_compile_definitions(${target_name} PRIVATE __cpp_modules=202001L)
set_target_properties(${target_name} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
POSITION_INDEPENDENT_CODE ON
DISABLE_PRECOMPILE_HEADERS On
)
endif()
endif()
endif()
endif()
endforeach()
add_custom_target(${PROJECT_NAME}-noexcept COMMENT "Building all tests with C++ exceptions disabled ...")
add_dependencies(${PROJECT_NAME}-noexcept ${noexcept_tests})
# Turn on latest C++ where possible for the test suite
if(UNIT_TESTS_CXX_VERSION STREQUAL "latest")
set(LATEST_CXX_FEATURE)
foreach(feature ${CMAKE_CXX_COMPILE_FEATURES})
if(feature STREQUAL "cxx_std_23")
set(LATEST_CXX_FEATURE "cxx_std_23")
indented_message(STATUS "NOTE: This compiler claims to support C++ 23, enabling for unit test suite")
endif()
endforeach()
if(NOT LATEST_CXX_FEATURE)
foreach(feature ${CMAKE_CXX_COMPILE_FEATURES})
if(feature STREQUAL "cxx_std_20")
set(LATEST_CXX_FEATURE "cxx_std_20")
indented_message(STATUS "NOTE: This compiler claims to support C++ 20, enabling for unit test suite")
endif()
endforeach()
endif()
if(NOT LATEST_CXX_FEATURE)
foreach(feature ${CMAKE_CXX_COMPILE_FEATURES})
if(feature STREQUAL "cxx_std_17")
set(LATEST_CXX_FEATURE "cxx_std_17")
indented_message(STATUS "NOTE: This compiler claims to support C++ 17, enabling for unit test suite")
endif()
endforeach()
endif()
elseif(UNIT_TESTS_CXX_VERSION)
set(LATEST_CXX_FEATURE "cxx_std_${UNIT_TESTS_CXX_VERSION}")
endif()
if(LATEST_CXX_FEATURE)
# Turn on latest C++ where possible for the test suite
if(ENABLE_CXX_MODULES)
target_compile_features(outcome_hl_ixx PUBLIC ${LATEST_CXX_FEATURE})
endif()
foreach(test_target ${outcome_TEST_TARGETS} ${outcome_EXAMPLE_TARGETS})
target_compile_features(${test_target} PUBLIC ${LATEST_CXX_FEATURE})
endforeach()
endif()
# Add in the documentation snippets
foreach(feature ${CMAKE_CXX_COMPILE_FEATURES})
if(feature STREQUAL cxx_std_17)
file(GLOB example_srcs RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/doc/src/snippets/*.c"
"${CMAKE_CURRENT_SOURCE_DIR}/doc/src/snippets/*.cpp"
)
set(example_bins)
foreach(example_src ${example_srcs})
if(example_src MATCHES ".+/(.+)[.](c|cpp|cxx)$")
set(example_bin "${PROJECT_NAME}-snippets_${CMAKE_MATCH_1}")
add_executable(${example_bin} EXCLUDE_FROM_ALL "${example_src}")
list(APPEND example_bins ${example_bin})
target_link_libraries(${example_bin} PRIVATE outcome::hl)
set_target_properties(${example_bin} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
POSITION_INDEPENDENT_CODE ON
)
if(example_src MATCHES "[.]c$")
set_target_properties(${example_bin} PROPERTIES DISABLE_PRECOMPILE_HEADERS On)
else()
target_compile_features(${example_bin} PUBLIC cxx_std_17)
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
target_link_libraries(${example_bin} PRIVATE stdc++fs)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" OR APPLE)
target_link_libraries(${example_bin} PRIVATE c++experimental)
endif()
endif()
endif()
endforeach()
add_custom_target(${PROJECT_NAME}-snippets COMMENT "Building all documentation snippets ...")
add_dependencies(${PROJECT_NAME}-snippets ${example_bins})
endif()
endforeach()
endif()
# Add in link tests
add_subdirectory("test/link")
# Turn on pedantic warnings for all tests, examples and snippets
if(NOT MSVC)
foreach(target ${outcome_TEST_TARGETS} ${outcome_EXAMPLE_TARGETS} ${outcome_LINK_TARGETS} ${example_bins})
target_compile_options(${target} PUBLIC "-Wpedantic")
endforeach()
endif()
if(OUTCOME_ENABLE_DEPENDENCY_SMOKE_TEST)
set(OUTCOME_SMOKE_TESTS)
add_executable(outcome-dependency-smoke-test_1 "test/tests/core-result.cpp")
list(APPEND OUTCOME_SMOKE_TESTS outcome-dependency-smoke-test_1)
add_executable(outcome-dependency-smoke-test_2 "test/tests/experimental-core-result-status.cpp")
list(APPEND OUTCOME_SMOKE_TESTS outcome-dependency-smoke-test_2)
foreach(target ${OUTCOME_SMOKE_TESTS})
target_link_libraries(${target} PRIVATE outcome::hl)
set_target_properties(${target} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
DISABLE_PRECOMPILE_HEADERS On
)
add_test(NAME ${target} CONFIGURATIONS Debug Release RelWithDebInfo MinSizeRel
COMMAND $ --reporter junit --out $.junit.xml
)
endforeach()
endif()
# Cache this library's auto scanned sources for later reuse
include(QuickCppLibCacheLibrarySources)
# Dependencies needed to consume our cmake package
set(PROJECT_PACKAGE_DEPENDENCIES "include(CMakeFindDependencyMacro)")
if(TARGET quickcpplib::hl)
string(APPEND PROJECT_PACKAGE_DEPENDENCIES "\nfind_dependency(quickcpplib)")
endif()
if(TARGET status-code::hl)
string(APPEND PROJECT_PACKAGE_DEPENDENCIES "\nfind_dependency(status-code)")
endif()
# Make available this library for install and export
include(QuickCppLibMakeInstall)
include(QuickCppLibMakeExport)