From 33a5939c1815df15d4d76f0a53b4deaa18c1a5ce Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Tue, 12 Mar 2024 16:39:44 +0200 Subject: [PATCH] Automatic crash reports. --- .cirrus.yml | 42 ++- .gitignore | 13 +- ChangeLog.txt | 1 + appveyor.yml | 63 ++++- conanfile.py | 20 +- qbs/imports/VDynamicLib.qbs | 5 + qbs/imports/VToolApp.qbs | 5 + qbs/module-providers/conan/provider.qbs | 32 ++- qbs/module-providers/conan/utils.js | 65 +++++ qbs/modules/buildconfig/buildconfig.qbs | 48 ++-- qbs/modules/vcs2/vcs2.qbs | 10 +- scripts/cppcheck.sh | 23 -- scripts/make_install.bat | 75 ------ scripts/sign_mac_bundle.sh | 27 -- scripts/symupload.py | 158 +++++++++++ .../puzzlepreferencesconfigurationpage.cpp | 31 +++ .../puzzlepreferencesconfigurationpage.h | 2 + .../puzzlepreferencesconfigurationpage.ui | 73 ++++- src/app/puzzle/main.cpp | 9 + src/app/puzzle/puzzle.qbs | 8 +- src/app/puzzle/vpmainwindow.cpp | 7 +- .../tapepreferencesconfigurationpage.cpp | 31 +++ .../tapepreferencesconfigurationpage.h | 2 + .../tapepreferencesconfigurationpage.ui | 71 ++++- src/app/tape/main.cpp | 9 + src/app/tape/tape.qbs | 14 +- src/app/tape/tkmmainwindow.cpp | 7 +- src/app/tape/tmainwindow.cpp | 7 +- src/app/tape/version.h | 10 +- src/app/valentina/core/vapplication.cpp | 81 +----- src/app/valentina/core/vapplication.h | 3 - .../preferencesconfigurationpage.cpp | 31 +++ .../preferencesconfigurationpage.h | 2 + .../preferencesconfigurationpage.ui | 73 ++++- src/app/valentina/main.cpp | 10 +- src/app/valentina/mainwindow.cpp | 7 +- src/app/valentina/valentina.qbs | 8 +- src/libs/ifc/ifc.qbs | 13 +- src/libs/vmisc/crashhandler/crashhandler.cpp | 251 ++++++++++++++++++ src/libs/vmisc/crashhandler/crashhandler.h | 35 +++ src/libs/vmisc/crashhandler/vcrashpaths.cpp | 91 +++++++ src/libs/vmisc/crashhandler/vcrashpaths.h | 56 ++++ .../dialogs/dialogaskcollectstatistic.cpp | 30 ++- .../vmisc/dialogs/dialogaskcollectstatistic.h | 4 +- .../dialogs/dialogaskcollectstatistic.ui | 84 +++++- src/libs/vmisc/projectversion.h | 2 +- src/libs/vmisc/vabstractapplication.cpp | 75 ++++++ src/libs/vmisc/vabstractapplication.h | 4 + src/libs/vmisc/vcommonsettings.cpp | 52 ++++ src/libs/vmisc/vcommonsettings.h | 9 + src/libs/vmisc/vmisc.qbs | 22 ++ .../vpropertyexplorer/vpropertyexplorer.qbs | 1 + src/test/CollectionTest/CollectionTest.qbs | 10 +- .../TranslationsTest/TranslationsTest.qbs | 11 +- valentina.qbs | 15 +- 55 files changed, 1498 insertions(+), 350 deletions(-) create mode 100644 qbs/module-providers/conan/utils.js delete mode 100755 scripts/cppcheck.sh delete mode 100644 scripts/make_install.bat delete mode 100644 scripts/sign_mac_bundle.sh create mode 100644 scripts/symupload.py create mode 100644 src/libs/vmisc/crashhandler/crashhandler.cpp create mode 100644 src/libs/vmisc/crashhandler/crashhandler.h create mode 100644 src/libs/vmisc/crashhandler/vcrashpaths.cpp create mode 100644 src/libs/vmisc/crashhandler/vcrashpaths.h diff --git a/.cirrus.yml b/.cirrus.yml index ae67b422f..63ba11839 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -125,10 +125,12 @@ linux_qt5_qbs_task_template: &LINUX_QT5_QBS_TASK_TEMPLATE appimage_task_template: &APPIMAGE_TASK_TEMPLATE pip_cache: folder: ${PIP_CACHE_DIR} + conan_cache: + folder: "~/.conan/data" install_script: - bash -c "$PACKAGE_MANAGER_INSTALL qt515base qt515svg qt515tools qt515xmlpatterns qt515translations qt515doc qt515imageformats poppler-utils git xvfb ccache build-essential libgl1-mesa-dev libicu-dev python3-pip" - python3 --version - - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' + - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' conan==1.63.0 build_script: - uname -a - mkdir -pm 0700 $XDG_RUNTIME_DIR @@ -143,12 +145,25 @@ appimage_task_template: &APPIMAGE_TASK_TEMPLATE - ${COMPILER} --version - qmake --version - qbs --version + - conan profile new valentina + - conan profile update settings.build_type=Release valentina + - conan profile update settings.os=Linux valentina + - conan profile update settings.compiler=gcc valentina + - conan profile update settings.compiler.cppstd=17 valentina + - conan profile update settings.compiler.libcxx=libstdc++11 valentina + - conan profile update settings.compiler.version=9 valentina - qbs setup-toolchains /usr/bin/${COMPILER} ${COMPILER} - qbs setup-qt /opt/qt515/bin/qmake qt5 - qbs config defaultProfile qt5 - qbs config profiles.qt5.baseProfile ${COMPILER} - - qbs build -f valentina.qbs -d $CIRRUS_WORKING_DIR/build --jobs $(nproc) profile:qt5 config:release modules.buildconfig.enableCcache:${ENABLE_CCACHE} qbs.installRoot:$CIRRUS_WORKING_DIR/build/AppDir modules.buildconfig.enableAppImage:true modules.buildconfig.enableRPath:false + - conan install . -s os=Linux --build=missing -o with_crash_reporting=True -pr valentina -g virtualrunenv + - qbs build -f valentina.qbs -d $CIRRUS_WORKING_DIR/build --jobs $(nproc) profile:qt5 config:release modules.buildconfig.enableCcache:${ENABLE_CCACHE} qbs.installRoot:$CIRRUS_WORKING_DIR/build/AppDir modules.buildconfig.enableAppImage:true modules.buildconfig.enableRPath:false project.conanWithCrashReporting:true project.enableConan:true - qbs -p autotest-runner -d build profile:qt5 config:release + - export CRASH_QT_VERSION=$(/opt/qt515/bin/qmake -query QT_VERSION | awk -F. '{print $1 "_" $2}') + - export CRASH_SHORT_SHA=$(git log --pretty=format:%h -n 1) + - source activate_run.sh + - python3 scripts/symupload.py $CIRRUS_WORKING_DIR/build/AppDir $VALENTINA_VERSION $CRASH_SHORT_SHA $CRASH_QT_VERSION --clean + - source deactivate_run.sh - appimage-builder --recipe dist/AppImage/AppImageBuilder.yml --appdir $CIRRUS_WORKING_DIR/build/AppDir --skip-test - ccache -s deploy_script: @@ -228,6 +243,7 @@ linux_task: QT_VERSION: Qt6 ARCH: x86_64 TARGET_PLATFORM: "Linux" + VALENTINA_VERSION: 0_7_52 container: cpu: 4 memory: 8G # Set to 8GB to avoid OOM. https://cirrus-ci.org/guide/linux/#linux-container @@ -350,16 +366,11 @@ macos_task_template: &MACOS_TASK_TEMPLATE - chmod -R 755 /opt/homebrew/opt/poppler/* - chmod -R 755 /opt/homebrew/opt/xerces-c/* - python3 --version - - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' + - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' conan==1.63.0 - ccache --set-config sloppiness=pch_defines,time_macros max_size="$CCACHE_SIZE" - qmake --version - which qmake - qbs --version - # Patch Qbs. Remove after Qbs 2.2.1+. - - curl https://gist.githubusercontent.com/dismine/43f3c51e05f3317c5d4fe16cd3c4b6d8/raw/2d297bcb53c2c022f740509923adf1eb1796afe2/qbs-pkg-config-probe.patch --output $HOME/qbs-pkg-config-probe.patch --silent - - patch -N -d $(brew --prefix qbs)/ -p1 < $HOME/qbs-pkg-config-probe.patch || true - - rm -f $(brew --prefix qbs)/share/qbs/imports/qbs/Probes/qbs-pkg-config-probe.js.rej - - rm $HOME/qbs-pkg-config-probe.patch build_script: - echo $PATH - export PATH="${HOME}/.local/bin:`python3 -m site --user-base`/bin:$PATH" @@ -373,12 +384,26 @@ macos_task_template: &MACOS_TASK_TEMPLATE - unzip ${HOME}/macdeployqt-main.zip -d ${HOME} - cmake ${HOME}/macdeployqt-main -GNinja -S ${HOME}/macdeployqt-main -B ${HOME}/macdeployqt-build-dir -DCMAKE_INSTALL_PREFIX=${HOME}/macdeployqt-install-dir -DCMAKE_BUILD_TYPE=Release - cmake --build ${HOME}/macdeployqt-build-dir --target install + - conan profile new valentina + - conan profile update settings.build_type=Release valentina + - conan profile update settings.os=Macos valentina + - conan profile update settings.os.Macos.sdk_version=${MACOS_DEPLOYMENT_TARGET} valentina + - conan profile update settings.compiler=clang valentina + - conan profile update settings.compiler.cppstd=17 valentina + - conan profile update settings.compiler.libcxx=libstdc++11 valentina + - conan profile update settings.compiler.version=14 valentina - qbs setup-toolchains --detect - qbs config --list profiles - qbs setup-qt /opt/homebrew/opt/qt6/bin/qmake qt6 - qbs config defaultProfile qt6 - qbs config profiles.qt6.baseProfile clang + - conan install . -s os=Macos --build=missing -o with_crash_reporting=True -pr valentina -g virtualrunenv - qbs build -f valentina.qbs -d $CIRRUS_WORKING_DIR/build --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:$CIRRUS_WORKING_DIR/build/install-root profile:qt6 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:${ENABLE_CCACHE} moduleProviders.qbspkgconfig.extraPaths:$(brew --prefix xerces-c)/lib/pkgconfig,$(brew --prefix qt6)/lib/pkgconfig,$(brew --prefix openssl@1.1)/lib/pkgconfig "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:$(brew --prefix qt6)/lib,$(brew --prefix poppler)/lib modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir + - export CRASH_QT_VERSION=$(/opt/homebrew/opt/qt6/bin/qmake -query QT_VERSION | awk -F. '{print $1 "_" $2}') + - export CRASH_SHORT_SHA=$(git log --pretty=format:%h -n 1) + - source activate_run.sh + - python3 scripts/symupload.py $CIRRUS_WORKING_DIR/build/install-root $VALENTINA_VERSION $CRASH_SHORT_SHA $CRASH_QT_VERSION --clean + - source deactivate_run.sh - qbs build -f valentina.qbs -d $CIRRUS_WORKING_DIR/build -p 'Valentina DMG' --force-probe-execution --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:$CIRRUS_WORKING_DIR/build/install-root profile:qt6 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:${ENABLE_CCACHE} moduleProviders.qbspkgconfig.extraPaths:$(brew --prefix xerces-c)/lib/pkgconfig,$(brew --prefix qt6)/lib/pkgconfig,$(brew --prefix openssl@1.1)/lib/pkgconfig "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:$(brew --prefix qt6)/lib,$(brew --prefix poppler)/lib modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir # Store the notarization credentials so that we can prevent a UI password dialog # from blocking the CI @@ -412,6 +437,7 @@ macos_task: TARGET_PLATFORM: "macOS_12.4+" MACOS_DEPLOYMENT_TARGET: 12.0 ENABLE_CCACHE: true + VALENTINA_VERSION: 0_7_52 matrix: - name: 'macOS Monterey 12 [signle bundle, no tests]' env: diff --git a/.gitignore b/.gitignore index f8f604d81..8d187fb0d 100644 --- a/.gitignore +++ b/.gitignore @@ -73,7 +73,6 @@ cov-int/ *.la *.a *.lib -*.lo # Executables *.exe @@ -117,7 +116,6 @@ Makefile* *.xcodeproj # OSX specific -.DS_Store .AppleDouble .LSOverride @@ -173,3 +171,14 @@ __pycache__ # Sonar Cloud .scannerwork bw-output + +# Conan +/activate_run.ps1 +/activate_run.sh +/conanbuildinfo.txt +/conaninfo.txt +/deactivate_run.ps1 +/deactivate_run.sh +/environment_run.ps1.env +/environment_run.sh.env +/graph_info.json diff --git a/ChangeLog.txt b/ChangeLog.txt index b690d2377..90b8b122d 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -64,6 +64,7 @@ - Fold line. - Minimal Qt version increased to Qt 5.15. Minimal C++ standard to C++17. - Updated Windows installer. +- Automatic crash reports. # Valentina 0.7.52 September 12, 2022 - Fix crash when default locale is ru. diff --git a/appveyor.yml b/appveyor.yml index 24afef3d6..1e1bb0f77 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -40,6 +40,7 @@ environment: ACCESS_TOKEN: secure: RUhnEHqaR8KhalOMWwZZOoO342Ja50QV4KpEWdm9g3pG+jG7i6aJqUmeKF1l5VN6dzksk1u+yN6pOLnU8oGcaVQ6v+1dpKK1oZvF0tyHhNE= APPVEYOR_SAVE_CACHE_ON_ERROR: "true" + VALENTINA_VERSION: 0_7_52 matrix: - job_name: Windows_Qt_6_5_(MSVC_x64) @@ -398,15 +399,26 @@ for: build_script: - conan profile list - - conan install . -s os=Windows --build=missing -pr valentina - - qbs build -f valentina.qbs -d %APPVEYOR_BUILD_FOLDER%\build --jobs %NUMBER_OF_PROCESSORS% config:release qbs.installRoot:%APPVEYOR_BUILD_FOLDER%\build\install-root\valentina profile:qt6 project.enableConan:true modules.buildconfig.enableCcache:false project.conanProfiles:valentina modules.buildconfig.enablePCH:%ENABLE_PCH% modules.windeployqt.windeployqtProgramBinPath:%WINDEPLOYQT_BIN_PATH% modules.windeployqt.compilerRuntime:%WINDEPLOYQT_COMPILER_RUNTIME% modules.windeployqt.noCompilerRuntime:%WINDEPLOYQT_NO_COMPILER_RUNTIME% + - conan install . -s os=Windows --build=missing -o with_crash_reporting=True -o with_xerces=True -pr valentina -g virtualrunenv + - qbs build -f valentina.qbs -d %APPVEYOR_BUILD_FOLDER%\build --jobs %NUMBER_OF_PROCESSORS% config:release qbs.installRoot:%APPVEYOR_BUILD_FOLDER%\build\install-root\valentina profile:qt6 project.enableConan:true project.conanWithCrashReporting:true project.conanWithXerces:true modules.buildconfig.enableCcache:false project.conanProfiles:valentina modules.buildconfig.enablePCH:%ENABLE_PCH% modules.windeployqt.windeployqtProgramBinPath:%WINDEPLOYQT_BIN_PATH% modules.windeployqt.compilerRuntime:%WINDEPLOYQT_COMPILER_RUNTIME% modules.windeployqt.noCompilerRuntime:%WINDEPLOYQT_NO_COMPILER_RUNTIME% test_script: - path - if "%RUN_TESTS%" == "true" (qbs -p autotest-runner -d %APPVEYOR_BUILD_FOLDER%\build profile:qt6 config:release) deploy_script: - - if "%DEPLOY%" == "true" (qbs build -f valentina.qbs -d %APPVEYOR_BUILD_FOLDER%\build -p ValentinaSetup --jobs %NUMBER_OF_PROCESSORS% config:release qbs.installRoot:%APPVEYOR_BUILD_FOLDER%\build\install-root\valentina profile:qt6 project.enableConan:true modules.buildconfig.enableCcache:false project.conanProfiles:valentina modules.buildconfig.enablePCH:%ENABLE_PCH% modules.windeployqt.windeployqtProgramBinPath:%WINDEPLOYQT_BIN_PATH% modules.windeployqt.compilerRuntime:%WINDEPLOYQT_COMPILER_RUNTIME% modules.windeployqt.noCompilerRuntime:%WINDEPLOYQT_NO_COMPILER_RUNTIME%) + - ps: | + if ($env:DEPLOY -eq "true") { + $qmakeOutput = %QTDIR%\bin\%QMAKE% -query QT_VERSION + $majorMinorVersion = $qmakeOutput -replace '\D+(\d+)\.(\d+).*', '$1_$2' + $env:CRASH_QT_VERSION = $majorMinorVersion + + $env:CRASH_SHORT_SHA = git log --pretty=format:%h -n 1 + } + - ps: activate_run.ps1 + - if "%DEPLOY%" == "true" (python3 scripts/symupload.py %APPVEYOR_BUILD_FOLDER%\build\install-root\valentina $VALENTINA_VERSION $CRASH_SHORT_SHA $CRASH_QT_VERSION --clean) + - ps: deactivate_run.ps1 + - if "%DEPLOY%" == "true" (qbs build -f valentina.qbs -d %APPVEYOR_BUILD_FOLDER%\build -p ValentinaSetup --jobs %NUMBER_OF_PROCESSORS% config:release qbs.installRoot:%APPVEYOR_BUILD_FOLDER%\build\install-root\valentina profile:qt6 project.enableConan:true project.conanWithCrashReporting:true project.conanWithXerces:true modules.buildconfig.enableCcache:false project.conanProfiles:valentina modules.buildconfig.enablePCH:%ENABLE_PCH% modules.windeployqt.windeployqtProgramBinPath:%WINDEPLOYQT_BIN_PATH% modules.windeployqt.compilerRuntime:%WINDEPLOYQT_COMPILER_RUNTIME% modules.windeployqt.noCompilerRuntime:%WINDEPLOYQT_NO_COMPILER_RUNTIME%) - ps: scripts/appveyor-deploy.ps1 on_finish: @@ -537,7 +549,7 @@ for: secure: B8yHPBym+BTDPK5ZCg7WlSnUCHLbcim8WqLTC6/PSNs= cache: - #- /Users/appveyor/.conan/data -> conanfile.py + - /Users/appveyor/.conan/data -> conanfile.py - $HOME/brew_cache_dir init: @@ -643,7 +655,7 @@ for: fi - sudo python3 -m pip install --upgrade pip - - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' + - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' conan==1.63.0 - export QTDIR=`$(brew --prefix qt6)` - export PATH="$PATH:`python3 -m site --user-base`/bin:$QTDIR/bin" - echo $PATH @@ -663,13 +675,28 @@ for: build_script: - pwd + - git describe --always HEAD + - conan profile new valentina + - conan profile update settings.build_type=Release valentina + - conan profile update settings.os=Macos valentina + - conan profile update settings.os.Macos.sdk_version=${MACOS_DEPLOYMENT_TARGET} valentina + - conan profile update settings.compiler=clang valentina + - conan profile update settings.compiler.cppstd=17 valentina + - conan profile update settings.compiler.libcxx=libstdc++11 valentina + - conan profile update settings.compiler.version=15 valentina - qbs setup-toolchains --detect - qbs config --list profiles - qbs setup-qt $(brew --prefix qt6)/bin/qmake qt6 - qbs config defaultProfile qt6 - qbs config profiles.qt6.baseProfile clang - - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt6 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true moduleProviders.qbspkgconfig.extraPaths:$(brew --prefix xerces-c)/lib/pkgconfig,$(brew --prefix qt6)/lib/pkgconfig,$(brew --prefix openssl@1.1)/lib/pkgconfig "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:$(brew --prefix qt6)/lib,$(brew --prefix poppler)/lib modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir - - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build -p 'Valentina DMG' --force-probe-execution --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt6 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true moduleProviders.qbspkgconfig.extraPaths:$(brew --prefix xerces-c)/lib/pkgconfig,$(brew --prefix qt6)/lib/pkgconfig,$(brew --prefix openssl@1.1)/lib/pkgconfig "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:$(brew --prefix qt6)/lib,$(brew --prefix poppler)/lib modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir + - conan install . -s os=Macos --build=missing -o with_crash_reporting=True -pr valentina -g virtualrunenv + - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt6 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true moduleProviders.qbspkgconfig.extraPaths:$(brew --prefix xerces-c)/lib/pkgconfig,$(brew --prefix qt6)/lib/pkgconfig,$(brew --prefix openssl@1.1)/lib/pkgconfig "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:$(brew --prefix qt6)/lib,$(brew --prefix poppler)/lib modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir project.enableConan:true project.conanWithCrashReporting:true + - export CRASH_QT_VERSION=$($QTDIR/bin/qmake -query QT_VERSION | awk -F. '{print $1 "_" $2}') + - export CRASH_SHORT_SHA=$(git log --pretty=format:%h -n 1) + - source activate_run.sh + - python3 scripts/symupload.py ${APPVEYOR_BUILD_FOLDER}/build/install-root $VALENTINA_VERSION $CRASH_SHORT_SHA $CRASH_QT_VERSION --clean + - source deactivate_run.sh + - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build -p 'Valentina DMG' --force-probe-execution --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt6 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true moduleProviders.qbspkgconfig.extraPaths:$(brew --prefix xerces-c)/lib/pkgconfig,$(brew --prefix qt6)/lib/pkgconfig,$(brew --prefix openssl@1.1)/lib/pkgconfig "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:$(brew --prefix qt6)/lib,$(brew --prefix poppler)/lib modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir project.enableConan:true project.conanWithCrashReporting:true # Store the notarization credentials so that we can prevent a UI password dialog # from blocking the CI - echo "Create keychain profile" @@ -720,7 +747,7 @@ for: secure: B8yHPBym+BTDPK5ZCg7WlSnUCHLbcim8WqLTC6/PSNs= cache: - #- /Users/appveyor/.conan/data -> conanfile.py + - /Users/appveyor/.conan/data -> conanfile.py - $HOME/brew_cache_dir init: @@ -827,7 +854,7 @@ for: fi - sudo python3 -m pip install --upgrade pip - - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' + - pip3 install --user --upgrade pip dropbox py7zr 'urllib3<2.0' conan==1.63.0 - export PATH="`brew --prefix qbs`/bin:$PATH" - echo $PATH - clang --version @@ -846,13 +873,27 @@ for: build_script: - pwd + - conan profile new valentina + - conan profile update settings.build_type=Release valentina + - conan profile update settings.os=Macos valentina + - conan profile update settings.os.Macos.sdk_version=${MACOS_DEPLOYMENT_TARGET} valentina + - conan profile update settings.compiler=clang valentina + - conan profile update settings.compiler.cppstd=17 valentina + - conan profile update settings.compiler.libcxx=libstdc++11 valentina + - conan profile update settings.compiler.version=14 valentina - qbs setup-toolchains --detect - qbs config --list profiles - qbs setup-qt ${QTDIR}/bin/qmake qt5 - qbs config defaultProfile qt5 - qbs config profiles.qt5.baseProfile clang - - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt5 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:${QTDIR}/lib modules.macdeployqt.pluginspath:${QTDIR}/plugins modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir - - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build -p 'Valentina DMG' --force-probe-execution --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt5 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:${QTDIR}/lib modules.macdeployqt.pluginspath:${QTDIR}/plugins modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir + - conan install . -s os=Macos --build=missing -o with_crash_reporting=True -pr valentina -g virtualrunenv + - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt5 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:${QTDIR}/lib modules.macdeployqt.pluginspath:${QTDIR}/plugins modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir project.enableConan:true project.conanWithCrashReporting:true + - export CRASH_QT_VERSION=$($QTDIR/bin/qmake -query QT_VERSION | awk -F. '{print $1 "_" $2}') + - export CRASH_SHORT_SHA=$(git log --pretty=format:%h -n 1) + - source activate_run.sh + - python3 scripts/symupload.py ${APPVEYOR_BUILD_FOLDER}/build/install-root $VALENTINA_VERSION $CRASH_SHORT_SHA $CRASH_QT_VERSION --clean + - source deactivate_run.sh + - qbs build -f valentina.qbs -d ${APPVEYOR_BUILD_FOLDER}/build -p 'Valentina DMG' --force-probe-execution --jobs $(nproc) config:release modules.buildconfig.enableUnitTests:false modules.buildconfig.enableMultiBundle:${MULTI_BUNDLE} qbs.installRoot:${APPVEYOR_BUILD_FOLDER}/build/install-root profile:qt5 project.minimumMacosVersion:${MACOS_DEPLOYMENT_TARGET} modules.buildconfig.enableCcache:true "modules.buildconfig.signingIdentity:$MACOS_CERTIFICATE_NAME" modules.macdeployqt.libpath:${QTDIR}/lib modules.macdeployqt.pluginspath:${QTDIR}/plugins modules.macdeployqt.macdeployqtProgramBinPath:${HOME}/macdeployqt-install-dir project.enableConan:true project.conanWithCrashReporting:true # Store the notarization credentials so that we can prevent a UI password dialog # from blocking the CI - echo "Create keychain profile" diff --git a/conanfile.py b/conanfile.py index a1f797bae..131da430f 100644 --- a/conanfile.py +++ b/conanfile.py @@ -4,8 +4,16 @@ from conan import ConanFile class Recipe(ConanFile): settings = "os" - requires = "xerces-c/[~3.2]" - default_options = {"xerces-c/*:shared": True} + requires = "xerces-c/[~3.2]", "crashpad/cci.20220219", "breakpad/cci.20210521" + options = { + "with_xerces": [True, False], + "with_crash_reporting": [True, False] + } + default_options = { + "xerces-c/*:shared": True, + "with_xerces": False, + "with_crash_reporting": False + } def configure(self): if self.settings.os == "Linux": @@ -13,3 +21,11 @@ class Recipe(ConanFile): if self.settings.os == "Macos" and "MACOS_DEPLOYMENT_TARGET" in os.environ: self.settings.os.version = os.environ["MACOS_DEPLOYMENT_TARGET"] + + def requirements(self): + if not self.options.with_xerces: + del self.requires["xerces-c"] + + if not self.options.with_crash_reporting: + del self.requires["crashpad/cci.20220219"] + del self.requires["breakpad/cci.20210521"] diff --git a/qbs/imports/VDynamicLib.qbs b/qbs/imports/VDynamicLib.qbs index 405ce581b..6fe36fbe9 100644 --- a/qbs/imports/VDynamicLib.qbs +++ b/qbs/imports/VDynamicLib.qbs @@ -44,4 +44,9 @@ VLib { install: true installDir: buildconfig.installLibraryPath } + + Properties { + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + qbs.debugInformation: true + } } diff --git a/qbs/imports/VToolApp.qbs b/qbs/imports/VToolApp.qbs index 3a0d9aef0..de8d65b85 100644 --- a/qbs/imports/VToolApp.qbs +++ b/qbs/imports/VToolApp.qbs @@ -33,6 +33,11 @@ VApp { cpp.dynamicLibraries: ["icudata", "icui18n", "icuuc"] } + Properties { + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + qbs.debugInformation: true + } + Group { name: "Translations" condition: product.primaryApp || (qbs.targetOS.contains("macos") && (!bundle.isBundle || (bundle.isBundle && buildconfig.enableMultiBundle))) diff --git a/qbs/module-providers/conan/provider.qbs b/qbs/module-providers/conan/provider.qbs index 19ff4561a..5af10ce82 100644 --- a/qbs/module-providers/conan/provider.qbs +++ b/qbs/module-providers/conan/provider.qbs @@ -2,6 +2,8 @@ import qbs.File import qbs.FileInfo import qbs.TextFile +import "utils.js" as Utils + ModuleProvider { relativeSearchPaths: { var conanPackageDir = FileInfo.cleanPath(FileInfo.joinPaths(outputBaseDir, "../../..", "genconan")); @@ -25,8 +27,6 @@ ModuleProvider { file.close(); - console.info(JSON.stringify(fileContent)); - var deps = fileContent.dependencies; for(i in deps){ @@ -48,11 +48,11 @@ ModuleProvider { // module name can be invalid for Javascrip. Search for alternative names for cmake. var moduleName = deps[i].name; - if (deps[i].hasOwnProperty("names")) + if (!Utils.isValidAttributeName(moduleName) && deps[i].hasOwnProperty("names")) { if (deps[i].names.hasOwnProperty("cmake_find_package")) moduleName = deps[i].names.cmake_find_package; - else if (deps.names.hasOwnProperty("cmake_find_package_multi")) + else if (deps[i].names.hasOwnProperty("cmake_find_package_multi")) moduleName = deps[i].names.cmake_find_package_multi; } @@ -63,15 +63,18 @@ ModuleProvider { var moduleFile = new TextFile(FileInfo.joinPaths(moduleDir, moduleName + ".qbs"), TextFile.WriteOnly); var shared = false; - if (fileContent.options[deps[i].name].hasOwnProperty("shared")) + if (fileContent.options.hasOwnProperty(deps[i].name) && fileContent.options[deps[i].name].hasOwnProperty("shared")) { shared = (fileContent.options[deps[i].name].shared === 'True'); } var cppLibraries = shared ? "\tcpp.dynamicLibraries: " : "\tcpp.staticLibraries: "; + var cppLibrariesTag = shared ? "dynamiclibrary" : "staticlibrary"; + var cppLibrarySuffix = shared ? "cpp.dynamicLibrarySuffix" : "cpp.staticLibrarySuffix"; moduleFile.write("import qbs\n" + "Module {\n" + + "\tDepends { name: \"cpp\" }\n\n" + "\tproperty bool installBin: false\n" + "\tproperty bool installLib: false\n" + "\tproperty bool installRes: false\n" + @@ -81,10 +84,9 @@ ModuleProvider { "\tproperty string resInstallDir: \"res\"\n" + "\tproperty string includeInstallDir: \"include\"\n" + "\tproperty stringList binFilePatterns: [\"**/*\"]\n" + - "\tproperty stringList libFilePatterns: [\"**/*\"]\n" + + "\tproperty stringList libFilePatterns: [\"**/*\" + " + cppLibrarySuffix + "]\n" + "\tproperty stringList resFilePatterns: [\"**/*\"]\n" + "\tproperty stringList includeFilePatterns: [\"**/*\"]\n\n" + - "\tDepends { name: \"cpp\" }\n\n" + "\tcpp.includePaths: " + JSON.stringify(deps[i].include_paths) + "\n" + "\tcpp.systemIncludePaths: " + JSON.stringify(deps[i].include_paths) + "\n" + "\tcpp.libraryPaths: " + JSON.stringify(deps[i].lib_paths) + "\n" + @@ -94,9 +96,9 @@ ModuleProvider { function writeGroups(file, moduleName, prefix, pathList, install) { for(j in pathList) { file.write("\tGroup {\n" + - "\t\tname: \"" + prefix + (j > 0 ? j : "") + "\"\n" + - "\t\tprefix: \"" + FileInfo.fromNativeSeparators(pathList[j]) + "/\"\n" + - "\t\tfilesAreTargets: true\n"); + "\t\tname: \"" + prefix + (j > 0 ? j : "") + "\"\n" + + "\t\tprefix: \"" + FileInfo.fromNativeSeparators(pathList[j]) + "/\"\n" + + "\t\tfilesAreTargets: true\n"); if (install) file.write("\t\tqbs.install: product.conan." + moduleName + ".install" + (prefix.charAt(0).toUpperCase() + prefix.substring(1)) + "\n" + @@ -104,15 +106,19 @@ ModuleProvider { "\t\tqbs.installDir: product.conan." + moduleName + "." + prefix + "InstallDir\n" + "\t\tqbs.installSourceBase: \"" + FileInfo.fromNativeSeparators(pathList[j]) + "\"\n"); - file.write("\t\tfiles: product.conan." + moduleName + "." + prefix + "FilePatterns\n" + - "\t}\n"); + file.write("\t\tfiles: product.conan." + moduleName + "." + prefix + "FilePatterns\n"); + + if (prefix === "lib") + file.write("\t\tfileTags: [\"" + cppLibrariesTag + "\"]\n"); + + file.write("\t}\n"); } } writeGroups(moduleFile, moduleName, "bin", deps[i].bin_paths, true); writeGroups(moduleFile, moduleName, "lib", deps[i].lib_paths, shared); writeGroups(moduleFile, moduleName, "res", deps[i].res_paths, true); - writeGroups(moduleFile, moduleName, "include", deps[i].include_paths, shared); + writeGroups(moduleFile, moduleName, "include", Utils.filterUniqueRootPaths(deps[i].include_paths), shared); moduleFile.writeLine("}"); moduleFile.close(); diff --git a/qbs/module-providers/conan/utils.js b/qbs/module-providers/conan/utils.js new file mode 100644 index 000000000..52575c0bd --- /dev/null +++ b/qbs/module-providers/conan/utils.js @@ -0,0 +1,65 @@ +var FileInfo = require("qbs.FileInfo"); + +function isValidAttributeName(name) { + // Check if the name starts with a letter, underscore, or dollar sign + if (!/^[a-zA-Z_$]/.test(name.charAt(0))) { + return false; + } + + // Check for subsequent characters: letters, digits, underscores, or dollar signs + for (var i = 1; i < name.length; i++) { + if (!/^[0-9a-zA-Z_$]/.test(name.charAt(i))) { + return false; + } + } + + // Check if the name is a reserved word + const reservedWords = [ + 'abstract', 'await', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', + 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'export', 'extends', + 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', + 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', 'null', 'package', + 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', + 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', 'with', 'yield' + ]; + + if (reservedWords.includes(name)) { + return false; + } + + // If all checks passed, the name is valid + return true; +} + +function filterUniqueRootPaths(paths) { + if (paths.length < 2) { + return paths; + } + + // Always compare canonocal paths + const canonicalPaths = paths.map(function(folder) { + return FileInfo.cleanPath(folder); + }); + + const isChildOf = function(child, parent) { + if (child === parent) return false; + + var parentTokens = parent.split('/').filter(function(i) { + return i.length; + }); + + var childTokens = child.split('/').filter(function(i) { + return i.length; + }); + + return parentTokens.every(function(t, i) { + return childTokens[i] === t; + }); + } + + return canonicalPaths.filter(function(folder) { + return !canonicalPaths.some(function(otherFolder) { + return folder !== otherFolder && isChildOf(folder, otherFolder); + }); + }); +} diff --git a/qbs/modules/buildconfig/buildconfig.qbs b/qbs/modules/buildconfig/buildconfig.qbs index 6ff90ee29..37ab7229c 100644 --- a/qbs/modules/buildconfig/buildconfig.qbs +++ b/qbs/modules/buildconfig/buildconfig.qbs @@ -48,6 +48,18 @@ Module { return project.enableConan; } + readonly property bool conanXercesEnabled : project.conanWithXerces + readonly property bool conanCrashReportingEnabled : { + if (qbs.targetOS.contains("windows")) { + if (qbs.toolchain.contains("msvc")) { + return project.conanWithCrashReporting; + } else { + return false; + } + } else { + return project.conanWithCrashReporting; + } + } readonly property bool enableCodeSigning: project.enableSigning @@ -140,6 +152,9 @@ Module { if (enableMultiBundle) defines.push('MULTI_BUNDLE'); + if (useConanPackages && conanCrashReportingEnabled) + defines.push('CRASH_REPORTING'); + return defines; } @@ -827,39 +842,6 @@ Module { } } - if (Utilities.versionCompare(qbs.version, "1.22") < 0) - { - var qtLibs = [ - "QtCore", - "QtSvg", - "QtXml", - "QtPrintSupport", - "QtXmlPatterns", - "QtWidgets", - "QtGui", - "QtNetwork", - "QtTest", - "QtConcurrent" - ]; - - if (!qbs.targetOS.contains("macos")) - { - paths.push(Qt.core.incPath); - - for (var i = 0; i < qtLibs.length; i++) { - paths.push(FileInfo.joinPaths(Qt.core.incPath, qtLibs[i])); - } - - } else { - for (var i = 0; i < qtLibs.length; i++) { - paths.push(FileInfo.joinPaths(Qt.core.incPath, - qtLibs[i] + ".framework/Versions/" + Qt.core.versionMajor + - "/Headers")); - paths.push(FileInfo.joinPaths(Qt.core.incPath, qtLibs[i] + ".framework/Headers")); - } - } - } - return paths; } } diff --git a/qbs/modules/vcs2/vcs2.qbs b/qbs/modules/vcs2/vcs2.qbs index ebe8f4704..d66123ead 100644 --- a/qbs/modules/vcs2/vcs2.qbs +++ b/qbs/modules/vcs2/vcs2.qbs @@ -7,11 +7,14 @@ import qbs.Utilities Module { property string type: typeProbe.type property string repoDir: project.sourceDirectory + + // TODO: If minimal qbs version is 1.23 replace with FileInfo.executableSuffix() + readonly property string executableSuffix: project.qbs.targetOS.contains("windows") ? ".exe" : "" property string toolFilePath: { if (type === "git") - return "git"; + return "git" + executableSuffix; if (type === "svn") - return "svn"; + return "svn" + executableSuffix; } property string headerFileName: "vcs-repo-state.h" @@ -101,13 +104,14 @@ Module { // 3. latest commit is gSHA proc.exec(tool, ["describe", "--always", "HEAD"], true); repoState = proc.readStdOut().trim(); - if (repoState) + if (repoState) { found = true; const tagSections = repoState.split("-"); repoStateTag = tagSections[0]; repoStateDistance = tagSections[1]; repoStateRevision = tagSections[2]; + } } finally { proc.close(); } diff --git a/scripts/cppcheck.sh b/scripts/cppcheck.sh deleted file mode 100755 index 98c029ccc..000000000 --- a/scripts/cppcheck.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -# This script helps run cppcheck with the same keys we have on codeship.com (except for key --platform=unix32). -# Please, run this script from folder /scripts. - -# Because we use the last available cppcheck version usually we build it manually. -CPPCHECK="../../../../cppcheck-2.8/cppcheck" -$CPPCHECK \ - -j4 -f -q \ - -UDRW_DBG \ - -U__INTEL_COMPILER_UPDATE \ - --template '{file}:{line}:{message}:{id}' \ - --inline-suppr \ - --platform=unix64 \ - --std=c++17 \ - --enable=all \ - --library=qt \ - --library=std \ - --library=posix \ - --inconclusive \ - --suppress=*:*/vdxf/libdxfrw/intern/make_unique.h \ - --inconclusive \ - --suppress=*:*/qmuparser/make_unique.h \ - ../src diff --git a/scripts/make_install.bat b/scripts/make_install.bat deleted file mode 100644 index c8b0f4808..000000000 --- a/scripts/make_install.bat +++ /dev/null @@ -1,75 +0,0 @@ -rem script helps create installer - -rem find target architecture -IF "%PROCESSOR_ARCHITECTURE%"=="x86" (set ARCHITECTURE=32BIT) else (set ARCHITECTURE=64BIT) - -rem Path to Inno Setup according to architecture -if %ARCHITECTURE%==32BIT set nsis_path="C:/Program Files/Inno Setup 5/iscc.exe" -if %ARCHITECTURE%==64BIT set nsis_path="C:/Program Files (x86)/Inno Setup 5/iscc.exe" - -if not exist %nsis_path% ( - echo Coudn't find Inno Setup. Package will not be created. -) - -:CONTINUE -rem detect windows version -ver | find "6.1" > nul - -IF ERRORLEVEL = 1 GOTO PREPARE -IF ERRORLEVEL = 0 GOTO WIN7 - -:WIN7 -chcp 65001 - -:PREPARE -cd .. -cd -rem force qmake create new qm files -del /Q share\translations\*.qm -IF exist build ( - echo Build exists. Clearing. - rd /s /q build\package - del /s /q /f build\Makefile - del /s /q /f build\*.exe - del /s /q /f build\*.dll - del /q /f build\src\app\tape\obj\dialogabouttape.o - del /q /f build\src\app\valentina\obj\dialogaboutapp.o -) -mkdir build && echo build created -cd build -cd - -qmake -r CONFIG+=noTests ..\Valentina.pro -IF ERRORLEVEL 1 GOTO ERRORQMAKE1 -IF ERRORLEVEL 0 GOTO MAKE - -:MAKE -mingw32-make -j%NUMBER_OF_PROCESSORS% -IF ERRORLEVEL 1 GOTO ERRORMAKE -IF ERRORLEVEL 0 GOTO MAKEINSTALL - -:MAKEINSTALL -mingw32-make install -IF ERRORLEVEL 1 GOTO ERRORMAKEINSTALL -IF ERRORLEVEL 0 GOTO ONEXIT - -:ERRORMAKEINSTALL -echo Failed to create installer! -@pause -exit /b 1 -:ERRORMAKE -echo Failed to build project! -@pause -exit /b 1 -:ERRORQMAKE2 -echo Failed to make the second run qmake! -@pause -exit /b 1 -:ERRORQMAKE1 -echo Failed to make the first run qmake! -@pause -exit /b 1 -:ONEXIT -echo Done! -@pause - diff --git a/scripts/sign_mac_bundle.sh b/scripts/sign_mac_bundle.sh deleted file mode 100644 index 68bbe21c7..000000000 --- a/scripts/sign_mac_bundle.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -# Use the script to sign your app bundle on Mac OS X. -# Requirement Mac OS El Capitan or later. -# An app bundle must be in the same folder. - -# Name of your certificate in look "Developer ID Application: " -# Be sure that you use Developer ID certificate! -CERTIFICATE="Developer ID Application: " -BUNDLE=Valentina.app - -# all must be signed -#s ign all *.dylib -find $BUNDLE -name *.dylib | xargs -I $ codesign -f -v -v -vvvv --deep --strict -s "$CERTIFICATE" $ - -# sign Qt frameworks -find $BUNDLE -name Qt* -type f | xargs -I $ codesign -f -v -v -vvvv --deep --strict -s "$CERTIFICATE" $ - -# sign all binaries -find $BUNDLE -path */MacOS/* -type f | xargs -I $ codesign -f -v -v -vvvv --deep --strict -s "$CERTIFICATE" $ - -# sign the app bundle -codesign -f -v -v -vvvv --deep --strict -s "$CERTIFICATE" ./$BUNDLE - -#verify in the end -codesign -v -vvvv ./$BUNDLE -spctl -a -t exec -vv ./$BUNDLE diff --git a/scripts/symupload.py b/scripts/symupload.py new file mode 100644 index 000000000..499c1f2fd --- /dev/null +++ b/scripts/symupload.py @@ -0,0 +1,158 @@ +import argparse +import os +import subprocess +import sys +import glob +import zipfile + +database = "valentina" + +def debug_extension(): + platform = sys.platform + if platform == "darwin": + debug_ext = ".dSYM" + elif platform == "win32": + debug_ext = ".pdb" + elif platform == "linux": + debug_ext = ".debug" + else: + print(f"Unsupported platform {platform}") + return "uknown" + + return debug_ext + +def check_binary(binary): + # Check if binary file exists + if not os.path.exists(binary): + # If binary file doesn't exist, it may be part of a framework + framework_binary = os.path.basename(binary.replace(".framework", "")) + for root, dirs, files in os.walk(binary): + for file in files: + if file == framework_binary: + return os.path.join(root, file) + return binary + +def zip_sym(sym_file): + zip_sym_file = sym_file + ".zip" + with zipfile.ZipFile(zip_sym_file, 'w', zipfile.ZIP_DEFLATED) as zipf: + zipf.write(sym_file, os.path.basename(sym_file)) + return zip_sym_file + +def generate_sym_files(install_root): + sym_files = [] + + platform = sys.platform + debug_ext = debug_extension() + + debug_files = glob.glob(os.path.join(install_root, "**", "*" + debug_ext), recursive=True) + + for debug_file in debug_files: + print(f"Generating symbols for: {os.path.basename(debug_file)}") + if platform == "win32": + # For Windows, return the executable file + sym_file = os.path.splitext(debug_file)[0] + ".exe" + else: + sym_file = os.path.splitext(debug_file)[0] + ".sym" + if platform == "darwin": + binary = check_binary(os.path.splitext(debug_file)[0]) + dump_syms_cmd = ["dump_syms", "-g", debug_file, binary] + elif platform == "linux": + dump_syms_cmd = ["dump_syms", debug_file] + + with open(sym_file, "w") as f: + subprocess.run(dump_syms_cmd, check=True, stdout=f) + + if platform == "linux": + # When symbols are dumped from a debug file Crashpad creates an incorrect module name. + sed_cmd = ["sed", "-i", "1s/.debug//", sym_file] + subprocess.run(sed_cmd, check=True) + + sym_files.append((debug_file, zip_sym(sym_file))) + + return sym_files + +def generate_version_string(val_version, commit_hash, qt_version): + # Determine the platform + platform = sys.platform + if platform == "win32": + platform_str = "windows" + elif platform == "darwin": + platform_str = "macos" + elif platform == "linux": + platform_str = "linux" + else: + platform_str = "unknown" + + # Generate the version string + version_string = f"{val_version}-{commit_hash}-Qt_{qt_version}-{platform_str}" + return version_string + +def get_app_name(sym_file): + # Get the base name of the symbol file without extension + base_name = os.path.splitext(os.path.basename(sym_file))[0].lower() + + # Determine the platform + platform = sys.platform + if platform == "linux": + if base_name.startswith("lib"): + base_name = base_name[3:] + return base_name.split(".so")[0] + elif platform == "darwin": + if base_name.endswith(".framework"): + return base_name.split(".framework")[0] + + return base_name + +def upload_symbols(install_root, val_version, commit_hash, qt_version, clean=False): + # Platform-specific commands for generating and uploading symbol files + platform = sys.platform + sym_files = generate_sym_files(install_root) + app_version = generate_version_string(val_version, commit_hash, qt_version) + print(f"Uploading symbols for version {app_version}") + + for _, sym_file in sym_files: + app_name = get_app_name(sym_file) + print(f"Uploading symbols for application {app_name}") + + if platform == "linux": + upload_cmd = ["sym_upload", sym_file, f"https://{database}.bugsplat.com/post/bp/symbol/breakpadsymbols.php?appName={app_name}&appVer={app_version}"] + elif platform == "darwin": + upload_cmd = ["symupload", sym_file, f"https://{database}.bugsplat.com/post/bp/symbol/breakpadsymbols.php?appName={app_name}&appVer={app_version}"] + elif platform == "win32": + upload_cmd = ["symupload.exe", "--product", app_name, sym_file, f"https://{database}.bugsplat.com/post/bp/symbol/breakpadsymbols.php?appName={app_name}&appVer={app_version}"] + + try: + subprocess.run(upload_cmd, check=True) + print(f"Symbol file '{sym_file}' uploaded successfully.") + except subprocess.CalledProcessError as e: + print(f"Error: {e}") + + # Cleanup if requested + if clean: + debug_ext = debug_extension() + for debug_file, sym_file in sym_files: + os.remove(sym_file) + print(f"Symbol file '{sym_file}' removed.") + os.remove(debug_file) + print(f"Debug file '{debug_file}' removed.") + +if __name__ == "__main__": + # Command-line usage: python symupload.py /path/to/install_root/folder 0_7_52 abcdef123456 6_6 --clean + # - First argument: Path to the install root folder + # - Second argument: Valentina version + # - Third argument: Commit git hash + # - Fourth argument: Qt version + # - Optional argument: --clean (Clean up after upload) + + # Parse command-line arguments + parser = argparse.ArgumentParser(description="Upload symbols to BugSplat.") + parser.add_argument("install_root", type=str, help="Path to the installation folder") + parser.add_argument("val_version", type=str, help="Valentina version") + parser.add_argument("hash", type=str, help="Commit git hash") + parser.add_argument("qt_version", type=str, help="Qt version") + parser.add_argument("--clean", action="store_true", help="Clean up after upload") + + args = parser.parse_args() + + # Call install_package function with provided arguments + upload_symbols(args.install_root, args.val_version, args.hash, args.qt_version, args.clean) diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp index 8312c88e8..c212fb6cb 100644 --- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp +++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.cpp @@ -117,6 +117,21 @@ PuzzlePreferencesConfigurationPage::PuzzlePreferencesConfigurationPage(QWidget * // Tab Privacy ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic()); + +#if !defined(CRASH_REPORTING) + ui->groupBoxCrashReports->setDisabled(true); +#endif + + ui->checkBoxSendCrashReports->setChecked(settings->IsSendCrashReport()); + connect(ui->checkBoxSendCrashReports, &QCheckBox::stateChanged, this, + [this]() { m_sendCrashReportsChanged = true; }); + + QRegularExpression const rx(QStringLiteral("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b"), + QRegularExpression::CaseInsensitiveOption); + ui->lineEditCrashUserEmail->setValidator(new QRegularExpressionValidator(rx, this)); + ui->lineEditCrashUserEmail->setText(settings->GetCrashEmail()); + connect(ui->lineEditCrashUserEmail, &QLineEdit::editingFinished, this, + [this]() { m_crashUserEmailChanged = true; }); } //--------------------------------------------------------------------------------------------------------------------- @@ -229,6 +244,22 @@ auto PuzzlePreferencesConfigurationPage::Apply() -> QStringList settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked()); VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked()); +#if defined(CRASH_REPORTING) + if (m_sendCrashReportsChanged) + { + settings->SeSendCrashReport(ui->checkBoxSendCrashReports->isChecked()); + m_sendCrashReportsChanged = false; + preferences.append(tr("send crash report")); + } + + if (m_crashUserEmailChanged) + { + settings->SetCrashEmail(ui->lineEditCrashUserEmail->text()); + m_crashUserEmailChanged = false; + preferences.append(tr("user email in case of crash")); + } +#endif + return preferences; } diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.h b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.h index 0471bbf5f..4e53251f5 100644 --- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.h +++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.h @@ -57,6 +57,8 @@ private: Q_DISABLE_COPY_MOVE(PuzzlePreferencesConfigurationPage) // NOLINT std::unique_ptr ui; bool m_langChanged{false}; + bool m_sendCrashReportsChanged{false}; + bool m_crashUserEmailChanged{false}; QList m_transientShortcuts{}; void SetThemeModeComboBox(); diff --git a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui index f929b8279..680cf1199 100644 --- a/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui +++ b/src/app/puzzle/dialogs/configpages/puzzlepreferencesconfigurationpage.ui @@ -522,22 +522,75 @@ This option will take an affect after restart. Privacy - + - - - Send usage statistics + + + Usage statistic + + + + + Send usage statistics + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + - - - Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. - - - true + + + Crash reports + + + + + Send automatic crash reports + + + + + + + + + Email: + + + + + + + true + + + + + + + + + Reporting crash reports will help us make Valentina more reliable. All information is treated as confidential and is only used to improve future versions of this program. Please activate sending automatic crash reports and fill your email address (optional). If provided, we may contact you with additional information about the crash. + + + true + + + + diff --git a/src/app/puzzle/main.cpp b/src/app/puzzle/main.cpp index a4ac684ce..0c550987d 100644 --- a/src/app/puzzle/main.cpp +++ b/src/app/puzzle/main.cpp @@ -35,6 +35,11 @@ #include #endif +#ifdef CRASH_REPORTING +#include "../vmisc/crashhandler/crashhandler.h" +#include "version.h" +#endif + // Fix bug in Qt. Deprecation warning in QMessageBox::critical. #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) #undef QT_REQUIRE_VERSION @@ -81,6 +86,10 @@ auto main(int argc, char *argv[]) -> int Q_INIT_RESOURCE(win_light_theme); // NOLINT Q_INIT_RESOURCE(win_dark_theme); // NOLINT +#ifdef CRASH_REPORTING + InitializeCrashpad(QStringLiteral(VER_PRODUCTNAME_STR).toLower()); +#endif + #if defined(Q_OS_WIN) VAbstractApplication::WinAttachConsole(); #endif diff --git a/src/app/puzzle/puzzle.qbs b/src/app/puzzle/puzzle.qbs index 9457cd85d..07e1e176f 100644 --- a/src/app/puzzle/puzzle.qbs +++ b/src/app/puzzle/puzzle.qbs @@ -36,11 +36,17 @@ VToolApp { type: base.concat("install_root_svg_fonts") Properties { - condition: buildconfig.useConanPackages && qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle + condition: buildconfig.useConanPackages && buildconfig.conanXercesEnabled && qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle conan.XercesC.libInstallDir: qbs.installPrefix + "/" + buildconfig.installLibraryPath conan.XercesC.installLib: true } + Properties { + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled && qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle + conan.crashpad.installBin: true + conan.crashpad.binInstallDir: qbs.installPrefix + "/" + buildconfig.installBinaryPath + } + files: [ "main.cpp", "vpapplication.cpp", diff --git a/src/app/puzzle/vpmainwindow.cpp b/src/app/puzzle/vpmainwindow.cpp index edf383fc6..16f9c10b9 100644 --- a/src/app/puzzle/vpmainwindow.cpp +++ b/src/app/puzzle/vpmainwindow.cpp @@ -4701,15 +4701,20 @@ void VPMainWindow::AskDefaultSettings() } } - if (settings->IsAskCollectStatistic()) + if (settings->IsAskCollectStatistic() || settings->IsAskSendCrashReport()) { DialogAskCollectStatistic dialog(this); if (dialog.exec() == QDialog::Accepted) { settings->SetCollectStatistic(dialog.CollectStatistic()); +#if defined(CRASH_REPORTING) + settings->SeSendCrashReport(dialog.SendCrashReport()); + settings->SetCrashEmail(dialog.UserEmail()); +#endif } settings->SetAskCollectStatistic(false); + settings->SetAskSendCrashReport(false); } if (settings->IsCollectStatistic()) diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp index 64fac52f2..554bacb2d 100644 --- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp +++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.cpp @@ -115,6 +115,21 @@ TapePreferencesConfigurationPage::TapePreferencesConfigurationPage(QWidget *pare // Tab Privacy ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic()); + +#if !defined(CRASH_REPORTING) + ui->groupBoxCrashReports->setDisabled(true); +#endif + + ui->checkBoxSendCrashReports->setChecked(settings->IsSendCrashReport()); + connect(ui->checkBoxSendCrashReports, &QCheckBox::stateChanged, this, + [this]() { m_sendCrashReportsChanged = true; }); + + QRegularExpression const rx(QStringLiteral("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b"), + QRegularExpression::CaseInsensitiveOption); + ui->lineEditCrashUserEmail->setValidator(new QRegularExpressionValidator(rx, this)); + ui->lineEditCrashUserEmail->setText(settings->GetCrashEmail()); + connect(ui->lineEditCrashUserEmail, &QLineEdit::editingFinished, this, + [this]() { m_crashUserEmailChanged = true; }); } //--------------------------------------------------------------------------------------------------------------------- @@ -202,6 +217,22 @@ auto TapePreferencesConfigurationPage::Apply() -> QStringList settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked()); VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked()); +#if defined(CRASH_REPORTING) + if (m_sendCrashReportsChanged) + { + settings->SeSendCrashReport(ui->checkBoxSendCrashReports->isChecked()); + m_sendCrashReportsChanged = false; + preferences.append(tr("send crash report")); + } + + if (m_crashUserEmailChanged) + { + settings->SetCrashEmail(ui->lineEditCrashUserEmail->text()); + m_crashUserEmailChanged = false; + preferences.append(tr("user email in case of crash")); + } +#endif + return preferences; } diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.h b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.h index 6e22f9734..f85c3be47 100644 --- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.h +++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.h @@ -61,6 +61,8 @@ private: std::unique_ptr ui; bool m_langChanged; bool m_systemChanged; + bool m_sendCrashReportsChanged{false}; + bool m_crashUserEmailChanged{false}; QList m_transientShortcuts{}; void RetranslateUi(); diff --git a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui index 7f7ef9d11..36f0184f7 100644 --- a/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui +++ b/src/app/tape/dialogs/configpages/tapepreferencesconfigurationpage.ui @@ -312,20 +312,73 @@ - - - Send usage statistics + + + Usage statistic + + + + + Send usage statistics + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + - - - Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. - - - true + + + Crash reports + + + + + Send automatic crash reports + + + + + + + + + Email: + + + + + + + true + + + + + + + + + Reporting crash reports will help us make Valentina more reliable. All information is treated as confidential and is only used to improve future versions of this program. Please activate sending automatic crash reports and fill your email address (optional). If provided, we may contact you with additional information about the crash. + + + true + + + + diff --git a/src/app/tape/main.cpp b/src/app/tape/main.cpp index 91f3f0954..c073be0e0 100644 --- a/src/app/tape/main.cpp +++ b/src/app/tape/main.cpp @@ -35,6 +35,11 @@ #include #endif +#ifdef CRASH_REPORTING +#include "../vmisc/crashhandler/crashhandler.h" +#include "version.h" +#endif + // Fix bug in Qt. Deprecation warning in QMessageBox::critical. #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) #undef QT_REQUIRE_VERSION @@ -79,6 +84,10 @@ auto main(int argc, char *argv[]) -> int Q_INIT_RESOURCE(win_light_theme); // NOLINT Q_INIT_RESOURCE(win_dark_theme); // NOLINT +#ifdef CRASH_REPORTING + InitializeCrashpad(QStringLiteral(VER_PRODUCTNAME_STR).toLower()); +#endif + #if defined(Q_OS_WIN) VAbstractApplication::WinAttachConsole(); #endif diff --git a/src/app/tape/tape.qbs b/src/app/tape/tape.qbs index 11fccef94..a9fb5fa82 100644 --- a/src/app/tape/tape.qbs +++ b/src/app/tape/tape.qbs @@ -18,12 +18,14 @@ VToolApp { Depends { name: "xerces-c"; - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && !buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && + (!buildconfig.useConanPackages || (buildconfig.useConanPackages && !buildconfig.conanXercesEnabled)) } Depends { name: "conan.XercesC"; - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages && + buildconfig.conanXercesEnabled } // Explicitly link to libcrypto and libssl to avoid error: Failed to load libssl/libcrypto. @@ -45,11 +47,17 @@ VToolApp { multibundle.targetApps: ["Valentina"] Properties { - condition: buildconfig.useConanPackages && qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle + condition: buildconfig.useConanPackages && buildconfig.conanXercesEnabled && qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle conan.XercesC.libInstallDir: qbs.installPrefix + "/" + buildconfig.installLibraryPath conan.XercesC.installLib: true } + Properties { + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled && qbs.targetOS.contains("macos") && buildconfig.enableMultiBundle + conan.crashpad.installBin: true + conan.crashpad.binInstallDir: qbs.installPrefix + "/" + buildconfig.installBinaryPath + } + files: [ "main.cpp", "tkmmainwindow.cpp", diff --git a/src/app/tape/tkmmainwindow.cpp b/src/app/tape/tkmmainwindow.cpp index 008e0b3f5..238f2cd60 100644 --- a/src/app/tape/tkmmainwindow.cpp +++ b/src/app/tape/tkmmainwindow.cpp @@ -1513,15 +1513,20 @@ void TKMMainWindow::AskDefaultSettings() } } - if (settings->IsAskCollectStatistic()) + if (settings->IsAskCollectStatistic() || settings->IsAskSendCrashReport()) { DialogAskCollectStatistic dialog(this); if (dialog.exec() == QDialog::Accepted) { settings->SetCollectStatistic(dialog.CollectStatistic()); +#if defined(CRASH_REPORTING) + settings->SeSendCrashReport(dialog.SendCrashReport()); + settings->SetCrashEmail(dialog.UserEmail()); +#endif } settings->SetAskCollectStatistic(false); + settings->SetAskSendCrashReport(false); } if (settings->IsCollectStatistic()) diff --git a/src/app/tape/tmainwindow.cpp b/src/app/tape/tmainwindow.cpp index 2e383f5a8..94a012f97 100644 --- a/src/app/tape/tmainwindow.cpp +++ b/src/app/tape/tmainwindow.cpp @@ -2777,15 +2777,20 @@ void TMainWindow::AskDefaultSettings() } } - if (settings->IsAskCollectStatistic()) + if (settings->IsAskCollectStatistic() || settings->IsAskSendCrashReport()) { DialogAskCollectStatistic dialog(this); if (dialog.exec() == QDialog::Accepted) { settings->SetCollectStatistic(dialog.CollectStatistic()); +#if defined(CRASH_REPORTING) + settings->SeSendCrashReport(dialog.SendCrashReport()); + settings->SetCrashEmail(dialog.UserEmail()); +#endif } settings->SetAskCollectStatistic(false); + settings->SetAskSendCrashReport(false); } if (settings->IsCollectStatistic()) diff --git a/src/app/tape/version.h b/src/app/tape/version.h index 686e09ddd..3c907d389 100644 --- a/src/app/tape/version.h +++ b/src/app/tape/version.h @@ -29,9 +29,11 @@ #ifndef VERSION_H #define VERSION_H -#define VER_INTERNALNAME_STR "Tape" // NOLINT(cppcoreguidelines-macro-usage) -#define VER_ORIGINALFILENAME_STR "tape.exe" // NOLINT(cppcoreguidelines-macro-usage) -#define VER_PRODUCTNAME_STR "Tape" // NOLINT(cppcoreguidelines-macro-usage) -#define VER_FILEDESCRIPTION_STR "Valentina's measurements editor." // NOLINT(cppcoreguidelines-macro-usage) +#include "../../libs/vmisc/projectversion.h" + +#define VER_INTERNALNAME_STR "Tape" // NOLINT(cppcoreguidelines-macro-usage) +#define VER_ORIGINALFILENAME_STR "tape.exe" // NOLINT(cppcoreguidelines-macro-usage) +#define VER_PRODUCTNAME_STR "Tape" // NOLINT(cppcoreguidelines-macro-usage) +#define VER_FILEDESCRIPTION_STR "Valentina's measurements editor." // NOLINT(cppcoreguidelines-macro-usage) #endif // VERSION_H diff --git a/src/app/valentina/core/vapplication.cpp b/src/app/valentina/core/vapplication.cpp index b50ce4de8..1dc9c6695 100644 --- a/src/app/valentina/core/vapplication.cpp +++ b/src/app/valentina/core/vapplication.cpp @@ -92,8 +92,6 @@ Q_LOGGING_CATEGORY(vApp, "v.application") // NOLINT QT_WARNING_POP -Q_DECL_CONSTEXPR auto DAYS_TO_KEEP_LOGS = 3; - namespace { auto AppFilePath(const QString &appName) -> QString @@ -537,38 +535,13 @@ auto VApplication::PuzzleFilePath() -> QString return AppFilePath(appName); } -//--------------------------------------------------------------------------------------------------------------------- -auto VApplication::LogDirPath() -> QString -{ -#if defined(Q_OS_WIN) || defined(Q_OS_OSX) - const QString logDirPath = - QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString(), QStandardPaths::LocateDirectory) + - "Valentina"; -#else - const QString logDirPath = - QStandardPaths::locate(QStandardPaths::ConfigLocation, QString(), QStandardPaths::LocateDirectory) + - QCoreApplication::organizationName(); -#endif - return logDirPath; -} - //--------------------------------------------------------------------------------------------------------------------- auto VApplication::LogPath() -> QString { + // Keep in sync with VCrashPaths::GetAttachmentPath return QStringLiteral("%1/valentina-pid%2.log").arg(LogDirPath()).arg(applicationPid()); } -//--------------------------------------------------------------------------------------------------------------------- -auto VApplication::CreateLogDir() -> bool -{ - QDir const logDir(LogDirPath()); - if (not logDir.exists()) - { - return logDir.mkpath(QChar('.')); // Create directory for log if need - } - return true; -} - //--------------------------------------------------------------------------------------------------------------------- void VApplication::BeginLogging() { @@ -594,58 +567,6 @@ void VApplication::BeginLogging() } } -//--------------------------------------------------------------------------------------------------------------------- -void VApplication::ClearOldLogs() -{ - const QString workingDirectory = QDir::currentPath(); // Save the app working directory - const QString logDirPath = LogDirPath(); - QDir logsDir(logDirPath); - - if (!logsDir.exists()) - { - return; - } - - logsDir.setNameFilters(QStringList(QStringLiteral("*.log"))); - QDir::setCurrent(logDirPath); - - // Restore working directory - auto restore = qScopeGuard([workingDirectory] { QDir::setCurrent(workingDirectory); }); - - const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files); - if (allFiles.isEmpty()) - { - qCDebug(vApp, "There are no old logs."); - return; - } - - qCDebug(vApp, "Clearing old logs"); - for (const auto &fn : allFiles) - { - QFileInfo const info(fn); - const QDateTime created = info.birthTime(); - if (created.daysTo(QDateTime::currentDateTime()) >= DAYS_TO_KEEP_LOGS) - { - VLockGuard const tmp(info.absoluteFilePath(), [&fn]() { return new QFile(fn); }); - if (tmp.GetProtected() != nullptr) - { - if (tmp.GetProtected()->remove()) - { - qCDebug(vApp, "Deleted %s", qUtf8Printable(info.absoluteFilePath())); - } - else - { - qCDebug(vApp, "Could not delete %s", qUtf8Printable(info.absoluteFilePath())); - } - } - else - { - qCDebug(vApp, "Failed to lock %s", qUtf8Printable(info.absoluteFilePath())); - } - } - } -} - //--------------------------------------------------------------------------------------------------------------------- void VApplication::InitOptions() { diff --git a/src/app/valentina/core/vapplication.h b/src/app/valentina/core/vapplication.h index e97ee85aa..e400ef73d 100644 --- a/src/app/valentina/core/vapplication.h +++ b/src/app/valentina/core/vapplication.h @@ -101,11 +101,8 @@ private: VKnownMeasurementsDatabase *m_knownMeasurementsDatabase{nullptr}; QFileSystemWatcher *m_knownMeasurementsDatabaseWatcher{nullptr}; - static auto LogDirPath() -> QString; static auto LogPath() -> QString; - static auto CreateLogDir() -> bool; void BeginLogging(); - static void ClearOldLogs(); }; //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp index df2541b05..940260dff 100644 --- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp +++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.cpp @@ -176,6 +176,21 @@ PreferencesConfigurationPage::PreferencesConfigurationPage(QWidget *parent) // Tab Privacy ui->checkBoxSendUsageStatistics->setChecked(settings->IsCollectStatistic()); + +#if !defined(CRASH_REPORTING) + ui->groupBoxCrashReports->setDisabled(true); +#endif + + ui->checkBoxSendCrashReports->setChecked(settings->IsSendCrashReport()); + connect(ui->checkBoxSendCrashReports, &QCheckBox::stateChanged, this, + [this]() { m_sendCrashReportsChanged = true; }); + + QRegularExpression const rx(QStringLiteral("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b"), + QRegularExpression::CaseInsensitiveOption); + ui->lineEditCrashUserEmail->setValidator(new QRegularExpressionValidator(rx, this)); + ui->lineEditCrashUserEmail->setText(settings->GetCrashEmail()); + connect(ui->lineEditCrashUserEmail, &QLineEdit::editingFinished, this, + [this]() { m_crashUserEmailChanged = true; }); } //--------------------------------------------------------------------------------------------------------------------- @@ -306,6 +321,22 @@ auto PreferencesConfigurationPage::Apply() -> QStringList settings->SetCollectStatistic(ui->checkBoxSendUsageStatistics->isChecked()); VGAnalytics::Instance()->Enable(ui->checkBoxSendUsageStatistics->isChecked()); +#if defined(CRASH_REPORTING) + if (m_sendCrashReportsChanged) + { + settings->SeSendCrashReport(ui->checkBoxSendCrashReports->isChecked()); + m_sendCrashReportsChanged = false; + preferences.append(tr("send crash report")); + } + + if (m_crashUserEmailChanged) + { + settings->SetCrashEmail(ui->lineEditCrashUserEmail->text()); + m_crashUserEmailChanged = false; + preferences.append(tr("user email in case of crash")); + } +#endif + return preferences; } diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.h b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.h index 1f52839a6..f0f0c15db 100644 --- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.h +++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.h @@ -60,6 +60,8 @@ private: bool m_pieceLabelLangChanged{false}; bool m_unitChanged{false}; bool m_labelLangChanged{false}; + bool m_sendCrashReportsChanged{false}; + bool m_crashUserEmailChanged{false}; QList m_transientShortcuts{}; void SetLabelComboBox(const QStringList &list); diff --git a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui index 90d6760f3..d8c3a637d 100644 --- a/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui +++ b/src/app/valentina/dialogs/configpages/preferencesconfigurationpage.ui @@ -661,22 +661,75 @@ Privacy - + - - - Send usage statistics + + + Usage statistic + + + + + Send usage statistics + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + - - - Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. - - - true + + + Crash reports + + + + + Send automatic crash reports + + + + + + + + + Email: + + + + + + + true + + + + + + + + + Reporting crash reports will help us make Valentina more reliable. All information is treated as confidential and is only used to improve future versions of this program. Please activate sending automatic crash reports and fill your email address (optional). If provided, we may contact you with additional information about the crash. + + + true + + + + diff --git a/src/app/valentina/main.cpp b/src/app/valentina/main.cpp index 011f557ad..c61aae4d2 100644 --- a/src/app/valentina/main.cpp +++ b/src/app/valentina/main.cpp @@ -39,6 +39,11 @@ #include #endif +#ifdef CRASH_REPORTING +#include "../vmisc/crashhandler/crashhandler.h" +#include "version.h" +#endif + // Fix bug in Qt. Deprecation warning in QMessageBox::critical. #if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) #undef QT_REQUIRE_VERSION @@ -69,7 +74,6 @@ #endif //--------------------------------------------------------------------------------------------------------------------- - auto main(int argc, char *argv[]) -> int { Q_INIT_RESOURCE(cursor); // NOLINT @@ -88,6 +92,10 @@ auto main(int argc, char *argv[]) -> int Q_INIT_RESOURCE(win_light_theme); // NOLINT Q_INIT_RESOURCE(win_dark_theme); // NOLINT +#ifdef CRASH_REPORTING + InitializeCrashpad(QStringLiteral(VER_PRODUCTNAME_STR).toLower()); +#endif + #if defined(Q_OS_WIN) VAbstractApplication::WinAttachConsole(); #endif diff --git a/src/app/valentina/mainwindow.cpp b/src/app/valentina/mainwindow.cpp index dffd40f62..a752b72c0 100644 --- a/src/app/valentina/mainwindow.cpp +++ b/src/app/valentina/mainwindow.cpp @@ -4930,15 +4930,20 @@ void MainWindow::AskDefaultSettings() } } - if (settings->IsAskCollectStatistic()) + if (settings->IsAskCollectStatistic() || settings->IsAskSendCrashReport()) { DialogAskCollectStatistic dialog(this); if (dialog.exec() == QDialog::Accepted) { settings->SetCollectStatistic(dialog.CollectStatistic()); +#if defined(CRASH_REPORTING) + settings->SeSendCrashReport(dialog.SendCrashReport()); + settings->SetCrashEmail(dialog.UserEmail()); +#endif } settings->SetAskCollectStatistic(false); + settings->SetAskSendCrashReport(false); } if (settings->IsCollectStatistic()) diff --git a/src/app/valentina/valentina.qbs b/src/app/valentina/valentina.qbs index 87a81fba6..087d4d6bc 100644 --- a/src/app/valentina/valentina.qbs +++ b/src/app/valentina/valentina.qbs @@ -51,7 +51,7 @@ VToolApp { type: base.concat("install_root_svg_fonts") Properties { - condition: buildconfig.useConanPackages && (qbs.targetOS.contains("windows") || qbs.targetOS.contains("macos")) + condition: buildconfig.useConanPackages && buildconfig.conanXercesEnabled && (qbs.targetOS.contains("windows") || qbs.targetOS.contains("macos")) conan.XercesC.libInstallDir: qbs.installPrefix + "/" + buildconfig.installLibraryPath conan.XercesC.binInstallDir: qbs.installPrefix + "/" + buildconfig.installBinaryPath conan.XercesC.installLib: { @@ -66,6 +66,12 @@ VToolApp { } } + Properties { + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + conan.crashpad.installBin: true + conan.crashpad.binInstallDir: qbs.installPrefix + "/" + buildconfig.installBinaryPath + } + files: [ "main.cpp", "mainwindow.cpp", diff --git a/src/libs/ifc/ifc.qbs b/src/libs/ifc/ifc.qbs index 12cb978bd..cede8a77e 100644 --- a/src/libs/ifc/ifc.qbs +++ b/src/libs/ifc/ifc.qbs @@ -11,12 +11,14 @@ VLib { Depends { name: "xerces-c" - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && !buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && + (!buildconfig.useConanPackages || (buildconfig.useConanPackages && !buildconfig.conanXercesEnabled)) } Depends { name: "conan.XercesC" - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages && + buildconfig.conanXercesEnabled } name: "IFCLib" @@ -102,8 +104,11 @@ VLib { Depends { name: "cpp" } Depends { name: "Qt"; submodules: ["core", "xml"] } Depends { name: "VMiscLib" } - Depends { name: "xerces-c"; condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && !buildconfig.useConanPackages } - Depends { name: "conan.XercesC"; condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages } + Depends { name: "xerces-c"; condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && + (!buildconfig.useConanPackages || + (buildconfig.useConanPackages && !buildconfig.conanXercesEnabled)) } + Depends { name: "conan.XercesC"; condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && + buildconfig.useConanPackages && buildconfig.conanXercesEnabled } cpp.includePaths: [exportingProduct.sourceDirectory] } } diff --git a/src/libs/vmisc/crashhandler/crashhandler.cpp b/src/libs/vmisc/crashhandler/crashhandler.cpp new file mode 100644 index 000000000..2b7387a9e --- /dev/null +++ b/src/libs/vmisc/crashhandler/crashhandler.cpp @@ -0,0 +1,251 @@ +/************************************************************************ + ** + ** @file crashhandler.cpp + ** @author Roman Telezhynskyi + ** @date 4 3, 2024 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2024 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#include "crashhandler.h" + +#if defined(Q_OS_WIN) +#define NOMINMAX +#include +#endif + +#if defined(Q_OS_MAC) +#include +#endif + +#if defined(Q_OS_LINUX) +#include +#include +#include +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) // NOLINT +#endif + +#include +#include +#include + +#include "vcrashpaths.h" + +#include "../projectversion.h" +#include "../vcommonsettings.h" + +#if defined(APPIMAGE) && defined(Q_OS_LINUX) +#include "../appimage.h" +#endif + +#include + +using namespace base; +using namespace crashpad; + +namespace +{ +//--------------------------------------------------------------------------------------------------------------------- +Q_REQUIRED_RESULT auto AppSettings(const QString &appName) -> VCommonSettings * +{ + return new VCommonSettings(QSettings::IniFormat, QSettings::UserScope, QStringLiteral(VER_COMPANYNAME_STR), + appName); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto AppCrashVersion() -> QString +{ + QString const version = QStringLiteral("%1_%2_%3").arg(MAJOR_VERSION).arg(MINOR_VERSION).arg(DEBUG_VERSION); + QString const qtVersion = QStringLiteral("Qt_%1_%2").arg(QT_VERSION_MAJOR).arg(QT_VERSION_MINOR); + +#if defined(Q_OS_MACOS) + QString const platform = QStringLiteral("macos"); +#elif defined(Q_OS_WIN) + QString const platform = QStringLiteral("windows"); +#elif defined(Q_OS_LINUX) + QString const platform = QStringLiteral("linux"); +#else + QString const platform = QStringLiteral("unknown"); +#endif + + return QStringLiteral("%1-%2-%3-%4").arg(version, VCS_REPO_STATE_REVISION, qtVersion, platform); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto GetExecutableDir() -> QString +{ +#if defined(Q_OS_MAC) + unsigned int bufferSize = 512; + std::vector buffer(bufferSize + 1); + + if (_NSGetExecutablePath(&buffer[0], &bufferSize)) + { + buffer.resize(bufferSize); + _NSGetExecutablePath(&buffer[0], &bufferSize); + } + + char *lastForwardSlash = strrchr(&buffer[0], '/'); + if (lastForwardSlash == nullptr) + { + return nullptr; + } + *lastForwardSlash = 0; + + return &buffer[0]; +#elif defined(Q_OS_WINDOWS) + HMODULE hModule = GetModuleHandleW(NULL); + WCHAR path[MAX_PATH]; + DWORD retVal = GetModuleFileNameW(hModule, path, MAX_PATH); + if (retVal == 0) + { + return nullptr; + } + + wchar_t *lastBackslash = wcsrchr(path, '\\'); + if (lastBackslash == nullptr) + { + return nullptr; + } + *lastBackslash = 0; + + return QString::fromWCharArray(path); +#elif defined(Q_OS_LINUX) + char pBuf[FILENAME_MAX]; + int len = sizeof(pBuf); + int bytes = MIN(static_cast(readlink("/proc/self/exe", pBuf, static_cast(len))), len - 1); + if (bytes >= 0) + { + pBuf[bytes] = '\0'; + } + + char *lastForwardSlash = strrchr(&pBuf[0], '/'); + if (lastForwardSlash == nullptr) + { + return nullptr; + } + *lastForwardSlash = '\0'; + + return QString::fromStdString(pBuf); +#else +#error GetExecutableDir not implemented on this platform +#endif +} +} // namespace + +//--------------------------------------------------------------------------------------------------------------------- +auto InitializeCrashpad(const QString &appName) -> bool +{ + QScopedPointer const appSettings(AppSettings(appName)); + + if (!appSettings->IsSendCrashReport()) + { + return true; + } + +// Get directory where the exe lives so we can pass a full path to handler, reportsDir and metricsDir +#if defined(APPIMAGE) && defined(Q_OS_LINUX) + QString const exeDir = AppImageRoot(GetExecutableDir(), QString(BINDIR)); +#else + QString const exeDir = GetExecutableDir(); +#endif + + // Helper class for cross-platform file systems + VCrashPaths crashpadPaths(exeDir); + + auto MakeDir = [](const QString &path) + { + QDir const directory(path); + if (not directory.exists()) + { + directory.mkpath(QChar('.')); + } + }; + + // Directory where reports will be saved. Important! Must be writable or crashpad_handler will crash. + QString const reportsPath = VCrashPaths::GetReportsPath(); + MakeDir(reportsPath); + FilePath const reportsDir(VCrashPaths::GetPlatformString(reportsPath)); + + // Initialize crashpad database + std::unique_ptr database = CrashReportDatabase::Initialize(reportsDir); + if (database == nullptr) + { + return false; + } + + // Enable automated crash uploads + Settings *settings = database->GetSettings(); + if (settings == nullptr) + { + return false; + } + settings->SetUploadsEnabled(true); + + // Attachments to be uploaded alongside the crash - default bundle size limit is 20MB + std::vector attachments; + FilePath const attachment(VCrashPaths::GetPlatformString(VCrashPaths::GetAttachmentPath(appName))); +#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) + // Crashpad hasn't implemented attachments on OS X yet + attachments.push_back(attachment); +#endif + + // Ensure that crashpad_handler is shipped with your application + FilePath const handler(VCrashPaths::GetPlatformString(crashpadPaths.GetHandlerPath())); + + // Directory where metrics will be saved. Important! Must be writable or crashpad_handler will crash. + QString const metricsPath = VCrashPaths::GetMetricsPath(); + MakeDir(metricsPath); + FilePath const metricsDir(VCrashPaths::GetPlatformString(metricsPath)); + + QString const dbName = QStringLiteral("valentina"); + // Configure url with your BugSplat database + QString const url = QStringLiteral("https://%1.bugsplat.com/post/bp/crash/crashpad.php").arg(dbName); + + // Metadata that will be posted to BugSplat + QMap annotations; + annotations["format"] = "minidump"; // Required: Crashpad setting to save crash as a minidump + annotations["database"] = dbName.toStdString(); // Required: BugSplat database + annotations["product"] = appName.toStdString(); // Required: BugSplat appName + annotations["version"] = AppCrashVersion().toStdString(); // Required: BugSplat appVersion + + QString clientID = appSettings->GetClientID(); + if (clientID.isEmpty()) + { + clientID = QUuid::createUuid().toString(); + appSettings->SetClientID(clientID); + } + annotations["key"] = clientID.toStdString(); // Optional: BugSplat key field + + QString const userEmail = appSettings->GetCrashEmail(); + if (!userEmail.isEmpty()) + { + annotations["user"] = userEmail.toStdString(); // Optional: BugSplat user email + } + + // Disable crashpad rate limiting so that all crashes have dmp files + std::vector arguments; + arguments.emplace_back("--no-rate-limit"); + + // Start crash handler + auto *client = new CrashpadClient(); + return client->StartHandler(handler, reportsDir, metricsDir, url.toStdString(), annotations.toStdMap(), arguments, + true, true, attachments); +} diff --git a/src/libs/vmisc/crashhandler/crashhandler.h b/src/libs/vmisc/crashhandler/crashhandler.h new file mode 100644 index 000000000..acf8fe0fa --- /dev/null +++ b/src/libs/vmisc/crashhandler/crashhandler.h @@ -0,0 +1,35 @@ +/************************************************************************ + ** + ** @file crashhandler.h + ** @author Roman Telezhynskyi + ** @date 4 3, 2024 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2024 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#ifndef CRASHHANDLER_H +#define CRASHHANDLER_H + +#include + +auto InitializeCrashpad(const QString &appName) -> bool; + +#endif // CRASHHANDLER_H diff --git a/src/libs/vmisc/crashhandler/vcrashpaths.cpp b/src/libs/vmisc/crashhandler/vcrashpaths.cpp new file mode 100644 index 000000000..e181d8d8a --- /dev/null +++ b/src/libs/vmisc/crashhandler/vcrashpaths.cpp @@ -0,0 +1,91 @@ +/************************************************************************ + ** + ** @file vcrashpaths.cpp + ** @author Roman Telezhynskyi + ** @date 4 3, 2024 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2024 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#include +#include +#include +#include + +#include "../projectversion.h" +#include "vcrashpaths.h" + +//--------------------------------------------------------------------------------------------------------------------- +VCrashPaths::VCrashPaths(QString exeDir) + : m_exeDir(std::move(exeDir)) +{ +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCrashPaths::GetAttachmentPath(const QString &appName) -> QString +{ + const QString logDirPath = + QStandardPaths::locate(QStandardPaths::ConfigLocation, QString(), QStandardPaths::LocateDirectory) + + QStringLiteral(VER_COMPANYNAME_STR); + return QStringLiteral("%1/%2-pid%3.log").arg(logDirPath, appName.toLower()).arg(QCoreApplication::applicationPid()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCrashPaths::GetHandlerPath() -> QString +{ +#if defined(Q_OS_WINDOWS) + const QString handler = QStringLiteral("crashpad_handler.exe"); +#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX) + const QString handler = QStringLiteral("crashpad_handler"); +#else +#error GetHandlerPath not implemented on this platform +#endif + return m_exeDir + QDir::separator() + handler; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCrashPaths::GetReportsPath() -> QString +{ + return QStandardPaths::locate(QStandardPaths::AppConfigLocation, QString(), QStandardPaths::LocateDirectory) + + QStringList{VER_COMPANYNAME_STR, "User Data", "Crashpad", "Reports"}.join(QDir::separator()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCrashPaths::GetMetricsPath() -> QString +{ + return QStandardPaths::locate(QStandardPaths::AppConfigLocation, QString(), QStandardPaths::LocateDirectory) + + QStringList{VER_COMPANYNAME_STR, "User Data", "Crashpad", "Metrics"}.join(QDir::separator()); +} + +//--------------------------------------------------------------------------------------------------------------------- +#if defined(Q_OS_UNIX) +auto VCrashPaths::GetPlatformString(const QString &string) -> std::string +{ + return string.toStdString(); +} +#elif defined(Q_OS_WINDOWS) +auto VCrashPaths::GetPlatformString(const QString &string) -> std::wstring +{ + return string.toStdWString(); +} +#else +#error GetPlatformString not implemented on this platform +#endif diff --git a/src/libs/vmisc/crashhandler/vcrashpaths.h b/src/libs/vmisc/crashhandler/vcrashpaths.h new file mode 100644 index 000000000..aa3f923ea --- /dev/null +++ b/src/libs/vmisc/crashhandler/vcrashpaths.h @@ -0,0 +1,56 @@ +/************************************************************************ + ** + ** @file vcrashpaths.h + ** @author Roman Telezhynskyi + ** @date 4 3, 2024 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2024 Valentina project + ** All Rights Reserved. + ** + ** Valentina is free software: you can redistribute it and/or modify + ** it under the terms of the GNU General Public License as published by + ** the Free Software Foundation, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ** GNU General Public License for more details. + ** + ** You should have received a copy of the GNU General Public License + ** along with Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VCRASHPATHS_H +#define VCRASHPATHS_H + +#include + +class VCrashPaths +{ +public: + explicit VCrashPaths(QString exeDir); + + auto GetHandlerPath() -> QString; + + static auto GetAttachmentPath(const QString &appName) -> QString; + static auto GetReportsPath() -> QString; + static auto GetMetricsPath() -> QString; + +#if defined(Q_OS_UNIX) + static auto GetPlatformString(const QString &string) -> std::string; +#elif defined(Q_OS_WINDOWS) + static auto GetPlatformString(QString string) -> std::wstring; +#else +#error GetPlatformString not implemented on this platform +#endif + +private: + QString m_exeDir; +}; + +#endif // VCRASHPATHS_H diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp index af16ce47a..122c30083 100644 --- a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp +++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.cpp @@ -28,12 +28,28 @@ #include "dialogaskcollectstatistic.h" #include "ui_dialogaskcollectstatistic.h" +#include +#include + +#include "../vabstractapplication.h" + //--------------------------------------------------------------------------------------------------------------------- DialogAskCollectStatistic::DialogAskCollectStatistic(QWidget *parent) : QDialog(parent), ui(new Ui::DialogAskCollectStatistic) { ui->setupUi(this); + +#if !defined(CRASH_REPORTING) + ui->groupBoxCrashReports->setDisabled(true); +#endif + + VCommonSettings *settings = VAbstractApplication::VApp()->Settings(); + + QRegularExpression const rx(QStringLiteral("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b"), + QRegularExpression::CaseInsensitiveOption); + ui->lineEditCrashUserEmail->setValidator(new QRegularExpressionValidator(rx, this)); + ui->lineEditCrashUserEmail->setText(settings->GetCrashEmail()); } //--------------------------------------------------------------------------------------------------------------------- @@ -43,7 +59,19 @@ DialogAskCollectStatistic::~DialogAskCollectStatistic() } //--------------------------------------------------------------------------------------------------------------------- -auto DialogAskCollectStatistic::CollectStatistic() -> bool +auto DialogAskCollectStatistic::CollectStatistic() const -> bool { return ui->checkBoxSendUsageStatistics->isChecked(); } + +//--------------------------------------------------------------------------------------------------------------------- +auto DialogAskCollectStatistic::SendCrashReport() const -> bool +{ + return ui->checkBoxSendCrashReports->isChecked(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto DialogAskCollectStatistic::UserEmail() const -> QString +{ + return ui->lineEditCrashUserEmail->text(); +} diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h index a848954e1..35a647f88 100644 --- a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h +++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.h @@ -44,7 +44,9 @@ public: explicit DialogAskCollectStatistic(QWidget *parent = nullptr); ~DialogAskCollectStatistic() override; - auto CollectStatistic() -> bool; + auto CollectStatistic() const -> bool; + auto SendCrashReport() const -> bool; + auto UserEmail() const -> QString; private: Q_DISABLE_COPY_MOVE(DialogAskCollectStatistic) // NOLINT diff --git a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui index eb977b4a7..11ba5eeb2 100644 --- a/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui +++ b/src/libs/vmisc/dialogs/dialogaskcollectstatistic.ui @@ -6,8 +6,8 @@ 0 0 - 376 - 183 + 593 + 406 @@ -19,23 +19,79 @@ - - - Send usage statistics - - - true + + + Usage statistic + + + + + Send usage statistics + + + true + + + + + + + Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. + + + true + + + + - - - Please help to improve Valentina's quality by automatically sending usage statistics. Sent data contains <span style=" font-weight:700;">no potentially sensitive information</span> like user names, email addresses, file contents or file paths. - - - true + + + Crash reports + + + + + Send automatic crash reports + + + true + + + + + + + + + Email: + + + + + + + true + + + + + + + + + Reporting crash reports will help us make Valentina more reliable. All information is treated as confidential and is only used to improve future versions of this program. Please activate sending automatic crash reports and fill your email address (optional). If provided, we may contact you with additional information about the crash. + + + true + + + + diff --git a/src/libs/vmisc/projectversion.h b/src/libs/vmisc/projectversion.h index c0f03a76c..d1dd939b6 100644 --- a/src/libs/vmisc/projectversion.h +++ b/src/libs/vmisc/projectversion.h @@ -58,7 +58,7 @@ constexpr inline auto AppVersion() -> unsigned #define VER_PRODUCTVERSION VER_FILEVERSION #define VER_PRODUCTVERSION_STR VER_FILEVERSION_STR -#define VER_COMPANYNAME_STR "ValentinaTeam" +#define VER_COMPANYNAME_STR "Valentina" // #define VER_FILEDESCRIPTION_STR "Patternmaking program." // Defined in program // #define VER_INTERNALNAME_STR "Valentina" // Defined in program #define VER_LEGALCOPYRIGHT_STR "Copyright © 2014-2022 Valentina Team" diff --git a/src/libs/vmisc/vabstractapplication.cpp b/src/libs/vmisc/vabstractapplication.cpp index 723488605..75beac589 100644 --- a/src/libs/vmisc/vabstractapplication.cpp +++ b/src/libs/vmisc/vabstractapplication.cpp @@ -30,6 +30,7 @@ #include "compatibility.h" #include "svgfont/vsvgfontdatabase.h" +#include "vlockguard.h" #include "vtranslator.h" #include "QtConcurrent/qtconcurrentrun.h" @@ -67,6 +68,8 @@ using namespace Qt::Literals::StringLiterals; namespace { +Q_DECL_CONSTEXPR auto DAYS_TO_KEEP_LOGS = 3; + auto FilterLocales(const QStringList &locales) -> QStringList { QStringList filtered; @@ -625,3 +628,75 @@ void VAbstractApplication::InitHighDpiScaling(int argc, char *argv[]) Q_UNUSED(argv); #endif } + +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractApplication::LogDirPath() -> QString +{ + const QString logDirPath = + QStandardPaths::locate(QStandardPaths::ConfigLocation, QString(), QStandardPaths::LocateDirectory) + + QCoreApplication::organizationName(); + return logDirPath; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractApplication::CreateLogDir() -> bool +{ + QDir const logDir(LogDirPath()); + if (not logDir.exists()) + { + return logDir.mkpath(QChar('.')); // Create directory for log if need + } + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VAbstractApplication::ClearOldLogs() +{ + const QString workingDirectory = QDir::currentPath(); // Save the app working directory + const QString logDirPath = LogDirPath(); + QDir logsDir(logDirPath); + + if (!logsDir.exists()) + { + return; + } + + logsDir.setNameFilters(QStringList(QStringLiteral("*.log"))); + QDir::setCurrent(logDirPath); + + // Restore working directory + auto restore = qScopeGuard([workingDirectory] { QDir::setCurrent(workingDirectory); }); + + const QStringList allFiles = logsDir.entryList(QDir::NoDotAndDotDot | QDir::Files); + if (allFiles.isEmpty()) + { + qDebug("There are no old logs."); + return; + } + + qDebug("Clearing old logs"); + for (const auto &fn : allFiles) + { + QFileInfo const info(fn); + const QDateTime created = info.birthTime(); + if (created.daysTo(QDateTime::currentDateTime()) >= DAYS_TO_KEEP_LOGS) + { + VLockGuard const tmp(info.absoluteFilePath(), [&fn]() { return new QFile(fn); }); + if (tmp.GetProtected() != nullptr) + { + if (tmp.GetProtected()->remove()) + { + qDebug("Deleted %s", qUtf8Printable(info.absoluteFilePath())); + } + else + { + qDebug("Could not delete %s", qUtf8Printable(info.absoluteFilePath())); + } + } + else + { + qDebug("Failed to lock %s", qUtf8Printable(info.absoluteFilePath())); + } + } + } +} diff --git a/src/libs/vmisc/vabstractapplication.h b/src/libs/vmisc/vabstractapplication.h index e0a41be05..9b30cb3c3 100644 --- a/src/libs/vmisc/vabstractapplication.h +++ b/src/libs/vmisc/vabstractapplication.h @@ -123,6 +123,10 @@ public: // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, hicpp-avoid-c-arrays, modernize-avoid-c-arrays) static void InitHighDpiScaling(int argc, char *argv[]); + static auto LogDirPath() -> QString; + static auto CreateLogDir() -> bool; + static void ClearOldLogs(); + protected: QUndoStack *undoStack; diff --git a/src/libs/vmisc/vcommonsettings.cpp b/src/libs/vmisc/vcommonsettings.cpp index 49832fb0f..47eb3927f 100644 --- a/src/libs/vmisc/vcommonsettings.cpp +++ b/src/libs/vmisc/vcommonsettings.cpp @@ -131,6 +131,13 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingConfigurationInteractiveTools, ( Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingConfigurationDontUseNativeDialog, ("configuration/dontUseNativeDialog"_L1)) +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsConfigurationCrashEmail, ("configuration/crashEmail"_L1)) // NOLINT +// NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsConfigurationSendCrashReport, ("configuration/sendCrashReport"_L1)) +// NOLINTNEXTLINE +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingsConfigurationAskSendCrashReport, + ("configuration/askSendCrashReport"_L1)) + Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingPatternUndo, ("pattern/undo"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingPatternForbidFlipping, ("pattern/forbidFlipping"_L1)) // NOLINT Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingPatternForceFlipping, ("pattern/forceFlipping"_L1)) // NOLINT @@ -1698,3 +1705,48 @@ void VCommonSettings::SetActionShortcuts(const QString &name, const QStringList settings.setValue(name, shortcuts); settings.sync(); } + +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::GetCrashEmail() const -> QString +{ + QSettings const settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + return settings.value(*settingsConfigurationCrashEmail, QString()).toString(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetCrashEmail(const QString &value) +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + settings.setValue(*settingsConfigurationCrashEmail, value); + settings.sync(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::IsSendCrashReport() const -> bool +{ + QSettings const settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + return settings.value(*settingsConfigurationSendCrashReport, 1).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SeSendCrashReport(bool value) +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + settings.setValue(*settingsConfigurationSendCrashReport, value); + settings.sync(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VCommonSettings::IsAskSendCrashReport() const -> bool +{ + QSettings const settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + return settings.value(*settingsConfigurationAskSendCrashReport, 1).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VCommonSettings::SetAskSendCrashReport(bool value) +{ + QSettings settings(this->format(), this->scope(), this->organizationName(), *commonIniFilename); + settings.setValue(*settingsConfigurationAskSendCrashReport, value); + settings.sync(); +} diff --git a/src/libs/vmisc/vcommonsettings.h b/src/libs/vmisc/vcommonsettings.h index 41bb7e0dd..8f39dfd69 100644 --- a/src/libs/vmisc/vcommonsettings.h +++ b/src/libs/vmisc/vcommonsettings.h @@ -349,6 +349,15 @@ public: auto GetActionShortcuts(const QString &name, const QStringList &defaultShortcuts) -> QStringList; void SetActionShortcuts(const QString &name, const QStringList &shortcuts); + auto GetCrashEmail() const -> QString; + void SetCrashEmail(const QString &value); + + auto IsSendCrashReport() const -> bool; + void SeSendCrashReport(bool value); + + auto IsAskSendCrashReport() const -> bool; + void SetAskSendCrashReport(bool value); + signals: void SVGFontsPathChanged(const QString &oldPath, const QString &newPath); void KnownMeasurementsPathChanged(const QString &oldPath, const QString &newPath); diff --git a/src/libs/vmisc/vmisc.qbs b/src/libs/vmisc/vmisc.qbs index 0c1a5d97b..ece065ff4 100644 --- a/src/libs/vmisc/vmisc.qbs +++ b/src/libs/vmisc/vmisc.qbs @@ -2,6 +2,12 @@ import qbs.Utilities VLib { Depends { name: "Qt"; submodules: ["core", "printsupport", "gui", "widgets"] } + Depends { name: "buildconfig" } + + Depends { + name: "conan.crashpad"; + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + } name: "VMiscLib" files: { @@ -178,9 +184,25 @@ VLib { condition: qbs.targetOS.contains("macos") } + Group { + name: "crashhandler" + prefix: "crashhandler/" + files: [ + "crashhandler.h", + "crashhandler.cpp", + "vcrashpaths.cpp", + "vcrashpaths.h", + ] + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + } + Export { Depends { name: "cpp" } Depends { name: "Qt"; submodules: ["printsupport", "widgets"] } + Depends { + name: "conan.crashpad"; + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + } cpp.includePaths: [exportingProduct.sourceDirectory] } } diff --git a/src/libs/vpropertyexplorer/vpropertyexplorer.qbs b/src/libs/vpropertyexplorer/vpropertyexplorer.qbs index 0cd43859d..1017f124d 100644 --- a/src/libs/vpropertyexplorer/vpropertyexplorer.qbs +++ b/src/libs/vpropertyexplorer/vpropertyexplorer.qbs @@ -1,6 +1,7 @@ VDynamicLib { Depends { name: "Qt"; submodules: ["gui", "widgets"] } Depends { name: "VMiscLib" } + Depends { name: "IFCLib" } Depends { name: "multibundle"; } name: "VPropertyExplorerLib" diff --git a/src/test/CollectionTest/CollectionTest.qbs b/src/test/CollectionTest/CollectionTest.qbs index 1ef20a4f0..c19e0161b 100644 --- a/src/test/CollectionTest/CollectionTest.qbs +++ b/src/test/CollectionTest/CollectionTest.qbs @@ -10,14 +10,20 @@ VTestApp { Depends { name: "xerces-c" - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && !buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && + (!buildconfig.useConanPackages || (buildconfig.useConanPackages && !buildconfig.conanXercesEnabled)) } Depends { name: "conan.XercesC" - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages && + buildconfig.conanXercesEnabled } + Depends { + name: "conan.crashpad"; + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled + } name: "CollectionTest" buildconfig.appTarget: qbs.targetOS.contains("macos") ? "CollectionTest" : "collectionTest" diff --git a/src/test/TranslationsTest/TranslationsTest.qbs b/src/test/TranslationsTest/TranslationsTest.qbs index f2d3eb464..7ea727d89 100644 --- a/src/test/TranslationsTest/TranslationsTest.qbs +++ b/src/test/TranslationsTest/TranslationsTest.qbs @@ -9,12 +9,19 @@ VTestApp { Depends { name: "xerces-c" - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && !buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && + (!buildconfig.useConanPackages || (buildconfig.useConanPackages && !buildconfig.conanXercesEnabled)) } Depends { name: "conan.XercesC" - condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages + condition: Utilities.versionCompare(Qt.core.version, "6") >= 0 && buildconfig.useConanPackages && + buildconfig.conanXercesEnabled + } + + Depends { + name: "conan.crashpad"; + condition: buildconfig.useConanPackages && buildconfig.conanCrashReportingEnabled } name: "TranslationsTest" diff --git a/valentina.qbs b/valentina.qbs index 7311aeaf2..8bfef2d75 100644 --- a/valentina.qbs +++ b/valentina.qbs @@ -4,10 +4,12 @@ import "qbs/imports/conan/ConanfileProbe.qbs" as ConanfileProbe Project { name: "Valentina" - minimumQbsVersion: "1.21" + minimumQbsVersion: "1.22" qbsModuleProviders: ["Qt", "conan", "qbspkgconfig"] property bool enableConan: false + property bool conanWithXerces: false + property bool conanWithCrashReporting: false property string minimumMacosVersion: undefined property string minimumQtVersion: "5.15" property stringList conanProfiles: [] @@ -16,10 +18,19 @@ Project { // Temporary probe until qbs doesn't support conan 2.0 ConanfileProbe { id: thirdPartyConanPackages - condition: enableConan + condition: enableConan && (conanWithXerces || conanWithCrashReporting) conanfilePath: project.sourceDirectory + "/conanfile.py" verbose: true profiles: conanProfiles + options: { + var o = {}; + if (conanWithXerces) + o.with_xerces = "True"; + + if (conanWithCrashReporting) + o.with_crash_reporting = "True"; + return o; + } } references: [