pax_global_header 0000666 0000000 0000000 00000000064 14677722710 0014527 g ustar 00root root 0000000 0000000 52 comment=3a6efdbe6a2a4b7fef9093b564a14ad1e046e9a4
qcoro-0.11.0/ 0000775 0000000 0000000 00000000000 14677722710 0012731 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.clang-format 0000664 0000000 0000000 00000010225 14677722710 0015304 0 ustar 00root root 0000000 0000000 ---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveBitFields: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(qcoro)/'
Priority: 2
SortPriority: 0
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
- Regex: '.*'
Priority: 1
SortPriority: 0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
ReflowComments: false
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
- addTest
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
...
qcoro-0.11.0/.git-blame-ignore-revs 0000664 0000000 0000000 00000000107 14677722710 0017027 0 ustar 00root root 0000000 0000000 # Initial run of clang-format
0c01ca40fd8bd337ecae3ea21be1e5a97ba40e4b
qcoro-0.11.0/.github/ 0000775 0000000 0000000 00000000000 14677722710 0014271 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.github/FUNDING.yml 0000664 0000000 0000000 00000000174 14677722710 0016110 0 ustar 00root root 0000000 0000000 # Thanks for any donations! :)
github: [danvratil]
liberapay: dvratil
ko_fi: dvratil
custom: ['https://paypal.me/dvratil']
qcoro-0.11.0/.github/actions/ 0000775 0000000 0000000 00000000000 14677722710 0015731 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.github/actions/install-compiler/ 0000775 0000000 0000000 00000000000 14677722710 0021207 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.github/actions/install-compiler/action.yml 0000664 0000000 0000000 00000002074 14677722710 0023212 0 ustar 00root root 0000000 0000000 name: Install Compiler
description: Installs compiler
inputs:
compiler:
description: Compiler to install (gcc|clang)
required: true
version:
description: Version of compiler to install
required: true
runs:
using: composite
steps:
- name: Install GCC
if: ${{ inputs.compiler == 'gcc' }}
shell: bash
run: |
GCC_VERSION_MAJOR=$(echo ${{ inputs.version }} | cut -d '.' -f1)
sudo add-apt-repository "deb https://ppa.launchpadcontent.net/ubuntu-toolchain-r/ppa/ubuntu focal main"
sudo apt-get update
sudo apt-get install -y \
gcc-${GCC_VERSION_MAJOR} g++-${GCC_VERSION_MAJOR} libstdc++-${GCC_VERSION_MAJOR}-dev
echo "CXX=/usr/bin/g++-${GCC_VERSION_MAJOR}" >> $GITHUB_ENV
- name: Install Clang
if : ${{ inputs.compiler == 'clang' }}
shell: bash
run: |
wget https://apt.llvm.org/llvm.sh -O llvm.sh
chmod a+x llvm.sh
sed -i "s/libunwind-\$LLVM_VERSION-dev//" llvm.sh
sudo ./llvm.sh ${{ inputs.version }} all
echo "CXX=/usr/bin/clang++-${{ inputs.version }}" >> $GITHUB_ENV qcoro-0.11.0/.github/actions/install-qt/ 0000775 0000000 0000000 00000000000 14677722710 0020021 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.github/actions/install-qt/action.yml 0000664 0000000 0000000 00000004713 14677722710 0022026 0 ustar 00root root 0000000 0000000 name: 'Install Qt'
description: 'Install Qt'
inputs:
qt_version:
description: 'Version of Qt to install'
required: true
qt_archives:
description: 'List of Qt archives to intall'
qt_modules:
description: 'List of Qt modules to install'
platform:
description: 'Operating system (linux|windows|macos)'
required: true
runs:
using: composite
steps:
- name: Install dependencies
if: ${{ inputs.platform == 'linux' }}
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
python3 python3-pip \
build-essential \
dbus dbus-x11 \
libgl-dev libegl-dev
- name: Install Qt
shell: bash
run: |
pip3 install aqtinstall~=2.1
case "${{ inputs.platform }}" in
"windows")
QT_ARCH="win64_msvc2019_64"
QT_INSTALL_PATH="C:\\Qt"
QT_BASE_DIR="${QT_INSTALL_PATH}\\${{ inputs.qt_version }}\\msvc2019_64"
QT_OS="windows"
;;
"linux")
QT_ARCH="gcc_64"
QT_INSTALL_PATH="/opt/qt"
QT_BASE_DIR="${QT_INSTALL_PATH}/${{ inputs.qt_version }}/${QT_ARCH}"
QT_OS="linux"
;;
"macos")
QT_ARCH=""
QT_INSTALL_PATH="/Users/runner/qt"
if [[ "$(echo ${{ inputs.qt_version }} | cut -d'.' -f1)" == "6" ]]; then
QT_BASE_DIR="${QT_INSTALL_PATH}/${{ inputs.qt_version }}/macos"
else
QT_BASE_DIR="${QT_INSTALL_PATH}/${{ inputs.qt_version }}/clang_64"
fi
QT_OS="mac"
;;
esac
if [[ -n "${{ inputs.qt_archives }}" ]]; then
qt_archives="--archives ${{ inputs.qt_archives }}"
fi
if [[ -n "${{ inputs.qt_modules }}" ]]; then
qt_modules="--modules ${{ inputs.qt_modules }}"
fi
python3 -m aqt install-qt -O ${QT_INSTALL_PATH} ${QT_OS} desktop ${{ inputs.qt_version }} ${QT_ARCH} ${qt_archives} ${qt_modules}
echo "${QT_BASE_DIR}/bin" >> $GITHUB_PATH
echo "CMAKE_PREFIX_PATH=${QT_BASE_DIR}/lib/cmake" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=${QT_BASE_DIR}/lib:${LD_LIBRARY_PATH}" >> $GITHUB_ENV
echo "XDG_DATA_DIRS=${QT_BASE_DIR}/share:${XDG_DATA_DIRS}" >> $GITHUB_ENV
if [[ "${{ inputs.platform }}" == "windows" ]]; then
powershell "./.github/actions/install-qt/install-dbus.ps1" "$QT_BASE_DIR"
fi
qcoro-0.11.0/.github/actions/install-qt/install-dbus.ps1 0000664 0000000 0000000 00000001102 14677722710 0023041 0 ustar 00root root 0000000 0000000 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Set-PSRepository -Name 'PSGallery' -SourceLocation "https://www.powershellgallery.com/api/v2" -InstallationPolicy Trusted
Install-Module -Name 7Zip4PowerShell -Force
Invoke-WebRequest -Uri https://files.kde.org/craft/master/23.09/windows/cl/msvc2019/x86_64/RelWithDebInfo/libs/dbus/dbus-1.14.8-20230912T142911-windows-cl-msvc2019-x86_64.7z -OutFile C:\Qt\dbus.7z
Expand-7Zip -ArchiveFileName C:\Qt\dbus.7z -TargetPath $args[0]
qcoro-0.11.0/.github/dependabot.yml 0000664 0000000 0000000 00000000353 14677722710 0017122 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "pip"
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
qcoro-0.11.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14677722710 0016326 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.github/workflows/build-docker-images.yml 0000664 0000000 0000000 00000004111 14677722710 0022655 0 ustar 00root root 0000000 0000000 name: Build Docker images
on:
push:
branches:
- main
paths:
- 'docker/**'
pull_request:
types: [opened, synchronize, reopened, edited]
paths:
- 'docker/**'
workflow_dispatch:
env:
BUILD_TYPE: RelWithDebInfo
QTEST_FUNCTION_TIMEOUT: 60000
jobs:
generate-matrix:
name: Generate build matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: set-matrix
name: Generate matrix
run: |
matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform linux)
echo "::set-output name=matrix::${matrix_json}"
build:
needs: generate-matrix
strategy:
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
fail-fast: false
runs-on: ${{ matrix.runs_on }}
name: Build Docker image for ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Docker Buildx setup
uses: docker/setup-buildx-action@v3
- name: Login to GCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}/build-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
build-args: |
compiler_image=${{ matrix.compiler_base_image }}
compiler_version=${{ matrix.compiler_version }}
qt_version=${{ matrix.qt_version }}
qt_modules=${{ matrix.qt_modules }}
qt_archives=${{ matrix.qt_archives }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: docker/Dockerfile.${{ matrix.compiler }}
context: docker
qcoro-0.11.0/.github/workflows/build-linux.yml 0000664 0000000 0000000 00000010546 14677722710 0021313 0 ustar 00root root 0000000 0000000 name: Linux CI
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, edited]
env:
BUILD_TYPE: Debug
QTEST_FUNCTION_TIMEOUT: 60000
jobs:
detect_run:
name: Check changes
outputs: { "source_code_changed": "${{ steps.source_code_changed.outputs.source_code_changed}}" }
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: changed_files
uses: tj-actions/changed-files@v45
name: Get changed files
with:
files_ignore: |
docs/**
docker/**
requirements.txt
mkdocs.yml
README.md
.github/**
- id: source_code_changed
name: Check for source code changes
if: steps.changed_files.outputs.any_changed == 'true'
run: |
echo "Detected changed files:"
echo "${{ steps.changed_files.outputs.all_changed_files }}"
echo "::set-output name=source_code_changed::true"
generate-matrix:
name: Generate build matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: set-matrix
name: Generate matrix
run: |
matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=linux)
echo "::set-output name=matrix::${matrix_json}"
build:
needs: [ generate-matrix, detect_run ]
strategy:
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
fail-fast: false
defaults:
run:
shell: bash -l {0}
runs-on: ${{ matrix.runs_on }}
name: ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
container:
image: ghcr.io/${{ github.repository }}/build-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}:main
steps:
- name: Checkout sources
uses: actions/checkout@v4
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
- name: Create Build Environment
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
run: |
cmake -E make_directory build
- name: Configure CMake
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
run: |
QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1)
EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DBUILD_SHARED_LIBS=ON"
# Disable ASAN for clang-11 - we are hitting some bugs in ASAN & generators
if [[ "${{ matrix.compiler }}" == "clang" && "${{ matrix.compiler_version }}" == "11" ]]; then
EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DQCORO_ENABLE_ASAN=OFF"
fi
cmake -B build \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DUSE_QT_VERSION=$QT_VERSION_MAJOR \
-DQCORO_WITH_QTDBUS=${{ matrix.with_qtdbus }} \
-DQCORO_ENABLE_ASAN=ON \
${EXTRA_CMAKE_FLAGS}
- name: Build
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
run: |
cmake --build build --config $BUILD_TYPE --parallel $(nproc) --verbose
- name: Test
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
run: |
cd build
QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \
--output-on-failure \
--verbose \
--output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml
- name: Upload Test Results
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }}
uses: actions/upload-artifact@v4
with:
name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }})
path: |
${{ github.workspace }}/build/${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml
- name: Upload build logs on failure
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }}
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
path: build/**
event_file:
name: "Event File"
runs-on: ubuntu-latest
steps:
- name: Upload
uses: actions/upload-artifact@v4
with:
name: Event File
path: ${{ github.event_path }}
qcoro-0.11.0/.github/workflows/build-macos.yml 0000664 0000000 0000000 00000011031 14677722710 0021244 0 ustar 00root root 0000000 0000000 name: MacOS CI
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, edited]
env:
BUILD_TYPE: Debug
QTEST_FUNCTION_TIMEOUT: 60000
jobs:
detect_run:
name: Check changes
outputs: { "source_code_changed": "${{ steps.source_code_changed.outputs.source_code_changed}}" }
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: changed_files
uses: tj-actions/changed-files@v45
name: Get changed files
with:
files_ignore: |
docs/**
docker/**
requirements.txt
mkdocs.yml
README.md
.github/**
- id: source_code_changed
name: Check for source code changes
if: steps.changed_files.outputs.any_changed == 'true'
run: |
echo "Detected changed files:"
echo "${{ steps.changed_files.outputs.all_changed_files }}"
echo "::set-output name=source_code_changed::true"
generate-matrix:
name: Generate build matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: set-matrix
name: Generate matrix
run: |
matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=macos)
echo "::set-output name=matrix::${matrix_json}"
build:
needs: [ generate-matrix, detect_run ]
strategy:
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
fail-fast: false
runs-on: ${{ matrix.runs_on }}
name: ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
- name: Install Qt
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
uses: ./.github/actions/install-qt
with:
qt_version: ${{ matrix.qt_version }}
qt_archives: ${{ matrix.qt_archives }}
qt_modules: ${{ matrix.qt_modules }}
platform: ${{ matrix.platform }}
- name: Create Build Environment
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
run: |
cmake -E make_directory ${{ github.workspace }}/build
- name: Configure CMake
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: bash
working-directory: ${{ github.workspace }}/build
run: |
QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1)
# Don't fiddle with dlls in Windows, it makes things complicated...
EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DBUILD_SHARED_LIBS=ON"
cmake $GITHUB_WORKSPACE \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DUSE_QT_VERSION=$QT_VERSION_MAJOR \
-DQCORO_WITH_QTDBUS=${{ matrix.with_qtdbus }} \
-DQCORO_ENABLE_ASAN=ON \
${EXTRA_CMAKE_FLAGS}
- name: Build
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: bash
working-directory: ${{ github.workspace }}/build
run: |
cmake --build . --config $BUILD_TYPE --parallel $(nproc) --verbose
- name: Test
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: bash
working-directory: ${{ github.workspace }}/build
run: |
QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \
--output-on-failure \
--verbose \
--output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml
- name: Upload Test Results
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }}
uses: actions/upload-artifact@v4
with:
name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }})
path: |
${{ github.workspace }}/build/${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml
- name: Upload build logs on failure
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }}
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
path: build/**
event_file:
name: "Event File"
runs-on: ubuntu-latest
steps:
- name: Upload
uses: actions/upload-artifact@v4
with:
name: Event File
path: ${{ github.event_path }}
qcoro-0.11.0/.github/workflows/build-windows.yml 0000664 0000000 0000000 00000013752 14677722710 0021650 0 ustar 00root root 0000000 0000000 name: Windows CI
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened, edited]
env:
BUILD_TYPE: Debug
QTEST_FUNCTION_TIMEOUT: 60000
jobs:
detect_run:
name: Check changes
outputs: { "source_code_changed": "${{ steps.source_code_changed.outputs.source_code_changed}}" }
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: changed_files
uses: tj-actions/changed-files@v45
name: Get changed files
with:
files_ignore: |
docs/**
docker/**
requirements.txt
mkdocs.yml
README.md
.github/**
- id: source_code_changed
name: Check for source code changes
if: steps.changed_files.outputs.any_changed == 'true'
run: |
echo "Detected changed files:"
echo "${{ steps.changed_files.outputs.all_changed_files }}"
echo '::set-output name=source_code_changed::true'
generate-matrix:
name: Generate build matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: set-matrix
name: Generate matrix
run: |
matrix_json=$(python3 ./.github/workflows/generate-matrix.py --platform=windows)
echo "::set-output name=matrix::${matrix_json}"
build:
needs: [ generate-matrix, detect_run ]
strategy:
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
fail-fast: false
runs-on: ${{ matrix.runs_on }}
name: ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
steps:
- name: Checkout sources
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
uses: actions/checkout@v4
- name: Install Qt
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
uses: ./.github/actions/install-qt
with:
qt_version: ${{ matrix.qt_version }}
qt_archives: ${{ matrix.qt_archives }}
qt_modules: ${{ matrix.qt_modules }}
platform: ${{ matrix.platform }}
- name: Create Build Environment
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
run: |
cmake -E make_directory ${{ github.workspace }}/build
- name: Configure CMake
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: bash
working-directory: ${{ github.workspace }}/build
run: |
QT_VERSION_MAJOR=$(echo ${{ matrix.qt_version }} | cut -d'.' -f1)
if [[ "${{ matrix.compiler }}" == "clang-cl" ]]; then
# FIXME: allow ASAN with clang-cl
EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -T ClangCL -DQCORO_ENABLE_ASAN=OFF"
fi
if [[ "${QT_VERSION_MAJOR}" == "5" ]]; then
EXTRA_CMAKE_FLAGS="${EXTRA_CMAKE_FLAGS} -DQCORO_ENABLE_ASAN=OFF"
fi
cmake $GITHUB_WORKSPACE \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DUSE_QT_VERSION=$QT_VERSION_MAJOR \
-DQCORO_WITH_QTDBUS=${{ matrix.with_qtdbus }} \
-DQCORO_ENABLE_ASAN=ON \
${EXTRA_CMAKE_FLAGS}
- name: Add ASAN DLL directory to PATH
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: pwsh
run: |
$installDir=(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath)
echo "Detected MSVC install dir: \"${installDir}\""
if ("${{ matrix.compiler}}" -eq "msvc") {
if (Test-Path -Path "${installDir}\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt") {
$vctoolsVersion=(Get-Content -Path "${installDir}\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt")
echo "Detected VCTools version: ${vctoolsVersion}"
} else {
echo "Failed to detect MSVC version"
exit 1
}
"${installDir}\VC\Tools\MSVC\${vctoolsVersion}\bin\HostX64\x64" >> $env:GITHUB_PATH
} else {
$clangVersion=((& "${installDir}\VC\Tools\Llvm\x64\bin\clang.exe" --version) | Select-String -Pattern "\d+.\d+.\d+" | % { $_.Matches } | % { $_.Value })
echo "Detected clang version: ${clangVersion}"
"${installDir}\VC\Tools\Llvm\x64\lib\clang\${clangVersion}\lib\windows" >> $env:GITHUB_PATH
}
- name: Build
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: bash
working-directory: ${{ github.workspace }}/build
run: |
cmake --build . --config $BUILD_TYPE --parallel $(nproc) --verbose
- name: Test
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' }}
shell: bash
working-directory: ${{ github.workspace }}/build
run: |
QT_LOGGING_TO_CONSOLE=1 ctest -C $BUILD_TYPE \
--output-on-failure \
--verbose \
--output-junit ${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml
- name: Upload Test Results
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && always() }}
uses: actions/upload-artifact@v4
with:
name: Unit Tests Results (${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }})
path: |
${{ github.workspace }}/build/${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}.xml
- name: Upload build logs on failure
if: ${{ needs.detect_run.outputs.source_code_changed == 'true' && failure() }}
uses: actions/upload-artifact@v4
with:
name: build-${{ matrix.platform }}-${{ matrix.compiler_full }}-qt-${{ matrix.qt_version }}
path: build/**
event_file:
name: "Event File"
runs-on: ubuntu-latest
steps:
- name: Upload
uses: actions/upload-artifact@v4
with:
name: Event File
path: ${{ github.event_path }}
qcoro-0.11.0/.github/workflows/generate-matrix.py 0000664 0000000 0000000 00000005563 14677722710 0022005 0 ustar 00root root 0000000 0000000 import json
from socket import create_connection
from argparse import ArgumentParser
qt5_config = {
"archives": [ "qtbase", "icu", "qtwebsockets", "qtdeclarative", "qtwebchannel", "qtlocation" ],
"modules": [ "qtwebengine" ]
}
qt6_config = {
"archives": [ "qtbase", "icu", "qtdeclarative" ],
"modules": [ "qtwebsockets", "qtwebengine", "qtwebchannel", "qtpositioning" ]
}
qt = [
{
"version": "5.15.2",
**qt5_config
},
{
"version": "6.2.0",
**qt6_config
},
{
"version": "6.5.0",
**qt6_config
}
]
platforms = [
{
"name": "windows",
"compilers": [
{ "name": "msvc" },
{ "name": "clang-cl" }
]
},
{
"name": "macos",
"compilers": [{ "name": "apple-clang" }]
},
{
"name": "linux",
"compilers": [
{
"name": "gcc",
"versions": [ "11", "12", "13" ]
},
{
"name": "clang",
"versions": [ "15", "16", "17", "dev" ]
}
]
}
]
output = {
"include": []
}
def get_os_for_platform(platform):
if platform == "windows":
return "windows-2022"
if platform == "linux":
return "ubuntu-20.04"
if platform == "macos":
return "macos-13"
raise RuntimeError(f"Invalid platform '{platform}'.")
def get_base_image_for_compiler(compiler):
if compiler == "gcc":
return "gcc"
elif compiler == "clang":
return "debian"
else:
return None
def create_configuration(qt, platform, compiler, compiler_version = ""):
return {
"qt_version": qt["version"],
"qt_modules": ' '.join(qt["modules"]),
"qt_archives": ' '.join(qt["archives"]),
"platform": platform,
"compiler": compiler,
"compiler_base_image": get_base_image_for_compiler(compiler),
"compiler_version": compiler_version,
"compiler_full": compiler if not compiler_version else f"{compiler}-{compiler_version}",
"runs_on": get_os_for_platform(platform),
"with_qtdbus": "OFF" if platform == "macos" else "ON"
}
parser = ArgumentParser()
parser.add_argument('--platform')
args = parser.parse_args()
filtered_platforms = list(filter(lambda p: p['name'] == args.platform, platforms))
for qt_version in qt:
for platform in filtered_platforms:
for compiler in platform["compilers"]:
if "versions" in compiler:
for compiler_version in compiler["versions"]:
output["include"].append(
create_configuration(qt_version, platform["name"], compiler["name"], compiler_version))
else:
output["include"].append(
create_configuration(qt_version, platform["name"], compiler["name"]))
print(json.dumps(output))
qcoro-0.11.0/.github/workflows/unit_tests_results.yml 0000664 0000000 0000000 00000002125 14677722710 0023033 0 ustar 00root root 0000000 0000000 name: Unit Test Results
on:
workflow_run:
workflows: ["Build and Test"]
types:
- completed
jobs:
unit-test-results:
name: Unit Test Results
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion != 'skipped'
steps:
- name: Download and Extract Artifacts
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
mkdir -p artifacts && cd artifacts
artifacts_url=${{ github.event.workflow_run.artifacts_url }}
gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact
do
IFS=$'\t' read name url <<< "$artifact"
gh api $url > "$name.zip"
unzip -d "$name" "$name.zip"
done
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v2
with:
commit: ${{ github.event.workflow_run.head_sha }}
event_file: artifacts/Event File/event.json
event_name: ${{ github.event.workflow_run.event }}
files: "artifacts/**/*.xml"
qcoro-0.11.0/.github/workflows/update-docs.yml 0000664 0000000 0000000 00000006057 14677722710 0021271 0 ustar 00root root 0000000 0000000 name: Build and Deploy Documentation
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
detect_run:
name: Check changes
outputs: { "docs_changed": "${{ steps.docs_changed.outputs.docs_changed}}" }
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- id: changed_files
uses: tj-actions/changed-files@v45
name: Get changed files
with:
files: |
docs/**
requirements.txt
mkdocs.yml
README.md
.github/workflows/update-docs.yml
- id: docs_changed
name: Check for documentatio nchanges
if: steps.changed_files.outputs.any_changed == 'true'
run: |
echo "Detected changed files:"
echo "${{ steps.changed_files.outputs.all_changed_files }}"
echo "::set-output name=docs_changed::true"
build:
name: Build
runs-on: ubuntu-latest
needs: detect_run
if: ${{ needs.detect_run.outputs.docs_changed == 'true' }}
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install -r requirements.txt
- name: Build
run: |
echo "{% extends \"base.html\" %}{% block analytics %}
{% endblock %}" > docs/overrides/main.html
mkdocs build --verbose
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: site
path: site
deploy:
name: Deploy
runs-on: ubuntu-latest
if: ${{ github.ref == 'refs/heads/main' }}
environment:
name: cloudflare-pages
url: ${{ steps.deploy.outputs.url }}
needs: build
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: site
path: site
- name: Deploy to Cloudflare Pages
id: deploy
uses: cloudflare/pages-action@1
env:
CLOUDFLARE_ACCOUNT_ID: ${{ SECRETS.CLOUDFLARE_ACCOUNT_ID }}
with:
apiToken: ${{ SECRETS.CLOUDFLARE_PAGES_TOKEN }}
accountId: ${{ SECRETS.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ vars.CLOUDFLARE_PAGES_NAME }}
directory: ./site
githubToken: ${{ SECRETS.GITHUB_TOKEN }}
qcoro-0.11.0/.gitignore 0000664 0000000 0000000 00000000213 14677722710 0014715 0 ustar 00root root 0000000 0000000 build/
build-*/
.*.swp
.ccls-cache
.cache
compile_commands.json
/.vs
/.vscode
/CMakeSettings.json
# Python (from mkdocs)
__pycache__
venv
qcoro-0.11.0/.reuse/ 0000775 0000000 0000000 00000000000 14677722710 0014132 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/.reuse/dep5 0000664 0000000 0000000 00000000433 14677722710 0014712 0 ustar 00root root 0000000 0000000 Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: QCoro
Upstream-Contact: Daniel Vrátil
Source: https://qcoro.github.io
# Sample paragraph, commented out:
#
# Files: src/*
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...
qcoro-0.11.0/CMakeLists.txt 0000664 0000000 0000000 00000021074 14677722710 0015475 0 ustar 00root root 0000000 0000000 cmake_minimum_required(VERSION 3.18.4)
set(qcoro_VERSION 0.11.0)
set(qcoro_SOVERSION 0)
project(qcoro LANGUAGES CXX VERSION ${qcoro_VERSION})
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(GNUInstallDirs)
include(CTest)
include(FeatureSummary)
#-----------------------------------------------------------#
# Options
#-----------------------------------------------------------#
# Allow QCORO_BUILD_TESTING to override BUILD_TESTING, but default to BUILD_TESTING if not set
if (DEFINED QCORO_BUILD_TESTING)
set(BUILD_TESTING ${QCORO_BUILD_TESTING})
endif()
option(QCORO_BUILD_EXAMPLES "Build examples" ON)
add_feature_info(Examples QCORO_BUILD_EXAMPLES "Build examples")
option(QCORO_BUILD_TESTING "Build QCoro tests" ${BUILD_TESTING})
add_feature_info(Testing QCORO_BUILD_TESTING "Build QCoro tests")
option(QCORO_ENABLE_ASAN "Build with AddressSanitizer" OFF)
add_feature_info(Asan QCORO_ENABLE_ASAN "Build with AddressSanitizer")
option(QCORO_DISABLE_DEPRECATED_TASK_H "Disable deprecated task.h header" OFF)
if(WIN32 OR APPLE OR ANDROID)
option(QCORO_WITH_QTDBUS "Build QtDBus support" OFF)
else()
option(QCORO_WITH_QTDBUS "Build QtDBus support" ON)
endif()
add_feature_info(QtDBus QCORO_WITH_QTDBUS "Build QtDBus support")
option(QCORO_WITH_QTNETWORK "Build QtNetwork support" ON)
add_feature_info(QtNetwork QCORO_WITH_QTNETWORK "Build QtNetwork support")
option(QCORO_WITH_QTWEBSOCKETS "Build QtWebSockets support" ON)
add_feature_info(QtWebSockets QCORO_WITH_QTWEBSOCKETS "Build QtWebSockets support")
option(QCORO_WITH_QTQUICK "Build QtQuick support" ON)
add_feature_info(QtQuick QCORO_WITH_QTQUICK "Build QtQuick support")
option(QCORO_WITH_QML "Build QML integration features" ON)
add_feature_info(QtQml QCORO_WITH_QML "Build QML integration features")
option(QCORO_WITH_QTTEST "Build QtTest support" ON)
add_feature_info(QtTest QCORO_WITH_QTTEST "Build QtTest support")
#-----------------------------------------------------------#
# Dependencies
#-----------------------------------------------------------#
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
include(cmake/CheckAtomic.cmake)
set(REQUIRED_QT_COMPONENTS Core)
set(REQUIRED_QT5_COMPONENTS)
set(REQUIRED_QT6_COMPONENTS)
if (QCORO_WITH_QTDBUS)
list(APPEND REQUIRED_QT_COMPONENTS DBus)
endif()
if (QCORO_WITH_QTNETWORK)
list(APPEND REQUIRED_QT_COMPONENTS Network)
endif()
if (QCORO_WITH_QTWEBSOCKETS)
list(APPEND REQUIRED_QT_COMPONENTS WebSockets)
endif()
if (QCORO_WITH_QTQUICK)
list(APPEND REQUIRED_QT_COMPONENTS Gui Quick QuickPrivate)
endif()
if (QCORO_WITH_QML)
list(APPEND REQUIRED_QT_COMPONENTS Qml)
# Qt6 needs access to private API
list(APPEND REQUIRED_QT6_COMPONENTS QmlPrivate)
endif()
if (QCORO_WITH_QTTEST)
list(APPEND REQUIRED_QT_COMPONENTS Test)
endif()
if (QCORO_BUILD_EXAMPLES)
list(APPEND REQUIRED_QT_COMPONENTS Widgets Concurrent)
endif()
if (BUILD_TESTING)
list(APPEND REQUIRED_QT_COMPONENTS Test Concurrent)
endif()
set(MIN_REQUIRED_QT5_VERSION "5.12")
set(MIN_REQUIRED_QT6_VERSION "6.2.0")
include(cmake/QCoroFindQt.cmake)
# Find Qt. If USE_QT_VERSION is not set, it will try to look for Qt6 first
# and fallback to Qt5 otherwise.
qcoro_find_qt(
QT_VERSION "${USE_QT_VERSION}"
COMPONENTS "${REQUIRED_QT_COMPONENTS}"
QT5_COMPONENTS "${REQUIRED_QT5_COMPONENTS}"
QT6_COMPONENTS "${REQUIRED_QT6_COMPONENTS}"
FOUND_VER_VAR QT_VERSION_MAJOR
)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/config.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
)
#-----------------------------------------------------------#
# Compiler Settings
#-----------------------------------------------------------#
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_AUTOMOC ON)
if (MSVC)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /W4 /WX")
# Disable warning C5054: "operator '&': deprecated between enumerations of different types" caused by QtWidgets/qsizepolicy.h
# Disable warning C4127: "conditional expression is constant" caused by QtCore/qiterable.h
if ("${QT_VERSION_MAJOR}" STREQUAL "6" AND "${Qt6_VERSION}" VERSION_GREATER_EQUAL "6.4.0" AND "${Qt6_VERSION}" VERSION_LESS "6.5.3")
# Disable warning C4702: "unreachable code" caused by QtTest/qtestcase.h - fixed in Qt 6.5.3
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /wd5054 /wd4127 /wd4702")
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Explicitly enable exceptions support for clang-cl (it's only enabled by CMake when targeting the Windows-MSVC platform,
# see https://github.com/danvratil/qcoro/issues/90 for details)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
endif()
else()
# Only enable strict warnings in debug mode
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Werror -pedantic")
if (ANDROID)
include(DetectAndroidNDKVersion)
detectAndroidNDKVersion(NDK_VERSION)
if ("${NDK_VERSION}" VERSION_LESS 26)
# Android NDK < 26 ships mismatching versions of clang and libc++ - libc++ is older and doesn't have coroutine support
# which forces us to use coroutines from std::experimental. But the clang itself is newer and emits an error about
# std::experimental coroutines being deprecated in LLVM 14. This option is needed to suppress the error.
add_compile_options(-Wno-error=deprecated-experimental-coroutine)
endif()
endif()
endif()
if (QCORO_ENABLE_ASAN)
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /INCREMENTAL:NO")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /INCREMENTAL:NO")
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address")
endif()
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fsanitize-recover=address")
endif()
endif()
add_compile_definitions(
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
QT_NO_URL_CAST_FROM_STRING
QT_NO_CAST_FROM_BYTEARRAY
QT_USE_STRINGBUILDER
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
QT_NO_KEYWORDS
QT_NO_FOREACH
)
if (NOT WIN32)
# strict iterators on MSVC only work when Qt itself is also built with them,
# which is not usually the case. Otherwise there are linking issues.
add_compile_definitions(QT_STRICT_ITERATORS)
endif()
include(qcoro/QCoroMacros.cmake)
qcoro_enable_coroutines()
include(cmake/CodeCoverage.cmake)
add_code_coverage()
add_code_coverage_all_targets(EXCLUDE "${CMAKE_BINARY_DIR}" tests/utils/*)
#-----------------------------------------------------------#
# Definitions
#-----------------------------------------------------------#
# debug suffixes for qmake compatibility
if(WIN32)
set(CMAKE_DEBUG_POSTFIX "d")
elseif(APPLE)
set(CMAKE_DEBUG_POSTFIX "_debug")
else()
set(CMAKE_DEBUG_POSTFIX "")
endif()
set(QCORO_TARGET_PREFIX "QCoro${QT_VERSION_MAJOR}")
set(QCORO_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/qcoro${QT_VERSION_MAJOR}")
#-----------------------------------------------------------#
# Sources
#-----------------------------------------------------------#
set(QCORO_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(qcoro)
if (QCORO_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
if (QCORO_BUILD_TESTING)
add_subdirectory(tests)
endif()
#-----------------------------------------------------------#
# Installation
#-----------------------------------------------------------#
include(CMakePackageConfigHelpers)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/config.h
DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro
COMPONENT Devel
)
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/QCoroConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}Config.cmake"
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}"
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}ConfigVersion.cmake"
VERSION ${qcoro_VERSION}
COMPATIBILITY SameMajorVersion
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/QCoro${QT_VERSION_MAJOR}ConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/QCoro${QT_VERSION_MAJOR}"
COMPONENT Devel
)
#-----------------------------------------------------------#
# Summary
#-----------------------------------------------------------#
feature_summary(FATAL_ON_MISSING_REQUIRED_PACKAGES WHAT ALL)
qcoro-0.11.0/LICENSE 0000664 0000000 0000000 00000002101 14677722710 0013730 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2022 Daniel Vrátil
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.
qcoro-0.11.0/LICENSES/ 0000775 0000000 0000000 00000000000 14677722710 0014136 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/LICENSES/BSD-3-Clause.txt 0000664 0000000 0000000 00000002664 14677722710 0016671 0 ustar 00root root 0000000 0000000 Copyright (c) .
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
qcoro-0.11.0/LICENSES/GFDL-1.3-or-later.txt 0000664 0000000 0000000 00000054407 14677722710 0017447 0 ustar 00root root 0000000 0000000 GNU Free Documentation License
Version 1.3, 3 November 2008
Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
0. PREAMBLE
The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
1. APPLICABILITY AND DEFINITIONS
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque".
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text.
The "publisher" means any person or entity that distributes copies of the Document to the public.
A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
2. VERBATIM COPYING
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
3. COPYING IN QUANTITY
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
4. MODIFICATIONS
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.
B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.
C. State on the Title page the name of the publisher of the Modified Version, as the publisher.
D. Preserve all the copyright notices of the Document.
E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.
G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License.
I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.
J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.
K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.
L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.
M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version.
N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section.
O. Preserve any Warranty Disclaimers.
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles.
You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
5. COMBINING DOCUMENTS
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements".
6. COLLECTIONS OF DOCUMENTS
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
7. AGGREGATION WITH INDEPENDENT WORKS
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
8. TRANSLATION
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
9. TERMINATION
You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.
10. FUTURE REVISIONS OF THIS LICENSE
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Document.
11. RELICENSING
"Massive Multiauthor Collaboration Site" (or "MMC Site") means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A "Massive Multiauthor Collaboration" (or "MMC") contained in the site means any set of copyrightable works thus published on the MMC site.
"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.
"Incorporate" means to publish or republish a Document, in whole or in part, as part of another Document.
An MMC is "eligible for relicensing" if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.
ADDENDUM: How to use this License for your documents
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this:
with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
qcoro-0.11.0/LICENSES/MIT.txt 0000664 0000000 0000000 00000002101 14677722710 0015322 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2021 Daniel Vrátil
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.
qcoro-0.11.0/QCoroConfig.cmake.in 0000664 0000000 0000000 00000003161 14677722710 0016512 0 ustar 00root root 0000000 0000000 if (CMAKE_VERSION VERSION_LESS 3.1.0)
message(FATAL_ERROR \"QCoro@QT_VERSION_MAJOR@ requires at least CMake version 3.1.0\")
endif()
if (NOT QCoro@QT_VERSION_MAJOR@_FIND_COMPONENTS)
set(QCoro@QT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "The QCoro@QT_VERSION_MAJOR@ package requires at least one component")
set(QCoro@QT_VERSION_MAJOR@_FOUND FALSE)
return()
endif()
set(_QCoro_FIND_PARTS_REQUIRED)
if (QCoro@QT_VERSION_MAJOR@_FIND_REQUIRED)
set(_QCoro_FIND_PARTS_REQUIRED REQUIRED)
endif()
set(_QCoro_FIND_PARTS_QUIET)
if (QCoro@QT_VERSION_MAJOR@_FIND_QUIET)
set(_QCoro_FIND_PARTS_QUIET QUIET)
endif()
get_filename_component(_qcoro_install_prefix "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(_QCoro_NOTFOUND_MESSAGE)
foreach(module ${QCoro@QT_VERSION_MAJOR@_FIND_COMPONENTS})
find_package(QCoro@QT_VERSION_MAJOR@${module}
${_QCoro_FIND_PARTS_QUIET}
${_QCoro_FIND_PARTS_REQUIRED}
PATHS ${_qcoro_install_prefix} NO_DEFAULT_PATH
)
if (NOT QCoro@QT_VERSION_MAJOR@${module}_FOUND)
if (QCoro@QT_VERSION_MAJOR@_FIND_REQUIRED_${module})
set(_QCoro_NOTFOUND_MESSAGE "${_QCoro_NOTFOUND_MESSAGE}Failed to find QCoro component \"${module}\" config file at \"${_qcoro_install_prefix}\"\n")
elseif (NOT QCoro@QT_VERSION_MAJOR@_FIND_QUIETLY)
message(WARNING "Failed to find QCoro@QT_VERSION_MAJOR@ component \"${module}\" config file at \"${_qcoro_install_prefix}\"")
endif()
endif()
endforeach()
if (_QCoro_NOTFOUND_MESSAGE)
set(QCoro@QT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "${_QCoro_NOTFOUND_MESSAGE}")
set(QCoro@QT_VERSION_MAJOR@_FOUND FALSE)
endif()
qcoro-0.11.0/README.md 0000664 0000000 0000000 00000020545 14677722710 0014216 0 ustar 00root root 0000000 0000000 [](https://github.com/danvratil/qcoro/actions/workflows/build-linux.yml)
[](https://github.com/danvratil/qcoro/actions/workflows/build-windows.yml)
[](https://github.com/danvratil/qcoro/actions/workflows/build-macos.yml)
[](https://github.com/danvratil/qcoro/actions/workflows/update-docs.yml)
[](https://github.com/danvratil/qcoro/releases)



# QCoro - Coroutines for Qt5 and Qt6
The QCoro library provides set of tools to make use of C++20 coroutines with Qt.
Take a look at the example below to see what an amazing thing coroutines are:
```cpp
QNetworkAccessManager networkAccessManager;
// co_await the reply - the coroutine is suspended until the QNetworkReply is finished.
// While the coroutine is suspended, *the Qt event loop runs as usual*.
const QNetworkReply *reply = co_await networkAccessManager.get(url);
// Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-)
const auto data = reply->readAll();
```
It requires a compiler with support for the couroutines TS, see [documentation](https://qcoro.dvratil.cz/#supported-compilers) for a list of supported compilers and versions.
## Documentation
👉 📘 [Documentation](https://qcoro.dvratil.cz/)
## Supported Qt Types
QCoro provides the tools necessary to make easy use of C++20 coroutines with Qt. The cornerstone of
the library is `QCoro::Task`, which represents an executed coroutine and allows the result of
the coroutine to be asynchronously awaited by its caller. Additionally, QCoro provides a set of
wrappers for common Qt types, such as `QTimer`, `QNetworkReply`, `QDBusPendingCall`, `QFuture`
and others, that allow to `co_await` their asynchronous operations directly.
Additionally, there's a magical `qCoro()` function that can wrap many native Qt functions and types
to make them coroutine-friendly.
Go check the [documentation](https://qcoro.dvratil.cz/reference) for a full list of all supported
features and Qt types.
### `QDBusPendingCall`
QCoro can wait for an asynchronous D-Bus call to finish. There's no need to use `QDBusPendingCallWatcher`
with QCoro - just `co_await` the result instead. While co_awaiting, the Qt event loop runs as usual.
```cpp
QDBusInterface remoteServiceInterface{serviceName, objectPath, interface};
const QDBusReply isReady = co_await remoteServiceInterface.asyncCall(QStringLiteral("isReady"));
```
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/dbus/qdbuspendingcall).
### `QFuture`
QFuture represents a result of an asynchronous task. Normally you have to use `QFutureWatcher` to get
notified when the future is ready. With QCoro, you can just `co_await` it!
```cpp
const QFuture task1 = QtConcurrent::run(....);
const QFuture task2 = QtConcurrent::run(....);
const int a = co_await task1;
const int b = co_await task2;
co_return a + b;
```
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/core/qfuture).
### `QNetworkReply`
Doing network requests with Qt can be tedious - the signal/slot approach breaks the flow
of your code. Chaining requests and error handling quickly become mess and your code is
broken into numerous functions. But not with QCoro, where you can simply `co_await` the
`QNetworkReply` to finish:
```cpp
QNetworkReply qnam;
QNetworkReply *reply = qnam.get(QStringLiteral("https://github.com/danvratil/qcoro"));
const auto contents = co_await reply;
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
co_return handleError(reply);
}
const auto link = findLinkInReturnedHtmlCode(contents);
reply = qnam.get(link);
const auto data = co_await reply;
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
co_return handleError(reply);
}
...
```
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/network/qnetworkreply).
### `QTimer`
Maybe you want to delay executing your code for a second, maybe you want to execute some
code in repeated interval. This becomes super-trivial with `co_await`:
```cpp
QTimer timer;
timer.setInterval(1s);
timer.start();
for (int i = 1; i <= 100; ++i) {
co_await timer;
qDebug() << "Waiting for " << i << " seconds...";
}
qDebug() << "Done!";
```
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/core/qtimer).
### `QIODevice`
`QIODevice` is a base-class for many classes in Qt that allow data to be asynchronously
written and read. How do you find out that there are data ready to be read? You could
connect to `QIODevice::readyRead()` singal, or you could use QCoro and `co_await` the object:
```cpp
socket->write("PING");
// Waiting for "pong"
const auto data = co_await socket;
co_return calculateLatency(data);
```
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/core/qiodevice).
### ...and more!
Go check the [full documentation](https://qcoro.dvratil.cz) to learn more.
## .then() continuations
Sometimes it's not possible to use `co_await` to handle result of a coroutine - usually
when interfacing with a 3rd party code that does not support coroutines. In those
scenarios it's possible to chain a continuation callback to the coroutine which will
get invoked asynchronously when the coroutine finishes.
```cpp
void regularFunction() {
someCoroutineReturningInt().then([](int result) {
// handle result
});
}
```
The continuation callback can also be a coroutine and the result of the entire
expression is Task where T is the return type of the continuation. Thanks to
that it's possible to `co_await` the entire chain, or chain multiple `.then()`
continuations.
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/coro/task).
## Generators
Generator is a coroutine that lazily produces multiple values. While there's
nothing Qt-specific, QCoro provides the necessary tools for users to create
custom generators in their Qt applications.
QCoro provides API for both synchronous generators (`QCoro::Generator`)
and asynchronous generators (`QCoro::AsyncGenerator`). Both generators provide
container-like API: `begin()` and `end()` member functions that return iterator-like
objects, which is well-known and established API and makes generators compatible
with existing algorithms.
```cpp
QCoro::Generator fibonacci() {
quint64 a = 0, b = 0;
Q_FOREVER {
co_yield b;
const auto tmp = b;
a = b;
b += tmp;
}
}
void printFib(quint64 max) {
for (auto fib : fibonacci()) {
if (fib > max) {
break;
}
std::cout << fib << std::endl;
}
}
```
📘 [Full documentation here](https://qcoro.dvratil.cz/reference/coro/generator).
## License
```text
MIT License
Copyright (c) 2022 Daniel Vrátil
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.
```
qcoro-0.11.0/cmake/ 0000775 0000000 0000000 00000000000 14677722710 0014011 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/cmake/AddQCoroLibrary.cmake 0000664 0000000 0000000 00000016003 14677722710 0017774 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2022 Daniel Vrátil
#
# SPDX-License-Identifier: MIT
include(GenerateHeaders)
include(GenerateExportHeader)
include(GenerateModuleConfigFile)
include(ECMGeneratePriFile)
function(add_qcoro_library)
function(prefix_libraries)
set(oneValueArgs PREFIX OUTPUT)
set(multiValueArgs LIBRARIES)
cmake_parse_arguments(prf "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_libs)
foreach(libname ${prf_LIBRARIES})
if ("${libname}" MATCHES "PUBLIC|PRIVATE|INTERFACE")
list(APPEND _libs "${libname}")
else()
list(APPEND _libs "${prf_PREFIX}::${libname}")
endif()
endforeach()
set(${prf_OUTPUT} ${_libs} PARENT_SCOPE)
endfunction()
function(process_qmake_deps)
set(oneValueArgs PREFIX OUTPUT)
set(multiValueArgs LIBRARIES)
cmake_parse_arguments(pqd "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_libs_priv FALSE)
set(_deps)
foreach (dep ${pqd_LIBRARIES})
if ("${dep}" MATCHES "PUBLIC|INTERFACE|public|interface")
set(_libs_priv FALSE)
continue()
elseif ("${dep}" MATCHES "PRIVATE|private")
set(_libs_priv TRUE)
continue()
endif()
if (NOT _libs_priv)
set(_deps "${_deps} ${pqd_PREFIX}${dep}")
endif()
endforeach()
set(${pqd_OUTPUT} ${_deps} PARENT_SCOPE)
endfunction()
set(params INTERFACE NO_CMAKE_CONFIG)
set(oneValueArgs NAME)
set(multiValueArgs SOURCES CAMELCASE_HEADERS HEADERS QCORO_LINK_LIBRARIES QT_LINK_LIBRARIES)
cmake_parse_arguments(LIB "${params}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(target_name "${QCORO_TARGET_PREFIX}${LIB_NAME}")
string(TOLOWER "${target_name}" target_name_lowercase)
set(target_interface)
set(target_include_interface "PUBLIC")
if (LIB_INTERFACE)
set(target_interface "INTERFACE")
set(target_include_interface "INTERFACE")
endif()
prefix_libraries(
PREFIX ${QCORO_TARGET_PREFIX}
LIBRARIES ${LIB_QCORO_LINK_LIBRARIES}
OUTPUT qcoro_LIBS
)
prefix_libraries(
PREFIX Qt${QT_VERSION_MAJOR}
LIBRARIES ${LIB_QT_LINK_LIBRARIES}
OUTPUT qt_LIBS
)
# TODO: How is it done in Qt?
# We want to export target QCoro5::Network but so far we are exporting
# QCoro5::QCoro5Network :shrug:
add_library(${target_name} ${target_interface})
add_library(${QCORO_TARGET_PREFIX}::${LIB_NAME} ALIAS ${target_name})
if (LIB_SOURCES)
target_sources(${target_name} PRIVATE ${LIB_SOURCES})
endif()
target_include_directories(
${target_name}
${target_include_interface} $
${target_include_interface} $
${target_include_interface} $
${target_include_interface} $
${target_include_interface} $
${target_include_interface} $
${target_include_interface} $
${target_include_interface} $
)
target_link_libraries(${target_name} ${qcoro_LIBS})
target_link_libraries(${target_name} ${qt_LIBS})
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB AND NOT LIB_INTERFACE)
target_link_libraries(${target_name} PUBLIC atomic)
endif()
set_target_properties(
${target_name}
PROPERTIES
EXPORT_NAME ${LIB_NAME}
)
if (NOT LIB_INTERFACE)
set_target_properties(
${target_name}
PROPERTIES
WINDOWS_EXPORT_ALL_SYMBOLS 1
VERSION ${qcoro_VERSION}
SOVERSION ${qcoro_SOVERSION}
)
target_code_coverage(${target_name} AUTO)
else()
target_code_coverage(${target_name} AUTO INTERFACE)
endif()
generate_headers(
camelcase_HEADERS
HEADER_NAMES ${LIB_CAMELCASE_HEADERS}
OUTPUT_DIR QCoro
ORIGINAL_HEADERS_VAR source_HEADERS
)
if (NOT LIB_INTERFACE)
string(TOUPPER "qcoro${LIB_NAME}" export_name)
string(TOLOWER "${export_name}" export_file)
generate_export_header(
${target_name}
BASE_NAME ${export_name}
)
endif()
if (NOT LIB_NO_CMAKE_CONFIG)
generate_cmake_module_config_file(
NAME ${LIB_NAME}
TARGET_NAME ${target_name}
QT_DEPENDENCIES ${LIB_QT_LINK_LIBRARIES}
QCORO_DEPENDENCIES ${LIB_QCORO_LINK_LIBRARIES}
)
endif()
string(TOLOWER "${LIB_QT_LINK_LIBRARIES}" lc_qt_link_libraries)
process_qmake_deps(
OUTPUT qmake_qt_deps
LIBRARIES ${lc_qt_link_libraries}
)
process_qmake_deps(
PREFIX QCoro
OUTPUT qmake_qcoro_deps
LIBRARIES ${LIB_QCORO_LINK_LIBRARIES}
)
set(egp_INTERFACE)
if (LIB_INTERFACE)
set(egp_INTERFACE "INTERFACE")
endif()
ecm_generate_pri_file(
${egp_INTERFACE}
BASE_NAME QCoro${LIB_NAME}
LIB_NAME ${target_name}
VERSION ${qcoro_VERSION}
INCLUDE_INSTALL_DIRS ${QCORO_INSTALL_INCLUDEDIR}/qcoro;${QCORO_INSTALL_INCLUDEDIR}/QCoro
DEPS "${qmake_qt_deps} ${qmake_qcoro_deps}"
)
install(
TARGETS ${target_name}
EXPORT ${target_name}Targets
)
install(
FILES ${source_HEADERS}
DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/
COMPONENT Devel
)
foreach(lib_header ${LIB_HEADERS})
get_filename_component(header_prefix_dir ${lib_header} DIRECTORY)
install(
FILES ${lib_header}
DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro/${header_prefix_dir}
COMPONENT Devel
)
endforeach()
install(
FILES ${camelcase_HEADERS}
DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/QCoro/
COMPONENT Devel
)
if (NOT LIB_INTERFACE)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${export_file}_export.h
DESTINATION ${QCORO_INSTALL_INCLUDEDIR}/qcoro
COMPONENT Devel
)
endif()
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/${target_name}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${target_name}ConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${target_name}"
COMPONENT Devel
)
install(
EXPORT ${target_name}Targets
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${target_name}"
FILE "${target_name}Targets.cmake"
NAMESPACE ${QCORO_TARGET_PREFIX}::
COMPONENT Devel
)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/qt_QCoro${LIB_NAME}.pri"
DESTINATION "${ECM_MKSPECS_INSTALL_DIR}"
COMPONENT Devel
)
endfunction()
qcoro-0.11.0/cmake/CheckAtomic.cmake 0000664 0000000 0000000 00000002317 14677722710 0017170 0 ustar 00root root 0000000 0000000 # std::atomic may need libatomic to function correctly.
INCLUDE(CheckCXXSourceCompiles)
INCLUDE(CheckLibraryExists)
# Sometimes linking against libatomic is required for atomic ops, if
# the platform doesn't support lock-free atomics.
function(check_working_cxx_atomics varname)
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++20")
CHECK_CXX_SOURCE_COMPILES("
#include
std::atomic x;
std::atomic y;
std::atomic z;
int main() {
++z;
++y;
return ++x;
}
" ${varname})
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
endfunction(check_working_cxx_atomics)
# Check for (non-64-bit) atomic operations.
if(MSVC)
set(HAVE_CXX_ATOMICS_WITHOUT_LIB True)
else()
# First check if atomics work without the library.
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
message(FATAL_ERROR "Host compiler must support std::atomic!")
endif()
endif()
endif()
qcoro-0.11.0/cmake/CodeCoverage.cmake 0000664 0000000 0000000 00000067377 14677722710 0017365 0 ustar 00root root 0000000 0000000 #
# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
# USAGE: To enable any code coverage instrumentation/targets, the single CMake
# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or
# on the command line.
#
# From this point, there are two primary methods for adding instrumentation to
# targets: 1 - A blanket instrumentation by calling `add_code_coverage()`, where
# all targets in that directory and all subdirectories are automatically
# instrumented. 2 - Per-target instrumentation by calling
# `target_code_coverage()`, where the target is given and thus only
# that target is instrumented. This applies to both libraries and executables.
#
# To add coverage targets, such as calling `make ccov` to generate the actual
# coverage information for perusal or consumption, call
# `target_code_coverage()` on an *executable* target.
#
# Example 1: All targets instrumented
#
# In this case, the coverage information reported will will be that of the
# `theLib` library target and `theExe` executable.
#
# 1a: Via global command
#
# ~~~
# add_code_coverage() # Adds instrumentation to all targets
#
# add_library(theLib lib.cpp)
#
# add_executable(theExe main.cpp)
# target_link_libraries(theExe PRIVATE theLib)
# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target (instrumentation already added via global anyways) for generating code coverage reports.
# ~~~
#
# 1b: Via target commands
#
# ~~~
# add_library(theLib lib.cpp)
# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets.
#
# add_executable(theExe main.cpp)
# target_link_libraries(theExe PRIVATE theLib)
# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports.
# ~~~
#
# Example 2: Target instrumented, but with regex pattern of files to be excluded
# from report
#
# ~~~
# add_executable(theExe main.cpp non_covered.cpp)
# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder.
# ~~~
#
# Example 3: Target added to the 'ccov' and 'ccov-all' targets
#
# ~~~
# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders.
#
# add_executable(theExe main.cpp non_covered.cpp)
# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder.
# ~~~
# Options
option(
CODE_COVERAGE
"Builds targets with code coverage instrumentation. (Requires GCC or Clang)"
OFF)
# Programs
find_program(LLVM_COV_PATH llvm-cov)
find_program(LLVM_PROFDATA_PATH llvm-profdata)
find_program(LCOV_PATH lcov)
find_program(GENHTML_PATH genhtml)
# Hide behind the 'advanced' mode flag for GUI/ccmake
mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH)
# Variables
set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov)
set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1)
# Common initialization/checks
if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED)
set(CODE_COVERAGE_ADDED ON)
# Common Targets
add_custom_target(
ccov-preprocessing
COMMAND ${CMAKE_COMMAND} -E make_directory
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}
DEPENDS ccov-clean)
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
# Messages
message(STATUS "Building with llvm Code Coverage Tools")
if(NOT LLVM_COV_PATH)
message(FATAL_ERROR "llvm-cov not found! Aborting.")
else()
# Version number checking for 'EXCLUDE' compatibility
execute_process(COMMAND ${LLVM_COV_PATH} --version
OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT)
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION
${LLVM_COV_VERSION_CALL_OUTPUT})
if(LLVM_COV_VERSION VERSION_LESS "7.0.0")
message(
WARNING
"target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0"
)
endif()
endif()
# Targets
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
add_custom_target(
ccov-clean
COMMAND ${CMAKE_COMMAND} -E remove -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND ${CMAKE_COMMAND} -E remove -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
else()
add_custom_target(
ccov-clean
COMMAND ${CMAKE_COMMAND} -E rm -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND ${CMAKE_COMMAND} -E rm -f
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
endif()
# Used to get the shared object file list before doing the main all-
# processing
add_custom_target(
ccov-libs
COMMAND ;
COMMENT "libs ready for coverage report.")
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
# Messages
message(STATUS "Building with lcov Code Coverage Tools")
if(CMAKE_BUILD_TYPE)
string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type)
if(NOT ${upper_build_type} STREQUAL "DEBUG")
message(
WARNING
"Code coverage results with an optimized (non-Debug) build may be misleading"
)
endif()
else()
message(
WARNING
"Code coverage results with an optimized (non-Debug) build may be misleading"
)
endif()
if(NOT LCOV_PATH)
message(FATAL_ERROR "lcov not found! Aborting...")
endif()
if(NOT GENHTML_PATH)
message(FATAL_ERROR "genhtml not found! Aborting...")
endif()
# Targets
add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory
${CMAKE_BINARY_DIR} --zerocounters)
else()
message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.")
endif()
endif()
# Adds code coverage instrumentation to a library, or instrumentation/targets
# for an executable target.
# ~~~
# EXECUTABLE ADDED TARGETS:
# GCOV/LCOV:
# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target.
# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
#
# LLVM-COV:
# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter.
# ccov-${TARGET_NAME} : Generates HTML code coverage report.
# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information.
# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file.
# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information.
# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line.
# ccov-all-export : Exports the coverage report to a JSON file.
#
# Required:
# TARGET_NAME - Name of the target to generate code coverage for.
# Optional:
# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE.
# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE.
# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects)
# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets.
# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets.
# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory
# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`.
# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.**
# OBJECTS - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output
# ARGS - For executables ONLY, appends the given arguments to the associated ccov-* executable call
# ~~~
function(target_code_coverage TARGET_NAME)
# Argument parsing
set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN)
set(single_value_keywords COVERAGE_TARGET_NAME)
set(multi_value_keywords EXCLUDE OBJECTS ARGS)
cmake_parse_arguments(
target_code_coverage "${options}" "${single_value_keywords}"
"${multi_value_keywords}" ${ARGN})
# Set the visibility of target functions to PUBLIC, INTERFACE or default to
# PRIVATE.
if(target_code_coverage_PUBLIC)
set(TARGET_VISIBILITY PUBLIC)
set(TARGET_LINK_VISIBILITY PUBLIC)
elseif(target_code_coverage_INTERFACE)
set(TARGET_VISIBILITY INTERFACE)
set(TARGET_LINK_VISIBILITY INTERFACE)
elseif(target_code_coverage_PLAIN)
set(TARGET_VISIBILITY PUBLIC)
set(TARGET_LINK_VISIBILITY)
else()
set(TARGET_VISIBILITY PRIVATE)
set(TARGET_LINK_VISIBILITY PRIVATE)
endif()
if(NOT target_code_coverage_COVERAGE_TARGET_NAME)
# If a specific name was given, use that instead.
set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME})
endif()
if(CODE_COVERAGE)
# Add code coverage instrumentation to the target's linker command
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY}
-fprofile-instr-generate -fcoverage-mapping)
target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY}
-fprofile-instr-generate -fcoverage-mapping)
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs
-ftest-coverage)
target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov)
endif()
# Targets
get_target_property(target_type ${TARGET_NAME} TYPE)
# Add shared library to processing for 'all' targets
if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL)
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
add_custom_target(
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${CMAKE_COMMAND} -E echo "-object=$" >>
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
DEPENDS ccov-preprocessing ${TARGET_NAME})
if(NOT TARGET ccov-libs)
message(
FATAL_ERROR
"Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'."
)
endif()
add_dependencies(ccov-libs
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
endif()
# For executables add targets to run and produce output
if(target_type STREQUAL "EXECUTABLE")
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
# If there are shared objects to also work with, generate the string to
# add them here
foreach(SO_TARGET ${target_code_coverage_OBJECTS})
# Check to see if the target is a shared object
if(TARGET ${SO_TARGET})
get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE)
if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY")
set(SO_OBJECTS ${SO_OBJECTS} -object=$)
endif()
endif()
endforeach()
# Run the executable, generating raw profile data Make the run data
# available for further processing. Separated to allow Windows to run
# this target serially.
add_custom_target(
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${CMAKE_COMMAND} -E env
LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw
$ ${target_code_coverage_ARGS}
COMMAND
${CMAKE_COMMAND} -E echo "-object=$"
${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
COMMAND
${CMAKE_COMMAND} -E echo
"${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw"
>> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list
JOB_POOL ccov_serial_pool
DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME})
# Merge the generated profile data so llvm-cov can process it
add_custom_target(
ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_PROFDATA_PATH} merge -sparse
${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o
${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
# Ignore regex only works on LLVM >= 7
if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
set(EXCLUDE_REGEX ${EXCLUDE_REGEX}
-ignore-filename-regex='${EXCLUDE_ITEM}')
endforeach()
endif()
# Print out details of the coverage information to the command line
add_custom_target(
ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} show $ ${SO_OBJECTS}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
-show-line-counts-or-regions ${EXCLUDE_REGEX}
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
# Print out a summary of the coverage information to the command line
add_custom_target(
ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} report $ ${SO_OBJECTS}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
# Export coverage information so continuous integration tools (e.g.
# Jenkins) can consume it
add_custom_target(
ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} export $ ${SO_OBJECTS}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
-format="text" ${EXCLUDE_REGEX} >
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
# Generates HTML output of the coverage information for perusal
add_custom_target(
ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${LLVM_COV_PATH} show $ ${SO_OBJECTS}
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
set(COVERAGE_INFO
"${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info"
)
# Run the executable, generating coverage information
add_custom_target(
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND $ ${target_code_coverage_ARGS}
DEPENDS ccov-preprocessing ${TARGET_NAME})
# Generate exclusion string for use
foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
'${EXCLUDE_ITEM}')
endforeach()
if(EXCLUDE_REGEX)
set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file
${COVERAGE_INFO})
else()
set(EXCLUDE_COMMAND ;)
endif()
if(NOT ${target_code_coverage_EXTERNAL})
set(EXTERNAL_OPTION --no-external)
endif()
# Capture coverage data
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
add_custom_target(
ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
COMMAND $ ${target_code_coverage_ARGS}
COMMAND
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-preprocessing ${TARGET_NAME})
else()
add_custom_target(
ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
COMMAND $ ${target_code_coverage_ARGS}
COMMAND
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-preprocessing ${TARGET_NAME})
endif()
# Generates HTML output of the coverage information for perusal
add_custom_target(
ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
COMMAND
${GENHTML_PATH} -o
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
${COVERAGE_INFO}
DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
add_custom_command(
TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
POST_BUILD
COMMAND ;
COMMENT
"Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report."
)
# AUTO
if(target_code_coverage_AUTO)
if(NOT TARGET ccov)
add_custom_target(ccov)
endif()
add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME})
if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID
MATCHES "GNU")
if(NOT TARGET ccov-report)
add_custom_target(ccov-report)
endif()
add_dependencies(
ccov-report
ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
endif()
# ALL
if(target_code_coverage_ALL)
if(NOT TARGET ccov-all-processing)
message(
FATAL_ERROR
"Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'."
)
endif()
add_dependencies(ccov-all-processing
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
endif()
endif()
endif()
endfunction()
# Adds code coverage instrumentation to all targets in the current directory and
# any subdirectories. To add coverage instrumentation to only specific targets,
# use `target_code_coverage`.
function(add_code_coverage)
if(CODE_COVERAGE)
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
add_link_options(-fprofile-instr-generate -fcoverage-mapping)
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
add_compile_options(-fprofile-arcs -ftest-coverage)
link_libraries(gcov)
endif()
endif()
endfunction()
# Adds the 'ccov-all' type targets that calls all targets added via
# `target_code_coverage` with the `ALL` parameter, but merges all the coverage
# data from them into a single large report instead of the numerous smaller
# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for
# use with coverage dashboards (e.g. codecov.io, coveralls).
# ~~~
# Optional:
# EXCLUDE - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex!
# ~~~
function(add_code_coverage_all_targets)
# Argument parsing
set(multi_value_keywords EXCLUDE)
cmake_parse_arguments(add_code_coverage_all_targets "" ""
"${multi_value_keywords}" ${ARGN})
if(CODE_COVERAGE)
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
# Merge the profile data for all of the run executables
if(WIN32)
add_custom_target(
ccov-all-processing
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe
merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-sparse $$FILELIST)
else()
add_custom_target(
ccov-all-processing
COMMAND
${LLVM_PROFDATA_PATH} merge -o
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`)
endif()
# Regex exclude only available for LLVM >= 7
if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
set(EXCLUDE_REGEX ${EXCLUDE_REGEX}
-ignore-filename-regex='${EXCLUDE_ITEM}')
endforeach()
endif()
# Print summary of the code coverage information to the command line
if(WIN32)
add_custom_target(
ccov-all-report
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe
report $$FILELIST
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
else()
add_custom_target(
ccov-all-report
COMMAND
${LLVM_COV_PATH} report `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
endif()
# Export coverage information so continuous integration tools (e.g.
# Jenkins) can consume it
add_custom_target(
ccov-all-export
COMMAND
${LLVM_COV_PATH} export `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-format="text" ${EXCLUDE_REGEX} >
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json
DEPENDS ccov-all-processing)
# Generate HTML output of all added targets for perusal
if(WIN32)
add_custom_target(
ccov-all
COMMAND
powershell -Command $$FILELIST = Get-Content
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show
$$FILELIST
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
else()
add_custom_target(
ccov-all
COMMAND
${LLVM_COV_PATH} show `cat
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
-show-line-counts-or-regions
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
-format="html" ${EXCLUDE_REGEX}
DEPENDS ccov-all-processing)
endif()
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
"GNU")
set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info")
# Nothing required for gcov
add_custom_target(ccov-all-processing COMMAND ;)
# Exclusion regex string creation
set(EXCLUDE_REGEX)
foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
'${EXCLUDE_ITEM}')
endforeach()
if(EXCLUDE_REGEX)
set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file
${COVERAGE_INFO})
else()
set(EXCLUDE_COMMAND ;)
endif()
# Capture coverage data
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
add_custom_target(
ccov-all-capture
COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
--output-file ${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-preprocessing ccov-all-processing)
else()
add_custom_target(
ccov-all-capture
COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
--output-file ${COVERAGE_INFO}
COMMAND ${EXCLUDE_COMMAND}
DEPENDS ccov-preprocessing ccov-all-processing)
endif()
# Generates HTML output of all targets for perusal
add_custom_target(
ccov-all
COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR}
DEPENDS ccov-all-capture)
endif()
add_custom_command(
TARGET ccov-all
POST_BUILD
COMMAND ;
COMMENT
"Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report."
)
endif()
endfunction()
qcoro-0.11.0/cmake/DetectAndroidNDKVersion.cmake 0000664 0000000 0000000 00000002413 14677722710 0021427 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2023 Daniel Vrátil
#
# SPDX-License-Identifier: MIT
cmake_policy(SET CMP0140 NEW)
function(detectAndroidNDKVersion outVar)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.20)
# CMAKE_ANDROID_NDK_VERSION introduced in CMake 3.20
set(${outVar} "${CMAKE_ANDROID_NDK_VERSION}")
else()
if (NOT CMAKE_ANDROID_NDK)
message(STATUS "Couldn't detect Android NDK version: CMAKE_ANDROID_NDK not set")
return()
endif()
if (NOT EXISTS "${CMAKE_ANDROID_NDK}/source.properties")
message(STATUS "Couldn't detect Android NDK version: ${CMAKE_ANDROID_NDK}/source.properties doesn't exist")
return()
endif()
file(STRINGS "${CMAKE_ANDROID_NDK}/source.properties" _sources REGEX "^Pkg\.Revision = [0-9]+\.[0-9]+\.[0-9]+$")
string(REGEX MATCH "= ([0-9]+\.[0-9]+)\." _match "${_sources}")
set(${outVar} "${CMAKE_MATCH_1}")
if (NOT ${outVar})
message(STATUS "Couldn't detect Android NDK version: ${CMAKE_ANDROID_NDK}/source.properties doesn't contain Pkg.Revision")
return()
endif()
endif()
message(STATUS "Detected Android NDK version ${${outVar}}")
return(PROPAGATE ${outVar})
endfunction()
qcoro-0.11.0/cmake/ECMGeneratePriFile.cmake 0000664 0000000 0000000 00000025453 14677722710 0020356 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2014 David Faure
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
ECMGeneratePriFile
------------------
Generate a ``.pri`` file for the benefit of qmake-based projects.
As well as the function below, this module creates the cache variable
``ECM_MKSPECS_INSTALL_DIR`` and sets the default value to ``mkspecs/modules``.
This assumes Qt and the current project are both installed to the same
non-system prefix. Packagers who use ``-DCMAKE_INSTALL_PREFIX=/usr`` will
certainly want to set ``ECM_MKSPECS_INSTALL_DIR`` to something like
``share/qt5/mkspecs/modules``.
The main thing is that this should be the ``modules`` subdirectory of either
the default qmake ``mkspecs`` directory or of a directory that will be in the
``$QMAKEPATH`` environment variable when qmake is run.
::
ecm_generate_pri_file(BASE_NAME
LIB_NAME
[VERSION ] # since 5.83
[DEPS " [ [...]]"]
[FILENAME_VAR ]
[INCLUDE_INSTALL_DIRS [ [...]]] # since 5.92
[INCLUDE_INSTALL_DIR ] # deprecated since 5.92
[LIB_INSTALL_DIR ])
If your CMake project produces a Qt-based library, you may expect there to be
applications that wish to use it that use a qmake-based build system, rather
than a CMake-based one. Creating a ``.pri`` file will make use of your
library convenient for them, in much the same way that CMake config files make
things convenient for CMake-based applications. ``ecm_generate_pri_file()``
generates just such a file.
VERSION specifies the version of the library the ``.pri`` file describes. If
not set, the value is taken from the context variable ``PROJECT_VERSION``.
This variable is usually set by the ``project(... VERSION ...)`` command or,
if CMake policy CMP0048 is not NEW, by :module:`ECMSetupVersion`.
For backward-compatibility with older ECM versions the
``PROJECT_VERSION_STRING`` variable as set by :module:`ECMSetupVersion`
will be preferred over ``PROJECT_VERSION`` if set, unless the minimum
required version of ECM is 5.83 and newer. Since 5.83.
BASE_NAME specifies the name qmake project (.pro) files should use to refer to
the library (eg: KArchive). LIB_NAME is the name of the actual library to
link to (ie: the first argument to add_library()). DEPS is a space-separated
list of the base names of other libraries (for Qt libraries, use the same
names you use with the ``QT`` variable in a qmake project file, such as "core"
for QtCore). FILENAME_VAR specifies the name of a variable to store the path
to the generated file in.
INCLUDE_INSTALL_DIRS are the paths (relative to ``CMAKE_INSTALL_PREFIX``) that
include files will be installed to. It defaults to
``${INCLUDE_INSTALL_DIR}/`` if the ``INCLUDE_INSTALL_DIR`` variable
is set. If that variable is not set, the ``CMAKE_INSTALL_INCLUDEDIR`` variable
is used instead, and if neither are set ``include`` is used. LIB_INSTALL_DIR
operates similarly for the installation location for libraries; it defaults to
``${LIB_INSTALL_DIR}``, ``${CMAKE_INSTALL_LIBDIR}`` or ``lib``, in that order.
INCLUDE_INSTALL_DIR is the old variant of INCLUDE_INSTALL_DIRS, taking only one
directory.
Example usage:
.. code-block:: cmake
ecm_generate_pri_file(
BASE_NAME KArchive
LIB_NAME KF5KArchive
DEPS "core"
FILENAME_VAR pri_filename
VERSION 4.2.0
)
install(FILES ${pri_filename} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
A qmake-based project that wished to use this would then do::
QT += KArchive
in their ``.pro`` file.
Since pre-1.0.0.
#]=======================================================================]
# Replicate the logic from KDEInstallDirs.cmake as we can't depend on it
# Ask qmake if we're using the same prefix as Qt
set(_should_query_qt OFF)
if(NOT DEFINED KDE_INSTALL_USE_QT_SYS_PATHS)
include(ECMQueryQt)
ecm_query_qt(qt_install_prefix_dir QT_INSTALL_PREFIX TRY)
if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}")
set(_should_query_qt ON)
endif()
endif()
if(KDE_INSTALL_USE_QT_SYS_PATHS OR _should_query_qt)
include(ECMQueryQt)
ecm_query_qt(qt_install_prefix_dir QT_INSTALL_PREFIX)
ecm_query_qt(qt_host_data_dir QT_HOST_DATA)
if(qt_install_prefix_dir STREQUAL "${CMAKE_INSTALL_PREFIX}")
file(RELATIVE_PATH qt_host_data_dir ${qt_install_prefix_dir} ${qt_host_data_dir})
endif()
if(qt_host_data_dir STREQUAL "")
set(mkspecs_install_dir mkspecs/modules)
else()
set(mkspecs_install_dir ${qt_host_data_dir}/mkspecs/modules)
endif()
set(ECM_MKSPECS_INSTALL_DIR ${mkspecs_install_dir} CACHE PATH "The directory where mkspecs will be installed to.")
else()
set(ECM_MKSPECS_INSTALL_DIR mkspecs/modules CACHE PATH "The directory where mkspecs will be installed to.")
endif()
function(ECM_GENERATE_PRI_FILE)
set(options INTERFACE)
set(oneValueArgs BASE_NAME LIB_NAME DEPS FILENAME_VAR INCLUDE_INSTALL_DIR LIB_INSTALL_DIR VERSION)
set(multiValueArgs INCLUDE_INSTALL_DIRS)
cmake_parse_arguments(EGPF "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(EGPF_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "Unknown keywords given to ECM_GENERATE_PRI_FILE(): \"${EGPF_UNPARSED_ARGUMENTS}\"")
endif()
if(ECM_GLOBAL_FIND_VERSION VERSION_LESS 5.83.0)
set(_support_backward_compat_version_string_var TRUE)
else()
set(_support_backward_compat_version_string_var FALSE)
endif()
if(NOT EGPF_BASE_NAME)
message(FATAL_ERROR "Required argument BASE_NAME missing in ECM_GENERATE_PRI_FILE() call")
endif()
if(NOT EGPF_LIB_NAME)
message(FATAL_ERROR "Required argument LIB_NAME missing in ECM_GENERATE_PRI_FILE() call")
endif()
if(NOT EGPF_VERSION)
if(_support_backward_compat_version_string_var)
if(NOT PROJECT_VERSION_STRING AND NOT PROJECT_VERSION)
message(FATAL_ERROR "Required variable PROJECT_VERSION_STRING or PROJECT_VERSION not set before ECM_GENERATE_PRI_FILE() call. Missing call of ecm_setup_version() or project(VERSION)?")
endif()
else()
if(NOT PROJECT_VERSION)
message(FATAL_ERROR "Required variable PROJECT_VERSION not set before ECM_GENERATE_PRI_FILE() call. Missing call of ecm_setup_version() or project(VERSION)?")
endif()
endif()
endif()
if(EGPF_INCLUDE_INSTALL_DIR)
if(EGPF_INCLUDE_INSTALL_DIRS)
message(FATAL_ERROR "Only one argument of INCLUDE_INSTALL_DIR & INCLUDE_INSTALL_DIRS can be used in ECM_GENERATE_PRI_FILE() call")
endif()
set(EGPF_INCLUDE_INSTALL_DIRS ${EGPF_INCLUDE_INSTALL_DIR})
endif()
if(NOT EGPF_INCLUDE_INSTALL_DIRS)
if(INCLUDE_INSTALL_DIR)
set(EGPF_INCLUDE_INSTALL_DIRS "${INCLUDE_INSTALL_DIR}/${EGPF_BASE_NAME}")
elseif(CMAKE_INSTALL_INCLUDEDIR)
set(EGPF_INCLUDE_INSTALL_DIRS "${CMAKE_INSTALL_INCLUDEDIR}/${EGPF_BASE_NAME}")
else()
set(EGPF_INCLUDE_INSTALL_DIRS "include/${EGPF_BASE_NAME}")
endif()
endif()
if(NOT EGPF_LIB_INSTALL_DIR)
if(LIB_INSTALL_DIR)
set(EGPF_LIB_INSTALL_DIR "${LIB_INSTALL_DIR}")
elseif(CMAKE_INSTALL_LIBDIR)
set(EGPF_LIB_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}")
else()
set(EGPF_LIB_INSTALL_DIR "lib")
endif()
endif()
if(EGPF_VERSION)
set(PRI_VERSION "${EGPF_VERSION}")
else()
if(_support_backward_compat_version_string_var AND PROJECT_VERSION_STRING)
set(PRI_VERSION "${PROJECT_VERSION_STRING}")
if(NOT PROJECT_VERSION_STRING STREQUAL PROJECT_VERSION)
message(DEPRECATION "ECM_GENERATE_PRI_FILE() will no longer support PROJECT_VERSION_STRING when the required minimum version of ECM is 5.83 or newer. Set VERSION parameter or use PROJECT_VERSION instead.")
endif()
else()
set(PRI_VERSION "${PROJECT_VERSION}")
endif()
endif()
string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" PRI_VERSION_MAJOR "${PRI_VERSION}")
string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" PRI_VERSION_MINOR "${PRI_VERSION}")
string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" PRI_VERSION_PATCH "${PRI_VERSION}")
# Prepare the right number of "../.." to go from ECM_MKSPECS_INSTALL_DIR to the install prefix
# This allows to make the generated pri files relocatable (no absolute paths)
if (IS_ABSOLUTE ${ECM_MKSPECS_INSTALL_DIR})
set(BASEPATH ${CMAKE_INSTALL_PREFIX})
else()
string(REGEX REPLACE "[^/]+" ".." PRI_ROOT_RELATIVE_TO_MKSPECS ${ECM_MKSPECS_INSTALL_DIR})
set(BASEPATH "$$PWD/${PRI_ROOT_RELATIVE_TO_MKSPECS}")
endif()
set(PRI_TARGET_BASENAME ${EGPF_BASE_NAME})
set(PRI_TARGET_LIBNAME ${EGPF_LIB_NAME})
set(PRI_TARGET_QTDEPS ${EGPF_DEPS})
set(PRI_TARGET_INCLUDES)
foreach(_dir ${EGPF_INCLUDE_INSTALL_DIRS})
# separate list entries with space
if(IS_ABSOLUTE "${_dir}")
string(APPEND PRI_TARGET_INCLUDES " ${_dir}")
else()
string(APPEND PRI_TARGET_INCLUDES " ${BASEPATH}/${_dir}")
endif()
endforeach()
if(IS_ABSOLUTE "${EGPF_LIB_INSTALL_DIR}")
set(PRI_TARGET_LIBS "${EGPF_LIB_INSTALL_DIR}")
else()
set(PRI_TARGET_LIBS "${BASEPATH}/${EGPF_LIB_INSTALL_DIR}")
endif()
set(PRI_TARGET_DEFINES "")
set(PRI_FILENAME ${CMAKE_CURRENT_BINARY_DIR}/qt_${PRI_TARGET_BASENAME}.pri)
if (EGPF_FILENAME_VAR)
set(${EGPF_FILENAME_VAR} ${PRI_FILENAME} PARENT_SCOPE)
endif()
set(PRI_TARGET_MODULE_CONFIG "")
# backward compat: it was not obvious LIB_NAME needs to be a target name,
# and some projects where the target name was not the actual library output name
# passed the output name for LIB_NAME, so .name & .module prperties are correctly set.
# TODO: improve API dox, allow control over module name if target name != output name
if(TARGET ${EGPF_LIB_NAME})
get_target_property(target_type ${EGPF_LIB_NAME} TYPE)
if (target_type STREQUAL "STATIC_LIBRARY")
set(PRI_TARGET_MODULE_CONFIG "staticlib")
endif()
endif()
if (EGPF_INTERFACE)
set(PRI_TARGET_MODULE "")
else()
set(PRI_TARGET_MODULE "${PRI_TARGET_LIBNAME}")
endif()
file(GENERATE
OUTPUT ${PRI_FILENAME}
CONTENT
"QT.${PRI_TARGET_BASENAME}.VERSION = ${PRI_VERSION}
QT.${PRI_TARGET_BASENAME}.MAJOR_VERSION = ${PRI_VERSION_MAJOR}
QT.${PRI_TARGET_BASENAME}.MINOR_VERSION = ${PRI_VERSION_MINOR}
QT.${PRI_TARGET_BASENAME}.PATCH_VERSION = ${PRI_VERSION_PATCH}
QT.${PRI_TARGET_BASENAME}.name = ${PRI_TARGET_LIBNAME}
QT.${PRI_TARGET_BASENAME}.module = ${PRI_TARGET_MODULE}
QT.${PRI_TARGET_BASENAME}.defines = ${PRI_TARGET_DEFINES}
QT.${PRI_TARGET_BASENAME}.includes = ${PRI_TARGET_INCLUDES}
QT.${PRI_TARGET_BASENAME}.private_includes =
QT.${PRI_TARGET_BASENAME}.libs = ${PRI_TARGET_LIBS}
QT.${PRI_TARGET_BASENAME}.depends = ${PRI_TARGET_QTDEPS}
QT.${PRI_TARGET_BASENAME}.module_config = ${PRI_TARGET_MODULE_CONFIG}
"
)
endfunction()
qcoro-0.11.0/cmake/ECMQueryQt.cmake 0000664 0000000 0000000 00000006076 14677722710 0016763 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2014 Rohan Garg
# SPDX-FileCopyrightText: 2014 Alex Merry
# SPDX-FileCopyrightText: 2014-2016 Aleix Pol
# SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau
# SPDX-FileCopyrightText: 2022 Ahmad Samir
#
# SPDX-License-Identifier: BSD-3-Clause
#[=======================================================================[.rst:
ECMQueryQt
---------------
This module can be used to query the installation paths used by Qt.
For Qt5 this uses ``qmake``, and for Qt6 this used ``qtpaths`` (the latter has built-in
support to query the paths of a target platform when cross-compiling).
This module defines the following function:
::
ecm_query_qt( [TRY])
Passing ``TRY`` will result in the method not making the build fail if the executable
used for querying has not been found, but instead simply print a warning message and
return an empty string.
Example usage:
.. code-block:: cmake
include(ECMQueryQt)
ecm_query_qt(bin_dir QT_INSTALL_BINS)
If the call succeeds ``${bin_dir}`` will be set to ``/path/to/bin/dir`` (e.g.
``/usr/lib64/qt/bin/``).
Since: 5.93
#]=======================================================================]
if (QT_VERSION_MAJOR STREQUAL "5")
find_package(Qt${QT_VERSION_MAJOR}Core QUIET)
get_target_property(_qmake_executable_default Qt5::qmake LOCATION)
set(QUERY_EXECUTABLE ${_qmake_executable_default}
CACHE FILEPATH "Location of the Qt5 qmake executable")
set(_exec_name_text "Qt5 qmake")
set(_cli_option "-query")
elseif(QT_VERSION_MAJOR STREQUAL "6")
find_package(Qt6 COMPONENTS CoreTools REQUIRED CONFIG)
get_target_property(_qtpaths_executable Qt6::qtpaths LOCATION)
set(QUERY_EXECUTABLE ${_qtpaths_executable}
CACHE FILEPATH "Location of the Qt6 qtpaths executable")
set(_exec_name_text "Qt6 qtpaths")
set(_cli_option "--query")
endif()
function(ecm_query_qt result_variable qt_variable)
set(options TRY)
set(oneValueArgs)
set(multiValueArgs)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT QUERY_EXECUTABLE)
if(ARGS_TRY)
set(${result_variable} "" PARENT_SCOPE)
message(STATUS "No ${_exec_name_text} executable found. Can't check ${qt_variable}")
return()
else()
message(FATAL_ERROR "No ${_exec_name_text} executable found. Can't check ${qt_variable} as required")
endif()
endif()
execute_process(
COMMAND ${QUERY_EXECUTABLE} ${_cli_option} "${qt_variable}"
RESULT_VARIABLE return_code
OUTPUT_VARIABLE output
)
if(return_code EQUAL 0)
string(STRIP "${output}" output)
file(TO_CMAKE_PATH "${output}" output_path)
set(${result_variable} "${output_path}" PARENT_SCOPE)
else()
message(WARNING "Failed call: ${_command} \"${qt_variable}\"")
message(FATAL_ERROR "${_exec_name_text} call failed: ${return_code}")
endif()
endfunction()
qcoro-0.11.0/cmake/GenerateHeaders.cmake 0000664 0000000 0000000 00000002264 14677722710 0020045 0 ustar 00root root 0000000 0000000 function(generate_headers output_var)
set(options)
set(oneValueArgs OUTPUT_DIR ORIGINAL_PREFIX ORIGINAL_HEADERS_VAR)
set(multiValueArgs HEADER_NAMES)
cmake_parse_arguments(GH "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (GH_OUTPUT_DIR)
set(GH_OUTPUT_DIR "${GH_OUTPUT_DIR}/")
endif()
foreach(_headername ${GH_HEADER_NAMES})
string(TOLOWER "${_headername}" originalbase)
set(CC_ORIGINAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${GH_ORIGINAL_PREFIX}/${originalbase}.h")
if (NOT EXISTS ${CC_ORIGINAL_FILE})
message(FATAL_ERROR "Could not find header \"${CC_ORIGINAL_FILE}\"")
endif()
set(CC_HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/${GH_OUTPUT_DIR}/${_headername}")
if (NOT EXISTS ${CC_HEADER_FILE})
file(WRITE ${CC_HEADER_FILE} "#include \"${GH_ORIGINAL_PREFIX}${originalbase}.h\"")
endif()
list(APPEND ${output_var} ${CC_HEADER_FILE})
list(APPEND ${GH_ORIGINAL_HEADERS_VAR} ${CC_ORIGINAL_FILE})
endforeach()
set(${output_var} ${${output_var}} PARENT_SCOPE)
set(${GH_ORIGINAL_HEADERS_VAR} ${${GH_ORIGINAL_HEADERS_VAR}} PARENT_SCOPE)
endfunction()
qcoro-0.11.0/cmake/GenerateModuleConfigFile.cmake 0000664 0000000 0000000 00000003541 14677722710 0021644 0 ustar 00root root 0000000 0000000 include(CMakePackageConfigHelpers)
function(generate_cmake_module_config_file)
function(process_dependencies)
set(oneValueArgs PREFIX OUTPUT)
set(multiValueArgs LIBRARIES)
cmake_parse_arguments(deps "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_deps)
set(_deps_private FALSE)
foreach(dep ${deps_LIBRARIES})
if ("${dep}" MATCHES "PUBLIC|INTERFACE")
set(_deps_private FALSE)
continue()
elseif ("${dep}" STREQUAL "PRIVATE")
set(_deps_private TRUE)
continue()
endif()
if (NOT _deps_private)
set(_deps "${_deps}find_dependency(${deps_PREFIX}${dep})\n")
endif()
endforeach()
set(${deps_OUTPUT} ${_deps} PARENT_SCOPE)
endfunction()
set(options)
set(oneValueArgs TARGET_NAME NAME)
set(multiValueArgs QT_DEPENDENCIES QCORO_DEPENDENCIES)
cmake_parse_arguments(cmc "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
process_dependencies(
PREFIX "Qt${QT_VERSION_MAJOR}"
LIBRARIES ${cmc_QT_DEPENDENCIES}
OUTPUT QT_DEPENDENCIES
)
process_dependencies(
PREFIX "QCoro${QT_VERSION_MAJOR}"
LIBRARIES ${cmc_QCORO_DEPENDENCIES}
OUTPUT QCORO_DEPENDENCIES
)
set(MODULE_NAME "${cmc_NAME}")
configure_package_config_file(
"${qcoro_SOURCE_DIR}/cmake/QCoroModuleConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${cmc_TARGET_NAME}Config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${cmc_TARGET_NAME}
PATH_VARS CMAKE_INSTALL_INCLUDEDIR
)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${cmc_TARGET_NAME}ConfigVersion.cmake"
VERSION ${qcoro_VERSION}
COMPATIBILITY SameMajorVersion
)
endfunction()
qcoro-0.11.0/cmake/QCoroFindQt.cmake 0000664 0000000 0000000 00000001354 14677722710 0017147 0 ustar 00root root 0000000 0000000 macro(qcoro_find_qt)
set(options)
set(oneValueArgs QT_VERSION FOUND_VER_VAR)
set(multiValueArgs COMPONENTS QT5_COMPONENTS QT6_COMPONENTS)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if (NOT ARGS_QT_VERSION)
find_package(Qt6Core QUIET)
if (Qt6Core_FOUND)
set(ARGS_QT_VERSION 6)
else()
set(ARGS_QT_VERSION 5)
endif()
endif()
list(APPEND REQUIRED_QT_COMPONENTS "${ARGS_QT${ARGS_QT_VERSION}_COMPONENTS}")
list(FILTER REQUIRED_QT_COMPONENTS EXCLUDE REGEX "Private$$")
find_package(Qt${ARGS_QT_VERSION} REQUIRED COMPONENTS ${REQUIRED_QT_COMPONENTS})
set(${ARGS_FOUND_VER_VAR} ${ARGS_QT_VERSION})
endmacro()
qcoro-0.11.0/cmake/QCoroModuleConfig.cmake.in 0000664 0000000 0000000 00000001004 14677722710 0020732 0 ustar 00root root 0000000 0000000 @PACKAGE_INIT@
include(CMakeFindDependencyMacro)
@QT_DEPENDENCIES@
@QCORO_DEPENDENCIES@
include("${CMAKE_CURRENT_LIST_DIR}/QCoro@QT_VERSION_MAJOR@@MODULE_NAME@Targets.cmake")
# Versionless target, for compatiblity with Qt6
if (TARGET QCoro@QT_VERSION_MAJOR@::@MODULE_NAME@ AND NOT TARGET QCoro::@MODULE_NAME@)
add_library(QCoro::@MODULE_NAME@ INTERFACE IMPORTED)
set_target_properties(QCoro::@MODULE_NAME@ PROPERTIES
INTERFACE_LINK_LIBRARIES "QCoro@QT_VERSION_MAJOR@::@MODULE_NAME@"
)
endif()
qcoro-0.11.0/config.cmake.in 0000664 0000000 0000000 00000000045 14677722710 0015604 0 ustar 00root root 0000000 0000000 #cmakedefine QCORO_QT_HAS_COMPAT_ABI
qcoro-0.11.0/docker/ 0000775 0000000 0000000 00000000000 14677722710 0014200 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docker/Dockerfile.clang 0000664 0000000 0000000 00000005432 14677722710 0017261 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2022 Daniel Vrátil
# SPDX-License-Identifier: MIT
#
# Docker image for specific versions of clang and specific version of Qt.
FROM debian:bullseye
ARG compiler_version
ARG qt_version
ARG qt_modules
ARG qt_archives
SHELL ["/bin/bash", "-c"]
# Install build & runtime dependencies
RUN apt-get update \
&& apt-get upgrade --yes \
&& apt-get install --yes --no-install-recommends \
cmake=3.18.4\* cmake-data=3.18.4\* \
make \
python3-pip python3-setuptools python3-wheel python3-dev \
dbus dbus-x11 \
libglib2.0-dev libxkbcommon-dev libfreetype6-dev libfontconfig1-dev \
libssl-dev \
libegl-dev libgl-dev libegl1=1.3.2\* libgl1=1.3.2\* libglx0=1.3.2\* libglvnd0=1.3.2\* \
libvulkan-dev
# Install and set up clang
RUN \
# Set up env \
if [ "${compiler_version}" == "dev" ]; then \
export CLANG_VERSION_SUFFIX=""; \
else \
export CLANG_VERSION_SUFFIX="-${compiler_version}"; \
fi && \
echo "export CC=\"/usr/bin/clang${CLANG_VERSION_SUFFIX}\"" >> /etc/profile.d/clang.sh && \
echo "export CXX=\"/usr/bin/clang++${CLANG_VERSION_SUFFIX}\"" >> /etc/profile.d/clang.sh && \
# Install tools needed to add the clang repository \
apt-get install --yes --no-install-recommends ca-certificates wget gnupg && \
# Add clang repository \
echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye${CLANG_VERSION_SUFFIX} main" > /etc/apt/sources.list.d/llvm.list && \
echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye${CLANG_VERSION_SUFFIX} main" >> /etc/apt/sources.list.d/llvm.list && \
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \
# Install clang \
apt-get update && \
apt-get install --yes --install-recommends clang${CLANG_VERSION_SUFFIX} && \
clang_major_version=$(clang${CLANG_VERSION_SUFFIX} --version | grep -Eo "[0-9]+.[0-9]+.[0-9]+" | cut -d'.' -f1) && \
if [ ${clang_major_version} -gt 11 ]; then \
apt-get install --yes --no-install-recommends libclang-rt-${clang_major_version}-dev; \
fi
# Workaround a bug in CMake (?) which tries to look for OpenGL-related libraries
# in /usr/lib/x86_64-unknown-linux-gnu instead of /usr/lib/x86_64-linux-gnu
RUN ln -s x86_64-linux-gnu /usr/lib/x86_64-unknown-linux-gnu
# Install Qt
WORKDIR /root
RUN pip3 install aqtinstall
COPY install-qt.sh ./install-qt.sh
RUN ./install-qt.sh "${qt_version}" "${qt_modules}" "${qt_archives}"
# Set Qt up environment
ENV QT_BASE_DIR "/opt/qt/${qt_version}/gcc_64"
ENV PATH "${QT_BASE_DIR}/bin:${PATH}"
ENV CMAKE_PREFIX_PATH "${QT_BASE_DIR}/lib/cmake"
ENV LD_LIBRARY_PATH "${QT_BASE_DIR}/lib:${LD_LIBRARY_PATH}"
ENV XDG_DATA_DIRS "${QT_BASE_DIR}/share:${XDG_DATA_DIRS}"
ENTRYPOINT [ "/bin/bash", "-l" ]
qcoro-0.11.0/docker/Dockerfile.gcc 0000664 0000000 0000000 00000003537 14677722710 0016735 0 ustar 00root root 0000000 0000000 # SPDX-FileCopyrightText: 2022 Daniel Vrátil
# SPDX-License-Identifier: MIT
#
# Docker image for specific versions of gcc and specific version of Qt.
ARG compiler_version
FROM gcc:${compiler_version}
ARG qt_version
ARG qt_modules
ARG qt_archives
SHELL ["/bin/bash", "-c"]
# Enable backports in older GCC images to get access to newer cmake
RUN source /etc/os-release && \
if [[ "${VERSION_ID}" == "10" ]]; then \
echo "deb http://deb.debian.org/debian buster-backports main" > /etc/apt/sources.list.d/backports.list; \
fi
# Install build-time and runtime dependencies
RUN apt-get update \
&& apt-get upgrade --yes \
&& apt-get install --yes --no-install-recommends \
cmake cmake-data \
python3-pip python3-setuptools python3-wheel python3-dev \
dbus dbus-x11 \
libglib2.0-dev libxkbcommon-dev libfreetype6-dev libfontconfig1-dev \
libssl-dev \
libegl-dev libgl-dev libegl1 libgl1 libglx0 libglvnd0 \
libvulkan-dev \
&& apt-get clean
# Workaround a bug in CMake (?) which tries to look for OpenGL-related libraries
# in /usr/lib/x86_64-unknown-linux-gnu instead of /usr/lib/x86_64-linux-gnu
RUN ln -s x86_64-linux-gnu /usr/lib/x86_64-unknown-linux-gnu
# Install Qt
WORKDIR /root
# Necessary to allow using pip on newer images (--break-system-packages doesn't work on older images)
RUN rm -f /usr/lib/python$(python3 --version | grep -oE "3.[0-9]+")/EXTERNALLY-MANAGED \
&& pip3 install aqtinstall
COPY install-qt.sh ./install-qt.sh
RUN ./install-qt.sh "${qt_version}" "${qt_modules}" "${qt_archives}"
# Set up environment
ENV QT_BASE_DIR "/opt/qt/${qt_version}/gcc_64"
ENV PATH "${QT_BASE_DIR}/bin:${PATH}"
ENV CMAKE_PREFIX_PATH="${QT_BASE_DIR}/lib/cmake"
ENV LD_LIBRARY_PATH "${QT_BASE_DIR}/lib:${LD_LIBRARY_PATH}"
ENV XDG_DATA_DIRS "${QT_BASE_DIR}/share:${XDG_DATA_DIRS}"
qcoro-0.11.0/docker/install-qt.sh 0000775 0000000 0000000 00000000666 14677722710 0016637 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -e
qt_version="$1"
qt_modules="$2"
qt_archives="$3"
if [[ -n "${qt_archives}" ]]; then
opt_archives="--archives ${qt_archives}"
fi
if [[ -n "${qt_modules}" ]]; then
opt_modules="--modules ${qt_modules}"
fi
echo "Installing Qt ${qt_version}"
echo "Modules: ${qt_modules}"
echo "Archives: ${qt_archives}"
aqt install-qt -O /opt/qt linux desktop "${qt_version}" gcc_64 ${opt_archives} ${opt_modules}
echo "Done."
qcoro-0.11.0/docs/ 0000775 0000000 0000000 00000000000 14677722710 0013661 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/about/ 0000775 0000000 0000000 00000000000 14677722710 0014773 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/about/license.md 0000664 0000000 0000000 00000002362 14677722710 0016742 0 ustar 00root root 0000000 0000000
# License
---
QCoro is published under the MIT License
## MIT License
Copyright (c) 2021 Daniel Vrátil
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.
qcoro-0.11.0/docs/building-and-using.md 0000664 0000000 0000000 00000005277 14677722710 0017676 0 ustar 00root root 0000000 0000000
# Building and Using QCoro
## Building QCoro
QCoro uses CMake build system. You can pass following options to the `cmake` command when building
QCoro to customize the build:
* `-DQCORO_BUILD_EXAMPLES` - whether to build examples or not (`ON` by default).
* `-DQCORO_BUILD_TESTING` - whether to build tests or not (defaults to `${BUILD_TESTING}`), can be used to disable building QCoro tests when building QCoro as part of a bigger project which has `BUILD_TESTING` enabled.
* `-DQCORO_ENABLE_ASAN` - whether to build QCoro with AddressSanitizer (`OFF` by default).
* `-DBUILD_SHARED_LIBS` - whether to build QCoro as a shared library (`OFF` by default).
* `-DUSE_QT_VERSION` - set to `5` or `6` to force a particular version of Qt. When not set the highest available version is used.
* `-DQCORO_WITH_QTDBUS` - whether to compile support for QtDBus (`ON` by default).
* `-DQCORO_WITH_QTNETWORK` - whether to compile support for QtNetwork (`ON` by default).
* `-DQCORO_WITH_QTWEBSOCKETS` - whether to compile support for QtWebSockets (`ON` by default).
* `-DQCORO_DISABLE_DEPRECATED_TASK_H` - will not build and install the deprecated task.h header (`OFF` by default).
```
mkdir build
cd build
cmake ..
make
# This will install QCoro into /usr/local/ prefix, change it by passing -DCMAKE_INSTALL_PREFIX=/usr
# to the cmake command above.
sudo make install
```
## CMake
Depending on whether you want to use Qt5 or Qt6 build of QCoro, you should use `QCoro5` or QCoro6` in your
CMake code, respectively. The example below is assuming Qt6:
```cmake
# Use QCoro5 if you are building for Qt5!
find_package(QCoro6 REQUIRED COMPONENTS Core Network DBus)
# Set necessary compiler flags to enable coroutine support
qcoro_enable_coroutines()
...
target_link_libraries(your-target QCoro::Core QCoro::Network QCoro::DBus)
```
Note the missing Qt version number in the `QCoro` target namespace: QCoro provides both
versioned (`QCoro5` and `QCoro6`) namespaces as well as version-less namespace, which is
especially useful for transitioning codebase from Qt5 to Qt6.
## QMake
Using QCoro with QMake projects (`.pro`) is simple: just add the required QCoro modules to the `QT`
variable:
```
QT += QCoroCore QCoroNetwork QCoroDBus
# Enable C++20
CONFIG += c+=20
# Enable coroutine support in the compiler
QMAKE_CXXFLAGS += -fcoroutines
```
You don't need to worry about Qt5 vs Qt6, qmake will pick up the correct build of QCoro depending
on whether you are using QMake for Qt5 or Qt6.
Currently it's necessary to manually enable C++20 and coroutine support (unless that's already
default in your system/compiler settings).
qcoro-0.11.0/docs/changelog.md 0000664 0000000 0000000 00000003662 14677722710 0016141 0 ustar 00root root 0000000 0000000 ---
title: Changelog
---
# Changelog
## 0.10.0 (2023-12-05)
* [Release announcement](news/2023/2023-12-05-qcoro-0.10.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.10.0)
## 0.9.0 (2023-04-27)
* [Release announcement](news/2023/2023-04-27-qcoro-0.9.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.9.0)
## 0.8.0 (2023-01-31)
* [Release announcement](news/2023/2023-01-31-qcoro-0.8.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.8.0)
## 0.7.0 (2022-11-20)
* [Release announcement](news/2022/2022-11-17-qcoro-0.7.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.7.0)
## 0.6.0 (2022-07-09)
* [Release announcement](news/2022/2022-07-09-qcoro-0.6.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.6.0)
## 0.5.1 (2022-04-27)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.5.1)
## 0.5.0 (2022-04-25)
* [Release announcement](news/2022/2022-04-25-qcoro-0.5.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.5.0)
## 0.4.0 (2022-01-06)
* [Release announcement](news/2022/2022-01-06-qcoro-0.4.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.4.0)
## 0.3.0 (2021-10-11)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.3.0)
## 0.2.0 (2021-09-08)
* [Release announcement](news/2021/2021-09-08-qcoro-0.2.0-announcement.md)
* [Github release changelog](https://github.com/danvratil/qcoro/releases/tag/v0.2.0)
## 0.1.0 (2021-08-15)
* Initial release QCoro
* [Release announcement](news/2021/2021-08-16-qcoro-0.1.0-announcement.md)
qcoro-0.11.0/docs/coroutines/ 0000775 0000000 0000000 00000000000 14677722710 0016053 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/coroutines/coawait.md 0000664 0000000 0000000 00000004616 14677722710 0020033 0 ustar 00root root 0000000 0000000
# `co_await` Explained
The following paragraphs try to explain what is a coroutine and what `co_await` does in some
simple way. I don't guarantee that any of this is factically correct. For more gritty (and
correct) details, refer to the articles linked at the bottom of this document.
Coroutines, simply put, are like normal functions except that they can be suspended (and resumed)
in the middle. When a coroutine is suspended, execution returns to the function that has called
the coroutine. If that function is also a coroutine and is waiting (`co_await`ing) for the current
coroutine to finish, then it is suspended as well and the execution returns to the function that has
called that coroutine and so on, until a function that is an actual function (not a coroutine) is reached.
In case of a regular Qt program, this "top-level" non-coroutine function will be the Qt's event loop -
which means that while your coroutine, when called from the Qt event loop is suspended, the Qt event
loop will continue to run until the coroutine is resumed again.
Amongst many other things, this allows you to write asynchronous code as if it were synchronous without
blocking the Qt event loop and making your application unresponsive. See the different examples in this
document.
Now let's look at the `co_await` keyword. This keyword tells the compiler that this is the point where
the coroutine wants to be suspended, until the *awaited* object (the *awaitable*) is ready. Anything type
can be *awaitable* - either because it directly implements the interface needed by the C++ coroutine
machinery, or because some external tools (like this library) are provided to wrap that type into something
that implements the *awaitable* interface.
The C++ coroutines introduce two additional keywords -`co_return` and `co_yield`:
From an application programmer point of view, `co_return` behaves exactly the same as `return`, except that
you cannot use the regular `return` in coroutines. There are some major differences under the hood, though,
which is likely why there's a special keyword for returning from coroutines.
`co_yield` allows a coroutine to produce a result without actually returning. Can be used for writing
generators. Currently, this library has no support/usage of `co_yield`, so I won't go into more details
here.
qcoro-0.11.0/docs/coroutines/qt-vs-coawait.md 0000664 0000000 0000000 00000002754 14677722710 0021104 0 ustar 00root root 0000000 0000000
# Qt vs. co_await
One of the best examples where coroutines simplify your code is when dealing with asynchronous
operations, like network operations.
Let's see how a simple HTTP request would be handled in Qt using the signals/slots mechanism:
```cpp
void MyClass::fetchData() {
auto *nam = new QNetworkAccessManager(this);
auto *reply = nam->get(QUrl{QStringLiteral("https://.../api/fetch")});
QObject::connect(reply, &QNetworkReply::finished,
[reply, nam]() {
const auto data = reply->readAll();
doSomethingWithData(data);
reply->deleteLater();
nam->deleteLater();
});
}
```
Now let's see how the code looks like if we use coroutines:
```cpp
QCoro::Task<> MyClass::fetchData() {
QNetworkReply nam;
auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")});
const auto data = reply->readAll();
reply->deleteLater();
doSomethingWithData(data);
}
```
The magic here is the `co_await` keyword which has turned our method `fetchData()`
into a coroutine and suspended its execution while the network request was running.
When the request finishes, the coroutine is resumed from where it was suspended and
continues.
And the best part? While the coroutine is suspended, the Qt event loop runs as usual!
qcoro-0.11.0/docs/coroutines/reading.md 0000664 0000000 0000000 00000001626 14677722710 0020013 0 ustar 00root root 0000000 0000000
# More reading
This library is inspired by Lewis Bakers' cppcoro library, which also served as a guide to implementing
the coroutine machinery, alongside his great series on C++ coroutines:
* [Coroutine Theory](https://lewissbaker.github.io/2017/09/25/coroutine-theory)
* [Understanding Operator co_await](https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await)
* [Understanding the promise type](https://lewissbaker.github.io/2018/09/05/understanding-the-promise-type)
* [Understanding Symmetric Transfer](https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer)
I can also recommend numerous articles about C++ coroutines by [Raymond Chen on his blog OldNewThink][oldnewthing].
[oldnewthing]: https://devblogs.microsoft.com/oldnewthing/author/oldnewthing
qcoro-0.11.0/docs/examples/ 0000775 0000000 0000000 00000000000 14677722710 0015477 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/examples/qdbus.cpp 0000664 0000000 0000000 00000002675 14677722710 0017333 0 ustar 00root root 0000000 0000000 #include
QCoro::Task PlayerControl::nextSong() {
// Create a regular QDBusInterface representing the Spotify MPRIS interface
QDBusInterface spotifyPlayer{QStringLiteral("org.mpris.MediaPlayer2.spotify"),
QStringLiteral("/org/mpris/MediaPlayer2"),
QStringLiteral("org.mpris.MediaPlayer2.Player")};
// Call CanGoNext DBus method and co_await reply. During that the current coroutine is suspended.
const QDBusReply canGoNext = co_await spotifyPlayer.asyncCall(QStringLiteral("CanGoNext"));
// Response has arrived and coroutine is resumed. If the player can go to the next song,
// do another async call to do so.
if (static_cast(canGoNext)) {
// co_await the call to finish, but throw away the result
co_await spotifyPlayer.asyncCall(QStringLiteral("Next"));
}
// Finally, another async call to retrieve new track metadata. Once again, the coroutine
// is suspended while we wait for the result.
const QDBusReply metadata = co_await spotifyPlayer.asyncCall(QStringLiteral("Metadata"));
// Since this function uses co_await, it is in fact a coroutine, so it must use co_return in order
// to return our result. By definition, the result of this function can be co_awaited by the caller.
co_return static_cast(metadata)[QStringLiteral("xesam:title")].toString();
}
qcoro-0.11.0/docs/examples/qfuture.cpp 0000664 0000000 0000000 00000001042 14677722710 0017673 0 ustar 00root root 0000000 0000000 #include
QCoro::Task<> runTask() {
// Starts a concurrent task and co_awaits on the returned QFuture. While the task is
// running, the coroutine is suspended.
const QString value = co_await QtConcurrent::run([]() {
QString result;
...
// do some long-running computation
...
return result;
});
// When the future has finished, the coroutine is resumed and the result of the
// QFuture is returned and stored in `value`.
// ... now do something with the value
}
qcoro-0.11.0/docs/examples/qnetworkreply.cpp 0000664 0000000 0000000 00000001344 14677722710 0021133 0 ustar 00root root 0000000 0000000 #include
QCoro::Task<> MyClass::fetchData() {
// Creates QNetworkAccessManager on stack
QNetworkAccessManager nam;
// Calls QNetworkAccessManager::get() and co_awaits on the returned QNetworkReply*
// until it finishes. The current coroutine is suspended until that.
auto *reply = co_await nam.get(QUrl{QStringLiteral("https://.../api/fetch")});
// When the reply finishes, the coroutine is resumed and we can access the reply content.
const auto data = reply->readAll();
// Raise your hand if you never forgot to delete a QNetworkReply...
delete reply;
doSomethingWithData(data);
// Extra bonus: the QNetworkAccessManager is destroyed automatically, since it's on stack.
}
qcoro-0.11.0/docs/examples/qprocess.cpp 0000664 0000000 0000000 00000000626 14677722710 0020046 0 ustar 00root root 0000000 0000000 #include
QCoro::Task listDir(const QString &dirPath) {
QProcess basicProcess;
auto process = qCoro(basicProcess);
qDebug() << "Starting ls...";
co_await process.start(QStringLiteral("/bin/ls"), {dirPath});
qDebug() << "Ls started, reading directory...";
co_await process.waitForFinished();
qDebug() << "Done";
return basicProcess.readAll();
}
qcoro-0.11.0/docs/examples/qtcpserver.cpp 0000664 0000000 0000000 00000000527 14677722710 0020405 0 ustar 00root root 0000000 0000000 #include
QCoro::Task<> runServer(uint16_t port) {
QTcpServer server;
server.listen(QHostAddress::LocalHost, port);
while (server.isListening()) {
auto *socket = co_await qCoro(server).waitForNewConnection(10s);
if (socket != nullptr) {
newClientConnection(socket);
}
}
}
qcoro-0.11.0/docs/examples/qtcpsocket.cpp 0000664 0000000 0000000 00000000712 14677722710 0020363 0 ustar 00root root 0000000 0000000 #include
QCoro::Task requestDataFromServer(const QString &hostName) {
QTcpSocket socket;
if (!co_await qCoro(socket).connectToHost(hostName)) {
qWarning() << "Failed to connect to the server";
co_return QByteArray{};
}
socket.write("SEND ME DATA!");
QByteArray data;
while (!data.endsWith("\r\n.\r\n")) {
data += co_await qCoro(socket).readAll();
}
co_return data;
}
`
qcoro-0.11.0/docs/examples/qthread.cpp 0000664 0000000 0000000 00000001004 14677722710 0017626 0 ustar 00root root 0000000 0000000 #include
#include
#include
QCoro::Task MainWindow::processData(const QVector &data) {
std::unique_ptr thread(QThread::create([data]() {
// Perform some intesive calculation
}));
thread->start();
ui->setState(tr("Processing is starting..."));
co_await qCoro(thread.get()).waitForStarted();
ui->setState(tr("Processing data..."));
co_await qCoro(thread.get()).waitForFinished();
ui->setState(tr("Processing done."));
}
qcoro-0.11.0/docs/index.md 0000664 0000000 0000000 00000010244 14677722710 0015313 0 ustar 00root root 0000000 0000000
# QCoro
C++ Coroutine Library for Qt5 and Qt6
---
## Overview
QCoro is a C++ library that provide set of tools to make use of C++20 coroutines
in connection with certain asynchronous Qt actions.
Take a look at the example below to see what an amazing thing coroutines are:
```cpp
QNetworkAccessManager networkAccessManager;
// co_await the reply - the coroutine is suspended until the QNetworkReply is finished.
// While the coroutine is suspended, *the Qt event loop runs as usual*.
const QNetworkReply *reply = co_await networkAccessManager.get(url);
// Once the reply is finished, your code resumes here as if nothing amazing has just happened ;-)
const auto data = reply->readAll();
```
This library has only one class and one function that the user must be aware of: the class is
[`QCoro::Task`](reference/coro/task.md) and must be used as a return type for any coroutine that `co_await`s
a Qt type. The function is [`qCoro()`](reference/coro/coro.md) and it provides coroutine-friendly
wrappers for Qt types that have multiple asynchronous operations that the user may want to `co_await`
(for example [`QProcess`](reference/core/qprocess.md)). All the other code (basically everything in the
`QCoro::detail` namespace) is here to provide the cogs and gears for the C++ coroutine machinery,
making it possible to use Qt types with coroutines.
The major benefit of using coroutines with Qt types is that it allows writing asynchronous code as if it
were synchronous and, most importantly, while the coroutine is `co_await`ing, the __Qt event loop runs
as usual__, meaning that your application remains responsive.
This is a rather experimental library that I started working on to better understand coroutines
in C++. After reading numerous articles and blog posts about coroutines, it still wasn't exactly
clear to me how the whole thing works, so I started working on this library to get a better idea
about coroutines.
## Coroutines
Coroutines are regular functions, except that they can be suspended and resumed again. When
a coroutine is suspended, it returns sort of a promise to the caller and the caller continues
executing their code. At some point, the caller can use the newly introduced `co_await` keyword
to wait for the returned promise to be fulfilled. When that happens, the caller is suspended
and instead the coroutine is resumed.
This allows writing asynchronous code as if it were synchronous, making it much easier to read
and understand.
That's not all that coroutines can do, you can read more about it in the 'Coroutines' section
of this documentation.
## Supported Qt Versions
QCoro supports compiling for both Qt5 and Qt6. Minimum supported versions are:
* Qt5 >= 5.15.2
* Qt6 >= 6.2.0
Pass `-DUSE_QT_VERSION=5` or `-DUSE_QT_VERSION=6` to CMake to force building QCoro with respective
major version of Qt. QCoro will default to Qt6 when available and fallback to Qt5 otherwise.
## Supported Compilers
This library requires a compiler that supports the Coroutine TS (obviously). Currently
GCC, Clang and MSVC are supported.
Officially supported compilers are:
* GCC >= 11 (April 2021)
* Clang >= 15 (September 2022)
* MSVC >= 19.40 (Visual Studio 17 2022)
* AppleClang >= 15.0.0 (Xcode 15.2)
In both GCC and Clang, coroutine support must be explicitly enabled.
### GCC
To enable coroutines support in GCC, add `-fcoroutines` to `CXX_FLAGS`.
CMake:
```
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines")
```
Alternatively, just use `qcoro_enable_coroutines()` CMake macro provided by QCoro to set the
flags automatically.
### Clang
In Clang coroutines are still considered experimental (unlike in GCC).
Coroutines are enabled by adding `-fcoroutines-ts` to `CMAKE_CXX_FLAGS`.
CMake:
```
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines-ts")
```
Alternatively, just use `qcoro_enable_coroutines()` CMake macro provided by QCoro to set the
flags automatically.
### MSVC
Coroutine support in MSVC is enabled automatically by CMake when C++20 standard is specified
in `CMAKE_CXX_STANDARD`:
```
set(CMAKE_CXX_STANDARD 20)
```
qcoro-0.11.0/docs/macros.py 0000664 0000000 0000000 00000001662 14677722710 0015524 0 ustar 00root root 0000000 0000000 def define_env(env):
@env.macro
def doctable(module, include, inherits=None, inheritedBy=[], since=None):
def row(th, td):
return f"{ th } | { td } |
"
def inheritsLink(inherits):
return f"""{inherits[1]}"""
out = """"""
out += row("Module", module)
out += row("Include", f"""
```cpp
#include <{include}>
```
""")
out += row("CMake", f"""
```cpp
target_link_libraries(myapp QCoro::{module})
```
""")
out += row("QMake", f"""
```cpp
QT += QCoro{module}
```
""")
if inherits:
out += row("Inherits", inheritsLink(inherits))
if inheritedBy:
out += row("Inherited By", ', '.join(sorted(map(inheritsLink, inheritedBy))))
if since:
out += row("Since", since)
out += "
"
return out
qcoro-0.11.0/docs/news.md 0000664 0000000 0000000 00000000033 14677722710 0015153 0 ustar 00root root 0000000 0000000 # Blog
{{ blog_content }}
qcoro-0.11.0/docs/news/ 0000775 0000000 0000000 00000000000 14677722710 0014635 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/news/2021/ 0000775 0000000 0000000 00000000000 14677722710 0015221 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/news/2021/2021-08-16-qcoro-0.1.0-announcement.md 0000664 0000000 0000000 00000005202 14677722710 0023042 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.1.0 Release Announcement
date: "2021-08-16"
description: I'm happy to announce first release of QCoro, a library that provides C++ coroutine support for Qt.
---
# QCoro 0.1.0 Release Announcement
I'm happy to announce first release of QCoro, a library that provides C++ coroutine support for Qt.
You can download QCoro 0.1.0 [here][qcoro-release] or check the latest sources on [QCoro GitHub][qcoro-github].
I have talked about QCoro (and C++ coroutines in general) recently at KDE Akademy, you can view the
[recording of my talk on YouTube][qcoro-youtube].
In general, QCoro provides coroutine support for various asynchronous operations provided by Qt. Since
Qt doesn't support coroutines by default, QCoro provides the necessary "glue" between native Qt types
and the C++ coroutine machinery, making it possible to use Qt types with coroutines easily.
QCoro provides coroutine support for asynchronous operations of `QIODevice`, `QNetworkReply`, `QProcess`,
`QDBusPendingReply`, `QTimer` and more. Take a look at the documentation for detailed description and list
of all currently supported Qt types.
A brief example from [our documentation][qcoro-docs] that demonstrates how using coroutines makes handling asynchronous
operations in Qt simpler:
This is a (simplified) example of how we do network requests with Qt normally, using signals and slots:
```cpp
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkReply *reply = manager->get(url);
connect(reply, &QNetworkReply::finished, this,
[this, reply]() {
const auto data = reply->readAll();
doSomethingWithData(data);
reply->deleteLater();
});
```
And this is the same code, written using C++ coroutines:
```cpp
QNetworkAccessManager networkAccessManager;
QNetworkReply *reply = co_await networkAccessManager.get(url);
const auto data = reply->readAll();
doSomethingWithData(data);
reply->deleteLater();
```
The `co_await` keyword here is the key here: it asynchronously waits for the reply to finish. During the wait,
the execution returns to the caller, which could be the Qt event loop, which means that even if this code *looks*
synchronous, in fact it won't block the event loop while keeping the code simple to read and understand.
[qcoro-release]: https://github.com/danvratil/qcoro/releases/tag/v0.1.0
[qcoro-github]: https://github.com/danvratil/qcoro
[qcoro-youtube]: https://www.youtube.com/watch?v=KKVqFqbXJaU&list=PLsHpGlwPdtMq6pJ4mqBeYNWOanjdIIPTJ&index=20
[qcoro-docs]: https://qcoro.dvratil.cz/
qcoro-0.11.0/docs/news/2021/2021-09-08-qcoro-0.2.0-announcement.md 0000664 0000000 0000000 00000005250 14677722710 0023050 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.2.0 Release Announcement
date: "2021-09-08"
description: >
QCoro 0.2.0 has been released. The most visible change is that the library has been modularized,
to follow the Qt modules and headers have been cleaned.
---
# QCoro 0.2.0 Release Announcement
## Library modularity
The code has been reorganized into three modules (and thus three standalone libraries): QCoroCore, QCoroDBus and
QCoroNetwork. QCoroCore contains the elementary QCoro tools (`QCoro::Task`, `qCoro()` wrapper etc.) and coroutine
support for some QtCore types. The QCoroDBus module contains coroutine support for types from the QtDBus module
and equally the QCoroNetwork module contains coroutine support for types from the QtNetwork module. The latter two
modules are also optional, the library can be built without them. It also means that an application that only uses
let's say QtNetwork and has no DBus dependency will no longer get QtDBus pulled in through QCoro, as long as it
only links against `libQCoroCore` and `libQCoroNetwork`. The reorganization will also allow for future
support of additional Qt modules.
## Headers clean up
The include headers in QCoro we a bit of a mess and in 0.2.0 they all got a unified form. All public header files
now start with `qcoro` (e.g. `qcorotimer.h`, `qcoronetworkreply.h` etc.), and QCoro also provides CamelCase headers
now. Thus users should simply do `#include ` if they want coroutine support for `QTimer`.
The reorganization of headers makes QCoro 0.2.0 incompatible with previous versions and any users of QCoro will
have to update their `#include` statements. I'm sorry about this extra hassle, but with this brings much needed
sanity into the header organization and naming scheme.
## Docs update
The documentation has been updated to reflect the reorganization as well as some internal changes. It should be
easier to understand now and hopefully will make it easier for users to start with QCoro now.
## Internal API cleanup and code de-duplication
Historically, certain types types which can be directly `co_await`ed with QCoro, for instance `QTimer` has their
coroutine support implemented differently than types that have multiple asynchronous operations and thus have
a coroutine-friendly wrapper classes (like `QIODevice` and it's `QCoroIODevice` wrapper). In 0.2.0 I have unified
the code so that even the coroutine support for simple types like `QTimer` are implemented through wrapper classes
(so there's `QCoroTimer` now)
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.2.0)
qcoro-0.11.0/docs/news/2022/ 0000775 0000000 0000000 00000000000 14677722710 0015222 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/news/2022/2022-01-06-qcoro-0.4.0-announcement.md 0000664 0000000 0000000 00000005272 14677722710 0023046 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.4.0 Release Announcement
date: "2022-01-06"
description: >
Major highlights of this release are co-installability of Qt5 and Qt6 builds of QCoro,
complete re-work of CMake configuration and support for compiling QCoro with the Clang
compiler against GNU's libstdc++.
---
# QCoro 0.4.0 Release Announcement
Major highlights in this release:
* Co-installability of Qt5 and Qt6 builds of QCoro
* Complete re-work of CMake configuration
* Support for compiling QCoro with Clang against libstdc++
## Co-installability of Qt5 and Qt6 builds of QCoro
This change mostly affects packagers of QCoro. It is now possible to install both Qt5 and Qt6 versions
of QCoro alongside each other without conflicting files. The shared libraries now contain the Qt version
number in their name (e.g. `libQCoro6Core.so`) and header files are also located in dedicated subdirectories
(e.g. `/usr/include/qcoro6/{qcoro,QCoro}`). User of QCoro should not need to do any changes to their codebase.
## Complete re-work of CMake configuration
This change affects users of QCoro, as they will need to adjust CMakeLists.txt of their projects. First,
depending on whether they want to use Qt5 or Qt6 version of QCoro, a different package must be used.
Additionally, list of QCoro components to use must be specified:
```
find_package(QCoro5 REQUIRED COMPONENTS Core Network DBus)
```
Finally, the target names to use in `target_link_libraries` have changed as well:
* `QCoro::Core`
* `QCoro::Network`
* `QCoro::DBus`
The version-less `QCoro` namespace can be used regardless of whether using Qt5 or Qt6 build of QCoro.
`QCoro5` and `QCoro6` namespaces are available as well, in case users need to combine both Qt5 and Qt6
versions in their codebase.
This change brings QCoro CMake configuration system to the same style and behavior as Qt itself, so it
should now be easier to use QCoro, especially when supporting both Qt5 and Qt6.
## Support for compiling QCoro with Clang against libstdc++
Until now, when the Clang compiler was detected, QCoro forced usage of LLVM's libc++ standard library.
Coroutine support requires tight co-operation between the compiler and standard library. Because Clang
still considers their coroutine support experimental it expects all coroutine-related types in standard
library to be located in `std::experimental` namespace. In GNU's libstdc++, coroutines are fully supported
and thus implemented in the `std` namespace. This requires a little bit of extra glue, which is now in place.
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.4.0)
qcoro-0.11.0/docs/news/2022/2022-04-25-qcoro-0.5.0-announcement.md 0000664 0000000 0000000 00000007743 14677722710 0023060 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.5.0 Release Announcement
date: "2022-04-25"
description: >
Major feature in the 0.5.0 release are .then() continuations for Task, all asynchronous
operations in QCoro returning Task and many operations have gained support for optional
timeout argument. Last but not least, coroutine-friendly wrapper for QThread has been
introduced.
---
# QCoro 0.5.0 Release Announcement
Major features:
* .then() continuation for `Task`
* All asynchronous operations now return `Task`
* Timeouts for many operations
* Support for `QThread`
## .then() continuation for Task
Sometimes it's not possible to `co_await` a coroutine - usually because you need to integrate with a 3rd party code
that is not coroutine-ready. A good example might be implementing `QAbstractItemModel`, where none of the virtual
methods are coroutines and thus it's not possible to use `co_await` in them.
To still make it possible to all coroutines from such code, `QCoro::Task` now has a new method: `.then()`,
which allows attaching a continuation callback that will be invoked by QCoro when the coroutine represented
by the `Task` finishes.
```cpp
void notACoroutine() {
someCoroutineReturningQString().then([](const QString &result) {
// Will be invoked when the someCoroutine() finishes.
// The result of the coroutine is passed as an argument to the continuation.
});
}
```
The continuation itself might be a coroutine, and the result of the `.then()` member function is again a `Task`
(where `R` is the return type of the continuation callback), so it is possible to chain multiple continuations
as well as `co_await`ing the entire chain.
## All asynchronous operations now return `Task`
Up until now each operation from the QCoro wrapper types returned a special awaitable - for example,
`QCoroIODevice::read()` returned `QCoro::detail::QCoroIODevice::ReadOperation`. In most cases users of QCoro do
not need to concern themselves with that type, since they can still directly `co_await` the returned awaitable.
However, it unnecessarily leaks implementation details of QCoro into public API and it makes it harded to return
a coroutine from a non-coroutine function.
As of QCoro 0.5.0, all the operations now return `Task`, which makes the API consistent. As a secondary effect,
all the operations can have a chained continuation using the `.then()` continuation, as described above.
## Timeout support for many operations
Qt doesn't allow specifying timeout for many operations, because they are typically non-blocking. But the timeout
makes sense in most QCoro cases, because they are combination of wait + the non-blocking operation. Let's take
`QIODevice::read()` for example: the Qt version doesn't have any timeout, because the call will never block - if
there's nothing to read, it simply returns an empty `QByteArray`.
On the other hand, `QCoroIODevice::read()` is an asynchronous operation, because under to hood, it's a coroutine
that asynchronously calls a sequence of
```cpp
device->waitForReadyRead();
device->read();
```
Since `QIODevice::waitForReadyRead()` takes a timeout argument, it makes sense for `QCoroIODevice::read()`
to also take (an optional) timeout argument. This and many other operations have gained support for timeout.
## Support for `QThread`
It's been a while since I added a new wrapper for a Qt class, so QCoro 0.5.0 adds wrapper for `QThread`. It's
now possible to `co_await` thread start and end:
```cpp
std::unique_ptr thread(QThread::create([]() {
...
});
ui->setLabel(tr("Starting thread...");
thread->start();
co_await qCoro(thread)->waitForStarted();
ui->setLabel(tr("Calculating..."));
co_await qCoro(thread)->waitForFinished();
ui->setLabel(tr("Finished!"));
```
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.5.0)
Thanks to everyone who contributed to QCoro!
qcoro-0.11.0/docs/news/2022/2022-07-09-qcoro-0.6.0-announcement.md 0000664 0000000 0000000 00000020560 14677722710 0023056 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.6.0 Release Announcement
date: "2022-07-09"
description: >
This release brings several major new featrues alongside a bunch of bufixes and
improvements inside QCoro. The four major features are generators support,
new QCoroWebSocket module, deprecation of task.h header, and introduction of
clang-cl and apple-clang support.
---
# QCoro 0.6.0 Release Announcement
This release brings several major new features alongside a bunch of bugfixes and
improvements inside QCoro.
The four major features are:
* Generator support
* New QCoroWebSockets module
* Deprecated task.h
* Clang-cl and apple-clang support
🎉 Starting with 0.6.0 I no longer consider this library to be experimental
(since clearly the experiment worked :-)) and its API to be stable enough for
general use. 🎉
As always, big thank you to everyone who report issues and contributed to QCoro.
Your help is much appreciated!
## Generator support
Unlike regular functions (or `QCoro::Task<>`-based coroutines) which can only ever
produce at most single result (through `return` or `co_return` statement), generators
can yield results repeatedly without terminating. In QCoro we have two types of generators:
synchronous and asynchronous. Synchronous means that the generator produces each value
synchronously. In QCoro those are implemented as `QCoro::Generator`:
```cpp
// A generator that produces a sequence of numbers from 0 to `end`.
QCoro::Generator sequence(int end) {
for (int i = 0; i <= end; ++i) {
// Produces current value of `i` and suspends.
co_yield i;
}
// End the iterator
}
int sumSequence(int end) {
int sum = 0;
// Loops over the returned Generator, resuming the generator on each iterator
// so it can produce a value that we then consume.
for (int value : sequence(end)) {
sum += value;
}
return sum;
}
```
The `Generator` interface implements `begin()` and `end()` methods which produce an
iterator-like type. When the iterator is incremented, the generator is resumed to yield
a value and then suspended again. The iterator-like interface is not mandated by the C++
standard (the C++ standard provides no requirements for generators), but it is an
intentional design choice, since it makes it possible to use the generators with existing
language constructs as well as standard-library and Qt features.
You can find more details about synchronous generators in the [`QCoro::Generator`
documentation](https://qcoro.dvratil.cz/reference/coro/generator/).
Asynchronous generators work in a similar way, but they produce value asynchronously,
that is the result of the generator must be `co_await`ed by the caller.
```cpp
QCoro::AsyncGenerator paginator(const QUrl &baseUrl) {
QUrl pageUrl = baseUrl;
Q_FOREVER {
pageUrl = co_await getNextPage(pageUrl); // co_awaits next page URL
if (pageUrl.isNull()) { // if empty, we reached the last page
break; // leave the loop
}
co_yield pageUrl; // finally, yield the value and suspend
}
// end the generator
}
QCoro::AsyncGenerator pageReader(const QUrl &baseUrl) {
// Create a new generator
auto generator = paginator(baseUrl);
// Wait for the first value
auto it = co_await generator.begin();
auto end = generator.end();
while (it != end) { // while the `it` iterator is valid...
// Asynchronously retrieve the page content
const auto content = co_await fetchPageContent(*it);
// Yield it to the caller, then suspend
co_yield content;
// When resumed, wait for the paginator generator to produce another value
co_await ++it;
}
}
QCoro::Task<> downloader(const QUrl &baseUrl) {
int page = 1;
// `QCORO_FOREACH` is like `Q_FOREACH` for asynchronous iterators
QCORO_FOREACH(const QString &page, pageReader(baseUrl)) {
// When value is finally produced, write it to a file
QFile file(QStringLiteral("page%1.html").arg(page));
file.open(QIODevice::WriteOnly);
file.write(page);
++page;
}
}
```
Async generators also have `begin()` and `end()` methods which provide an asynchronous
iterator-like types. For one, the `begin()` method itself is a coroutine and must be
`co_await`ed to obtain the initial iterator. The increment operation of the iterator
must then be `co_await`ed as well to obtain the iterator for the next value.
Unfortunately, asynchronous iterator cannot be used with ranged-based for loops, so
QCoro provides [`QCORO_FOREACH` macro](https://qcoro.dvratil.cz/reference/coro/asyncgenerator/#qcoro_foreach) to make using asynchronous generators simpler.
Read the [documentation for `QCoro::AsyncGenerator`](https://qcoro.dvratil.cz/reference/coro/asyncgenerator) for more details.
## New QCoroWebSockets module
The QCoroWebSockets module provides QCoro wrappers for `QWebSocket` and `QWebSocketServer`
classes to make them usable with coroutines. Like the other modules, it's a standalone
shared or static library that you must explicitly link against in order to be able to use
it, so you don't have to worry that QCoro would pull websockets dependency into your
project if you don't want to.
```cpp
QCoro::Task<> ChatApp::handleNotifications(const QUrl &wsServer) {
if (!co_await qCoro(mWebSocket).open(wsServer)) {
qWarning() << "Failed to open websocket connection to" << wsServer << ":" << mWebSocket->errorString();
co_return;
}
qDebug() << "Connected to" << wsServer;
// Loops whenever a message is received until the socket is disconnected
QCORO_FOREACH(const QString &rawMessage, qCoro(mWebSocket).textMessages()) {
const auto message = parseMessage(rawMessage);
switch (message.type) {
case MessageType::ChatMessage:
handleChatMessage(message);
break;
case MessageType::PresenceChange:
handlePresenceChange(message);
break;
case MessageType::Invalid:
qWarning() << "Received an invalid message:" << message.error;
break;
}
}
}
```
The `textMessages()` methods returns an asynchronous generator, which yields the message
whenever it arrives. The messages are received and enqueued as long as the generator
object exists. The difference between using a generator and just `co_await`ing the next
emission of the `QWebSocket::textMessage()` signal is that the generator holds a connection
to the signal for its entire lifetime, so no signal emission is lost. If we were only
`co_await`ing a singal emission, any message that is received before we start `co_await`ing
again after handling the current message would be lost.
You can find more details about the `QCoroWebSocket` and `QCoroWebSocketSever`
in the [QCoro's websocket module documentation](https://qcoro.dvratil.cz/reference/websockets/).
You can build QCoro without the WebSockets module by passing `-DQCORO_WITH_QTWEBSOCKETS=OFF`
to CMake.
## Deprecated tasks.h header
The `task.h` header and it's camelcase variant `Task` been deprecated in QCoro 0.6.0
in favor of `qcorotask.h` (and `QCoroTask` camelcase version). The main reasons are to
avoid such a generic name in a library and to make the name consistent with the rest of
QCoro's public headers which all start with `qcoro` (or `QCoro`) prefix.
The old header is still present and fully functional, but including it will produce a
warning that you should port your code to use `qcorotask.h`. You can suppress the warning
by defining `QCORO_NO_WARN_DEPRECATED_TASK_H` in the compiler definitions:
CMake:
```cmake
add_compiler_definitions(QCORO_NO_WARN_DEPRECATED_TASK_H)
```
QMake
```qmake
DEFINES += QCORO_NO_WARN_DEPRECATED_TASK_H
```
The header file will be removed at some point in the future, at latest in the 1.0 release.
You can also pass `-DQCORO_DISABLE_DEPRECATED_TASK_H=ON` to CMake when compiling QCoro
to prevent it from installing the deprecated task.h header.
## Clang-cl and apple-clang support
The clang compiler is fully supported by QCoro since 0.4.0. This version of QCoro
intruduces supports for clang-cl and apple-clang.
Clang-cl is a compiler-driver that provides MSVC-compatible command line options,
allowing to use clang and LLVM as a drop-in replacement for the MSVC toolchain.
Apple-clang is the official build of clang provided by Apple on MacOS, which may be
different from the upstream clang releases.
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.6.0)
qcoro-0.11.0/docs/news/2022/2022-11-17-qcoro-0.7.0-announcement.md 0000664 0000000 0000000 00000007603 14677722710 0023054 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.7.0 Release Announcement
date: "2022-11-16"
description: >
Initial QML support, new QCoro::connect() helper and many small fixes - all of this and more in QCoro 0.7.0.
---
# QCoro 0.7.0 Release Announcement
The major new feature in this release is initial QML support, contributed by
Jonah Brüchert. Jonah also contributed `QObject::connect` helper and
a coroutine version of QQuickImageProvider. As always, this release includes
some smaller enhancements and bugfixes, you can find a full list of them
on the Github release page.
As always, big thank you to everyone who report issues and contributed to QCoro.
Your help is much appreciated!
## Initial QML Support
Jonah Brüchert has contributed initial support for QML. Unfortunately, we
cannot extend the QML engine to support the `async` and `await` keywords from
ES8, but we can make it possible to set a callback from QML that should be
called when the coroutine finishes.
The problem with `QCoro::Task` is that it is a template class so it cannot be
registered into the QML type system and used from inside QML. The solution
that Jonach has come up with is to introduce `QCoro::QmlTask` class, which
can wrap any awaitable (be it `QCoro::Task` or any generic awaitable type)
and provides a `then()` method that can be called from QML and that takes
a JavaScript function as its only argument. The function will be invoked by
`QCoro::QmlTask` when the wrapped awaitable has finished.
The disadvantage of this approach is that in order to expose a class that
uses `QCoro::Task` as return types of its member functions into QML, we
need to create a wrapper class that converts those return types to
`QCoro::QmlTask`.
Luckily, we should be able to provide a smoother user experience when using
QCoro in QML for Qt6 in a future QCoro release.
```cpp
class QmlCoroTimer: public QObject {
Q_OBJECT
public:
explicit QmlCoroTimer(QObject *parent = nullptr)
: QObject(parent)
{}
Q_INVOCABLE QCoro::QmlTask start(int milliseconds) {
// Implicitly wraps QCoro::Task<> into QCoro::QmlTask
return waitFor(milliseconds);
}
private:
// A simple coroutine that co_awaits a timer timeout
QCoro::Task<> waitFor(int milliseconds) {
QTimer timer;
timer.start(milliseconds);
co_await timer;
}
};
...
QCoro::Qml::registerTypes();
qmlRegisterType("cz.dvratil.qcoro.example", 0, 1);
```
```qml
import cz.dvratil.qcoro.example 1.0
Item {
QmlCoroTimer {
id: timer
}
Component.onCompleted: () {
// Attaches a callback to be called when the QmlCoroTimer::waitFor()
// coroutine finishes.
timer.start(1000).then(() => {
console.log("1 second elapsed!");
});
}
}
```
Read the [documentation for `QCoro::QmlTask`](https://qcoro.dvratil.cz/reference/qml/qmltask) for more details.
## QCoro::connect Helper
The `QCoro::connect()` helper is similar to `QObject::connect()` - except you
you pass in a `QCoro::Task` instead of a sender and signal pointers. While
using the `.then()` continuation can achieve similar results, the main
difference is that `QCoro::connect()` takes a pointer to a context (receiver)
QObject. If the receiver is destroyed before the connected `QCoro::Task`
finishes, the slot is not invoked.
```cpp
void MyClass::buttonClicked() {
QCoro::Task task = sendNetworkRequest();
// If this object is deleted before the `task` completes,
// the slot is not invoked.
QCoro::connect(std::move(task), this, &handleNetworkReply);
}
```
See the [QCoro documentation](https://qcoro.dvratil.cz/reference/coro/task/#interfacing-with-synchronous-functions) for more details.
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.7.0)
qcoro-0.11.0/docs/news/2023/ 0000775 0000000 0000000 00000000000 14677722710 0015223 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/news/2023/2023-01-31-qcoro-0.8.0-announcement.md 0000664 0000000 0000000 00000005717 14677722710 0023056 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.8.0 Release Announcement
date: "2023-01-31"
description: >
Improved QCoro::waitFor(), new sleepFor() and sleepUntil() helper functions and
improved thread support.
---
# QCoro 0.8.0 Release Announcement
This is a rather small release with only two new features and one small improvement.
Big thank you to [Xstrahl Inc.](https://xstrahl.com) who sponsored development of
new features included in this release and of QCoro in general.
And as always, thank you to everyone who reported issues and contributed to QCoro.
Your help is much appreciated!
## Improved `QCoro::waitFor()`
Up until this version, `QCoro::waitFor()` was only usable for `QCoro::Task`.
Starting with QCoro 0.8.0, it is possible to use it with any type that satisfies
the `Awaitable` concept. The concept has also been fixed to satisfies not just
types with the `await_resume()`, `await_suspend()` and `await_ready()` member functions,
but also types with member `operator co_await()` and non-member `operator co_await()`
functions.
## `QCoro::sleepFor()` and `QCoro::sleepUntil()`
Working both on QCoro codebase as well as some third-party code bases using QCoro
it's clear that there's a usecase for a simple coroutine that will sleep for
specified amount of time (or until a specified timepoint). It is especially useful
in tests, where simulating delays, especially in asynchronous code is common.
Previously I used to create small coroutines like this:
```cpp
QCoro::Task<> timer(std::chrono::milliseconds timeout) {
QTimer timer;
timer.setSingleShot(true);
timer.start(timeout);
co_await timer;
}
```
Now we can do the same simply by using `QCoro::sleepFor()`.
Read the [documentation for `QCoro::sleepFor()`](https://qcoro.dvratil.cz/reference/core/qtimer/#qcorosleepfor)
and [`QCoro::sleepUntil()`](https://qcoro.dvratil.cz/reference/core/qtimer/#qcorosleepuntil) for more details.
## `QCoro::moveToThread()`
A small helper coroutine that allows a piece of function to be executed in the context
of another thread.
```cpp
void App::runSlowOperation(QThread *helperThread) {
// Still on the main thread
ui->statusLabel.setText(tr("Running"));
const QString input = ui->userInput.text();
co_await QCoro::moveToThread(helperThread);
// Now we are running in the context of the helper thread, the main thread is not blocked
// It is safe to use `input` which was created in another thread
doSomeComplexCalculation(input);
// Move the execution back to the main thread
co_await QCoro::moveToThread(this->thread());
// Runs on the main thread again
ui->statusLabel.setText(tr("Done"));
}
```
Read the [documentation for `QCoro::moveToThread`](https://qcoro.dvratil.cz/reference/core/qthread#qcoromovetothread) for more details.
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.8.0)
qcoro-0.11.0/docs/news/2023/2023-04-27-qcoro-0.9.0-announcement.md 0000664 0000000 0000000 00000004513 14677722710 0023060 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.9.0 Release Announcement
date: "2023-04-27"
description: >
Important bugfixes, huge improvement to QML support and a brand new Test module.
---
# QCoro 0.9.0 Release Announcement
Another smallish release with just a few new features, but quite important bugfixes.
As always, thank you to everyone who reported issues and contributed to QCoro.
Your help is much appreciated!
## Enhanced QML Support
Jonah Brüchert has improved the QML support by introducing a declarative API to await
a task result.
Previously a task result could have only been obtained inside a JavaScript function:
```qml
Label {
id: usernameLabel
Component.onCompleted: {
api.getUserName().then(function(result) {
usernameLabel.text = result
})
}
}
```
With QCoro 0.9.0 you can use the new declarative API to use the result of an asynchronous
task in a property binding:
```qml
Label {
id: usernameLabel
text: api.getUserName().await("Loading...").value
}
```
The `Label` will now show the string "Loading..." while the asynchronous task is running
and will automatically change to the result of the task once if finishes.
You can check the [`QCoro::QmlTask` documentation][qcoro-qmltask-docs] for more details.
## QCoroTest Module
Yet another release with a new QCoro module! This time it's for tests! The QCoroTest
module contains macros (e.g., `QCORO_VERIFY`, `QCORO_COMPARE`, ...) that are basically
identical to their QtTest counteparts with the only difference being that they can be
used inside a coroutine.
We already had this macros inside QCoro test suite almost since the very beginning
and realized they can be useful to our users as well, since they will likely want
to have unittests for their coroutine code.
In some of the next releases we would like to add a little bit more infrastructure
to make writing unittests for coroutines with QtTest even easier.
Check the [`QCoroTest` module documentation][qcoro-test-docs] with a full list of
the test macros.
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.9.0)
[qcoro-qmltask-docs]: https://qcoro.dvratil.cz/reference/qml/qmltask/
[qcoro-test-docs]: https://qcoro.dvratil.cz/reference/test/
qcoro-0.11.0/docs/news/2023/2023-12-05-qcoro-0.10.0-announcement.md 0000664 0000000 0000000 00000010044 14677722710 0023117 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.10.0 Release Announcement
date: "2023-12-05"
description: >
Improved signal/slots features and compatibility and bug fixes.
---
# QCoro 0.10.0 Release Announcement
Thank you to everyone who reported issues and contributed to QCoro.
Your help is much appreciated!
## Support for awaiting Qt signals with QPrivateSignal
Qt has a feature where signals can be made "private" (in the sense that only class
that defines the signal can emit it) by appending `QPrivateSignal` argument to the
signal method:
```cpp
class MyObject : public QObject {
Q_OBJECT
...
Q_SIGNALS:
void error(int code, const QString &message, QPrivateSignal);
};
```
`QPrivateSignal` is a type that is defined inside the `Q_OBJECT` macro, so it's
private and as such only `MyObject` class can emit the signal, since only `MyObject`
can instantiate `QPrivateSignal`:
```cpp
void MyObject::handleError(int code, const QString &message)
{
Q_EMIT error(code, message, QPrivateSignal{});
}
```
QCoro has a feature that makes it possible to `co_await` a signal emission and
returns the signals arguments as a tuple:
```cpp
MyObject myObject;
const auto [code, message] = co_await qCoro(&myObject, &MyObject::handleError);
```
While it was possible to `co_await` a "private" signal previously, it would get
return the `QPrivateSignal` value as an additional value in the result tuple
and on some occasions would not compile at all.
In QCoro 0.10, we can detect the `QPrivateSignal` argument and drop it inside QCoro
so that it does not cause trouble and does not clutter the result type.
Achieving this wasn't simple, as it's not really possible to detect the type (because
it's private), e.g. code like this would fail to compile, because we are not allowed
to refer to `Obj::QPrivateSignal`, since that type is private to `Obj`.
```cpp
template
constexpr bool is_qprivatesignal = std::same_as_v;
```
After many different attempts we ended up abusing `__PRETTY_FUNCTION__`
(and `__FUNCSIG__` on MSVC) and checking whether the function's name contains
`QPrivateSignal` string in the expected location. It's a whacky hack, but hey - if it
works, it's not stupid :). And thanks to improvements in compile-time evaluation in
C++20, the check is evaluated completely at compile-time, so there's no runtime
overhead of obtaining current source location and doing string comparisons.
## Source Code Reorganization (again!)
Big part of QCoro are template classes, so there's a lot of code in headers. In my
opinion, some of the files (especially qcorotask.h) were getting hard to read and
navigate and it made it harder to just see the API of the class (like you get
with non-template classes), which is what users of a library are usually most
interested in.
Therefore I decided to move definitions into separated files, so that they don't
clutter the main include files.
This change is completely source- and binary-compatible, so QCoro users don't have
to make any changes to their code. The only difference is that the main QCoro
headers are much prettier to look at now.
## Bugfixes
* `QCoro::waitFor()` now re-throws exceptions ([#172][issue172], Daniel Vrátil)
* Replaced deprecated `QWebSocket::error` with `QWbSocket::errorOccured` in QCoroWebSockets module ([#174][pr174], Marius P)
* Fix `QCoro::connect()` not working with lambdas ([#179][pr179], Johan Brüchert)
* Fix library name postfix for qmake compatibilty ([#192][pr192], Shantanu Tushar)
* Fix `std::coroutine_traits isn't a class template` error with LLVM 16 ([#196][pr196], Rafael Sadowski)
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.10.0)
[issue172]: https://github.com/danvratil/qcoro/issues/172
[pr174]: https://github.com/danvratil/qcoro/pulls/174
[pr179]: https://github.com/danvratil/qcoro/pulls/179
[pr192]: https://github.com/danvratil/qcoro/pulls/192
[pr196]: https://github.com/danvratil/qcoro/pulls/196
qcoro-0.11.0/docs/news/2024/ 0000775 0000000 0000000 00000000000 14677722710 0015224 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/news/2024/2024-10-04-qcoro-0.11.0-announcement.md 0000664 0000000 0000000 00000014214 14677722710 0023122 0 ustar 00root root 0000000 0000000 ---
title: QCoro 0.11.0 Release Announcement
date: "2024-10-04"
description: >
LazyTask and many bugfixes
---
# QCoro 0.11.0 Release Announcement
A long over-due release which has accumulated a bunch of bugfixes but also some
fancy new features...read on!
As always, big thanks to everyone who reported issues and contributed to QCoro.
Your help is much appreciated!
## `QCoro::LazyTask`
The biggest new features in this release is the brand-new [`QCoro::LazyTask`][qcoro-lazytask].
It's a new return type that you can use for your coroutines. It differs from `QCoro::Task`
in that, as the name suggest, the coroutine is evaluated lazily. What that means is when
you call a coroutine that returns `LazyTask`, it will return imediately without executing
the body of the coroutine. The body will be executed only once you `co_await` on the returned
`LazyTask` object.
This is different from the behavior of `QCoro::Task`, which is eager, meaning that it will
start executing the body immediately when called (like a regular function call).
```cpp
QCoro::LazyTask myWorker()
{
qDebug() << "Starting worker";
co_return 42;
}
QCoro::Task<> mainCoroutine()
{
qDebug() << "Creating worker";
const auto task = myWorker();
qDebug() << "Awaiting on worker";
const auto result = co_await task;
// do something with the result
}
```
This will result in the following output:
```plain
mainCoroutine(): Creating worker
mainCoroutine(): Awaiting on worker
myWorker(): Starting worker
```
If `myWorker()` were a `QCoro::Task` as we know it, the output would look like this:
```plain
mainCoroutine(): Creating worker
myWorker(): Starting worker
mainCoroutine(): Awaiting on worker
```
The fact that the body of a `QCoro::LazyTask` coroutine is only executed when `co_await`ed has one
very important implication: **it must not be used for Qt slots**, `Q_INVOKABLE`s or, in general, for any
coroutine that may be executed directly by the Qt event loop. The reason is, that the Qt event loop
is not aware of coroutines (or QCoro), so it will never `co_await` on the returned `QCoro::LazyTask`
object - which means that the code inside the coroutine would never get executed. This is the
reason why the good old `QCoro::Task` is an eager coroutine - to ensure the body of the coroutine
gets executed even when called from the Qt event loop and not `co_await`ed.
For more details, see the [documentation of `QCoro::LazyTask`][qcoro-lazytask].
## Defined Semantics for Awaiting Default-Constructed and Moved-From Tasks
This is something that wasn't clearely defined until now (both in the docs and in the code), which is
what happens when you try to `co_await` on a default-constructed `QCoro::Task` (or `QCoro::LazyTask`):
```cpp
co_await QCoro::Task<>(); // will hang indefinitely!
```
Previously this would trigger a `Q_ASSERT` in debug build and most likely a crash in production build.
Starting with QCoro 0.11, awaiting such task will print a `qWarning()` and will hang indefinitely.
The same applies to awaiting a moved-from task, which is identical to a default-constructed task:
```cpp
QCoro::LazyTask task = myTask();
handleTask(std::move(task));
co_await task; // will hang indefinitely!`
```
## Compiler Support
We have dropped official support for older compilers. Since QCoro 0.11, the officially supported compilers are:
* GCC >= 11
* Clang >= 15
* MSVC >= 19.40 (Visual Studio 17 2022)
* AppleClang >= 15 (Xcode 15.2)
QCoro might still compile or work with older versions of those compilers, but we no longer test it and
do not guarantee that it will work correctly.
The reason is that coroutine implementation in older versions of GCC and clang were buggy and behaved differently
than they do in newer versions, so making sure that QCoro behaves correctly across wide range of compilers was
getting more difficult as we implemented more and more complex and advanced features.
## Other Features and Changes
A coroutine-friendly version of [`QFuture::takeResult()`][qtdoc-qfuture-takeresult] is now available in the
form of [`QCoroFuture::takeResult()`][qcorofuture-takeresult] when building QCoro against Qt 6 ([#217][issue217]).
`QCoro::waitFor(QCoro::Task)` no longer requires that the task return type `T` is default-constructible ([#223][pr223], Joey Richey)
## Bugfixes
* Suppress Clang error when building against Android NDK <= 25 ([#204][issue204], Daniel Vrátil)
* Fixed missing QtGui dependency in QCoroQuick module ([#209][pr209], Andreas Sturmlechner)
* Fixed `QCoroIODevice::write()` always returning 0 instead of bytes written ([#211][issue211], Daniel Vrátil)
* Fixed unchecked `std::optional` access in `QCoroIODevice::write`
* Fixed awaiting on signal emission with `qCoro()` would resume the awaiter in the sender's thread context ([#213][issue213], Daniel Vrátil)
* Fixed build wilth clang 18 due to missing `#include ` ([#220][pr220], Micah Terhaar)
* Fixed crash when `QNetworkAccessManager` is destroyed from a coroutine awaiting on a network reply ([#231][issue231], Daniel Vrátil)
## Full changelog
[See changelog on Github](https://github.com/danvratil/qcoro/releases/tag/v0.11.0)
## Support
If you enjoy using QCoro, consider supporting its development on [GitHub Sponsors][github-sponsors] or buy me a coffee
on [Ko-fi][kofi] (after all, more coffee means more code, right?).
[issue231]: https://github.com/danvratil/qcoro/issues/231
[issue217]: https://github.com/danvratil/qcoro/issues/217
[issue213]: https://github.com/danvratil/qcoro/issues/213
[issue211]: https://github.com/danvratil/qcoro/issues/211
[issue204]: https://github.com/danvratil/qcoro/issues/204
[pr223]: https://github.com/danvratil/qcoro/pulls/223
[pr220]: https://github.com/danvratil/qcoro/pulls/220
[pr209]: https://github.com/danvratil/qcoro/pulls/209
[qtdoc-qfuture-takeresult]: https://doc.qt.io/qt-6/qfuture.html#takeResult
[qcorofuture-takeresult]: ../../reference/core/qfuture.md#takeResult
[qcoro-lazytask]: ../../reference/coro/lazytask.md
[github-sponsors]: https://github.com/sponsors/danvratil
[kofi]: https://ko-fi.com/danvratil
qcoro-0.11.0/docs/overrides/ 0000775 0000000 0000000 00000000000 14677722710 0015663 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/overrides/.gitkeep 0000664 0000000 0000000 00000000000 14677722710 0017302 0 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/ 0000775 0000000 0000000 00000000000 14677722710 0015617 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/core/ 0000775 0000000 0000000 00000000000 14677722710 0016547 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/core/index.md 0000664 0000000 0000000 00000000662 14677722710 0020204 0 ustar 00root root 0000000 0000000
# Core Module
The `Core` module contains coroutine-friendly wrapper for
[QtCore][qtdoc-qtcore] classes.
## CMake Usage
```
find_package(QCoro6 COMPONENTS Core)
...
target_link_libraries(my-target QCoro::Core)
```
## QMake Usage
```
QT += QCoroCore
```
[qtdoc-qtcore]: https://doc.qt.io/qt-5/qtcore-index.html
qcoro-0.11.0/docs/reference/core/qfuture.md 0000664 0000000 0000000 00000006531 14677722710 0020571 0 ustar 00root root 0000000 0000000
# QFuture
{{ doctable("Core", "QCoroFuture") }}
[`QFuture`][qdoc-qfuture], which represents an asynchronously executed call, doesn't have any
operation on its own that could be awaited asynchronously, this is usually done through a helper
class called [`QFutureWatcher`][qdoc-qfuturewatcher]. To simplify the API, QCoro allows to directly
`co_await` completion of the running `QFuture` or use of a wrapper class `QCoroFuture`. To wrap
a `QFuture` into a `QCoroFuture`, use [`qCoro()`][qcoro-coro]:
```cpp
template
QCoroFuture qCoro(const QFuture &future);
```
It's possible to directly `co_await` a completion of a `QFuture` and obtain a result:
```cpp
const QString result = co_await QtConcurrent::run(...);
```
This is a convenient alternative to using `co_await qCoro(future).result()`.
## `result()`
Asynchronously waits for the `QFuture` to finish and returns the result of the future. If the future
result type is `void`, then returns nothing. If the asynchronous operation has thrown an exception, it
will be rethrown here.
Example:
```cpp
QFuture future = QtConcurrent::run(...);
const QString result = co_await qCoro(future).result();
```
This is equivalent to using `QFutureWatcher` and then retrieving the result using
[`QFuture::result()`][qdoc-qfuture-result]:
```cpp
QFuture future = QtConcurrent::run(...);
auto watcher = new QFutureWatcher();
connect(watcher, &QFutureWatcherBase::finished,
this, [watcher]() {
watcher->deleteLater();
auto result = watcher.result();
...
});
watcher->addFuture(future);
```
## `takeResult()`
Asynchronously waits for the `QFuture` to finish and returns the result of the future by taking
(moving) it from the future object. If the asynchronous operation has thrown an exception, it will
be rethrown here.
This is useful when dealing with move-only types (like `std::unique_ptr`) or when you simply want to
move the result instead of copying it out from the future.
Example:
```cpp
QFuture> future = QtConcurrent::run(...);
auto result = co_await qCoro(future).takeResult();
```
This is equivalent to using `QFutureWatcher` and then retrieving the result using
[`QFuture::takeResult()`][qdoc-qfuture-takeResult]:
```cpp
QFuture> future = QtConcurrent::run(...);
auto watcher = new QFutureWatcher>();
connect(watcher, &QFutureWatcherBase::finished,
this, [watcher]() {
watcher->deleteLater();
auto result = watcher.future().takeResult();
...
});
watcher->addFuture(future);
```
See documentation for [`QFuture::takeResult()`][qdoc-qfuture-takeResult] for limitations regarding
moving the result out from `QFuture`.
!!! info "This method is only available in Qt 6."
## `waitForFinished()`
This is equivalent to using the [`result()`](#result) method.
## Example
```cpp
{% include "../../examples/qfuture.cpp" %}
```
[qdoc-qfuture]: https://doc.qt.io/qt-5/qfuture.html
[qdoc-qfuturewatcher]: https://doc.qt.io/qt-5/qfuturewatcher.html
[qdoc-qfuture-result]: https://doc.qt.io/qt-6/qfuture.html#result
[qdoc-qfuture-takeResult]: https://doc.qt.io/qt-6/qfuture.html#takeResult
[qcoro-coro]: ../coro/coro.md
qcoro-0.11.0/docs/reference/core/qiodevice.md 0000664 0000000 0000000 00000014111 14677722710 0021037 0 ustar 00root root 0000000 0000000
# QIODevice
{{
doctable("Core", "QCoroIODevice", None,
[('network/qabstractsocket', 'QCoroAbstractSocket'),
('network/qlocalsocket', 'QCoroLocalSocket'),
('network/qnetworkreply', 'QCoroNetworkReply'),
('core/qprocess', 'QCoroProcess')])
}}
```cpp
class QCoroIODevice
```
[`QIODevice`][qtdoc-qiodevice] has several different IO operations that can be waited on
asynchronously. Since `QIODevice` itself doesn't provide the abaility to `co_await` those
operations, QCoro provides a wrapper class called `QCoroIODevice`. To wrap a `QIODevice`
into a `QCoroIODevice`, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroIODevice qCoro(QIODevice &);
QCoroIODevice qCoro(QIODevice *);
```
Note that Qt provides several subclasses of `QIODevice`. QCoro provides coroutine-friendly
wrappers for some of those types as well (e.g. for [`QLocalSocket`][qlocalsocket]). This
subclass can be passed to `qCoro()` function as well. Oftentimes the wrapper class
will provide some additional features (like co_awaiting establishing connection etc.).
You can check whether QCoro supports the QIODevice subclass by checking the list of supported
Qt types.
## `readAll()`
Waits until there are any data to be read from the device (similar to waiting until the device
emits [`QIODevice::readyRead()`][qtdoc-qiodevice-readyread] signal) and then returns all data
available in the buffer as a `QByteArray`. Doesn't suspend the coroutine if there are already
data available in the `QIODevice` or if the `QIODevice` is not opened for reading.
This is the default operation when `co_await`ing an instance of a `QIODevice` directly. Thus,
it is possible to just do
```cpp
const QByteArray content = co_await device;
```
instead of
```cpp
const QByteArray content = qCoro(device).readAll();
```
See documentation for [`QIODevice::readAll()`][qtdoc-qiodevice-readall] for details.
`QCoroIODevice::readAll()` additionally accepts an optional timeout parameter. If no data
become available within the timeout, the coroutine returns an empty `QByteArray`. If no
timeout is specified or if it is set to `-1`, the operation will never time out.
```cpp
QCoro::Task QCoroIODevice::readAll();
QCoro::Task QCoroIODevice::readAll(std::chrono::milliseconds timeout);
```
## `read()`
Waits until there are any data to be read from the device (similar to waiting until the device
emits [`QIODevice::readyRead()`][qtdoc-qiodevice-readyread] signal) and then returns up to
`maxSize` bytes as a `QByteArray`. Doesn't suspend the coroutine if there are already data
available in the `QIODevice` or if the device is not opened for reading.
See documentation for [`QIODevice::read()`][qtdoc-qiodevice-read] for details.
`QCoroIODevice::read()` additionally accepts an optional timeout parameter. If no data
become available within the timeout, the coroutine returns an empty `QByteArray`. If no
timeout is specified or if it is set to `-1`, the operation will never time out.
```cpp
QCoro::Task QCoroIODevice::read(qint64 maxSize = 0);
QCoro::Task QCoroIODevice::read(qint64 maxSize, std::chrono::milliseconds timeout);
```
## `readLine()`
Repeatedly waits for data to arrive until it encounters a newline character, end-of-data or
until it reads `maxSize` bytes. Returns the resulting data as `QByteArray`.
See documentation for [`QIODevice::readLine()`][qtdoc-qiodevice-readline] for details.
`QCoroIODevice::readLine()` additionally accepts an optional timeout parameter. If no data
become available within the timeout, the coroutine returns an empty `QByteArray`. If no
timeout is specified or if it is set to `-1`, the operation will never time out.
```cpp
QCoro::Task QCoroIODevice::readLine(qint64 maxSize = 0)
QCoro::Task QCoroIODevice::readLine(qint64 maxSize, std::chrono::milliseconds timeout);
```
## `waitForReadyRead()`
Waits for at most `timeout_msecs` milliseconds for data to become available for reading
in the `QIODevice`. Returns `true` when the device becomes ready for reading within the
given timeout. Returns `false` if the operation times out, if the device is not opened
for reading or in any other state in which the device will never become ready for reading.
If the timeout is -1, the operation will never time out.
See documentation for [`QIODevice::waitForReadyRead()`][qtdoc-qiodevice-waitforreadyread] for details.
```cpp
QCoro::Task QCoroIODevice::waitForReadyRead(int timeout_msecs = 30'000);
QCoro::Task QCoroIODevice::waitForReadyRead(std::chrono::milliseconds timeout);
```
## `waitForBytesWritten()`
Waits for at most `timeout_msecs` milliseconds for data to be flushed from a buffered
`QIODevice`. Returns `std::optional`, which is empty if the operation has timed
out, the device is not opened for writing or is in any other state in which the device
will never be able to write any data. When the data are successfully flushed, returns
number of bytes written.
If the timeout is -1, the operation will never time out.
See documentation for [`QIODevice::waitForBytesWritten()`][qtdoc-qiodevice-waitforbyteswritten] for details.
```cpp
QCoro::Task> QCoroIODevice::waitForBytesWritten(int timeout_msecs = 30'000);
QCoro::Task> QCoroIODevice::waitForBytesWritten(std::chrono::milliseconds timeout);
```
## Examples
```cpp
const QByteArray data = co_await qCoro(device).readAll();
```
[qlocalsocket]: ../network/qlocalsocket.md
[qcoro-coro]: ../coro/coro.md
[qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html
[qtdoc-qiodevice-read]: https://doc.qt.io/qt-5/qiodevice.html#read
[qtdoc-qiodevice-readyread]: https://doc.qt.io/qt-5/qiodevice.html#readyRead
[qtdoc-qiodevice-readall]: https://doc.qt.io/qt-5/qiodevice.html#readAll
[qtdoc-qiodevice-readline]: https://doc.qt.io/qt-5/qiodevice.html#readLine
[qtdoc-qiodevice-waitforreadyread]: https://doc.qt.io/qt-5/qiodevice.html#waitForReadyRead
[qtdoc-qiodevice-waitforbyteswritten]: https://doc.qt.io/qt-5/qiodevice.html#waitForBytesWritten
qcoro-0.11.0/docs/reference/core/qprocess.md 0000664 0000000 0000000 00000006110 14677722710 0020726 0 ustar 00root root 0000000 0000000
# QProcess
{{ doctable("Core", "QCoroProcess", ("core/qiodevice", "QCoroIODevice")) }}
[`QProcess`][qtdoc-qprocess] normally has two features to wait for asynchronously: the process to start
and to finish. Since `QProcess` itself doesn't provide the ability to `co_await` those operations,
QCoro provides a wrapper class `QCoroProcess`. To wrap a `QProcess` object into the `QCoroProcess`
wrapper, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroProcess qCoro(QProcess &);
QCoroProcess qCoro(QProcess *);
```
Same as `QProcess` is a subclass of `QIODevice`, `QCoroProcess` subclasses [`QCoroIODevice`][qcoro-qcoroiodevice],
so it also provides the awaitable interface for selected QIODevice functions.
See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details.
## `waitForStarted()`
Waits for the process to be started or until it times out. Returns `bool` indicating
whether the process has started successfuly (`true`) or timed out (`false`).
See documentation for [`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details.
```cpp
QCoro::Task QCoroProcess::waitForStarted(int timeout = 30'000);
QCoro::Task QCoroProcess::waitForStarted(std::chrono::milliseconds timeout);
```
## `waitForFinished()`
Waits for the process to finish or until it times out. Returns `bool` indicating
whether the process has finished successfuly (`true`) or timed out (`false`).
See documentation for [`QProcess::waitForFinished()`][qtdoc-qprocess-waitForFinished] for details.
```cpp
QCoro::Task QCoroProcess::waitForFinishedint timeout = 30'000);
QCoro::Task QCoroProcess::waitForFinished(std::chrono::milliseconds timeout);
```
## `start()`
QCoroProcess provides an additional method called `start()` which is equivalent to calling
`QProcess::start()` followed by `QCoroProcess::waitForStarted()`. This operation is `co_awaitable`
as well.
See the documentation for [`QProcess::start()`][qtdoc-qprocess-start] and
[`QProcess::waitForStarted()`][qtdoc-qprocess-waitForStarted] for details.o
Returns `true` when the process has successfully started, `false` otherwise.
```cpp
QCoro::Task QCoroProcess::start(QIODevice::OpenMode openMode = QIODevice::ReadOnly,
std::chrono::milliseconds timeout = std::chrono::seconds(30));
QCoro::Task QCoroProcess::start(const QString &program, const QStringList &arguments,
QIODevice::OpenMode openMode = QIODevice::ReadOnly,
std::chrono::milliseconds timeout = std::chrono::seconds(30));
```
## Examples
```cpp
{% include "../../examples/qprocess.cpp" %}
```
[qtdoc-qprocess]: https://doc.qt.io/qt-5/qprocess.html
[qtdoc-qprocess-start]: https://doc.qt.io/qt-5/qprocess.html#start
[qtdoc-qprocess-waitForStarted]: https://doc.qt.io/qt-5/qprocess.html#waitForStarted
[qtdoc-qprocess-waitForFiished]: https://doc.qt.io/qt-5/qprocess.html#waitForFinished
[qcoro-coro]: ../coro/coro.md
[qcoro-qcoroiodevice]: qiodevice.md
qcoro-0.11.0/docs/reference/core/qthread.md 0000664 0000000 0000000 00000004523 14677722710 0020525 0 ustar 00root root 0000000 0000000
# QThread
{{ doctable("Core", "QCoroThread") }}
[`QThread`][qtdoc-qthread] has two events: `started` and `finished`. QCoro provides
a coroutine-friendly wrapper for `QThread` - `QCoroThread`, which allows `co_await`ing
those events.
To wrap a `QThread` object into the `QCoroThread` wrapper, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroThread qCoro(QThread &);
QCoroThread qCoro(QThread *);
```
## `waitForStarted()`
Waits for the thread to start. Returns `true` if the thread is already running
or when the thread starts within the specified timeout. If the thread has already
finished or fails to start within the specified timeout, the coroutine will return
`false`.
If the timeout is set to -1, the operation will never time out.
See documentation for [`QThread::started()`][qtdoc-qthread-started] for details.
```cpp
QCoro::Task QCoroThread::waitForStarted(std::chrono::milliseconds timeout);
```
## `waitForFinished()`
Waits for the Waits for the process to finish or until it times out. Returns `bool` indicating
whether the process has finished successfuly (`true`) or timed out (`false`).
thread to finish. Returns `true` if the thread has already finished
or if it finishes within the specified timeout. If the thread has not started yet
or fails to stop within the specified timeout the coroutine will return `false`.
If the timeout is set to -1, the operation will never time out.
See documentation for [`QThread::finished()`][qtdoc-qthread-finished] for details.
```cpp
QCoro::Task QCoroThread::waitForFinished(std::chrono::milliseconds timeout);
```
## `QCoro::moveToThread()`
A helper coroutine that allows changing the thread context in which the coroutine
code is currently being executed.
When `co_await`ed, the current coroutine will be suspended on the current thread and
immediately resumed again, but in the context of the thread that was passed in as
an argument.
```cpp
QCoro::Task<> QCoro::moveToThread(QThread *thread);
```
## Examples
```cpp
{% include "../../examples/qthread.cpp" %}
```
[qtdoc-qthread]: https://doc.qt.io/qt-5/qthread.html
[qtdoc-qthread-started]: https://doc.qt.io/qt-5/qthread.html#started
[qtdoc-qthread-finished]: https://doc.qt.io/qt-5/qthread.html#finished
[qcoro-coro]: ../coro/coro.md
qcoro-0.11.0/docs/reference/core/qtimer.md 0000664 0000000 0000000 00000004227 14677722710 0020377 0 ustar 00root root 0000000 0000000
# QTimer
{{ doctable("Core", "QCoroTimer") }}
```cpp
QTimer timer;
timer.start(1s);
co_await timer;
```
The QCoro frameworks allows `co_await`ing on [QTimer][qdoc-qtimer] object. The
co-awaiting coroutine is suspended, until the timer finishes, that is until
[`QTimer::timeout()`][qdoc-qtimer-timeout] signal is emitted.
The timer must be active. If the timer is not active (not started yet or already
finished) the `co_await` expression will return immediately.
To make it work, include `QCoroTimer` in your implementation.
```cpp
#include
#include
using namespace std::chrono_literals;
QCoro::Task<> MyClass::pretendWork() {
// Creates and starts a QTimer that will tick every second
QTimer timer;
timer.setInterval(1s);
timer.start();
for (int i = 1; i <= 100; ++i) {
// Wait for the timer to tick
co_await timer;
// Update the progress bar value
mProgressBar->setValue(i);
// And repeat...
}
// ... until the for loop finishes.
}
```
## `QCoro::sleepFor()`
A simple coroutine that will suspend for the specified time duration. Can be quite
useful especially in unit-tests.
```cpp
template
QCoro::Task<> QCoro::sleepFor(const std::chrono::duration &timeout);
```
## `QCoro::sleepUntil()`
A simple coroutine that will suspend until the specified point in time. Can be useful
for scheduling tasks.
```cpp
template
QCoro::Task< QCoro::sleepUntil(const std::chrono::time_point &when);
```
Example:
```cpp
const auto now = std::chrono::system_clock::now();
const auto tomorrow_time = std::chrono::system_clock::to_time_t(now + 86400s);
std::tm *gt = std::gmtime(&tomorrow_time);
gt.tm_hour = 0;
gt.tm_min = 0;
gt.tm_sec = 0;
const auto tomorrow_midnight = std::mktime(>);
co_await QCoro::sleepUntil(std::chrono::system_clock::from_time_t(tomorrow_midnight));
```
[qdoc-qtimer]: https://doc.qt.io/qt-5/qtimer.html
[qdoc-qtimer-timeout]: https://doc.qt.io/qt-5/qtimer.html#timeout
qcoro-0.11.0/docs/reference/core/signals.md 0000664 0000000 0000000 00000007773 14677722710 0020547 0 ustar 00root root 0000000 0000000
{{ doctable("Core", "QCoroSignal") }}
# Signals
It's possible to `co_await` a single signal emission through a special
overload of the [`qCoro()`][qcoro-coro] function. The below function returns
an awaitable that will suspend the current coroutine until the specified signal
is emitted.
```cpp
Task qCoro(QObject *obj, QtSignalPtr ptr);
```
The arguments are a pointer to a QObject-derived object and a pointer
to a the object's signal to connect to. Note that if the object is destroyed
while being `co_await`ed, the coroutine will never be resumed.
The returned awaitable produces the signal's arguments. That is, if the
signal has no arguments, the result of the awaitable will be `void`. If
the signal has exactly one argument, then the awaitable produces the value
of the argument. If the signal has more than one arguments, then the result
of the awaitable is a `std::tuple` with all the arguments.
Example:
```
// void signal
co_await qCoro(timer, &QTimer::timeout);
// single-argument signal
const QUrl url = co_await qCoro(reply, &QNetworkReply::redirected);
// multi-argument signal, captured using structured bindings
const auto [exitCode, exitStatus] = co_await qCoro(process, &QProcess::finished);
```
```cpp
Task> qCoro(QObject *obj, QtSignalPtr ptr, std::chrono::milliseconds timeout);
```
An overload that behaves similar to the two-argument overload, but takes an additional
`timeout` argument. If the signal is not emitted within the specified timeout, the returned
awaitable produces an empty `std::optional`. Otherwise the return type behaves the same way
as the two-argument overload.
## QCoroSignalListener
A helper function that creates an [`AsyncGenerator`][qcoro-asyncgenerator] which yields a value
whenever the signal is emitted.
```cpp
QCoro::AsyncGenerator qCoroSignalListener(QObject *obj, QtSignalPtr ptr, std::chrono::milliseconds timeout);
```
The function takes up to three arguments, the `obj` and `ptr` are a QObject-derived object and
a pointer to a signal member function to connect to. The third `timeout` argument is optional.
When the timeout is set, the generator will end if the signal is not emitted within the specified
timeout. When not set, or set to -1, the generator will never terminate on its own, even if
the `obj` QObject is destroyed!
The generator produces all signal emissions, even those that ocur in between the generator being
`co_await`ed. In the example below, even when the `QNetworkReply::downloadProgress()` signal is
emitted while asynchronously processing something in the middle of the `while` loop body, the
emission will not be lost. It will be enqueued, and returned synchronously with the next
`co_await ++it` call.
```cpp
auto listener = qCoroSignalListener(networkReply, &QNetworkReply::downloadProgress);
auto it = co_await listener.begin(); // waits for first emission
while (it != listener.end() && networkReply->isRunning()) {
const auto [received, total] = *it;
// do something with results
//...
if (received == total || networkReply->isFinished()) {
break;
}
co_await ++it; // waits for next signal emission
}
```
Alternatively, it's possible to use `QCORO_FOREACH` to look over the generator:
```cpp
QCORO_FOREACH(const auto [received, total], qCoroSignalListener(reply, &QNetworkReply::downloadProgress)) {
// do something with `received` and `total` values
if (received == total || reply->isFinished()) {
break;
}
}
```
!!! warning "Listener doesn't stop listening until destroyed."
Keep in mind that the `listener` generator will keep listening and collecting the
signal emissions until it is destroyed, even if you are no longer actively iterating
over the generator. It is recommended that you destroy the generator as soon as possible
when you no longer need it.
[qcoro-coro]: ../coro/coro.md
[qcoro-asyncgenerator]: ../coro/asyncgenerator.md
qcoro-0.11.0/docs/reference/coro/ 0000775 0000000 0000000 00000000000 14677722710 0016561 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/coro/asyncgenerator.md 0000664 0000000 0000000 00000010412 14677722710 0022125 0 ustar 00root root 0000000 0000000
# QCoro::AsyncGenerator
{{ doctable("Coro", "QCoroAsyncGenerator") }}
```cpp
template class QCoro::AsyncGenerator
```
`AsyncGenerator` is fundamentally identical to [`QCoro::Generator`][qcoro-generator].
The only difference is that the value is produced asynchronously. Asynchronous
generator is used exactly the same way as synchronous generators, except that the
result of `AsyncGenerator::begin()` must be `co_await`ed, and incrementing
the returned iterator must be `co_await`ed as well.
```cpp
QCoro::AsyncGenerator lottoNumbersGenerator(int count) {
Hat hat; // Hat with numbers
Hand hand; // Hand to pull numbers out of the hat
for (int i = 0; i < count; ++i) {
const uint8_t number = co_await hand.pullNumberFrom(hat);
co_yield number; // guaranteed to be a winning number
}
}
QCoro::Task<> winningLottoNumbers() {
const auto makeMeRich = lottoNumbersGenerator(10);
// We must co_await begin() to obtain the initial iterator
auto winningNumber = co_await makeMeRich.begin();
std::cout << "Winning numbers: ";
while (winningNumber != makeMeRich.end()) {
std::cout << *winningNumber;
// And we must co_await increment
co_await ++(winningNumber);
}
}
```
You might be wondering why are we `co_await`ing `AsyncGenerator::begin()` and
`AsyncGeneratorIterator::operator++()`, and not just the value (the result of
dereferencing the iterator) - it would surely make the code simpler and more intuitive.
The simple reason is that we are `co_await`ing the next iterator (which either holds
a value or is past-the-end iterator) rather than the value itself. Once we have the
iterator, we must check whether it's valid or not, because paste-the-end iterator is
not dereferencable. That's why the `AsyncGenerator::begin()` and
`AsyncGeneratorIterator::operator++()` operations are asynchronous, rather than
`AsyncGeneratorIterator::operator*()`.
#### Exceptions
When the generator coroutine throws an exception, the exception will be rethrown from
the `operator++()` (or from generator's `begin()`) function when they are `co_await`ed.
Afterwards, the iterator is considered invalid and the generator is finished and may not
be used anymore.
```cpp
QCoro::AsyncGenerator generatorThatThrows() {
while (true) {
const int val = co_await generateRandomNumber();
if (val == 0) {
throw std::runtime("Division by zero!");
}
co_yield 42 / val;
}
}
QCoro::Task<> divide42ForNoReason(std::size_t count) {
auto generator = generatorThatThrows();
std::vector numbers;
try {
// Might throw if generator generates 0 immediately.
auto it = co_await generator.begin();
while (numbers.size() < count) {
numbers.push_back(*it);
co_await ++it; // might throw
}
} catch (const std::runtime_error &e) {
// We were unable to generate all numbers
}
}
```
### QCORO_FOREACH
```cpp
#define QCORO_FOREACH(value, generator)
```
The example in previous chapter shows one example of looping over values produced
by an asynchronous generator with a `while` loop. This is how it would look like
with a `for` loop:
```cpp
auto generator = createGenerator();
for (auto it = co_await generator.begin(), end = generator.end(); it != end; co_await ++it) {
const QString &value = *it;
...//do something with value
}
```
Sadly, it's not possible to use the generator with a ranged-based for loop. While
initially the proposal for C++ coroutines did contain `for co_await` construct, it
was removed as the committee was [concerned that it was making assumptions about the
future interface of asynchronous generators][p0664r8c35].
For that reason, QCoro provides `QCORO_FOREACH` macro, which is very similar to
[`Q_FOREACH`][qdoc-qforeach] and works exactly like the for-loop in the example
above:
```cpp
QCORO_FOREACH(const QString &value, createGenerator()) {
... // do something with value
}
```
[qcoro-generator]: ./generator.md
[p0664r8c35]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0664r8.html#35
[qdoc-qforeach]: https://doc.qt.io/qt-5/qtglobal.html#Q_FOREACH
qcoro-0.11.0/docs/reference/coro/coro.md 0000664 0000000 0000000 00000004276 14677722710 0020056 0 ustar 00root root 0000000 0000000
# qCoro()
## Wrapping Qt Types
```cpp
QCoroType qCoro(QtClass *);
QCoroType qCoro(QtClass &);
```
This function is overloaded for all Qt types supported by this library. It accepts either
a pointer or a reference to a Qt type, and returns a QCoro type that wraps the Qt type and
provides coroutine-friendly API for the type.
Some objects have only a single asynchronous event, so it makes sense to make them
directly `co_await`able. An example is `QTimer`, where only one thing can be `co_await`ed -
the timer timeout. Thus with QCoro, it's possible to simply do this:
```cpp
QTimer timer;
...
co_await timer;
```
However, some Qt classes have multiple asynchronous operations that the user may want to `co_await`.
For such types, simply `co_await`ing the class instance doesn't make sense since it's not clear
what operation is being `co_await`ed. For those types, QCoro provides `qCoro()` function
which returns a wrapper that provides coroutine-friendly versions of the asynchronous methods
for the given type.
Let's take QProcess as an example: one may want to `co_await` for the program to start or finish.
Therefore the type must be wrapped into `qCoro()` like this:
```cpp
QProcess process;
// Wait for the process to be started
co_await qCoro(process).start(...);
// The process is running now
...
...
// Wait for it to finish
co_await qCoro(process).finished();
// The process is no longer running
...
```
`qCoro()` is simply overloaded for all the Qt types currently supported by the QCoro library.
The function returns a wrapper object (e.g. `QCoro::detail::QCoroProcess`) which copies the
QProcess API. It doesn't copy the entire API, only the bits that we want to make `co_await`able.
When you call one of those metods (e.g. `QCoroProcess::start()`), it returns an awaitable
type that calls `QProcess::start()`, suspends the coroutine and resumes it again when the
wrapped `QProcess` object emits the `started()` signal.
Normally you don't need to concern yourself with anything inside the `QCoro::detail` namespace,
it's mentioned in the previous paragraph simply to explain how the wrapper works.
qcoro-0.11.0/docs/reference/coro/generator.md 0000664 0000000 0000000 00000010630 14677722710 0021071 0 ustar 00root root 0000000 0000000
# QCoro::Generator
{{ doctable("Coro", "QCoroGenerator") }}
```cpp
template class QCoro::Generator
```
Generator is a coroutine that that yields a single value and then
suspends itself, until resumed again. Then it yields another value
and suspends again. Generator can be inifinite (the coroutine will
never finish and will produce new values for ever until destroyed
from the outside) or finite (after generating some amount of values
the coroutine finishes).
The `QCoro::Generator` is a template class providing interface
for the generator consumer. There is no standard API for generators
specified in the C++ standard (as of C++20). The design chosen by
QCoro copies the design of cppcoro library, which is for the `Generator`
class to provide `begin()` and `end()` methods to obtain iterator-
like objects, allowing the generators to be used like containers and
providing an interface that is familiar to every C++ programmer.
```cpp
// Simple generator that produces `count` values from 0 to `(count-1)`.
QCoro::Generator iota(int count) {
for (int i = 0; i < count; ++i) {
// Yields a value and suspends the generator.
co_yield count;
}
}
void counter() {
// Creates a new initially suspended generator coroutine
auto generator = iota(10);
// Resumes the coroutine, obtains first value and returns
// an iterator representing the first value.
auto it = generator.begin();
// Obtains a past-the-end iterator.
const auto end = generator.end();
// Loops until the generator doesn't finish.
while (it != end) {
// Resumes the generator until it co_yields another value.
++it;
// Reads the current value from the iterator.
std::cout << *it << std::endl;
}
// The code above can be written more consisely using ranged based for-loop
for (const auto value : iota(10)) {
std::count << value << std::endl;
}
}
```
## Infinite generators
A generator may be inifinite, that is it may never finish and keep
producing values whenever queried. A simple naive example might be
a generator producing random value whenever resumed.
See the next chapter on generator lifetime regarding how to destroy
an infinite generator.
```cpp
QCoro::Generator randomNumberGenerator() {
auto *generator = QRandomGenerator::system();
while (true) {
// Generates a single random value and suspends.
co_yield generator->generate();
}
}
void randomInitialize(QVector &vector) {
// Constructs the generator
auto rng = randomNumberGenerator();
// Gets the first tandom value
auto rngIt = rng.begin();
// Loops over all values of the vector
for (auto &val : vector) {
// Stores the current random value and generates a next one
val = *(rngIt++);
}
} // Destroyes the generator coroutine.
```
## Generator lifetime
The lifetime of the generator coroutine is tight to the lifetime
of the associated `QCoro::Generator` object. The generator
coroutine is destroyed when the associated `QCoro::Generator`
object is destroyed, that includes the stack of the coroutine
and everything allocated on the stack.
It doesn't matter whether the coroutine has already finished or
whether it is suspended after yielding a value. When the
`QCoro::Generator` object is destroyed, the stack of the
coroutine and all associated state will be destroyed using the
regular rules of stack destruction.
Therefore, it is stafe to allocate values on generator coroutine
stack. However, dynamically allocated memory will not be free'd
automatically. Therefore if you need to dynamically allocate
memory on heap inside the generator coroutine, you must make
sure to either free it before the generator coroutine is suspended,
or that it is destroyed when the stack is destroyed, e.g. by using
it in `std::unique_ptr` or `QScopeGuard`.
!!! info "Memory usage"
Keep in mind, that that generator coroutine will keep occupying
memory even when not used until it finishes or until the
associated `QCoro::Generator` is destroyed.
## Exceptions
When a generator coroutine throws an exception, it will be rethrown
from `operator++()` or from generator's `begin()` method.
Afterwards the iterator is considered invalid and the generator
is finished and may not be used anymore.
qcoro-0.11.0/docs/reference/coro/index.md 0000664 0000000 0000000 00000002476 14677722710 0020223 0 ustar 00root root 0000000 0000000
# Coro module
The Coro module contains the fundamental coroutine types - the
[QCoro::Task<T>][qcoro-task] for eager coroutines,
[QCoro::LazyTask<T>][qcoro-lazytask] for lazy coroutines,
[QCoro::Generator<T>][qcoro-generator] for synchronous generators and
[QCoro::AsyncGenerator<T>][qcoro-asyncgenerator] for asynchronous generators.
Another useful bit of the Coro module is the [qCoro()][qcoro-coro] wrapper
function that wraps native Qt types into a coroutine-friendly versions supported by
QCoro (check the [Core][qcoro-core], [Network][qcoro-network] and
[DBus][qcoro-dbus] modules of QCoro to see which
Qt types are currently supported by QCoro).
If you don't want to use any of the Qt types supported by QCoro in your
code, but you still want to use C++ coroutines with QCoro, you can simply
just link against `QCoro::Coro` target in your CMakeLists.txt. This will
give you all you need to start implementing custom coroutine-native types
with Qt and QCoro.
[qcoro-task]: task.md
[qcoro-lazytask]: lazytask.md
[qcoro-coro]: coro.md
[qcoro-generator]: generator.md
[qcoro-asyncgenerator]: asyncgenerator.md
[qcoro-core]: ../core/index.md
[qcoro-network]: ../network/index.md
[qcoro-dbus]: ../dbus/index.md
qcoro-0.11.0/docs/reference/coro/lazytask.md 0000664 0000000 0000000 00000005552 14677722710 0020754 0 ustar 00root root 0000000 0000000
# QCoro::LazyTask
{{ doctable("Coro", "QCoroLazyTask", None, [], "0.11") }}
```cpp
template class QCoro::LazyTask
```
`QCoro::LazyTask` represents, as the name suggests, a lazily evaluated coroutine. A lazily
evaluated coroutine is suspended immediately when called and will not execute its body until
the returned `QCoro::LazyTask` is `co_await`ed.
This is in contrast to `QCoro::Task`, which is an eager coroutine, meaning that its body
is executed immediately when invoked and is only suspended when it first `co_await`s another
coroutine.
!!! warning "Don't use LazyTask as a Qt slot"
Do not use `LazyTask` as a return type of Qt slots, or any other coroutine that can be
invoked by the Qt event loop. The Qt event loop is not aware of coroutines and will never
`co_await` the returned `LazyTask`. Therefore, the coroutine's body would never be
executed!
This is the main reason why the "default" `QCoro::Task` coroutines are eager - they
don't need to be `co_await`ed in order to be executed, which makes them compatible with
the Qt event loop.
If you need to have a lazy coroutine that is also invokable from the Qt event loop, use an
eager wrapper coroutine to pass to the event loop:
```cpp
QCoro::LazyTask<> myLazyCoroutine();
Q_INVOKABLE QCoro::Task<> myLazyCoroutineWrapper() {
co_await myLazyCoroutine();
}
```
The eager wrapper is always immediately executed, and since it will immediately start
`co_await`ing on the lazy coroutine, the lazy coroutine will effectively get executed
immediately.
## `then()` continuation
It is possible to chain a continuation to the coroutine by using `.then()`. It is possible to
use both lazy and eager continuations and even non-coroutine continuations:
```cpp
auto task = myLazyTask().then([]() -> QCoro::Task<> { ... }); // #1
auto task = myLazyTask().then([]() -> QCoro::LazyTask<> { ... }); // #2
auto task = myLazyTask().then([]() { return 42; }) // #3
```
In case #1, the `myLazyTask()` will be evaluated eagerly becaues of the `.then()` continuation being
eager (basically equivalent to the eager wrapper mentioned in the warning note at the top).
In case #2 and #3, the the entire chain will be evaluated lazily - that is, both the `myLazyTask()` and the
`.then()` continuation will be evaluated only once `task` is `co_await`ed.
## Blocking wait
It's possible to use `QCoro::waitFor()` to synchronously wait for completion of a lazy coroutine.
Check the documentation in [`QCoro::Task`](task.md#Blocking-wait) for details.
## Interfacing with synchronous functions
See [`QCoro::connect() documentation for `QCoro::Task`](task.md#interfacing-with-synchronous-functions)
for details.
qcoro-0.11.0/docs/reference/coro/task.md 0000664 0000000 0000000 00000020775 14677722710 0020060 0 ustar 00root root 0000000 0000000
# QCoro::Task
{{ doctable("Coro", "QCoroTask") }}
```cpp
template class QCoro::Task
```
Any coroutine that wants to `co_await` one of the types supported by the QCoro library must have
return type `QCoro::Task`, where `T` is the type of the "regular" coroutine return value.
There's no need by the user to interact with or construct `QCoro::Task` manually, the object is
constructed automatically by the compiler before the user code is executed. To return a value
from a coroutine, use `co_return`, which will store the result in the `Task` object and leave
the coroutine.
```cpp
QCoro::Task getUserName(UserID userId) {
...
// Obtain a QString by co_awaiting another coroutine
const QString result = co_await fetchUserNameFromDb(userId);
...
// Return the QString from the coroutine as you would from a regular function,
// just use `co_return` instead of `return` keyword.
co_return result;
}
```
To obtain the result of a coroutine that returns `QCoro::Task`, the result must be `co_await`ed.
When the coroutine `co_return`s a result, the result is stored in the `Task` object and the `co_await`ing
coroutine is resumed. The result is obtained from the returned `Task` object and returned as a result
of the `co_await` call.
```cpp
QCoro::Task getUserDetails(UserID userId) {
...
const QString name = co_await getUserName(userId);
...
}
```
!!! info "Exception Propagation"
When coroutines throws an unhandled exception, the exception is stored in the `Task` object and
is re-thrown from the `co_await` call in the awaiting coroutine.
Note that a default-constructed `Task` object is not associated with any coroutine and awaiting
on it will suspend the awaiter indefinitely. Moving into a default constructed `Task` associates
it with the coroutine previously associated with the moved-from `Task`, and so awaiting on the
moved-to `Task` will work as expcted. On the other hand, the moved-from `Task` will no longer
be associated with any coroutine and awaiting on it will behave the same as awaiting a default-
constructed task - it will suspend the awaiter indefinitely.
## `then()` continuation
!!! note "This feature is available since QCoro 0.5.0"
Sometimes it's not possible to `co_await` a coroutine, for example when calling a coroutine from a
reimplementation of a virtual function from a 3rd party library, where we cannot change the signature
of that function to be a coroutine (e.g. a reimplementation of `QAbstractItemModel::data()`).
Even in this case, we want to process the result of the coroutine asynchronously, though. For such
cases, `Task` provides a `then()` member function that allows the caller to provide a custom
continuation callback to be invoked when the coroutine finishes.
---
```cpp
template
requires (std::invocable || (!std::is_void && std::invocable))
Task Task::then(ThenCallback callback);
```
The `Task::then()` member function has two arguments. The first argument is the continuation
that is called when the coroutine finishes. The second argument is optional - it is a callable
that gets invoked instead of the continuation when the coroutine throws an exception.
The continuation callback must be a callable that accepts either zero arguments (effectively
discardin the result of the coroutine), or exactly one argument of type `T` or type implicitly
constructible from `T`.
If the return type of the `ThenCallback` is `void`, then the return type of the `then()` functon is
`Task`. If the return type of the `ThenCallback` is `R` or `Task`, the return type of the
`then()` function is `Task`. This means that the `ThenCallback` can be a coroutine as well. Thanks
to the return type always being of type `Task`, it is possible to chain multiple `.then()` calls,
or `co_await` the result of the entire chain.
If the coroutine throws an exception, the exception is re-thrown when the result of the entire
continuation is `co_await`ed. If the result of the continuation is not `co_await`ed, the exception
is silently ignored.
If an exception is thrown from the `ThenCallback`, then the exception is either propagated to the nex
chained `then()` continuation or re-thrown if directly `co_await`ed. If the result is not `co_await`ed
and no futher `then()` continuation is chained after the one that has thrown, then the exception is
silently ignored.
---
```cpp
template
requires (((std::is_void_t && std::invocable) || std::invocable)
&& std::invocable)
Task Task::then(ThenCallback thenCallback, ErrorCallback errorCallback);
```
An overload of the `then()` member function which takes an additional callback to be invoked when
an exception is thrown from the coroutine. The `ErrorCallback` must be a callable that takes exactly
one argument, which is `const std::exception &`, holding reference to the exception thrown. An exception
thrown from the `ErrorCallback` will be re-thrown if the entire continuation is `co_await`ed. If another
`.then()` continuation is chained after the current continuation and has an `ErrorCallback`, then the
`ErrorCallback` will be invoked. Otherwise, the exception is silently ignored.
If an exception is thrown by the non-void coroutine and is handled by the `ErrorCallback`, then if the
resulting continuation is `co_await`ed, the result will be a default-constructed instance of type `R`
(since the `ThenCallback` was unable to provide a proper instance of type `R`). If `R` is not default-
constructible, the program will not compile. Thus, if returning a non-default-constructible type from
a coroutine that may throw an exception, we recommend to wrap the type in `std::optional`.
Examples:
```cpp
QString User::name() {
if (mName.isNull()) {
mApi.fetchUserName().then(
[this](const QString &name) {
mName = name;
Q_EMIT nameChanged();
}, [](const std::exception &e) {
mName = QStringLiteral("Failed to fetch name: %1").arg(e.what());
Q_EMIT nameChanged();
});
return QStringLiteral("Loading...");
} else {
return mName;
}
}
```
## Blocking wait
Sometimes it's necessary to wait for a coroutine in a blocking manner - this is especially useful
in tests where possibly no event loop is running. QCoro has `QCoro::waitFor()` function
which takes `QCoro::Task` (that is, result of calling any QCoro-based coroutine) and blocks
until the coroutine finishes. If the coroutine has a non-void return value, the value is returned
from `waitFor().`
Since QCoro 0.8.0 it is possible to use `QCoro::waitFor()` with any awaitable type, not just `QCoro::Task`.
```cpp
QCoro::Task computeAnswer() {
co_await QCoro::sleepFor(std::chrono::years{7'500'00});
co_return 42;
}
void nonCoroutineFunction() {
// The following line will block as if computeAnswer were not a coroutine. It will internally run a
// a QEventLoop, so other events are still processed.
const int answer = QCoro::waitFor(computeAnswer());
std::cout << "The answer is: " << answer << std::endl;
}
```
!!! info "Event loops"
The implementation internally uses a `QEventLoop` to wait for the coroutine to be completed.
This means that a `QCoreApplication` instance must exist, although it does not need to be
executed. Usual warnings about using a nested event loop apply here as well.
## Interfacing with synchronous functions
!!! note "This feature is available since QCoro 0.7.0"
Sometimes you need to interface with code that is not coroutine-aware, for example when building list models.
If you'd use the normal `.then()` function, and the coroutine would finish after the model is deleted, the program would crash.
The `QCoro::connect` function is similar to `QObject::connect`,
but for `QCoro::Task`s.
Just like `QObject::connect`, it only calls its callback if the context object still exists.
This is an example for a function that fetches data and updates a model:
```cpp
void updateModel() {
auto task = someCoroutine();
QCoro::connect(std::move(task), this, [this](auto &&result) {
beginResetModel();
m_entries = std::move(result);
endResetModel();
});
}
```
If the model is deleted before the coroutine finishes, the connected lambda will not be called.
qcoro-0.11.0/docs/reference/dbus/ 0000775 0000000 0000000 00000000000 14677722710 0016554 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/dbus/index.md 0000664 0000000 0000000 00000000662 14677722710 0020211 0 ustar 00root root 0000000 0000000
# DBus Module
The `DBus` module contains coroutine-friendly wrapper for
[QtDBus][qtdoc-qtdbus] classes.
## CMake Usage
```
find_package(QCoro6 COMPONENTS DBus)
...
target_link_libraries(my-target QCoro::DBus)
```
## QMake Usage
```
QT += QCoroDBus
```
[qtdoc-qtdbus]: https://doc.qt.io/qt-5/qtdbus-index.html
qcoro-0.11.0/docs/reference/dbus/qdbuspendingcall.md 0000664 0000000 0000000 00000005215 14677722710 0022420 0 ustar 00root root 0000000 0000000
# QDBusPendingCall
{{ doctable("DBus", "QCoroDBusPendingCall") }}
[`QDBusPendingCall`][qdoc-qdbuspendingcall] on its own doesn't have any operation that could
be awaited asynchronously, this is usually done through a helper class called
[`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]. To simplify the API, QCoro allows to
directly `co_await` completion of the pending call or use a wrapper class `QCoroDBusPendingCall`.
To wrap a `QDBusPendingCall` into a `QCoroDBusPendingCall`, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroDBusPendingCall qCoro(const QDBusPendingCall &);
```
To await completion of the pending call without the `qCoro` wrapper, just use the pending call
in a `co_await` expression. The behavior is identical to awaiting on result of
`QCoroDBusPendingCall::waitForFinished()`.
```cpp
QDBusPendingCall call = interface.asyncCall(...);
const QDBusReply<...> reply = co_await pendigCall;
```
!!! info "`QDBusPendingCall` vs. `QDBusPendingReply`"
As the Qt documentation for [`QDBusPendingCall`][qdoc-qdbuspendingcall] says, you are more likely to use
[`QDBusPendingReply`][qdoc-qdbuspendingreply] in application code than `QDBusPendingCall`. QCoro has explicit
support for `QDBusPendingCall` to allow using functions that return `QDBusPendingCall` directly in `co_await`
expressions without the programmer having to first convert it to `QDBusPendingReply`. `QDBusPendingReply` can
be constructed from a `QDBusMessage`, which is a result of awaiting `QDBusPendingCall`, therefore it's possible
to perform both the conversion and awaiting in a single line of code:
```cpp
QDBusPendingReply<...> reply = co_await iface.asyncCall(...);
```
Note that [`QDBusAbstractInterface::asyncCall`][qdoc-qdbusabstractinterface-asyncCall] returns
a `QDBusPendingCall`.
## `waitForFinished()`
{% include-markdown "../../../qcoro/dbus/qcorodbuspendingcall.h"
dedent=true
rewrite-relative-urls=false
start=""
end="" %}
## Example
```cpp
{% include "../../examples/qdbus.cpp" %}
```
[qdoc-qdbuspendingcall]: https://doc.qt.io/qt-5/qdbuspendingcall.html
[qdoc-qdbuspendingreply]: https://doc.qt.io/qt-5/qdbuspendingreply.html
[qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html
[qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished
[qdoc-qdbusabstractinterface-asyncCall]: https://doc.qt.io/qt-5/qdbusabstractinterface.html#asyncCall-1
[qcoro-coro]: ../coro/coro.md
qcoro-0.11.0/docs/reference/dbus/qdbuspendingreply.md 0000664 0000000 0000000 00000004562 14677722710 0022644 0 ustar 00root root 0000000 0000000
# QDBusPendingReply
{{ doctable("DBus", "QCoroDBusPendingReply") }}
[`QDBusPendingReply`][qdoc-qdbuspendingreply] on its own doesn't have any operation that could
be awaited asynchronously, this is usually done through a helper class called
[`QDBusPendingCallWatcher`][qdoc-qdbuspendingcallwatcher]. To simplify the API, QCoro allows to
directly `co_await` completion of the pending reply or use a wrapper class `QCoroDBusPendingReply`.
To wrap a `QDBusPendingReply` into a `QCoroDBusPendingReply`, use [`qCoro()`][qcoro-coro]:
```cpp
template
QCoroDBusPendingCall qCoro(const QDBusPendingReply &);
```
!!! info "`QDBusPendingReply` in Qt5 vs Qt6"
`QDBusPendingReply` in Qt6 is a variadic template, meaning that it can take any amount of template arguments.
In Qt5, however, `QDBusPendingReply` is a template class that accepts only up to 8 paremeters. In QCoro the
`QCoroDBusPendingReply` wrapper is implemented as a variadic template for compatibility with Qt6, but when
building against Qt5, the number of template parameters is artificially limited to 8 to mirror the limitation
of Qt5 `QDBusPendingReply` limitation.
To await completion of the pending call without the `qCoro` wrapper, just use the pending call
in a `co_await` expression. The behavior is identical to awaiting on result of
`QCoroDBusPendingReply::waitForFinished()`.
```cpp
QDBusPendingReply<...> reply = interface.asyncCall(...);
co_await reply;
// Now the reply is finished and the result can be retrieved.
```
## `waitForFinished()`
{% include-markdown "../../../qcoro/dbus/qcorodbuspendingreply.h"
dedent=true
rewrite-relative-urls=false
start=""
end="" %}
## Example
```cpp
{% include "../../examples/qdbus.cpp" %}
```
[qdoc-qdbuspendingcall]: https://doc.qt.io/qt-5/qdbuspendingcall.html
[qdoc-qdbuspendingreply]: https://doc.qt.io/qt-5/qdbuspendingreply.html
[qdoc-qdbuspendingcallwatcher]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html
[qdoc-qdbuspendingcallwatcher-finished]: https://doc.qt.io/qt-5/qdbuspendingcallwatcher.html#finished
[qdoc-qdbusabstractinterface-asyncCall]: https://doc.qt.io/qt-5/qdbusabstractinterface.html#asyncCall-1
[qcoro-coro]: ../coro/coro.md
qcoro-0.11.0/docs/reference/network/ 0000775 0000000 0000000 00000000000 14677722710 0017310 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/network/index.md 0000664 0000000 0000000 00000000714 14677722710 0020743 0 ustar 00root root 0000000 0000000
# Network Module
The `Network` module contains coroutine-friendly wrapper for
[QtNetwork][qtdoc-qtnetwork] classes.
## CMake Usage
```
find_package(QCoro6 COMPONENTS Network)
...
target_link_libraries(my-target QCoro::Network)
```
## QMake Usage
```
QT += QCoroNetwork
```
[qtdoc-qtnetwork]: https://doc.qt.io/qt-5/qtnetwork-index.html
qcoro-0.11.0/docs/reference/network/qabstractsocket.md 0000664 0000000 0000000 00000010642 14677722710 0023032 0 ustar 00root root 0000000 0000000
# QAbstractSocket
{{ doctable("Network", "QCoroAbstractSocket", ("core/qiodevice", "QCoroIODevice")) }}
[`QAbstractSocket`][qtdoc-qabstractsocket] is a base class for [`QTcpSocket`][qtdoc-qtcpsocket]
and [`QUdpSocket`][qtdoc-qudpsocket] and has some potentially asynchronous operations.
In addition to reading and writing, which are provided by [`QIODevice`][qtdoc-qiodevice]
baseclass and can be used with coroutines thanks to QCoro's [`QCoroIODevice`][qcoro-qcoroiodevice]
it provides asynchronous waiting for connecting to and disconnecting from the server.
Since `QAbstractSocket` doesn't provide the ability to `co_await` those operations, QCoro provides
a wrapper calss `QCoroAbstractSocket`. To wrap a `QAbstractSocket` object into the `QCoroAbstractSocket`
wrapper, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroAbstractSocket qCoro(QAbstractSocket &);
QCoroAbstractSocket qCoro(QAbstractSocket *);
```
Same as `QAbstractSocket` is a subclass of `QIODevice`, `QCoroAbstractSocket` subclasses
[`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected
`QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details.
## `waitForConnected()`
Waits for the socket to connect or until it times out. Returns `bool` indicating whether
connection has been established (`true`) or whether the operation has timed out or another
error has occurred (`false`). Returns immediatelly when the socket is already in connected
state.
If the timeout is -1, the operation will never time out.
See documentation for [`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected]
for details.
```cpp
QCoro::Task QCoroAbstractSocket::waitForConnected(int timeout_msecs = 30'000);
QCoro::Task QCoroAbstractSocket::waitForConnected(std::chrono::milliseconds timeout);
```
## `waitForDisconnected()`
Waits for the socket to disconnect from the server or until the operation times out.
Returns immediatelly if the socket is not in connected state.
If the timeout is -1, the operation will never time out.
See documentation for [`QAbstractSocket::waitForDisconnected()`][qtdoc-qabstractsocket-waitForDisconnected]
for details.
```cpp
QCoro::Task QCoroAbstractSocket::waitForDisconnected(timeout_msecs = 30'000);
QCoro::Task QCoroAbstractSocket::waitForDisconnected(std::chrono::milliseconds timeout);
```
## `connectToHost()`
`QCoroAbstractSocket` provides an additional method called `connectToHost()` which is equivalent
to calling `QAbstractSocket::connectToHost()` followed by `QAbstractSocket::waitForConnected()`. This
operation is co_awaitable as well.
If the timeout is -1, the operation will never time out.
See the documentation for [`QAbstractSocket::connectToHost()`][qtdoc-qabstractsocket-connectToHost] and
[`QAbstractSocket::waitForConnected()`][qtdoc-qabstractsocket-waitForConnected] for details.
```cpp
QCoro::Task QCoroAbstractSocket::connectToHost(const QHostAddress &address, quint16 port,
QIODevice::OpenMode openMode = QIODevice::ReadOnly,
std::chrono::milliseconds timeout = std::chrono::seconds(30));
QCoro::Task QCoroAbstractSocket::connectToHost(const QString &hostName, quint16 port,
QIODevice::OpenMode openMode = QIODevice::ReadOnly,
QAbstractSocket::NetworkLayerProtocol protocol = QAbstractSocket::AnyIPProtocol,
std::chrono::milliseconds timeout = std::chrono::seconds(30));
```
## Examples
```cpp
{% include "../../examples/qtcpsocket.cpp" %}
```
[qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html
[qtdoc-qtcpsocket]: https://doc.qt.io/qt-5/qtcpsocket.html
[qtdoc-qudpsocket]: https://doc.qt.io/qt-5/qudpsocket.html
[qtdoc-qabstractsocket]: https://doc.qt.io/qt-5/qabstractsocket.html
[qtdoc-qabstractsocket-connectToServer]: https://doc.qt.io/qt-5/qabstractsocket.html#connectToServer
[qtdoc-qabstractsocket-waitForConnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForConnected
[qtdoc-qabstractsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qabstractsocket.html#waitForDisconnected
[qcoro-coro]: ../coro/coro.md
[qcoro-qcoroiodevice]: ../core/qiodevice.md
qcoro-0.11.0/docs/reference/network/qlocalsocket.md 0000664 0000000 0000000 00000010257 14677722710 0022323 0 ustar 00root root 0000000 0000000
# QLocalSocket
{{ doctable("Network", "QCoroLocalSocket", ("core/qiodevice", "QCoroIODevice")) }}
[`QLocalSocket`][qtdoc-qlocalsocket] has several potentially asynchronous operations
in addition to reading and writing, which are provided by [`QIODevice`][qtdoc-qiodevice]
baseclass and can be used with coroutines thanks to QCoro's [`QCoroIODevice`][qcoro-qcoroiodevice].
Those operations are connecting to and disconnecting from the server.
Since `QLocalSocket` doesn't provide the ability to `co_await` those operations, QCoro provides
a wrapper calss `QCoroLocalSocket`. To wrap a `QLocalSocket` object into the `QCoroLocalSocket`
wrapper, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroLocalSocket qCoro(QLocalSocket &);
QCoroLocalSocket qCoro(QLocalSocket *);
```
Same as `QLocalSocket` is a subclass of `QIODevice`, `QCoroLocalSocket` subclasses
[`QCoroIODevice`][qcoro-qcoroiodevice], so it also provides the awaitable interface for selected
`QIODevice` functions. See [`QCoroIODevice`][qcoro-qcoroiodevice] documentation for details.
## `waitForConnected()`
Waits for the socket to connect or until it times out. Returns `bool` indicating whether
connection has been established (`true`) or whether the operation has timed out or another
error has occurred (`false`). Returns immeditelly if the socket is already in connected
state.
If the timeout is -1, the operation will never time out.
See documentation for [`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected]
for details.
```cpp
QCoro::Task QCoroLocalSocket::waitForConnected(int timeout_msecs = 30'000);
QCoro::Task QCoroLocalSocket::waitForConnected(std::chrono::milliseconds timeout);
```
## `waitForDisconnected()`
Waits for the socket to disconnect from the server or until the operation times out.
Returns immediatelly if the socket is not in connected state.
If the timeout is -1, the operation will never time out.
See documentation for [`QLocalSocket::waitForDisconnected()`][qtdoc-qlocalsocket-waitForDisconnected]
for details.
```cpp
QCoro::Task QCoroLocalSocket::waitForDisconnected(timeout_msecs = 30'000);
QCoro::Task QCoroLocalSocket::waitForDisconnected(std::chrono::milliseconds timeout);
```
## `connectToServer()`
`QCoroLocalSocket` provides an additional method called `connectToServer()` which is equivalent
to calling `QLocalSocket::connectToServer()` followed by `QLocalSocket::waitForConnected()`. This
operation is co_awaitable as well.
If the timeout is -1, the operation will never time out.
See the documentation for [`QLocalSocket::connectToServer()`][qtdoc-qlocalsocket-connectToServer] and
[`QLocalSocket::waitForConnected()`][qtdoc-qlocalsocket-waitForConnected] for details.
```cpp
QCoro::Task QCoroLocalSocket::connectToServer(QIODevice::OpenMode openMode = QIODevice::ReadOnly,
std::chrono::milliseconds timeout = std::chrono::seconds(30));
QCoro::Taks QCoroLocalSocket::connectToServer(const QString &name, QIODevice::OpenMode openMode = QIODevice::ReadOnly,
std::chrono::milliseconds timeout = std::chrono::seconds(30));
```
## Examples
```cpp
QCoro::Task requestDataFromServer(const QString &serverName) {
QLocalSocket socket;
if (!co_await qCoro(socket).connectToServer(serverName)) {
qWarning() << "Failed to connect to the server";
co_return QByteArray{};
}
socket.write("SEND ME DATA!");
QByteArray data;
while (!data.endsWith("\r\n.\r\n")) {
data += co_await qCoro(socket).readAll();
}
co_return data;
}
```
[qtdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html
[qtdoc-qlocalsocket]: https://doc.qt.io/qt-5/qlocalsocket.html
[qtdoc-qlocalsocket-connectToServer]: https://doc.qt.io/qt-5/qlocalsocket.html#connectToServer
[qtdoc-qlocalsocket-waitForConnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForConnected
[qtdoc-qlocalsocket-waitForDisconnected]: https://doc.qt.io/qt-5/qlocalsocket.html#waitForDisconnected
[qcoro-coro]: ../coro/coro.md
[qcoro-qcoroiodevice]: ../core/qiodevice.md
qcoro-0.11.0/docs/reference/network/qnetworkreply.md 0000664 0000000 0000000 00000002527 14677722710 0022566 0 ustar 00root root 0000000 0000000
# QNetworkReply
{{ doctable("Network", "QCoroNetworkReply", ("network/qiodevice", "QCoroIODevice")) }}
[`QNetworkReply`][qdoc-qnetworkreply] has two asynchronous aspects: one is waiting for the
reply to finish, and one for reading the response data as they arrive. QCoro supports both.
`QNetworkReply` is a subclass of [`QIODevice`][qdoc-qiodevice], so you can leverage all the
features of [`QCoroIODevice`][qcoro-iodevice] to asynchronously read data from the underlying
`QIODevice` using coroutines.
To wait for the reply to finish, one can simply `co_await` the reply object:
```cpp
QNetworkAccessManager nam;
auto *reply = co_await nam.get(request);
```
The QCoro frameworks allows `co_await`ing on [QNetworkReply][qdoc-qnetworkreply] objects. The
co-awaiting coroutine is suspended, until [`QNetworkReply::finished()`][qdoc-qnetworkreply-finished]
signal is emitted.
To make it work, include `QCoroNetworkReply` in your implementation.
```cpp
{% include "../../examples/qnetworkreply.cpp" %}
```
[qdoc-qnetworkreply]: https://doc.qt.io/qt-5/qnetworkreply.html
[qdoc-qnetworkreply-finished]: https://doc.qt.io/qt-5/qnetworkreply.html#finished
[qdoc-qiodevice]: https://doc.qt.io/qt-5/qiodevice.html
[qcoro-iodevice]: ../core/qiodevice.md
qcoro-0.11.0/docs/reference/network/qtcpserver.md 0000664 0000000 0000000 00000002664 14677722710 0022040 0 ustar 00root root 0000000 0000000
# QTcpServer
{{ doctable("Network", "QCoroTcpServer") }}
[`QTcpServer`][qtdoc-qtcpserver] really only has one asynchronous operation worth `co_await`ing, and that's
`waitForNewConnection()`.
Since `QTcpServer` doesn't provide the ability to `co_await` those operations, QCoro provides
a wrapper class `QCoroTcpServer`. To wrap a `QTcpServer` object into the `QCoroTcpServer`
wrapper, use [`qCoro()`][qcoro-coro]:
```cpp
QCoroTcpServer qCoro(QTcpServer &);
QCoroTcpServer qCoro(QTcpServer *);
```
## `waitForNewConnection()`
Waits until a new incoming connection is available or until it times out. Returns pointer to `QTcpSocket` or
`nullptr` if the operation timed out or another error has occured.
If the timeout is -1 the operation will never time out.
See documentation for [`QTcpServer::waitForNewConnection()`][qtdoc-qtcpserver-waitForNewConnection]
for details.
```cpp
QCoro::Task QCoroTcpServer::waitForNewConnection(int timeout_msecs = 30'000);
QCoro::Task QCoroTcpServer::waitForNewConnection(std::chrono::milliseconds timeout);
```
## Examples
```cpp
{% include "../../examples/qtcpserver.cpp" %}
```
[qtdoc-qtcpserver]: https://doc.qt.io/qt-5/qtcpserver.html
[qtdoc-qtcpserver-waitForNewConnection]: https://doc.qt.io/qt-5/qtcpserver.html#waitForNewConnection
[qcoro-coro]: ../coro/coro.md
qcoro-0.11.0/docs/reference/qml/ 0000775 0000000 0000000 00000000000 14677722710 0016410 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/qml/index.md 0000664 0000000 0000000 00000001173 14677722710 0020043 0 ustar 00root root 0000000 0000000
# QML Module
The `QML` module contains coroutine-friendly wrappers for
[QtQml][qtdoc-qml] classes.
## CMake Usage
```cmake
find_package(QCoro6 COMPONENTS Qml)
...
target_link_libraries(my-target QCoro::Qml)
```
## QMake Usage
```
QT += QCoroQml
```
## Type registration
To use types defined in QCoroQml, you need to call the `QCoro::Qml::registerTypes` function before loading the QML.
```C++
int main() {
...
QCoro::Qml::registerTypes();
...
}
```
[qtdoc-qml]: https://doc.qt.io/qt-5/qml-index.html
qcoro-0.11.0/docs/reference/qml/qmltask.md 0000664 0000000 0000000 00000002367 14677722710 0020416 0 ustar 00root root 0000000 0000000
# QmlTask
{{ doctable("Qml", "QCoroQmlTask") }}
QmlTask allows to return [QCoro::Task][qcoro-task]s directly to QML.
It can be constructed from any [QCoro::Task][qcoro-task] that returns a value that can be converted to a [QVariant][qdoc-qml].
```C++
#include
#include
int main()
{
...
qmlRegisterType("io.me.qmlmodule", 1, 0, "Example");
QCoro::Qml::registerTypes();
...
}
class Example : public QObject
{
Q_OBJECT
...
public:
Q_INVOKABLE QCoro::QmlTask fetchValue(const QString &name) const
{
return database->fetchValue(name);
// Returns QCoro::Task
}
}
```
QmlTasks can call a JavaScript function when they complete:
```QML
Example {
Component.onCompleted: {
fetchValue("key").then((result) => console.log("Result", result))
}
}
```
They can also set properties when the value is available:
```QML
import QCoro 0
import io.me.qmlmodule 1.0
Item {
Example { id: store }
Label {
text: store.fetchValue("key").await().value
}
}
```
[qdoc-qml]: https://doc.qt.io/qt-5/qvariant.html
[qcoro-task]: ../coro/task.md
qcoro-0.11.0/docs/reference/quick/ 0000775 0000000 0000000 00000000000 14677722710 0016733 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/quick/imageprovider.md 0000664 0000000 0000000 00000001555 14677722710 0022120 0 ustar 00root root 0000000 0000000
# ImageProvider
{{ doctable("Quick", "QCoroImageProvider") }}
Coroutines based implementation of [`QQuickImaqeProvider`][qdoc-imageprovider].
To use `QCoro::ImageProvider`, you need to create a subclass of it, and implement the `asyncRequestImage` function, as shown in the example below:
```cpp
#include
class IconImageProvider : public QCoro::ImageProvider
{
public:
QCoro::Task asyncRequestImage(const QString &id, const QSize &) override;
};
```
The subclass [can be registered with a `QQmlEngine`][qdoc-addimageprovider] like any `QQuickImageProvider` subclass.
[qdoc-addimageprovider]: https://doc.qt.io/qt-5/qqmlengine.html#addImageProvider
[qdoc-imageprovider]: https://doc.qt.io/qt-5/qquickimageprovider.html
qcoro-0.11.0/docs/reference/quick/index.md 0000664 0000000 0000000 00000000674 14677722710 0020373 0 ustar 00root root 0000000 0000000
# Quick Module
The `Quick` module contains coroutine-friendly wrappers for
[QtQuick][qtdoc-qtquick] classes.
## CMake Usage
```cmake
find_package(QCoro6 COMPONENTS Quick)
...
target_link_libraries(my-target QCoro::Quick)
```
## QMake Usage
```
QT += QCoroQuick
```
[qtdoc-qtquick]: https://doc.qt.io/qt-5/qtquick-index.html
qcoro-0.11.0/docs/reference/test/ 0000775 0000000 0000000 00000000000 14677722710 0016576 5 ustar 00root root 0000000 0000000 qcoro-0.11.0/docs/reference/test/index.md 0000664 0000000 0000000 00000005256 14677722710 0020237 0 ustar 00root root 0000000 0000000
# Test Module
The `Test` module contains coroutine-friendly versions of tests macros
from the [QtTest][qdoc-qtest] module.
## CMake Usage
```cmake
find_package(QCoro6 COMPONENTS Test)
...
target_link_libraries(my-target QCoro::Test)
```
## QMake Usage
```
QT += QCoroTest
```
## Test Macros
The module contains a bunch of test macros that behave identically to their QtTest
counterparts, the only difference being that they can be used inside a coroutine.
```cpp
#include