init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

58
cmake/external/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,58 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
macro(add_checked_subdirectory name)
if (NOT DEFINED desktop_app_skip_libs
OR NOT ${name} IN_LIST desktop_app_skip_libs)
add_subdirectory(${name})
endif()
endmacro()
add_checked_subdirectory(ada)
add_checked_subdirectory(angle)
add_checked_subdirectory(auto_updates)
add_checked_subdirectory(boost)
if (add_cld3_library)
add_checked_subdirectory(cld3)
endif()
add_checked_subdirectory(crash_reports)
if (LINUX)
add_checked_subdirectory(dispatch)
endif()
add_checked_subdirectory(expected)
add_checked_subdirectory(ffmpeg)
if (LINUX)
add_checked_subdirectory(glib)
add_checked_subdirectory(glibmm)
endif()
add_checked_subdirectory(gsl)
if (add_hunspell_library)
add_checked_subdirectory(hunspell)
endif()
add_checked_subdirectory(iconv)
add_checked_subdirectory(jpeg)
add_checked_subdirectory(kcoreaddons)
add_checked_subdirectory(lz4)
add_checked_subdirectory(minizip)
add_checked_subdirectory(openal)
add_checked_subdirectory(openh264)
add_checked_subdirectory(openssl)
add_checked_subdirectory(opus)
add_checked_subdirectory(qt)
add_checked_subdirectory(qr_code_generator)
add_checked_subdirectory(ranges)
add_checked_subdirectory(rlottie)
add_checked_subdirectory(rnnoise)
add_checked_subdirectory(tde2e)
add_checked_subdirectory(ton)
add_checked_subdirectory(variant)
add_checked_subdirectory(vpx)
add_checked_subdirectory(webrtc)
if (LINUX AND NOT DESKTOP_APP_DISABLE_X11_INTEGRATION)
add_checked_subdirectory(xcb)
endif()
add_checked_subdirectory(xxhash)
add_checked_subdirectory(zlib)

46
cmake/external/ada/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,46 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
if (DESKTOP_APP_USE_PACKAGED)
add_library(external_ada INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_ada ALIAS external_ada)
find_package(ada REQUIRED)
target_link_libraries(external_ada INTERFACE ada::ada)
return()
endif()
add_library(external_ada STATIC IMPORTED GLOBAL)
add_library(desktop-app::external_ada ALIAS external_ada)
if (WIN32)
set(ada_lib_loc ${libs_loc}/ada/out/singleheader)
target_include_directories(external_ada SYSTEM
INTERFACE
${ada_lib_loc}
)
set_target_properties(external_ada PROPERTIES
IMPORTED_LOCATION "${ada_lib_loc}/Release/ada-singleheader-lib.lib"
IMPORTED_LOCATION_DEBUG "${ada_lib_loc}/Debug/ada-singleheader-lib.lib"
)
elseif (APPLE)
target_include_directories(external_ada SYSTEM
INTERFACE
${libs_loc}/local/include
)
set_target_properties(external_ada PROPERTIES
IMPORTED_LOCATION ${libs_loc}/local/lib/libada.a
)
else()
target_include_directories(external_ada SYSTEM
INTERFACE
/usr/local/include
)
find_library(DESKTOP_APP_ADA_LIBRARIES libada.a REQUIRED)
set_target_properties(external_ada PROPERTIES
IMPORTED_LOCATION "${DESKTOP_APP_ADA_LIBRARIES}"
)
endif()

28
cmake/external/angle/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,28 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_angle INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_angle ALIAS external_angle)
if (WIN32)
target_include_directories(external_angle SYSTEM
INTERFACE
${libs_loc}/tg_angle/include
)
target_link_libraries(external_angle
INTERFACE
${libs_loc}/tg_angle/out/$<IF:$<CONFIG:Debug>,Debug,Release>/tg_angle.lib
dxguid.lib
)
target_link_libraries(external_angle
INTERFACE
desktop-app::win_directx_helper
)
target_compile_definitions(external_angle
INTERFACE
KHRONOS_STATIC
)
endif()

View File

@@ -0,0 +1,24 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_auto_updates INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_auto_updates ALIAS external_auto_updates)
if (NOT DESKTOP_APP_DISABLE_AUTOUPDATE)
if (WIN32 AND NOT DESKTOP_APP_USE_PACKAGED)
add_subdirectory(lzma)
target_link_libraries(external_auto_updates
INTERFACE
desktop-app::external_lzma
)
else()
add_subdirectory(xz)
target_link_libraries(external_auto_updates
INTERFACE
desktop-app::external_xz
)
endif()
endif()

View File

@@ -0,0 +1,28 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_lzma INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_lzma ALIAS external_lzma)
target_include_directories(external_lzma SYSTEM
INTERFACE
${libs_loc}/lzma/C
)
if (build_winarm)
set(lzma_platform_dir ARM64/)
elseif (build_win64)
set(lzma_platform_dir x64/)
else()
set(lzma_platform_dir "")
endif()
set(lzma_lib_loc ${libs_loc}/lzma/C/Util/LzmaLib/${lzma_platform_dir}$<IF:$<CONFIG:Debug>,Debug,Release>)
target_link_libraries(external_lzma
INTERFACE
${lzma_lib_loc}/LzmaLib.lib
)

View File

@@ -0,0 +1,22 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_xz INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_xz ALIAS external_xz)
if (DESKTOP_APP_USE_PACKAGED OR LINUX)
find_package(LibLZMA REQUIRED)
target_link_libraries(external_xz INTERFACE LibLZMA::LibLZMA)
elseif (APPLE)
target_include_directories(external_xz SYSTEM
INTERFACE
${libs_loc}/local/include
)
target_link_libraries(external_xz
INTERFACE
${libs_loc}/local/lib/liblzma.a
)
endif()

25
cmake/external/boost/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,25 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_boost_regex INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_boost_regex ALIAS external_boost_regex)
target_compile_definitions(external_boost_regex
INTERFACE
BOOST_NO_INTRINSIC_WCHAR_T
BOOST_REGEX_NO_W32
)
if (DESKTOP_APP_USE_PACKAGED OR LINUX)
find_package(Boost COMPONENTS regex REQUIRED)
target_link_libraries(external_boost_regex INTERFACE Boost::regex)
return()
endif()
target_include_directories(external_boost_regex SYSTEM
INTERFACE
${libs_loc}/regex/include
)

130
cmake/external/cld3/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,130 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_cld3 INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_cld3 ALIAS external_cld3)
if (DESKTOP_APP_USE_PACKAGED)
find_package(protobuf CONFIG)
find_library(DESKTOP_APP_CLD3_LIBRARIES NAMES cld3)
find_path(DESKTOP_APP_CLD3_INCLUDE_DIRS NAMES nnet_language_identifier.h PATH_SUFFIXES cld3)
if (protobuf_FOUND AND DESKTOP_APP_CLD3_LIBRARIES AND DESKTOP_APP_CLD3_INCLUDE_DIRS)
list(APPEND DESKTOP_APP_CLD3_LIBRARIES protobuf::libprotobuf-lite)
target_include_directories(external_cld3 SYSTEM INTERFACE ${DESKTOP_APP_CLD3_INCLUDE_DIRS})
target_link_libraries(external_cld3 INTERFACE ${DESKTOP_APP_CLD3_LIBRARIES})
return()
endif()
endif()
add_library(external_cld3_bundled STATIC)
init_target(external_cld3_bundled "(external)")
set(cld3_loc ${third_party_loc}/cld3)
set(cld3_src ${cld3_loc}/src)
set(gen_loc ${CMAKE_CURRENT_BINARY_DIR}/gen)
set(gen_dst ${gen_loc}/cld_3/protos)
if (DESKTOP_APP_USE_PACKAGED OR LINUX)
find_package(protobuf REQUIRED CONFIG)
set(protoc_executable protobuf::protoc)
set(protobuf_lib protobuf::libprotobuf-lite)
elseif (WIN32)
set(build_loc ${libs_loc}/protobuf/build/$<IF:$<CONFIG:Debug>,Debug,Release>)
set(protoc_executable ${build_loc}/protoc.exe)
set(protobuf_lib ${build_loc}/libprotobuf-lite.lib)
else()
set(protoc_executable ${libs_loc}/protobuf/build/protoc)
set(protobuf_lib ${libs_loc}/protobuf/build/libprotobuf-lite.a)
endif()
if (PROTOBUF_PROTOC_EXECUTABLE)
set(protoc_executable ${PROTOBUF_PROTOC_EXECUTABLE})
endif()
include(generate_protobuf.cmake)
function(generate protobuf_name)
generate_single_protobuf(
external_cld3_bundled
${gen_dst}
${protobuf_name}
${protoc_executable})
endfunction()
generate(feature_extractor.proto)
generate(sentence.proto)
generate(task_spec.proto)
nice_target_sources(external_cld3_bundled ${cld3_src}
PRIVATE
feature_extractor.proto
sentence.proto
task_spec.proto
base.cc
embedding_feature_extractor.cc
embedding_network.cc
feature_extractor.cc
feature_extractor.h
feature_types.cc
fml_parser.cc
language_identifier_features.cc
lang_id_nn_params.cc
nnet_language_identifier.cc
registry.cc
relevant_script_feature.cc
sentence_features.cc
task_context.cc
task_context_params.cc
unicodetext.cc
utils.cc
workspace.cc
script_span/generated_entities.cc
script_span/getonescriptspan.cc
script_span/getonescriptspan.h
script_span/utf8statetable.cc
script_span/offsetmap.cc
script_span/text_processing.cc
script_span/text_processing.h
script_span/fixunicodevalue.cc
)
target_compile_definitions(external_cld3_bundled
PUBLIC
$<$<NOT:$<CONFIG:Debug>>:NDEBUG>
)
if (NOT MSVC)
target_compile_options_if_exists(external_cld3_bundled
PRIVATE
-Wno-implicit-fallthrough
)
endif()
target_include_directories(external_cld3_bundled
PUBLIC
${cld3_src}
${gen_loc}
)
if (NOT DESKTOP_APP_USE_PACKAGED AND NOT LINUX)
target_include_directories(external_cld3_bundled
PUBLIC
${libs_loc}/protobuf/src
${libs_loc}/protobuf/third_party/abseil-cpp
)
endif()
target_link_libraries(external_cld3_bundled
PUBLIC
${protobuf_lib}
)
target_link_libraries(external_cld3
INTERFACE
external_cld3_bundled
)

View File

@@ -0,0 +1,58 @@
function(generate_single_protobuf target_name gen_dst protobuf_name executable)
file(MAKE_DIRECTORY ${gen_dst})
# Copied from myprotobuf.cmake.
if (PROTOBUF_GENERATE_CPP_APPEND_PATH)
# Create an include path for each file specified
set(FIL ${cld3_src}/${protobuf_name})
get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
get_filename_component(ABS_PATH ${ABS_FIL} PATH)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if (${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
else()
set(_protobuf_include_path -I ${cld3_src})
endif()
if (DEFINED PROTOBUF_IMPORT_DIRS)
foreach (DIR ${PROTOBUF_IMPORT_DIRS})
get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
if (${_contains_already} EQUAL -1)
list(APPEND _protobuf_include_path -I ${ABS_PATH})
endif()
endforeach()
endif()
#
get_filename_component(protobuf_name_we ${protobuf_name} NAME_WE)
set(gen_timestamp ${gen_dst}/${protobuf_name}.timestamp)
set(gen_files
${gen_dst}/${protobuf_name_we}.pb.cc
${gen_dst}/${protobuf_name_we}.pb.h
)
set(gen_src ${cld3_src}/${protobuf_name})
add_custom_command(
OUTPUT
${gen_timestamp}
BYPRODUCTS
${gen_files}
COMMAND
${executable}
--cpp_out
${gen_dst}
${_protobuf_include_path}
${gen_src}
COMMAND
echo 1> ${gen_timestamp}
COMMENT "Generating protobuf ${protobuf_name} (${target_name})"
DEPENDS
${executable}
${gen_src}
VERBATIM
)
generate_target(${target_name} ${protobuf_name} ${gen_timestamp} "${gen_files}" ${gen_dst})
endfunction()

View File

@@ -0,0 +1,24 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_crash_reports INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_crash_reports ALIAS external_crash_reports)
if (NOT DESKTOP_APP_DISABLE_CRASH_REPORTS)
if (WIN32 OR LINUX OR build_macstore)
add_subdirectory(breakpad)
target_link_libraries(external_crash_reports
INTERFACE
desktop-app::external_breakpad
)
else()
add_subdirectory(crashpad)
target_link_libraries(external_crash_reports
INTERFACE
desktop-app::external_crashpad
)
endif()
endif()

View File

@@ -0,0 +1,39 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_breakpad INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_breakpad ALIAS external_breakpad)
if (DESKTOP_APP_USE_PACKAGED OR LINUX)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DESKTOP_APP_BREAKPAD REQUIRED IMPORTED_TARGET breakpad-client)
target_link_libraries(external_breakpad INTERFACE PkgConfig::DESKTOP_APP_BREAKPAD)
return()
endif()
target_include_directories(external_breakpad SYSTEM
INTERFACE
${libs_loc}/breakpad/src
)
if (build_winarm)
set(breakpad_config_add _ARM64)
elseif (build_win64)
set(breakpad_config_add _x64)
else()
set(breakpad_config_add "")
endif()
set(breakpad_lib_loc ${libs_loc}/breakpad/src/out/$<IF:$<CONFIG:Debug>,Debug${breakpad_config_add},Release${breakpad_config_add}>/obj/client)
if (WIN32)
target_link_libraries(external_breakpad
INTERFACE
${breakpad_lib_loc}/windows/common.lib
${breakpad_lib_loc}/windows/handler/exception_handler.lib
${breakpad_lib_loc}/windows/crash_generation/crash_generation_client.lib
)
endif()

View File

@@ -0,0 +1,23 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_crashpad INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_crashpad ALIAS external_crashpad)
target_include_directories(external_crashpad SYSTEM
INTERFACE
${libs_loc}/crashpad
${libs_loc}/crashpad/gen
${libs_loc}/crashpad/third_party/mini_chromium
)
set(crashpad_lib_loc ${libs_loc}/crashpad/out/$<IF:$<CONFIG:Debug>,Debug,Release>)
target_link_libraries(external_crashpad
INTERFACE
${crashpad_lib_loc}/libcrashpad_client.a
bsm
)

71
cmake/external/dispatch/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,71 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
set(dispatch_loc ${third_party_loc}/dispatch)
set(dispatch_prefix ${CMAKE_CURRENT_BINARY_DIR}/dispatch-prefix)
set(dispatch_binary_dir ${dispatch_prefix}/src/dispatch-build)
if (DESKTOP_APP_USE_PACKAGED)
find_library(DESKTOP_APP_DISPATCH_LIBRARIES dispatch)
find_path(DESKTOP_APP_DISPATCH_INCLUDE_DIRS dispatch/dispatch.h)
endif()
if (NOT DESKTOP_APP_DISPATCH_LIBRARIES OR NOT DESKTOP_APP_DISPATCH_INCLUDE_DIRS)
execute_process(
COMMAND ${CMAKE_COMMAND} ${dispatch_loc}
-B ${dispatch_binary_dir}
-GNinja
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_C_COMPILER=clang
-DCMAKE_CXX_COMPILER=clang++
-DCMAKE_C_FLAGS=-g
-DCMAKE_CXX_FLAGS=-g
-DBUILD_SHARED_LIBS=OFF
-DBUILD_TESTING=OFF
)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${dispatch_binary_dir} --parallel
)
if (NOT EXISTS ${dispatch_binary_dir}/src/libdispatch.a)
if (DESKTOP_APP_SPECIAL_TARGET)
message(FATAL_ERROR "Dispatch build failed")
else()
return()
endif()
endif()
endif()
add_library(external_dispatch INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_dispatch ALIAS external_dispatch)
if (DESKTOP_APP_USE_PACKAGED AND DESKTOP_APP_DISPATCH_LIBRARIES AND DESKTOP_APP_DISPATCH_INCLUDE_DIRS)
target_include_directories(external_dispatch SYSTEM INTERFACE ${DESKTOP_APP_DISPATCH_INCLUDE_DIRS})
target_link_libraries(external_dispatch INTERFACE ${DESKTOP_APP_DISPATCH_LIBRARIES})
return()
endif()
add_library(external_dispatch_bundled STATIC IMPORTED)
set_target_properties(external_dispatch_bundled PROPERTIES
IMPORTED_LOCATION "${dispatch_binary_dir}/src/libdispatch.a"
)
target_include_directories(external_dispatch_bundled SYSTEM
INTERFACE
${dispatch_loc}
)
target_link_libraries(external_dispatch_bundled
INTERFACE
${dispatch_binary_dir}/src/BlocksRuntime/libBlocksRuntime.a
)
target_link_libraries(external_dispatch
INTERFACE
external_dispatch_bundled
)

21
cmake/external/expected/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,21 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_expected INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_expected ALIAS external_expected)
if (DESKTOP_APP_USE_PACKAGED)
find_package(tl-expected QUIET)
if (tl-expected_FOUND)
target_link_libraries(external_expected INTERFACE tl::expected)
return()
endif()
endif()
target_include_directories(external_expected SYSTEM
INTERFACE
${third_party_loc}/expected/include
)

49
cmake/external/ffmpeg/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,49 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_ffmpeg INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_ffmpeg ALIAS external_ffmpeg)
if (DESKTOP_APP_USE_PACKAGED OR LINUX)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DESKTOP_APP_FFMPEG REQUIRED IMPORTED_TARGET
libavcodec
libavfilter
libavformat
libavutil
libswresample
libswscale
)
target_link_libraries(external_ffmpeg INTERFACE PkgConfig::DESKTOP_APP_FFMPEG)
return()
endif()
set(ffmpeg_lib_loc ${libs_loc}/ffmpeg)
target_include_directories(external_ffmpeg SYSTEM
INTERFACE
${ffmpeg_lib_loc}
)
target_link_libraries(external_ffmpeg
INTERFACE
${ffmpeg_lib_loc}/libavfilter/libavfilter.a
${ffmpeg_lib_loc}/libavformat/libavformat.a
${ffmpeg_lib_loc}/libavcodec/libavcodec.a
${ffmpeg_lib_loc}/libswresample/libswresample.a
${ffmpeg_lib_loc}/libswscale/libswscale.a
${ffmpeg_lib_loc}/libavutil/libavutil.a
$<LINK_ONLY:desktop-app::external_openh264>
$<TARGET_FILE:desktop-app::external_openh264>
$<LINK_ONLY:desktop-app::external_opus>
$<TARGET_FILE:desktop-app::external_opus>
$<LINK_ONLY:desktop-app::external_vpx>
$<TARGET_FILE:desktop-app::external_vpx>
)
if (APPLE)
target_link_libraries(external_ffmpeg INTERFACE bz2)
endif()

37
cmake/external/glib/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,37 @@
# This file is part of Desktop App Toolkit,
# a set of libraries for developing nice desktop applications.
#
# For license and copyright information please follow this link:
# https://github.com/desktop-app/legal/blob/master/LEGAL
add_library(external_glib INTERFACE IMPORTED GLOBAL)
add_library(desktop-app::external_glib ALIAS external_glib)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DESKTOP_APP_GLIB2 REQUIRED IMPORTED_TARGET glib-2.0 gobject-2.0 gio-2.0 gio-unix-2.0)
target_link_libraries(external_glib
INTERFACE
PkgConfig::DESKTOP_APP_GLIB2
)
target_compile_definitions(external_glib
INTERFACE
GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_56
GLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_56
)
block()
set(BUILD_TESTING OFF)
set(BUILD_DOC OFF)
set(BUILD_EXAMPLES OFF)
add_subdirectory(cppgir EXCLUDE_FROM_ALL)
endblock()
include(generate_cppgir.cmake)
if (DESKTOP_APP_GLIB2_gio-unix-2.0_VERSION VERSION_GREATER_EQUAL 2.86)
generate_cppgir(external_glib GioUnix-2.0)
else()
generate_cppgir(external_glib Gio-2.0)
endif()

View File

@@ -0,0 +1,30 @@
Language: Cpp
Standard: Cpp11
BasedOnStyle: llvm
AlignAfterOpenBracket: DontAlign
PenaltyBreakBeforeFirstCallParameter: 200
IndentWidth: 2
AllowShortFunctionsOnASingleLine: All
KeepEmptyLinesAtTheStartOfBlocks: false
#BreakBeforeBraces: Linux
BreakBeforeBraces: Custom
BraceWrapping:
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterClass: true
AfterUnion: true
SplitEmptyFunction: false
SplitEmptyRecord: false
ColumnLimit: 80
ContinuationIndentWidth: 4
PointerAlignment: Right
AlwaysBreakAfterReturnType: TopLevelDefinitions
#BreakInheritanceList: AfterColon
FixNamespaceComments: true
IndentCaseLabels: true
AlwaysBreakTemplateDeclarations: true
SpaceAfterTemplateKeyword: false
#SpaceBeforeCpp11BracedList: true
AlignEscapedNewlines: DontAlign

View File

@@ -0,0 +1,54 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
stages:
- build
default:
image: registry.gitlab.com/mnauw/cppgir:noble
ubuntu-focal-gcc:
image: registry.gitlab.com/mnauw/cppgir:focal
stage: build
script:
- mkdir build-gcc
- cd build-gcc
- cmake ..
- make
- make examples
except:
- tags
ubuntu-focal-clang:
image: registry.gitlab.com/mnauw/cppgir:focal
stage: build
script:
- mkdir build-clang
- cd build-clang
- CC=clang-11 CXX=clang++-11 cmake ..
- make
- make examples
except:
- tags
ubuntu-noble-gcc-13:
stage: build
script:
- mkdir build-gcc-13
- cd build-gcc-13
- cmake ..
- make
- make examples
except:
- tags
ubuntu-noble-clang-18:
stage: build
script:
- mkdir build-clang-18
- cd build-clang-18
- CC=clang-18 CXX=clang++-18 cmake ..
- make
- make examples
except:
- tags

View File

@@ -0,0 +1,15 @@
FROM ubuntu:noble
RUN apt-get update
RUN apt-get -y --no-install-recommends install \
build-essential cmake libboost-program-options-dev libboost-fiber-dev \
libfmt-dev ronn clang-18 clang-tools-18 \
libgirepository1.0-dev libgstreamer1.0-dev \
libgtk-3-dev gir1.2-gtk-3.0 \
libgtk-4-dev gir1.2-gtk-4.0 \
qtbase5-dev meson git
RUN apt-get clean
WORKDIR /build
ENV LANG C.UTF-8

View File

@@ -0,0 +1,9 @@
set -e
TAG="registry.gitlab.com/mnauw/cppgir:jammy"
docker build --build-arg HOST_USER_ID="$UID" --tag "${TAG}" \
--file "Dockerfile" .
docker run --rm \
--volume "$(pwd)/..:/home/user/app" --workdir "/home/user/app" \
--tty --interactive "${TAG}" bash

View File

@@ -0,0 +1,3 @@
[submodule "expected-lite"]
path = expected-lite
url = https://github.com/martinmoene/expected-lite.git

View File

@@ -0,0 +1,582 @@
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif ()
if (POLICY CMP0069)
cmake_policy(SET CMP0069 NEW)
endif ()
if (POLICY CMP0167)
# try to use FindBoost for now
cmake_policy(SET CMP0167 OLD)
endif ()
project(cppgir VERSION 2.0.0)
include(FindPkgConfig)
include(GNUInstallDirs)
include(CTest)
enable_testing()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 14)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
add_compile_options (-Wall -Wextra $<$<COMPILE_LANGUAGE:CXX>:-Wnon-virtual-dtor>)
endif()
# clang debug stdc++
if (${CMAKE_C_COMPILER_ID} STREQUAL "Clang")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstandalone-debug")
endif()
## OPTIONS ##
option(BUILD_DOC "build documentation" ON)
option(BUILD_TOOLS "build generator tool" ON)
option(BUILD_EXAMPLES "build examples" ON)
option(BUILD_EXAMPLES_MODULES "build module examples" OFF)
option(BUILD_EXAMPLES_FORCE "build failing module examples" OFF)
set(BUILD_EXAMPLES_OPTIONS "" CACHE STRING "example compile options")
option(BUILD_EMBED_IGNORE "embed default ignore" OFF)
option(INTERNAL_EXPECTED "use internal expected-lite" ON)
option(LINK_STDFS "link to stdc++fs" OFF)
set(BUILD_FMT AUTO CACHE STRING "format library")
set_property(CACHE BUILD_FMT PROPERTY STRINGS AUTO FMTLIB STDFORMAT)
set(GTK_MAJOR GTK3 CACHE STRING "gtk version")
set_property(CACHE GTK_MAJOR PROPERTY STRINGS GTK3 GTK4)
set(GIR_DIR ${CMAKE_INSTALL_FULL_DATADIR}/gir-1.0 CACHE STRING "extra GIR search directory")
# the following will have gir-1.0 appended (for legacy reasons)
# best left as-is to follow standard GIR search
set(GIR_DEFAULT_DIRS "/usr/local/${CMAKE_INSTALL_DATADIR}:/usr/${CMAKE_INSTALL_DATADIR}"
CACHE STRING "fallback GIR search prefix paths (to be suffixed with gir-1.0)")
## CONTENT ##
if (BUILD_TOOLS)
find_package(Boost 1.58 REQUIRED)
# check fmtlib
pkg_check_modules(FORMAT fmt)
if (FORMAT_FOUND)
set(FORMAT_LIBRARIES "${FORMAT_LDFLAGS}")
else ()
# fallback for old version without pkg-config
find_path(FORMAT_INCLUDE_DIRS fmt/format.h)
find_library(FORMAT_LIBRARIES fmt)
endif ()
if ("${FORMAT_INCLUDE_DIRS}" STREQUAL "FORMAT_INCLUDE_DIRS-NOTFOUND" OR
"${FORMAT_LIBRARIES}" STREQUAL "FORMAT_LIBRARIES-NOTFOUND")
set(HAVE_FMTLIB OFF)
else()
set(HAVE_FMTLIB ON)
endif()
message(STATUS "fmtlib available status: ${HAVE_FMTLIB}")
# check C++20 format
try_compile(HAVE_STDFORMAT ${CMAKE_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_LIST_DIR}/cmake/cpp20_format.cpp
CXX_STANDARD 20)
message(STATUS "std::format available status: ${HAVE_STDFORMAT}")
# check and decide on which fmt to use
set(USE_FMTLIB NONE)
if (BUILD_FMT STREQUAL "AUTO")
if (HAVE_FMTLIB)
set(USE_FMTLIB ON)
elseif (HAVE_STDFORMAT)
set(USE_FMTLIB OFF)
endif()
elseif(BUILD_FMT STREQUAL "FMTLIB" AND HAVE_FMTLIB)
set(USE_FMTLIB ON)
elseif(BUILD_FMT STREQUAL "STDFORMAT" AND HAVE_STDFORMAT)
set(USE_FMTLIB OFF)
endif()
if (USE_FMTLIB STREQUAL "NONE")
message (FATAL_ERROR "no format library found")
endif()
# check C++20 coroutine
try_compile(HAVE_CORO ${CMAKE_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_LIST_DIR}/cmake/cpp20_coro.cpp
CXX_STANDARD 20)
message(STATUS "coroutine support available status: ${HAVE_CORO}")
# required ignore file
set(GI_IGNORE_FILE_DIR data)
set(GI_IGNORE_FILE cppgir.ignore)
file(READ data/cppgir.ignore CPPGIR_IGNORE)
if (UNIX)
set(GI_IGNORE_FILE_PLATFORM cppgir_unix.ignore)
file(READ data/cppgir_unix.ignore CPPGIR_UNIX_IGNORE)
set(CPPGIR_WIN_IGNORE "")
else ()
set(GI_IGNORE_FILE_PLATFORM cppgir_win.ignore)
file(READ data/cppgir_win.ignore CPPGIR_WIN_IGNORE)
set(CPPGIR_UNIX_IGNORE "")
endif ()
set(GI_IGNORE_FILE_INSTALL_DIR ${CMAKE_INSTALL_FULL_DATADIR}/${PROJECT_NAME})
add_executable(cppgir tools/cppgir.cpp
tools/genbase.cpp tools/genbase.hpp
tools/genns.cpp tools/genns.hpp
tools/genutils.cpp tools/genutils.hpp
tools/function.cpp tools/function.hpp
tools/repository.cpp tools/repository.hpp
tools/common.hpp)
target_link_libraries(cppgir Boost::boost)
if (UNIX)
# add fixed fallback search places
target_compile_definitions(cppgir PRIVATE
-DGI_GIR_DIR=${GIR_DIR}
-DGI_DATA_DIR=${CMAKE_INSTALL_FULL_DATADIR}/gir-1.0
)
if (GIR_DEFAULT_DIRS)
target_compile_definitions(cppgir PRIVATE
-DDEFAULT_GIRPATH=${GIR_DEFAULT_DIRS}
)
endif ()
endif ()
if (BUILD_EMBED_IGNORE)
# generate embedded ignore data
configure_file(tools/ignore.hpp.in tools/ignore.hpp @ONLY)
target_include_directories(cppgir PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/tools)
set(GENERATED_IGNORE "")
else()
target_compile_definitions(cppgir PRIVATE
-DDEFAULT_IGNORE_FILE=${GI_IGNORE_FILE_INSTALL_DIR}/${GI_IGNORE_FILE}:${GI_IGNORE_FILE_INSTALL_DIR}/${GI_IGNORE_FILE_PLATFORM})
set(GENERATED_IGNORE --ignore ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE}:${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM})
endif()
if (USE_FMTLIB)
target_link_libraries(cppgir ${FORMAT_LIBRARIES})
set_property(TARGET cppgir PROPERTY CXX_STANDARD 17)
else()
set_property(TARGET cppgir PROPERTY CXX_STANDARD 20)
endif()
if (LINK_STDFS)
# some older gcc might sometimes (?) need this, even in c++17 mode
# see issue #80
target_link_libraries(cppgir stdc++fs)
endif ()
add_executable(CppGir::cppgir ALIAS cppgir)
endif () # BUILD_TOOLS
add_library(gi INTERFACE)
target_include_directories(gi INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/override>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/override>"
)
add_library(CppGir::gi ALIAS gi)
if (INTERNAL_EXPECTED)
set(EXPECTED_LITE_INCLUDE "expected-lite/include")
if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${EXPECTED_LITE_INCLUDE}/nonstd/expected.hpp)
target_include_directories(gi INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/${EXPECTED_LITE_INCLUDE}>"
)
else ()
message (FATAL_ERROR "missing submodule expected-lite")
endif ()
else ()
find_package(expected-lite)
if (expected-lite_FOUND)
target_link_libraries(gi INTERFACE nonstd::expected-lite)
else ()
target_compile_features(gi INTERFACE cxx_std_23)
message (WARNING "thus cppgir headers will require C++23")
endif ()
endif ()
if (BUILD_TOOLS)
if (BUILD_EXAMPLES OR BUILD_TESTING)
pkg_check_modules(GOBJECT gobject-2.0)
endif ()
if (BUILD_EXAMPLES)
pkg_check_modules(GIO gio-2.0 gio-unix-2.0)
pkg_check_modules(GST gstreamer-1.0)
if (GTK_MAJOR STREQUAL "GTK3")
pkg_check_modules(GTK gtk+-3.0)
set(GTK_GIR Gtk-3.0)
endif ()
if (GTK_MAJOR STREQUAL "GTK4")
# only plain gio is a dependency, but GIR requires other gio parts
pkg_check_modules(GTK gtk4 gtk4-unix-print gio-unix-2.0)
set(GTK_GIR Gtk-4.0)
endif ()
endif ()
## TEST ##
if (BUILD_TESTING AND GOBJECT_FOUND)
add_library(ltest
test/test_object.c test/test_object.h test/test_boxed.c test/test_boxed.h)
target_include_directories(ltest PUBLIC "gi" "override")
target_link_libraries(ltest gi ${GOBJECT_LDFLAGS})
target_compile_options(ltest PUBLIC ${GOBJECT_CFLAGS})
add_executable(gi-test test/main.cpp)
target_link_libraries(gi-test PRIVATE ltest)
add_test(NAME gi-test COMMAND gi-test)
# a C++17 version of the above
add_executable(gi-test-17 test/main.cpp)
target_link_libraries(gi-test-17 PRIVATE ltest)
set_property(TARGET gi-test-17 PROPERTY CXX_STANDARD 17)
add_test(NAME gi-test-17 COMMAND gi-test-17)
endif ()
## EXAMPLES ##
# generated wrappers' dir
set (GENERATED_DIR_DEFAULT "/tmp/gi")
set (EXAMPLES_LIBS "")
if (NOT GENERATED_DIR)
set (GENERATED_DIR ${GENERATED_DIR_DEFAULT})
endif ()
# arguments to generator
if (NOT DEFINED GENERATED_ARGS_DEFAULT)
set(GENERATED_ARGS_DEFAULT --class --class-full --call-args 0 --basic-collection)
endif ()
if (NOT DEFINED GENERATED_ARGS_EXTRA)
set(GENERATED_ARGS_EXTRA "")
endif ()
set (GENERATED_ARGS ${GENERATED_ARGS_DEFAULT} ${GENERATED_ARGS_EXTRA})
set(EXAMPLE_TARGETS "")
set(EXAMPLE_NS "")
# if both --dl and --expected are enabled,
# all function return value become gi::result<>
# to keep examples straight and simple,
# only few of them are adjusted to handle that scenario
set(PLAIN_API ON)
if ("${GENERATED_ARGS}" MATCHES "--dl")
set (EXAMPLES_LIBS ${CMAKE_DL_LIBS})
if ("${GENERATED_ARGS}" MATCHES "--expected")
set(PLAIN_API OFF)
endif ()
endif ()
if (BUILD_EXAMPLES)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-mxgot" NEEDS_LARGE_GOT)
endif ()
if (GOBJECT_FOUND)
add_executable(example-gobject EXCLUDE_FROM_ALL examples/gobject.cpp)
target_compile_options(example-gobject PRIVATE ${GOBJECT_CFLAGS})
target_link_libraries(example-gobject PRIVATE ${GOBJECT_LDFLAGS})
set_property(TARGET example-gobject PROPERTY CXX_STANDARD 14)
message(STATUS "adding GObject example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gobject)
set(EXAMPLE_NS ${EXAMPLE_NS} GObject-2.0)
endif ()
if (GIO_FOUND)
add_executable(example-gio EXCLUDE_FROM_ALL examples/gio.cpp)
target_compile_options(example-gio PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio PRIVATE ${GIO_LDFLAGS})
set_property(TARGET example-gio PROPERTY CXX_STANDARD 17)
message(STATUS "adding Gio communication example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio)
endif ()
if (GIO_FOUND)
add_executable(example-gio-dbus-client EXCLUDE_FROM_ALL examples/gio-dbus-client.cpp)
target_compile_options(example-gio-dbus-client PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-dbus-client PRIVATE ${GIO_LDFLAGS})
message(STATUS "adding Gio dbus example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-dbus-client)
set(EXAMPLE_NS ${EXAMPLE_NS} Gio-2.0)
endif ()
if (GIO_FOUND AND PLAIN_API)
find_package(Boost 1.65 COMPONENTS fiber)
if (Boost_FOUND)
# no import target; multiple calls do not override first call targets
add_executable(example-gio-async EXCLUDE_FROM_ALL examples/gio-async.cpp)
target_include_directories(example-gio-async PRIVATE ${Boost_INCLUDE_DIRS})
target_compile_options(example-gio-async PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-async PRIVATE ${GIO_LDFLAGS} ${Boost_LIBRARIES})
message(STATUS "adding Gio async example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-async)
else ()
set(GIO_ASYNC_EXAMPLE_TARGET "")
message(STATUS "disabling Gio async example")
endif ()
endif ()
if (GIO_FOUND AND HAVE_CORO AND PLAIN_API)
add_executable(example-gio-async-co EXCLUDE_FROM_ALL examples/gio-async-co.cpp)
target_compile_options(example-gio-async-co PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-async-co PRIVATE ${GIO_LDFLAGS})
set_property(TARGET example-gio-async-co PROPERTY CXX_STANDARD 20)
message(STATUS "adding Gio async coroutine example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-async-co)
set(EXAMPLE_NS ${EXAMPLE_NS} Gio-2.0)
endif ()
if (GST_FOUND AND PLAIN_API)
add_executable(example-gst EXCLUDE_FROM_ALL examples/gst.cpp)
# add generated files
foreach (GENSRC IN ITEMS ${GENERATED_DIR}/glib/glib.cpp
${GENERATED_DIR}/gst/gst.cpp ${GENERATED_DIR}/gobject/gobject.cpp)
target_sources(example-gst PRIVATE ${GENSRC})
set_property(SOURCE ${GENSRC} PROPERTY GENERATED true)
endforeach ()
target_link_libraries(example-gst PRIVATE ${GST_LDFLAGS})
target_compile_options(example-gst PRIVATE ${GST_CFLAGS})
# a lot of class methods are wrapped these days, so there is some overlap
# however, no class implementation is used here, so arrange for suppression
target_compile_definitions(example-gst PRIVATE GI_CLASS_IMPL_PRAGMA=1)
set_property(TARGET example-gst PROPERTY CXX_STANDARD 14)
if (NEEDS_LARGE_GOT)
set_property(SOURCE ${GENERATED_DIR}/gst/gst.cpp APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
message(STATUS "adding Gst example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gst)
set(EXAMPLE_NS ${EXAMPLE_NS} Gst-1.0)
function(add_gst_module_example TARGET_NAME)
add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL examples/gst.cpp)
# add generated files
foreach (GENSRC IN ITEMS ${GENERATED_DIR}/gobject/gobject.cppm
${GENERATED_DIR}/gmodule/gmodule.cppm ${GENERATED_DIR}/gst/gst.cppm)
target_sources(${TARGET_NAME} PRIVATE FILE_SET CXX_MODULES
BASE_DIRS ${GENERATED_DIR} FILES ${GENSRC})
set_property(SOURCE ${GENSRC} PROPERTY GENERATED true)
endforeach ()
target_link_libraries(${TARGET_NAME} PRIVATE ${GST_LDFLAGS})
target_compile_options(${TARGET_NAME} PRIVATE ${GST_CFLAGS})
target_compile_definitions(${TARGET_NAME} PRIVATE USE_GI_MODULE=1 GI_CLASS_IMPL_PRAGMA=1)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_SCAN_FOR_MODULES ON)
if (NEEDS_LARGE_GOT)
set_property(SOURCE ${GENERATED_DIR}/gst/gst.cppm APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
message(STATUS "adding Gst module example ${TARGET_NAME}")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} ${TARGET_NAME} PARENT_SCOPE)
endfunction ()
# (separate) module variant, similar to case above
# GCC fails to link this properly :-( (missing symbols in object files)
if (CMAKE_VERSION GREATER_EQUAL 3.28 AND BUILD_EXAMPLES_MODULES AND
(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR BUILD_EXAMPLES_FORCE))
add_gst_module_example(example-gst-mod)
add_gst_module_example(example-gst-mod-extern)
target_compile_definitions(example-gst-mod-extern PRIVATE GI_MODULE_EXTERN=1)
endif ()
endif ()
if (GTK_FOUND AND PLAIN_API)
add_executable(example-gtk EXCLUDE_FROM_ALL examples/gtk.cpp examples/gtk-obj.cpp)
target_compile_options(example-gtk PRIVATE ${GTK_CFLAGS})
target_link_libraries(example-gtk PRIVATE ${GTK_LIBRARIES})
target_compile_options(example-gtk PRIVATE -DEXAMPLES_DIR=${CMAKE_CURRENT_LIST_DIR}/examples)
if (NEEDS_LARGE_GOT)
set_property(SOURCE examples/gtk-obj.cpp APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
# sanity check
if (NOT GTK_GIR)
message(FATAL_ERROR "unknown Gtk GIR")
endif ()
message(STATUS "adding Gtk example ${GTK_GIR}")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gtk)
set(EXAMPLE_NS ${EXAMPLE_NS} ${GTK_GIR})
function(add_gtk_module_example TARGET_NAME)
add_executable(${TARGET_NAME} EXCLUDE_FROM_ALL)
target_sources(${TARGET_NAME} PRIVATE examples/gtk.cpp
PRIVATE FILE_SET CXX_MODULES BASE_DIRS ${GENERATED_DIR}
FILES ${GENERATED_DIR}/gtk/gtk_rec.cppm)
target_compile_options(${TARGET_NAME} PRIVATE ${GTK_CFLAGS})
target_link_libraries(${TARGET_NAME} PRIVATE ${GTK_LIBRARIES})
target_compile_options(${TARGET_NAME} PRIVATE -DUSE_GI_MODULE=1
-DEXAMPLES_DIR=${CMAKE_CURRENT_LIST_DIR}/examples)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 20)
set_property(TARGET ${TARGET_NAME} PROPERTY CXX_SCAN_FOR_MODULES ON)
set_property(SOURCE ${GENERATED_DIR}/gtk/gtk_rec.cppm PROPERTY GENERATED ON)
if (NEEDS_LARGE_GOT)
set_property(SOURCE ${GENERATED_DIR}/gtk/gtk_rec.cppm APPEND PROPERTY COMPILE_OPTIONS "-mxgot")
endif ()
message(STATUS "adding Gtk recursive module example ${TARGET_NAME}")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} ${TARGET_NAME} PARENT_SCOPE)
endfunction ()
# (recursive) module variant, similar to non-module case above
if (CMAKE_VERSION GREATER_EQUAL 3.28 AND BUILD_EXAMPLES_MODULES)
add_gtk_module_example(example-gtk-mod)
add_gtk_module_example(example-gtk-mod-extern)
target_compile_definitions(example-gtk-mod-extern PRIVATE GI_MODULE_EXTERN=1)
endif ()
endif ()
# optional Qt example
if (BUILD_EXAMPLES)
find_package(Qt5Core 5.9)
endif ()
if (Qt5Core_FOUND AND GIO_FOUND AND PLAIN_API)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_executable(example-gio-qt-async EXCLUDE_FROM_ALL examples/gio-qt-async.cpp)
target_compile_options(example-gio-qt-async PRIVATE ${GIO_CFLAGS})
target_link_libraries(example-gio-qt-async PRIVATE ${GIO_LDFLAGS} Qt5::Core)
set_target_properties(example-gio-qt-async PROPERTIES AUTOMOC ON)
message(STATUS "adding Qt GIO async example")
set(EXAMPLE_TARGETS ${EXAMPLE_TARGETS} example-gio-qt-async)
endif ()
add_custom_command(OUTPUT ${GENERATED_DIR}
COMMENT "Generating wrapper code for: ${EXAMPLE_NS}"
DEPENDS cppgir ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE} ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM}
COMMAND cppgir ${GENERATED_ARGS} ${GENERATED_IGNORE}
--output ${GENERATED_DIR} ${EXAMPLE_NS}
COMMAND cmake -E touch_nocreate ${GENERATED_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
# example might have been added if dependencies found
# make sure to disable as needed
if (NOT BUILD_EXAMPLES)
set(EXAMPLE_TARGETS "")
endif ()
message(STATUS "example programs: ${EXAMPLE_TARGETS}")
add_custom_target(examples)
if (EXAMPLE_TARGETS)
add_dependencies(examples ${EXAMPLE_TARGETS})
endif ()
add_custom_target(wrappers DEPENDS ${GENERATED_DIR})
foreach (example ${EXAMPLE_TARGETS})
target_link_libraries(${example} PRIVATE gi ${EXAMPLES_LIBS})
target_include_directories(${example} PRIVATE ${GENERATED_DIR})
target_compile_options(${example} PRIVATE ${BUILD_EXAMPLES_OPTIONS})
add_dependencies(${example} wrappers)
endforeach ()
## INSTALL ##
# manpage processor
find_program(RONN ronn DOC "ronn markdown man page processor")
if (${RONN} STREQUAL "RONN-NOTFOUND")
message(STATUS "ronn manpage processor not found; not building manpage")
elseif (BUILD_DOC)
message(STATUS "building manpage")
add_custom_command(OUTPUT cppgir.1
COMMAND ${RONN} --roff --pipe ${CMAKE_CURRENT_LIST_DIR}/docs/cppgir.md > cppgir.1
DEPENDS docs/cppgir.md
WORKING_DIRECTORY .)
add_custom_target(manpages ALL DEPENDS cppgir.1)
endif()
endif () # BUILD_TOOLS
# headers
install(DIRECTORY gi override
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
if (INTERNAL_EXPECTED)
install(DIRECTORY ${EXPECTED_LITE_INCLUDE}/nonstd
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/gi)
endif ()
# doc
install(FILES README.md docs/cppgir.md
DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(DIRECTORY examples
DESTINATION ${CMAKE_INSTALL_DOCDIR}
PATTERN external EXCLUDE)
if (TARGET manpages)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cppgir.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif()
# pkgconfig
set(PKG_CONFIG "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc")
set(PKG_CONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
# configure pkg config file
configure_file("cmake/cppgir.pc.in" "${PKG_CONFIG}" @ONLY)
install(FILES "${PKG_CONFIG}"
DESTINATION "${PKG_CONFIG_INSTALL_DIR}")
# cmake EXPORTS
set(CONFIG_PACKAGE_LOCATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
set(CONFIG_VERSION_NAME ${PROJECT_NAME}-config-version.cmake)
set(CONFIG_TARGETS_NAME ${PROJECT_NAME}-targets.cmake)
set(CONFIG_NAME ${PROJECT_NAME}-config.cmake)
if (INTERNAL_EXPECTED OR NOT expected-lite_FOUND)
set(CONFIG_NAME_IN ${CONFIG_NAME})
else ()
set(CONFIG_NAME_IN ${PROJECT_NAME}-config-deps.cmake)
endif ()
set(TARGETS_EXPORT_NAME CppGirTargets)
if (BUILD_TOOLS)
# generator
install(TARGETS cppgir
DESTINATION ${CMAKE_INSTALL_BINDIR}
EXPORT "${TARGETS_EXPORT_NAME}")
# ignore file
if (NOT BUILD_EMBED_IGNORE)
install(FILES ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE} ${GI_IGNORE_FILE_DIR}/${GI_IGNORE_FILE_PLATFORM}
DESTINATION ${GI_IGNORE_FILE_INSTALL_DIR})
endif()
endif () # BUILD_TOOLS
# headers
install(TARGETS gi EXPORT "${TARGETS_EXPORT_NAME}")
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_VERSION_NAME}"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
ARCH_INDEPENDENT
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_VERSION_NAME}"
DESTINATION ${CONFIG_PACKAGE_LOCATION}
)
install(FILES cmake/${CONFIG_NAME_IN} RENAME ${CONFIG_NAME}
DESTINATION ${CONFIG_PACKAGE_LOCATION}
)
export(EXPORT ${TARGETS_EXPORT_NAME}
FILE "${CMAKE_CURRENT_BINARY_DIR}/cmake/${CONFIG_TARGETS_NAME}"
NAMESPACE CppGir::
)
install(EXPORT ${TARGETS_EXPORT_NAME}
FILE ${CONFIG_TARGETS_NAME}
NAMESPACE CppGir::
DESTINATION ${CONFIG_PACKAGE_LOCATION}
)
# uninstall target;
# intermediate directories are not removed though
if(NOT TARGET uninstall)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
IMMEDIATE @ONLY)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
endif()

21
cmake/external/glib/cppgir/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Mark Nauwelaerts
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

221
cmake/external/glib/cppgir/README.md vendored Normal file
View File

@@ -0,0 +1,221 @@
# cppgir
`cppgir` is a [GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection)
C++ binding wrapper generator. That is, it processes `.gir` files derived
from GObject-Introspection annotations into a set of C++ files defining
suitable namespaces, classes and other types that together from a C++ binding.
In this way, the plain C libraries and objects become available as native
objects along with (RAII) managed resource handling. The generated code
only requires a C++14 compiler and library (as well as obviously the underlying
C headers and libraries that are being wrapped).
## Installation
The code generator can be built using [CMake](https://cmake.org) or
[meson](https://mesonbuild.com/) and requires
Boost and either [fmtlib](http://fmtlib.net/) or a C++20 std library with format
support (e.g. gcc 13+). While it obviously depends on distribution,
these may be typically be obtained by installing following packages;
libfmt-dev libboost-dev
If it is required to compile the manual page ronn is required to be installed
The following step will provide an additional dependency as a submodule;
git submodule update --init
With all that in place, the usual steps apply, so e.g.
mkdir build
cd build
cmake ..
cmake --build .
cmake --install .
or alternatively
mkdir build
cd build
meson setup . ..
meson compile
meson install
The installed components consist of:
* the code generator executable
* (if not embedded by build option) default ignore files
* common headers required by the generated code and default overrides
The latter is needed when using the generated code, the former only
at generation time.
In case of CMake build, it is also possible to build some examples whose
sources are also installed along with some documentation.
As such, the meson system only builds and install the (most) relevant parts,
as may be useful when used as a subproject.
For either build system, an [example](examples/external) shows how `cppgir`
can be used in a project.
## Release numbering and API stability
The generated code for an even-numbered major version should be (compile) API
stable. If the major version turns odd, then a cycle/series of incompatible
changes may occur, which after actual practice and time can then become
the next even stable version.
## Running
All details can be found in the [manpage](docs/cppgir.md), but a simple
example wrapping [GStreamer](https://gstreamer.freedesktop.org/) libraries is
as follows:
generate --output /tmp/gi GStreamer-1.0
The above (obviously) assumes that suitable `.gir` files are present in
the usual location, as typically installed by a `-dev` package. It is
also assumed the generator has been installed (so "running installed").
Running the generator uninstalled is also possible, but then (if not embedded) the
default ignore files in `data` directory have to explicitly specified as well
(using either option or environment variable as specified in manual).
That's it, all wrapping code is now available in `/tmp/gi` (in a number of
subdirectories). It only requires a C++14 compiler and library and can be
included and compiled into the target library or executable. The latter then
has no additional dependencies other than the plain C libraries (gstreamer and
lower level ones in this case).
## Compilation
The wrapping binding code presents an API along the lines of any other binding,
which also means it maps pretty straight onto the original library API.
So all API is simply found where expected. To use it fully inline,
the following include snippet suffices:
#define GI_INLINE 1
#include <gst/gst.hpp>
Of course, the include path must have been setup properly to contain the output
root of the generated code (i.e. `/tmp/gi` in previous example). It must also
contain the proper path for header code supplied by this repo (using e.g. a
pkgconfig fragment if it has been installed, or an IMPORT target or by other
means).
See the [manpage](docs/cppgir.md) for additional essential remarks on the API
of the generated code or typical non-inline use of the code.
## Examples
The examples (in the so-named directory) illustrate and cover various practical
aspects and use of the generated API. While they are all simple and trivial,
they do illustrate use in various domains such as `Gio`, `Gst` and `Gtk`.
For example, when it comes to traditional C `libgio`, there is usually a choice
between a synchronous (blocking) call and a corresponding asynchronous
(non-blocking) call. The latter is then typically used to establish a chain of
completion handlers. In other settings and languages, it has become customary
to employ async/await operations/keywords. That way, the flow of code remains
linear as in the blocking case. However, some form of callback chain is
typically present behind the (implementation) scenes and a more practical
consequence is that async/await keywords need to be sprinkled in quite some
places (down to call stack depth). That, in turn, is then intrusive/disruptive
in its own way.
Alternatively, one can use so-called stackful co-routines or fibers to maintain
stack context when execution has to be suspended (e.g. awaiting I/O, or some
condition). An example implementation is provided by [Boost
Fiber](https://www.boost.org/doc/libs/latest/libs/fiber/doc/html/index.html)
which caters for (cooperative) switching between multiple fibers on a single
thread. This is viable in C++ since such code should be exception-safe anyway
and as such should not be taken by surprise upon code execution not making it
all the way through a code sequence. After all, that might happen at any time
upon exception, as in the particular case of fiber context destruction in stead
of regular fiber termination. This approach also allows using legacy code in
multiple fibers on a single thread, since no additional synchronization is
required, and no special support (or keyword sprinkling) is needed along the
callstack (down to point of suspension). To come down to it, the async Gio
example provides a `GMainContext` based `Fiber` scheduler to integrate and
support using Fiber in a standard GLib mainloop setup. In particular, that
allows turning `Gio`'s async calls into (apparent) blocking calls when run on a
fiber, which then leads to a concise sequential code flow.
## Features
A few simple but nice to have features of the generated code are:
* well structured and easily understood
* it can be used either fully inline or have implementation code compiled
into one or more object files for subsequent linking
* it allows for extensions/overrides (similar to e.g. PyGObject)
supplied by separate files (so not in the generated ones),
either default ones supplied along with this repo or added custom by
the project using the generated code
Another feauture might be handy even if one is not interested in generated
C++ code. As the `.gir` files as processed, a number of consistency checks
are performed and warned about. In that way, the generator also acts somewhat
as annotation validator, and can typically spot some missing `(out)` or
`(array)` (and related) annotations.
Some other features have been implicitly mentioned above, but are perhaps best
high-lighted by addressing how it differs from [gktmm](https://www.gtkmm.org)
(and the related question; why another C++ interface). To this end, first
note or recall that gtkmm consists of many repositories (glibmm, gtkmm,
gstreamermm, ...), all of which typically also yield a distribution
package for library code and headers. So, when using e.g. gstreamermm, several
such packages are required, either for their headers at compile time or for the
libraries at runtime. In contrast, only 1 repo is needed here, and using the
generated code then incurs no additional (runtime) dependencies other than the
(unavoidable) C libraries.
The code in gtkmm's (and friends') libraries can be considered as hand-crafted.
Well, it is produced based on templates, but those are manually maintained. It
does not use or consider the GObject-Introspection annotations in any way (not
in the least because it predates that system). So, as a C library and API
evolves, the corresponding gtkmm has to be manually synchronized. That is, if
there is at least a corresponding gtkmm one. For a not-so-well-known-or-popular
one there may not be, and definitely not for a custom developed one. On the
other hand, whenever (minimal) annotations are available, you are good to go
with any binding, whether PyGObject or the one provided here.
Since the gtkmm code is hand-crafted API from the ground up, that allows for
some nifty things (such as the signal approach mentioned further below). On the
other hand, sometimes it might be too nifty in that the C++ API does not quite
track or match the original one. Instead, it is then more of an alternative or
parallel one. For example, in GStreamer, there is (only) one (mini-object)
C-type of `GstEvent` (with a type field indicating the precise type of event).
So it is handled this way throughout the API and in bindings (e.g. PyGObject).
However, in gstreamermm it has been chosen to have many types (i.e. subclasses)
of `Gst::Event`. This may well be a natural C++ way (or a Python one if done
with Python classes), but all together it presents an API where things are not
quite where expected, and it is in that regard not a straight binding. In
contrast, the generated code here is as straight as can be, and functions to
call are right where the fingers expect them to be, whether in PyGObject or
here.
So, in the concept of "C++ binding API", gtkmm has emphasis on C++
up to the point of almost a parallel or independent API with quite some
trimming. The latter is illustrated by the separate
[libsigcplusplus](https://github.com/GNOME/libsigcplusplus) (with similar
notions in e.g. [boost signals2](https://github.com/boostorg/signals2)). Here,
however, the emphasis is on binding.
Of course, other than this straight binding, one is still free to use and bring
in any other lib, e.g. one of the aforementioned signal helper libs. However,
in most cases the "straight bindings" will suffice. Moreover, a few small
additional RAII helpers are provided that may be useful. See the examples for
an illustration on how they can be used.
## Limitations and Remarks
A compile-time binding is somewhat different than a typical more runtime script
language binding (e.g. Python). While most of the annotated API is usually
well enough handled and covered for practical use, there are some limitations
and issues to consider. Again, more details and further remarks are provided
in the [manpage](docs/cppgir.md)

View File

@@ -0,0 +1,25 @@
# from https://gitlab.kitware.com/cmake/community/-/wikis/FAQ
# short unix alternative;
# xargs rm < install_manifest.txt
if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
endif()
file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
foreach(file ${files})
message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
exec_program(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
if(NOT "${rm_retval}" STREQUAL 0)
message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
endif()
else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
endif()
endforeach()

View File

@@ -0,0 +1,29 @@
#include <coroutine>
struct promise;
struct coroutine : std::coroutine_handle<promise>
{
using promise_type = ::promise;
};
struct promise
{
coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
static coroutine
f()
{
co_return;
}
int
main(int argc, char **argv)
{
return 0;
}

View File

@@ -0,0 +1,7 @@
#include <format>
int
main(int argc, char **argv)
{
(void)std::format("");
return 0;
}

View File

@@ -0,0 +1,4 @@
include(CMakeFindDependencyMacro)
find_dependency(expected-lite)
include("${CMAKE_CURRENT_LIST_DIR}/cppgir-targets.cmake")

View File

@@ -0,0 +1 @@
include("${CMAKE_CURRENT_LIST_DIR}/cppgir-targets.cmake")

View File

@@ -0,0 +1,9 @@
prefix=@CMAKE_INSTALL_PREFIX@
includedir=${prefix}/include/@PROJECT_NAME@
Name: @PROJECT_NAME@
Description: GObject Introspection C++ wrapper generator.
Version: @PROJECT_VERSION@
# unfortunately, no pkg-config on nonstd-expected
# so no Requires if used as external dependency
Cflags: -I${includedir} -I${includedir}/override

72
cmake/external/glib/cppgir/conanfile.py vendored Normal file
View File

@@ -0,0 +1,72 @@
import os
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
class CppGirConan(ConanFile):
version = "2.0.0"
name = "cppgir"
description = "gobject-introspection C++ binding generator"
license = "MIT Software License"
url = "https://gitlab.com/mnauw/cppgir.git"
exports_sources = "data/*", "docs/*", "expected-lite/*", \
"gi/*", "override/*", "tools/*", "test/*", \
"examples/*", "!examples/external/*", \
"CMakeLists.txt", "cmake/*", "LICENSE", "README.md"
# ignore data depends on OS
settings = "os", "compiler", "build_type", "arch"
build_policy = "missing"
author = "Mark Nauwelaerts"
test_package_folder = "examples/external"
options = {"header_only": [True, False]}
# default host dependency case
default_options = {"header_only": True}
def requirements(self):
if not self.options.header_only:
self.requires("boost/[>=1.58]", options={'header_only': True})
self.requires("fmt/[>=8.1.1]")
def layout(self):
cmake_layout(self, build_folder="build.conan")
# adjust for editable
self.cpp.source.includedirs = ["."]
# self.cpp.build.bindir = "."
def generate(self):
tc = CMakeToolchain(self)
tc.variables["BUILD_EXAMPLES"] = False
tc.variables["BUILD_DOC"] = False
# cmake --install relocation does not play well with ignore paths
# so have those compiled in
tc.variables["BUILD_EMBED_IGNORE"] = True
tc.variables["BUILD_TOOLS"] = not self.options.header_only
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
if not self.conf.get("tools.build:skip_test", default=False):
test_folder = os.path.join(".")
# test depends on GLib, which may not be present
for t in ["gi-test", "gi-test-17"]:
test = os.path.join(test_folder, t)
if os.path.exists(test):
self.run(test)
def package(self):
"""Run CMake install"""
cmake = CMake(self)
cmake.install()
def package_info(self):
self.cpp_info.includedirs = ["include/cppgir"]
def package_id(self):
# options access not allowed here, unfortunately
if False and self.options.header_only:
self.info.clear()

View File

@@ -0,0 +1,181 @@
# generic
.*_unref
.*_free
.*_ref
.*_ref_sink
# GLib
GLib:constant:LOG_DOMAIN
GLib:record:Error
# deprecated
GLib:function:g_assert_warning
GLib:function:g_slice_.et_config
GLib:function:g_variant_get_gtype
GLib:function:g_thread_init_with_errorcheck_mutexes
# GVariantIter apparently represents 2 distinct types;
# + a C-boxed type (used with _init)
# + a heap-based behaving like GBoxed (with _copy and _free)
# however, annotations do not cover/mention anything of the latter
# so drop those parts here
GLib:method:g_variant_iter_new
GLib:method:g_variant_iter_copy
# too tricky with generic signature
GLib:method:g_source_set_callback.*
# annotation problem
GLib:function:g_strv_contains.*
GLib:function:g_unichar_to_utf8
# not needed; cause trouble otherwise
#GLib:record:ByteArray
#GLib:record:Bytes
GLib:record:PtrArray
GLib:record:Array
GLib:record:S?List
GLib:record:HashTable
GLib:record:HashTableIter
GLib:record:Queue
# annotation problem
GLib:VariantType:string_scan
# conditional constants
GLib:constant:macro__has_attribute.*
GLib:constant:.*GETTEXT_DOMAIN
GLib:constant:WIN32.*
# not defined for C++ compiler
GLib:constant:.*C_STD_VERSION*
# GObject
GObject:record:Value
# deprecated
GObject:record:ValueArray
# GIO
# deprecated
Gio:interface:DesktopAppInfoLookup
Gio:method:g_notification_set_urgent
Gio:method:g_settings_list_keys
# annotation problem
Gio:virtual-method:ask_question
# not covered by header includes
Gio:function:g_networking_init
# external plugin module API
Gio:method:g_io_module_(load|unload)
Gio:function:g_io_module_query
# private parts of the above; these should not make into the GIRs
# but they might if gobject-introspection was built with embedded glib (meson wrapper)
GModule:constant:MODULE_IMPL_.*
GLib:constant:TRACE_.*
GLib:function:trace_.*
GLib:function:set_prgname_once
Gio:function:to_rrtype
Gio:class:ThreadedResolver
GObject:bitfield:IOCondition
# Gst
Gst:constant:ERROR_SYSTEM
Gst:callback:DebugFuncPtr
# GstBase
# actually macros, but with gtk-doc comment
GstBase:method:gst_byte_writer_put_buffer
GstBase:method:gst_bit_writer_get_remaining
# missing G_BEGIN_DECLS in video-blend.h header in some versions
# (leads to C/C++ link mismatch)
GstVideo:function:gst_video_blend_scale_linear_RGBA
GstVideo:function:gst_video_blend
# likewise missing G_BEGIN_DECLS in gstaudioiec61937.h
GstAudio:function:gst_audio_iec61937_frame_size
GstAudio:function:gst_audio_iec61937_payload
# GstNtpClock is child of GstNetClientClock
# but it uses the same C struct type
GstNet:class:NtpClock
# Gtk and lower layers
#
# deprecated
GdkPixbuf:record:Pixdata.*
GdkPixbuf:bitfield:Pixdata.*
GdkPixbuf:constant:PIXBUF_MAGIC_NUMBER
GdkPixbuf:constant:PIXDATA_HEADER_LENGTH
# wrong annotation
cairo:function:image_surface_create
# repeated as GdkRectangle
cairo:record:RectangleInt
# from generated code
Pango:record:ScriptForLang
# in a header without extern C guard
Atk:function:get_major_version
Atk:function:get_minor_version
Atk:function:get_micro_version
Atk:function:get_binary_age
Atk:function:get_interface_age
# private
Gdk:method:destroy_notify
Gdk:function:synthesize_window_state
# xlib GIR does not specify header
# and including that one makes things really messy
# (due to all sorts of define's)
xlib:.*
# recent GIR describes way more than it specifies headers
# also pretty low level, so let's sidestep altogether
HarfBuzz:.*
# likwise so for freetype2
freetype2:.*
# Gsk
# deprecated
Gsk:class:GLRenderer
# in separate gtk4-broadway.pc
Gsk:class:BroadwayRenderer
# likewise filter out some related GtkX parts
# (should be in a separate ns btw for good measure)
Gtk:class:Plug
Gtk:class:Socket
# private
Gtk:method:gtk_widget_path_iter_add_qclass
# missing in summary header gtk-a11y.h
Gtk:class:HeaderBarAccessible
Gtk:class:FileChooserWidgetAccessible
# generated dbus sekeleton parts made it into GIR but otherwise missing
Gtk:record:_MountOperation.*
# Gtk4
# header not part of included headers
Gtk:constant:IM_MODULE_EXTENSION_POINT_NAME
# Gtk 4.9.1 deprecates TreeView and Cell Renderers
# alternatives appear not so binding-friendly
# so arrange to not discard these
deprecated:Gtk:4.0
# misc config; specify substitute c:type for GtkSnapshot (defined in override)
#!ctype:Gtk.Snapshot:GI_PATCH_GtkSnapshot
# signal emission method wrappers
# undocumented; and should not be wrapped either
# (with some problematic parameter types)
Soup:method:soup_message_content_sniffed
Soup:method:soup_message_wrote_chunk
Soup:method:soup_message_wrote_headers
Soup:method:soup_message_wrote_body
Soup:method:soup_message_wrote_informational
Soup:method:soup_message_got_chunk
Soup:method:soup_message_got_headers
Soup:method:soup_message_got_body
Soup:method:soup_message_got_informational
Soup:method:soup_message_starting
Soup:method:soup_message_restarted
Soup:method:soup_message_finished
# deprecated interface since 13 years
Soup:interface:PasswordManager
# private
Soup:function:soup_get_resource
# unstable API
Soup:class:Requester$
Soup:method:soup_websocket_extension.*
Soup:enumeration:RequesterError

View File

@@ -0,0 +1,5 @@
# items to ignore on UNIX platform
GLib:.*:.*WIN32.*
GLib:.*:g_win32.*
GLib:enumeration:Win32.*

View File

@@ -0,0 +1,3 @@
# items to ignore on WIN platform
GLib:.*:.*g_unix.*

View File

@@ -0,0 +1,791 @@
cppgir(1) - GObject-Introspection C++ binding wrapper generator
==================================================================
## SYNOPSIS
`cppgir` [OPTION...] `--output` _DIRECTORY_ GIR...
## DESCRIPTION
`cppgir` reads each of the specified GIR and converts these (and any
dependencies) into C++14 wrapper code that collectively then make up a
'binding' (in
[GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection)
terminology). Each GIR can be specified as a full pathname to the `.gir` file
or simply by the basename (i.e. no path or `.gir` suffix), with or without
version. Of course, in the latter case, the `.gir` must be in a standard
location, or other options must specify additional whereabouts.
## OPTIONS
See [BACKGROUND](#background) later on for further details on some of the
concepts used in the following descriptions.
* `--output` _DIRECTORY_:
Specifies the top-level directory in which to generate code.
It will be created if it does not yet exist.
* `--gir-path` _PATHS_:
Adds a colon-separated list of additional directories within which
to (recursively) search for a `.gir` file (if not specified by full pathname).
* `--debug` _LEVEL_:
Debug level or level of verbosity, higher numbers are more verbose.
* `--ignore` _FILES_:
Adds a colon-separated list of so-called ignore files.
* `--suppression` _FILES_:
Adds a colon-separated list of so-called suppression files.
* `--gen-suppression` _FILE_:
Specifies a suppression file to generate during this run.
* `--class`:
Requests generation of implementation class code needed for subclassing.
* `--class-full`:
Requests generation of a plain as-is C signature fall-back method for
an otherwise unsupported unwrapped method. Only applicable if
`--class` is also specified. It also requires use of the latest custom
subclass (signature) approach (see below for details on that),
as these plain methods are not "activated" in case of legacy approach
(for backwards compatibility).
* `--expected`:
Use an error return type based on [std::expected](http://wg21.link/p0323) proposal
(as opposed to throwing exception).
* `--dl`:
Use dlopen/dlsym to generate (most) calls rather than usual "direct" calls.
As such, a great many calls might then fail at runtime. So, if combined
with `--expected` all those calls will use the above error return type.
* `--const-method`:
Mark (almost) all generated methods in generated wrappers as `const`.
Alternatively, perhaps more recommended, see also the helper `gi:cs_ptr` type.
* `--class-args` _MIN_OPTIONAL_:
If >= 0, minimum number of non-required arguments that triggers generation of a
`CallArgs` signature variant (see below for details).
* `--basic-container`:
Also generate a collection signature for an input collection of basic type
(e.g. `int`, etc). See below for some details and discussion.
* `--output-top`:
Also generate convenience `.cpp` and `.hpp` files in root output directory
per namespace, as may be useful for some build tool setups.
* `--dump-ignore`:
(only if compiled with embedded ignore) Dumps embedded ignore data.
## ENVIRONMENT
In stead of command-line options, environment variables can also be used. Note,
however, that options are still taken into account even when variables have
been set. The following environment variables are considered, and have the same
meaning as the corresponding command-line option:
`GI_DEBUG`, `GI_IGNORE`, `GI_SUPPRESSION`, `GI_GEN_SUPPRESSION`, `GI_OUTPUT`,
`GI_CLASS`, `GI_CLASS_FULL`, `GI_EXPECTED`, `GI_DL`, `GI_GIR_PATH`
In addition to the above, `GI_GIR` can specify a colon-separated lists of GIRs
(specified as on command-line). `XDG_DATA_DIRS` is also used as additional
source of directories to search for GIRs (within a `gir-1.0` subdirectory).
## BACKGROUND
### API v2
Note that v2 API is somewhat different than previous API, so some porting of
existing code may be needed. See also later section for a rationale and
discussion on changes.
The generated code provides a straight binding as specified by the annotations,
so everything is pretty much where expected, such as methods within classes
in turn within namespaces. For example, all `GObject` types are within
namespace `gi::repository::GObject`. With that in mind, it should be easy
to use and navigate in generated code, along with following comments:
* As customary, anything within a `detail` or `internal` namespace is not meant
for public use and subject to change. The top-level gi namespace defines
a few things that make up public API which is meant to be stable
(though at this stage of maturity no full guarantee is provided).
* Some generated code may have `_` (underscore) appended to it simply to avoid
clashing with a reserved keyword (or a preprocessor definition). It has
no special (reserved) meaning otherwise.
* However, anything with leading underscore (if encountered) should be considered
as internal (and not meant for public API).
In overall, the generated code is very lightweight and clear, easily understood
and with little runtime overhead, as also illustrated by the following
overview of wrappers for various kinds of types. Note that almost all of
them essentially wrap a pointer and therefore should be checked for validity
prior to many uses as with any "smart pointer"
(e.g. using provided `operator bool()`).
**Objects.**
A GObject is a single pointer along with class code that manages a single
refcount (including decrement upon destruction). The refcount it manages is
either received/taken from a `full` transfer, or `ref_sink`'ed (in case of
`none`/`floating` transfer, see also discussion in subsequent section on the
intricacies of the latter and theoretical edge cases).
**Boxed Types.**
Similarly, but with a minor twist, wrappers for a boxed GType `MyBox` come in 2
kinds; an owning `MyBox` and a non-owning `MyBox_Ref`. In both cases, the
wrapper is again a single pointer with some suitable/applicable helper methods.
The former essentially acts a "unique ptr" (with `g_boxed_free` deleter) whereas
the latter acts as a "naked ptr/reference" (without any ownership or cleanup).
Obviously, for the latter case, all the usual caution regarding dangling
references (etc) applies. The latter are used for transfer `none` cases and
the former in transfer `full` situations. In case a safe "reference" needs to
be kept around (e.g. in some member), then a `_Ref` can be `.copy_()`'d (which
uses `g_boxed_copy`) to an owning wrapper. The above semantics also imply that
the owning wrapper is move-only (and again `.copy_()` yields a copy). However,
there are quite some cases where a boxed copy is based on a refcount (which also
preserves the box identity/pointer). Those cases have been specially marked (in
overrides) to make the owning wrappers copyable as well. Likewise, a `_Ref` of
such cases can be (implicitly) assigned/copied to an owning one (in each case
triggering a `g_boxed_copy` which is then known to be plain and cheap).
If desired, additional wrappers could be marked as copyable, in which case a
wrapper copy invokes a potentially more expensive (and non-identity preserving)
`g_boxed_copy`. Also, or alternatively, if `GI_ENABLE_BOXED_COPY_ALL` is
defined and truthy, then all boxed wrappers are copyable in that way.
**Record Types.**
Plain records (i.e. structs with no registered GType) are handled in a similar
fashion, with `g_free` as "deleter" (and without any copy support). Since no
lifecycle resource management (construction, destruction) is available for such
types, there are (quite some) limitations to what code generation or binding can
do here (see also discussion in corresponding section).
**Strings.**
A string (e.g. `char*`) is also regarded and wrapped in a similar way. That is,
a `gi::cstring` wraps (and owns and manages) a C `char*` and `gi::cstring_v` is
the corresponding non-owning variant. Obviously, the former bears resemblance
to `std::string` whereas the latter to `std::string_view`. In fact, as there is
no real definitive "string API" (in C or glib), their API is fairly similar
(though not guaranteed identical) to the `std` counterparts. Also, various
conversions from/to `std` counterparts should allow for convenient type
interchange. Additional integration with other string types is also possible by
further specialization of `gi::convert::converter` (see `gi/string.hpp` source
for details).
**Collections**.
That is, `GList`, `GSList`, `GPtrArray`, `GHashTable` or plain arrays
(zero-terminated or not). Similar to `std` container, each collection wrapper
is a templatized `gi::Collection` type, with (a.o.) a type parameter for the
contained type. As with some of the above types, such wrappers come in an
owning and non-owning variants, as specified by another (type) parameter and
obtained from annotations, i.e. transfer `none`, transfer `container` or transfer
`full`. Note that the "ownership" specifies both ownership of the container and
of the contained elements. Of course, where needed, code generation will select
and specify the proper type (e.g. as function parameter). Following aspects
are worth mentioning;
* Templatized constructors and conversion operators support construction
from/of and assignment from/to (e.g.) `std` container types. Likewise
so for "similar" (duck-ed) types, where "similar" refers to member types and
constructor signatures.
* A (`std`) container-ish API is also provided, though neither identical
nor fully compatible (a.o. due to limitations of the C wrappee's API).
However, the `none` (ownership) variant is considered read-only and so
it does not provide any "modification" API parts and only a `const` iterator.
As almost no wrapper methods are `const`, an `auto p : coll` (range-for)
pattern is recommended (wrappers are cheaply copied). Other variants do
support modification as well as iteration that allows for a `auto &p : coll`
pattern (if so desired). In particular, this applies to the `full` variant,
which is the recommended one for "standalone" use (as container), as it
safely manages ownership of both itself and elements.
* Wrappers of refcounted collections (`GPtrArray`, `GHashTable`) are
otherwise similar to object wrappers. So they *always* manage a refcount (and
are copyable) regardless of ownership variant (none, etc). The other wrappers
are similar to boxed wrappers, e.g. copyable in `none` variant, but otherwise
assume unique ownership and are non-copyable.
* A `gi::CollectionParameter` may also used by code generation for a function
input parameter. In case of `none` ownership, this type/instance will
temporarily hold ownership of a collection that may be created by conversion
from another container. Temporarily here refers to the duration of the call
during which the parameter instance exists. It is not (and should not be)
used elsewhere.
* For an input collection parameter of basic type (e.g. `int`), the
original C signature is typically used. That is, 2 parameters (`int*` and
`gsize`). The rationale here is that the same signature may also occur for an
output collection (of specified input size). Preserving the original signature
ensures that it can be used whether or not the annotation is correct. The
latter may not be the case as these APIs are typically "low-level" (e.g. involve
some buffers), and as such are often not considered by "scripted binding". It is
also an efficient and clear API as any buffer's "location" (e.g. `std::vector`
or otherwise) can easily be provided by means of these 2 parameters.
However, if desired and specified by `--basic-container` option, then an
additional collection-based signature is generated as well.
In short, one can choose to work with `std` types and convert to
collection wrappers upon function call/return, but for simple cases (or beyond),
the collection wrapper might well serve (without conversion).
**Plain Types.**
Various enum, (static) method, functions, typedef (for callback) fill in the
rest.
**Functions.**
Functions that involve the usual `GError` return pattern are wrapped in a few
ways. On the one hand, in a straight way, where the error is a (wrapped error)
output parameter. Alternatively, the error parameter is removed from the
signature. In that case it is "returned" by either throwing the (wrapped) error
(which is also a `std::exception` subclasss), or by returning a suitable
`expected` type (with the wrapped error type as error type). While throwing is
default behaviour, the latter can be requested using `--expected` option.
In case of a `GError` in (function) callback or virtual method signature, it is
always retained as a (wrapped) error output parameter and preferably used to
report an error that way. Alternatively, an exception can be thrown, preferably
then a `GLib::Error` instance. Callback wrapping code will catch any exception
and report (to `C` caller) using `GError` output along with a zero-initialized
return value, which is likely but not necessarily a good choice.
Note, however, that the aforementioned `catch` only applies if exception support
is enabled. Auto-detection of this should usually work, but if needed can be
specified by defining `GI_CONFIG_EXCEPTIONS` explicitly (truth/falsy).
If a function has (non-GError) output parameters, then there is a signature
where these outputs are parameters (as in the plain C case) and another variant
where these are incorporated in the return value, which may then become a
`std::tuple<>` type.
If so configured, some so-called `CallArgs` variations may also be generated. In
this case, (roughly) a custom `xyz_CallArgs` struct type is generated (for each
function `xyz`) with members corresponding to the function arguments along with
a function with 1 argument (of that custom struct type). In case of many
(optional) arguments, this argument could be specified using designated
initializer syntax, thereby allowing a sort-of "call by keyword". Again, there
are variations with output parameters as return value or not. Besides some
potential advantages, there are also some drawbacks, however. First and
foremost, when using the member names in designated initializers, these names
then become essentially part of the API, although the name's origin as
plain C function parameter does not provide stability guarantee. Also,
suffice it to say a great many struct types can be generated this way. This could
be mitigated by employing a suitable "generation level", e.g. 2. In that case,
only functions that have at least 2 (or more) non-required arguments will have
such a custom type and signature generated.
**Subclasses and Interfaces.**
Some additional specifications on how subclasses and interfaces are mapped
may also be in order. A subclass in the GObject world is directly mapped as a
subclass in the C++ binding. However, if a GObject implements an interface, the
generated class does not inherit from the interface's (generated) class. This
is mostly of a matter of implementation choice (and to ensure its lightweight
simplicity). However, knowledge of implemented interfaces is not always
available at compile time, e.g. in case of dynamically loaded GStreamer
elements (though it is more likely in case of Gtk hierarchy). Since there would
be no inheritance in the dynamic case, a consistent choice is not to have it at
any time. However, for ease of use, some helper code is generated when an
implemented interface is known at generation/compile time, as illustrated in
the following snippet from an example
// use a cast if not known, either to a class or interface
auto bin = gi::object_cast<Gst::Bin>(playbin_);
// known at compile time; overloaded interface_ method
auto cp = bin.interface_ (gi::interface_tag<Gst::ChildProxy>());
### SUBCLASS IMPLEMENTATION API
There may be times when one would want to make a custom subclass of GObject, or
of some Gtk widget. In the same vein, (current) implementation choices imply
that one should not simply inherit from `Gtk::Window`. Part of the motivation
here is that such subclassing depends on style and setting, i.e. it is rather
rare when in a GStreamer setting, but less so in e.g. Gtk. As such, the
possibly rare cases should not burden or complicate the basic wrapping usecase.
Before going into the details of "how", let's first consider what a "subclass"
actually means in this context. It will probably involve a (C++) (sub)class of
some generated class (related to a GObject class type) with potentially
extra/custom properties and signals. In particular, the latter implies it also
involves a new `GType` that defines a subtype (of the aforementioned GObject
class type). An instance of such class/type then consists of a C++ instance
that is 1-to-1 associated with a GObject instance of the custom defined (sub)
`GType`.
In turn, this leads to (at least, for now) 2 possible ways to create instances;
* triggered on C++ side, in the usual way through a constructor. The bottom
of the constructor chain registers a custom `GType` (if still needed), creates
an instance (`g_object_new()`) and associates it (with the C++ instance).
The advantage is that standard use of C++ constructor applies. The downside
is that the defined `GType` can not be (safely) used in the GObject
(eco)system, as any `g_object_new()`'d instance is incomplete (as it lacks a
C++ instance).
* triggered on C-side by `g_object_new()`. In this case, the registered type's
custom (GObject) `constructor` first delegates to parent constructor to create
a GObject subtype instance and then `new`'s a C++ object, and again the
bottom of the constructor chain associates it with created GObject instance.
In this case, an instance has to be created based on `GType`. However, this
type can be safely used in any `GType` based factory system, e.g. when
referenced (by name) in XML/UI file or (by number) in a GStreamer plugin
factory.
In summary, (up to) 2 different ways to create objects, and so (up to) 2
different custom `GType` that can be defined by a "subclass". The latter one
is more recent and is also the recommended approach as instances are always
properly created. Also, if desired, some additional helper `new_()` member
can be defined that acts as a surrogate constructor.
So, how to subclass then? By a slight twist by using the `impl` namespace
variations, as in following excerpt from an example:
class TreeViewFilterWindow : public Gtk::impl::WindowImpl
{
// ...
public:
// Assume (hypothetically) that Window also implements FakeInterface
// with a set_focus method, then a compilation failure will be triggered (as
// it can no longer be detected whether set_focus is defined in this class).
// Then the following inner struct is needed to resolve so manually;
struct DefinitionData
{
// the last parameter specifies whether the method is defined
// (which may well be false in all class/interface cases if not defined)
GI_DEFINES_MEMBER(WindowClassDef, set_focus, true)
GI_DEFINES_MEMBER(FakeInterfaceDef, set_focus, false)
};
// NOTE for the auto-detection to work, the methods must be accessible
// so either they should be defined public, or (e.g.) WindowClassDef
// must be declared friend, or the above manual resolution can be used.
// this (super)constructor signature leads to a C++ side type
TreeViewFilterWindow () : Gtk::impl::WindowImpl (this)
{
// ...
}
// this (super)constructor signature leads to a C-side type
// NOTE InitData is not (easily) instantiated,
// so this constructor is only used by the internal C-side mechanics
TreeViewFilterWindow (const InitData &id)
: Gtk::impl::WindowImpl (this, id, "TreeViewFilterWindow")
void set_focus_ (Gtk::Widget focus) noexcept override
{
}
// the above constructor is also re-used during (C-side) type registration
// (in that case with an "empty" id and no associated GObject setup)
// that can avoided by providing a separate ...
GType get_type_()
{
// no interfaces, properties or signals to declare
return register_type_<TreeViewFilterWindow>("TreeViewFilterWindow", 0, {}, {}, {});
}
};
// create an instance of (either) type
// it prefers the latter C-side type, if supported, and supports extra arguments
// (a second template parameter allows for specific selection,
// see code comments and examples for details)
gi::make_ref<TreeViewFilterWindow>()
Parent (class or interface) methods can then be overridden or implemented
in the usual way by simply defining them in the subclass. It is also possible
to define custom signal and properties in the subclass, as illustrated in the
`gobject.cpp` example. As mentioned, the inner `DefinitionData` struct in the
above fragment is usually not needed, but only in case of conflict/duplication
of class/interface member(s).
Since this is considered an optional feature, the `impl` parts are not generated
by default, but only if the `--class` option is specified. Since the virtual
methods share some similarities with callbacks they are also subject to some
limitations (see corresponding section). As such, it may happen that some
virtual methods do not have a wrapper. If the `--class-full` option is
specified, then a passthrough virtual method (with C signature as-is) is then
generated instead, which can then be overridden and implemented as a fallback.
So the custom type registration (that happens behind the scenes) can then still
be used, albeit at the expense of dealing with a plain C signature and types
(which is similar to directly calling a C function as a fallback if no wrapper
function was generated for some reason).
It is also a fairly advanced feature, with various aspects not immediately
obvious. For example, the resulting "instances" have "2 sides"; there is C++
object instance as well as an associated (derived) GObject instance (which
internally refer to each other in 1-to-1 association ). In particular, it
follows that their destruction must be closely coordinated. This is potentially
tricky as the GObject side is traditionally reference-counted, whereas the C++
object could be anything (stack-allocated or otherwise). The latter can be
stack-allocated if it is ensured that there is no lingering reference in the
GObject world. So, it depends on the use-case as to how to proceed. But in
overall, it is probably recommended to manage lifetime and ownership based on
GObject (side) reference count. The `gi::make_ptr` and `gi::ref_ptr` helpers
can be helpful in this regard.
There are also situations where the C API (implementation) involves some
"low-level tricks" which do not port over in a simple or straight way (e.g.
`GtkBuilder`). Those are likely in need of some custom overrides or extensions
(see also below) which may or may not already be provided for some particular
API/situation. It is highly advised to browse provided overrides and
extensions and/or consult the closest relevant related example
(which usually showcases what is available, along with additional explanations).
### CODE LAYOUT AND BUILD SETUP
The generated code is written to the top-level with the following layout.
Each GIR namespace has a corresponding subdirectory, say `ns`
(and also a C++ namespace, `cppgir::repository::ns`). The top-levels
headers for a namespace are then:
* `ns.hpp`:
a regular header providing the namespace's declarations.
It will also include the dependent namespaces' top headers.
If the macro `GI_INLINE` is defined, then it will also include ...
* `ns_impl.hpp`:
contains the definitions corresponding to the declarations.
Normally, this would be a `.cpp` file, but as they might be included directly
in the inline case, they have been named `xxx_impl.hpp` instead.
* `ns.cpp`:
this merely includes `ns_impl.hpp` and is as such no different
than the latter, except for more traditional naming.
Compiling this file in the non-inline case provides all the definitions
for the namespace in the resulting object file.
* `*.cppm`; module wrappers, see next subsection for details.
So, in summary, it comes down to setting up the build system to build each of
the namespaces' `.cpp`, as is also done in this repo's CMake build setup.
There is one other shortcut build setup that is illustrated by the `gtk-obj.cpp`
example file, which includes all definitions (recursively):
#define GI_INCLUDE_IMPL 1
#include <gtk/gtk.hpp>
Note, however, this is only possible if there is exactly 1 top-level namespace,
as doing this for several namespaces will lead to duplicate definitions.
Some items (functions, types) may be marked as deprecated (in source code).
while still present in GIR data. Wrappers will still be generated and
`pragma` are issued to avoid warnings that might otherwise occur.
Generic `gi` support tries to avoid using deprecated code. There is, however,
one exception regarding the use of `g_object_newv`, which is deprecated
but may have to be used if support for an older GLib is required.
This can be arranged by defining `GI_OBJECT_NEWV` (and the deprecation
warning should also be silenced when dealing with newer version).
If the items are also marked deprecated in GIR data, then these are skipped
by default. However, if the string `deprecated:<NAMESPACE>:<VERSION>`
matches (a regexp) in specified ignore data/files, then deprecated items
will be considered for the namespace in question, after being checked as
usual against the ignore list.
If you have specified the `--class` option, then the generated code will
possibly contain classes that inherit from several classes (representing
interfaces). Since various interfaces may have overlapping member names, this
might trigger compilation warnings. These are not suppressed by default, as you
may need to be made aware of this. However, if it does no harm in your
particular case, then defining `GI_CLASS_IMPL_PRAGMA` should arrange for proper
suppression.
The generated code may be quite extensive and so it may present a "heavy build".
Other than that the above allows for a number of different build setups,
`cppgir` tries not to impose any particular approach. In particular, standard
tried-and-tested build optimizations can be applied, such as precompiled headers
(with some good results, as reported in
[issue #99](https://gitlab.com/mnauw/cppgir/-/issues/99)).
**C++ Module support**
At this time of writing, module support is still new-ish in compilers
and (not in the least) build tools. Unfortunately, all major compilers also
take a different approach in handling these wrt command-line argument, source
file extension or binary output. Note that this also likely complicates any
`compile_commands.json` based tooling (e.g. LSP server as used by IDEs) along
with other issues as outlined in
[this post](https://nibblestew.blogspot.com/2023/12/even-more-breakage-in-c-module-world.html)
and originally raised
[long ago](https://vector-of-bool.github.io/2019/01/27/modules-doa.html).
By comparison, precompiled headers are long since well supported, so these may
be a more recommended alternative approach.
Failing that, a next natural fit might be "header units" (in module spec sense).
Unfortunately, compiler setup here varies somewhat and build tool support may be
limited.
So, a next step is a pure/real module. Unfortunately, "core gi" can not be
provided in that form;
* in addition to code, it also "exports" some macros, which can not really be
`export`'ed or `import`'ed (only from a "header unit")
* the core is somewhat intertwined with basic types from GObject,
e.g. some code-generated types are forward declared (which satisfies
for their purposes in "core gi"). However, forward and real declaration
can not pass module boundary.
But "core gi" combined with (generated) GLib and GObject can be wrapped
in a module (along with a separate "macro API header", `gi_inc.hpp`).
Again, `cppgir` does not impose a particular module layout/setup, but provides
the basic parts and pieces to do so. The generated code also provides a few
example module wrappers (with no stability guarantee), also see `gst.cpp` and
`gtk.cpp` examples for possible usage details;
* `ns.cppm` (exports `gi.repo.ns`);
module that wraps/provides `ns` (inline) code, so a typical compilation
produces `.o` code of `ns` and a BMI/CMI that exports `ns` declarations
* `ns_rec.cppm` (exports `gi.repo.ns.rec`);
module that wraps/provides `ns` and all (recursive) dependencies, so a
typical compilation produces `.o` code of `ns` and dependencies, along
with a BMI/CMI with corresponding declarations.
The above have been tested with gcc-15 and clang-18 with some varied measure of
success (apparently, GCC fails to link the non-recursive approach). They each
come with an extra "knob"; if `GI_MODULE_EXTERN` is defined, then the module
purview is essentially `extern "C++" { ... }`. So all declarations are then
attached to global module, as opposed to the named module, and as such do not
incur any modified ABI linkage (`@M`) (so it may be recommended).
Other variations are likely possible, e.g. module partitions, separate
module implementation, etc.
### OVERRIDING OR EXTENDING
It is possible to add functions or methods or override existing names (by
effect of name hiding). To this end, the generated code contains various
'optional include hooks' using the `__has_include` directive. This way, code in
externally supplied (include) files can be inserted into the class definition
chain. There are roughly 3 such 'hook points':
* **initial setup**:
this part is (conditionally) included before the namespace's C headers are included.
This allows specifying define's to tweak subsequent headers or to add
headers that also need to be include'd, and which may not have been specified
in the GIR.
* **class definition**:
these hooks allow extending the wrapped class with new or tweaked methods
* **global extra definitions**:
these are included after all generated code, and supports adding of new global
functions, typedef's, type trait helper declarations, ...
The reader is invited to examine the default overrides in this repo as well as
the generated code to see how this fits together based on a simple naming
scheme and use of macros. In particular, see the provided `GLib` overrides.
Suffice it to add that the `_def` suffix refers to 'default' as supplied by
this repo and which are installed alongside the common headers. The
corresponding non-suffixed filenames should be used by project specific custom
additions.
### CODE GENERATION
It might be necessary to exclude a GIR entry from processing, either because it
is a basic type handled by custom code (e.g. `GObject`, `GValue`, ...) or
because of a faulty annotation. The latter can be a glitch in the annotation
itself, or one that actually refers to a symbol in a non-included private
header. The exclusion can be directed by so-called ignore files, and at least
one such is supplied as a system default ignore containing known and essential
cases to exclude (and without which code generation would not produce valid
code). Such a file consists of lines of regular expressions (`#` commented
lines are ignored). At generation time, each symbol is turned into a
`<NAMESPACE>:<SYMBOLKIND>:<SYMBOL>` string, and excluded if it matches one of
the lines' regular expression. So, for instance, `GObject:record:Value`
prevents processing of `GValue`, since there is already special-case code for
that in the common header code. Further expression examples are found in the
default ignore file. Additional files can be specified by the `--ignore`
option.
As each entry is processed, some notification may be given regarding a
perceived inconsistency in an annotation or an unsupported case (see also [BUGS
AND LIMITATIONS](#bugs-and-limitations)). When the reported cases have been
(manually) checked and considered harmless, the corresponding notices can be
suppressed by specifying suppression files to `--suppression`. The format of
such files is the same as ignore files, except that a match then simply serves
to decrease reporting verbosity. Such a file could be hand-crafted, but it can
also be auto-generated by a run when specifying `--gen-suppression`.
Besides excluding problematic GIR parts, one might also consider solutions to
some problematic GIRs used by other projects, such as fixed GIRs maintained by
[gtk-rs](https://gtk-rs.org/gir/book/tutorial/finding_gir_files.html#gtk-dependencies)
in the referenced [repo](https://github.com/gtk-rs/gir-files).
### (RATIONALE OF) v2 CHANGES
Consider the following python session using gobject-introspection:
>>> import gi
>>> gi.require_version('Gst', '1.0')
>>> from gi.repository import Gst
>>> Gst.init(None)
>>> c = Gst.caps_from_string('video/x-raw')
>>> c.get_structure(0)
<Gst.Structure object at 0x7fe284096760 (GstStructure at 0x1bb4420)>
>>> c.get_structure(0)
<Gst.Structure object at 0x7fe2840b5d00 (GstStructure at 0x1bb43a0)>
What happens here? A different `GstStructure*` is created each time, even
though the same one is returned (by C code) in each case. The python binding
here has no other choice than to use `g_boxed_copy()` on the transfer `none`
return value. If it would not, it would be carrying around an unguarded/unowned
and hence potentially dangling pointer (in some `PyObject` wrapper), which is
a definite no-go in a scripted setting that must always ensure valid objects.
v1 API followed a similary "scripted" style approach where all objects/pointers
should always be safe and valid, with (roughly) `std::shared_ptr` in place of
`PyObject`. Of course, also then with similar (copy) effects as in the above
excerpt and in e.g. [issue #32](https://gitlab.com/mnauw/cppgir/-/issues/32).
v2 now follows a different approach. After all, C++ is much closer to C, and it
is customary to mind about (potentially dangling) references and such, and where
and how (not) to use e.g. `std::string_view`. And so while types/objects are
now no longer always "owning" (and as such always safe), the type conventions do
clearly specify whether or not they do (own). As such, standard C++ practices
should handle what v2 API provides, while avoiding superfluous and potentially
surprising copies or any other "automagic". In particular, the v2 bindings
are therefore even more "tight and direct" than before, with a typical wrapper
being only a cast away from the wrappee (and matching in size and semantics).
**Migration.**
In practice, only limited changes have been needed in the included examples.
Of course, your mileage may vary, depending on usage of "boxed types" as well
as use of (type deduction) `auto` versus explicit type specification.
Some `_Ref` types may have to be used instead here or there, as well as possibly
some `std::move` on "owning" variants (unless overall boxed copy is enabled).
For reasons of consistency and to avoid collision with generated methods,
some more "custom methods" may have had `_` appended
(e.g. `CBoxed::allocate_()`).
## BUGS AND LIMITATIONS
The generated code's coverage is pretty good and comfortably serves most
cases that arise in practice as also illustrated by the examples.
Nevertheless, the following should be mentioned:
**Callback types.**
Only callback types that have an explicit `user_data`
parameter are supported. That includes (fortunately) cases such as connecting
to a signal, or a `GstPadProbeCallback`, though a `GstPadChainFunction` is
excluded. The reason is a technical one; the `user_data` parameter is used to
pass data used by callback wrapper code. A typical (script) runtime binding
handles this using [libffi](https://github.com/libffi/libffi)'s closure API. In
effect, a little bit of executable code is then generated at runtime, and the
address of that code then essentially serves as surrogate `user_data` that can
carry extra meta-data for use by the runtime. This could also be employed here
to lift the `user_data` limitation, it would take a bit extra work, but would
more importantly then also incur an additional dependency.
**Callback handling.**
Even if `user_data` is present, other aspects of a callback signature
may not be supported (at this time), e.g. certain (sized) array parameters.
However, few (if any) of such actual cases are known at this time.
Note that both signals and virtual methods are somewhat similar to a callback
and as such share similar limitations.
Whereas the above items could (in theory) be resolved, the following are more
inherent limitations (by the very context and nature of e.g. annotations).
Fortunately, though, the practical impact is fairly limited (if any).
**const handling.** In C++, this is a Bigger Thing. For instance, a simple
'getter' should preferably be marked const. However, on the original C-side of
things, only very limited consideration is given to this. Even if there is some
`const`, it is not treated with all that much respect, e.g.
`g_value_take_boxed` starts `const` but it is merrily cast away along the way.
As such, there is not much to find on const-ness in annotation data, and so no
point in inventing any. Rather, the focus is simply on getting the proper
function calls done along with automagic refcount and resource management (much
as any runtime binding would do, with no regard for const whatsoever in that
case).
In particular, methods are usually not marked `const` either, as there is
similarly no (semantic) data to decide either way. As such, it is not
recommended to use `const` wrapper types. However, they may arise due to generic
templates or when captured in a lambda. In such cases, the helper (template)
type `gi::cs_ptr` may be useful, or alternatively one might set the option to
mark all code-generated methods as `const`.
**Floating (into darkness).**
[Gobject docs](https://docs.gtk.org/gobject/floating-refs.html) mention the
following about floating references (i.e. transfer `floating`);
> Floating references are a C convenience API and should not be used in modern
GObject code. Language bindings in particular find the concept highly
problematic, as floating references are not identifiable through annotations,
...
Indeed, by the time `floating` makes it into the parsed annotation, it has
become `none`. And in case of a "factory" `some_widget_new()`, `floating`
behaves more like `full` as the caller must "take ownership" to avoid a leak. So
a "floating" `none` is quite different from a "real" `none` (e.g. "getter"
method). But no way to know from annotation data. So, in case of `none`, an
object wrapper always `ref_sink()`s. If it was floating, it has taken suitable
ownership. If it was really none, then it is now managing an extra refcount.
And in either case, it will release/decrement upon destruction. Essentially,
this follows the recommendation given in referenced docs. In practice, it
actually Just Works.
It gets really tricky when this is combined with e.g. lists. So what does `none`
mean in this case (in annotation)? In the worst case, the contained elements
might actually be floating, so one would have to go through the list and
`ref_sink` them all (un)conditionally? Suffice it to say, no such "automagic"
is handled/injected by any wrapper code. Fortunately, at this time there does
not seem to be such a "multiple factory" API. Even if there were, then in
practice the calling code is likely to loop over the list and access the
elements. The ensuing C++ wrappers (even if existing only briefly) would then
effectively `ref_sink()`, so again we are ok. And last but not least, by the
above quoted recommendation, there should be no such new tricky API coming
along. So, again, it Just Works. If needed, any such old or new API can and
should be handled by custom overrides.
**Boxed (by darkness).**
This refers to so-called "plain records" which are "C structs" with no
registered `GType` (referred to as "C boxed" types in `cppgir` code), e.g.
`GOptionEntry` or `GstMapInfo`. While their fields may be described in
annotations, there is no information regarding the "ownership" of any data
(which may even vary upon context). In particular, also no way to create/free.
This corresponds with their frequent stack-allocated use in C code in typically
"low-level" API which is usually not considered "binding friendly". Based on
the mild assumption that 0-initialized data makes a valid instance, they are
treated somewhat similar to (GType) boxed types and as such can be used in some
limited (function call) situations. Any improvement beyond that is likely to
remain in the purview of overrides.
### WORKAROUNDS
As C++ allows direct mixing/calls with C, there are usually some fallback
workarounds when confronted with one of the limitations. First of all,
note that a C++ wrapper typically has e.g. a `gobj_()` method that
provides the underlying C pointer/object. Conversely, `gi::wrap` can be
used to obtain a wrapper from a C pointer/object obtained by some means.
With that in mind, the following are some workarounds;
* function call;
using/given the above, the C function can then (simply) be called directly
* custom subclass virtual method;
use `--class-full` to generate a virtual method with plain C signature
* signal;
use `Object::connect_unchecked` (see also `gst.cpp` example)
* callback;
use `gi::callback_wrapper` (see also in same example location as above).
Or perhaps there is an API variant using closures which may be useful in
combination with some `Closure::from_*` helpers (see still same example).
## SEE ALSO
g-ir-scanner(1)

View File

@@ -0,0 +1,159 @@
#pragma once
#include <gi/gi.hpp>
#include <coroutine>
#include <future>
#ifdef CO_DEBUG
#include <iostream>
static auto &dout = std::cerr;
#else
#include <sstream>
static std::ostringstream dout;
#endif
template<typename T, typename SELF>
struct holder
{
void return_value(T &&v)
{
dout << "return value " << std::endl;
auto self = (SELF *)this;
self->set_value(std::move(v));
}
};
template<typename SELF>
struct holder<void, SELF>
{
void return_void()
{
auto self = (SELF *)this;
self->set_value();
}
};
template<typename RESULT>
class promise_type_t : public holder<RESULT, promise_type_t<RESULT>>
{
protected:
std::promise<RESULT> result_;
std::coroutine_handle<> waiter_;
public:
struct init
{
std::coroutine_handle<> handle;
std::future<RESULT> f;
};
~promise_type_t() { dout << "promise destruction" << std::endl; }
auto get_return_object(bool refresh = false)
{
dout << "return obj " << std::endl;
if (refresh)
result_ = decltype(result_)();
return init{std::coroutine_handle<promise_type_t>::from_promise(*this),
result_.get_future()};
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
bool resume()
{
auto w = waiter_;
if (w) {
// waiter takes care of itself again
waiter_ = nullptr;
w.resume();
}
return bool(w);
}
// use any dummy type to avoid reference to void below
using arg_type = typename std::conditional<std::is_same<RESULT, void>::value,
std::nullptr_t, RESULT>::type;
void set_value(arg_type &&v)
{
result_.set_value(std::move(v));
resume();
}
void set_value()
{
result_.set_value();
resume();
}
void set_waiter(std::coroutine_handle<> handle)
{
// a task/promise represent a coroutine function (frame)
// it should only be waited upon by one other task
// (rather than handed around and waited in multiple locations)
if (waiter_)
gi::detail::try_throw(std::logic_error("already waited upon"));
waiter_ = handle;
}
void unhandled_exception()
{
#if GI_CONFIG_EXCEPTIONS
result_.set_exception(std::current_exception());
// if no-one waiting, deliver to caller
// the latter likely is the original caller
// (to which we have not yet returned, so it can yet await)
// otherwise it might end up totally lost
if (!resume())
throw;
#endif
}
};
template<typename RESULT, typename P = promise_type_t<RESULT>>
class task
{
public:
using promise_type = P;
// only 1 actually active
std::coroutine_handle<promise_type> coro_;
std::unique_ptr<promise_type> p_;
// but always this
std::future<RESULT> result_;
public:
// NOTE if coroutine exits by co_return, then handle is not useful
// but the future should have a value
task(typename P::init i)
: coro_(decltype(coro_)::from_address(i.handle.address())),
result_(std::move(i.f))
{
dout << "init task" << std::endl;
}
task() : p_(new promise_type()) { result_ = p_->get_return_object().f; }
// move-only
task(task &&other) = default;
task &operator=(task &&other) = delete;
bool await_ready()
{
return result_.wait_for(std::chrono::seconds(0)) ==
std::future_status::ready;
}
promise_type &promise() const { return coro_ ? coro_.promise() : *p_; }
void await_suspend(std::coroutine_handle<> handle)
{
if (!coro_ && !p_)
gi::detail::try_throw(std::logic_error("no routine to wait on"));
promise().set_waiter(handle);
}
RESULT await_resume() { return result_.get(); }
};

View File

@@ -0,0 +1,74 @@
cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR)
project(cppgir_example VERSION 2.0.0)
# following example uses the Gio-2.0 gir
# will generate code for that
# locate cppgir
# NOTE recent cmake version have more options and automagic find_package integration
# (see https://cmake.org/cmake/help/latest/guide/using-dependencies/index.html
# or https://cmake.org/cmake/help/latest/module/FetchContent.html)
# but a slightly older approach is used here
find_package(cppgir ${CMAKE_PROJECT_VERSION} QUIET)
if (cppgir_FOUND)
# ok
message(STATUS "using system cppgir")
elseif (CMAKE_VERSION GREATER_EQUAL 3.22)
# older version fail processing the subdirectory below
message(STATUS "cppgir not found, fetching instead")
# of course, alternatively,
# use any other method to include cppgir (e.g. git submodule)
# which can also be done unconditionally (without trying find_package first)
include(FetchContent)
FetchContent_Declare(
cppgir
GIT_REPOSITORY https://gitlab.com/mnauw/cppgir.git
GIT_TAG master
GIT_SHALLOW TRUE
GIT_SUBMODULES_RECURSE TRUE
# disable auto-invoke of add_subdirectory() below
SOURCE_SUBDIR data
)
# no CMakeLists in specified subdir, so we handle it explicitly
FetchContent_MakeAvailable(cppgir)
set(CPPGIR_DIR "${cppgir_SOURCE_DIR}")
else ()
# obviously, actual mileage/location may vary (e.g. using git submodule)
set(CPPGIR_DIR "../..")
endif ()
if (CPPGIR_DIR)
# adjust options
set(BUILD_TESTING OFF)
set(BUILD_EXAMPLES OFF)
set(BUILD_EMBED_IGNORE ON)
add_subdirectory(${CPPGIR_DIR} cppgir EXCLUDE_FROM_ALL)
endif ()
set(EXAMPLE_NS "Gio-2.0")
# of course, can be anywhere, e.g. inside/outside the build directory
# set(GENERATED_DIR "/tmp/gi.example")
set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
add_custom_command(OUTPUT ${GENERATED_DIR}
COMMENT "Generating wrapper code for: ${EXAMPLE_NS}"
DEPENDS CppGir::cppgir
COMMAND CppGir::cppgir --output ${GENERATED_DIR} ${EXAMPLE_NS}
COMMAND cmake -E touch_nocreate ${GENERATED_DIR}
)
add_custom_target(generate DEPENDS ${GENERATED_DIR})
# need the code and libs and especially GIRs
include(FindPkgConfig)
pkg_check_modules(GOBJECT REQUIRED IMPORTED_TARGET gobject-2.0)
pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0 gio-unix-2.0)
# (actually) needs no generated code, but it needs basic libs
add_executable(ext-gobject src/ext-gobject.cpp)
target_link_libraries(ext-gobject PRIVATE CppGir::gi PkgConfig::GOBJECT)
# (really) needs generated code
add_executable(ext-gio src/ext-gio.cpp)
target_link_libraries(ext-gio PRIVATE CppGir::gi PkgConfig::GIO)
target_include_directories(ext-gio PRIVATE ${GENERATED_DIR})
add_dependencies(ext-gio generate)

View File

@@ -0,0 +1,6 @@
# cppgir_example
This example project shows how `cppgir` can be used with `CMake` or `meson`.
In either case, the required `cppgir` parts can either be found "in the system"
(as previously installed by some package or build), or built as part of the
project itself (by suitable form of inclusion).

View File

@@ -0,0 +1,49 @@
import os
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
from conan.tools.build import can_run
class CppGirExampleConan(ConanFile):
version = "2.0.0"
name = "cppgir-example"
description = "cppgir example"
license = "MIT Software License"
url = "https://gitlab.com/mnauw/cppgir.git"
exports_sources = "src/*", "CMakeLists.txt"
settings = "os", "compiler", "build_type", "arch"
build_policy = "missing"
author = "Mark Nauwelaerts"
def requirements(self):
req = self.tested_reference_str
if req is None:
req = "cppgir/" + self.version
self.requires(req)
# also requires GLib
# however, no GIRs in conan binary package
# which nowadays requires a bootstrap dance with gobject-introspection
# so rely on a distro package instead
# self.requires("glib/2.78.3")
def build_requirements(self):
self.tool_requires("cppgir/<host_version>",
options={'header_only': False})
def layout(self):
cmake_layout(self, build_folder="build.conan")
def generate(self):
tc = CMakeToolchain(self)
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
def test(self):
if can_run(self):
cmd = os.path.join(self.cpp.build.bindir, "ext-gobject")
self.run(cmd, env="conanrun")

View File

@@ -0,0 +1,57 @@
project(
'cppgir_example',
['c', 'cpp'],
version: '2.0.0',
meson_version: '>= 0.61',
default_options: ['warning_level=2', 'cpp_std=c++17'],
)
# following example uses the Gio-2.0 gir
# will generate code for that
# locate cppgir, either in system or subproject
# if subproject, arrange to embed ignore data
# (since it will run un-installed, so data can not be found in installed location)
gi = dependency(
'cppgir',
required: true,
version: '>=2.0.0',
fallback: 'cppgir',
default_options: ['build_embed_ignore=true'],
)
cppgir = find_program('cppgir', required: true)
generated_src = custom_target(
'src',
# actually it generates all this (and more)
# output : ['gio.cpp', 'gio.hpp', 'glib.cpp', 'glib.hpp', 'gobject.cpp', 'gobject.hpp'],
# but the example below will inline all of the above, so no source files are used
# (though it is recommended for a real case, rather than a full inline)
output: ['gio.hpp'],
console: true,
# output-top creates extra files in top-level (as oppposed to subdir)
# since "output" paths above "must not contain a path segment" (says meson, sigh)
command: [cppgir, '--output-top', '--output', '@OUTDIR@', 'Gio-2.0'],
)
# need the code and libs and especially GIRs
gobject = dependency('gobject-2.0', required: true)
gio = dependency('gio-2.0', required: true)
# the Gio GIR data also covers this, so generated code references it
gio_unix = dependency('gio-unix-2.0', required: true)
deps = [gi, gobject, gio, gio_unix]
# (actually) needs no generated code, but it needs basic libs
ex_gobject = executable(
'ext-gobject',
['src/ext-gobject.cpp'],
dependencies: deps,
)
# (really) needs generated code
ex_gio = executable(
'ext-gio',
['src/ext-gio.cpp', generated_src],
dependencies: deps,
)

View File

@@ -0,0 +1 @@
#include "../../gio.cpp"

View File

@@ -0,0 +1 @@
#include "../../gobject.cpp"

View File

@@ -0,0 +1,5 @@
[wrap-git]
url = https://gitlab.com/mnauw/cppgir.git
revision = master
clone-recursive = true
depth = 1

View File

@@ -0,0 +1,254 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include "co-async.hpp"
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gio = gi::repository::Gio;
template<typename R>
class async_result_promise_type_t : public promise_type_t<R>
{
public:
Gio::Cancellable cancel_;
};
template<typename RESULT = Gio::AsyncResult>
class async_result : public task<RESULT, async_result_promise_type_t<RESULT>>
{
public:
using super_type = task<RESULT, async_result_promise_type_t<RESULT>>;
using super_type::super_type;
using super_type::await_suspend;
// NOTE; in general, if await_ready == false, then there is no result yet,
// so the handle's frame should not have co_return'ed yet
// so the handle/promise should still be valid
template<typename OR>
void await_suspend(
std::coroutine_handle<async_result_promise_type_t<OR>> handle)
{
// propagate cancellable
// the handle should be valid
// (as await_ready == false, otherwise no suspend should happen)
handle.promise().cancel_ = this->promise().cancel_;
// usual suspend
super_type::await_suspend(handle);
}
operator Gio::AsyncReadyCallback()
{
// only makes sense in default case
static_assert(std::is_same<RESULT, Gio::AsyncResult>::value, "");
if (this->p_) {
// setup for new gio call
this->result_ = this->p_->get_return_object(true).f;
// also arrange for new cancellable below
this->p_->cancel_ = nullptr;
return [this](GObject_::Object, Gio::AsyncResult result) {
this->promise().return_value(std::move(result));
};
} else {
// so this task is the result of a coroutine frame
// then it should be completed by the latter, not a Gio callback
g_warning("no callback to complete coroutine");
return nullptr;
}
}
Gio::Cancellable cancellable()
{
Gio::Cancellable ret;
if (this->p_) {
if (!this->p_->cancel_)
this->p_->cancel_ = Gio::Cancellable::new_();
ret = this->p_->cancel_;
} else if (!this->await_ready()) {
// no create here, only propagate
ret = this->promise().cancel_;
}
return ret;
}
static Gio::Cancellable timeout(
const std::chrono::milliseconds &to, Gio::Cancellable cancel)
{
if (to.count() > 0 && cancel) {
GLib::timeout_add_once(
to.count(), [cancel]() mutable { cancel.cancel(); });
}
return cancel;
}
};
async_result<void>
sleep_for(std::chrono::milliseconds to)
{
async_result<void> w;
GLib::timeout_add_once(to.count(), [&w] { w.promise().return_void(); });
co_await w;
co_return;
}
static task<void>
async_client(int port, int id, int &count)
{
async_result w;
auto dest = Gio::NetworkAddress::new_loopback(port);
std::string sid = "client ";
sid += std::to_string(id);
// connect a client
std::cout << sid << ": connect" << std::endl;
auto client = Gio::SocketClient::new_();
client.connect_async(dest, w);
auto conn = gi::expect(client.connect_finish(co_await w));
// say something
auto os = conn.get_output_stream();
std::cout << sid << ": send: " << sid << std::endl;
os.write_all_async(
(guint8 *)sid.data(), sid.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(co_await w, (gsize *)nullptr);
// now hear what the other side has to say
std::cout << sid << ": receive" << std::endl;
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_, w);
auto size = gi::expect(is.read_finish(co_await w));
if (!size)
break;
std::string msg(data, data + size);
std::cout << sid << ": got data: " << msg << std::endl;
}
std::cout << sid << ": closing down" << std::endl;
--count;
}
static task<void>
async_handle_client(Gio::SocketConnection conn)
{
async_result w;
// say hello
auto os = conn.get_output_stream();
std::string msg = "hello ";
os.write_all_async(
(guint8 *)msg.data(), msg.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(co_await w, (gsize *)nullptr);
// now echo what the other side has to say
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
// give up if timeout
GLib::Error error;
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_,
w.timeout(std::chrono::milliseconds(200), w.cancellable()), w);
auto size = gi::expect(is.read_finish(co_await w, &error));
if (error) {
if (error.matches(G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
break;
} else {
gi::detail::try_throw(std::move(error));
}
}
std::string msg(data, data + size);
std::cout << "server: got data: " << msg << std::endl;
os.write_all_async(data, size, GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(co_await w, (gsize *)nullptr);
}
std::cout << "server: closing down client" << std::endl;
}
static task<void>
async_server(int clients, int &port)
{
async_result w;
auto listener = Gio::SocketListener::new_();
port = gi::expect(listener.add_any_inet_port());
int count = 0;
while (count < clients) {
// accept clients
std::cout << "server: accepting" << std::endl;
listener.accept_async(w);
auto conn = gi::expect(
listener.accept_finish(co_await w, (GObject_::Object *)nullptr));
// spawn client handler
std::cout << "server: new connection" << std::endl;
// task will run itself to completion, no need to wait/watch it here
async_handle_client(conn);
++count;
}
// wait a bit more and shutdown, because we can
co_await sleep_for(std::chrono::milliseconds(1000));
}
void
async_demo(GLib::MainLoop loop, int clients)
{
// run server
// dispatch at once to obtain port
int port = 0;
auto server = async_server(clients, port);
// make clients
int count = 0;
for (int i = 0; i < clients; ++i) {
++count;
// client task runs to completion, no need to wait/watch
// NOTE this frame will stay alive, so count ref is valid
async_client(port, i, count);
}
// plain-and-simple; poll regularly and quit when all clients done
task<void> cd;
auto check = [&]() -> gboolean {
if (!count) {
cd.promise().return_void();
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
};
GLib::timeout_add(100, check);
auto wait = [&]() -> task<void> {
// wait clients
co_await cd;
// server should also have completed
co_await server;
std::cout << "server down" << std::endl;
loop.quit();
};
wait();
std::cout << "running loop" << std::endl;
loop.run();
std::cout << "ending loop" << std::endl;
}
int
main(int argc, char **argv)
{
auto loop = GLib::MainLoop::new_();
int clients = argc > 1 ? std::stoi(argv[1]) : 0;
std::cout << clients << " clients" << std::endl;
if (clients > 0)
async_demo(loop, clients);
}

View File

@@ -0,0 +1,351 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <iostream>
#if GI_CONFIG_EXCEPTIONS
#include <boost/fiber/all.hpp>
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gio = gi::repository::Gio;
class context_scheduler : public boost::fibers::algo::round_robin
{
typedef context_scheduler self;
typedef boost::fibers::algo::round_robin super;
struct src : GSource
{
self *scheduler;
};
GSourceFuncs funcs{};
GLib::MainContext ctx_;
src *source_;
boost::fibers::condition_variable cond_;
boost::fibers::mutex mtx_;
using clock_type = std::chrono::steady_clock;
bool dispatching_ = false;
static gboolean src_dispatch(
GSource *source, GSourceFunc /*callback*/, gpointer /*user_data*/)
{
auto s = (src *)(source);
auto sched = s->scheduler;
// wait here to give (other) fibers a chance
sched->dispatching_ = true;
std::unique_lock<boost::fibers::mutex> lk(sched->mtx_);
sched->cond_.wait(lk);
sched->dispatching_ = false;
// all available work has been done while we were waiting above
// no need to dispatch again until new work
// which we accept as of now (due to mainloop activity)
return G_SOURCE_CONTINUE;
}
public:
context_scheduler(GLib::MainContext ctx) : ctx_(ctx)
{
// this is a bit too much for bindings, so handle the raw C way
funcs.dispatch = src_dispatch;
auto s = g_source_new(&funcs, sizeof(src));
source_ = (src *)(s);
source_->scheduler = this;
g_source_attach(s, ctx.gobj_());
}
~context_scheduler()
{
g_source_destroy(source_);
g_source_unref(source_);
}
void awakened(boost::fibers::context *t) noexcept override
{
// delegate first
super::awakened(t);
// arrange for dispatch of work
// discard awake of source dispatch
if (!dispatching_)
g_source_set_ready_time(source_, 0);
}
void suspend_until(
std::chrono::steady_clock::time_point const &abs_time) noexcept override
{
// release dispatch
// should only end up here while dispatching in source
// (rather than inadvertently trying to block main loop,
// which would then lead to busy loop)
if (dispatching_) {
// derive time of subsequent dispatch
if (clock_type::time_point::max() != abs_time) {
auto to = abs_time - std::chrono::steady_clock::now();
int ms =
std::chrono::duration_cast<std::chrono::milliseconds>(to).count();
ms = std::max(ms, 0);
g_source_set_ready_time(source_, g_get_monotonic_time() + ms);
} else {
g_source_set_ready_time(source_, -1);
}
// release source dispatching
cond_.notify_one();
} else {
// suspend is requested, so there is nothing to do for a while
// so in particular the main fiber is then also blocked (e.g. some sleep)
// such main loop blocking is also/still not allowed
// (if such is active)
g_assert(g_main_depth() == 0);
// no running loop (so also no dispatch)
// so delegate to the usual scheduling
// (which will really block the hard way, rather than poll)
super::suspend_until(abs_time);
}
}
// might be called from a different thread
void notify() noexcept override
{
// discard our own notify above to resume source dispatch
if (dispatching_)
return;
ctx_.wakeup();
}
};
class async_future
{
boost::fibers::promise<Gio::AsyncResult> p_;
boost::fibers::future<Gio::AsyncResult> f_;
Gio::Cancellable cancel_;
GLib::Source timeout_;
public:
~async_future()
{
if (timeout_)
timeout_.destroy();
}
operator Gio::AsyncReadyCallback()
{
// prepare a new promise
p_ = decltype(p_)();
f_ = p_.get_future();
cancel_ = nullptr;
return [&](GObject_::Object, Gio::AsyncResult result) {
p_.set_value(result);
};
}
Gio::AsyncResult get() { return f_.get(); }
Gio::Cancellable cancellable()
{
if (!cancel_)
cancel_ = Gio::Cancellable::new_();
return cancel_;
}
Gio::Cancellable timeout(const std::chrono::milliseconds &to)
{
auto cancel = cancellable();
if (to.count() > 0) {
timeout_ = GLib::timeout_source_new(to.count());
auto do_timeout = [cancel]() mutable {
cancel.cancel();
return GLib::SOURCE_REMOVE_;
};
timeout_.set_callback<GLib::SourceFunc>(do_timeout);
timeout_.attach(GLib::MainContext::get_thread_default());
}
return cancel_;
}
};
static void
async_client(int port, int id, int &count)
{
async_future w;
auto dest = Gio::NetworkAddress::new_loopback(port);
std::string sid = "client ";
sid += std::to_string(id);
// connect a client
std::cout << sid << ": connect" << std::endl;
auto client = Gio::SocketClient::new_();
client.connect_async(dest, w);
auto conn = gi::expect(client.connect_finish(w.get()));
// say something
auto os = conn.get_output_stream();
std::cout << sid << ": send: " << sid << std::endl;
os.write_all_async(
(guint8 *)sid.data(), sid.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(w.get(), (gsize *)nullptr);
// now hear what the other side has to say
std::cout << sid << ": receive" << std::endl;
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_, w);
auto size = gi::expect(is.read_finish(w.get()));
if (!size)
break;
std::string msg(data, data + size);
std::cout << sid << ": got data: " << msg << std::endl;
}
std::cout << sid << ": closing down" << std::endl;
--count;
}
static void
async_handle_client(Gio::SocketConnection conn)
{
async_future w;
// say hello
auto os = conn.get_output_stream();
std::string msg = "hello ";
os.write_all_async(
(guint8 *)msg.data(), msg.size(), GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(w.get(), (gsize *)nullptr);
// now echo what the other side has to say
auto is = conn.get_input_stream();
while (1) {
guint8 data[1024];
// give up if timeout
GLib::Error error;
is.read_async(data, sizeof(data), GLib::PRIORITY_DEFAULT_,
w.timeout(std::chrono::milliseconds(200)), w);
auto size = gi::expect(is.read_finish(w.get(), &error));
if (error) {
if (error.matches(G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
break;
} else {
throw error;
}
}
std::string msg(data, data + size);
std::cout << "server: got data: " << msg << std::endl;
os.write_all_async(data, size, GLib::PRIORITY_DEFAULT_, w);
os.write_all_finish(w.get(), (gsize *)nullptr);
}
std::cout << "server: closing down client" << std::endl;
}
static void
async_server(int clients, int &port)
{
async_future w;
auto listener = Gio::SocketListener::new_();
port = gi::expect(listener.add_any_inet_port());
int count = 0;
while (count < clients) {
// accept clients
std::cout << "server: accepting" << std::endl;
listener.accept_async(w);
auto conn = gi::expect(
listener.accept_finish(w.get(), (GObject_::Object *)nullptr));
// spawn client handler
std::cout << "server: new connection" << std::endl;
boost::fibers::fiber c(async_handle_client, conn);
c.detach();
++count;
}
// wait a bit and shutdown
// wait long enough to test the out-of-loop join below
boost::this_fiber::sleep_for(std::chrono::milliseconds(1000));
std::cout << "server: shutdown" << std::endl;
}
static void
async_demo(GLib::MainLoop loop, int clients)
{
// run server
// dispatch at once to obtain port
int port = 0;
boost::fibers::fiber server(
boost::fibers::launch::dispatch, async_server, clients, std::ref(port));
// make clients
int count = 0;
for (int i = 0; i < clients; ++i) {
++count;
auto c = boost::fibers::fiber(async_client, port, i, std::ref(count));
c.detach();
}
// plain-and-simple; poll regularly and quit when all clients done
auto check = [&]() {
if (!count)
loop.quit();
return G_SOURCE_CONTINUE;
};
GLib::timeout_add(100, check);
std::cout << "running loop" << std::endl;
loop.run();
std::cout << "ending loop" << std::endl;
server.join();
}
int
main(int argc, char **argv)
{
GLib::MainLoop loop = GLib::MainLoop::new_();
auto ctx = GLib::MainContext::default_();
boost::fibers::use_scheduling_algorithm<context_scheduler>(ctx);
{ // basic fiber demo
int count = 0;
auto work = [&](const std::string &msg) {
std::cout << msg << std::endl;
++count;
};
auto quit = [&](int limit, const std::chrono::milliseconds &d) {
while (count < limit)
boost::this_fiber::sleep_for(d);
loop.quit();
};
boost::fibers::fiber f1(work, "fiber 1");
boost::fibers::fiber f2(work, "fiber 2");
boost::fibers::fiber q(quit, 2, std::chrono::milliseconds(100));
loop.run();
f1.join();
f2.join();
q.join();
}
// now an optional async GIO demo
int clients = argc > 1 ? std::stoi(argv[1]) : 0;
std::cout << clients << " clients" << std::endl;
if (clients > 0)
async_demo(loop, clients);
}
#else
int
main(int argc, char **argv)
{
(void)argc;
(void)argv;
std::cerr << "boost::fibers needs exceptions";
}
#endif

View File

@@ -0,0 +1,63 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <iostream>
namespace GObject_ = gi::repository::GObject;
namespace GLib = gi::repository::GLib;
namespace Gio = gi::repository::Gio;
static GLib::MainLoop loop;
// many calls here support GError
// so typically will throw here instead
// (unless GError output is explicitly requested in call signature)
// NOTE abundancy of gi::expect not generally needed;
// only needed when using --dl and --expected
static void
on_reply(GObject_::Object ob, Gio::AsyncResult result)
{
// if not caught here, it will be caught before returning to plain C
#if GI_CONFIG_EXCEPTIONS
try {
#endif
auto connection = gi::object_cast<Gio::DBusConnection>(ob);
auto call_result = gi::expect(connection.call_finish(result));
// get single array child
auto names = gi::expect(call_result.get_child_value(0));
int count = gi::expect(names.n_children());
std::cout << count << " message bus names: " << std::endl;
for (int i = 0; i < count; ++i)
std::cout << gi::expect(
gi::expect(names.get_child_value(i)).get_string(nullptr))
<< std::endl;
#if GI_CONFIG_EXCEPTIONS
} catch (const GLib::Error &error) {
std::cerr << "error: '" << error.what() << "'." << std::endl;
}
#endif
// quit when idle
GLib::idle_add([]() {
loop.quit();
return GLib::SOURCE_REMOVE_;
});
}
int
main(int argc, char ** /*argv*/)
{
auto bustype = argc <= 1 ? Gio::BusType::SESSION_ : Gio::BusType::SYSTEM_;
auto connection = gi::expect(Gio::bus_get_sync(bustype));
connection.call("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", "ListNames", nullptr, nullptr,
Gio::DBusCallFlags::NONE_, -1, nullptr, on_reply);
loop = gi::expect(GLib::MainLoop::new_());
loop.run();
}

View File

@@ -0,0 +1,225 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <functional>
#include <iostream>
#include <memory>
#include <QCoreApplication>
#include <QFuture>
#include <QFutureWatcher>
#include <QObject>
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gio = gi::repository::Gio;
template<typename T>
using ResultExtractor = std::function<void(
GObject_::Object, Gio::AsyncResult result, QFutureInterface<T> &fut)>;
template<typename T>
class qt_future
{
struct CallbackData
{
QFutureInterface<T> promise;
QFutureWatcher<T> watcher;
Gio::Cancellable cancel;
ResultExtractor<T> handler;
};
std::shared_ptr<CallbackData> data_;
public:
qt_future(ResultExtractor<T> h)
{
data_ = std::make_shared<CallbackData>();
data_->handler = h;
}
QFuture<T> future() { return data_->promise.future(); }
QFutureInterface<T> promise() { return data_->promise; }
operator Gio::AsyncReadyCallback()
{
auto d = data_;
return [d](GObject_::Object obj, Gio::AsyncResult result) {
assert(d->handler);
d->handler(obj, result, d->promise);
};
}
Gio::Cancellable cancellable()
{
if (!data_->cancel) {
auto cancel = data_->cancel = Gio::Cancellable::new_();
data_->watcher.setFuture(future());
auto h = [cancel]() mutable { cancel.cancel(); };
data_->watcher.connect(
&data_->watcher, &decltype(data_->watcher)::finished, std::move(h));
}
return data_->cancel;
}
// why not ... ??
operator Gio::Cancellable() { return cancellable(); }
};
#if GI_CONST_METHOD
#define CONST_METHOD const
#else
#define CONST_METHOD
#endif
template<typename Result, typename Object>
qt_future<Result>
make_future(Result (Object::*mf)(Gio::AsyncResult, GLib::Error *) CONST_METHOD)
{
ResultExtractor<Result> f;
f = [mf](GObject_::Object cbobj, Gio::AsyncResult result,
QFutureInterface<Result> &fut) mutable {
auto obj = gi::object_cast<Object>(cbobj);
GLib::Error error{};
auto res = (obj.*mf)(result, &error);
if (error.gobj_()) {
// FIXME use std::expected<Result> as a result type ??
// reportException might be an option, if enabled, but leads to throw
fut.reportFinished();
qWarning() << "error code " << error.code_();
qWarning() << "error message "
<< QString::fromUtf8(error.message_().c_str());
} else {
fut.reportFinished(&res);
}
};
return {f};
}
// ====
// some small future-centric API wrappers using above helper class
QFuture<Gio::FileInfo>
file_query_file_system_info(
Gio::File f, const std::string &attributes, gint io_priority)
{
auto fut = make_future(&Gio::File::query_filesystem_info_finish);
f.query_filesystem_info_async(attributes, io_priority, fut, fut);
return fut.future();
}
QFuture<bool>
file_copy(Gio::File src, Gio::File destination, Gio::FileCopyFlags flags,
gint io_priority)
{
auto fut = make_future(&Gio::File::copy_finish);
auto promise = fut.promise();
auto p = [promise](
goffset current_num_bytes, goffset total_num_bytes) mutable {
promise.setProgressRange(0, total_num_bytes);
promise.setProgressValue(current_num_bytes);
};
src.copy_async(destination, flags, io_priority, fut, p, fut);
return fut.future();
}
// ====
// save on typing elsewhere
// also makes it movable in a way
template<typename T>
std::shared_ptr<QFutureWatcher<T>>
make_watcher(QFuture<T> fut)
{
auto watcher = std::make_shared<QFutureWatcher<T>>();
watcher->setFuture(fut);
return watcher;
}
// type-erased owner/cleanup type
using Retainer = std::shared_ptr<std::nullptr_t>;
Retainer
do_query(QCoreApplication &app, const std::string &fpath)
{
auto file = Gio::File::new_for_commandline_arg(fpath);
auto fut = file_query_file_system_info(file, "*", GLib::PRIORITY_DEFAULT_);
auto watcher = make_watcher(fut);
auto h = [fut, &app]() {
Q_ASSERT(fut.isFinished());
std::cout << "query finished" << std::endl;
if (fut.resultCount()) {
auto finfo = fut.result();
for (auto attr : {Gio::FILE_ATTRIBUTE_FILESYSTEM_TYPE_,
Gio::FILE_ATTRIBUTE_FILESYSTEM_FREE_,
Gio::FILE_ATTRIBUTE_FILESYSTEM_SIZE_}) {
std::cout << attr << ": " << finfo.get_attribute_as_string(attr)
<< std::endl;
}
} else {
std::cout << "... but no results" << std::endl;
}
app.quit();
};
watcher->connect(
watcher.get(), &decltype(watcher)::element_type::finished, &app, h);
return {watcher, nullptr};
}
Retainer
do_copy(QCoreApplication &app, const std::string &src, const std::string &dest)
{
auto fsrc = Gio::File::new_for_commandline_arg(src);
auto fdest = Gio::File::new_for_commandline_arg(dest);
auto fut = file_copy(
fsrc, fdest, Gio::FileCopyFlags::ALL_METADATA_, GLib::PRIORITY_DEFAULT_);
auto watcher = make_watcher(fut);
auto h = [fut, &app]() {
Q_ASSERT(fut.isFinished());
std::cout << "copy finished" << std::endl;
if (fut.resultCount()) {
auto ok = fut.result();
if (ok) {
std::cout << "copy ok" << std::endl;
} else {
std::cout << "copy failed" << std::endl;
}
} else {
std::cout << "... but no results" << std::endl;
}
app.quit();
};
watcher->connect(
watcher.get(), &decltype(watcher)::element_type::finished, &app, h);
// also monitor progress
auto progress = [fut](int) {
auto max = fut.progressMaximum();
auto min = fut.progressMinimum();
auto current = fut.progressValue();
std::cout << "copy progress " << current << " (" << min << " -> " << max
<< ")" << std::endl;
};
watcher->connect(watcher.get(),
&decltype(watcher)::element_type::progressValueChanged, &app, progress);
return {watcher, nullptr};
}
int
main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
if (argc < 2)
return -1;
Retainer r;
if (argc == 2) {
r = do_query(app, argv[1]);
} else if (argc == 3) {
r = do_copy(app, argv[1], argv[2]);
}
return app.exec();
}

View File

@@ -0,0 +1,98 @@
#define GI_INLINE 1
#include <gio/gio.hpp>
#include <iostream>
namespace GLib = gi::repository::GLib;
namespace Gio = gi::repository::Gio;
const std::string localhost("127.0.0.1");
static GLib::MainLoop loop;
// many calls here support GError
// so typically will throw here instead
// (unless GError output is explicitly requested in call signature)
// NOTE abundancy of gi::expect not generally needed;
// only needed when using --dl and --expected
static bool
receive(Gio::Socket s, GLib::IOCondition /*cond*/)
{
guint8 buffer[1024] = {
0,
};
Gio::SocketAddress a;
int count = gi::expect(s.receive_from(&a, buffer, sizeof(buffer)));
if (count > 0) {
// let's see where it came from
std::string origin("someone");
auto ia = gi::object_cast<Gio::InetSocketAddress>(a);
if (ia) {
origin = gi::expect(gi::expect(ia.get_address()).to_string());
origin += ":";
origin += std::to_string(gi::expect(ia.get_port()));
}
std::cout << origin << " said " << (char *)buffer << std::endl;
// quit when idle
GLib::idle_add([]() {
loop.quit();
return GLib::SOURCE_REMOVE_;
});
}
return true;
}
Gio::Socket
open(bool listen)
{
auto socket = gi::expect(Gio::Socket::new_(Gio::SocketFamily::IPV4_,
Gio::SocketType::DATAGRAM_, Gio::SocketProtocol::DEFAULT_));
auto address =
gi::expect(Gio::InetSocketAddress::new_from_string(localhost, 0));
socket.bind(address, false);
socket.set_blocking(false);
if (listen) {
// runtime introspection has a hard time here,
// but with a bit of extra information, we can keep going
#if defined(GI_CALL_ARGS) && CALL_ARGS <= 1
// so we should have this signature for a function with 1 non-required
GLib::Source source =
gi::expect(socket.create_source({.condition = GLib::IOCondition::IN_}));
#else
GLib::Source source =
gi::expect(socket.create_source(GLib::IOCondition::IN_, nullptr));
#endif
source.set_callback<Gio::SocketSourceFunc>(receive);
source.attach();
}
return socket;
}
static void
die(const std::string &why)
{
std::cerr << why << std::endl;
exit(2);
}
int
main(int argc, char **argv)
{
if (argc < 2)
die("missing argument");
std::string msg = argv[1];
std::cout << "will send message " << msg << std::endl;
auto recv = open(true);
auto local = gi::expect(recv.get_local_address());
auto send = open(false);
send.send_to(local, (guint8 *)msg.data(), msg.size(), nullptr);
loop = gi::expect(GLib::MainLoop::new_());
loop.run();
}

View File

@@ -0,0 +1,178 @@
// no code generation is needed
// all required functionality is required by the basic code
#include <gi/gi.hpp>
#include <iostream>
namespace GObject_ = gi::repository::GObject;
static const int DEFAULT_AGE = 10;
static const char *PERSON_TYPE = "GIPerson";
// class must be a ObjectImpl to support properties and signals
class Person : public GObject_::impl::ObjectImpl
{
using self_type = Person;
private:
// the implementation/definition part of the properties
// the setup parameters shadow the corresponding g_param_spec_xxx
// so in practice define the property (name, nick, description)
// along with min, max, default and so (where applicable)
// (could also be in a constructor initializer list,
// but this way it applies to any constructor)
// this provides interface to set/get the actual value
gi::property<int> prop_age_{this, "age", "age", "age", 0, 100, DEFAULT_AGE};
gi::property<std::string> prop_firstname_{
this, "firstname", "firstname", "firstname", ""};
gi::property<std::string> prop_lastname_{
this, "lastname", "lastname", "lastname", ""};
public:
// likewise for signal
// public because there is no extra interface for owning class
// (both owner and outside can connect and/or emit)
// btw, using Person in this signature would not be the way to go,
// should stick to a plain wrapped type
gi::signal<void(GObject_::Object, int)> signal_trigger{this, "trigger"};
// std::string also works here,
// but that would cost an extra allocation during invocation
gi::signal<void(GObject_::Object, gi::cstring_v)> signal_example{
this, "example"};
public:
// old-style C++ first
Person() : ObjectImpl(this) {}
// new-style C-first;
// must use proper signature for constructor and superclass
// this also registers a "public" GType, whose name must be specified
Person(const InitData &id) : ObjectImpl(this, id, PERSON_TYPE) {}
// usually the above constructor is also used to during GType registration,
// as such "dummy instantiation" auto-magically collects property info, etc)
// however, it does involve an "incomplete" instantiation (with no .gobj_())
// if such is considered too inelegant or too costly, then alternatively
// a ::get_type_() can be defined that is then used to register type instead
// however, as the example below demonstrates, this may be much less ergonomic
// so it is likely only useful if really so desired or in advanced cases)
// (e.g. subclass-ing a subclass)
static GType get_type_disabled() // not really used due to extra suffix
{
// if this is used, the member initializers only need to mention name
// no longer the full spec(ification)
// (as the name then only serves to link the propertyspec to actual storage)
return register_type_<Person>(PERSON_TYPE, 0, {},
{{&self_type::prop_age_, "age", "age", "age", 0, 100, DEFAULT_AGE},
{&self_type::prop_firstname_, "firstname", "firstname", "firstname",
""},
{&self_type::prop_lastname_, "lastname", "lastname", "lastname",
""}},
{{&self_type::signal_trigger, "trigger"},
{&self_type::signal_example, "example"}});
}
// the public counterpart providing the same interface
// as with any wrapped object's predefined properties
gi::property_proxy<int> prop_age() { return prop_age_.get_proxy(); }
gi::property_proxy<std::string> prop_firstname()
{
return prop_firstname_.get_proxy();
}
gi::property_proxy<std::string> prop_lastname()
{
return prop_lastname_.get_proxy();
}
void action(int id)
{
std::cout << "Changing the properties of 'p'" << std::endl;
prop_firstname_ = "John";
prop_lastname_ = "Doe";
prop_age_ = 43;
std::cout << "Done changing the properties of 'p'" << std::endl;
// we were triggered after all
signal_trigger.emit(id);
}
};
void
on_firstname_changed(GObject_::Object, GObject_::ParamSpec)
{
std::cout << "- firstname changed!" << std::endl;
}
void
on_lastname_changed(GObject_::Object, GObject_::ParamSpec)
{
std::cout << "- lastname changed!" << std::endl;
}
void
on_age_changed(GObject_::Object, GObject_::ParamSpec)
{
std::cout << "- age changed!" << std::endl;
}
int
main(int /*argc*/, char ** /*argv*/)
{
Person p;
// should have default age
assert(p.prop_age().get() == DEFAULT_AGE);
// register some handlers that will be called when the values of the
// specified parameters are changed
p.prop_firstname().signal_notify().connect(&on_firstname_changed);
p.prop_lastname().signal_notify().connect(on_lastname_changed);
p.prop_age().signal_notify().connect(&on_age_changed);
// now change the properties and see that the handlers get called
p.action(0);
// (derived) object can be constructed on stack for simple cases
// but in other (real) cases it is recommended that it is heap based.
// so as not to have a naked ptr, it can be managed by a (special) shared
// ptr that uses the GObject refcount for (shared) ownership tracking
auto dp = gi::make_ref<Person, gi::construct_cpp_t>();
// however, the GObject world has no knowledge of the subclass
// (each instance is 1-to-1 with Person instance though).
// so when we get something from that world, we can (sort-of dynamic)
// cast to the subclass
auto l = [](GObject_::Object ob, int id) {
std::cout << " - triggered id " << id << std::endl;
// obtain Person
auto lp = gi::ref_ptr_cast<Person>(ob);
if (lp)
std::cout << " - it was a person!" << std::endl;
// it really should be ...
assert(lp);
};
dp->signal_trigger.connect(l);
dp->action(1);
// all of the above constructed Person in C++ centric style
// that is, as part of the constructor (super class) execution,
// a (derived) GObject is created and associated/extended with C++ side Person
// alternatively, another derived GObject type can be registered,
// which will trigger construction of a Person (as part of g_object_new()),
// and then again associate those
// the following essentially runs g_object_new()
static int CUSTOM_AGE = 5;
auto dpc = gi::make_ref<Person, gi::construct_c_t>("age", CUSTOM_AGE);
// this will have a different GType
assert(dpc->gobj_type_() != dp->gobj_type_());
// as registered using specified name
assert(g_type_from_name(PERSON_TYPE) == dpc->gobj_type_());
// and the right age as specified in (gobject style) construct params
assert(dpc->prop_age().get() == CUSTOM_AGE);
// if the class supports C-style, it is selected automatically
auto dpa = gi::make_ref<Person>();
// should have ended up with C-style GType
assert(dpa->gobj_type_() == dpc->gobj_type_());
return 0;
}

View File

@@ -0,0 +1,419 @@
#include <functional>
#include <iostream>
#include <memory>
#include <ostream>
#include <sstream>
#include <vector>
#include "assert.h"
#ifndef USE_GI_MODULE
// no inline as the implementation is provided by other TUs
// #define GI_INLINE 1
#include <gst/gst.hpp>
#else
// optional, but recommended if gi macros are used
#include <gi/gi_inc.hpp>
// we also use some gst macros and api
#include <gst/gst.h>
// import used modules
import gi.repo.gobject;
import gi.repo.gst;
#endif
namespace GLib = gi::repository::GLib;
namespace Gst = gi::repository::Gst;
namespace GObject_ = gi::repository::GObject;
void
say(const std::string &msg)
{
std::cout << msg << std::endl;
}
void
die(const std::string &why)
{
std::cerr << why << std::endl;
exit(2);
}
class Player
{
typedef Player self;
GLib::MainLoop loop_;
std::string url_;
Gst::Element playbin_;
bool live_ = false;
Gst::Element vfilter_;
Gst::Element afilter_;
std::map<std::string, Gst::Element> filter_;
GLib::SourceScopedConnection monitor_;
GObject_::SignalConnection busconn_;
public:
Player(GLib::MainLoop loop, const std::string &url) : loop_(loop), url_(url)
{
playbin_ = Gst::ElementFactory::make("playbin");
assert(playbin_);
// get a property
// this one is known at introspection time
// (so type is known and suitable checked)
auto name = playbin_.property_name().get();
std::cout << "created playbin " << name << std::endl;
}
void start()
{
// set some property
playbin_.set_property("uri", url_);
// ... or several ones
playbin_.set_properties("volume", 0.5, "mute", false);
// ... non-primitive also possible
vfilter_ = Gst::ElementFactory::make("identity", "vfilter");
afilter_ = Gst::ElementFactory::make("identity", "afilter");
playbin_.set_properties("video-filter", vfilter_, "audio-filter", afilter_);
// connect some; find out what the source element is
// signal not known to introspection, so have to provide signature here
playbin_.connect<void(Gst::Element, Gst::Element)>(
"source-setup", gi::mem_fun(&self::on_source_setup, this));
// for a known signal, it is a bit easier to connect
auto bus = playbin_.get_bus();
bus.add_signal_watch();
// signal connect; using introspected (hence checked) signal definition
// could do without the slot step here and directly pass the lambda,
// but wrapping it this way allows combining this with the returned
// (plain) id into a scoped connection guard
auto slot = bus.signal_message().slot(gi::mem_fun(&self::on_message, this));
busconn_ =
gi::make_connection(bus.signal_message().connect(slot), slot, bus);
// again, if the guard is not desired; the following simply suffices
if (false)
bus.signal_message().connect(gi::mem_fun(&self::on_message, this));
// if the signal signature is somehow not supported
// then the following is a fallback manual method
// which still allows to use lambda (and such)
// and also provides some ownership management
if (false) {
auto h = [](GstBus *, GstMessage *) {
// dummy no-op
};
bus.connect_unchecked<void(GstBus *, GstMessage *)>("message", h);
}
// likewise, if a callback is to be used whose arguments are not supported
// or in a function that is not supported, then the following is a fallback
// which still allows to use lambda (and such)
// and also provides some ownership management
if (false) {
auto h = []() { return false; };
auto cb = new gi::callback_wrapper<gboolean(), false>(h);
// now the above pointer is managed as user data
// and will be suitable destroyed when needed
g_idle_add_full(0, &cb->wrapper, cb, &cb->destroy);
// in case of a typical single-use async callback (with no GDestroyNotify)
// use true as AUTODESTROY template parameter
// (then it will auto-clean up after invoking callback)
}
// alternatively, maybe there is some API that accepts a Closure
// the following are some convenient ways to get a closure
if (false) {
GLib::LogFunc h = [](gi::cstring_v log_domain,
GLib::LogLevelFlags log_level,
gi::cstring_v message) {
(void)log_domain;
(void)log_level;
(void)message;
};
GObject_::Closure::from_callback(h);
}
// likewise, but with a bit more manual (C++) signature specification
if (false) {
auto h = [](GObject_::Object, int) {};
GObject_::Closure::from_functor<void(GObject_::Object, int)>(h);
}
say("Setting pipeline to PAUSED ...");
auto ret = playbin_.set_state(Gst::State::PAUSED_);
if (ret == Gst::StateChangeReturn::FAILURE_) {
die("Pipeline does not want to pause");
} else if (ret == Gst::StateChangeReturn::NO_PREROLL_) {
say("Pipeline is live and does not need PREROLL ...");
live_ = true;
} else if (ret == Gst::StateChangeReturn::ASYNC_) {
say("Pipeline is PREROLLING ...");
}
// inspect after a while
GLib::timeout_add_seconds(2, [this]() {
inspect();
return GLib::SOURCE_REMOVE_;
});
// some regular progress reporting ...
GLib::SourceFunc func = [this]() {
progress();
return GLib::SOURCE_CONTINUE_;
};
// the introspected function returns a plain id,
// which can be used in the usual way to disconnect
// e.g. at destructor time of owning object
// alternatively, a helper scoped object can take care of that
monitor_ = gi::make_connection(GLib::timeout_add_seconds(1, func), func);
// other such make_connection variations exist;
// e.g. for a signal connection, a probe callback
// (and can easily be custom added)
// add a pad probe; use a casual lambda
// but not too casual, mind (dangling) references/pointers though
filter_["video"] = vfilter_;
filter_["audio"] = afilter_;
for (auto &&p : filter_) {
if (p.second) {
Gst::Pad pad = p.second.get_static_pad("sink");
auto name = p.first;
auto handler = [name](Gst::Pad p, Gst::PadProbeInfo_Ref info) {
auto s = p.get_path_string();
s += "received " + name + " buffer";
auto buffer = info.get_buffer();
if (buffer) {
s += " of size ";
s += std::to_string(buffer.get_size());
}
return Gst::PadProbeReturn::REMOVE_;
};
pad.add_probe(Gst::PadProbeType::BUFFER_, handler);
}
}
// shamelessly demo some helpers that aid in caps/value handling
auto caps = Gst::Caps::new_empty_simple("video/x-raw");
caps.set_value("width", GObject_::Value(Gst::IntRange(240, 320)));
caps.set_value("pixel-aspect-ratio", GObject_::Value(Gst::Fraction(4, 3)));
caps.set_value(
"framerate", GObject_::Value(Gst::FractionRange({25, 1}, 30)));
// retrieving pretty much the same way
auto s = caps.get_structure(0);
auto par = s.get_value("pixel-aspect-ratio").get_value<Gst::Fraction>();
// helpers also stream to string properly
std::ostringstream oss;
oss << par;
oss << (Gst::FlagSet(1, 1) == Gst::FlagSet(2, 1));
// silly test code to exercise some operators
// Rank override allows for succinct numeric conversion
if (+Gst::Rank::PRIMARY_ + 0) {
// flags support various typical operations
(void)(Gst::PadProbeType::BUFFER_ | Gst::PadProbeType::BUFFER_LIST_);
}
}
void stop()
{
if (playbin_)
playbin_.set_state(Gst::State::NULL_);
loop_.quit();
}
void on_message(Gst::Bus /*bus*/, Gst::Message_Ref msg)
{
auto &&src = msg.src_();
switch (msg.type_()) {
case Gst::MessageType::EOS_:
say("Got EOS from " + src.get_path_string());
stop();
break;
case Gst::MessageType::ERROR_: {
GLib::Error err;
gi::cstring debug;
msg.parse_error(&err, &debug);
say("Got error from " + src.get_path_string());
if (debug.size())
say("debug info:\n" + debug);
break;
}
case Gst::MessageType::STATE_CHANGED_:
/* only handle top-level case */
if (src != playbin_)
break;
Gst::State old, new_, pending;
msg.parse_state_changed(&old, &new_, &pending);
if (new_ == Gst::State::PAUSED_ && old == Gst::State::READY_)
playbin_.set_state(Gst::State::PLAYING_);
break;
default:
// never mind
break;
}
}
void on_source_setup(Gst::Element /*pb*/, Gst::Element src)
{
say("source is " + src.get_path_string());
}
void inspect()
{
std::ostringstream oss;
// this should work
auto bin = gi::object_cast<Gst::Bin>(playbin_);
assert(bin);
// a dynamically loaded element may implement a number of interfaces
// which is not known at compile-time
// the cast above can also cast to interface, which can be obtained as
// follows if known at introspection/compile time
auto cp = bin.interface_(gi::interface_tag<Gst::ChildProxy>());
oss << "player bin has " << cp.get_children_count() << " children"
<< std::endl;
// get some properties (dynamically, i.e. not known at introspection
// time) type will have to match (i.e. transformable) at runtime
auto n_v = playbin_.get_property<int>("n-video");
auto n_a = playbin_.get_property<int>("n-audio");
// minimal stuff
oss << "sample streams: video=" << n_v << ", audio=" << n_a << std::endl;
// show some tag info
for (auto &&p :
std::map<std::string, int>{{"video", n_v}, {"audio", n_a}}) {
for (int i = 0; i < p.second; ++i) {
// the argument's type should match the signal definition
// (cast if needed to make it so)
auto action = std::string("get-");
action += p.first + "-tags";
auto taglist = playbin_.emit<Gst::TagList>(action, i);
if (!taglist)
continue;
auto ntags = taglist.n_tags();
oss << p.first << " stream " << i << std::endl;
for (int j = 0; j < ntags; ++j) {
auto tname = taglist.nth_tag_name(j);
auto value = taglist.get_value_index(tname, 0);
#if GI_CONFIG_EXCEPTIONS
try {
#endif
auto sval = value.transform_value<std::string>();
oss << " " << tname << ": " << sval << std::endl;
#if GI_CONFIG_EXCEPTIONS
} catch (...) {
// could be object or otherwise, never mind
}
#endif
}
}
}
// should also have caps here by now
// there are other ways to obtain this, but let's go this way here
for (auto &&p : filter_) {
if (p.second) {
Gst::Pad pad = p.second.get_static_pad("sink");
auto caps = pad.get_current_caps();
oss << p.first << " caps: " << caps.to_string() << std::endl;
}
}
say(oss.str());
say("Playbin elements:");
// Gst::Iterator could be used with native interface
// but a helper wrapper has been provided that supports ease-of-use as
// in ...
for (auto &&el : Gst::IteratorAdapter<Gst::Element>(bin.iterate_recurse()))
say(el.get_path_string());
say("");
}
static std::string time_to_str(Gst::ClockTime time)
{
// could be done otherwise
// but we have native C access at hand, so let's use that
auto s = g_strdup_printf("%" GST_TIME_FORMAT, GST_TIME_ARGS(time));
std::string ret(s);
g_free(s);
return ret;
}
void progress()
{
bool ok = true;
gint64 duration = -1, position = -1;
ok &= playbin_.query_duration(Gst::Format::TIME_, &duration);
ok &= playbin_.query_position(Gst::Format::TIME_, &position);
std::ostringstream oss;
if (ok) {
oss << "Duration: " << time_to_str(duration)
<< ", Position: " << time_to_str(position);
} else {
oss << "No progress info available";
}
say(oss.str());
}
};
#ifdef GI_CLASS_IMPL
// not used in the above, but serves as a subclass example
class ChattyBin : public Gst::impl::BinImpl
{
public:
#if 0
// this part is only needed if there is some conflict
// (among members of class and/or interfaces)
// otherwise it should be auto-detected
struct DefinitionData
{
GI_DEFINES_MEMBER(BinClassDef, add_element, true)
};
#endif
ChattyBin() : Gst::impl::BinImpl(this) {}
bool add_element_(Gst::Element element) noexcept override
{
say("adding element " + element.name_() + '\n');
return Gst::impl::BinImpl::add_element_(element);
}
};
#endif
int
main(int argc, char **argv)
{
if (argc < 2)
die("missing argument");
// C signature fits C main best anyway
gst_init(&argc, &argv);
std::string url = argv[1];
// make it URL if not so
if (!Gst::Uri::is_valid(url)) {
#if GI_CONFIG_EXCEPTIONS
try {
#endif
url = gi::expect(Gst::filename_to_uri(url));
#if GI_CONFIG_EXCEPTIONS
} catch (const GLib::Error &ex) {
die(ex.what());
}
#endif
}
say("Playing " + url);
// simply local var will do here
auto loop = GLib::MainLoop::new_();
Player player(loop, url);
// schedule start
GLib::idle_add([&] {
player.start();
return GLib::SOURCE_REMOVE_;
});
// ... and auto end after a while
GLib::timeout_add_seconds(10, [&] {
player.stop();
return GLib::SOURCE_REMOVE_;
});
loop.run();
}

View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkListStore" id="liststore1">
<columns>
<column type="gchararray"/>
<column type="gchararray"/>
<column type="gint"/>
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">John</col>
<col id="1" translatable="yes">Doe</col>
<col id="2">25</col>
<col id="3" translatable="yes">This is the John Doe row</col>
</row>
<row>
<col id="0" translatable="yes">Mary</col>
<col id="1" translatable="yes">Unknown</col>
<col id="2">50</col>
<col id="3" translatable="yes">This is the Mary Unknown row</col>
</row>
</data>
</object>
<object class="GtkWindow" id="window1">
<property name="default-height">250</property>
<property name="default-width">440</property>
<property name="title" translatable="yes">Gtk::Builder demo</property>
<child>
<object class="GtkBox" id="vbox1">
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="toolbar1">
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Help</property>
<property name="tooltip-text" translatable="yes">Help</property>
<property name="action-name">win.help</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkTreeView" id="treeview1">
<property name="model">liststore1</property>
<property name="tooltip-column">3</property>
<child>
<object class="GtkTreeViewColumn" id="column1">
<property name="title">Name</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="column2">
<property name="title">Surname</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="column3">
<property name="title">Age</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<!-- INSERT -->
<child>
<object class="GtkStatusbar" id="statusbar1"/>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -0,0 +1,2 @@
#define GI_INCLUDE_IMPL 1
#include <gtk/gtk.hpp>

View File

@@ -0,0 +1,429 @@
#ifndef USE_GI_MODULE
// no inline as the implementation is in another TU
// #define GI_INLINE 1
#include <gtk/gtk.hpp>
#else
// optional, but recommended if gi macros are used
#include <gi/gi_inc.hpp>
// we use some gtk macros
#include <gtk/gtk.h>
// import recursive module
import gi.repo.gtk.rec;
#endif
// adapt to API as needed
#if GTK_CHECK_VERSION(4, 0, 0)
#define GTK4 1
#endif
#include <fstream>
#include <iostream>
#include <tuple>
#include <vector>
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
namespace Gtk = gi::repository::Gtk;
static GLib::MainLoop loop;
#ifdef GI_CLASS_IMPL
// based on python-gtk3 example
// https://python-gtk-3-tutorial.readthedocs.io/en/latest/treeview.html
// list of tuples for each software,
// containing the software name, initial release, and main programming languages
const std::vector<std::tuple<std::string, int, std::string>> software_list{
std::make_tuple("Firefox", 2002, "C++"),
std::make_tuple("Eclipse", 2004, "Java"),
std::make_tuple("Pitivi", 2004, "Python"),
std::make_tuple("Netbeans", 1996, "Java"),
std::make_tuple("Chrome", 2008, "C++"),
std::make_tuple("Filezilla", 2001, "C++"),
std::make_tuple("Bazaar", 2005, "Python"),
std::make_tuple("Git", 2005, "C"),
std::make_tuple("Linux Kernel", 1991, "C"),
std::make_tuple("GCC", 1987, "C"),
std::make_tuple("Frostwire", 2004, "Java")};
class TreeViewFilterWindow : public Gtk::impl::WindowImpl
{
typedef TreeViewFilterWindow self_type;
Gtk::ListStore store_;
Gtk::TreeModelFilter language_filter_;
std::string current_filter_language_;
public:
TreeViewFilterWindow() : Gtk::impl::WindowImpl(this)
{
Gtk::Window &self = *(this);
self.set_title("TreeView filter demo");
#ifdef GTK4
#else
self.set_border_width(10);
#endif
// set up the grid in which elements are positioned
auto grid = Gtk::Grid::new_();
grid.set_column_homogeneous(true);
grid.set_row_homogeneous(true);
#ifdef GTK4
self.set_child(grid);
#if 0
// this could work, but the annotation for .load_from_data
// is too unstable across versions
{ // migrate call above to CSS style
const char *css = "grid { margin: 10px; }";
auto provider = Gtk::CssProvider::new_();
provider.load_from_data((guint8 *)css, -1);
grid.get_style_context().add_provider(
provider, Gtk::STYLE_PROVIDER_PRIORITY_USER_);
}
#endif
#else
self.add(grid);
#endif
// create ListStore model
store_ = Gtk::ListStore::new_type_<std::string, int, std::string>();
for (auto &e : software_list) {
auto it = store_.append();
GObject_::Value cols[] = {std::get<0>(e), std::get<1>(e), std::get<2>(e)};
for (unsigned i = 0; i < G_N_ELEMENTS(cols); ++i) {
store_.set_value(it, i, cols[i]);
}
}
// create the filter, feeding it with the liststore model
auto treemodel = store_.interface_(gi::interface_tag<Gtk::TreeModel>());
language_filter_ =
gi::object_cast<Gtk::TreeModelFilter>(treemodel.filter_new(nullptr));
// set the filter function
language_filter_.set_visible_func(
gi::mem_fun(&self_type::language_filter_func, this));
// create the treeview, make it use the filter as a model, and add
// columns
auto treeview = Gtk::TreeView::new_with_model(language_filter_);
int i = 0;
for (auto &e : {"Software", "Release Year", "Programming Language"}) {
auto renderer = Gtk::CellRendererText::new_();
auto column = Gtk::TreeViewColumn::new_(e, renderer, {{"text", i}});
treeview.append_column(column);
++i;
}
// create buttons to filter by programming language, and set up their
// events
std::vector<Gtk::Widget> buttons;
for (auto &prog_language : {"Java", "C", "C++", "Python", "None"}) {
auto button = Gtk::Button::new_with_label(prog_language);
buttons.push_back(button);
button.signal_clicked().connect(
gi::mem_fun(&self_type::on_selection_button_clicked, this));
}
// set up the layout;
// put the treeview in a scrollwindow, and the buttons in a row
auto scrollable_treelist = Gtk::ScrolledWindow::new_();
scrollable_treelist.set_vexpand(true);
grid.attach(scrollable_treelist, 0, 0, 8, 10);
grid.attach_next_to(
buttons[0], scrollable_treelist, Gtk::PositionType::BOTTOM_, 1, 1);
auto it = buttons.begin() + 1;
while (it != buttons.end()) {
grid.attach_next_to(*it, *(it - 1), Gtk::PositionType::RIGHT_, 1, 1);
++it;
}
#ifdef GTK4
scrollable_treelist.set_child(treeview);
self.show();
#else
scrollable_treelist.add(treeview);
self.show_all();
#endif
}
bool language_filter_func(Gtk::TreeModel filter, Gtk::TreeIter_Ref it) const
{
if (current_filter_language_.empty() || current_filter_language_ == "None")
return true;
return current_filter_language_ ==
filter.get_value(it, 2).get_value<std::string>();
}
void on_selection_button_clicked(Gtk::Button button)
{
// set the current language filter to the button's label
current_filter_language_ = button.get_label();
std::cout << current_filter_language_ << " language selected!" << std::endl;
// update the filter, which updates in turn the view
language_filter_.refilter();
}
};
// other part based on gtkmm builder example
namespace Gio = gi::repository::Gio;
auto templ = R"|(
<interface>
<template class="FooWidget" parent="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">4</property>
<child>
<object class="GtkButton" id="hello_button">
<property name="label">Hello World</property>
<signal name="clicked" handler="hello_button_clicked"/>
<signal name="clicked" handler="hello_button_clicked_object" object="label" swapped="no"/>
<signal name="clicked" handler="hello_button_clicked_object_swapped" object="label" swapped="yes"/>
</object>
</child>
<child>
<object class="GtkButton" id="goodbye_button">
<property name="label">Goodbye World</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="label">Greetings World</property>
</object>
</child>
</template>
</interface>
)|";
auto templ_child = R"|(
<child>
<object class="FooWidget" id="foowidget1"/>
</child>
)|";
using WidgetTemplateHelper = Gtk::impl::WidgetTemplateHelper;
class FooWidget : public Gtk::impl::BoxImpl, public WidgetTemplateHelper
{
using self_type = FooWidget;
public:
static void custom_class_init(GtkBoxClass *klass, gpointer)
{
// this is similar to the C case
// plain C functions are used, as klass struct has no direct equivalent
auto wklass = GTK_WIDGET_CLASS(klass);
auto bytes = GLib::Bytes::new_static((const guint8 *)templ, strlen(templ));
gtk_widget_class_set_template(wklass, bytes.gobj_());
gtk_widget_class_bind_template_child_full(wklass, "hello_button", TRUE, 0);
// chain up
WidgetTemplateHelper::custom_class_init<gi::register_type<self_type>>(
klass, nullptr);
}
public:
Gtk::Button hello_;
void on_hello(Gtk::Button) { std::cout << "Hi" << std::endl; }
void on_hello_tail(Gtk::Button b, Gtk::Label l)
{
g_assert(l.gobj_type_() == GTK_TYPE_LABEL);
g_assert(b.gobj_type_() == GTK_TYPE_BUTTON);
std::cout << "Hi tail" << std::endl;
}
void on_hello_head(Gtk::Label l, Gtk::Button b)
{
g_assert(l.gobj_type_() == GTK_TYPE_LABEL);
g_assert(b.gobj_type_() == GTK_TYPE_BUTTON);
std::cout << "Hi head" << std::endl;
}
FooWidget(const InitData &id)
: Gtk::impl::BoxImpl(this, id, "FooWidget"),
WidgetTemplateHelper(object_())
{
// skip registration case
if (!id) {
// as we have defined a get_type_() below, that should not happen
g_assert_not_reached();
return;
}
// locate object
hello_ = gi::object_cast<Gtk::Button>(
get_template_child(gobj_type_(), "hello_button"));
// setup signal
// NOTE the "manual" signature here should suitably match
// (also considering object/swapped flags, etc)
auto ok = set_handler<void(Gtk::Button)>(
"hello_button_clicked", gi::mem_fun(&self_type::on_hello, this));
g_assert(ok);
ok = set_handler<void(Gtk::Button, Gtk::Label), ConnectObject::TAIL>(
"hello_button_clicked_object",
gi::mem_fun(&self_type::on_hello_tail, this));
g_assert(ok);
ok = set_handler<void(Gtk::Label, Gtk::Button), ConnectObject::HEAD>(
"hello_button_clicked_object_swapped",
gi::mem_fun(&self_type::on_hello_head, this));
g_assert(ok);
}
static GType get_type_()
{
return register_type_<FooWidget>("FooWidget", 0, {}, {}, {});
}
};
class ExampleWindow : public Gtk::impl::WindowImpl
{
using self_type = ExampleWindow;
public:
ExampleWindow(Gtk::Window base, Gtk::Builder builder)
: Gtk::impl::WindowImpl(base, this, "ExampleWindow")
// custom name parameter is optional, but can be provided if used in .ui
{
(void)builder;
setup();
}
// name parameter is required and specifies registered type as-is
// (so some proper namespace prefix is advisable in real case)
ExampleWindow(const InitData &id)
: Gtk::impl::WindowImpl(this, id, "ExampleWindow")
{
// skip registration case
if (id)
setup();
}
void setup()
{
auto actions = Gio::SimpleActionGroup::new_();
auto am = Gio::ActionMap(actions);
auto action = Gio::SimpleAction::new_("help");
action.signal_activate().connect(gi::mem_fun(&self_type::on_help, this));
am.add_action(action);
insert_action_group("win", actions);
}
void on_help(Gio::Action, GLib::Variant) { std::cout << "Help" << std::endl; }
// subclass_type == 0; UI specifies GtkWindow
// subclass_type < 0; UI specifies GIOBJECT__ExampleWindow
// in either cases above; manual C++ association is needed
// (uses first constructor)
// -> NOT recommended
// subclass_type > 0; UI specifies ExampleWindow;
// all is properly created by (C-side) instance construction
// (uses second constructor)
// -> recommended
// subclass_type > 1; also insert a FooWidget
// (also all created by C-side construction)
static Gtk::Window build(int subclass_type)
{
const char *UIFILE = G_STRINGIFY(EXAMPLES_DIR) "/gtk-builder.ui";
const char *WINID = "window1";
auto builder = Gtk::Builder::new_();
// if the subtype has additional functionality (e.g. method overrides)
// then builder should instantiate using that type,
// otherwise base type suffices
// NOTE in the former case, there is a "short time" that the GObject side
// exists without a corresponding C++ side, so any attempt to use the
// extended parts (e.g. method calls) will be unfortunate
if (subclass_type) {
std::string WINCLASS = "GtkWindow";
std::ifstream f(UIFILE, std::ios::binary);
std::string ui;
std::getline(f, ui, '\0');
auto index = ui.find(WINCLASS);
assert(index != ui.npos);
ui.replace(ui.begin() + index, ui.begin() + index + WINCLASS.size(),
subclass_type < 0 ? "GIOBJECT__ExampleWindow" : "ExampleWindow");
if (subclass_type > 1) {
// sprinkle a template instance
std::string MARKER = "<!-- INSERT -->";
index = ui.find(MARKER);
ui.replace(ui.begin() + index, ui.begin() + index + MARKER.size(),
templ_child);
// ensure type registered
gi::register_type<FooWidget>();
}
// now we got a UI file that references the subclass type
// ensure the latter is registered
if (subclass_type < 0) {
// use a temporary instance
gi::make_ref<ExampleWindow, gi::construct_cpp_t>(nullptr, builder);
} else {
// a new cleaner way
gi::register_type<ExampleWindow>();
}
builder.add_from_string(ui, -1);
} else {
builder.add_from_file(UIFILE);
}
if (false) {
// some compile checks
builder.get_object(WINID);
builder.get_object<Gtk::Window>(WINID);
}
if (subclass_type > 1) {
// verify proper C++ side setup
auto foo_obj =
builder.get_object<FooWidget::baseclass_type>("foowidget1");
assert(foo_obj);
auto foo = gi::ref_ptr_cast<FooWidget>(foo_obj);
assert(foo);
assert(foo->hello_);
}
// the special _derived may be needed to setup C++ side and associate
return subclass_type > 0 ? builder.get_object<Gtk::Window>(WINID)
: builder.get_object_derived<self_type>(WINID);
}
};
#endif // GI_CLASS_IMPL
int
main(int argc, char **argv)
{
#ifdef GTK4
(void)argc;
(void)argv;
gtk_init();
#else
gtk_init(&argc, &argv);
#endif
loop = GLib::MainLoop::new_();
// recommended general approach iso stack based
// too much vmethod calling which is not safe for plain case
Gtk::Window win;
#ifdef GI_CLASS_IMPL
// silly compile/link check
static_assert(gi::transfer_full.value == 1, "");
if (argc == gi::transfer_full.value) {
win = gi::make_ref<TreeViewFilterWindow>();
} else {
win = ExampleWindow::build(std::stoi(argv[1]));
}
// TODO auto-handle arg ignore ??
#ifdef GTK4
win.signal_close_request().connect([](Gtk::Window) {
loop.quit();
return true;
});
win.show();
#else
win.signal_destroy().connect([](Gtk::Widget) { loop.quit(); });
win.show_all();
#endif
#else // GI_CLASS_IMPL
(void)win;
#endif
loop.run();
}

View File

@@ -0,0 +1,31 @@
# Configuration file for EditorConfig, see https://EditorConfig.org
# Ignore any other files further up in the file system
root = true
# All files:
[*]
# Let git determine line ending: end_of_line = lf
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
# Markdown files: keep trailing space-pair as line-break
[*.md]
trim_trailing_whitespace = false
# Python scripts:
[*.py]
# YAML scripts:
[*.yml]
indent_size = 2
# Makefiles: Tab indentation (no size specified)
[Makefile]
indent_style = tab
# C, C++ source files:
[*.{h,hpp,c,cpp}]

View File

@@ -0,0 +1,26 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for CodeBlocks
*.cbp text eol=lf
*.workspace text eol=lf
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@@ -0,0 +1,97 @@
name: CI
env:
PROJECT: EXPECTED_LITE
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
gcc:
strategy:
matrix:
version: [8, 9, 10, 11]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install GCC ${{ matrix.version }}
run: sudo apt-get install -y gcc-${{ matrix.version }} g++-${{ matrix.version }}
- name: Configure tests
env:
CXX: g++-${{ matrix.version }}
run: cmake -S . -B build
-D CMAKE_BUILD_TYPE:STRING=Release
-D ${{ env.PROJECT }}_OPT_SELECT_NONSTD=ON
-D ${{ env.PROJECT }}_OPT_BUILD_TESTS=ON
-D ${{ env.PROJECT }}_OPT_BUILD_EXAMPLES=OFF
- name: Build tests
run: cmake --build build -j 4
- name: Run tests
working-directory: build
run: ctest --output-on-failure -j 4
clang:
strategy:
matrix:
version: [8, 9, 10, 11, 12]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Clang ${{ matrix.version }}
run: sudo apt-get install -y clang-${{ matrix.version }}
- name: Configure tests
env:
CXX: clang-${{ matrix.version }}
run: cmake -S . -B build
-D CMAKE_CXX_COMPILER=clang++
-D CMAKE_BUILD_TYPE:STRING=Release
-D ${{ env.PROJECT }}_OPT_SELECT_NONSTD=ON
-D ${{ env.PROJECT }}_OPT_BUILD_TESTS=ON
-D ${{ env.PROJECT }}_OPT_BUILD_EXAMPLES=OFF
- name: Build tests
run: cmake --build build -j 4
- name: Run tests
working-directory: build
run: ctest --output-on-failure -j 4
msvc:
strategy:
matrix:
os: [windows-2019, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Configure tests
run: cmake -S . -B build
-D ${{ env.PROJECT }}_OPT_SELECT_NONSTD=ON
-D ${{ env.PROJECT }}_OPT_BUILD_TESTS=ON
-D ${{ env.PROJECT }}_OPT_BUILD_EXAMPLES=OFF
- name: Build tests
run: cmake --build build --config Release -j 4
- name: Run tests
working-directory: build
run: ctest -C Release --output-on-failure -j 4

View File

@@ -0,0 +1,41 @@
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
test/expected.t
# Build folder
/build/
# CodeBlocks IDE files
*.layout
# Visual Studio Code
/.vscode/
# Visual Studio
/.vs/

View File

@@ -0,0 +1,4 @@
[bugtraq]
url = https://github.com/martinmoene/expected-lite/issues/%BUGID%
number = true
logregex = "(\\s*(,|and)?\\s*#\\d+)+\n(\\d+)"

View File

@@ -0,0 +1,166 @@
os: linux
dist: trusty
sudo: false
group: travis_latest
language: c++
cache: ccache
addons:
apt:
sources: &apt_sources
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.5
- llvm-toolchain-precise-3.6
- llvm-toolchain-precise-3.7
- llvm-toolchain-precise-3.8
- llvm-toolchain-trusty-3.9
- llvm-toolchain-trusty-4.0
- llvm-toolchain-trusty-5.0
- llvm-toolchain-trusty-6.0
matrix:
include:
- os: linux
env: COMPILER=g++-4.8
compiler: gcc
addons: &gcc4_8
apt:
packages: ["g++-4.8", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-4.9
compiler: gcc
addons: &gcc4_9
apt:
packages: ["g++-4.9", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-5
compiler: gcc
addons: &gcc5
apt:
packages: ["g++-5", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-6
compiler: gcc
addons: &gcc6
apt:
packages: ["g++-6", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-7
compiler: gcc
addons: &gcc7
apt:
packages: ["g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=g++-8
compiler: gcc
addons: &gcc8
apt:
packages: ["g++-8", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.5
compiler: clang
addons: &clang3_5
apt:
packages: ["clang-3.5", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.6
compiler: clang
addons: &clang3_6
apt:
packages: ["clang-3.6", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.7
compiler: clang
addons: &clang3-7
apt:
packages: ["clang-3.7", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.8
compiler: clang
addons: &clang3_8
apt:
packages: ["clang-3.8", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-3.9
compiler: clang
addons: &clang3_9
apt:
packages: ["clang-3.9", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-4.0
compiler: clang
addons: &clang4_0
apt:
packages: ["clang-4.0", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-5.0
compiler: clang
addons: &clang5_0
apt:
packages: ["clang-5.0", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: linux
env: COMPILER=clang++-6.0
compiler: clang
addons: &clang6_0
apt:
packages: ["clang-6.0", "g++-7", "python3-pip", "lcov"]
sources: *apt_sources
- os: osx
osx_image: xcode7.3
compiler: clang
env: COMPILER='clang++'
- os: osx
osx_image: xcode8
compiler: clang
env: COMPILER='clang++'
- os: osx
osx_image: xcode9
compiler: clang
env: COMPILER='clang++'
- os: osx
osx_image: xcode10
compiler: clang
env: COMPILER='clang++'
allow_failures:
- env: COMPILER=clang++-3.5
fast_finish: true
script:
- export CXX=${COMPILER}
- JOBS=2 # Travis machines have 2 cores.
- mkdir build && cd build
- cmake -G "Unix Makefiles" -DEXPECTED_LITE_OPT_SELECT_NONSTD=ON -DEXPECTED_LITE_OPT_BUILD_TESTS=ON -DEXPECTED_LITE_OPT_BUILD_EXAMPLES=OFF ..
- cmake --build . -- -j${JOBS}
- ctest --output-on-failure -j${JOBS}

View File

@@ -0,0 +1,5 @@
Changes for expected lite
version 0.0 2016-03-13
- Initial code commit.

View File

@@ -0,0 +1,130 @@
# Copyright 2016-2018 by Martin Moene
#
# https://github.com/martinmoene/expected-lite
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
cmake_minimum_required( VERSION 3.5 FATAL_ERROR )
# expected-lite project and version, updated by script/update-version.py:
project(
expected_lite
VERSION 0.6.2
# DESCRIPTION "Expected objects in C++11 and later in a single-file header-only library"
# HOMEPAGE_URL "https://github.com/martinmoene/expected-lite"
LANGUAGES CXX )
# Package information:
set( unit_name "expected" )
set( package_nspace "nonstd" )
set( package_name "${unit_name}-lite" )
set( package_version "${${PROJECT_NAME}_VERSION}" )
message( STATUS "Project '${PROJECT_NAME}', package '${package_name}' version: '${package_version}'")
# Toplevel or subproject:
if ( CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME )
set( expected_IS_TOPLEVEL_PROJECT TRUE )
else()
set( expected_IS_TOPLEVEL_PROJECT FALSE )
endif()
# If toplevel project, enable building and performing of tests, disable building of examples:
option( EXPECTED_LITE_OPT_BUILD_TESTS "Build and perform expected-lite tests" ${expected_IS_TOPLEVEL_PROJECT} )
option( EXPECTED_LITE_OPT_BUILD_EXAMPLES "Build expected-lite examples" OFF )
set( EXPEXTED_P0323R "99" STRING "Specify proposal revision compatibility (99: latest)" )
option( EXPECTED_LITE_OPT_SELECT_STD "Select std::expected" OFF )
option( EXPECTED_LITE_OPT_SELECT_NONSTD "Select nonstd::expected" OFF )
# If requested, build and perform tests, build examples:
if ( EXPECTED_LITE_OPT_BUILD_TESTS )
enable_testing()
add_subdirectory( test )
endif()
if ( EXPECTED_LITE_OPT_BUILD_EXAMPLES )
add_subdirectory( example )
endif()
#
# Interface, installation and packaging
#
# CMake helpers:
include( GNUInstallDirs )
include( CMakePackageConfigHelpers )
# Interface library:
add_library(
${package_name} INTERFACE )
add_library(
${package_nspace}::${package_name} ALIAS ${package_name} )
target_include_directories(
${package_name}
INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" )
# Package configuration:
# Note: package_name and package_target are used in package_config_in
set( package_folder "${package_name}" )
set( package_target "${package_name}-targets" )
set( package_config "${package_name}-config.cmake" )
set( package_config_in "${package_name}-config.cmake.in" )
set( package_config_version "${package_name}-config-version.cmake" )
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/${package_config_in}"
"${CMAKE_CURRENT_BINARY_DIR}/${package_config}"
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_folder}"
)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/${package_config_version}.in"
"${CMAKE_CURRENT_BINARY_DIR}/${package_config_version}" @ONLY
)
# Installation:
install(
TARGETS ${package_name}
EXPORT ${package_target}
# INCLUDES DESTINATION "${...}" # already set via target_include_directories()
)
install(
EXPORT ${package_target}
NAMESPACE ${package_nspace}::
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_folder}"
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/${package_config}"
"${CMAKE_CURRENT_BINARY_DIR}/${package_config_version}"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${package_folder}"
)
install(
DIRECTORY "include/"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
export(
EXPORT ${package_target}
NAMESPACE ${package_nspace}::
FILE "${CMAKE_CURRENT_BINARY_DIR}/${package_name}-targets.cmake"
)
# end of file

View File

@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
Plan
----
- [ ] Implement expected.then() etc.
- [x] Write CMake files
- [x] Check code against current Dxxxxr0 and adapt
- [x] Expand README.md
- [x] Expand test/expected.t.cpp
- [x] Improve use of travis matrix

View File

@@ -0,0 +1,459 @@
# expected lite: expected objects for C++11 and later
[![Language](https://img.shields.io/badge/C%2B%2B-11-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![License](https://img.shields.io/badge/license-BSL-blue.svg)](https://opensource.org/licenses/BSL-1.0) [![Build Status](https://github.com/martinmoene/expected-lite/actions/workflows/ci.yml/badge.svg)](https://github.com/martinmoene/expected-lite/actions/workflows/ci.yml) [![Build Status](https://travis-ci.org/martinmoene/expected-lite.svg?branch=master)](https://travis-ci.org/martinmoene/expected-lite) [![Build status](https://ci.appveyor.com/api/projects/status/sle31w7obrm8lhe1?svg=true)](https://ci.appveyor.com/project/martinmoene/expected-lite) [![Version](https://badge.fury.io/gh/martinmoene%2Fexpected-lite.svg)](https://github.com/martinmoene/expected-lite/releases) [![download](https://img.shields.io/badge/latest-download-blue.svg)](https://raw.githubusercontent.com/martinmoene/expected-lite/master/include/nonstd/expected.hpp) [![Conan](https://img.shields.io/badge/on-conan-blue.svg)](https://conan.io/center/expected-lite) [![Try it online](https://img.shields.io/badge/on-wandbox-blue.svg)](https://wandbox.org/permlink/MnnwqOtE8ZQ4rRsv) [![Try it on godbolt online](https://img.shields.io/badge/on-godbolt-blue.svg)](https://godbolt.org/z/9BuMZx)
*expected lite* is a single-file header-only library for objects that either represent a valid value or an error that you can pass by value. It is intended for use with C++11 and later. The library is based on the [std:&#58;expected](http://wg21.link/p0323) proposal [1] .
**Contents**
- [Example usage](#example-usage)
- [In a nutshell](#in-a-nutshell)
- [License](#license)
- [Dependencies](#dependencies)
- [Installation](#installation)
- [Synopsis](#synopsis)
- [Comparison with like types](#comparison)
- [Reported to work with](#reported-to-work-with)
- [Implementation notes](#implementation-notes)
- [Other implementations of expected](#other-implementations-of-expected)
- [Notes and references](#notes-and-references)
- [Appendix](#appendix)
## Example usage
```Cpp
#include "nonstd/expected.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
using namespace nonstd;
using namespace std::literals;
auto to_int( char const * const text ) -> expected<int, std::string>
{
char * pos = nullptr;
auto value = strtol( text, &pos, 0 );
if ( pos != text ) return value;
else return make_unexpected( "'"s + text + "' isn't a number" );
}
int main( int argc, char * argv[] )
{
auto text = argc > 1 ? argv[1] : "42";
auto ei = to_int( text );
if ( ei ) std::cout << "'" << text << "' is " << *ei << ", ";
else std::cout << "Error: " << ei.error();
}
```
### Compile and run
```
prompt> g++ -std=c++14 -Wall -I../include -o 01-basic.exe 01-basic.cpp && 01-basic.exe 123 && 01-basic.exe abc
'123' is 123, Error: 'abc' isn't a number
```
## In a nutshell
**expected lite** is a single-file header-only library to represent value objects that either contain a valid value or an error. The library is a partly implementation of the proposal for [std:&#58;expected](http://wg21.link/p0323) [1,2,3] for use with C++11 and later.
**Some Features and properties of expected lite** are ease of installation (single header), default and explicit construction of an expected, construction and assignment from a value that is convertible to the underlying type, copy- and move-construction and copy- and move-assignment from another expected of the same type, testing for the presence of a value, operators for unchecked access to the value or the error (pointer or reference), value() and value_or() for checked access to the value, relational operators, swap() and various factory functions.
*expected lite* shares the approach to in-place tags with [any-lite](https://github.com/martinmoene/any-lite), [optional-lite](https://github.com/martinmoene/optional-lite) and with [variant-lite](https://github.com/martinmoene/variant-lite) and these libraries can be used together.
**Not provided** are reference-type expecteds. *expected lite* doesn't honour triviality of value and error types. *expected lite* doesn't handle overloaded *address of* operators.
For more examples, see [1].
## License
*expected lite* is distributed under the [Boost Software License](https://github.com/martinmoene/XXXX-lite/blob/master/LICENSE.txt).
## Dependencies
*expected lite* has no other dependencies than the [C++ standard library](http://en.cppreference.com/w/cpp/header).
## Installation
*expected lite* is a single-file header-only library. Put `expected.hpp` directly into the project source tree or somewhere reachable from your project.
## Synopsis
**Contents**
- [Configuration](#configuration)
- [Types in namespace nonstd](#types-in-namespace-nonstd)
- [Interface of expected](#interface-of-expected)
- [Algorithms for expected](#algorithms-for-expected)
- [Interface of unexpected_type](#interface-of-unexpected_type)
- [Algorithms for unexpected_type](#algorithms-for-unexpected_type)
### Configuration
#### Tweak header
If the compiler supports [`__has_include()`](https://en.cppreference.com/w/cpp/preprocessor/include), *expected lite* supports the [tweak header](https://vector-of-bool.github.io/2020/10/04/lib-configuration.html) mechanism. Provide your *tweak header* as `nonstd/expected.tweak.hpp` in a folder in the include-search-path. In the tweak header, provide definitions as documented below, like `#define expected_CPLUSPLUS 201103L`.
#### Standard selection macro
\-D<b>nsel\_CPLUSPLUS</b>=199711L
Define this macro to override the auto-detection of the supported C++ standard, or if your compiler does not set the `__cplusplus` macro correctly.
#### Select `std::expected` or `nonstd::expected`
At default, *expected lite* uses `std::expected` if it is available and lets you use it via namespace `nonstd`. You can however override this default and explicitly request to use `std::expected` or expected lite's `nonstd::expected` as `nonstd::expected` via the following macros.
-D<b>nsel\_CONFIG\_SELECT\_EXPECTED</b>=nsel_EXPECTED_DEFAULT
Define this to `nsel_EXPECTED_STD` to select `std::expected` as `nonstd::expected`. Define this to `nsel_EXPECTED_NONSTD` to select `nonstd::expected` as `nonstd::expected`. Default is undefined, which has the same effect as defining to `nsel_EXPECTED_DEFAULT`.
-D<b>nsel\_P0323R</b>=7 *(default)*
Define this to the proposal revision number to control the presence and behavior of features (see tables). Default is 7 for the latest revision.
#### Disable C++ exceptions
-D<b>nsel\_CONFIG\_NO\_EXCEPTIONS</b>=0
Define this to 1 if you want to compile without exceptions. If not defined, the header tries and detect if exceptions have been disabled (e.g. via `-fno-exceptions` or `/kernel`). Default determined in header.
#### Enable SEH exceptions
-D<b>nsel\_CONFIG\_NO\_EXCEPTIONS\_SEH</b>=0
Define this to 1 or 0 to control the use of SEH when C++ exceptions are disabled (see above). If not defined, the header tries and detect if SEH is available if C++ exceptions have been disabled (e.g. via `-fno-exceptions` or `/kernel`). Default determined in header.
#### Enable compilation errors
\-D<b>nsel\_CONFIG\_CONFIRMS\_COMPILATION\_ERRORS</b>=0
Define this macro to 1 to experience the by-design compile-time errors of the library in the test suite. Default is 0.
### Types in namespace nonstd
| Purpose | Type | Note / Object |
|-----------------|------|---------------|
| Expected | template&lt;typename T, typename E = std::exception_ptr><br>class **expected**; | nsel_P0323 <= 2 |
| Expected | template&lt;typename T, typename E><br>class **expected**; | nsel_P0323 > 2 |
| Error type | template&lt;typename E><br>class **unexpected_type**; | &nbsp; |
| Error type | template&lt;><br>class **unexpected_type**&lt;std::exception_ptr>; | nsel_P0323 <= 2 |
| Error type | template&lt;typename E><br>class **unexpected**; | >= C++17 |
| Traits | template&lt;typename E><br>struct **is_unexpected**; | nsel_P0323 <= 3 |
| In-place value construction | struct **in_place_t**; | in_place_t in_place{}; |
| In-place error construction | struct **in_place_unexpected_t**; | in_place_unexpected_t<br>unexpect{}; |
| In-place error construction | struct **in_place_unexpected_t**; | in_place_unexpected_t<br>in_place_unexpected{}; |
| Error reporting | class **bad_expected_access**; |&nbsp; |
### Interface of expected
| Kind | Method | Result |
|--------------|-------------------------------------------------------------------------|--------|
| Construction | [constexpr] **expected**() noexcept(...) | an object with default value |
| &nbsp; | [constexpr] **expected**( expected const & other ) | initialize to contents of other |
| &nbsp; | [constexpr] **expected**( expected && other ) | move contents from other |
| &nbsp; | [constexpr] **expected**( value_type const & value ) | initialize to value |
| &nbsp; | [constexpr] **expected**( value_type && value ) noexcept(...) | move from value |
| &nbsp; | [constexpr] explicit **expected**( in_place_t, Args&&... args ) | construct value in-place from args |
| &nbsp; | [constexpr] explicit **expected**( in_place_t,<br>&emsp;std::initializer_list&lt;U> il, Args&&... args ) | construct value in-place from args |
| &nbsp; | [constexpr] **expected**( unexpected_type<E> const & error ) | initialize to error |
| &nbsp; | [constexpr] **expected**( unexpected_type<E> && error ) | move from error |
| &nbsp; | [constexpr] explicit **expected**( in_place_unexpected_t,<br>&emsp;Args&&... args ) | construct error in-place from args |
| &nbsp; | [constexpr] explicit **expected**( in_place_unexpected_t,<br>&emsp;std::initializer_list&lt;U> il, Args&&... args )| construct error in-place from args |
| Destruction | ~**expected**() | destruct current content |
| Assignment | expected **operator=**( expected const & other ) | assign contents of other;<br>destruct current content, if any |
| &nbsp; | expected & **operator=**( expected && other ) noexcept(...) | move contents of other |
| &nbsp; | expected & **operator=**( U && v ) | move value from v |
| &nbsp; | expected & **operator=**( unexpected_type<E> const & u ) | initialize to unexpected |
| &nbsp; | expected & **operator=**( unexpected_type<E> && u ) | move from unexpected |
| &nbsp; | template&lt;typename... Args><br>void **emplace**( Args &&... args ) | emplace from args |
| &nbsp; | template&lt;typename U, typename... Args><br>void **emplace**( std::initializer_list&lt;U> il, Args &&... args ) | emplace from args |
| Swap | void **swap**( expected & other ) noexcept | swap with other |
| Observers | constexpr value_type const \* **operator->**() const | pointer to current content (const);<br>must contain value |
| &nbsp; | value_type \* **operator->**() | pointer to current content (non-const);<br>must contain value |
| &nbsp; | constexpr value_type const & **operator \***() const & | the current content (const ref);<br>must contain value |
| &nbsp; | constexpr value_type && **operator \***() && | the current content (non-const ref);<br>must contain value |
| &nbsp; | constexpr explicit operator **bool**() const noexcept | true if contains value |
| &nbsp; | constexpr **has_value**() const noexcept | true if contains value |
| &nbsp; | constexpr value_type const & **value**() const & | current content (const ref);<br>see [note 1](#note1) |
| &nbsp; | value_type & **value**() & | current content (non-const ref);<br>see [note 1](#note1) |
| &nbsp; | constexpr value_type && **value**() && | move from current content;<br>see [note 1](#note1) |
| &nbsp; | constexpr error_type const & **error**() const & | current error (const ref);<br>must contain error |
| &nbsp; | error_type & **error**() & | current error (non-const ref);<br>must contain error |
| &nbsp; | constexpr error_type && **error**() && | move from current error;<br>must contain error |
| &nbsp; | constexpr unexpected_type<E> **get_unexpected**() const | the error as unexpected&lt;>;<br>must contain error |
| &nbsp; | template&lt;typename Ex><br>bool **has_exception**() const | true of contains exception (as base) |
| &nbsp; | value_type **value_or**( U && v ) const & | value or move from v |
| &nbsp; | value_type **value_or**( U && v ) && | move from value or move from v |
| &nbsp; | ... | &nbsp; |
<a id="note1"></a>Note 1: checked access: if no content, for std::exception_ptr rethrows error(), otherwise throws bad_expected_access(error()).
### Algorithms for expected
| Kind | Function |
|---------------------------------|----------|
| Comparison with expected | &nbsp; |
| ==&ensp;!= | template&lt;typename T1, typename E1, typename T2, typename E2><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T1,E1> const & x,<br>&emsp;expected&lt;T2,E2> const & y ) |
| Comparison with expected | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;expected&lt;T,E> const & y ) |
| Comparison with unexpected_type | &nbsp; |
| ==&ensp;!= | template&lt;typename T1, typename E1, typename E2><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T1,E1> const & x,<br>&emsp;unexpected_type&lt;E2> const & u ) |
| &nbsp; | template&lt;typename T1, typename E1, typename E2><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E2> const & u,<br>&emsp;expected&lt;T1,E1> const & x ) |
| Comparison with unexpected_type | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;unexpected_type&lt;E> const & u ) |
| &nbsp; | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E> const & u,<br>&emsp;expected&lt;T,E> const & x ) |
| Comparison with T | &nbsp; |
| ==&ensp;!= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;T const & v ) |
| &nbsp; | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;T const & v,<br>&emsp;expected&lt;T,E> const & x ) |
| Comparison with T | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;expected&lt;T,E> const & x,<br>&emsp;T const & v ) |
| &nbsp; | template&lt;typename T, typename E><br>constexpr bool operator ***op***(<br>&emsp;T const & v,<br>&emsp;expected&lt;T,E> const & x ) |
| Specialized algorithms | &nbsp; |
| Swap | template&lt;typename T, typename E><br>void **swap**(<br>&emsp;expected&lt;T,E> & x,<br>&emsp;expected&lt;T,E> & y )&emsp;noexcept( noexcept( x.swap(y) ) ) |
| Make expected from | nsel_P0323R <= 3 |
| &emsp;Value | template&lt;typename T><br>constexpr auto **make_expected**( T && v ) -><br>&emsp;expected< typename std::decay&lt;T>::type> |
| &emsp;Nothing | auto **make_expected**() -> expected&lt;void> |
| &emsp;Current exception | template&lt;typename T><br>constexpr auto **make_expected_from_current_exception**() -> expected&lt;T> |
| &emsp;Exception | template&lt;typename T><br>auto **make_expected_from_exception**( std::exception_ptr v ) -> expected&lt;T>|
| &emsp;Error | template&lt;typename T, typename E><br>constexpr auto **make_expected_from_error**( E e ) -><br>&emsp;expected&lt;T, typename std::decay&lt;E>::type> |
| &emsp;Call | template&lt;typename F><br>auto **make_expected_from_call**( F f ) -><br>&emsp;expected< typename std::result_of&lt;F()>::type>|
| &emsp;Call, void specialization | template&lt;typename F><br>auto **make_expected_from_call**( F f ) -> expected&lt;void> |
### Interface of unexpected_type
| Kind | Method | Result |
|--------------|-----------------------------------------------------------|--------|
| Construction | **unexpected_type**() = delete; | no default construction |
| &nbsp; | constexpr explicit **unexpected_type**( E const & error ) | copy-constructed from an E |
| &nbsp; | constexpr explicit **unexpected_type**( E && error ) | move-constructed from an E |
| Observers | constexpr error_type const & **value**() const | can observe contained error |
| &nbsp; | error_type & **value**() | can modify contained error |
### Algorithms for unexpected_type
| Kind | Function |
|-------------------------------|----------|
| Comparison with unexpected | &nbsp; |
| ==&ensp;!= | template&lt;typename E><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E> const & x,<br>&emsp;unexpected_type&lt;E> const & y ) |
| Comparison with unexpected | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | template&lt;typename E><br>constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;E> const & x,<br>&emsp;unexpected_type&lt;E> const & y ) |
| Comparison with exception_ptr | &nbsp; |
| ==&ensp;!= | constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;std::exception_ptr> const & x,<br>&emsp;unexpected_type&lt;std::exception_ptr> const & y ) |
| Comparison with exception_ptr | nsel_P0323R <= 2 |
| <&ensp;>&ensp;<=&ensp;>= | constexpr bool operator ***op***(<br>&emsp;unexpected_type&lt;std::exception_ptr> const & x,<br>&emsp;unexpected_type&lt;std::exception_ptr> const & y ) |
| Specialized algorithms | &nbsp; |
| Make unexpected from | &nbsp; |
| &emsp;Error | template&lt;typename E><br>[constexpr] auto **make_unexpected**( E && v) -><br>&emsp;unexpected_type< typename std::decay&lt;E>::type>|
| Make unexpected from | nsel_P0323R <= 3 |
| &emsp;Current exception | [constexpr] auto **make_unexpected_from_current_exception**() -><br>&emsp;unexpected_type< std::exception_ptr>|
<a id="comparison"></a>
## Comparison with like types
|Feature |<br>std::pair|std:: optional |std:: expected |nonstd:: expected |Boost. Expected |Nonco expected |Andrei Expected |Hagan required |
|----------------------|-------------|---------------|---------------|------------------|----------------|---------------|----------------|---------------|
|More information | see [14] | see [5] | see [1] | this work | see [4] | see [7] | see [8] | see [13] |
| | | | | | | | | |
| C++03 | yes | no | no | no/not yet | no (union) | no | no | yes |
| C++11 | yes | no | no | yes | yes | yes | yes | yes |
| C++14 | yes | no | no | yes | yes | yes | yes | yes |
| C++17 | yes | yes | no | yes | yes | yes | yes | yes |
| | | | | | | | | |
|DefaultConstructible | T param | yes | yes | yes | yes | no | no | no |
|In-place construction | no | yes | yes | yes | yes | yes | no | no |
|Literal type | yes | yes | yes | yes | yes | no | no | no |
| | | | | | | | | |
|Disengaged information| possible | no | yes | yes | yes | yes | yes | no |
|Vary disengaged type | yes | no | yes | yes | yes | no | no | no |
|Engaged nonuse throws | no | no | no | no | error_traits | no | no | yes |
|Disengaged use throws | no | yes, value() | yes, value() | yes, value() | yes,<br>value()| yes,<br>get() | yes,<br>get() | n/a |
| | | | | | | | | |
|Proxy (rel.ops) | no | yes | yes | yes | yes | no | no | no |
|References | no | yes | no/not yet | no/not yet | no/not yet | yes | no | no |
|Chained visitor(s) | no | no | yes | maybe | yes | no | no | no |
Note 1: std:&#58;*experimental*:&#58;expected
Note 2: sources for [Nonco expected](https://github.com/martinmoene/spike-expected/tree/master/nonco), [Andrei Expected](https://github.com/martinmoene/spike-expected/tree/master/alexandrescu) and [Hagan required](https://github.com/martinmoene/spike-expected/tree/master/hagan) can befound in the [spike-expected](https://github.com/martinmoene/spike-expected) repository.
## Reported to work with
TBD
## Implementation notes
TBD
## Other implementations of expected
- Simon Brand. [C++11/14/17 std::expected with functional-style extensions](https://github.com/TartanLlama/expected). Single-header.
- Isabella Muerte. [MNMLSTC Core](https://github.com/mnmlstc/core) (C++11).
- Vicente J. Botet Escriba. [stdmake's expected](https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/expected) (C++17).
- Facebook. [ Folly's Expected.h](https://github.com/facebook/folly/blob/master/folly/Expected.h) (C++14).
## Notes and references
[1] Vicente J. Botet Escriba. [p0323 - A proposal to add a utility class to represent expected object (latest)](http://wg21.link/p0323) (HTML). ([r10](http://wg21.link/p0323r10), [r9](http://wg21.link/p0323r9), [r8](http://wg21.link/p0323r8), [r7](http://wg21.link/p0323r7), [r6](http://wg21.link/p0323r6), [r5](http://wg21.link/p0323r5), [r4](http://wg21.link/p0323r4), [r3](http://wg21.link/p0323r3), [r2](http://wg21.link/p0323r2), [r1](http://wg21.link/n4109), [r0](http://wg21.link/n4015), [draft](https://github.com/viboes/std-make/blob/master/doc/proposal/expected/DXXXXR0_expected.pdf)).
[2] Vicente J. Botet Escriba. [JASEL: Just a simple experimental library for C++](https://github.com/viboes/std-make). Reference implementation of [expected](https://github.com/viboes/std-make/tree/master/include/experimental/fundamental/v3/expected).
[3] Vicente J. Botet Escriba. [Expected - An exception-friendly Error Monad](https://www.youtube.com/watch?v=Zdlt1rgYdMQ). C++Now 2014. 24 September 2014.
[4] Pierre Talbot. [Boost.Expected. Unofficial Boost candidate](http://www.google-melange.com/gsoc/proposal/review/google/gsoc2013/trademark/25002). 5 May 2013. [GitHub](https://github.com/TrademarkPewPew/Boost.Expected), [GSoC 2013 Proposal](http://www.google-melange.com/gsoc/proposal/review/google/gsoc2013/trademark/25002), [boost@lists.boost.org](http://permalink.gmane.org/gmane.comp.lib.boost.devel/240056 ).
[5] Fernando Cacciola and Andrzej Krzemieński. [A proposal to add a utility class to represent optional objects (Revision 4)](http://isocpp.org/files/papers/N3672.html). ISO/IEC JTC1 SC22 WG21 N3672 2013-04-19.
[6] Andrzej Krzemieński, [Optional library implementation in C++11](https://github.com/akrzemi1/Optional/).
[7] Anto Nonco. [Extending expected<T> to deal with references](http://anto-nonco.blogspot.nl/2013/03/extending-expected-to-deal-with.html). 27 May 2013.
[8] Andrei Alexandrescu. Systematic Error Handling in C++. Prepared for The C++and Beyond Seminar 2012. [Video](http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C). [Slides](http://sdrv.ms/RXjNPR).
[9] Andrei Alexandrescu. [Choose your Poison: Exceptions or Error Codes? (PDF)](http://accu.org/content/conf2007/Alexandrescu-Choose_Your_Poison.pdf). ACCU Conference 2007.
[10] Andrei Alexandrescu. [The Power of None (PPT)](http://nwcpp.org/static/talks/2006/The_Power_of_None.ppt). Northwest C++ Users' Group. [May 17th, 2006](http://nwcpp.org/may-2006.html).
[11] Jon Jagger. [A Return Type That Doesn't Like Being Ignored](http://accu.org/var/uploads/journals/overload53-FINAL.pdf#page=18). Overload issue 53, February 2003.
[12] Andrei Alexandrescu. [Error Handling in C++: Are we inching towards a total solution?](http://accu.org/index.php/conferences/2002/speakers2002). ACCU Conference 2002.
[13] Ken Hagan et al. [Exploding return codes](https://groups.google.com/d/msg/comp.lang.c++.moderated/BkZqPfoq3ys/H_PMR8Sat4oJ). comp.lang.c++.moderated. 11 February 2000.
[14] [std::pair](http://en.cppreference.com/w/cpp/utility/pair). cppreference.com
[15] Niall Douglas. [Outcome](https://ned14.github.io/outcome/). Very lightweight outcome&lt;T> and result&lt;T> (non-Boost edition).
[16] Niall Douglas. [p0762 - Concerns about expected&lt;T, E> from the Boost.Outcome peer review](http://wg21.link/p0762). 15 October 2017.
## Appendix
### A.1 Compile-time information
The version of *expected lite* is available via tag `[.version]`. The following tags are available for information on the compiler and on the C++ standard library used: `[.compiler]`, `[.stdc++]`, `[.stdlanguage]` and `[.stdlibrary]`.
### A.2 Expected lite test specification
<details>
<summary>click to expand</summary>
<p>
```Text
unexpected_type: Disallows default construction
unexpected_type: Allows to copy-construct from unexpected_type, default
unexpected_type: Allows to move-construct from unexpected_type, default
unexpected_type: Allows to in-place-construct
unexpected_type: Allows to in-place-construct from initializer_list
unexpected_type: Allows to copy-construct from error_type
unexpected_type: Allows to move-construct from error_type
unexpected_type: Allows to copy-construct from unexpected_type, explicit converting
unexpected_type: Allows to copy-construct from unexpected_type, non-explicit converting
unexpected_type: Allows to move-construct from unexpected_type, explicit converting
unexpected_type: Allows to move-construct from unexpected_type, non-explicit converting
unexpected_type: Allows to copy-assign from unexpected_type, default
unexpected_type: Allows to move-assign from unexpected_type, default
unexpected_type: Allows to copy-assign from unexpected_type, converting
unexpected_type: Allows to move-assign from unexpected, converting
unexpected_type: Allows to observe its value via a l-value reference
unexpected_type: Allows to observe its value via a r-value reference
unexpected_type: Allows to modify its value via a l-value reference
unexpected_type: Allows to be swapped
unexpected_type<std::exception_ptr>: Disallows default construction
unexpected_type<std::exception_ptr>: Allows to copy-construct from error_type
unexpected_type<std::exception_ptr>: Allows to move-construct from error_type
unexpected_type<std::exception_ptr>: Allows to copy-construct from an exception
unexpected_type<std::exception_ptr>: Allows to observe its value
unexpected_type<std::exception_ptr>: Allows to modify its value
unexpected_type: Provides relational operators
unexpected_type: Provides relational operators, std::exception_ptr specialization
make_unexpected(): Allows to create an unexpected_type<E> from an E
unexpected: C++17 and later provide unexpected_type as unexpected
bad_expected_access: Disallows default construction
bad_expected_access: Allows construction from error_type
bad_expected_access: Allows to observe its error
bad_expected_access: Allows to change its error
bad_expected_access: Provides non-empty what()
expected: Allows to default construct
expected: Allows to copy-construct from expected: value
expected: Allows to copy-construct from expected: error
expected: Allows to move-construct from expected: value
expected: Allows to move-construct from expected: error
expected: Allows to copy-construct from expected; value, explicit converting
expected: Allows to copy-construct from expected; error, explicit converting
expected: Allows to copy-construct from expected; value, non-explicit converting
expected: Allows to copy-construct from expected; error, non-explicit converting
expected: Allows to move-construct from expected; value, explicit converting
expected: Allows to move-construct from expected; error, explicit converting
expected: Allows to move-construct from expected; value, non-explicit converting
expected: Allows to move-construct from expected; error, non-explicit converting
expected: Allows to forward-construct from value, explicit converting
expected: Allows to forward-construct from value, non-explicit converting
expected: Allows to in-place-construct value
expected: Allows to in-place-construct value from initializer_list
expected: Allows to copy-construct from unexpected, explicit converting
expected: Allows to copy-construct from unexpected, non-explicit converting
expected: Allows to move-construct from unexpected, explicit converting
expected: Allows to move-construct from unexpected, non-explicit converting
expected: Allows to in-place-construct error
expected: Allows to in-place-construct error from initializer_list
expected: Allows to copy-assign from expected, value
expected: Allows to copy-assign from expected, error
expected: Allows to move-assign from expected, value
expected: Allows to move-assign from expected, error
expected: Allows to forward-assign from value
expected: Allows to copy-assign from unexpected
expected: Allows to move-assign from unexpected
expected: Allows to move-assign from move-only unexpected
expected: Allows to emplace value
expected: Allows to emplace value from initializer_list
expected: Allows to be swapped
expected: Allows to observe its value via a pointer
expected: Allows to observe its value via a pointer to constant
expected: Allows to modify its value via a pointer
expected: Allows to observe its value via a l-value reference
expected: Allows to observe its value via a r-value reference
expected: Allows to modify its value via a l-value reference
expected: Allows to modify its value via a r-value reference
expected: Allows to observe if it contains a value (or error)
expected: Allows to observe its value
expected: Allows to modify its value
expected: Allows to move its value
expected: Allows to observe its error
expected: Allows to modify its error
expected: Allows to move its error
expected: Allows to observe its error as unexpected
expected: Allows to query if it contains an exception of a specific base type
expected: Allows to observe its value if available, or obtain a specified value otherwise
expected: Allows to move its value if available, or obtain a specified value otherwise
expected: Throws bad_expected_access on value access when disengaged
expected<void>: Allows to default-construct
expected<void>: Allows to copy-construct from expected<void>: value
expected<void>: Allows to copy-construct from expected<void>: error
expected<void>: Allows to move-construct from expected<void>: value
expected<void>: Allows to move-construct from expected<void>: error
expected<void>: Allows to in-place-construct
expected<void>: Allows to copy-construct from unexpected, explicit converting
expected<void>: Allows to copy-construct from unexpected, non-explicit converting
expected<void>: Allows to move-construct from unexpected, explicit converting
expected<void>: Allows to move-construct from unexpected, non-explicit converting
expected<void>: Allows to in-place-construct unexpected_type
expected<void>: Allows to in-place-construct error from initializer_list
expected<void>: Allows to copy-assign from expected, value
expected<void>: Allows to copy-assign from expected, error
expected<void>: Allows to move-assign from expected, value
expected<void>: Allows to move-assign from expected, error
expected<void>: Allows to emplace value
expected<void>: Allows to be swapped
expected<void>: Allows to observe if it contains a value (or error)
expected<void>: Allows to observe its value
expected<void>: Allows to observe its error
expected<void>: Allows to modify its error
expected<void>: Allows to move its error
expected<void>: Allows to observe its error as unexpected
expected<void>: Allows to query if it contains an exception of a specific base type
expected<void>: Throws bad_expected_access on value access when disengaged
operators: Provides expected relational operators
swap: Allows expected to be swapped
std::hash: Allows to compute hash value for expected
tweak header: reads tweak header if supported [tweak]
```
</p>
</details>

View File

@@ -0,0 +1,71 @@
version: "{branch} #{build}"
shallow_clone: true
image:
- Visual Studio 2019
- Visual Studio 2017
- Visual Studio 2015
platform:
- Win32
- x64
configuration:
- Debug
- Release
build:
parallel: true
environment:
matrix:
- generator: "Visual Studio 16 2019"
select_sv: -DEXPECTED_LITE_OPT_SELECT_STD=ON
- generator: "Visual Studio 16 2019"
select_sv: -DEXPECTED_LITE_OPT_SELECT_NONSTD=ON
- generator: "Visual Studio 15 2017"
select_sv: -DEXPECTED_LITE_OPT_SELECT_STD=ON
- generator: "Visual Studio 15 2017"
select_sv: -DEXPECTED_LITE_OPT_SELECT_NONSTD=ON
- generator: "Visual Studio 14 2015"
# - generator: "Visual Studio 12 2013"
# - generator: "Visual Studio 11 2012"
# - generator: "Visual Studio 10 2010"
matrix:
exclude:
- image: Visual Studio 2015
generator: "Visual Studio 16 2019"
- image: Visual Studio 2019
generator: "Visual Studio 15 2017"
- image: Visual Studio 2019
generator: "Visual Studio 14 2015"
- image: Visual Studio 2019
generator: "Visual Studio 12 2013"
- image: Visual Studio 2019
generator: "Visual Studio 11 2012"
- image: Visual Studio 2019
generator: "Visual Studio 10 2010"
- image: Visual Studio 2015
generator: "Visual Studio 15 2017"
- image: Visual Studio 2017
generator: "Visual Studio 16 2019"
- image: Visual Studio 2017
generator: "Visual Studio 14 2015"
- image: Visual Studio 2017
generator: "Visual Studio 12 2013"
- image: Visual Studio 2017
generator: "Visual Studio 11 2012"
- image: Visual Studio 2017
generator: "Visual Studio 10 2010"
before_build:
- mkdir build && cd build
- cmake -A %platform% -G "%generator%" "%select_sv%" -DEXPECTED_LITE_OPT_BUILD_TESTS=ON -DEXPECTED_LITE_OPT_BUILD_EXAMPLES=OFF ..
build_script:
- cmake --build . --config %configuration%
test_script:
- ctest --output-on-failure -C %configuration%

View File

@@ -0,0 +1,24 @@
# Adapted from write_basic_package_version_file(... COMPATIBILITY SameMajorVersion) output
# ARCH_INDEPENDENT is only present in cmake 3.14 and onwards
set( PACKAGE_VERSION "@package_version@" )
if( PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION )
set( PACKAGE_VERSION_COMPATIBLE FALSE )
else()
if( "@package_version@" MATCHES "^([0-9]+)\\." )
set( CVF_VERSION_MAJOR "${CMAKE_MATCH_1}" )
else()
set( CVF_VERSION_MAJOR "@package_version@" )
endif()
if( PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR )
set( PACKAGE_VERSION_COMPATIBLE TRUE )
else()
set( PACKAGE_VERSION_COMPATIBLE FALSE )
endif()
if( PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION )
set( PACKAGE_VERSION_EXACT TRUE )
endif()
endif()

View File

@@ -0,0 +1,7 @@
@PACKAGE_INIT@
# Only include targets once:
if( NOT TARGET @package_name@::@package_name@ )
include( "${CMAKE_CURRENT_LIST_DIR}/@package_target@.cmake" )
endif()

View File

@@ -0,0 +1,27 @@
from conans import ConanFile, CMake
class ExpectedLiteConan(ConanFile):
version = "0.6.2"
name = "expected-lite"
description = "Expected objects for C++11 and later"
license = "Boost Software License - Version 1.0. http://www.boost.org/LICENSE_1_0.txt"
url = "https://github.com/martinmoene/expected-lite.git"
exports_sources = "include/nonstd/*", "CMakeLists.txt", "cmake/*", "LICENSE.txt"
settings = "compiler", "build_type", "arch"
build_policy = "missing"
author = "Martin Moene"
def build(self):
"""Avoid warning on build step"""
pass
def package(self):
"""Run CMake install"""
cmake = CMake(self)
cmake.definitions["EXPECTED_LITE_OPT_BUILD_TESTS"] = "OFF"
cmake.definitions["EXPECTED_LITE_OPT_BUILD_EXAMPLES"] = "OFF"
cmake.configure()
cmake.install()
def package_info(self):
self.info.header_only()

View File

@@ -0,0 +1,33 @@
// Convert text to number and yield expected with number or error text.
#include "nonstd/expected.hpp"
#include <cstdlib>
#include <iostream>
#include <string>
using namespace nonstd;
using namespace std::literals;
auto to_int( char const * const text ) -> expected<int, std::string>
{
char * pos = nullptr;
auto value = strtol( text, &pos, 0 );
if ( pos != text ) return value;
else return make_unexpected( "'"s + text + "' isn't a number" );
}
int main( int argc, char * argv[] )
{
auto text = argc > 1 ? argv[1] : "42";
auto ei = to_int( text );
if ( ei ) std::cout << "'" << text << "' is " << *ei << ", ";
else std::cout << "Error: " << ei.error();
}
// cl -EHsc -wd4814 -I../include 01-basic.cpp && 01-basic.exe 123 && 01-basic.exe abc
// g++ -std=c++14 -Wall -I../include -o 01-basic.exe 01-basic.cpp && 01-basic.exe 123 && 01-basic.exe abc
// '123' is 123, Error: 'abc' isn't a number

View File

@@ -0,0 +1,68 @@
// Use a non-ignorable value with expected.
#include "nonstd/expected.hpp"
#include <iostream>
using namespace nonstd;
template< typename T >
class required
{
public:
required( T const & value)
: content( value ) {}
required( required && other )
: content( other.content )
, ignored( other.ignored )
{
other.ignored = false;
}
required( required const & other ) = delete;
~required() noexcept( false )
{
if ( ignored )
throw std::runtime_error("required: content unobserved");
};
T const & operator *() const { ignored = false; return content; }
private:
T content;
mutable bool ignored = true;
};
template< typename T >
auto make_required( T value ) -> required<T>
{
return required<T>( std::move(value) );
}
using unused_type = char;
auto produce( int value ) -> expected< required<int>, unused_type >
{
return make_required( std::move(value) );
}
int main( int argc, char * argv[] )
{
try
{
auto er42 = produce( 42 );
auto er13 = produce( 13 );
std::cout << "value: " << **er42 << "\n";
}
catch ( std::exception const & e )
{
std::cout << "Error: " << e.what();
}
}
// cl -EHsc -wd4814 -Zc:implicitNoexcept- -I../include 02-required.cpp && 02-required.exe
// g++ -std=c++14 -Wall -I../include -o 02-required.exe 02-required.cpp && 02-required.exe
// value: 42
// Error: required: content unobserved

View File

@@ -0,0 +1,77 @@
// Copyright (c) 2016-2020 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "nonstd/expected.hpp"
#include <iostream>
template< typename T >
void use( T const & /*x*/) {}
#define expected_PRESENT( x ) \
std::cout << #x << ": " << x << "\n"
#define expected_ABSENT( x ) \
std::cout << #x << ": (undefined)\n"
void report()
{
#ifdef __cpp_exceptions
expected_PRESENT( __cpp_exceptions );
#else
expected_ABSENT( __cpp_exceptions );
#endif
#ifdef __EXCEPTIONS
expected_PRESENT( __EXCEPTIONS );
#else
expected_ABSENT( __EXCEPTIONS );
#endif
#ifdef _HAS_EXCEPTIONS
expected_PRESENT( _HAS_EXCEPTIONS );
#else
expected_ABSENT( _HAS_EXCEPTIONS );
#endif
#ifdef _CPPUNWIND
expected_PRESENT( _CPPUNWIND );
#else
expected_ABSENT( _CPPUNWIND );
#endif
}
int violate_access()
{
nonstd::expected<int, char> eu( nonstd:: make_unexpected('a') );
return eu.value();
}
int main()
{
report();
#if ! nsel_CONFIG_NO_EXCEPTIONS_SEH
return violate_access();
#else
__try
{
return violate_access();
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
std::cerr << "\n*** Executing SEH __except block ***\n";
}
#endif
}
// -Dnsel_CONFIG_NO_EXCEPTIONS=1 automatically determined in expected.hpp
// -Dnsel_CONFIG_NO_EXCEPTIONS_SEH=0 default:1 for msvc
// cl -nologo -kernel -EHs-c- -GR- -I../include 03-no-exceptions.cpp && 03-no-exceptions
// cl -nologo -kernel -EHs-c- -GR- -Dnsel_CONFIG_NO_EXCEPTIONS_SEH=0 -I../include 03-no-exceptions.cpp && 03-no-exceptions
// g++ -Wall -fno-exceptions -I../include -o 03-no-exceptions 03-no-exceptions.cpp && 03-no-exceptions

View File

@@ -0,0 +1,79 @@
# Copyright (c) 2016-2022 Martin Moene.
#
# https://github.com/martinmoene/expected-lite
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
if( NOT DEFINED CMAKE_MINIMUM_REQUIRED_VERSION )
cmake_minimum_required( VERSION 3.8 FATAL_ERROR )
endif()
project( example LANGUAGES CXX )
set( unit_name "expected" )
set( PACKAGE ${unit_name}-lite )
message( STATUS "Subproject '${PROJECT_NAME}', various examples")
# Target default options and definitions:
set( OPTIONS "" )
#set( DEFINITIONS "" )
# Sources (.cpp), normal and no-exception, and their base names:
set( SOURCES_CPP11
02-required.cpp
)
set( SOURCES_CPP14
01-basic.cpp
)
# note: here variable must be quoted to create semicolon separated list:
string( REPLACE ".cpp" "" BASENAMES_CPP11 "${SOURCES_CPP11}" )
string( REPLACE ".cpp" "" BASENAMES_CPP14 "${SOURCES_CPP14}" )
set( TARGETS_CPP11 ${BASENAMES_CPP11} )
set( TARGETS_CPP14 ${BASENAMES_CPP14} )
set( TARGETS_ALL ${TARGETS_CPP11} ${TARGETS_CPP14} )
# add targets:
foreach( name ${TARGETS_ALL} )
add_executable( ${name} ${name}.cpp )
target_link_libraries( ${name} PRIVATE ${PACKAGE} )
endforeach()
# set compiler options:
if( ${CMAKE_GENERATOR} MATCHES Visual )
foreach( name ${TARGETS_ALL} )
target_compile_options( ${name} PUBLIC -W3 -EHsc -wd4814 -Zc:implicitNoexcept- )
endforeach()
else()
foreach( name ${TARGETS_ALL} )
target_compile_options( ${name} PUBLIC -Wall )
endforeach()
foreach( name ${TARGETS_CPP11} )
target_compile_options( ${name} PUBLIC -std=c++11 )
endforeach()
foreach( name ${TARGETS_CPP14} )
target_compile_options( ${name} PUBLIC -std=c++14 )
endforeach()
endif()
# configure unit tests via CTest:
enable_testing()
foreach( name ${TARGETS_ALL} )
add_test ( NAME ${name} COMMAND ${name} )
set_property( TEST ${name} PROPERTY LABELS example )
endforeach()
# end of file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<CodeBlocks_project_file>
<FileVersion major="1" minor="6" />
<Project>
<Option title="Expected Lite" />
<Option pch_mode="2" />
<Option compiler="gcc" />
<Build>
<Target title="Release">
<Option output="bin/Release/optional3" prefix_auto="1" extension_auto="1" />
<Option object_output="obj/Release/" />
<Option type="1" />
<Option compiler="gcc" />
<Compiler>
<Add option="-O2" />
</Compiler>
<Linker>
<Add option="-s" />
</Linker>
</Target>
</Build>
<Compiler>
<Add option="-Wall" />
</Compiler>
<Unit filename="../../.gitattributes" />
<Unit filename="../../.gitignore" />
<Unit filename="../../.travis.yml" />
<Unit filename="../../CHANGES.txt" />
<Unit filename="../../CMakeLists.txt" />
<Unit filename="../../LICENSE.txt" />
<Unit filename="../../Notes.md" />
<Unit filename="../../README.md" />
<Unit filename="../../appveyor.yml" />
<Unit filename="../../cmake/expected-lite-config-version.cmake.in" />
<Unit filename="../../cmake/expected-lite-config.cmake.in" />
<Unit filename="../../conanfile.py" />
<Unit filename="../../example/01-basic.cpp" />
<Unit filename="../../example/02-required.cpp" />
<Unit filename="../../example/CMakeLists.txt" />
<Unit filename="../../include/nonstd/expected.hpp" />
<Unit filename="../../script/create-cov-rpt.py" />
<Unit filename="../../script/create-vcpkg.py" />
<Unit filename="../../script/update-version.py" />
<Unit filename="../../script/upload-conan.py" />
<Unit filename="../../test/CMakeLists.txt" />
<Unit filename="../../test/expected-main.t.cpp" />
<Unit filename="../../test/expected-main.t.hpp" />
<Unit filename="../../test/expected.t.cpp" />
<Unit filename="../../test/lest.hpp" />
<Unit filename="../../test/odr.cpp" />
<Unit filename="../../test/t-odr.bat" />
<Unit filename="../../test/t.bat" />
<Unit filename="../../test/tc.bat" />
<Unit filename="../../test/tg-all.bat" />
<Unit filename="../../test/tg.bat" />
<Extensions>
<code_completion />
<envvars />
<debugger />
<lib_finder disable_auto="1" />
</Extensions>
</Project>
</CodeBlocks_project_file>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<CodeBlocks_workspace_file>
<Workspace title="Workspace">
<Project filename="expected.cbp" />
<Project filename="../../optional-lite/project/optional.cbp" />
<Project filename="../../expected-spike/codeblocks.cbp" />
<Project filename="../../../GitHub-Pre/IsoCpp/gsl-lite/project/gsl-lite.cbp" />
</Workspace>
</CodeBlocks_workspace_file>

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python
#
# Copyright 2019-2019 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/create-cov-rpt.py, Python 3.4 and later
#
import argparse
import os
import re
import sys
import subprocess
# Configuration:
cfg_github_project = 'expected-lite'
cfg_github_user = 'martinmoene'
cfg_prj_folder_level = 3
tpl_coverage_cmd = 'opencppcoverage --no_aggregate_by_file --sources {src} -- {exe}'
# End configuration.
def project_folder( f, args ):
"""Project root"""
if args.prj_folder:
return args.prj_folder
return os.path.normpath( os.path.join( os.path.dirname( os.path.abspath(f) ), '../' * args.prj_folder_level ) )
def executable_folder( f ):
"""Folder where the xecutable is"""
return os.path.dirname( os.path.abspath(f) )
def executable_name( f ):
"""Folder where the executable is"""
return os.path.basename( f )
def createCoverageReport( f, args ):
print( "Creating coverage report for project '{usr}/{prj}', '{file}':".
format( usr=args.user, prj=args.project, file=f ) )
cmd = tpl_coverage_cmd.format( folder=executable_folder(f), src=project_folder(f, args), exe=executable_name(f) )
if args.verbose:
print( "> {}".format(cmd) )
if not args.dry_run:
os.chdir( executable_folder(f) )
subprocess.call( cmd, shell=False )
os.chdir( project_folder(f, args) )
def createCoverageReports( args ):
for f in args.executable:
createCoverageReport( f, args )
def createCoverageReportFromCommandLine():
"""Collect arguments from the commandline and create coverage report."""
parser = argparse.ArgumentParser(
description='Create coverage report.',
epilog="""""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'executable',
metavar='executable',
type=str,
nargs=1,
help='executable to report on')
parser.add_argument(
'-n', '--dry-run',
action='store_true',
help='do not execute conan commands')
parser.add_argument(
'-v', '--verbose',
action='count',
default=0,
help='level of progress reporting')
parser.add_argument(
'--user',
metavar='u',
type=str,
default=cfg_github_user,
help='github user name')
parser.add_argument(
'--project',
metavar='p',
type=str,
default=cfg_github_project,
help='github project name')
parser.add_argument(
'--prj-folder',
metavar='f',
type=str,
default=None,
help='project root folder')
parser.add_argument(
'--prj-folder-level',
metavar='n',
type=int,
default=cfg_prj_folder_level,
help='project root folder level from executable')
createCoverageReports( parser.parse_args() )
if __name__ == '__main__':
createCoverageReportFromCommandLine()
# end of file

View File

@@ -0,0 +1,223 @@
#!/usr/bin/env python
#
# Copyright 2019-2019 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/upload-conan.py, Python 3.4 and later
#
import argparse
import os
import re
import sys
import subprocess
# Configuration:
cfg_github_project = 'expected-lite'
cfg_github_user = 'martinmoene'
cfg_description = '(unused)'
cfg_cmakelists = 'CMakeLists.txt'
cfg_readme = 'Readme.md'
cfg_license = 'LICENSE.txt'
cfg_ref_prefix = 'v'
cfg_sha512 = 'dadeda'
cfg_vcpkg_description = '(no description found)'
cfg_vcpkg_root = os.environ['VCPKG_ROOT']
cfg_cmake_optpfx = "EXPECTED_LITE"
# End configuration.
# vcpkg control and port templates:
tpl_path_vcpkg_control = '{vcpkg}/ports/{prj}/CONTROL'
tpl_path_vcpkg_portfile = '{vcpkg}/ports/{prj}/portfile.cmake'
tpl_vcpkg_control =\
"""Source: {prj}
Version: {ver}
Description: {desc}"""
tpl_vcpkg_portfile =\
"""include(vcpkg_common_functions)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO {usr}/{prj}
REF {ref}
SHA512 {sha}
)
vcpkg_configure_cmake(
SOURCE_PATH ${{SOURCE_PATH}}
PREFER_NINJA
OPTIONS
-D{optpfx}_OPT_BUILD_TESTS=OFF
-D{optpfx}_OPT_BUILD_EXAMPLES=OFF
)
vcpkg_install_cmake()
vcpkg_fixup_cmake_targets(
CONFIG_PATH lib/cmake/${{PORT}}
)
file(REMOVE_RECURSE
${{CURRENT_PACKAGES_DIR}}/debug
${{CURRENT_PACKAGES_DIR}}/lib
)
file(INSTALL
${{SOURCE_PATH}}/{lic} DESTINATION ${{CURRENT_PACKAGES_DIR}}/share/${{PORT}} RENAME copyright
)"""
tpl_vcpkg_note_sha =\
"""
Next actions:
- Obtain package SHA: 'vcpkg install {prj}', copy SHA mentioned in 'Actual hash: [...]'
- Add SHA to package: 'script\create-vcpkg --sha={sha}'
- Install package : 'vcpkg install {prj}'"""
tpl_vcpkg_note_install =\
"""
Next actions:
- Install package: 'vcpkg install {prj}'"""
# End of vcpkg templates
def versionFrom( filename ):
"""Obtain version from CMakeLists.txt"""
with open( filename, 'r' ) as f:
content = f.read()
version = re.search(r'VERSION\s(\d+\.\d+\.\d+)', content).group(1)
return version
def descriptionFrom( filename ):
"""Obtain description from CMakeLists.txt"""
with open( filename, 'r' ) as f:
content = f.read()
description = re.search(r'DESCRIPTION\s"(.*)"', content).group(1)
return description if description else cfg_vcpkg_description
def vcpkgRootFrom( path ):
return path if path else './vcpkg'
def to_ref( version ):
"""Add prefix to version/tag, like v1.2.3"""
return cfg_ref_prefix + version
def control_path( args ):
"""Create path like vcpks/ports/_project_/CONTROL"""
return tpl_path_vcpkg_control.format( vcpkg=args.vcpkg_root, prj=args.project )
def portfile_path( args ):
"""Create path like vcpks/ports/_project_/portfile.cmake"""
return tpl_path_vcpkg_portfile.format( vcpkg=args.vcpkg_root, prj=args.project )
def createControl( args ):
"""Create vcpkg CONTROL file"""
output = tpl_vcpkg_control.format(
prj=args.project, ver=args.version, desc=args.description )
if args.verbose:
print( "Creating control file '{f}':".format( f=control_path( args ) ) )
if args.verbose > 1:
print( output )
os.makedirs( os.path.dirname( control_path( args ) ), exist_ok=True )
with open( control_path( args ), 'w') as f:
print( output, file=f )
def createPortfile( args ):
"""Create vcpkg portfile"""
output = tpl_vcpkg_portfile.format(
optpfx=cfg_cmake_optpfx, usr=args.user, prj=args.project, ref=to_ref(args.version), sha=args.sha, lic=cfg_license )
if args.verbose:
print( "Creating portfile '{f}':".format( f=portfile_path( args ) ) )
if args.verbose > 1:
print( output )
os.makedirs( os.path.dirname( portfile_path( args ) ), exist_ok=True )
with open( portfile_path( args ), 'w') as f:
print( output, file=f )
def printNotes( args ):
if args.sha == cfg_sha512:
print( tpl_vcpkg_note_sha.
format( prj=args.project, sha='...' ) )
else:
print( tpl_vcpkg_note_install.
format( prj=args.project ) )
def createVcpkg( args ):
print( "Creating vcpkg for '{usr}/{prj}', version '{ver}' in folder '{vcpkg}':".
format( usr=args.user, prj=args.project, ver=args.version, vcpkg=args.vcpkg_root, ) )
createControl( args )
createPortfile( args )
printNotes( args )
def createVcpkgFromCommandLine():
"""Collect arguments from the commandline and create vcpkg."""
parser = argparse.ArgumentParser(
description='Create microsoft vcpkg.',
epilog="""""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'-v', '--verbose',
action='count',
default=0,
help='level of progress reporting')
parser.add_argument(
'--user',
metavar='u',
type=str,
default=cfg_github_user,
help='github user name')
parser.add_argument(
'--project',
metavar='p',
type=str,
default=cfg_github_project,
help='github project name')
parser.add_argument(
'--description',
metavar='d',
type=str,
# default=cfg_description,
default=descriptionFrom( cfg_cmakelists ),
help='vcpkg description [from ' + cfg_cmakelists + ']')
parser.add_argument(
'--version',
metavar='v',
type=str,
default=versionFrom( cfg_cmakelists ),
help='version number [from ' + cfg_cmakelists + ']')
parser.add_argument(
'--sha',
metavar='s',
type=str,
default=cfg_sha512,
help='sha of package')
parser.add_argument(
'--vcpkg-root',
metavar='r',
type=str,
default=vcpkgRootFrom( cfg_vcpkg_root ),
help='parent folder containing ports to write files to')
createVcpkg( parser.parse_args() )
if __name__ == '__main__':
createVcpkgFromCommandLine()
# end of file

View File

@@ -0,0 +1,129 @@
#!/usr/bin/env python
#
# Copyright 2017-2018 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/update-version.py
#
from __future__ import print_function
import argparse
import os
import re
import sys
# Configuration:
table = (
# path, substitute find, substitute format
( 'CMakeLists.txt'
, r'\W{2,4}VERSION\W+([0-9]+\.[0-9]+\.[0-9]+)\W*$'
, ' VERSION {major}.{minor}.{patch}' )
, ( 'CMakeLists.txt'
, r'set\W+expected_lite_version\W+"([0-9]+\.[0-9]+\.[0-9]+)"\W+$'
, 'set( expected_lite_version "{major}.{minor}.{patch}" )\n' )
# , ( 'example/cmake-pkg/CMakeLists.txt'
# , r'set\W+expected_lite_version\W+"([0-9]+\.[0-9]+(\.[0-9]+)?)"\W+$'
# , 'set( expected_lite_version "{major}.{minor}" )\n' )
#
# , ( 'script/install-xxx-pkg.py'
# , r'\expected_lite_version\s+=\s+"([0-9]+\.[0-9]+\.[0-9]+)"\s*$'
# , 'expected_lite_version = "{major}.{minor}.{patch}"\n' )
, ( 'conanfile.py'
, r'version\s+=\s+"([0-9]+\.[0-9]+\.[0-9]+)"\s*$'
, 'version = "{major}.{minor}.{patch}"' )
, ( 'include/nonstd/expected.hpp'
, r'\#define\s+expected_lite_MAJOR\s+[0-9]+\s*$'
, '#define expected_lite_MAJOR {major}' )
, ( 'include/nonstd/expected.hpp'
, r'\#define\s+expected_lite_MINOR\s+[0-9]+\s*$'
, '#define expected_lite_MINOR {minor}' )
, ( 'include/nonstd/expected.hpp'
, r'\#define\s+expected_lite_PATCH\s+[0-9]+\s*$'
, '#define expected_lite_PATCH {patch}\n' )
)
# End configuration.
def readFile( in_path ):
"""Return content of file at given path"""
with open( in_path, 'r' ) as in_file:
contents = in_file.read()
return contents
def writeFile( out_path, contents ):
"""Write contents to file at given path"""
with open( out_path, 'w' ) as out_file:
out_file.write( contents )
def replaceFile( output_path, input_path ):
# prevent race-condition (Python 3.3):
if sys.version_info >= (3, 3):
os.replace( output_path, input_path )
else:
os.remove( input_path )
os.rename( output_path, input_path )
def editFileToVersion( version, info, verbose ):
"""Update version given file path, version regexp and new version format in info"""
major, minor, patch = version.split('.')
in_path, ver_re, ver_fmt = info
out_path = in_path + '.tmp'
new_text = ver_fmt.format( major=major, minor=minor, patch=patch )
if verbose:
print( "- {path} => '{text}':".format( path=in_path, text=new_text.strip('\n') ) )
writeFile(
out_path,
re.sub(
ver_re, new_text, readFile( in_path )
, count=0, flags=re.MULTILINE
)
)
replaceFile( out_path, in_path )
def editFilesToVersion( version, table, verbose ):
if verbose:
print( "Editing files to version {v}:".format(v=version) )
for item in table:
editFileToVersion( version, item, verbose )
def editFilesToVersionFromCommandLine():
"""Update version number given on command line in paths from configuration table."""
parser = argparse.ArgumentParser(
description='Update version number in files.',
epilog="""""",
formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
'version',
metavar='version',
type=str,
nargs=1,
help='new version number, like 1.2.3')
parser.add_argument(
'-v', '--verbose',
action='store_true',
help='report the name of the file being processed')
args = parser.parse_args()
editFilesToVersion( args.version[0], table, args.verbose )
if __name__ == '__main__':
editFilesToVersionFromCommandLine()
# end of file

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env python
#
# Copyright 2019-2019 by Martin Moene
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
# script/upload-conan.py
#
from __future__ import print_function
import argparse
import os
import re
import sys
import subprocess
# Configuration:
def_conan_project = 'expected-lite'
def_conan_user = 'nonstd-lite'
def_conan_channel = 'stable'
cfg_conanfile = 'conanfile.py'
tpl_conan_create = 'conan create . {usr}/{chn}'
tpl_conan_upload = 'conan upload --remote {usr} {prj}/{ver}@{usr}/{chn}'
# End configuration.
def versionFrom( filename ):
"""Obtain version from conanfile.py"""
with open( filename ) as f:
content = f.read()
version = re.search(r'version\s=\s"(.*)"', content).group(1)
return version
def createConanPackage( args ):
"""Create conan package and upload it."""
cmd = tpl_conan_create.format(usr=args.user, chn=args.channel)
if args.verbose:
print( "> {}".format(cmd) )
if not args.dry_run:
subprocess.call( cmd, shell=False )
def uploadConanPackage( args ):
"""Create conan package and upload it."""
cmd = tpl_conan_upload.format(prj=args.project, usr=args.user, chn=args.channel, ver=args.version)
if args.verbose:
print( "> {}".format(cmd) )
if not args.dry_run:
subprocess.call( cmd, shell=False )
def uploadToConan( args ):
"""Create conan package and upload it."""
print( "Updating project '{prj}' to user '{usr}', channel '{chn}', version {ver}:".
format(prj=args.project, usr=args.user, chn=args.channel, ver=args.version) )
createConanPackage( args )
uploadConanPackage( args )
def uploadToConanFromCommandLine():
"""Collect arguments from the commandline and create conan package and upload it."""
parser = argparse.ArgumentParser(
description='Create conan package and upload it to conan.',
epilog="""""",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
'-n', '--dry-run',
action='store_true',
help='do not execute conan commands')
parser.add_argument(
'-v', '--verbose',
action='count',
default=0,
help='level of progress reporting')
parser.add_argument(
'--project',
metavar='p',
type=str,
default=def_conan_project,
help='conan project')
parser.add_argument(
'--user',
metavar='u',
type=str,
default=def_conan_user,
help='conan user')
parser.add_argument(
'--channel',
metavar='c',
type=str,
default=def_conan_channel,
help='conan channel')
parser.add_argument(
'--version',
metavar='v',
type=str,
default=versionFrom( cfg_conanfile ),
help='version number [from conanfile.py]')
uploadToConan( parser.parse_args() )
if __name__ == '__main__':
uploadToConanFromCommandLine()
# end of file

View File

@@ -0,0 +1,233 @@
# Copyright 2016-2018 by Martin Moene
#
# https://github.com/martinmoene/expected-lite
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
if( NOT DEFINED CMAKE_MINIMUM_REQUIRED_VERSION )
cmake_minimum_required( VERSION 3.5 FATAL_ERROR )
endif()
project( test LANGUAGES CXX )
set( unit_name "expected" )
set( PACKAGE ${unit_name}-lite )
set( PROGRAM ${unit_name}-lite )
set( SOURCES ${unit_name}-main.t.cpp ${unit_name}.t.cpp )
set( TWEAKD "." )
message( STATUS "Subproject '${PROJECT_NAME}', programs '${PROGRAM}-*'")
set( OPTIONS "" )
set( DEFCMN "-Dlest_FEATURE_AUTO_REGISTER=1" )
set( HAS_STD_FLAGS FALSE )
set( HAS_CPP98_FLAG FALSE )
set( HAS_CPP11_FLAG FALSE )
set( HAS_CPP14_FLAG FALSE )
set( HAS_CPP17_FLAG FALSE )
set( HAS_CPP20_FLAG FALSE )
set( HAS_CPPLATEST_FLAG FALSE )
if ( EXPEXTED_P0323R LESS "99" )
set( DEFCMN ${DEFCMN} "-Dnsel_P0323R=${EXPEXTED_P0323R}" )
endif()
if( MSVC )
message( STATUS "Matched: MSVC")
set( HAS_STD_FLAGS TRUE )
set( OPTIONS -W3 -EHsc )
set( DEFINITIONS -D_SCL_SECURE_NO_WARNINGS ${DEFCMN} )
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.00 )
set( HAS_CPP14_FLAG TRUE )
set( HAS_CPPLATEST_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.11 )
set( HAS_CPP17_FLAG TRUE )
endif()
elseif( CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang" )
message( STATUS "CompilerId: '${CMAKE_CXX_COMPILER_ID}'")
set( HAS_STD_FLAGS TRUE )
set( HAS_CPP98_FLAG TRUE )
set( OPTIONS -Wall -Wextra -Wconversion -Wsign-conversion -Wno-missing-braces -fno-elide-constructors )
set( DEFINITIONS ${DEFCMN} )
# GNU: available -std flags depends on version
if( CMAKE_CXX_COMPILER_ID MATCHES "GNU" )
message( STATUS "Matched: GNU")
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8.0 )
set( HAS_CPP11_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9.2 )
set( HAS_CPP14_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.1.0 )
set( HAS_CPP17_FLAG TRUE )
endif()
# AppleClang: available -std flags depends on version
elseif( CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" )
message( STATUS "Matched: AppleClang")
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0 )
set( HAS_CPP11_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.1.0 )
set( HAS_CPP14_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.2.0 )
set( HAS_CPP17_FLAG TRUE )
endif()
# Clang: available -std flags depends on version
elseif( CMAKE_CXX_COMPILER_ID MATCHES "Clang" )
message( STATUS "Matched: Clang")
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.3.0 )
set( HAS_CPP11_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.4.0 )
set( HAS_CPP14_FLAG TRUE )
endif()
if( NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0.0 )
set( HAS_CPP17_FLAG TRUE )
endif()
endif()
elseif( CMAKE_CXX_COMPILER_ID MATCHES "Intel" )
# as is
message( STATUS "Matched: Intel")
else()
# as is
message( STATUS "Matched: nothing")
endif()
# enable MS C++ Core Guidelines checker if MSVC:
function( enable_msvs_guideline_checker target )
if( MSVC )
set_target_properties( ${target} PROPERTIES
VS_GLOBAL_EnableCppCoreCheck true
VS_GLOBAL_CodeAnalysisRuleSet CppCoreCheckRules.ruleset
VS_GLOBAL_RunCodeAnalysis true )
endif()
endfunction()
# make target, compile for given standard if specified:
function( make_target target std )
message( STATUS "Make target: '${std}'" )
add_executable ( ${target} ${SOURCES} )
target_include_directories( ${target} SYSTEM PRIVATE lest )
target_include_directories( ${target} PRIVATE ${TWEAKD} )
target_link_libraries ( ${target} PRIVATE ${PACKAGE} )
target_compile_options ( ${target} PRIVATE ${OPTIONS} )
target_compile_definitions( ${target} PRIVATE ${DEFINITIONS} )
if( std )
if( MSVC )
target_compile_options( ${target} PRIVATE -std:c++${std} )
else()
# Necessary for clang 3.x:
target_compile_options( ${target} PRIVATE -std=c++${std} )
# Ok for clang 4 and later:
# set( CMAKE_CXX_STANDARD ${std} )
# set( CMAKE_CXX_STANDARD_REQUIRED ON )
# set( CMAKE_CXX_EXTENSIONS OFF )
endif()
endif()
endfunction()
# add generic executable, unless -std flags can be specified:
if( NOT HAS_STD_FLAGS )
make_target( ${PROGRAM}.t "" )
else()
# # unconditionally add C++98 variant as MSVC has no option for it:
# if( HAS_CPP98_FLAG )
# make_target( ${PROGRAM}-cpp98.t 98 )
# else()
# make_target( ${PROGRAM}-cpp98.t "" )
# endif()
# unconditionally add C++11 variant as MSVC has no option for it:
if( HAS_CPP11_FLAG )
make_target( ${PROGRAM}-cpp11.t 11 )
else()
make_target( ${PROGRAM}-cpp11.t "" )
endif()
if( HAS_CPP14_FLAG )
make_target( ${PROGRAM}-cpp14.t 14 )
endif()
if( HAS_CPP17_FLAG )
set( std17 17 )
if( CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" )
set( std17 1z )
endif()
make_target( ${PROGRAM}-cpp17.t ${std17} )
enable_msvs_guideline_checker( ${PROGRAM}-cpp17.t )
endif()
if( HAS_CPPLATEST_FLAG )
make_target( ${PROGRAM}-cpplatest.t latest )
endif()
endif()
# with C++20, honour explicit request for std::expected or nonstd::expected:
if( HAS_CPP20_FLAG )
set( WHICH nsel_EXPECTED_DEFAULT )
if( EXPECTED_LITE_OPT_SELECT_STD )
set( WHICH nsel_EXPECTED_STD )
elseif( EXPECTED_LITE_OPT_SELECT_NONSTD )
set( WHICH nsel_EXPECTED_NONSTD )
endif()
target_compile_definitions( ${PROGRAM}-cpp17.t PRIVATE nsel_CONFIG_SELECT_EXPECTED=${WHICH} )
if( HAS_CPPLATEST_FLAG )
target_compile_definitions( ${PROGRAM}-cpplatest.t PRIVATE nsel_CONFIG_SELECT_EXPECTED=${WHICH} )
endif()
endif()
# configure unit tests via CTest:
enable_testing()
if( HAS_STD_FLAGS )
# # unconditionally add C++98 variant for MSVC:
# add_test( NAME test-cpp98 COMMAND ${PROGRAM}-cpp98.t )
#
# unconditionally add C++11 variant for MSVC:
add_test( NAME test-cpp11 COMMAND ${PROGRAM}-cpp11.t )
if( HAS_CPP14_FLAG )
add_test( NAME test-cpp14 COMMAND ${PROGRAM}-cpp14.t )
endif()
if( HAS_CPP17_FLAG )
add_test( NAME test-cpp17 COMMAND ${PROGRAM}-cpp17.t )
endif()
if( HAS_CPPLATEST_FLAG )
add_test( NAME test-cpplatest COMMAND ${PROGRAM}-cpplatest.t )
endif()
else()
add_test( NAME test COMMAND ${PROGRAM}.t --pass )
add_test( NAME list_version COMMAND ${PROGRAM}.t --version )
add_test( NAME list_tags COMMAND ${PROGRAM}.t --list-tags )
add_test( NAME list_tests COMMAND ${PROGRAM}.t --list-tests )
endif()
# end of file

View File

@@ -0,0 +1,131 @@
// Copyright (c) 2016-2018 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "expected-main.t.hpp"
#define expected_PRESENT( x ) \
std::cout << #x << ": " << x << "\n"
#define expected_ABSENT( x ) \
std::cout << #x << ": (undefined)\n"
// Suppress:
// - unused parameter, for cases without assertions such as [.std...]
#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunused-parameter"
#elif defined __GNUC__
# pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
lest::tests & specification()
{
static lest::tests tests;
return tests;
}
CASE( "expected-lite version" "[.expected][.version]" )
{
expected_PRESENT( expected_lite_MAJOR );
expected_PRESENT( expected_lite_MINOR );
expected_PRESENT( expected_lite_PATCH );
expected_PRESENT( expected_lite_VERSION );
}
CASE( "any configuration" "[.expected][.config]" )
{
expected_PRESENT( nsel_HAVE_STD_EXPECTED );
expected_PRESENT( nsel_USES_STD_EXPECTED );
expected_PRESENT( nsel_EXPECTED_DEFAULT );
expected_PRESENT( nsel_EXPECTED_NONSTD );
expected_PRESENT( nsel_EXPECTED_STD );
expected_PRESENT( nsel_CONFIG_SELECT_EXPECTED );
expected_PRESENT( nsel_CONFIG_NO_EXCEPTIONS );
expected_PRESENT( nsel_CPLUSPLUS );
}
CASE( "__cplusplus" "[.stdc++]" )
{
expected_PRESENT( __cplusplus );
#ifdef _MSVC_LANG
expected_PRESENT( _MSVC_LANG );
#else
expected_ABSENT( _MSVC_LANG );
#endif
}
CASE( "Compiler version" "[.compiler]" )
{
#if nsel_USES_STD_EXPECTED
std::cout << "(Compiler version not available: using std::expected)\n";
#else
expected_PRESENT( nsel_COMPILER_CLANG_VERSION );
expected_PRESENT( nsel_COMPILER_GNUC_VERSION );
expected_PRESENT( nsel_COMPILER_MSVC_VERSION );
#endif
}
CASE( "presence of C++ language features" "[.stdlanguage]" )
{
#if nsel_USES_STD_EXPECTED
std::cout << "(Presence of C++ language features not available: using std::expected)\n";
#else
std::cout << "[.stdlanguage]: none\n";
#endif
}
CASE( "presence of C++ library features" "[.stdlibrary]" )
{
#if nsel_USES_STD_EXPECTED
std::cout << "(Presence of C++ library features not available: using std::expected)\n";
#else
#ifdef __cpp_exceptions
expected_PRESENT( __cpp_exceptions );
#else
expected_ABSENT( __cpp_exceptions );
#endif
#ifdef __EXCEPTIONS
expected_PRESENT( __EXCEPTIONS );
#else
expected_ABSENT( __EXCEPTIONS );
#endif
#ifdef _HAS_EXCEPTIONS
expected_PRESENT( _HAS_EXCEPTIONS );
#else
expected_ABSENT( _HAS_EXCEPTIONS );
#endif
#ifdef _CPPUNWIND
expected_PRESENT( _CPPUNWIND );
#else
expected_ABSENT( _CPPUNWIND );
#endif
#endif
}
int main( int argc, char * argv[] )
{
return lest::run( specification(), argc, argv );
}
#if 0
g++ -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++98 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++03 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++0x -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++11 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++14 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
g++ -std=c++17 -I../include -o expected-lite.t.exe expected-lite.t.cpp && expected-lite.t.exe --pass
cl -EHsc -I../include expected-lite.t.cpp && expected-lite.t.exe --pass
#endif
// end of file

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2016-2018 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#pragma once
#ifndef TEST_EXPECTED_LITE_H_INCLUDED
#define TEST_EXPECTED_LITE_H_INCLUDED
// Limit C++ Core Guidelines checking to expected-lite:
#include "nonstd/expected.hpp"
#if nsel_COMPILER_MSVC_VER >= 1910
# include <CppCoreCheck/Warnings.h>
# pragma warning(disable: ALL_CPPCORECHECK_WARNINGS)
#endif
#include <iosfwd>
namespace lest {
template< typename T, typename E >
std::ostream & operator<<( std::ostream & os, nonstd::expected<T,E> const & );
template< typename E >
std::ostream & operator<<( std::ostream & os, nonstd::expected<void,E> const & );
} // namespace lest
// Compiler warning suppression for usage of lest:
#ifdef __clang__
# pragma clang diagnostic ignored "-Wstring-conversion"
# pragma clang diagnostic ignored "-Wunused-parameter"
# pragma clang diagnostic ignored "-Wunused-template"
# pragma clang diagnostic ignored "-Wunused-function"
# pragma clang diagnostic ignored "-Wunused-member-function"
#elif defined __GNUC__
# pragma GCC diagnostic ignored "-Wunused-parameter"
# pragma GCC diagnostic ignored "-Wunused-function"
#endif
#include "lest.hpp"
#define CASE( name ) lest_CASE( specification(), name )
extern lest::tests & specification();
namespace lest {
// use oparator<< instead of to_string() overload;
// see http://stackoverflow.com/a/10651752/437272
template< typename T, typename E >
inline std::ostream & operator<<( std::ostream & os, nonstd::expected<T,E> const & v )
{
using lest::to_string;
return os << "[expected:" << (v ? to_string(*v) : "[empty]") << "]";
}
template< typename E >
inline std::ostream & operator<<( std::ostream & os, nonstd::expected<void,E> const & v )
{
using lest::to_string;
return os << "[expected<void>:" << (v ? "[non-empty]" : "[empty]") << "]";
}
} // namespace lest
#endif // TEST_EXPECTED_LITE_H_INCLUDED
// end of file

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2016-2020 Martin Moene
//
// https://github.com/martinmoene/expected-lite
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "nonstd/expected.hpp"
#include <iostream>
template< typename T >
void use( T const & /*x*/) {}
#define expected_PRESENT( x ) \
std::cout << #x << ": " << x << "\n"
#define expected_ABSENT( x ) \
std::cout << #x << ": (undefined)\n"
void report()
{
#ifdef __cpp_exceptions
expected_PRESENT( __cpp_exceptions );
#else
expected_ABSENT( __cpp_exceptions );
#endif
#ifdef __EXCEPTIONS
expected_PRESENT( __EXCEPTIONS );
#else
expected_ABSENT( __EXCEPTIONS );
#endif
#ifdef _HAS_EXCEPTIONS
expected_PRESENT( _HAS_EXCEPTIONS );
#else
expected_ABSENT( _HAS_EXCEPTIONS );
#endif
#ifdef _CPPUNWIND
expected_PRESENT( _CPPUNWIND );
#else
expected_ABSENT( _CPPUNWIND );
#endif
#ifdef _CPPRTTI
expected_PRESENT( _CPPRTTI );
#else
expected_ABSENT( _CPPRTTI );
#endif
}
int violate_access()
{
nonstd::expected<int, char> eu( nonstd:: make_unexpected('a') );
return eu.value();
}
int main()
{
report();
#if ! nsel_CONFIG_NO_EXCEPTIONS_SEH
return violate_access();
#else
__try
{
return violate_access();
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
std::cerr << "\n*** Executing SEH __except block ***\n";
}
#endif
}
// end of file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
#define EXPECTED_TWEAK_VALUE 42

View File

@@ -0,0 +1,3 @@
#include "nonstd/expected.hpp"
MAIN

View File

@@ -0,0 +1,63 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: t.bat - compile & run tests (MSVC).
::
set unit=expected
:: if no std is given, use compiler default
set std=%1
if not "%std%"=="" set std=-std:%std%
call :CompilerVersion version
echo VC%version%: %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
set msvc_defines=^
-Dlest_FEATURE_AUTO_REGISTER=1 ^
-D_CRT_SECURE_NO_WARNINGS ^
-D_SCL_SECURE_NO_WARNINGS ^
-D_HAS_EXCEPTIONS=0 ^
-Dnsel_CONFIG_NO_SEH=0
:: -Dnsel_CONFIG_NO_EXCEPTIONS=1
set CppCoreCheckInclude=%VCINSTALLDIR%\Auxiliary\VS\include
:: -EHsc
::cl -kernel -GR- -W3 %std% %unit_select% %unit_config% %msvc_defines% -I"%CppCoreCheckInclude%" -I../include -I. %unit%-noexcept.t.cpp && %unit%-noexcept.t.exe
cl -EHs -GR- -W3 %std% %unit_select% %unit_config% %msvc_defines% -I"%CppCoreCheckInclude%" -Ilest -I../include -I. %unit%-noexcept.t.cpp && %unit%-noexcept.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
@echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> >%tmpsource%
echo int main(){printf("%%d\n",_MSC_VER);} >>%tmpsource%
cl /nologo %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
set offset=0
if %version% LSS 1900 set /a offset=1
set /a version="version / 10 - 10 * ( 5 + offset )"
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,3 @@
cl -EHsc -DMAIN="" -Dnsel_P0323R=-2 -I../include -I. -Foodr1.obj -c odr.cpp
cl -EHsc -DMAIN="int main(){}" -Dnsel_P0323R=-2 -I../include -I. -Foodr2.obj -c odr.cpp
cl odr1.obj odr2.obj

View File

@@ -0,0 +1,57 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: t.bat - compile & run tests (MSVC).
::
set unit=expected
:: if no std is given, use compiler default
set std=%1
if not "%std%"=="" set std=-std:%std%
call :CompilerVersion version
echo VC%version%: %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
set msvc_defines=^
-Dlest_FEATURE_AUTO_REGISTER=1 ^
-D_CRT_SECURE_NO_WARNINGS ^
-D_SCL_SECURE_NO_WARNINGS
set CppCoreCheckInclude=%VCINSTALLDIR%\Auxiliary\VS\include
cl -nologo -W3 -EHsc %std% %unit_select% %unit_config% %msvc_defines% -I"%CppCoreCheckInclude%" -Ilest -I../include -I. %unit%-main.t.cpp %unit%.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
@echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> >%tmpsource%
echo int main(){printf("%%d\n",_MSC_VER);} >>%tmpsource%
cl /nologo %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
set offset=0
if %version% LSS 1900 set /a offset=1
set /a version="version / 10 - 10 * ( 5 + offset )"
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,60 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tc-cl.bat - compile & run tests (clang-cl).
::
set unit=expected
set unit_file=%unit%
:: if no std is given, use c++14
set std=c++14
if NOT "%1" == "" set std=%1 & shift
set UCAP=%unit%
call :toupper UCAP
set unit_select=%unit%_%UCAP%_NONSTD
::set unit_select=%unit%_CONFIG_SELECT_%UCAP%_NONSTD
if NOT "%1" == "" set unit_select=%1 & shift
set args=%1 %2 %3 %4 %5 %6 %7 %8 %9
set clang=clang-cl
call :CompilerVersion version
echo %clang% %version%: %std% %unit_select% %args%
set unit_config=^
-Dlest_FEATURE_AUTO_REGISTER=1 ^
-D%unit%_%UCAP%_HEADER=\"nonstd/%unit%.hpp\" ^
-D%unit%_TEST_NODISCARD=0 ^
-D%unit%_CONFIG_SELECT_%UCAP%=%unit_select%
rem -flto / -fwhole-program
set optflags=-O2
set warnflags=-Wall -Wextra -Wpedantic -Weverything -Wshadow -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors -Wno-sign-conversion -Wno-sign-compare -Wno-implicit-int-conversion -Wno-deprecated-declarations -Wno-date-time
"%clang%" -EHsc -std:%std% %optflags% %warnflags% %unit_config% -fms-compatibility-version=19.00 /imsvc lest -I../include -Ics_string -I. -o %unit_file%-main.t.exe %unit_file%-main.t.cpp %unit_file%.t.cpp && %unit_file%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__clang_major__,__clang_minor__,__clang_patchlevel__);} >> %tmpsource%
"%clang%" -m32 -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,53 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tc.bat - compile & run tests (clang).
::
set unit=expected
:: if no std is given, use c++14
set std=%1
if "%std%"=="" set std=c++14
set clang=clang
call :CompilerVersion version
echo %clang% %version%: %std%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
rem -flto / -fwhole-program
set optflags=-O2 -fno-exceptions
set warnflags=-Wall -Wextra -Wpedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors
"%clang%" -m32 -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -Dlest_FEATURE_AUTO_REGISTER=1 -fms-compatibility-version=19.00 -isystem "%VCInstallDir%include" -isystem "%WindowsSdkDir_71A%include" -I../include -I. -o %unit%-main.t.exe %unit%-noexcept.t.cpp && %unit%-noexcept.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__clang_major__,__clang_minor__,__clang_patchlevel__);} >> %tmpsource%
"%clang%" -m32 -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,53 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tc.bat - compile & run tests (clang).
::
set unit=expected
:: if no std is given, use c++14
set std=%1
if "%std%"=="" set std=c++14
set clang=clang
call :CompilerVersion version
echo %clang% %version%: %std%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
rem -flto / -fwhole-program
set optflags=-O2
set warnflags=-Wall -Wextra -Wpedantic -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded -Wno-missing-noreturn -Wno-documentation-unknown-command -Wno-documentation-deprecated-sync -Wno-documentation -Wno-weak-vtables -Wno-missing-prototypes -Wno-missing-variable-declarations -Wno-exit-time-destructors -Wno-global-constructors
"%clang%" -m32 -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -Dlest_FEATURE_AUTO_REGISTER=1 -fms-compatibility-version=19.00 -isystem "%VCInstallDir%include" -isystem "%WindowsSdkDir_71A%include" -isystem lest -I../include -I. -o %unit%-main.t.exe %unit%-main.t.cpp %unit%.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__clang_major__,__clang_minor__,__clang_patchlevel__);} >> %tmpsource%
"%clang%" -m32 -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,3 @@
@for %%s in ( c++98 c++03 c++11 c++14 c++17 ) do (
call tg.bat %%s
)

View File

@@ -0,0 +1,55 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tg.bat - compile & run tests (GNUC).
::
set unit=expected
:: if no std is given, use c++11
set std=%1
set args=%2 %3 %4 %5 %6 %7 %8 %9
if "%1" == "" set std=c++11
set gpp=g++
call :CompilerVersion version
echo %gpp% %version%: %std% %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=-Dnsel_CONFIG_NO_EXCEPTIONS=1
rem -flto / -fwhole-program
set optflags=-O2 -fno-exceptions
set warnflags=-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wno-padded -Wno-missing-noreturn
%gpp% -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -o %unit%-main.t.exe -Dlest_FEATURE_AUTO_REGISTER=1 -I../include -I. %unit%-noexcept.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__);} >> %tmpsource%
%gpp% -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

View File

@@ -0,0 +1,55 @@
@echo off & setlocal enableextensions enabledelayedexpansion
::
:: tg.bat - compile & run tests (GNUC).
::
set unit=expected
:: if no std is given, use c++11
set std=%1
set args=%2 %3 %4 %5 %6 %7 %8 %9
if "%1" == "" set std=c++11
set gpp=g++
call :CompilerVersion version
echo %gpp% %version%: %std% %args%
set UCAP=%unit%
call :toupper UCAP
set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_DEFAULT
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_NONSTD
::set unit_select=-D%unit%_CONFIG_SELECT_%UCAP%=%unit%_%UCAP%_STD
set unit_config=
rem -flto / -fwhole-program
set optflags=-O2
set warnflags=-Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wno-padded -Wno-missing-noreturn
%gpp% -std=%std% %optflags% %warnflags% %unit_select% %unit_config% -o %unit%-main.t.exe -Dlest_FEATURE_AUTO_REGISTER=1 -isystem lest -I../include -I. %unit%-main.t.cpp %unit%.t.cpp && %unit%-main.t.exe
endlocal & goto :EOF
:: subroutines:
:CompilerVersion version
echo off & setlocal enableextensions
set tmpprogram=_getcompilerversion.tmp
set tmpsource=%tmpprogram%.c
echo #include ^<stdio.h^> > %tmpsource%
echo int main(){printf("%%d.%%d.%%d\n",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__);} >> %tmpsource%
%gpp% -o %tmpprogram% %tmpsource% >nul
for /f %%x in ('%tmpprogram%') do set version=%%x
del %tmpprogram%.* >nul
endlocal & set %1=%version%& goto :EOF
:: toupper; makes use of the fact that string
:: replacement (via SET) is not case sensitive
:toupper
for %%L IN (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO SET %1=!%1:%%L=%%L!
goto :EOF

12
cmake/external/glib/cppgir/format.sh vendored Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
cd $1
echo -n "Running dos2unix "
git ls-files | grep '\.cpp$\|\.h$\|\.hpp$' | \
xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
echo
echo -n "Running clang-format "
git ls-files | grep '\.cpp$\|\.h$\|\.hpp$' | \
xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
echo

503
cmake/external/glib/cppgir/gi/base.hpp vendored Normal file
View File

@@ -0,0 +1,503 @@
#ifndef GI_BASE_HPP
#define GI_BASE_HPP
#include "gi_inc.hpp"
#include "boxed.hpp"
#include "objectbase.hpp"
// un-inline some glib parts
// (otherwise they have internal linkage and not usable in non-TU-local context)
#ifdef GI_MODULE_IN_INTERFACE
#ifdef g_strdup
#undef g_strdup
#endif
#endif
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
inline std::string
exception_desc(const std::exception &e)
{
auto desc = e.what();
return desc ? desc : typeid(e).name();
}
inline std::string
exception_desc(...)
{
return "[unknown]";
}
template<typename E>
[[noreturn]] inline void
try_throw(E &&e)
{
#if GI_CONFIG_EXCEPTIONS
throw std::forward<E>(e);
#else
g_critical("no throw exception; %s", exception_desc(e).c_str());
abort();
#endif
}
// constructor does not appreciate NULL, so wrap that here
// map NULL to empty string; not quite the same, but it will do
inline std::string
make_string(const char *s)
{
return std::string(s ? s : "");
}
// helper string subtype
// used to overload unwrap of optional string argument
// (transfrom empty string to null)
// NOTE std::optional requires C++17
class optional_string : public std::string
{};
class noncopyable
{
public:
noncopyable() {}
noncopyable(const noncopyable &) = delete;
noncopyable &operator=(const noncopyable &) = delete;
noncopyable(noncopyable &&) = default;
noncopyable &operator=(noncopyable &&) = default;
};
class scope_guard : public noncopyable
{
private:
std::function<void()> cleanup_;
public:
scope_guard(std::function<void()> &&cleanup) : cleanup_(std::move(cleanup)) {}
~scope_guard() noexcept(false)
{
#if GI_CONFIG_EXCEPTIONS
#if __cplusplus >= 201703L
auto pending = std::uncaught_exceptions();
#else
auto pending = std::uncaught_exception();
#endif
try {
#endif
cleanup_();
#if GI_CONFIG_EXCEPTIONS
} catch (...) {
if (!pending)
throw;
}
#endif
}
};
// as in
// http://ericniebler.com/2013/08/07/universal-references-and-the-copy-constructo/
template<typename A, typename B>
using disable_if_same_or_derived = typename std::enable_if<
!std::is_base_of<A, typename std::remove_reference<B>::type>::value>::type;
} // namespace detail
namespace repository
{
// class types declare c type within class
// others can do so using this (e.g. enum)
template<typename CppType>
struct declare_ctype_of
{};
// and for all cases the reverse cpp type
template<typename CType>
struct declare_cpptype_of
{};
// generate code must specialize appropriately
template<typename T>
struct is_enumeration : public std::false_type
{};
template<typename T>
struct is_bitfield : public std::false_type
{};
} // namespace repository
struct transfer_full_t;
struct transfer_none_t;
namespace traits
{
template<typename T, typename U = void>
struct if_valid_type
{
typedef U type;
};
template<typename, typename = void>
struct is_type_complete : public std::false_type
{};
template<typename T>
struct is_type_complete<T, typename if_valid_type<decltype(sizeof(
typename std::decay<T>::type))>::type>
: public std::true_type
{};
template<typename T>
using is_decayed = std::is_same<typename std::decay<T>::type, T>;
template<typename T>
using is_cboxed =
typename std::conditional<std::is_base_of<detail::CBoxed, T>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_gboxed =
typename std::conditional<std::is_base_of<detail::GBoxed, T>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_boxed =
typename std::conditional<std::is_base_of<detail::Boxed, T>::value,
std::true_type, std::false_type>::type;
// avoid derived cases
template<typename T>
using is_object =
typename std::conditional<std::is_base_of<detail::ObjectBase, T>::value &&
sizeof(T) == sizeof(gpointer),
std::true_type, std::false_type>::type;
template<typename T>
using is_wrapper =
typename std::conditional<std::is_base_of<detail::wrapper_tag, T>::value &&
sizeof(T) == sizeof(gpointer),
std::true_type, std::false_type>::type;
// bring in to this namespace
using repository::is_bitfield;
using repository::is_enumeration;
// aka passthrough
template<typename T>
using is_basic =
typename std::conditional<std::is_same<T, gpointer>::value ||
std::is_same<T, gconstpointer>::value ||
std::is_arithmetic<T>::value,
std::true_type, std::false_type>::type;
// almost passthrough (on lower level at least)
template<typename T>
using is_plain = typename std::conditional<traits::is_basic<T>::value ||
std::is_enum<T>::value,
std::true_type, std::false_type>::type;
template<typename T, typename E = void>
struct is_reftype : public std::false_type
{};
template<typename T>
struct is_reftype<T,
typename if_valid_type<typename std::decay<T>::type::BoxType>::type>
: public std::true_type
{};
template<typename T, typename Enable = void>
struct has_ctype_member : public std::false_type
{};
template<typename T>
struct has_ctype_member<T,
typename if_valid_type<typename T::BaseObjectType>::type>
: public std::true_type
{};
// return corresponding c type (if any)
// (string and basic type not considered)
// preserve const
template<typename T, typename Enable = void>
struct ctype
{};
// class case
template<typename T>
struct ctype<T,
typename if_valid_type<typename std::decay<T>::type::BaseObjectType>::type>
{
typedef typename std::remove_reference<T>::type CppType;
// make sure; avoid subclassed cases
static_assert(is_wrapper<CppType>::value || is_boxed<CppType>::value,
"must be object or boxed wrapper");
typedef typename CppType::BaseObjectType CType;
typedef typename std::conditional<std::is_const<CppType>::value, const CType,
CType>::type *type;
};
// remaining cases
template<typename T>
struct ctype<T, typename if_valid_type<
typename repository::declare_ctype_of<T>::type>::type>
{
typedef typename repository::declare_ctype_of<T>::type CType;
typedef typename std::conditional<std::is_const<T>::value, const CType,
CType>::type type;
};
// basic cases passthrough
template<typename T>
struct ctype<T,
typename std::enable_if<(std::is_fundamental<T>::value &&
!std::is_same<T, bool>::value) ||
std::is_same<T, gpointer>::value ||
std::is_same<T, gconstpointer>::value>::type>
{
typedef T type;
};
// ... exception though for bool
template<>
struct ctype<bool, void>
{
typedef gboolean type;
};
// as used in callback signatures
// or in list (un)wrapping
template<>
struct ctype<const std::string &, void>
{
typedef const char *type;
};
template<>
struct ctype<std::string, void>
{
typedef char *type;
};
template<typename T1, typename T2>
struct ctype<std::pair<T1, T2>>
{
typedef std::pair<typename ctype<T1>::type, typename ctype<T2>::type> type;
};
// conversely
// return corresponding cpp type (if known)
// (string and basic type not considered)
// preserve const
template<typename T, typename Transfer = transfer_full_t,
typename Enable = void>
struct cpptype
{};
// generic
template<typename T>
struct cpptype<T *, transfer_full_t,
typename if_valid_type<typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type>::type>
{
typedef typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type CppType;
typedef typename std::conditional<std::is_const<T>::value, const CppType,
CppType>::type type;
};
template<typename T>
struct cpptype<T, transfer_full_t,
typename if_valid_type<typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type>::type>
{
typedef typename repository::declare_cpptype_of<
typename std::remove_const<T>::type>::type CppType;
typedef typename std::conditional<std::is_const<T>::value, const CppType,
CppType>::type type;
};
// basic cases passthrough
template<typename T>
struct cpptype<T, transfer_full_t,
typename std::enable_if<std::is_fundamental<T>::value ||
std::is_same<T, gpointer>::value ||
std::is_same<T, gconstpointer>::value>::type>
{
typedef T type;
};
#if 0
template<>
struct cpptype<char *, transfer_full_t>
{
using type = std::string;
};
#endif
// handle none transfer case
template<typename T>
struct cpptype<T, transfer_none_t>
{
using CppType = typename cpptype<T, transfer_full_t>::type;
template<typename TT, typename Enable = void>
struct map_type
{
using type = TT;
};
template<typename TT>
struct map_type<TT, typename if_valid_type<typename TT::ReferenceType>::type>
{
using type = typename TT::ReferenceType;
};
using type = typename map_type<CppType>::type;
};
// map owning box type to corresponding reference box type
template<typename T>
struct reftype
{
typedef typename T::ReferenceType type;
};
} // namespace traits
// specify transfer type when (un)wrapping
// this approach is safer than some booleans and allows overload combinations
struct transfer_t
{
const int value;
constexpr explicit transfer_t(int v = 0) : value(v) {}
};
struct transfer_none_t : public transfer_t
{
constexpr transfer_none_t() : transfer_t(0) {}
};
struct transfer_full_t : public transfer_t
{
constexpr transfer_full_t() : transfer_t(1) {}
};
struct transfer_container_t : public transfer_t
{
constexpr transfer_container_t() : transfer_t(2) {}
};
GI_MODULE_INLINE const constexpr transfer_t transfer_dummy = transfer_t();
GI_MODULE_INLINE const constexpr transfer_none_t transfer_none =
transfer_none_t();
GI_MODULE_INLINE const constexpr transfer_full_t transfer_full =
transfer_full_t();
GI_MODULE_INLINE const constexpr transfer_container_t transfer_container =
transfer_container_t();
template<typename Transfer>
struct element_transfer
{};
template<>
struct element_transfer<transfer_none_t>
{
typedef transfer_none_t type;
};
template<>
struct element_transfer<transfer_full_t>
{
typedef transfer_full_t type;
};
template<>
struct element_transfer<transfer_container_t>
{
typedef transfer_none_t type;
};
// unwrapping a callback
// specify call scope type
struct scope_t
{
const int value;
constexpr explicit scope_t(int v = 0) : value(v) {}
};
struct scope_call_t : public scope_t
{
constexpr scope_call_t() : scope_t(0) {}
};
struct scope_async_t : public scope_t
{
constexpr scope_async_t() : scope_t(1) {}
};
struct scope_notified_t : public scope_t
{
constexpr scope_notified_t() : scope_t(2) {}
};
GI_MODULE_INLINE const constexpr scope_t scope_dummy = scope_t();
GI_MODULE_INLINE const constexpr scope_call_t scope_call = scope_call_t();
GI_MODULE_INLINE const constexpr scope_async_t scope_async = scope_async_t();
GI_MODULE_INLINE const constexpr scope_notified_t scope_notified =
scope_notified_t();
// (dummy) helper tag to aid in overload resolution
template<typename Interface>
struct interface_tag
{
typedef Interface type;
};
// CallArgs minimal helper type
// only provide what is needed
// not intended for general use, even though not in internal namespace
template<typename T>
class required
{
T data_;
public:
constexpr required(T v) : data_(std::move(v)) {}
constexpr operator T &() &noexcept { return data_; }
constexpr operator T &&() &&noexcept { return std::move(data_); }
constexpr T &value() &noexcept { return data_; }
constexpr T &&value() &&noexcept { return std::move(data_); }
};
// helper tag type to aid in overload resolution of CallArgs
// this is used as the first argument of a (non-plain) signature variant
// (since it may otherwise have only 1 struct argument, like the plain variant)
template<typename... CA_TAG>
using ca = std::tuple<CA_TAG...>;
struct ca_tag
{};
struct ca_in_tag : public ca_tag
{};
struct ca_bc_tag : public ca_tag
{};
// convenience abbreviation
using ca_in = ca<ca_in_tag>;
#if GI_DL
namespace detail
{
// dynamic load of symbol
inline void *
load_symbol(const std::vector<const char *> libs, const char *symbol)
{
void *s = nullptr;
for (const auto &l : libs) {
auto h = dlopen(l, RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE);
if (h) {
s = dlsym(h, symbol);
dlclose(h);
if (s)
break;
}
}
return s;
}
} // namespace detail
#endif // GI_DL
} // namespace gi
#endif // GI_BASE_HPP

349
cmake/external/glib/cppgir/gi/boxed.hpp vendored Normal file
View File

@@ -0,0 +1,349 @@
#ifndef GI_BOXED_HPP
#define GI_BOXED_HPP
#include "gi_inc.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace repository
{
// specialize to enable copyable boxed wrapper
// ideally only used in case where the copy is (equivalently);
// * cheap
// * does not change the underlying pointer
// * refcount based
// * has no semantics effects
// (e.g. GstMiniObject refcount affects writable)
template<typename CType>
struct enable_boxed_copy :
#if GI_ENABLE_BOXED_COPY_ALL
public std::true_type
#else
public std::false_type
#endif
{};
} // namespace repository
namespace detail
{
// class tags
class Boxed
{};
class CBoxed : public Boxed
{};
class GBoxed : public Boxed
{};
template<typename CType>
class SharedWrapper
{
public:
typedef SharedWrapper self;
protected:
CType *data_ = nullptr;
public:
CType *gobj_() { return data_; }
const CType *gobj_() const { return data_; }
explicit operator bool() const { return (bool)data_; }
bool operator==(const SharedWrapper &other) const
{
return data_ == other.data_;
}
bool operator==(std::nullptr_t o) const { return data_ == o; }
bool operator!=(const SharedWrapper &other) const
{
return data_ != other.data_;
}
bool operator!=(std::nullptr_t o) const { return data_ != o; }
CType *release_()
{
auto tmp = this->data_;
this->data_ = nullptr;
return tmp;
}
};
template<typename CppType, typename CType>
struct GBoxedFuncs
{
static CType *copy(const void *data)
{
return (CType *)g_boxed_copy(CppType::get_type_(), data);
}
static void free(void *data) { g_boxed_free(CppType::get_type_(), data); }
};
struct CBoxedFuncsBase
{
static void free(void *data) { g_free(data); }
};
template<typename CppType, typename CType>
struct CBoxedFuncs : CBoxedFuncsBase
{
static CType *copy(const void *data)
{
#if GLIB_CHECK_VERSION(2, 68, 0)
return (CType *)g_memdup2(data, sizeof(CType));
#else
return (CType *)g_memdup(data, sizeof(CType));
#endif
}
};
template<typename CType, typename Funcs, typename TagType>
class BoxedWrapper : public SharedWrapper<CType>, public TagType
{
typedef BoxedWrapper self;
protected:
static void _deleter(CType *obj, ...)
{
if (obj)
Funcs::free(obj);
}
static CType *_copy(const CType *obj)
{
return obj ? Funcs::copy(obj) : nullptr;
}
public:
typedef CType BaseObjectType;
BoxedWrapper(CType *obj = nullptr) noexcept { this->data_ = obj; }
CType *gobj_copy_() const { return _copy(this->gobj_()); }
// resulting casted type determines ownership
template<typename Cpp>
static Cpp wrap(const typename Cpp::BaseObjectType *obj)
{
static_assert(sizeof(Cpp) == sizeof(self), "type wrap not supported");
static_assert(std::is_base_of<self, Cpp>::value, "type wrap not supported");
BoxedWrapper w(const_cast<typename Cpp::BaseObjectType *>(obj));
return std::move(*static_cast<Cpp *>(&w));
}
};
// in templates below, Base should be a subclass of BoxedWrapper
// so we re-use the members it provides, as well as the wrap template
// to avoid ambiguous reference to the latter
// (if BoxedWrapper were inherited from again)
// the nullptr_t constructor (indirectly) supports `= nullptr` (assignment)
template<typename CppType, typename CType>
using GBoxedWrapperBase =
BoxedWrapper<CType, GBoxedFuncs<CppType, CType>, GBoxed>;
// assuming Base has suitable members such as above BoxedWrapper
template<typename Base>
class MoveWrapper : public Base
{
typedef Base super;
public:
using super::super;
~MoveWrapper() { this->_deleter(this->data_, static_cast<Base *>(this)); }
MoveWrapper(const MoveWrapper &) = delete;
MoveWrapper &operator=(const MoveWrapper &) = delete;
MoveWrapper(MoveWrapper &&o) noexcept { *this = std::move(o); }
MoveWrapper &operator=(MoveWrapper &&o) noexcept
{
if (this != &o) {
this->_deleter(this->data_, static_cast<Base *>(this));
(Base &)(*this) = std::move(o);
o.data_ = nullptr;
}
return *this;
}
};
template<typename Base>
class CopyWrapper : public MoveWrapper<Base>
{
typedef MoveWrapper<Base> super;
public:
using super::super;
CopyWrapper(const CopyWrapper &o) noexcept : super()
{
this->data_ = this->_copy(o.data_);
}
CopyWrapper &operator=(const CopyWrapper &o) noexcept
{
if (this != &o) {
this->_deleter(this->data_, static_cast<Base *>(this));
if (sizeof(Base) > sizeof(this->data_))
(Base &)(*this) = std::move(o);
this->data_ = this->_copy(o.data_);
}
return *this;
}
CopyWrapper(CopyWrapper &&o) noexcept = default;
CopyWrapper &operator=(CopyWrapper &&o) noexcept = default;
// also accept from corresponding Reference (also based on Base)
CopyWrapper(const Base &o) noexcept : super()
{
this->data_ = this->_copy(((CopyWrapper &)o).data_);
}
CopyWrapper(Base &&o) noexcept : super()
{
(super &)(*this) = std::move((super &)o);
}
};
template<typename CType, typename Base>
using SelectWrapper =
typename std::conditional<repository::enable_boxed_copy<CType>::value,
CopyWrapper<Base>, MoveWrapper<Base>>::type;
// basis for registered boxed types
template<typename CppType, typename CType,
typename Base = GBoxedWrapperBase<CppType, CType>, typename RefType = void>
class GBoxedWrapper : public SelectWrapper<CType, Base>
{
typedef SelectWrapper<CType, Base> super;
public:
typedef RefType ReferenceType;
using super::super;
GBoxedWrapper(std::nullptr_t = nullptr) {}
void allocate_()
{
if (this->data_)
return;
// make sure we match boxed allocation with boxed free
// still guessing here that all-0 makes for a decent init :-(
CType tmp;
memset(&tmp, 0, sizeof(tmp));
this->data_ = (CType *)g_boxed_copy(this->get_type_(), &tmp);
}
// essentially g_boxed_copy
CppType copy_() const
{
return super::template wrap<CppType>(this->gobj_copy_());
}
};
template<typename CppType, typename CType>
using CBoxedWrapperBase =
BoxedWrapper<CType, CBoxedFuncs<CppType, CType>, CBoxed>;
// basis for non-registered plain C boxed type
template<typename CppType, typename CType,
typename Base = CBoxedWrapperBase<CppType, CType>, typename RefType = void>
class CBoxedWrapper : public MoveWrapper<Base>
{
typedef Base super;
public:
typedef RefType ReferenceType;
CBoxedWrapper(std::nullptr_t = nullptr) {}
void allocate_()
{
if (this->data_)
return;
this->data_ = g_new0(CType, 1);
}
static CppType new_()
{
return super::template wrap<CppType>(g_new0(CType, 1));
}
};
// allocate helper;
// dispatch to method if available
template<typename T, typename Enable = void>
struct allocator : public std::false_type
{
static void allocate(T &) {}
};
template<typename T>
struct allocator<T, decltype(T().allocate_())> : public std::true_type
{
static void allocate(T &v) { v.allocate_(); }
};
template<typename T>
void
allocate(T &v)
{
allocator<T>::allocate(v);
}
// basis for ref-holding to registered box type
template<typename CppType, typename CType, typename Base>
class GBoxedRefWrapper : public Base
{
typedef Base super;
public:
typedef CppType BoxType;
GBoxedRefWrapper(std::nullptr_t = nullptr) {}
// construct from owning version, but not the other way around
// (which requires an explicit copy)
GBoxedRefWrapper(const CppType &other)
{
(super &)(*this) = super::template wrap<super>(other.gobj_());
}
// support way to convert to owning box
// (by means of copy as opposed to a simple ref)
CppType copy_() const
{
return super::template wrap<CppType>(this->gobj_copy_());
}
};
// basis for ref-holding to non-registered plain C boxed type
template<typename CppType, typename CType, typename Base>
class CBoxedRefWrapper : public Base
{
typedef Base super;
public:
typedef CppType BoxType;
CBoxedRefWrapper(std::nullptr_t = nullptr) {}
// construct from owning version, but not the other way around
// (which requires an explicit copy)
CBoxedRefWrapper(const CppType &other)
{
(super &)(*this) = super::template wrap<super>(other.gobj_());
}
};
} // namespace detail
} // namespace gi
#endif // GI_BOXED_HPP

View File

@@ -0,0 +1,960 @@
#ifndef GI_CALLBACK_HPP
#define GI_CALLBACK_HPP
#include "base.hpp"
#include "exception.hpp"
#include "wrap.hpp"
GI_MODULE_EXPORT
namespace gi
{
namespace detail
{
#if GI_CONFIG_EXCEPTIONS
inline ::GError **
find_gerror(bool &has_gerror)
{
has_gerror = false;
return nullptr;
}
inline ::GError **
find_gerror(bool &has_gerror, ::GError **error)
{
has_gerror = true;
return error;
}
template<typename CT, typename... CType>
inline ::GError **
find_gerror(bool &has_gerror, CT /*arg*/, CType... args)
{
return find_gerror(has_gerror, args...);
}
inline ::GError *
exception_error(const repository::GLib::Error &exc)
{
return g_error_copy(exc.gobj_());
}
inline ::GError *
exception_error(const repository::GLib::Error_Ref &exc)
{
return g_error_copy(exc.gobj_());
}
template<typename E>
inline ::GError *
exception_error(const E &exc)
{
static auto quark = g_quark_from_static_string("gi-error-quark");
return g_error_new(quark, 0, "%s", exception_desc(exc).c_str());
}
template<bool SILENT = FALSE, typename E, typename... CType>
void
report_exception(const E &exc, CType... args)
{
// see if we can really report error somewhere
bool has_gerror = false;
GError **error = find_gerror(has_gerror, args...);
if (has_gerror) {
g_return_if_fail(error == NULL || *error == NULL);
if (error)
*error = exception_error(exc);
// if caller does not need/want error, exception disappears here
} else {
// simply report the hard and simple way
// otherwise catch internally if something else/more is desired
if (!SILENT) {
auto msg = std::string("handler exception; ") + exception_desc(exc);
g_critical("%s", msg.c_str());
}
}
}
#endif
// (re)float only applies to GObject
template<typename CppType, typename Transfer,
typename std::enable_if<!traits::is_object<CppType>::value>::type * =
nullptr>
auto
unwrap_maybe_float(CppType &&v, const Transfer &t)
{
return unwrap(std::forward<CppType>(v), t);
}
// (re) float only for transfer none/floating
template<typename CppType,
typename std::enable_if<traits::is_object<CppType>::value>::type * =
nullptr>
inline typename traits::ctype<CppType>::type
unwrap_maybe_float(CppType &&v, const transfer_full_t &t)
{
return unwrap(std::forward<CppType>(v), t);
}
template<typename CppType,
typename std::enable_if<traits::is_object<CppType>::value>::type * =
nullptr>
inline typename traits::ctype<CppType>::type
unwrap_maybe_float(CppType &&v, const transfer_none_t &)
{
// expected called with r-value
static_assert(!std::is_reference<CppType>::value, "");
// steal/take from wrapper
auto result = (typename traits::ctype<CppType>::type)v.release_();
// the following is essentially a bit of a hack as mentioned in
// https://bugzilla.gnome.org/show_bug.cgi?id=693393)
// that is, we are about to return an object to C (from binding/callback)
// and this should be done with none transfer
// this none may actually mean floating (e.g. a factory-like callback)
// but no way to know from annotations
// so if at runtime the wrapper actually holds the only reference,
// then it is about to be destroyed (when wrapper goes away)
// *before* the object can make it back to caller
// so turn that ref into a floating one (as only that makes sense)
// if it is not the only ref, it is kept alive elsewhere
// (as typically so for a "getter" callback)
// so it is really treated as none
auto obj = (GObject *)result;
// theoretically not MT safe, but if == 1, only 1 thread should be involved
if (obj->ref_count == 1) {
g_object_force_floating(obj);
} else {
// otherwise unref as wrapper would have
g_object_unref(obj);
}
return result;
}
template<typename T>
constexpr T
unconst(T t)
{
return t;
}
inline char *
unconst(const char *t)
{
return (char *)t;
}
// helper types to provide additional argument info beyond Transfer
template<std::size_t... index>
struct args_index
{
static constexpr auto value = std::make_tuple(index...);
using value_type = decltype(value);
};
template<typename Transfer, bool _inout, typename CustomTraits = void,
typename ArgsIndex = args_index<>>
struct arg_info
{
using transfer_type = Transfer;
static constexpr bool inout = _inout;
// tuple of index; used to selects the C arguments (to assemble C++ argument)
using args_type = ArgsIndex;
// additional info as used by corresponding cb_arg_handler
using custom_type = CustomTraits;
};
// access above info by forwarding types
template<typename T, typename Enable = void>
struct arg_traits
{
using transfer_type = typename T::transfer_type;
static constexpr bool inout = T::inout;
using args_type = typename T::args_type;
using custom_type = typename T::custom_type;
};
// legacy case; only transfer type
template<typename T>
struct arg_traits<T,
typename std::enable_if<std::is_base_of<transfer_t, T>::value>::type>
{
using transfer_type = T;
static constexpr bool inout = false;
using args_type = args_index<>;
using custom_type = void;
};
// IndexTuple is essentially an args_index<...>
template<typename IndexTuple, std::size_t... Index, typename F,
typename ArgTuple>
decltype(auto)
apply_with_args(std::index_sequence<Index...>, F &&f, ArgTuple &&args)
{
return f(std::get<std::get<Index>(IndexTuple::value)>(args)...);
}
template<typename IndexTuple, typename F, typename... Args>
decltype(auto)
apply_with_args(F &&f, Args &&...args)
{
return apply_with_args<IndexTuple>(
std::make_index_sequence<
std::tuple_size<typename IndexTuple::value_type>::value>(),
std::forward<F>(f), std::forward_as_tuple(args...));
}
// a simple callback has no (need for) args_index
template<typename T>
struct is_simple_cb : public std::true_type
{};
template<typename Transfer, typename... Transfers>
struct is_simple_cb<std::tuple<Transfer, Transfers...>>
{
static constexpr bool value =
std::tuple_size<
typename arg_traits<Transfer>::args_type::value_type>::value == 0 ||
(sizeof...(Transfers) > 0 &&
is_simple_cb<std::tuple<Transfers...>>::value);
};
template<typename T, typename CT = void>
struct map_cpp_function;
// handles all calls C -> C++ (callbacks, virtual method calls)
// restrictions though on types supported (enforced by code generation)
template<typename T, typename RetTransfer, typename ArgTransfers,
typename CT = typename map_cpp_function<T>::type,
bool SIMPLE = is_simple_cb<ArgTransfers>::value>
struct transform_caller;
// helper used below that provides pre-call and post-call steps
// to handle each argument's conversion to and from C++
// in so-called (most) simple cases, there is one-to-one mapping between
// C and C++ arguments and C++ argument type that allows to deduce context
// (in particular, no callbacks or sized array)
// as such, proper steps can be obtained by specialization on Cpp argument type
template<typename CppArg, typename Enable = void>
struct cb_arg_handler;
// in simple cases, C signature can be derived from C++ signature
template<typename T, typename CT>
struct map_cpp_function
{
using type = CT;
};
template<typename R, typename... Args>
struct map_cpp_function<R(Args...), void>
{
using type = typename traits::ctype<R>::type(
typename cb_arg_handler<Args>::c_type...);
};
// signature used in virtual method handling
template<typename R, typename... Args>
struct map_cpp_function<R (*)(Args...), void>
{
using type = typename traits::ctype<R>::type(
typename cb_arg_handler<Args>::c_type...);
};
// NOTE function type R(const A) is identical to R(A)
// so no deduced Args below will retain const (if such)
// in simple cases, *Transfer* is simply a transfer type
// but it may also be a more elaborate argument trait
template<typename R, typename... Args, typename RetTransfer,
typename... Transfers, typename CR, typename... CArgs, bool SIMPLE>
struct transform_caller<R(Args...), RetTransfer, std::tuple<Transfers...>,
CR(CArgs...), SIMPLE>
{
static_assert(sizeof...(Args) == sizeof...(Transfers), "");
static_assert(!SIMPLE || sizeof...(Args) == sizeof...(CArgs), "");
typedef transform_caller self_type;
typedef R (*caller_type)(Args &&..., void *d);
private:
static R do_call(Args &&...args, caller_type func, void *d)
{
return func(std::forward<Args>(args)..., d);
}
// helper that provides context for pack expansion below
static void dummy_call(...){};
template<typename T>
static auto _tt(const T &)
{
return typename arg_traits<T>::transfer_type();
}
// non-void return
template<typename T, std::size_t... Index,
typename std::enable_if<SIMPLE && !std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
auto ret = do_call(
std::get<Index>(handlers).arg(args, _tt(Transfers()), Transfers())...,
func, d);
dummy_call((std::get<Index>(handlers).post(args, _tt(Transfers())), 0)...);
return unconst(unwrap_maybe_float(std::move(ret), RetTransfer()));
}
// void return
template<typename T, std::size_t... Index,
typename std::enable_if<SIMPLE && std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
do_call(
std::get<Index>(handlers).arg(args, _tt(Transfers()), Transfers())...,
func, d);
dummy_call((std::get<Index>(handlers).post(args, _tt(Transfers())), 0)...);
}
// complex; non-void return
template<typename T, std::size_t... Index,
typename std::enable_if<!SIMPLE && !std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
auto ret = do_call(
apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).arg(selargs...,
typename arg_traits<Transfers>::transfer_type(), Transfers());
},
args...)...,
func, d);
dummy_call((apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).post(selargs...,
typename arg_traits<Transfers>::transfer_type());
},
args...),
0)...);
return unconst(unwrap_maybe_float(std::move(ret), RetTransfer()));
}
// complex; void return
template<typename T, std::size_t... Index,
typename std::enable_if<!SIMPLE && std::is_void<T>::value>::type * =
nullptr>
static CR _wrapper(
CArgs... args, caller_type func, void *d, std::index_sequence<Index...>)
{
std::tuple<cb_arg_handler<Args>...> handlers;
do_call(apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).arg(selargs...,
typename arg_traits<Transfers>::transfer_type(),
Transfers());
},
args...)...,
func, d);
dummy_call((apply_with_args<typename arg_traits<Transfers>::args_type>(
[&handlers](auto... selargs) mutable -> decltype(auto) {
return std::get<Index>(handlers).post(selargs...,
typename arg_traits<Transfers>::transfer_type());
},
args...),
0)...);
}
public:
static CR wrapper(CArgs... args, caller_type func, void *d)
{
// exceptions should not escape into plain C
#if GI_CONFIG_EXCEPTIONS
try {
#endif
return self_type::template _wrapper<R>(
args..., func, d, std::make_index_sequence<sizeof...(Args)>());
#if GI_CONFIG_EXCEPTIONS
} catch (const repository::GLib::Error &exc) {
report_exception(exc, args...);
} catch (const repository::GLib::Error_Ref &exc) {
report_exception(exc, args...);
} catch (const std::exception &exc) {
report_exception(exc, args...);
} catch (...) {
report_exception(nullptr, args...);
}
return typename traits::ctype<R>::type();
#endif
}
};
// minor helper traits used below
namespace _traits
{
template<typename T>
struct remove_all_pointers
{
using type = T;
};
template<typename T>
struct remove_all_pointers<T *>
{
using type = typename remove_all_pointers<T>::type;
};
template<typename T>
struct remove_all_pointers<T *const>
{
using type = typename remove_all_pointers<T>::type;
};
template<typename T>
using is_basic_or_void = typename std::conditional<traits::is_basic<T>::value ||
std::is_void<T>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_basic_argument = typename std::conditional<
is_basic_or_void<typename std::remove_cv<
typename remove_all_pointers<T>::type>::type>::value,
std::true_type, std::false_type>::type;
template<typename T>
using is_const_lvalue = typename std::conditional<
std::is_lvalue_reference<T>::value &&
std::is_const<typename std::remove_reference<T>::type>::value,
std::true_type, std::false_type>::type;
} // namespace _traits
// generic fallback case; assume input parameter
// (also covers boxed callerallocates, which pretty much is/becomes input)
template<typename CppArg, typename Enable>
struct cb_arg_handler
{
using c_type = typename traits::ctype<CppArg>::type;
// use generic CType to handle const differences wrt c_type
template<typename CType, typename Transfer, typename ArgTrait>
typename std::decay<CppArg>::type arg(
CType arg, const Transfer &t, const ArgTrait &) noexcept
{
// wrap to normalized destination target (no const &)
// the destination type here is really only relevant for collection cases
// (since that depends on the contained element)
// otherwise all the info is pretty much in c_type type
return wrap_to<typename std::decay<CppArg>::type>(arg, t);
}
// minor variation; dynamic sized array input
// also accepts length input
template<typename CType, typename Transfer, typename ArgTrait>
typename std::decay<CppArg>::type arg(
CType arg, int length, const Transfer &t, const ArgTrait &) noexcept
{
// destination also really needed here
return wrap_to<typename std::decay<CppArg>::type>(arg, length, t);
}
void post(...){};
};
// passthrough on (pointers to) basic values;
// handles input/output of basic values, as well as arrays of such
template<typename CppArg>
struct cb_arg_handler<CppArg,
typename std::enable_if<_traits::is_basic_argument<CppArg>::value>::type>
{
using c_type = CppArg;
template<typename CType, typename Transfer, typename ArgTrait>
CType arg(CType arg, const Transfer &, const ArgTrait &) noexcept
{
return arg;
}
void post(...){};
// array size case; inout C int* to in Cpp int
// (only enable if so tagged by non-default custom trait type)
template<typename CType, typename Transfer, typename ArgTrait>
typename std::enable_if<
!std::is_void<typename arg_traits<ArgTrait>::custom_type>::value,
CType>::type
arg(CType *arg, const Transfer &, const ArgTrait &) noexcept
{
return *arg;
}
};
// handle "complex" (in)out argument
// (though also handles e.g. int& case, which might be optimized, but anyways)
// these are recognized/assumed to be a pointer or reference to non-basic type
template<typename CppArg>
struct cb_arg_handler<CppArg,
typename std::enable_if<
!_traits::is_basic_argument<CppArg>::value &&
(std::is_pointer<CppArg>::value ||
(std::is_lvalue_reference<CppArg>::value &&
!_traits::is_const_lvalue<CppArg>::value))>::type>
{
using BaseCppType =
typename std::decay<typename std::remove_pointer<CppArg>::type>::type;
// no more pointer expected here
// (other than for void* cases, which do not introspect well, but anyways)
static_assert(!std::is_pointer<BaseCppType>::value ||
std::is_same<BaseCppType, gpointer>::value ||
std::is_same<BaseCppType, gconstpointer>::value,
"");
using c_type = typename traits::ctype<BaseCppType>::type *;
// intermediate helper storage
BaseCppType var_{};
// pointer case
CppArg rv(std::true_type) { return &var_; }
// ref case
CppArg rv(std::false_type) { return var_; }
// handle const variations (e.g. const char**)
template<typename T, typename X>
static void assign(T *&t, X val)
{
t = const_cast<T *>(val);
}
template<typename T, typename X>
static void assign(T &t, X val)
{
t = unconst(val);
}
template<typename Transfer, typename ArgTrait>
CppArg arg(c_type arg, const Transfer &t, const ArgTrait &)
{
bool inout = arg_traits<ArgTrait>::inout;
// a plain type has no special needs
// so we can always take any bogus value as-is
// (which is then less sensitive to incorrect annotation)
if (arg && (inout || traits::is_plain<BaseCppType>::value))
var_ = wrap_to<BaseCppType>(*arg, t);
return rv(std::is_pointer<CppArg>());
}
// overall function call happens following arg call above
// post invoked after function call
template<typename Transfer>
void post(c_type arg, const Transfer &t)
{
if (arg)
assign(*arg, unwrap_maybe_float(std::move(var_), t));
}
// sized array variants; arguments (data, size)
// latter could be input (int) or (in)out (int*)
template<typename Transfer, typename Int, typename ArgTrait>
CppArg arg(c_type arg, Int size, const Transfer &t, const ArgTrait &)
{
bool inout = arg_traits<ArgTrait>::inout;
if (arg && inout)
var_ = wrap_to<BaseCppType>(*arg, size, t);
return rv(std::is_pointer<CppArg>());
}
template<typename Transfer, typename Int, typename ArgTrait>
CppArg arg(c_type arg, Int *size, const Transfer &t, const ArgTrait &)
{
bool inout = arg_traits<ArgTrait>::inout;
if (arg && size && inout)
var_ = wrap_to<BaseCppType>(*arg, *size, t);
return rv(std::is_pointer<CppArg>());
}
template<typename Transfer, typename Int>
void post(c_type arg, Int, const Transfer &t)
{
post(arg, nullptr, t);
}
template<typename Transfer, typename Int>
void post(c_type arg, Int *size, const Transfer &t)
{
if (arg)
assign(*arg, unwrap_maybe_float(std::move(var_), t));
if (size)
*size = var_.size();
}
};
// handles callback argument
// (as argument in anther callback, most likely a virtual method)
template<typename CppArg>
struct cb_arg_handler<CppArg,
typename std::enable_if<
std::is_pointer<typename CppArg::cfunction_type>::value>::type>
{
template<typename CType, typename Transfer, typename ArgTrait>
CppArg arg(CType cb, gpointer userdata, ::GDestroyNotify destroy,
const Transfer &, const ArgTrait &) noexcept
{
using ct = typename arg_traits<ArgTrait>::custom_type;
// setup shared pointer to userdata with destroy as Deleter
auto sp = destroy ? std::shared_ptr<void>(userdata, destroy) : nullptr;
// keep userdata/destroy alive as long as lambda handler
auto h = [cb, userdata, sp = std::move(sp)](
auto &&...args) -> decltype(auto) {
// original callback type CType should match deduced handler_cb_tpe
// but let's make sure as usual
return ct::handler(std::forward<decltype(args)>(args)...,
typename ct::handler_cb_type(cb), userdata);
};
return h;
}
template<typename CType, typename Transfer, typename ArgTrait>
CppArg arg(CType cb, gpointer userdata, const Transfer &t,
const ArgTrait &tt) noexcept
{
return arg(cb, userdata, nullptr, t, tt);
}
void post(...){};
};
class connection_status
{
public:
struct data
{
bool connected{false};
};
bool connected() const
{
auto sp = data_.lock();
return sp && sp->connected;
}
protected:
std::weak_ptr<data> data_;
};
// callback handling
template<typename G>
class connectable;
template<typename G, bool AUTODESTROY = false>
class callback_wrapper;
template<typename R, typename... Args>
class connectable<R(Args...)>
{
friend class callback_wrapper<R(Args...)>;
struct data : public connection_status::data
{
template<typename T,
typename Enable = typename detail::disable_if_same_or_derived<data, T>>
data(T &&t) : callable(std::forward<T>(t))
{}
std::function<R(Args... args)> callable;
};
struct connection_status_factory : public connection_status
{
connection_status_factory(std::shared_ptr<data> p)
{
data_ = std::weak_ptr<connection_status::data>(p);
}
};
public:
// avoid copy constructor mishaps
template<typename T,
typename Enable =
typename detail::disable_if_same_or_derived<connectable, T>>
connectable(T &&t) : data_(std::make_shared<data>(std::forward<T>(t)))
{}
connection_status connection() const
{
return connection_status_factory(data_);
}
R operator()(Args... args) const
{
return data_->callable(std::forward<Args>(args)...);
}
explicit operator bool() const { return data_ && data_->callable; }
private:
// state management by wrapper
void connected(bool conn) { data_->connected = conn; }
void disconnect() { data_->connected = false; }
private:
std::shared_ptr<data> data_;
};
template<typename R, typename... Args, bool AUTODESTROY>
class callback_wrapper<R(Args...), AUTODESTROY>
{
typedef callback_wrapper self_type;
public:
template<typename T,
typename Enable =
typename detail::disable_if_same_or_derived<callback_wrapper, T>>
callback_wrapper(T &&t) : _callback(std::forward<T>(t))
{
// mark connected now that it is wrapped
_callback.connected(true);
}
// (only) used by manual callback workaround
static R wrapper(Args... args, gpointer user_data)
{
auto t = reinterpret_cast<callback_wrapper *>(user_data);
std::unique_ptr<self_type> wt(AUTODESTROY ? t : nullptr);
return t->_callback(args...);
}
static void destroy(gpointer user_data)
{
auto t = reinterpret_cast<callback_wrapper *>(user_data);
delete t;
}
// (async scope) wrapper may have to take ownership of additional data
// (other callback wrapper)
template<typename T>
void take_data(std::shared_ptr<T> d)
{
auto cb = std::move(_callback.data_->callable);
auto newcb = [d, cb](Args &&...args) {
return cb(std::forward<Args>(args)...);
};
_callback.data_->callable = std::move(newcb);
}
template<typename T>
void take_data(T *d)
{
take_data(std::shared_ptr<T>(d));
}
~callback_wrapper()
{
// other shared ptr to data might be around (unlikely though)
// but regardless disconnect now as requested (as wrapper is going away)
_callback.disconnect();
}
connectable<R(Args... args)> _callback;
};
template<typename G, typename CG = typename map_cpp_function<G>::type>
struct transform_callback_wrapper;
template<typename R, typename... Args, typename CR, typename... CArgs>
struct transform_callback_wrapper<R(Args...), CR(CArgs...)>
{
// transfers of arguments
template<bool AUTODESTROY, typename RetTransfer, typename... Transfers>
class with_transfer : public callback_wrapper<R(Args...)>
{
typedef callback_wrapper<R(Args...)> super_type;
typedef with_transfer self_type;
public:
template<typename T>
explicit with_transfer(T &&t) : super_type(std::forward<T>(t))
{}
private:
static R caller(Args &&...args, void *d)
{
auto this_ = (self_type *)d;
return this_->_callback(std::forward<Args>(args)...);
}
public:
static CR wrapper(CArgs... args, gpointer user_data)
{
auto t = reinterpret_cast<self_type *>(user_data);
std::unique_ptr<self_type> wt(AUTODESTROY ? t : nullptr);
return transform_caller<R(Args...), RetTransfer, std::tuple<Transfers...>,
CR(CArgs...)>::wrapper(args..., caller, t);
}
};
};
// used early in declarations, so avoid using unknown types in CSigOrVoid
template<typename CppSig, typename RetTransfer,
typename ArgTransfers = std::tuple<>, typename CSigOrVoid = void>
class callback;
template<typename CppSig, typename RetTransfer, typename... Transfers,
typename CSigOrVoid>
class callback<CppSig, RetTransfer, std::tuple<Transfers...>, CSigOrVoid>
: public connectable<CppSig>
{
typedef connectable<CppSig> super_type;
public:
typedef CppSig function_type;
typedef typename map_cpp_function<CppSig, CSigOrVoid>::type CSig;
template<bool ASYNC = false>
using wrapper_type = typename transform_callback_wrapper<function_type,
CSig>::template with_transfer<ASYNC, RetTransfer, Transfers...>;
typedef typename std::add_pointer<decltype(wrapper_type<>::wrapper)>::type
cfunction_type;
using super_type::super_type;
};
// signal handling;
// transfer none for arguments
// transfer full for return
template<typename G>
struct transform_signal_wrapper;
template<typename T>
struct signal_arg_transfer
{
typedef transfer_none_t type;
};
template<typename R, typename... Args>
struct transform_signal_wrapper<R(Args...)>
: public transform_callback_wrapper<R(
Args...)>::template with_transfer<false, transfer_full_t,
typename detail::signal_arg_transfer<Args>::type...>
{
private:
typedef typename transform_callback_wrapper<R(
Args...)>::template with_transfer<false, transfer_full_t,
typename detail::signal_arg_transfer<Args>::type...>
super_;
public:
template<typename T>
transform_signal_wrapper(T &&t) : super_(std::forward<T>(t))
{}
};
// connection helpers
class connection_impl
{
public:
connection_impl(gulong id, connection_status s) : id_(id), status_(s) {}
bool connected() const { return status_.connected(); }
gulong id() const { return id_; }
protected:
gulong id_;
connection_status status_;
};
template<typename Connection>
class connection
{
typedef Connection impl;
public:
connection() = default;
template<typename... Args>
explicit connection(Args... arg)
: conn_(std::make_shared<impl>(std::forward<Args>(arg)...))
{
// this is to be expected at this time
if (!connected()) {
g_warning("creating non-connected connection");
}
}
// implicit copy/move
bool connected() const { return conn_ && conn_->connected(); }
void disconnect()
{
if (connected())
conn_->disconnect();
}
protected:
std::shared_ptr<impl> conn_;
};
template<typename ConnectionBase>
class scoped_connection : public ConnectionBase
{
typedef ConnectionBase connection;
public:
scoped_connection() : connection() {}
~scoped_connection() { this->disconnect(); }
void release() { this->conn_.reset(); }
// ensure default movable
scoped_connection(scoped_connection &&other) = default;
scoped_connection &operator=(scoped_connection &&other) = default;
// not copyable; to avoid inadvertent disconnect
scoped_connection(const scoped_connection &other) = delete;
scoped_connection &operator=(const scoped_connection &other) = delete;
// but convert/assign from base class
scoped_connection(const connection &other) : connection(other) {}
scoped_connection &operator=(const connection &other)
{
(*(connection *)(this)) = other;
return *this;
}
scoped_connection &operator=(const connection &&other)
{
(*(connection *)(this)) = std::move(other);
return *this;
}
};
} // namespace detail
// function bind helpers
template<typename R, typename T, typename Tp, typename... Args>
inline std::function<R(Args...)>
mem_fun(R (T::*pm)(Args...), Tp object)
{
return [object, pm](Args... args) {
return (object->*pm)(std::forward<Args>(args)...);
};
}
template<typename R, typename T, typename Tp, typename... Args>
inline std::function<R(Args...)>
mem_fun(R (T::*pm)(Args...) const, Tp object)
{
return [object, pm](Args... args) {
return (object->*pm)(std::forward<Args>(args)...);
};
}
// expose for use in fallback scenarios
using detail::callback_wrapper;
} // namespace gi
#endif // CALLBACK_HPP

Some files were not shown because too many files have changed in this diff Show More