From 76ace61d17ef822a2bb8770908e8c60781646e77 Mon Sep 17 00:00:00 2001 From: Max Risuhin Date: Thu, 30 Aug 2018 01:45:19 +0300 Subject: [PATCH] Integration with breakpad; crash report handler dialog #5425 Signed-off-by: Max Risuhin --- ci/Jenkinsfile.desktopbuild | 11 ++++ ci/desktop.groovy | 11 ++++ deployment/macos/qt.conf | 4 ++ desktop/CMakeLists.txt | 2 + desktop/main.cpp | 84 +++++++++++++++++-------- desktop/reportApp/CMakeLists.txt | 36 +++++++++++ desktop/reportApp/main.cpp | 43 +++++++++++++ desktop/reportApp/main.qml | 76 +++++++++++++++++++++++ desktop/reportApp/main.qrc | 5 ++ desktop/reportApp/reportpublisher.cpp | 89 +++++++++++++++++++++++++++ desktop/reportApp/reportpublisher.h | 39 ++++++++++++ desktop_files/package.json | 4 +- 12 files changed, 377 insertions(+), 27 deletions(-) create mode 100644 deployment/macos/qt.conf create mode 100644 desktop/reportApp/CMakeLists.txt create mode 100644 desktop/reportApp/main.cpp create mode 100644 desktop/reportApp/main.qml create mode 100644 desktop/reportApp/main.qrc create mode 100644 desktop/reportApp/reportpublisher.cpp create mode 100644 desktop/reportApp/reportpublisher.h diff --git a/ci/Jenkinsfile.desktopbuild b/ci/Jenkinsfile.desktopbuild index 5ce1ef5225..892286534f 100644 --- a/ci/Jenkinsfile.desktopbuild +++ b/ci/Jenkinsfile.desktopbuild @@ -26,6 +26,7 @@ external_modules_dir = [ 'node_modules/react-native-keychain/desktop', 'node_modules/react-native-securerandom/desktop', 'modules/react-native-status/desktop', + 'node_modules/google-breakpad', ] external_fonts = [ @@ -145,7 +146,10 @@ timeout(90) { sh 'cp -r assets/share/assets StatusIm.app/Contents/MacOs' sh 'chmod +x StatusIm.app/Contents/MacOs/ubuntu-server' sh 'cp ../desktop/bin/StatusIm StatusIm.app/Contents/MacOs' + sh 'cp ../desktop/reportApp/reportApp StatusIm.app/Contents/MacOs' + sh 'install_name_tool -add_rpath "@executable_path/../Frameworks" StatusIm.app/Contents/MacOs/reportApp' sh 'cp -f ../deployment/macos/Info.plist StatusIm.app/Contents' + sh 'cp -f ../deployment/macos/qt.conf StatusIm.app/Contents/MacOs' sh """ macdeployqt StatusIm.app -verbose=1 -dmg \\ -qmldir='${workspace}/node_modules/react-native/ReactQt/runtime/src/qml/' @@ -212,6 +216,7 @@ timeout(90) { sh "cp -r ./deployment/linux/usr ${packageFolder}/AppDir" sh "cp ./deployment/linux/.env ${packageFolder}/AppDir" sh "cp ./desktop/bin/StatusIm ${packageFolder}/AppDir/usr/bin" + sh "cp ./desktop/reportApp/reportApp ${packageFolder}/AppDir/usr/bin" sh 'wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage' sh 'chmod a+x ./linuxdeployqt-continuous-x86_64.AppImage' @@ -219,6 +224,12 @@ timeout(90) { sh 'rm -f StatusIm-x86_64.AppImage' sh "ldd ${packageFolder}/AppDir/usr/bin/StatusIm" + sh """ + ./linuxdeployqt-continuous-x86_64.AppImage \\ + ${packageFolder}/AppDir/usr/bin/reportApp \\ + -verbose=3 -always-overwrite -no-strip -no-translations -qmake=${qt_bin}/qmake \\ + -qmldir='${workspace}/desktop/reportApp' + """ sh """ ./linuxdeployqt-continuous-x86_64.AppImage \\ ${packageFolder}/AppDir/usr/share/applications/StatusIm.desktop \\ diff --git a/ci/desktop.groovy b/ci/desktop.groovy index 9530d6a0e8..bc1a9e7d70 100644 --- a/ci/desktop.groovy +++ b/ci/desktop.groovy @@ -11,6 +11,7 @@ external_modules_dir = [ 'node_modules/react-native-keychain/desktop', 'node_modules/react-native-securerandom/desktop', 'modules/react-native-status/desktop', + 'node_modules/google-breakpad', ] external_fonts = [ @@ -116,6 +117,7 @@ def bundleLinux(type = 'nightly') { sh "cp -r ./deployment/linux/usr ${packageFolder}/AppDir" sh "cp ./deployment/linux/.env ${packageFolder}/AppDir" sh "cp ./desktop/bin/StatusIm ${packageFolder}/AppDir/usr/bin" + sh "cp ./desktop/reportApp/reportApp ${packageFolder}/AppDir/usr/bin" sh 'wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage' sh 'chmod a+x ./linuxdeployqt-continuous-x86_64.AppImage' @@ -123,6 +125,12 @@ def bundleLinux(type = 'nightly') { sh 'rm -f StatusIm-x86_64.AppImage' sh "ldd ${packageFolder}/AppDir/usr/bin/StatusIm" + sh """ + ./linuxdeployqt-continuous-x86_64.AppImage \\ + ${packageFolder}/AppDir/usr/bin/reportApp \\ + -verbose=3 -always-overwrite -no-strip -no-translations -qmake=${qtBin}/qmake \\ + -qmldir='${workspace}/desktop/reportApp' + """ sh """ ./linuxdeployqt-continuous-x86_64.AppImage \\ ${packageFolder}/AppDir/usr/share/applications/StatusIm.desktop \\ @@ -177,6 +185,9 @@ def bundleMacOS(type = 'nightly') { sh 'cp -r assets/share/assets StatusIm.app/Contents/MacOs' sh 'chmod +x StatusIm.app/Contents/MacOs/ubuntu-server' sh 'cp ../desktop/bin/StatusIm StatusIm.app/Contents/MacOs' + sh 'cp ../desktop/reportApp/reportApp StatusIm.app/Contents/MacOs' + sh 'cp -f ../deployment/macos/qt.conf StatusIm.app/Contents/MacOs' + sh 'install_name_tool -add_rpath "@executable_path/../Frameworks" StatusIm.app/Contents/MacOs/reportApp' sh 'cp -f ../deployment/macos/Info.plist StatusIm.app/Contents' sh """ macdeployqt StatusIm.app -verbose=1 -dmg \\ diff --git a/deployment/macos/qt.conf b/deployment/macos/qt.conf new file mode 100644 index 0000000000..df50e9dba0 --- /dev/null +++ b/deployment/macos/qt.conf @@ -0,0 +1,4 @@ +[Paths] +Plugins = ../PlugIns +Imports = ../Resources/qml +Qml2Imports = ../Resources/qml diff --git a/desktop/CMakeLists.txt b/desktop/CMakeLists.txt index b213cd5bbd..be248fe87d 100644 --- a/desktop/CMakeLists.txt +++ b/desktop/CMakeLists.txt @@ -20,6 +20,8 @@ foreach(external_module ${EXTERNAL_MODULES_DIR}) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../${external_module} ${CMAKE_CURRENT_BINARY_DIR}/${external_module}) endforeach(external_module) +add_subdirectory(reportApp) + # APPLICATION_MAIN_CPP_PATH contains absolute path to generated template copy of main.cpp for application executable get_filename_component(APPLICATION_MAIN_CPP_PATH main.cpp ABSOLUTE) diff --git a/desktop/main.cpp b/desktop/main.cpp index 7859549374..a570faad3a 100644 --- a/desktop/main.cpp +++ b/desktop/main.cpp @@ -12,8 +12,8 @@ #include #include -#include #include +#include #include #include #include @@ -26,16 +26,22 @@ #include "rootview.h" #include "utilities.h" +#include "exceptionglobalhandler.h" + #ifdef BUILD_FOR_BUNDLE #include QStringList consoleOutputStrings; bool ubuntuServerStarted = false; QMutex consoleOutputMutex; +QProcess *g_ubuntuServerProcess = nullptr; #endif const int MAIN_WINDOW_WIDTH = 1024; const int MAIN_WINDOW_HEIGHT = 768; +const QString CRASH_REPORT_EXECUTABLE = QStringLiteral("reportApp"); +const QString CRASH_REPORT_EXECUTABLE_RELATIVE_PATH = + QStringLiteral("/../reportApp"); // TODO: some way to change while running class ReactNativeProperties : public QObject { @@ -143,13 +149,22 @@ void writeLogsToFile(); void loadFontsFromResources() { - QDirIterator it(":", QDirIterator::Subdirectories); - while (it.hasNext()) { - QString resourceFile = it.next(); - if (resourceFile.endsWith(".otf", Qt::CaseInsensitive) || resourceFile.endsWith(".ttf", Qt::CaseInsensitive)) { - QFontDatabase::addApplicationFont(resourceFile); - } + QDirIterator it(":", QDirIterator::Subdirectories); + while (it.hasNext()) { + QString resourceFile = it.next(); + if (resourceFile.endsWith(".otf", Qt::CaseInsensitive) || + resourceFile.endsWith(".ttf", Qt::CaseInsensitive)) { + QFontDatabase::addApplicationFont(resourceFile); } + } +} + +void exceptionPostHandledCallback() { +#ifdef BUILD_FOR_BUNDLE + if (g_ubuntuServerProcess) { + g_ubuntuServerProcess->kill(); + } +#endif } int main(int argc, char **argv) { @@ -157,6 +172,17 @@ int main(int argc, char **argv) { QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); + app.setApplicationName("StatusIm"); + + QString appPath = QCoreApplication::applicationDirPath(); +#ifndef BUILD_FOR_BUNDLE + appPath.append(CRASH_REPORT_EXECUTABLE_RELATIVE_PATH); +#endif + + ExceptionGlobalHandler exceptionHandler(appPath + QDir::separator() + + CRASH_REPORT_EXECUTABLE, + exceptionPostHandledCallback); + Q_INIT_RESOURCE(react_resources); loadFontsFromResources(); @@ -242,35 +268,41 @@ void writeLogsToFile() { } void runUbuntuServer() { - QProcess *process = new QProcess(); - process->setWorkingDirectory(getDataStoragePath()); - process->setProgram(QGuiApplication::applicationDirPath() + "/ubuntu-server"); - QObject::connect(process, &QProcess::errorOccurred, + g_ubuntuServerProcess = new QProcess(); + g_ubuntuServerProcess->setWorkingDirectory(getDataStoragePath()); + g_ubuntuServerProcess->setProgram(QGuiApplication::applicationDirPath() + + "/ubuntu-server"); + QObject::connect(g_ubuntuServerProcess, &QProcess::errorOccurred, [=](QProcess::ProcessError) { - qDebug() << "process name: " << process->program(); - qDebug() << "process error: " << process->errorString(); + qDebug() << "process name: " + << g_ubuntuServerProcess->program(); + qDebug() << "process error: " + << g_ubuntuServerProcess->errorString(); }); - QObject::connect(process, &QProcess::readyReadStandardOutput, [=] { - qDebug() << "ubuntu-server std: " - << process->readAllStandardOutput().trimmed(); - }); - QObject::connect(process, &QProcess::readyReadStandardError, [=] { - QString output = process->readAllStandardError().trimmed(); - qDebug() << "ubuntu-server err: " << output; - if (output.contains("Server starting")) { - ubuntuServerStarted = true; - } - }); + QObject::connect( + g_ubuntuServerProcess, &QProcess::readyReadStandardOutput, [=] { + qDebug() << "ubuntu-server std: " + << g_ubuntuServerProcess->readAllStandardOutput().trimmed(); + }); + QObject::connect( + g_ubuntuServerProcess, &QProcess::readyReadStandardError, [=] { + QString output = + g_ubuntuServerProcess->readAllStandardError().trimmed(); + qDebug() << "ubuntu-server err: " << output; + if (output.contains("Server starting")) { + ubuntuServerStarted = true; + } + }); QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, [=]() { qDebug() << "Kill ubuntu server"; - process->kill(); + g_ubuntuServerProcess->kill(); }); qDebug() << "starting ubuntu server..."; - process->start(); + g_ubuntuServerProcess->start(); qDebug() << "wait for started..."; while (!ubuntuServerStarted) { diff --git a/desktop/reportApp/CMakeLists.txt b/desktop/reportApp/CMakeLists.txt new file mode 100644 index 0000000000..76a4bac06c --- /dev/null +++ b/desktop/reportApp/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2017-present, Status Research and Development GmbH. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. An additional grant +# of patent rights can be found in the PATENTS file in the same directory. + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11") + +set(APP_NAME "reportApp") + +find_package(Qt5Core REQUIRED) +find_package(Qt5Qml REQUIRED) +find_package(Qt5Quick REQUIRED) +find_package(Qt5WebSockets REQUIRED) +find_package(Qt5Svg REQUIRED) + +set(MAIN_CPP_SOURCE reportpublisher.cpp + reportpublisher.cpp + main.cpp) + +add_executable( + ${APP_NAME} + ${MAIN_CPP_SOURCE} + main.qrc +) + +set(USED_QT_MODULES Core Qml Quick WebSockets Svg) + +qt5_use_modules(${APP_NAME} ${USED_QT_MODULES}) + +set(REACT_NATIVE_DESKTOP_EXTERNAL_PROJECT_DEPS ${REACT_NATIVE_DESKTOP_EXTERNAL_PROJECT_DEPS} reportApp PARENT_SCOPE) diff --git a/desktop/reportApp/main.cpp b/desktop/reportApp/main.cpp new file mode 100644 index 0000000000..6ae852a0b3 --- /dev/null +++ b/desktop/reportApp/main.cpp @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2017-present, Status Research and Development GmbH. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#include +#include +#include +#include + +#include "reportpublisher.h" + +const int MAIN_WINDOW_WIDTH = 1024; +const int MAIN_WINDOW_HEIGHT = 768; +const int INPUT_ARGUMENTS_COUNT = 5; + +int main(int argc, char **argv) { + + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + if (argc != INPUT_ARGUMENTS_COUNT) { + return 1; + } + + app.setApplicationName("Crash Report"); + + ReportPublisher reportPublisher(argv[1], argv[2]); + + QQuickView view; + view.rootContext()->setContextProperty("reportPublisher", &reportPublisher); + view.setSource(QUrl("qrc:///main.qml")); + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.resize(MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT); + view.show(); + + return app.exec(); +} diff --git a/desktop/reportApp/main.qml b/desktop/reportApp/main.qml new file mode 100644 index 0000000000..2c463a44d7 --- /dev/null +++ b/desktop/reportApp/main.qml @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2017-present, Status Research and Development GmbH. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +import QtQuick 2.4 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +Rectangle { + id: root + width: 384 + height: 640 + + ColumnLayout { + anchors.centerIn: parent + Text { + Layout.alignment: Qt.AlignCenter + text: "Oh, no! StatusIm application just crashed!" + font.bold: true + font.pointSize: 25 + } + Text { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 20 + text: "Please report us crash log files to allow us fix the issue!" + font.bold: true + font.pointSize: 20 + } + RowLayout { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 40 + spacing: 25 + + Button { + Layout.minimumWidth: 150 + text: "Report (highly appreciated)" + onClicked: reportPublisher.submit() + } + + Button { + text: "Restart and Quit" + onClicked: reportPublisher.restartAndQuit() + } + + Button { + text: "Just Quit" + onClicked: reportPublisher.quit() + } + } + RowLayout { + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 100 + + TextEdit { + readOnly: true + Layout.maximumWidth: 500 + wrapMode: TextEdit.Wrap + selectByMouse: true + font.pointSize: 12 + text: "Log files directory:\n" + reportPublisher.resolveDataStoragePath() + } + + Button { + text: "View" + onClicked: reportPublisher.showDirectory() + } + } + } +} + diff --git a/desktop/reportApp/main.qrc b/desktop/reportApp/main.qrc new file mode 100644 index 0000000000..79babe091c --- /dev/null +++ b/desktop/reportApp/main.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/desktop/reportApp/reportpublisher.cpp b/desktop/reportApp/reportpublisher.cpp new file mode 100644 index 0000000000..c79d1fcdc3 --- /dev/null +++ b/desktop/reportApp/reportpublisher.cpp @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2017-present, Status Research and Development GmbH. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#include "reportpublisher.h" + +#include +#include +#include +#include +#include +#include +#include + +const QString REPORT_SUBMIT_URL = + QStringLiteral("https://goo.gl/forms/0705ZN0EMW3xLDpI2"); + +ReportPublisher::ReportPublisher(QString minidumpFilePath, + QString crashedExecutablePath, QObject *parent) + : QObject(parent), m_minidumpFilePath(minidumpFilePath), + m_crashedExecutablePath(crashedExecutablePath) {} + +void ReportPublisher::submit() { + QDesktopServices::openUrl(QUrl(REPORT_SUBMIT_URL)); + showDirectory(); +} + +void ReportPublisher::restartAndQuit() { + QString appPath = m_crashedExecutablePath; + +#ifdef Q_OS_MACOS + QFileInfo crashedExecutableFileInfo(m_crashedExecutablePath); + QString fullPath = crashedExecutableFileInfo.dir().absolutePath(); + const QString bundleExtension = QStringLiteral(".app"); + if (fullPath.contains(bundleExtension)) { + appPath = fullPath.left(fullPath.indexOf(bundleExtension) + + bundleExtension.size()); + } + QString cmd = QString("open %1").arg(appPath); +#elif defined(Q_OS_LINUX) + QString cmd = appPath; +#endif + + QProcess::startDetached(cmd); + + qApp->quit(); +} + +void ReportPublisher::quit() { qApp->quit(); } + +void ReportPublisher::showDirectory() { + QString dataStoragePath = resolveDataStoragePath(); + if (!m_logFilesPrepared) { + m_logFilesPrepared = prepareReportFiles(dataStoragePath); + } + QDesktopServices::openUrl( + QUrl("file://" + dataStoragePath, QUrl::TolerantMode)); +} + +bool ReportPublisher::prepareReportFiles(QString reportDirPath) { + QFileInfo minidumpFileInfo(m_minidumpFilePath); + QFileInfo crashedExecutableFileInfo(m_crashedExecutablePath); + if (!minidumpFileInfo.exists() || !crashedExecutableFileInfo.exists()) + return false; + + return QFile::copy(m_minidumpFilePath, + reportDirPath + QDir::separator() + "crash.dmp") && + QFile::copy(m_crashedExecutablePath, + reportDirPath + QDir::separator() + + crashedExecutableFileInfo.fileName()); +} + +QString ReportPublisher::resolveDataStoragePath() { + QFileInfo minidumpFileInfo(m_minidumpFilePath); + QString dataStoragePath = + QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + + QDir::separator() + minidumpFileInfo.baseName(); + QDir dir(dataStoragePath); + if (!dir.exists()) { + dir.mkpath("."); + } + return dataStoragePath; +} diff --git a/desktop/reportApp/reportpublisher.h b/desktop/reportApp/reportpublisher.h new file mode 100644 index 0000000000..dc411a5c90 --- /dev/null +++ b/desktop/reportApp/reportpublisher.h @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2017-present, Status Research and Development GmbH. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#ifndef REPORTPUBLISHER +#define REPORTPUBLISHER + +#include +#include + +class ReportPublisher : public QObject { + Q_OBJECT + +public: + ReportPublisher(QString minidumpFilePath, QString crashedExecutablePath, QObject* parent = 0); + + Q_INVOKABLE void submit(); + Q_INVOKABLE void restartAndQuit(); + Q_INVOKABLE void quit(); + Q_INVOKABLE void showDirectory(); + Q_INVOKABLE QString resolveDataStoragePath(); + +private: + + bool prepareReportFiles(QString reportDirPath); + + QString m_minidumpFilePath; + QString m_crashedExecutablePath; + bool m_logFilesPrepared = false; +}; + + +#endif // REPORTPUBLISHER diff --git a/desktop_files/package.json b/desktop_files/package.json index 8d8695104b..7941a806b5 100644 --- a/desktop_files/package.json +++ b/desktop_files/package.json @@ -14,7 +14,8 @@ "node_modules/react-native-webview-bridge/desktop", "node_modules/react-native-keychain/desktop", "node_modules/react-native-securerandom/desktop", - "modules/react-native-status/desktop" + "modules/react-native-status/desktop", + "node_modules/google-breakpad" ], "desktopFonts": [ "../../../../../resources/fonts/SF-Pro-Text-Regular.otf", @@ -37,6 +38,7 @@ "dns.js": "1.0.1", "emojilib": "2.2.9", "events": "1.1.1", + "google-breakpad": "git+https://github.com/status-im/google-breakpad.git", "homoglyph-finder": "1.1.1", "identicon.js": "github:status-im/identicon.js", "instabug-reactnative": "2.12.0",