forked from qt-creator/qt-creator
Introduce Breakpad crash handler
Google Breakpad (https://chromium.googlesource.com/breakpad/breakpad) is a widely used crash handler framework, e.g. by Mozilla and Chromium. It is providing a platform neutral solution to generate mini dumps, collect debug information and generate stack traces from those. Done-with: Orgad Shaneh <orgad.shaneh@audiocodes.com> Change-Id: I09382e7db0dc9e29b228e7b554fda7b6f5684349 Reviewed-by: Orgad Shaneh <orgads@gmail.com> Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
This commit is contained in:
committed by
Orgad Shaneh
parent
131a796f9f
commit
4beaae0f79
1
.gitignore
vendored
1
.gitignore
vendored
@@ -51,6 +51,7 @@ wrapper.sh
|
|||||||
/src/app/Info.plist
|
/src/app/Info.plist
|
||||||
/src/plugins/**/*.json
|
/src/plugins/**/*.json
|
||||||
/src/plugins/coreplugin/ide_version.h
|
/src/plugins/coreplugin/ide_version.h
|
||||||
|
/src/libs/qt-breakpad/bin
|
||||||
app_version.h
|
app_version.h
|
||||||
phony.c
|
phony.c
|
||||||
|
|
||||||
|
@@ -13,11 +13,6 @@ include (../interfaces/interfaces.pri)
|
|||||||
include (../types/types.pri)
|
include (../types/types.pri)
|
||||||
include (../qmlprivategate/qmlprivategate.pri)
|
include (../qmlprivategate/qmlprivategate.pri)
|
||||||
|
|
||||||
QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH)
|
|
||||||
!isEmpty(QT_BREAKPAD_ROOT_PATH) {
|
|
||||||
include($$QT_BREAKPAD_ROOT_PATH/qtbreakpad.pri)
|
|
||||||
}
|
|
||||||
|
|
||||||
SOURCES += $$PWD/qml2puppetmain.cpp
|
SOURCES += $$PWD/qml2puppetmain.cpp
|
||||||
RESOURCES += $$PWD/../qmlpuppet.qrc
|
RESOURCES += $$PWD/../qmlpuppet.qrc
|
||||||
|
|
||||||
|
@@ -111,7 +111,8 @@ int internalMain(QGuiApplication *application)
|
|||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_QT_BREAKPAD
|
#ifdef ENABLE_QT_BREAKPAD
|
||||||
QtSystemExceptionHandler systemExceptionHandler;
|
const QString libexecPath = QCoreApplication::applicationDirPath() + '/' + RELATIVE_LIBEXEC_PATH;
|
||||||
|
QtSystemExceptionHandler systemExceptionHandler(libexecPath);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
new QmlDesigner::Qt5NodeInstanceClientProxy(application);
|
new QmlDesigner::Qt5NodeInstanceClientProxy(application);
|
||||||
|
@@ -12,13 +12,10 @@ HEADERS += ../tools/qtcreatorcrashhandler/crashhandlersetup.h
|
|||||||
SOURCES += main.cpp ../tools/qtcreatorcrashhandler/crashhandlersetup.cpp
|
SOURCES += main.cpp ../tools/qtcreatorcrashhandler/crashhandlersetup.cpp
|
||||||
|
|
||||||
include(../rpath.pri)
|
include(../rpath.pri)
|
||||||
|
include(../libs/qt-breakpad/qtbreakpad.pri)
|
||||||
|
|
||||||
LIBS *= -l$$qtLibraryName(ExtensionSystem) -l$$qtLibraryName(Aggregation) -l$$qtLibraryName(Utils)
|
LIBS *= -l$$qtLibraryName(ExtensionSystem) -l$$qtLibraryName(Aggregation) -l$$qtLibraryName(Utils)
|
||||||
|
|
||||||
QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH)
|
|
||||||
!isEmpty(QT_BREAKPAD_ROOT_PATH) {
|
|
||||||
include($$QT_BREAKPAD_ROOT_PATH/qtbreakpad.pri)
|
|
||||||
}
|
|
||||||
win32 {
|
win32 {
|
||||||
# We need the version in two separate formats for the .rc file
|
# We need the version in two separate formats for the .rc file
|
||||||
# RC_VERSION=4,3,82,0 (quadruple)
|
# RC_VERSION=4,3,82,0 (quadruple)
|
||||||
|
@@ -329,13 +329,13 @@ int main(int argc, char **argv)
|
|||||||
const int threadCount = QThreadPool::globalInstance()->maxThreadCount();
|
const int threadCount = QThreadPool::globalInstance()->maxThreadCount();
|
||||||
QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount));
|
QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount));
|
||||||
|
|
||||||
// Display a backtrace once a serious signal is delivered (Linux only).
|
|
||||||
const QString libexecPath = QCoreApplication::applicationDirPath()
|
const QString libexecPath = QCoreApplication::applicationDirPath()
|
||||||
+ '/' + RELATIVE_LIBEXEC_PATH;
|
+ '/' + RELATIVE_LIBEXEC_PATH;
|
||||||
CrashHandlerSetup setupCrashHandler(appNameC, CrashHandlerSetup::EnableRestart, libexecPath);
|
|
||||||
|
|
||||||
#ifdef ENABLE_QT_BREAKPAD
|
#ifdef ENABLE_QT_BREAKPAD
|
||||||
QtSystemExceptionHandler systemExceptionHandler;
|
QtSystemExceptionHandler systemExceptionHandler(libexecPath);
|
||||||
|
#else
|
||||||
|
// Display a backtrace once a serious signal is delivered (Linux only).
|
||||||
|
CrashHandlerSetup setupCrashHandler(appNameC, CrashHandlerSetup::EnableRestart, libexecPath);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
|
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||||
|
32
src/libs/qt-breakpad/poster/__init__.py
Normal file
32
src/libs/qt-breakpad/poster/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Copyright (c) 2010 Chris AtLee
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
"""poster module
|
||||||
|
|
||||||
|
Support for streaming HTTP uploads, and multipart/form-data encoding
|
||||||
|
|
||||||
|
```poster.version``` is a 3-tuple of integers representing the version number.
|
||||||
|
New releases of poster will always have a version number that compares greater
|
||||||
|
than an older version of poster.
|
||||||
|
New in version 0.6."""
|
||||||
|
|
||||||
|
import poster.streaminghttp
|
||||||
|
import poster.encode
|
||||||
|
|
||||||
|
version = (0, 8, 0) # Thanks JP!
|
433
src/libs/qt-breakpad/poster/encode.py
Normal file
433
src/libs/qt-breakpad/poster/encode.py
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
# Copyright (c) 2010 Chris AtLee
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
"""multipart/form-data encoding module
|
||||||
|
|
||||||
|
This module provides functions that faciliate encoding name/value pairs
|
||||||
|
as multipart/form-data suitable for a HTTP POST or PUT request.
|
||||||
|
|
||||||
|
multipart/form-data is the standard way to upload files over HTTP"""
|
||||||
|
|
||||||
|
__all__ = ['gen_boundary', 'encode_and_quote', 'MultipartParam',
|
||||||
|
'encode_string', 'encode_file_header', 'get_body_size', 'get_headers',
|
||||||
|
'multipart_encode']
|
||||||
|
|
||||||
|
try:
|
||||||
|
import uuid
|
||||||
|
def gen_boundary():
|
||||||
|
"""Returns a random string to use as the boundary for a message"""
|
||||||
|
return uuid.uuid4().hex
|
||||||
|
except ImportError:
|
||||||
|
import random, sha
|
||||||
|
def gen_boundary():
|
||||||
|
"""Returns a random string to use as the boundary for a message"""
|
||||||
|
bits = random.getrandbits(160)
|
||||||
|
return sha.new(str(bits)).hexdigest()
|
||||||
|
|
||||||
|
import urllib, re, os, mimetypes
|
||||||
|
try:
|
||||||
|
from email.header import Header
|
||||||
|
except ImportError:
|
||||||
|
# Python 2.4
|
||||||
|
from email.Header import Header
|
||||||
|
|
||||||
|
def encode_and_quote(data):
|
||||||
|
"""If ``data`` is unicode, return urllib.quote_plus(data.encode("utf-8"))
|
||||||
|
otherwise return urllib.quote_plus(data)"""
|
||||||
|
if data is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(data, unicode):
|
||||||
|
data = data.encode("utf-8")
|
||||||
|
return urllib.quote_plus(data)
|
||||||
|
|
||||||
|
def _strify(s):
|
||||||
|
"""If s is a unicode string, encode it to UTF-8 and return the results,
|
||||||
|
otherwise return str(s), or None if s is None"""
|
||||||
|
if s is None:
|
||||||
|
return None
|
||||||
|
if isinstance(s, unicode):
|
||||||
|
return s.encode("utf-8")
|
||||||
|
return str(s)
|
||||||
|
|
||||||
|
class MultipartParam(object):
|
||||||
|
"""Represents a single parameter in a multipart/form-data request
|
||||||
|
|
||||||
|
``name`` is the name of this parameter.
|
||||||
|
|
||||||
|
If ``value`` is set, it must be a string or unicode object to use as the
|
||||||
|
data for this parameter.
|
||||||
|
|
||||||
|
If ``filename`` is set, it is what to say that this parameter's filename
|
||||||
|
is. Note that this does not have to be the actual filename any local file.
|
||||||
|
|
||||||
|
If ``filetype`` is set, it is used as the Content-Type for this parameter.
|
||||||
|
If unset it defaults to "text/plain; charset=utf8"
|
||||||
|
|
||||||
|
If ``filesize`` is set, it specifies the length of the file ``fileobj``
|
||||||
|
|
||||||
|
If ``fileobj`` is set, it must be a file-like object that supports
|
||||||
|
.read().
|
||||||
|
|
||||||
|
Both ``value`` and ``fileobj`` must not be set, doing so will
|
||||||
|
raise a ValueError assertion.
|
||||||
|
|
||||||
|
If ``fileobj`` is set, and ``filesize`` is not specified, then
|
||||||
|
the file's size will be determined first by stat'ing ``fileobj``'s
|
||||||
|
file descriptor, and if that fails, by seeking to the end of the file,
|
||||||
|
recording the current position as the size, and then by seeking back to the
|
||||||
|
beginning of the file.
|
||||||
|
|
||||||
|
``cb`` is a callable which will be called from iter_encode with (self,
|
||||||
|
current, total), representing the current parameter, current amount
|
||||||
|
transferred, and the total size.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, value=None, filename=None, filetype=None,
|
||||||
|
filesize=None, fileobj=None, cb=None):
|
||||||
|
self.name = Header(name).encode()
|
||||||
|
self.value = _strify(value)
|
||||||
|
if filename is None:
|
||||||
|
self.filename = None
|
||||||
|
else:
|
||||||
|
if isinstance(filename, unicode):
|
||||||
|
# Encode with XML entities
|
||||||
|
self.filename = filename.encode("ascii", "xmlcharrefreplace")
|
||||||
|
else:
|
||||||
|
self.filename = str(filename)
|
||||||
|
self.filename = self.filename.encode("string_escape").\
|
||||||
|
replace('"', '\\"')
|
||||||
|
self.filetype = _strify(filetype)
|
||||||
|
|
||||||
|
self.filesize = filesize
|
||||||
|
self.fileobj = fileobj
|
||||||
|
self.cb = cb
|
||||||
|
|
||||||
|
if self.value is not None and self.fileobj is not None:
|
||||||
|
raise ValueError("Only one of value or fileobj may be specified")
|
||||||
|
|
||||||
|
if fileobj is not None and filesize is None:
|
||||||
|
# Try and determine the file size
|
||||||
|
try:
|
||||||
|
self.filesize = os.fstat(fileobj.fileno()).st_size
|
||||||
|
except (OSError, AttributeError):
|
||||||
|
try:
|
||||||
|
fileobj.seek(0, 2)
|
||||||
|
self.filesize = fileobj.tell()
|
||||||
|
fileobj.seek(0)
|
||||||
|
except:
|
||||||
|
raise ValueError("Could not determine filesize")
|
||||||
|
|
||||||
|
def __cmp__(self, other):
|
||||||
|
attrs = ['name', 'value', 'filename', 'filetype', 'filesize', 'fileobj']
|
||||||
|
myattrs = [getattr(self, a) for a in attrs]
|
||||||
|
oattrs = [getattr(other, a) for a in attrs]
|
||||||
|
return cmp(myattrs, oattrs)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
if self.fileobj is not None:
|
||||||
|
self.fileobj.seek(0)
|
||||||
|
elif self.value is None:
|
||||||
|
raise ValueError("Don't know how to reset this parameter")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file(cls, paramname, filename):
|
||||||
|
"""Returns a new MultipartParam object constructed from the local
|
||||||
|
file at ``filename``.
|
||||||
|
|
||||||
|
``filesize`` is determined by os.path.getsize(``filename``)
|
||||||
|
|
||||||
|
``filetype`` is determined by mimetypes.guess_type(``filename``)[0]
|
||||||
|
|
||||||
|
``filename`` is set to os.path.basename(``filename``)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return cls(paramname, filename=os.path.basename(filename),
|
||||||
|
filetype=mimetypes.guess_type(filename)[0],
|
||||||
|
filesize=os.path.getsize(filename),
|
||||||
|
fileobj=open(filename, "rb"))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_params(cls, params):
|
||||||
|
"""Returns a list of MultipartParam objects from a sequence of
|
||||||
|
name, value pairs, MultipartParam instances,
|
||||||
|
or from a mapping of names to values
|
||||||
|
|
||||||
|
The values may be strings or file objects, or MultipartParam objects.
|
||||||
|
MultipartParam object names must match the given names in the
|
||||||
|
name,value pairs or mapping, if applicable."""
|
||||||
|
if hasattr(params, 'items'):
|
||||||
|
params = params.items()
|
||||||
|
|
||||||
|
retval = []
|
||||||
|
for item in params:
|
||||||
|
if isinstance(item, cls):
|
||||||
|
retval.append(item)
|
||||||
|
continue
|
||||||
|
name, value = item
|
||||||
|
if isinstance(value, cls):
|
||||||
|
assert value.name == name
|
||||||
|
retval.append(value)
|
||||||
|
continue
|
||||||
|
if hasattr(value, 'read'):
|
||||||
|
# Looks like a file object
|
||||||
|
filename = getattr(value, 'name', None)
|
||||||
|
if filename is not None:
|
||||||
|
filetype = mimetypes.guess_type(filename)[0]
|
||||||
|
else:
|
||||||
|
filetype = None
|
||||||
|
|
||||||
|
retval.append(cls(name=name, filename=filename,
|
||||||
|
filetype=filetype, fileobj=value))
|
||||||
|
else:
|
||||||
|
retval.append(cls(name, value))
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def encode_hdr(self, boundary):
|
||||||
|
"""Returns the header of the encoding of this parameter"""
|
||||||
|
boundary = encode_and_quote(boundary)
|
||||||
|
|
||||||
|
headers = ["--%s" % boundary]
|
||||||
|
|
||||||
|
if self.filename:
|
||||||
|
disposition = 'form-data; name="%s"; filename="%s"' % (self.name,
|
||||||
|
self.filename)
|
||||||
|
else:
|
||||||
|
disposition = 'form-data; name="%s"' % self.name
|
||||||
|
|
||||||
|
headers.append("Content-Disposition: %s" % disposition)
|
||||||
|
|
||||||
|
if self.filetype:
|
||||||
|
filetype = self.filetype
|
||||||
|
else:
|
||||||
|
filetype = "text/plain; charset=utf-8"
|
||||||
|
|
||||||
|
headers.append("Content-Type: %s" % filetype)
|
||||||
|
|
||||||
|
headers.append("")
|
||||||
|
headers.append("")
|
||||||
|
|
||||||
|
return "\r\n".join(headers)
|
||||||
|
|
||||||
|
def encode(self, boundary):
|
||||||
|
"""Returns the string encoding of this parameter"""
|
||||||
|
if self.value is None:
|
||||||
|
value = self.fileobj.read()
|
||||||
|
else:
|
||||||
|
value = self.value
|
||||||
|
|
||||||
|
if re.search("^--%s$" % re.escape(boundary), value, re.M):
|
||||||
|
raise ValueError("boundary found in encoded string")
|
||||||
|
|
||||||
|
return "%s%s\r\n" % (self.encode_hdr(boundary), value)
|
||||||
|
|
||||||
|
def iter_encode(self, boundary, blocksize=4096):
|
||||||
|
"""Yields the encoding of this parameter
|
||||||
|
If self.fileobj is set, then blocks of ``blocksize`` bytes are read and
|
||||||
|
yielded."""
|
||||||
|
total = self.get_size(boundary)
|
||||||
|
current = 0
|
||||||
|
if self.value is not None:
|
||||||
|
block = self.encode(boundary)
|
||||||
|
current += len(block)
|
||||||
|
yield block
|
||||||
|
if self.cb:
|
||||||
|
self.cb(self, current, total)
|
||||||
|
else:
|
||||||
|
block = self.encode_hdr(boundary)
|
||||||
|
current += len(block)
|
||||||
|
yield block
|
||||||
|
if self.cb:
|
||||||
|
self.cb(self, current, total)
|
||||||
|
last_block = ""
|
||||||
|
encoded_boundary = "--%s" % encode_and_quote(boundary)
|
||||||
|
boundary_exp = re.compile("^%s$" % re.escape(encoded_boundary),
|
||||||
|
re.M)
|
||||||
|
while True:
|
||||||
|
block = self.fileobj.read(blocksize)
|
||||||
|
if not block:
|
||||||
|
current += 2
|
||||||
|
yield "\r\n"
|
||||||
|
if self.cb:
|
||||||
|
self.cb(self, current, total)
|
||||||
|
break
|
||||||
|
last_block += block
|
||||||
|
if boundary_exp.search(last_block):
|
||||||
|
raise ValueError("boundary found in file data")
|
||||||
|
last_block = last_block[-len(encoded_boundary)-2:]
|
||||||
|
current += len(block)
|
||||||
|
yield block
|
||||||
|
if self.cb:
|
||||||
|
self.cb(self, current, total)
|
||||||
|
|
||||||
|
def get_size(self, boundary):
|
||||||
|
"""Returns the size in bytes that this param will be when encoded
|
||||||
|
with the given boundary."""
|
||||||
|
if self.filesize is not None:
|
||||||
|
valuesize = self.filesize
|
||||||
|
else:
|
||||||
|
valuesize = len(self.value)
|
||||||
|
|
||||||
|
return len(self.encode_hdr(boundary)) + 2 + valuesize
|
||||||
|
|
||||||
|
def encode_string(boundary, name, value):
|
||||||
|
"""Returns ``name`` and ``value`` encoded as a multipart/form-data
|
||||||
|
variable. ``boundary`` is the boundary string used throughout
|
||||||
|
a single request to separate variables."""
|
||||||
|
|
||||||
|
return MultipartParam(name, value).encode(boundary)
|
||||||
|
|
||||||
|
def encode_file_header(boundary, paramname, filesize, filename=None,
|
||||||
|
filetype=None):
|
||||||
|
"""Returns the leading data for a multipart/form-data field that contains
|
||||||
|
file data.
|
||||||
|
|
||||||
|
``boundary`` is the boundary string used throughout a single request to
|
||||||
|
separate variables.
|
||||||
|
|
||||||
|
``paramname`` is the name of the variable in this request.
|
||||||
|
|
||||||
|
``filesize`` is the size of the file data.
|
||||||
|
|
||||||
|
``filename`` if specified is the filename to give to this field. This
|
||||||
|
field is only useful to the server for determining the original filename.
|
||||||
|
|
||||||
|
``filetype`` if specified is the MIME type of this file.
|
||||||
|
|
||||||
|
The actual file data should be sent after this header has been sent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return MultipartParam(paramname, filesize=filesize, filename=filename,
|
||||||
|
filetype=filetype).encode_hdr(boundary)
|
||||||
|
|
||||||
|
def get_body_size(params, boundary):
|
||||||
|
"""Returns the number of bytes that the multipart/form-data encoding
|
||||||
|
of ``params`` will be."""
|
||||||
|
size = sum(p.get_size(boundary) for p in MultipartParam.from_params(params))
|
||||||
|
return size + len(boundary) + 6
|
||||||
|
|
||||||
|
def get_headers(params, boundary):
|
||||||
|
"""Returns a dictionary with Content-Type and Content-Length headers
|
||||||
|
for the multipart/form-data encoding of ``params``."""
|
||||||
|
headers = {}
|
||||||
|
boundary = urllib.quote_plus(boundary)
|
||||||
|
headers['Content-Type'] = "multipart/form-data; boundary=%s" % boundary
|
||||||
|
headers['Content-Length'] = str(get_body_size(params, boundary))
|
||||||
|
return headers
|
||||||
|
|
||||||
|
class multipart_yielder:
|
||||||
|
def __init__(self, params, boundary, cb):
|
||||||
|
self.params = params
|
||||||
|
self.boundary = boundary
|
||||||
|
self.cb = cb
|
||||||
|
|
||||||
|
self.i = 0
|
||||||
|
self.p = None
|
||||||
|
self.param_iter = None
|
||||||
|
self.current = 0
|
||||||
|
self.total = get_body_size(params, boundary)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
"""generator function to yield multipart/form-data representation
|
||||||
|
of parameters"""
|
||||||
|
if self.param_iter is not None:
|
||||||
|
try:
|
||||||
|
block = self.param_iter.next()
|
||||||
|
self.current += len(block)
|
||||||
|
if self.cb:
|
||||||
|
self.cb(self.p, self.current, self.total)
|
||||||
|
return block
|
||||||
|
except StopIteration:
|
||||||
|
self.p = None
|
||||||
|
self.param_iter = None
|
||||||
|
|
||||||
|
if self.i is None:
|
||||||
|
raise StopIteration
|
||||||
|
elif self.i >= len(self.params):
|
||||||
|
self.param_iter = None
|
||||||
|
self.p = None
|
||||||
|
self.i = None
|
||||||
|
block = "--%s--\r\n" % self.boundary
|
||||||
|
self.current += len(block)
|
||||||
|
if self.cb:
|
||||||
|
self.cb(self.p, self.current, self.total)
|
||||||
|
return block
|
||||||
|
|
||||||
|
self.p = self.params[self.i]
|
||||||
|
self.param_iter = self.p.iter_encode(self.boundary)
|
||||||
|
self.i += 1
|
||||||
|
return self.next()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.i = 0
|
||||||
|
self.current = 0
|
||||||
|
for param in self.params:
|
||||||
|
param.reset()
|
||||||
|
|
||||||
|
def multipart_encode(params, boundary=None, cb=None):
|
||||||
|
"""Encode ``params`` as multipart/form-data.
|
||||||
|
|
||||||
|
``params`` should be a sequence of (name, value) pairs or MultipartParam
|
||||||
|
objects, or a mapping of names to values.
|
||||||
|
Values are either strings parameter values, or file-like objects to use as
|
||||||
|
the parameter value. The file-like objects must support .read() and either
|
||||||
|
.fileno() or both .seek() and .tell().
|
||||||
|
|
||||||
|
If ``boundary`` is set, then it as used as the MIME boundary. Otherwise
|
||||||
|
a randomly generated boundary will be used. In either case, if the
|
||||||
|
boundary string appears in the parameter values a ValueError will be
|
||||||
|
raised.
|
||||||
|
|
||||||
|
If ``cb`` is set, it should be a callback which will get called as blocks
|
||||||
|
of data are encoded. It will be called with (param, current, total),
|
||||||
|
indicating the current parameter being encoded, the current amount encoded,
|
||||||
|
and the total amount to encode.
|
||||||
|
|
||||||
|
Returns a tuple of `datagen`, `headers`, where `datagen` is a
|
||||||
|
generator that will yield blocks of data that make up the encoded
|
||||||
|
parameters, and `headers` is a dictionary with the assoicated
|
||||||
|
Content-Type and Content-Length headers.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
>>> datagen, headers = multipart_encode( [("key", "value1"), ("key", "value2")] )
|
||||||
|
>>> s = "".join(datagen)
|
||||||
|
>>> assert "value2" in s and "value1" in s
|
||||||
|
|
||||||
|
>>> p = MultipartParam("key", "value2")
|
||||||
|
>>> datagen, headers = multipart_encode( [("key", "value1"), p] )
|
||||||
|
>>> s = "".join(datagen)
|
||||||
|
>>> assert "value2" in s and "value1" in s
|
||||||
|
|
||||||
|
>>> datagen, headers = multipart_encode( {"key": "value1"} )
|
||||||
|
>>> s = "".join(datagen)
|
||||||
|
>>> assert "value2" not in s and "value1" in s
|
||||||
|
|
||||||
|
"""
|
||||||
|
if boundary is None:
|
||||||
|
boundary = gen_boundary()
|
||||||
|
else:
|
||||||
|
boundary = urllib.quote_plus(boundary)
|
||||||
|
|
||||||
|
headers = get_headers(params, boundary)
|
||||||
|
params = MultipartParam.from_params(params)
|
||||||
|
|
||||||
|
return multipart_yielder(params, boundary, cb), headers
|
216
src/libs/qt-breakpad/poster/streaminghttp.py
Normal file
216
src/libs/qt-breakpad/poster/streaminghttp.py
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# Copyright (c) 2010 Chris AtLee
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
# THE SOFTWARE.
|
||||||
|
"""Streaming HTTP uploads module.
|
||||||
|
|
||||||
|
This module extends the standard httplib and urllib2 objects so that
|
||||||
|
iterable objects can be used in the body of HTTP requests.
|
||||||
|
|
||||||
|
In most cases all one should have to do is call :func:`register_openers()`
|
||||||
|
to register the new streaming http handlers which will take priority over
|
||||||
|
the default handlers, and then you can use iterable objects in the body
|
||||||
|
of HTTP requests.
|
||||||
|
|
||||||
|
**N.B.** You must specify a Content-Length header if using an iterable object
|
||||||
|
since there is no way to determine in advance the total size that will be
|
||||||
|
yielded, and there is no way to reset an interator.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
>>> from StringIO import StringIO
|
||||||
|
>>> import urllib2, poster.streaminghttp
|
||||||
|
|
||||||
|
>>> opener = poster.streaminghttp.register_openers()
|
||||||
|
|
||||||
|
>>> s = "Test file data"
|
||||||
|
>>> f = StringIO(s)
|
||||||
|
|
||||||
|
>>> req = urllib2.Request("http://localhost:5000", f,
|
||||||
|
... {'Content-Length': str(len(s))})
|
||||||
|
"""
|
||||||
|
|
||||||
|
import httplib, urllib2, socket
|
||||||
|
from httplib import NotConnected
|
||||||
|
|
||||||
|
__all__ = ['StreamingHTTPConnection', 'StreamingHTTPRedirectHandler',
|
||||||
|
'StreamingHTTPHandler', 'register_openers']
|
||||||
|
|
||||||
|
if hasattr(httplib, 'HTTPS'):
|
||||||
|
__all__.extend(['StreamingHTTPSHandler', 'StreamingHTTPSConnection'])
|
||||||
|
|
||||||
|
class _StreamingHTTPMixin:
|
||||||
|
"""Mixin class for HTTP and HTTPS connections that implements a streaming
|
||||||
|
send method."""
|
||||||
|
def send(self, value):
|
||||||
|
"""Send ``value`` to the server.
|
||||||
|
|
||||||
|
``value`` can be a string object, a file-like object that supports
|
||||||
|
a .read() method, or an iterable object that supports a .next()
|
||||||
|
method.
|
||||||
|
"""
|
||||||
|
# Based on python 2.6's httplib.HTTPConnection.send()
|
||||||
|
if self.sock is None:
|
||||||
|
if self.auto_open:
|
||||||
|
self.connect()
|
||||||
|
else:
|
||||||
|
raise NotConnected()
|
||||||
|
|
||||||
|
# send the data to the server. if we get a broken pipe, then close
|
||||||
|
# the socket. we want to reconnect when somebody tries to send again.
|
||||||
|
#
|
||||||
|
# NOTE: we DO propagate the error, though, because we cannot simply
|
||||||
|
# ignore the error... the caller will know if they can retry.
|
||||||
|
if self.debuglevel > 0:
|
||||||
|
print "send:", repr(value)
|
||||||
|
try:
|
||||||
|
blocksize = 8192
|
||||||
|
if hasattr(value, 'read') :
|
||||||
|
if hasattr(value, 'seek'):
|
||||||
|
value.seek(0)
|
||||||
|
if self.debuglevel > 0:
|
||||||
|
print "sendIng a read()able"
|
||||||
|
data = value.read(blocksize)
|
||||||
|
while data:
|
||||||
|
self.sock.sendall(data)
|
||||||
|
data = value.read(blocksize)
|
||||||
|
elif hasattr(value, 'next'):
|
||||||
|
if hasattr(value, 'reset'):
|
||||||
|
value.reset()
|
||||||
|
if self.debuglevel > 0:
|
||||||
|
print "sendIng an iterable"
|
||||||
|
for data in value:
|
||||||
|
self.sock.sendall(data)
|
||||||
|
else:
|
||||||
|
self.sock.sendall(value)
|
||||||
|
except socket.error, v:
|
||||||
|
if v[0] == 32: # Broken pipe
|
||||||
|
self.close()
|
||||||
|
raise
|
||||||
|
|
||||||
|
class StreamingHTTPConnection(_StreamingHTTPMixin, httplib.HTTPConnection):
|
||||||
|
"""Subclass of `httplib.HTTPConnection` that overrides the `send()` method
|
||||||
|
to support iterable body objects"""
|
||||||
|
|
||||||
|
class StreamingHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
|
||||||
|
"""Subclass of `urllib2.HTTPRedirectHandler` that overrides the
|
||||||
|
`redirect_request` method to properly handle redirected POST requests
|
||||||
|
|
||||||
|
This class is required because python 2.5's HTTPRedirectHandler does
|
||||||
|
not remove the Content-Type or Content-Length headers when requesting
|
||||||
|
the new resource, but the body of the original request is not preserved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
handler_order = urllib2.HTTPRedirectHandler.handler_order - 1
|
||||||
|
|
||||||
|
# From python2.6 urllib2's HTTPRedirectHandler
|
||||||
|
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
||||||
|
"""Return a Request or None in response to a redirect.
|
||||||
|
|
||||||
|
This is called by the http_error_30x methods when a
|
||||||
|
redirection response is received. If a redirection should
|
||||||
|
take place, return a new Request to allow http_error_30x to
|
||||||
|
perform the redirect. Otherwise, raise HTTPError if no-one
|
||||||
|
else should try to handle this url. Return None if you can't
|
||||||
|
but another Handler might.
|
||||||
|
"""
|
||||||
|
m = req.get_method()
|
||||||
|
if (code in (301, 302, 303, 307) and m in ("GET", "HEAD")
|
||||||
|
or code in (301, 302, 303) and m == "POST"):
|
||||||
|
# Strictly (according to RFC 2616), 301 or 302 in response
|
||||||
|
# to a POST MUST NOT cause a redirection without confirmation
|
||||||
|
# from the user (of urllib2, in this case). In practice,
|
||||||
|
# essentially all clients do redirect in this case, so we
|
||||||
|
# do the same.
|
||||||
|
# be conciliant with URIs containing a space
|
||||||
|
newurl = newurl.replace(' ', '%20')
|
||||||
|
newheaders = dict((k, v) for k, v in req.headers.items()
|
||||||
|
if k.lower() not in (
|
||||||
|
"content-length", "content-type")
|
||||||
|
)
|
||||||
|
return urllib2.Request(newurl,
|
||||||
|
headers=newheaders,
|
||||||
|
origin_req_host=req.get_origin_req_host(),
|
||||||
|
unverifiable=True)
|
||||||
|
else:
|
||||||
|
raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
|
||||||
|
|
||||||
|
class StreamingHTTPHandler(urllib2.HTTPHandler):
|
||||||
|
"""Subclass of `urllib2.HTTPHandler` that uses
|
||||||
|
StreamingHTTPConnection as its http connection class."""
|
||||||
|
|
||||||
|
handler_order = urllib2.HTTPHandler.handler_order - 1
|
||||||
|
|
||||||
|
def http_open(self, req):
|
||||||
|
"""Open a StreamingHTTPConnection for the given request"""
|
||||||
|
return self.do_open(StreamingHTTPConnection, req)
|
||||||
|
|
||||||
|
def http_request(self, req):
|
||||||
|
"""Handle a HTTP request. Make sure that Content-Length is specified
|
||||||
|
if we're using an interable value"""
|
||||||
|
# Make sure that if we're using an iterable object as the request
|
||||||
|
# body, that we've also specified Content-Length
|
||||||
|
if req.has_data():
|
||||||
|
data = req.get_data()
|
||||||
|
if hasattr(data, 'read') or hasattr(data, 'next'):
|
||||||
|
if not req.has_header('Content-length'):
|
||||||
|
raise ValueError(
|
||||||
|
"No Content-Length specified for iterable body")
|
||||||
|
return urllib2.HTTPHandler.do_request_(self, req)
|
||||||
|
|
||||||
|
if hasattr(httplib, 'HTTPS'):
|
||||||
|
class StreamingHTTPSConnection(_StreamingHTTPMixin,
|
||||||
|
httplib.HTTPSConnection):
|
||||||
|
"""Subclass of `httplib.HTTSConnection` that overrides the `send()`
|
||||||
|
method to support iterable body objects"""
|
||||||
|
|
||||||
|
class StreamingHTTPSHandler(urllib2.HTTPSHandler):
|
||||||
|
"""Subclass of `urllib2.HTTPSHandler` that uses
|
||||||
|
StreamingHTTPSConnection as its http connection class."""
|
||||||
|
|
||||||
|
handler_order = urllib2.HTTPSHandler.handler_order - 1
|
||||||
|
|
||||||
|
def https_open(self, req):
|
||||||
|
return self.do_open(StreamingHTTPSConnection, req)
|
||||||
|
|
||||||
|
def https_request(self, req):
|
||||||
|
# Make sure that if we're using an iterable object as the request
|
||||||
|
# body, that we've also specified Content-Length
|
||||||
|
if req.has_data():
|
||||||
|
data = req.get_data()
|
||||||
|
if hasattr(data, 'read') or hasattr(data, 'next'):
|
||||||
|
if not req.has_header('Content-length'):
|
||||||
|
raise ValueError(
|
||||||
|
"No Content-Length specified for iterable body")
|
||||||
|
return urllib2.HTTPSHandler.do_request_(self, req)
|
||||||
|
|
||||||
|
|
||||||
|
def register_openers():
|
||||||
|
"""Register the streaming http handlers in the global urllib2 default
|
||||||
|
opener object.
|
||||||
|
|
||||||
|
Returns the created OpenerDirector object."""
|
||||||
|
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
|
||||||
|
if hasattr(httplib, "HTTPS"):
|
||||||
|
handlers.append(StreamingHTTPSHandler)
|
||||||
|
|
||||||
|
opener = urllib2.build_opener(*handlers)
|
||||||
|
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
|
||||||
|
return opener
|
64
src/libs/qt-breakpad/qtbreakpad.pri
Normal file
64
src/libs/qt-breakpad/qtbreakpad.pri
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
isEmpty(BREAKPAD_SOURCE_DIR): return()
|
||||||
|
|
||||||
|
HEADERS += $$PWD/qtbreakpad/qtsystemexceptionhandler.h
|
||||||
|
SOURCES += $$PWD/qtbreakpad/qtsystemexceptionhandler.cpp
|
||||||
|
|
||||||
|
DEFINES += ENABLE_QT_BREAKPAD
|
||||||
|
|
||||||
|
win32:BREAKPAD_SOURCE_DIR ~= s,\\,/,
|
||||||
|
|
||||||
|
INCLUDEPATH += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src \
|
||||||
|
$$PWD/qtbreakpad
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/string_conversion.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/convert_UTF.c \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/md5.cc
|
||||||
|
|
||||||
|
linux:SOURCES += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/minidump_file_writer.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/log/log.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/handler/exception_handler.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/handler/minidump_descriptor.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/linux/guid_creator.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/dump_writer_common/thread_info.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/dump_writer_common/ucontext_reader.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/minidump_writer/linux_dumper.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/minidump_writer/minidump_writer.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/minidump_writer/linux_ptrace_dumper.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/microdump_writer/microdump_writer.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/linux/file_id.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/linux/elfutils.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/linux/linux_libc_support.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/linux/memory_mapped_file.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/linux/safe_readlink.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/linux/crash_generation/crash_generation_client.cc
|
||||||
|
|
||||||
|
win32:SOURCES += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/windows/guid_string.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/windows/handler/exception_handler.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/windows/crash_generation/minidump_generator.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/windows/crash_generation/client_info.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/windows/crash_generation/crash_generation_client.cc
|
||||||
|
|
||||||
|
macos {
|
||||||
|
SOURCES += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/minidump_file_writer.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/mac/crash_generation/crash_generation_client.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/mac/handler/exception_handler.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/mac/handler/minidump_generator.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/mac/handler/breakpad_nlist_64.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/mac/handler/dynamic_images.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/client/mac/handler/protected_memory_allocator.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/bootstrap_compat.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/file_id.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/macho_id.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/macho_reader.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/macho_utilities.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/macho_walker.cc \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/string_utilities.cc
|
||||||
|
OBJECTIVE_SOURCES += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src/common/mac/MachIPC.mm
|
||||||
|
LIBS += -framework Foundation
|
||||||
|
}
|
222
src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp
Normal file
222
src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.cpp
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "qtsystemexceptionhandler.h"
|
||||||
|
|
||||||
|
#include <utils/fileutils.h>
|
||||||
|
#include <utils/hostosinfo.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
# include "client/linux/handler/exception_handler.h"
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
# include "client/windows/handler/exception_handler.h"
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
# include "client/mac/handler/exception_handler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
static bool exceptionHandlerCallback(const google_breakpad::MinidumpDescriptor& descriptor,
|
||||||
|
void* /*context*/,
|
||||||
|
bool succeeded)
|
||||||
|
{
|
||||||
|
if (!succeeded)
|
||||||
|
return succeeded;
|
||||||
|
|
||||||
|
const QStringList argumentList = {
|
||||||
|
QString::fromLocal8Bit(descriptor.path()),
|
||||||
|
QString::number(QtSystemExceptionHandler::startTime().toTime_t()),
|
||||||
|
QCoreApplication::applicationName(),
|
||||||
|
QCoreApplication::applicationVersion(),
|
||||||
|
QtSystemExceptionHandler::plugins(),
|
||||||
|
QtSystemExceptionHandler::buildVersion(),
|
||||||
|
QCoreApplication::applicationFilePath()
|
||||||
|
};
|
||||||
|
|
||||||
|
return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList);
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
static bool exceptionHandlerCallback(const char *dump_dir,
|
||||||
|
const char *minidump_id,
|
||||||
|
void *context,
|
||||||
|
bool succeeded)
|
||||||
|
{
|
||||||
|
Q_UNUSED(context);
|
||||||
|
|
||||||
|
if (!succeeded)
|
||||||
|
return succeeded;
|
||||||
|
|
||||||
|
const QString path = QString::fromLocal8Bit(dump_dir) + '/'
|
||||||
|
+ QString::fromLocal8Bit(minidump_id) + ".dmp";
|
||||||
|
const QStringList argumentList = {
|
||||||
|
path,
|
||||||
|
QString::number(QtSystemExceptionHandler::startTime().toTime_t()),
|
||||||
|
QCoreApplication::applicationName(),
|
||||||
|
QCoreApplication::applicationVersion(),
|
||||||
|
QtSystemExceptionHandler::plugins(),
|
||||||
|
QtSystemExceptionHandler::buildVersion(),
|
||||||
|
QCoreApplication::applicationFilePath()
|
||||||
|
};
|
||||||
|
|
||||||
|
return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList);
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
static bool exceptionHandlerCallback(const wchar_t* dump_path,
|
||||||
|
const wchar_t* minidump_id,
|
||||||
|
void* context,
|
||||||
|
EXCEPTION_POINTERS* exinfo,
|
||||||
|
MDRawAssertionInfo* assertion,
|
||||||
|
bool succeeded)
|
||||||
|
{
|
||||||
|
Q_UNUSED(assertion);
|
||||||
|
Q_UNUSED(exinfo);
|
||||||
|
Q_UNUSED(context);
|
||||||
|
|
||||||
|
if (!succeeded)
|
||||||
|
return succeeded;
|
||||||
|
|
||||||
|
const QString path = QString::fromWCharArray(dump_path, int(wcslen(dump_path))) + '/'
|
||||||
|
+ QString::fromWCharArray(minidump_id, int(wcslen(minidump_id))) + ".dmp";
|
||||||
|
const QStringList argumentList = {
|
||||||
|
path,
|
||||||
|
QString::number(QtSystemExceptionHandler::startTime().toTime_t()),
|
||||||
|
QCoreApplication::applicationName(),
|
||||||
|
QCoreApplication::applicationVersion(),
|
||||||
|
QtSystemExceptionHandler::plugins(),
|
||||||
|
QtSystemExceptionHandler::buildVersion(),
|
||||||
|
QCoreApplication::applicationFilePath()
|
||||||
|
};
|
||||||
|
|
||||||
|
return !QProcess::execute(QtSystemExceptionHandler::crashHandlerPath(), argumentList);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static QDateTime s_startTime;
|
||||||
|
static QString s_plugins;
|
||||||
|
static QString s_buildVersion;
|
||||||
|
static QString s_crashHandlerPath;
|
||||||
|
|
||||||
|
#if defined(Q_OS_LINUX)
|
||||||
|
QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath)
|
||||||
|
: exceptionHandler(new google_breakpad::ExceptionHandler(
|
||||||
|
google_breakpad::MinidumpDescriptor(QDir::tempPath().toStdString()),
|
||||||
|
NULL,
|
||||||
|
exceptionHandlerCallback,
|
||||||
|
NULL,
|
||||||
|
true,
|
||||||
|
-1))
|
||||||
|
{
|
||||||
|
init(libexecPath);
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath)
|
||||||
|
: exceptionHandler(new google_breakpad::ExceptionHandler(
|
||||||
|
QDir::tempPath().toStdString(),
|
||||||
|
NULL,
|
||||||
|
exceptionHandlerCallback,
|
||||||
|
NULL,
|
||||||
|
true,
|
||||||
|
NULL))
|
||||||
|
{
|
||||||
|
init(libexecPath);
|
||||||
|
}
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
QtSystemExceptionHandler::QtSystemExceptionHandler(const QString &libexecPath)
|
||||||
|
: exceptionHandler(new google_breakpad::ExceptionHandler(
|
||||||
|
QDir::tempPath().toStdWString(),
|
||||||
|
NULL,
|
||||||
|
exceptionHandlerCallback,
|
||||||
|
NULL,
|
||||||
|
google_breakpad::ExceptionHandler::HANDLER_ALL))
|
||||||
|
{
|
||||||
|
init(libexecPath);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
QtSystemExceptionHandler::QtSystemExceptionHandler(const QString & /*libexecPath*/)
|
||||||
|
: exceptionHandler(0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void QtSystemExceptionHandler::init(const QString &libexecPath)
|
||||||
|
{
|
||||||
|
s_startTime = QDateTime::currentDateTime();
|
||||||
|
s_crashHandlerPath = libexecPath + Utils::HostOsInfo::withExecutableSuffix("/qtcrashhandler");
|
||||||
|
}
|
||||||
|
|
||||||
|
QtSystemExceptionHandler::~QtSystemExceptionHandler()
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_QT_BREAKPAD
|
||||||
|
delete exceptionHandler;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtSystemExceptionHandler::setPlugins(const QStringList &pluginNameList)
|
||||||
|
{
|
||||||
|
s_plugins = QString("{%1}").arg(pluginNameList.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtSystemExceptionHandler::setBuildVersion(const QString &version)
|
||||||
|
{
|
||||||
|
s_buildVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtSystemExceptionHandler::buildVersion()
|
||||||
|
{
|
||||||
|
return s_buildVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtSystemExceptionHandler::plugins()
|
||||||
|
{
|
||||||
|
return s_plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtSystemExceptionHandler::setCrashHandlerPath(const QString &crashHandlerPath)
|
||||||
|
{
|
||||||
|
s_crashHandlerPath = crashHandlerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QtSystemExceptionHandler::crashHandlerPath()
|
||||||
|
{
|
||||||
|
return s_crashHandlerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtSystemExceptionHandler::crash()
|
||||||
|
{
|
||||||
|
int *a = (int*)0x42;
|
||||||
|
|
||||||
|
fprintf(stdout, "Going to crash...\n");
|
||||||
|
fprintf(stdout, "A = %d", *a);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime QtSystemExceptionHandler::startTime()
|
||||||
|
{
|
||||||
|
return s_startTime;
|
||||||
|
}
|
56
src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h
Normal file
56
src/libs/qt-breakpad/qtbreakpad/qtsystemexceptionhandler.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
class ExceptionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QtSystemExceptionHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QtSystemExceptionHandler(const QString &libexecPath);
|
||||||
|
~QtSystemExceptionHandler();
|
||||||
|
|
||||||
|
static void crash();
|
||||||
|
static void setPlugins(const QStringList &pluginNameList);
|
||||||
|
static void setBuildVersion(const QString &version);
|
||||||
|
static void setCrashHandlerPath(const QString &crashHandlerPath);
|
||||||
|
|
||||||
|
static QString plugins();
|
||||||
|
static QString buildVersion();
|
||||||
|
static QString crashHandlerPath();
|
||||||
|
|
||||||
|
static QDateTime startTime();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void init(const QString &libexecPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
google_breakpad::ExceptionHandler *exceptionHandler = nullptr;
|
||||||
|
};
|
271
src/libs/qt-breakpad/qtbreakpadsymbols
Executable file
271
src/libs/qt-breakpad/qtbreakpadsymbols
Executable file
@@ -0,0 +1,271 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from poster.encode import multipart_encode
|
||||||
|
from poster.encode import MultipartParam
|
||||||
|
from poster.streaminghttp import register_openers
|
||||||
|
import urllib2
|
||||||
|
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
|
register_openers()
|
||||||
|
|
||||||
|
breakpadSourceDir = os.environ['BREAKPAD_SOURCE_DIR']
|
||||||
|
breakpadUploadUrl = os.environ['BREAKPAD_UPLOAD_URL']
|
||||||
|
breakpadUserName = os.environ['BREAKPAD_USER_NAME']
|
||||||
|
breakpadUserPassword = os.environ['BREAKPAD_USER_PASSWORD']
|
||||||
|
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
nullfilename = 'nul:'
|
||||||
|
else:
|
||||||
|
nullfilename = '/dev/null'
|
||||||
|
nullfile = open(nullfilename, 'r+b')
|
||||||
|
|
||||||
|
|
||||||
|
def stdoutFromProcess(process):
|
||||||
|
(stdout, stderr) = process.communicate()
|
||||||
|
if stderr:
|
||||||
|
print stderr
|
||||||
|
raise SystemError()
|
||||||
|
sys.exit(1)
|
||||||
|
return stdout
|
||||||
|
|
||||||
|
def toolPath():
|
||||||
|
if sys.platform == 'linux2':
|
||||||
|
dumpsymsPath = os.path.join(breakpadSourceDir, 'src/tools/linux/dump_syms/dump_syms')
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
dumpsymsPath = os.path.join(breakpadSourceDir, 'src/tools/mac/dump_syms/build/Release/dump_syms')
|
||||||
|
elif sys.platform == 'win32':
|
||||||
|
dumpsymsPath = os.path.join(breakpadSourceDir,'src\\tools\\windows\\binaries\\dump_syms.exe')
|
||||||
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
return dumpsymsPath
|
||||||
|
|
||||||
|
gitRootDirectoryCache = {}
|
||||||
|
|
||||||
|
def gitRootDirectory(path):
|
||||||
|
directory = os.path.dirname(path)
|
||||||
|
if directory in gitRootDirectoryCache:
|
||||||
|
return gitRootDirectoryCache[directory]
|
||||||
|
directoryList = directory.split(os.sep)
|
||||||
|
while len(directoryList) > 0:
|
||||||
|
gitDirectory = os.sep.join(directoryList + ['.git'])
|
||||||
|
if os.path.exists(gitDirectory):
|
||||||
|
gitDirectory = os.sep.join(directoryList)
|
||||||
|
gitRootDirectoryCache[directory] = gitDirectory
|
||||||
|
return gitDirectory
|
||||||
|
directoryList.pop()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
gitCommitShaCache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def gitCommitSha(gitRootPath):
|
||||||
|
if gitRootPath in gitCommitShaCache:
|
||||||
|
return gitCommitShaCache[gitRootPath]
|
||||||
|
gitProcess = subprocess.Popen(shlex.split('git rev-parse HEAD'),
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=nullfile, cwd=gitRootPath)
|
||||||
|
commitSha = stdoutFromProcess(gitProcess).strip('\n')
|
||||||
|
gitCommitShaCache[gitRootPath] = commitSha
|
||||||
|
return commitSha
|
||||||
|
|
||||||
|
|
||||||
|
gitRemoteRepositoryCache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def gitRemoteRepository(gitRootPath):
|
||||||
|
if gitRootPath in gitRemoteRepositoryCache:
|
||||||
|
return gitRemoteRepositoryCache[gitRootPath]
|
||||||
|
gitProcess = \
|
||||||
|
subprocess.Popen(shlex.split('git config remote.origin.url'),
|
||||||
|
stdout=subprocess.PIPE, stderr=nullfile,
|
||||||
|
cwd=gitRootPath)
|
||||||
|
repository = stdoutFromProcess(gitProcess).strip('\n')
|
||||||
|
gitRemoteRepositoryCache[gitRootPath] = repository
|
||||||
|
return repository
|
||||||
|
|
||||||
|
|
||||||
|
gitFilePathCache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def populateFilePathCache(gitRootPath):
|
||||||
|
if gitRootPath not in gitFilePathCache:
|
||||||
|
gitProcess = \
|
||||||
|
subprocess.Popen(shlex.split('git ls-files --full-name'),
|
||||||
|
stdout=subprocess.PIPE, stderr=nullfile,
|
||||||
|
cwd=gitRootPath)
|
||||||
|
fileNameList = stdoutFromProcess(gitProcess).split('\n')
|
||||||
|
filePathCache = {}
|
||||||
|
for fileName in fileNameList:
|
||||||
|
baseFileName = os.path.basename(fileName)
|
||||||
|
filePathCache[baseFileName] = fileName
|
||||||
|
gitFilePathCache[gitRootPath] = filePathCache
|
||||||
|
|
||||||
|
|
||||||
|
def gitFilePath(path, gitRootPath):
|
||||||
|
if not gitRootPath:
|
||||||
|
return path
|
||||||
|
|
||||||
|
populateFilePathCache(gitRootPath)
|
||||||
|
|
||||||
|
baseFileName = os.path.basename(path)
|
||||||
|
filePathCache = gitFilePathCache[gitRootPath]
|
||||||
|
if baseFileName in filePathCache:
|
||||||
|
return filePathCache[baseFileName]
|
||||||
|
else:
|
||||||
|
return os.path.relpath(path, gitRootPath)
|
||||||
|
|
||||||
|
|
||||||
|
def isInRepository(path):
|
||||||
|
gitRootPath = gitRootDirectory(path)
|
||||||
|
if not gitRootPath:
|
||||||
|
return False
|
||||||
|
|
||||||
|
populateFilePathCache(gitRootPath)
|
||||||
|
baseFileName = os.path.basename(path)
|
||||||
|
if baseFileName in gitFilePathCache[gitRootPath]:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def sendSymbolsToServer(
|
||||||
|
breakpadUploadUrl,
|
||||||
|
symbolText,
|
||||||
|
codeFile,
|
||||||
|
debugFile,
|
||||||
|
debugIdentifier,
|
||||||
|
operatingSystem,
|
||||||
|
cpu,
|
||||||
|
):
|
||||||
|
(data, headers) = multipart_encode({
|
||||||
|
'symbol_file': MultipartParam('symbol_file', value=symbolText,
|
||||||
|
filename='symbol_file'),
|
||||||
|
'code_file': codeFile,
|
||||||
|
'debug_file': debugFile,
|
||||||
|
'debug_identifier': debugIdentifier,
|
||||||
|
'os': operatingSystem,
|
||||||
|
'cpu': cpu,
|
||||||
|
})
|
||||||
|
request = urllib2.Request(breakpadUploadUrl, data, headers)
|
||||||
|
auth = base64.encodestring('%s:%s' % (breakpadUserName,
|
||||||
|
breakpadUserPassword))[:-1] # This is just standard un/pw encoding
|
||||||
|
request.add_header('Authorization', 'Basic %s' % auth) # Add Auth header to request
|
||||||
|
result = urllib2.urlopen(request).read()
|
||||||
|
|
||||||
|
|
||||||
|
def generateSymbolFilesAndSend(binaryPath, projectPath):
|
||||||
|
dumpsymsPath = toolPath()
|
||||||
|
|
||||||
|
originalBinaryPath = binaryPath
|
||||||
|
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
dsymutilProcess = \
|
||||||
|
subprocess.Popen(shlex.split('/usr/bin/dsymutil "'
|
||||||
|
+ binaryPath + '"'), stdout=nullfile,
|
||||||
|
stderr=nullfile)
|
||||||
|
dsymutilProcess.wait()
|
||||||
|
binaryPath += os.path.join('.dSYM/Contents/Resources/DWARF/',
|
||||||
|
os.path.basename(binaryPath))
|
||||||
|
|
||||||
|
binaryPath = os.path.normpath(binaryPath)
|
||||||
|
|
||||||
|
dumpsymsProcess = subprocess.Popen(shlex.split('"' + dumpsymsPath
|
||||||
|
+ '" "' + binaryPath + '"'), stdout=subprocess.PIPE,
|
||||||
|
stderr=nullfile, cwd=projectPath)
|
||||||
|
symbolList = stdoutFromProcess(dumpsymsProcess).split('\n')
|
||||||
|
|
||||||
|
outputTextList = []
|
||||||
|
codeFile = os.path.basename(binaryPath)
|
||||||
|
debugFile = ''
|
||||||
|
debugIdentifier = ''
|
||||||
|
operatingSystem = ''
|
||||||
|
cpu = ''
|
||||||
|
moduleNotParsed = True
|
||||||
|
for line in symbolList:
|
||||||
|
line = line.strip('\n').strip('\r')
|
||||||
|
if line[:4] == 'FILE':
|
||||||
|
(marker, idnumber, filepath) = line.split(' ', 2)
|
||||||
|
filepath = os.path.normpath(os.path.join(projectPath,
|
||||||
|
filepath))
|
||||||
|
|
||||||
|
if isInRepository(filepath):
|
||||||
|
gitRootPath = gitRootDirectory(filepath)
|
||||||
|
commitSha = gitCommitSha(gitRootPath)
|
||||||
|
repository = \
|
||||||
|
gitRemoteRepository(gitRootPath).replace(':', '/')
|
||||||
|
relativeFilePath = gitFilePath(filepath,
|
||||||
|
gitRootPath).replace('\\', '/')
|
||||||
|
outputTextList.append('FILE ' + idnumber + ' git:'
|
||||||
|
+ repository + ':' + relativeFilePath + ':'
|
||||||
|
+ commitSha)
|
||||||
|
else:
|
||||||
|
outputTextList.append(line)
|
||||||
|
elif moduleNotParsed and line[:6] == 'MODULE':
|
||||||
|
(operatingSystem, cpu, debugIdentifier, debugFile) = \
|
||||||
|
line[7:].split(' ', 3)
|
||||||
|
moduleNotParsed = False
|
||||||
|
elif line:
|
||||||
|
outputTextList.append(line)
|
||||||
|
|
||||||
|
if moduleNotParsed:
|
||||||
|
print 'Module not parsed'
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sendSymbolsToServer(
|
||||||
|
breakpadUploadUrl,
|
||||||
|
'\n'.join(outputTextList),
|
||||||
|
codeFile,
|
||||||
|
debugFile,
|
||||||
|
debugIdentifier,
|
||||||
|
operatingSystem,
|
||||||
|
cpu,
|
||||||
|
)
|
||||||
|
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
shutil.rmtree(originalBinaryPath + '.dSYM')
|
||||||
|
|
||||||
|
|
||||||
|
def testForBreakpad():
|
||||||
|
try:
|
||||||
|
dumpsymsPath = toolPath()
|
||||||
|
if not dumpsymsPath:
|
||||||
|
sys.exit(1)
|
||||||
|
subprocess.Popen([dumpsymsPath], stdout=nullfile,
|
||||||
|
stderr=nullfile)
|
||||||
|
except (OSError, KeyError):
|
||||||
|
print 'No dumpsyms can be executed. Maybe BREAKPAD_SOURCE_DIR is wrong.'
|
||||||
|
sys.exit(1)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
usage = 'usage: %prog [options] binary projectpath'
|
||||||
|
parser = OptionParser(usage=usage)
|
||||||
|
parser.add_option('-v', '--verbose', action='store_true',
|
||||||
|
dest='verbose')
|
||||||
|
parser.add_option('-e', '--breakpad-exists', action='store_true',
|
||||||
|
dest='testForBreakpad')
|
||||||
|
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
if options.testForBreakpad == True:
|
||||||
|
testForBreakpad()
|
||||||
|
if len(args) > 1:
|
||||||
|
generateSymbolFilesAndSend(args[0], args[1])
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
src/libs/qt-breakpad/qtbreakpadsymbols.bat
Normal file
1
src/libs/qt-breakpad/qtbreakpadsymbols.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@python qtbreakpadsymbols %*
|
21
src/libs/qt-breakpad/qtcrashhandler.pri
Normal file
21
src/libs/qt-breakpad/qtcrashhandler.pri
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
isEmpty(BREAKPAD_SOURCE_DIR): return()
|
||||||
|
|
||||||
|
QT += network
|
||||||
|
|
||||||
|
INCLUDEPATH += \
|
||||||
|
$$BREAKPAD_SOURCE_DIR/src \
|
||||||
|
$$PWD/qtcrashhandler
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/qtcrashhandler/main.cpp \
|
||||||
|
$$PWD/qtcrashhandler/mainwidget.cpp \
|
||||||
|
$$PWD/qtcrashhandler/detaildialog.cpp \
|
||||||
|
$$PWD/qtcrashhandler/dumpsender.cpp
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
$$PWD/qtcrashhandler/mainwidget.h \
|
||||||
|
$$PWD/qtcrashhandler/detaildialog.h \
|
||||||
|
$$PWD/qtcrashhandler/dumpsender.h
|
||||||
|
|
||||||
|
FORMS += \
|
||||||
|
$$PWD/qtcrashhandler/mainwidget.ui
|
55
src/libs/qt-breakpad/qtcrashhandler/detaildialog.cpp
Normal file
55
src/libs/qt-breakpad/qtcrashhandler/detaildialog.cpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "detaildialog.h"
|
||||||
|
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QTextBrowser>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
DetailDialog::DetailDialog(QWidget *parent) :
|
||||||
|
QDialog(parent)
|
||||||
|
{
|
||||||
|
resize(640, 480);
|
||||||
|
QVBoxLayout *verticalLayout = new QVBoxLayout(this);
|
||||||
|
textBrowser = new QTextBrowser(this);
|
||||||
|
verticalLayout->addWidget(textBrowser);
|
||||||
|
buttonBox = new QDialogButtonBox(this);
|
||||||
|
buttonBox->setOrientation(Qt::Horizontal);
|
||||||
|
buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
||||||
|
|
||||||
|
verticalLayout->addWidget(buttonBox);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
DetailDialog::~DetailDialog()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetailDialog::setText(const QString &text)
|
||||||
|
{
|
||||||
|
textBrowser->setPlainText(text);
|
||||||
|
}
|
48
src/libs/qt-breakpad/qtcrashhandler/detaildialog.h
Normal file
48
src/libs/qt-breakpad/qtcrashhandler/detaildialog.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QTextBrowser;
|
||||||
|
class QDialogButtonBox;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class DetailDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DetailDialog(QWidget *parent = nullptr);
|
||||||
|
~DetailDialog();
|
||||||
|
|
||||||
|
void setText(const QString &text);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTextBrowser *textBrowser = nullptr;
|
||||||
|
QDialogButtonBox *buttonBox = nullptr;
|
||||||
|
};
|
178
src/libs/qt-breakpad/qtcrashhandler/dumpsender.cpp
Normal file
178
src/libs/qt-breakpad/qtcrashhandler/dumpsender.cpp
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "dumpsender.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QHttpMultiPart>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
static const QByteArray boundary = "12345cfdfzfsdfsdfassssaaadsd24324jxccxzzzzz98xzcz";
|
||||||
|
|
||||||
|
DumpSender::DumpSender(QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_httpMultiPart(QHttpMultiPart::FormDataType)
|
||||||
|
{
|
||||||
|
const QString dumpPath = QCoreApplication::arguments().at(1);
|
||||||
|
const QByteArray startupTime = QCoreApplication::arguments().at(2).toLocal8Bit();
|
||||||
|
const QByteArray applicationName = QCoreApplication::arguments().at(3).toLocal8Bit();
|
||||||
|
QByteArray applicationVersion = QCoreApplication::arguments().at(4).toLocal8Bit();
|
||||||
|
const QByteArray plugins = QCoreApplication::arguments().at(5).toLocal8Bit();
|
||||||
|
// QByteArray ideRevision = QCoreApplication::arguments().at(6).toLocal8Bit();
|
||||||
|
m_applicationFilePath = QCoreApplication::arguments().at(7);
|
||||||
|
|
||||||
|
if (applicationVersion.isEmpty())
|
||||||
|
applicationVersion = "1.0.0";
|
||||||
|
|
||||||
|
QFile dumpFile(dumpPath, this);
|
||||||
|
const bool isOpen = dumpFile.open(QIODevice::ReadOnly);
|
||||||
|
Q_ASSERT(isOpen);
|
||||||
|
Q_UNUSED(isOpen);
|
||||||
|
|
||||||
|
const QList<QPair<QByteArray, QByteArray> > pairList = {
|
||||||
|
{ "StartupTime", startupTime },
|
||||||
|
{ "Vendor", "Qt Project" },
|
||||||
|
{ "InstallTime", "0" },
|
||||||
|
{ "Add-ons", plugins },
|
||||||
|
{ "BuildID", "" },
|
||||||
|
{ "SecondsSinceLastCrash", "0" },
|
||||||
|
{ "ProductName", applicationName },
|
||||||
|
{ "URL", "" },
|
||||||
|
{ "Theme", "" },
|
||||||
|
{ "Version", applicationVersion },
|
||||||
|
{ "CrashTime", QByteArray::number(QDateTime::currentDateTime().toTime_t()) },
|
||||||
|
{ "Throttleable", "0" }
|
||||||
|
};
|
||||||
|
|
||||||
|
m_formData.append("--" + boundary + "\r\n");
|
||||||
|
for (const auto &pair : pairList) {
|
||||||
|
m_formData.append("Content-Disposition: form-data; name=\"" + pair.first + "\"\r\n\r\n");
|
||||||
|
m_formData.append(pair.second + "\r\n");
|
||||||
|
m_formData.append("--" + boundary + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray dumpArray = dumpFile.readAll();
|
||||||
|
m_formData.append("Content-Type: application/octet-stream\r\n");
|
||||||
|
m_formData.append("Content-Disposition: form-data; name=\"upload_file_minidump\"; filename=\""
|
||||||
|
+ QFileInfo(dumpPath).baseName().toUtf8() + "\r\n");
|
||||||
|
m_formData.append("Content-Transfer-Encoding: binary\r\n\r\n");
|
||||||
|
m_formData.append(dumpArray);
|
||||||
|
|
||||||
|
m_formData.append("--" + boundary + "--\r\n");
|
||||||
|
|
||||||
|
for (const auto &pair : pairList) {
|
||||||
|
QHttpPart httpPart;
|
||||||
|
httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"" + pair.first + "\"");
|
||||||
|
httpPart.setBody(pair.second);
|
||||||
|
m_httpMultiPart.append(httpPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHttpPart dumpPart;
|
||||||
|
dumpPart.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
|
||||||
|
dumpPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
"form-data; name=\"upload_file_minidump\"; filename=\""
|
||||||
|
+ QFileInfo(dumpPath).baseName().toUtf8() + "\"");
|
||||||
|
dumpPart.setRawHeader("Content-Transfer-Encoding:", "binary");
|
||||||
|
dumpPart.setBody(dumpArray);
|
||||||
|
m_httpMultiPart.append(dumpPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpSender::sendDumpAndQuit()
|
||||||
|
{
|
||||||
|
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl("http://crashes.qt.io/submit"));
|
||||||
|
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + boundary);
|
||||||
|
|
||||||
|
QList<QPair<QByteArray, QByteArray>> pairList;
|
||||||
|
|
||||||
|
if (!m_emailAddress.isEmpty())
|
||||||
|
pairList.append({ "Email", m_emailAddress.toLocal8Bit() });
|
||||||
|
|
||||||
|
if (!m_commentText.isEmpty())
|
||||||
|
pairList.append({ "Comments", m_commentText.toLocal8Bit() });
|
||||||
|
|
||||||
|
for (const auto &pair : pairList) {
|
||||||
|
m_formData.append("Content-Disposition: form-data; name=\"" + pair.first + "\"\r\n\r\n");
|
||||||
|
m_formData.append(pair.second + "\r\n");
|
||||||
|
m_formData.append("--" + boundary + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &pair : pairList) {
|
||||||
|
QHttpPart httpPart;
|
||||||
|
httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"" + pair.first + "\"");
|
||||||
|
httpPart.setBody(pair.second);
|
||||||
|
m_httpMultiPart.append(httpPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = manager->post(request, &m_httpMultiPart);
|
||||||
|
|
||||||
|
m_httpMultiPart.setParent(reply);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::uploadProgress, this, &DumpSender::uploadProgress);
|
||||||
|
connect(reply, &QNetworkReply::finished, QCoreApplication::instance(), &QCoreApplication::quit);
|
||||||
|
connect(reply,
|
||||||
|
static_cast<void (QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
|
||||||
|
QCoreApplication::instance(), &QCoreApplication::quit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpSender::restartCrashedApplicationAndSendDump()
|
||||||
|
{
|
||||||
|
QProcess::startDetached(m_applicationFilePath);
|
||||||
|
sendDumpAndQuit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpSender::restartCrashedApplication()
|
||||||
|
{
|
||||||
|
QProcess::startDetached(m_applicationFilePath);
|
||||||
|
QCoreApplication::quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpSender::setEmailAddress(const QString &email)
|
||||||
|
{
|
||||||
|
m_emailAddress = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DumpSender::setCommentText(const QString &comment)
|
||||||
|
{
|
||||||
|
m_commentText = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DumpSender::dumperSize() const
|
||||||
|
{
|
||||||
|
return m_formData.size();
|
||||||
|
}
|
55
src/libs/qt-breakpad/qtcrashhandler/dumpsender.h
Normal file
55
src/libs/qt-breakpad/qtcrashhandler/dumpsender.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHttpMultiPart>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class DumpSender : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DumpSender(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int dumperSize() const;
|
||||||
|
|
||||||
|
void sendDumpAndQuit();
|
||||||
|
void restartCrashedApplication();
|
||||||
|
void restartCrashedApplicationAndSendDump();
|
||||||
|
void setEmailAddress(const QString &email);
|
||||||
|
void setCommentText(const QString &comment);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHttpMultiPart m_httpMultiPart;
|
||||||
|
QByteArray m_formData;
|
||||||
|
QString m_applicationFilePath;
|
||||||
|
QString m_emailAddress;
|
||||||
|
QString m_commentText;
|
||||||
|
};
|
73
src/libs/qt-breakpad/qtcrashhandler/main.cpp
Normal file
73
src/libs/qt-breakpad/qtcrashhandler/main.cpp
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "dumpsender.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QHostInfo>
|
||||||
|
#include <QNetworkProxyFactory>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication application(argc, argv);
|
||||||
|
|
||||||
|
if (application.arguments().count() <= 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const QString dumpPath = QApplication::arguments().at(1);
|
||||||
|
if (!QFileInfo(dumpPath).exists())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||||
|
|
||||||
|
QHostInfo hostInfo = QHostInfo::fromName("crashes.qt.io");
|
||||||
|
|
||||||
|
// if (hostInfo.error() != QHostInfo::NoError)
|
||||||
|
// return 0;
|
||||||
|
|
||||||
|
DumpSender dumpSender;
|
||||||
|
|
||||||
|
MainWidget mainWindow;
|
||||||
|
|
||||||
|
mainWindow.setProgressbarMaximum(dumpSender.dumperSize());
|
||||||
|
|
||||||
|
QObject::connect(&mainWindow, &MainWidget::restartCrashedApplication,
|
||||||
|
&dumpSender, &DumpSender::restartCrashedApplication);
|
||||||
|
QObject::connect(&mainWindow, &MainWidget::restartCrashedApplicationAndSendDump,
|
||||||
|
&dumpSender, &DumpSender::restartCrashedApplicationAndSendDump);
|
||||||
|
QObject::connect(&mainWindow, &MainWidget::sendDump,
|
||||||
|
&dumpSender, &DumpSender::sendDumpAndQuit);
|
||||||
|
QObject::connect(&mainWindow, &MainWidget::commentChanged,
|
||||||
|
&dumpSender, &DumpSender::setCommentText);
|
||||||
|
QObject::connect(&mainWindow, &MainWidget::emailAdressChanged,
|
||||||
|
&dumpSender, &DumpSender::setEmailAddress);
|
||||||
|
QObject::connect(&dumpSender, &DumpSender::uploadProgress,
|
||||||
|
&mainWindow, &MainWidget::updateProgressBar);
|
||||||
|
|
||||||
|
mainWindow.show();
|
||||||
|
return application.exec();
|
||||||
|
}
|
155
src/libs/qt-breakpad/qtcrashhandler/mainwidget.cpp
Normal file
155
src/libs/qt-breakpad/qtcrashhandler/mainwidget.cpp
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "ui_mainwidget.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
MainWidget::MainWidget(QWidget *parent) :
|
||||||
|
QWidget(parent),
|
||||||
|
ui(new Ui::MainWidget)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui->restartButton, &QAbstractButton::clicked, this, &MainWidget::restartApplication);
|
||||||
|
connect(ui->quitButton, &QAbstractButton::clicked, this, &MainWidget::quitApplication);
|
||||||
|
connect(ui->detailButton, &QAbstractButton::clicked, this, &MainWidget::showDetails);
|
||||||
|
connect(ui->commentTextEdit, &QTextEdit::textChanged, this, &MainWidget::commentIsProvided);
|
||||||
|
connect(ui->emailLineEdit, &QLineEdit::textEdited, this, &MainWidget::emailAdressChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWidget::~MainWidget()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::setProgressbarMaximum(int maximum)
|
||||||
|
{
|
||||||
|
ui->progressBar->setMaximum(maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::changeEvent(QEvent *e)
|
||||||
|
{
|
||||||
|
QWidget::changeEvent(e);
|
||||||
|
if (e->type() == QEvent::LanguageChange)
|
||||||
|
ui->retranslateUi(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::updateProgressBar(qint64 progressCount, qint64 fullCount)
|
||||||
|
{
|
||||||
|
ui->progressBar->setValue(static_cast<int>(progressCount));
|
||||||
|
ui->progressBar->setMaximum(static_cast<int>(fullCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::showError(QNetworkReply::NetworkError error)
|
||||||
|
{
|
||||||
|
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
|
||||||
|
if (error != QNetworkReply::NoError && reply) {
|
||||||
|
ui->commentTextEdit->setReadOnly(true);
|
||||||
|
ui->commentTextEdit->setPlainText(reply->errorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::restartApplication()
|
||||||
|
{
|
||||||
|
if (ui->sendDumpCheckBox->isChecked())
|
||||||
|
emit restartCrashedApplicationAndSendDump();
|
||||||
|
else
|
||||||
|
emit restartCrashedApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::quitApplication()
|
||||||
|
{
|
||||||
|
ui->quitButton->setEnabled(false);
|
||||||
|
if (ui->sendDumpCheckBox->isChecked())
|
||||||
|
emit sendDump();
|
||||||
|
else
|
||||||
|
QCoreApplication::quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::commentIsProvided()
|
||||||
|
{
|
||||||
|
m_commentIsProvided = true;
|
||||||
|
emit commentChanged(ui->commentTextEdit->toPlainText());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWidget::showDetails()
|
||||||
|
{
|
||||||
|
if (m_detailDialog.isNull()) {
|
||||||
|
m_detailDialog = new DetailDialog(this);
|
||||||
|
|
||||||
|
QString detailText;
|
||||||
|
|
||||||
|
detailText.append(tr("We specifically send the following information:\n\n"));
|
||||||
|
|
||||||
|
QString dumpPath = QApplication::arguments().at(1);
|
||||||
|
QString startupTime = QApplication::arguments().at(2);
|
||||||
|
QString applicationName = QApplication::arguments().at(3);
|
||||||
|
QString applicationVersion = QApplication::arguments().at(4);
|
||||||
|
QString plugins = QApplication::arguments().at(5);
|
||||||
|
QString ideRevision = QApplication::arguments().at(6);
|
||||||
|
|
||||||
|
detailText.append(QString("StartupTime: %1\n").arg(startupTime));
|
||||||
|
detailText.append(QString("Vendor: %1\n").arg("Qt Project"));
|
||||||
|
detailText.append(QString("InstallTime: %1\n").arg("0"));
|
||||||
|
detailText.append(QString("Add-ons: %1\n").arg(plugins));
|
||||||
|
detailText.append(QString("BuildID: %1\n").arg("0"));
|
||||||
|
detailText.append(QString("SecondsSinceLastCrash: %1\n").arg("0"));
|
||||||
|
detailText.append(QString("ProductName: %1\n").arg(applicationName));
|
||||||
|
detailText.append(QString("URL: %1\n").arg(""));
|
||||||
|
detailText.append(QString("Theme: %1\n").arg(""));
|
||||||
|
detailText.append(QString("Version: %1\n").arg(applicationVersion));
|
||||||
|
detailText.append(QString("CrashTime: %1\n").arg(QString::number(QDateTime::currentDateTime().toTime_t())));
|
||||||
|
|
||||||
|
if (!ui->emailLineEdit->text().isEmpty())
|
||||||
|
detailText.append(tr("Email: %1\n").arg(ui->emailLineEdit->text()));
|
||||||
|
|
||||||
|
if (m_commentIsProvided)
|
||||||
|
detailText.append(tr("Comments: %1\n").arg(ui->commentTextEdit->toPlainText()));
|
||||||
|
|
||||||
|
detailText.append(
|
||||||
|
tr("In addition, we send a Microsoft Minidump file, which contains information "
|
||||||
|
"about this computer, such as the operating system and CPU, and most "
|
||||||
|
"importantly, it contains the stacktrace, which is an internal structure that "
|
||||||
|
"shows where the program crashed. This information will help us to identify "
|
||||||
|
"the cause of the crash and to fix it."));
|
||||||
|
|
||||||
|
m_detailDialog.data()->setText(detailText);
|
||||||
|
}
|
||||||
|
if (m_detailDialog->isVisible())
|
||||||
|
m_detailDialog->showNormal();
|
||||||
|
else
|
||||||
|
m_detailDialog->show();
|
||||||
|
}
|
70
src/libs/qt-breakpad/qtcrashhandler/mainwidget.h
Normal file
70
src/libs/qt-breakpad/qtcrashhandler/mainwidget.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "detaildialog.h"
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Ui { class MainWidget; }
|
||||||
|
|
||||||
|
class MainWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MainWidget(QWidget *parent = nullptr);
|
||||||
|
~MainWidget();
|
||||||
|
|
||||||
|
void setProgressbarMaximum(int maximum);
|
||||||
|
void updateProgressBar(qint64 progressCount, qint64 fullCount);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void restartCrashedApplication();
|
||||||
|
void sendDump();
|
||||||
|
void restartCrashedApplicationAndSendDump();
|
||||||
|
void emailAdressChanged(const QString &email);
|
||||||
|
void commentChanged(const QString &comment);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void changeEvent(QEvent *e);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void restartApplication();
|
||||||
|
void quitApplication();
|
||||||
|
void showError(QNetworkReply::NetworkError error);
|
||||||
|
void showDetails();
|
||||||
|
void commentIsProvided();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWidget *ui;
|
||||||
|
|
||||||
|
QPointer<DetailDialog> m_detailDialog;
|
||||||
|
bool m_commentIsProvided = false;
|
||||||
|
};
|
143
src/libs/qt-breakpad/qtcrashhandler/mainwidget.ui
Normal file
143
src/libs/qt-breakpad/qtcrashhandler/mainwidget.ui
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWidget</class>
|
||||||
|
<widget class="QWidget" name="MainWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>422</width>
|
||||||
|
<height>510</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Crash Handler</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<pointsize>20</pointsize>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Qt Creator has crashed</string>
|
||||||
|
</property>
|
||||||
|
<property name="scaledContents">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>You can send us a crash report in order to help us diagnose and fix the problem.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Email:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="emailLineEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Enter here your email (optional)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="sendDumpCheckBox">
|
||||||
|
<property name="text">
|
||||||
|
<string>Tell The Qt Company about this crash so they can fix it</string>
|
||||||
|
</property>
|
||||||
|
<property name="checked">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="detailButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Details</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="commentTextEdit">
|
||||||
|
<property name="overwriteMode">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>Please describe what you did before it crashed (comments are publicly visible)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Your crash report will be submitted before you quit or restart.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="format">
|
||||||
|
<string>%v/%m Bytes</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="restartButton">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Restart</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="quitButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Quit</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
6
src/libs/qt-breakpad/qtcrashhandler/qtcrashhandler.pro
Normal file
6
src/libs/qt-breakpad/qtcrashhandler/qtcrashhandler.pro
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
TARGET = qtcrashhandler
|
||||||
|
QT += network
|
||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
include(../qtcrashhandler.pri)
|
||||||
|
DESTDIR = ../bin
|
43
src/libs/qt-breakpad/testapp/main.cpp
Normal file
43
src/libs/qt-breakpad/testapp/main.cpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2017 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of Qt Creator.
|
||||||
|
**
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include <qtsystemexceptionhandler.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QCoreApplication a(argc, argv);
|
||||||
|
|
||||||
|
a.setApplicationName("testapp");
|
||||||
|
a.setApplicationVersion("1.0.0");
|
||||||
|
|
||||||
|
QtSystemExceptionHandler exceptionHandler;
|
||||||
|
|
||||||
|
QTimer::singleShot(100, [] { QtSystemExceptionHandler::crash(); });
|
||||||
|
|
||||||
|
return a.exec();
|
||||||
|
}
|
14
src/libs/qt-breakpad/testapp/testapp.pro
Normal file
14
src/libs/qt-breakpad/testapp/testapp.pro
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
QT -= gui
|
||||||
|
|
||||||
|
include(../qtbreakpad.pri)
|
||||||
|
|
||||||
|
unix:TARGET = testapp.bin
|
||||||
|
win32:TARGET = testapp
|
||||||
|
|
||||||
|
## install_name_tool -change libQtBreakpad.1.dylib @executable_path/../lib/qtbreakpad/libQtBreakpad.dylib
|
||||||
|
|
||||||
|
DESTDIR = ../bin
|
||||||
|
|
||||||
|
CONFIG += console release c++11
|
||||||
|
|
||||||
|
SOURCES += main.cpp
|
@@ -1,7 +1,7 @@
|
|||||||
TARGET = qml2puppet
|
TARGET = qml2puppet
|
||||||
|
|
||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
|
QTC_LIB_DEPENDS += utils
|
||||||
include(../../../../qtcreator.pri)
|
include(../../../../qtcreator.pri)
|
||||||
|
|
||||||
osx: DESTDIR = $$IDE_LIBEXEC_PATH/qmldesigner
|
osx: DESTDIR = $$IDE_LIBEXEC_PATH/qmldesigner
|
||||||
@@ -9,6 +9,7 @@ else: DESTDIR = $$IDE_LIBEXEC_PATH
|
|||||||
|
|
||||||
include(../../../rpath.pri)
|
include(../../../rpath.pri)
|
||||||
|
|
||||||
|
include(../../../libs/qt-breakpad/qtbreakpad.pri)
|
||||||
include(../../../../share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri)
|
include(../../../../share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri)
|
||||||
|
|
||||||
isEmpty(PRECOMPILED_HEADER):PRECOMPILED_HEADER = $$PWD/../../../shared/qtcreator_pch.h
|
isEmpty(PRECOMPILED_HEADER):PRECOMPILED_HEADER = $$PWD/../../../shared/qtcreator_pch.h
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
TARGET = qtcrashhandler
|
TARGET = qtcrashhandler
|
||||||
|
|
||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH)
|
|
||||||
include($${QT_BREAKPAD_ROOT_PATH}/qtcrashhandler.pri)
|
|
||||||
|
|
||||||
include(../../../qtcreator.pri)
|
include(../../qtcreatortool.pri)
|
||||||
DESTDIR = $$IDE_BIN_PATH
|
include(../../libs/qt-breakpad/qtcrashhandler.pri)
|
||||||
include(../../rpath.pri)
|
|
||||||
|
@@ -41,8 +41,7 @@ isEmpty(BUILD_CPLUSPLUS_TOOLS):BUILD_CPLUSPLUS_TOOLS=$$(BUILD_CPLUSPLUS_TOOLS)
|
|||||||
cplusplus-update-frontend
|
cplusplus-update-frontend
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_BREAKPAD_ROOT_PATH = $$(QT_BREAKPAD_ROOT_PATH)
|
!isEmpty(BREAKPAD_SOURCE_DIR) {
|
||||||
!isEmpty(QT_BREAKPAD_ROOT_PATH) {
|
|
||||||
SUBDIRS += qtcrashhandler
|
SUBDIRS += qtcrashhandler
|
||||||
} else {
|
} else {
|
||||||
linux-* {
|
linux-* {
|
||||||
|
Reference in New Issue
Block a user