.size
79 |
80 | let success = name.withUnsafeMutableBytes { (nameBytePtr: UnsafeMutableRawBufferPointer) -> Bool in
81 | guard let nameBytesBlindMemory = nameBytePtr.bindMemory(to: Int32.self).baseAddress else { return false }
82 | return -1 != sysctl(nameBytesBlindMemory, 4, &info, &info_size, nil, 0)
83 | }
84 |
85 | if !success {
86 | debuggerIsAttached = false
87 | }
88 |
89 | if !debuggerIsAttached && (info.kp_proc.p_flag & P_TRACED) != 0 {
90 | debuggerIsAttached = true
91 | }
92 |
93 | return debuggerIsAttached
94 | }()
95 |
--------------------------------------------------------------------------------
/CrashReporter.xcodeproj/xcshareddata/xcschemes/CrashReporter.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/CrashReporter.xcodeproj/xcshareddata/xcschemes/Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
51 |
52 |
62 |
64 |
70 |
71 |
72 |
73 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/php/index.php:
--------------------------------------------------------------------------------
1 |
33 | *
34 | *****************************************************************************/
35 |
36 |
37 | use PHPMailer\PHPMailer\PHPMailer;
38 | use PHPMailer\PHPMailer\Exception;
39 |
40 | require 'vendor/PHPMailer/src/Exception.php';
41 | require 'vendor/PHPMailer/src/PHPMailer.php';
42 | require 'vendor/PHPMailer/src/SMTP.php';
43 |
44 | function postString($key) {
45 | return array_key_exists($key, $_POST) ? $_POST[$key] : '';
46 | }
47 |
48 | function clean($userData) {
49 | $userData = htmlspecialchars($userData, ENT_IGNORE, 'utf-8');
50 | $userData = strip_tags($userData);
51 | $userData = trim($userData);
52 | return $userData;
53 | }
54 |
55 | function isEmpty($var) {
56 | return !isset($var) || strlen(trim($var)) == 0;
57 | }
58 |
59 | function sendEmailForReportAsFilenameForSender($path, $filename, $app, $userProvidedDetails = '', $userEmail = '') {
60 | $mail = new PHPMailer(true);
61 |
62 | //Server settings
63 | if (DEBUG) {
64 | $mail->Debugoutput = 'echo';
65 | $mail->SMTPDebug = 4;
66 | }
67 | $mail->isSMTP();
68 | $mail->Host = SMTP_HOST;
69 | $mail->SMTPAuth = True;
70 | $mail->Username = SMTP_USER;
71 | $mail->Password = SMTP_PASS;
72 | $mail->SMTPSecure = SMTP_SECURE;
73 | $mail->Port = SMTP_PORT;
74 |
75 | //Recipients
76 | $mail->setFrom(SENDER_EMAIL, SENDER_NAME);
77 | $mail->addAddress(SUPPORT_EMAIL, SUPPORT_NAME);
78 | if (!isEmpty($userEmail) && SEND_CC_TO_USER) {
79 | $mail->addCC($userEmail);
80 | }
81 |
82 | // Attachments
83 | $mail->addAttachment($path, $filename);
84 |
85 | // Content
86 | $mail->Subject = $app . ' crash log' . (!isEmpty($userEmail) ? ' from ' . clean($userEmail) : '');
87 |
88 | $message = 'Processed on: ' . date("Y-m-d H:i:s") . "
\r\n"
89 | . 'App: ' . $app . "
\r\n"
90 | . 'Sender: ' . (!isEmpty($userEmail) ? clean($userEmail) : 'unknown') . "
\r\n
\r\n"
91 | . (!isEmpty($userProvidedDetails) ? "User-provided details:\r\n" . $userProvidedDetails . "
\r\n" : '');
92 | $mail->Body = $message;
93 | $mail->AltBody = $message;
94 |
95 | $mail->send();
96 | }
97 |
98 | // Collect request data
99 | $crashlog = postString('crashlog');
100 | $userProvidedDetails = clean(postString('userProvidedDetails'));
101 | $app = clean($_SERVER['HTTP_USER_AGENT']);
102 |
103 | /*
104 | // To test sending email from this script via `php index.php`, provide replacement data
105 | $crashlog = "test crash log content";
106 | $app = "test app 2000";
107 | */
108 |
109 | if (isEmpty($crashlog) || isEmpty($app)) {
110 | header('X-PHP-Response-Code: 401', true, 401);
111 | die();
112 | }
113 |
114 | $logIsJSON = $crashlog[0] == '{';
115 | $filename = date("Y-m-d H.i.s") . ' ' . $app . ($logIsJSON ? '.ips' : '.crash');
116 | $userEmail = postString('userEmail');
117 |
118 | $tmpfile = tmpfile();
119 | try {
120 | // Write report to file
121 | fwrite($tmpfile, $crashlog);
122 | fseek($tmpfile, 0);
123 | $path = stream_get_meta_data($tmpfile)['uri'];
124 |
125 | sendEmailForReportAsFilenameForSender($path, $filename, $app, $userProvidedDetails, $userEmail);
126 | } catch (Exception $e) { // PHPMailer exception
127 | header('X-PHP-Response-Code: 400', true, 400);
128 | echo($e->getMessage());
129 | } catch (\Exception $e) { // Global PHP exception
130 | header('X-PHP-Response-Code: 400', true, 400);
131 | echo $e->getMessage();
132 | } finally {
133 | fclose($tmpfile);
134 | }
135 |
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CrashReporter for macOS Apps
2 |
3 | [](https://travis-ci.org/CleanCocoa/CrashReporter)
4 | 
5 | 
6 | 
7 | 
8 | [](https://github.com/Carthage/Carthage)
9 |
10 | Your app will crash one day. Be prepared to collect crash data automatically, because not every user is a techno wizard capable of sending you `.crash` files from the built-in Console app.
11 |
12 |
13 |

14 |

15 |
16 |
17 | ## Requirements
18 |
19 | - macOS 10.12+
20 | - Xcode 10.2+
21 | - Swift 5+
22 |
23 | For the optional server endpoint script, you'll need PHP 7.x.
24 |
25 |
26 | ## Installation
27 |
28 | ### Carthage
29 |
30 | github "CleanCocoa/CrashReporter"
31 |
32 | ### CocoaPods
33 |
34 | pod 'CrashReporterMac'
35 |
36 | ### SwiftPM
37 |
38 | .package(url: "https://github.com/CleanCocoa/CrashReporter", from: "0.2.0")
39 |
40 | ### Manual
41 |
42 | If you want to customize the UI, checkout the source and copy all code from `CrashReporter/` into your project.
43 |
44 |
45 | ## Usage
46 |
47 | ### Server Endpoint
48 |
49 | You need a server endpoint to receive crash reports.
50 |
51 | The framework does not care what the server does:
52 |
53 | - You can email the incoming crash report from your server, or
54 | - you can store the crash report as a timestamped file on disk for later reference, of
55 | - you can store the crash report in a database.
56 |
57 | The crash reporter framework will perform a HTTP POST request:
58 |
59 | - The `User-Agent` metadata is set to `"\(APP_NAME)-\(VERSION)"` if the values are found in the app's bundle, e.g. `"Sherlock-2.0"`.
60 | - The `userEmail` variable is either left out or set to the email entered by the user.
61 | - The `userProvidedDetails` variable is either left out or set to the details entered by the user.
62 | - The `crashlog` variable is set to the contents of the `.crash` file the user submits.
63 | - The server response will be ignored.
64 |
65 | You can roll your own endpoint as long as its URL is reachable from the app.
66 |
67 | Or you can use the simple endpoint shipped in this repository! It's located at `php/index.php`. This PHP server script will attempt to email you the crash log as an attachment with a timestamp, e.g. `20190701204853 Sherlock-2.0.crash` (where the timestamp signifies the ISO-formatted date 2019-07-01 20:48:52). If the user enters her email address, she'll receive a copy as CC by default. You can toggle this in the PHP script's frontmatter.
68 |
69 | To run the script on your local machine for quick testing, run:
70 |
71 | $ php -S 127.0.0.1:3333 php/index.php
72 |
73 | Then use `URL(string: "http://127.0.0.1:3333/")` in your Swift code for the endpoint.
74 |
75 |
76 | ### Application Setup
77 |
78 | See the code in `Example/`, which is part of the Xcode project.
79 |
80 | You can use the framework as-is in your app to check for crash reports:
81 |
82 | ```swift
83 | import CrashReporter
84 |
85 | let crashReporterURL = URL(string: "http://127.0.0.1:3333/")!
86 | let crashReporter = CrashReporter(
87 | crashReporterURL: crashReporterURL,
88 | privacyPolicyURL: URL(string: "https://example.com/privacy-policy")!)
89 |
90 | // Run the check in the background and display
91 | // a crash reporter window if needed
92 | crashReporter.check()
93 | ```
94 |
95 |
96 | ### Preference Pane in Your Application
97 |
98 | If you allow the user to tick "Send crash reports automatically" in the crash reporter window, you should add a similar option to your app's preference pane to enable undoing this setting.
99 |
100 | Refer to `DefaultsKeys.sendCrashLogsAutomaticallyKey`.
101 |
102 | If you want to employ Cocoa Bindings to configure a "Send crash reports automatically" checkbox in your preference panes or main menu, then you can create a simple KVC-compliant wrapper in your classes:
103 |
104 | ```swift
105 | // Assuming this is loaded from a Nib where you set an object of this type as the
106 | // target for "Value" Cocoa Bindings.
107 | class PreferenceController: NSViewController {
108 | let crashReporter: CrashReporter = // ... setup before ...
109 |
110 | // Cocoa bindings path is `self.sendCrashReportsAutomatically`
111 | @objc public dynamic var sendCrashReportsAutomatically: Bool {
112 | get {
113 | return crashReporter.sendCrashReportsAutomatically
114 | }
115 | set {
116 | crashReporter.sendCrashReportsAutomatically = newValue
117 | }
118 | }
119 | }
120 | ```
121 |
122 |
123 | ## API
124 |
125 | - `CrashReporter.check()` is the default call that displays the crash reporter window for the current app if needed, and uploads the crash report to the server.
126 | - `CrashReporter.check(appName:collectEmailAddress:alwaysShowCrashReporterWindow:)` allows you to control the app name for which the reporter searches `.crash` files. Set `collectEmailAddress` to false if you don't want to collect the email address of the user to get back to them. Set `alwaysShowCrashReporterWindow` to `true` if you always want to show the crash reporter window instead of letting the user pick when she sees the window.
127 | - `CrashReporter.sendCrashReportsAutomatically` exposes the user setting of sending reports automatically. Useful for preference panes.
128 |
129 | If you don't change the `UserDefaults` keys for the crash reporter settings, use the various `DefaultsKeys.standard` properties in your app to look up the values:
130 |
131 | - `emailAddressKey` is `"CRR_emailAddress"`, where the email address of the user is stored
132 | - `sendCrashLogsAutomaticallyKey` is `"CRR_sendCrashLogsAutomatically"` -- use this for your preference panes to toggle automatically sending crash reports
133 | - `lastSeenCrashLogTimeSince1970Key` is `"CRR_lastSeenCrashLogTimeSince1970"`
134 | - `lastSeenCrashLogMD5Key` is `"CRR_lastSeenCrashLogMD5"`
135 |
136 |
137 | ## License
138 |
139 | The whole project is distributed under the MIT license. See [the LICENSE file](LICENSE) for reference.
140 |
141 | A quick overview:
142 |
143 | - The Swift code is adapted from [Brent Simmons's NetNewsWire 5](https://github.com/brentsimmons/NetNewsWire), Copyright © Brent Simmons 2017-2019. All rights reserved.
144 | - Changes in this repository are Copyright © Christian Tietze 2019. All rights reserved.
145 | - The PHP server code is Copyright © Christian Tietze 2019. All rights reserved.
146 |
147 |
--------------------------------------------------------------------------------
/Sources/CrashReporter/UI/CrashReportWindowController.swift:
--------------------------------------------------------------------------------
1 | // Part of
2 | //
3 | // Created by Brent Simmons on 12/28/18.
4 | // Copyright © 2018 Ranchero Software. All rights reserved.
5 | // Copyright © 2019 Christian Tietze. All rights reserved.
6 | // Distributed under the MIT License.
7 |
8 | import AppKit
9 |
10 | protocol SendsCrashLog {
11 | func send(emailAddress: String?, userProvidedDetails: String?, crashLogText: String)
12 | }
13 |
14 | final class CrashReportWindowController: NSWindowController, NSWindowDelegate {
15 |
16 | convenience init(
17 | appName: String,
18 | crashLogText: String,
19 | crashLogSender: SendsCrashLog,
20 | privacyPolicyURL: URL,
21 | collectEmailSetting: EmailAddressSetting,
22 | sendReportsAutomaticallySetting: SendReportsAutomaticallySetting
23 | ) {
24 | self.init()
25 |
26 | var nibTopLevelObjects: NSArray?
27 | CrashReporterBundle.loadNibNamed(
28 | "CrashReporterWindow", owner: self, topLevelObjects: &nibTopLevelObjects)
29 | self.window = nibTopLevelObjects?.lazy.compactMap({ $0 as? NSWindow }).first
30 |
31 | self.crashLogText = crashLogText
32 | self.crashLogSender = crashLogSender
33 | self.privacyPolicyURL = privacyPolicyURL
34 | self.collectEmailSetting = collectEmailSetting
35 | self.sendReportsAutomaticallySetting = sendReportsAutomaticallySetting
36 |
37 | // Setup window
38 | window?.center()
39 | window?.delegate = self
40 |
41 | window?.title = "\(appName) Crash Reporter"
42 | titleLabel.stringValue = "\(appName) quit unexpectedly."
43 | crashLogContainerView.isHidden = true
44 |
45 | updateCrashLogText()
46 | updateCollectEmailVisibility()
47 | updateAutomaticallySendCrashLogVisibility()
48 | updateButtonStates()
49 | }
50 |
51 | var onWindowWillClose: ((NSWindow?) -> Void)?
52 |
53 | func windowWillClose(_ notification: Notification) {
54 | onWindowWillClose?(notification.object as? NSWindow)
55 | }
56 |
57 | // MARK: View components
58 |
59 | @IBOutlet var textView: NSTextView! {
60 | didSet {
61 | textView.font = NSFont.userFixedPitchFont(ofSize: 0.0)
62 | textView.textContainerInset = NSSize(width: 5.0, height: 5.0)
63 | updateCrashLogText()
64 | }
65 | }
66 |
67 | @IBOutlet var titleLabel: NSTextField!
68 | @IBOutlet var bodyLabel: NSTextField!
69 |
70 | @IBOutlet weak var collectEmailContainerView: NSView!
71 | @IBOutlet weak var crashLogContainerView: NSView!
72 | @IBOutlet weak var sendAutomaticallyContainerView: NSView!
73 |
74 | @IBOutlet var sendCrashLogButton: NSButton!
75 | @IBOutlet var dontSendButton: NSButton!
76 | @IBOutlet var toggleCrashLogButton: NSButton!
77 |
78 | private func updateCrashLogText() {
79 | guard let textView = self.textView else { return }
80 | textView.string = crashLogText ?? ""
81 | }
82 |
83 | private func updateButtonStates() {
84 | sendCrashLogButton?.isEnabled = (crashLogSender != nil) && !didSendCrashLog
85 | dontSendButton?.isEnabled = !didSendCrashLog
86 | }
87 |
88 | private func updateCollectEmailVisibility() {
89 | collectEmailContainerView.isHidden = self.hideCollectEmail
90 | bodyLabel.stringValue =
91 | "Help us fix crashes by submitting this crash report."
92 | + (self.hideCollectEmail
93 | ? ""
94 | : " You can include your email address if you agree to being contacted for more details.")
95 | }
96 |
97 | private func updateAutomaticallySendCrashLogVisibility() {
98 | sendAutomaticallyContainerView.isHidden = self.hideAutomaticallySend
99 | }
100 |
101 | // MARK: Model
102 |
103 | internal var collectEmailSetting: EmailAddressSetting = .standard {
104 | didSet {
105 | updateCollectEmailVisibility()
106 | }
107 | }
108 |
109 | internal var hideCollectEmail: Bool {
110 | return !collectEmailSetting.isVisible
111 | }
112 |
113 | internal var sendReportsAutomaticallySetting: SendReportsAutomaticallySetting = .standard {
114 | didSet {
115 | updateAutomaticallySendCrashLogVisibility()
116 | }
117 | }
118 |
119 | internal var hideAutomaticallySend: Bool {
120 | return !sendReportsAutomaticallySetting.isVisible
121 | }
122 |
123 | /// KVC wrapper for `sendReportsAutomaticallySetting.isEnabled`
124 | @objc dynamic var sendCrashReportsAutomatically: Bool {
125 | get {
126 | return sendReportsAutomaticallySetting.isEnabled
127 | }
128 | set {
129 | sendReportsAutomaticallySetting.isEnabled = newValue
130 | }
131 | }
132 |
133 | /// KVC wrapper for `collectEmailSetting.emailAddress`
134 | @objc dynamic var emailAddress: String {
135 | get {
136 | return collectEmailSetting.emailAddress ?? ""
137 | }
138 | set {
139 | collectEmailSetting.emailAddress = newValue
140 | }
141 | }
142 |
143 | @objc dynamic var userProvidedDetails = ""
144 |
145 | internal var privacyPolicyURL: URL?
146 |
147 | internal var crashLogText: String? {
148 | didSet {
149 | updateCrashLogText()
150 | }
151 | }
152 |
153 | internal var crashLogSender: SendsCrashLog? {
154 | didSet {
155 | updateButtonStates()
156 | }
157 | }
158 |
159 | private var didSendCrashLog = false {
160 | didSet {
161 | updateButtonStates()
162 | }
163 | }
164 |
165 | // MARK: - User Interactions
166 |
167 | lazy var isRunningTests: Bool = false
168 |
169 | @IBAction func sendCrashReport(_ sender: Any?) {
170 | guard !didSendCrashLog else { return }
171 | defer { didSendCrashLog = true }
172 |
173 | if !isRunningTests,
174 | let crashLogText = self.crashLogText,
175 | let crashLogSender = self.crashLogSender
176 | {
177 |
178 | let emailAddress = self.collectEmailSetting.isVisible ? self.emailAddress : nil
179 | crashLogSender.send(emailAddress: emailAddress, userProvidedDetails: userProvidedDetails, crashLogText: crashLogText)
180 | }
181 |
182 | close()
183 | }
184 |
185 | @IBAction func dontSendCrashReport(_ sender: Any?) {
186 | close()
187 | }
188 |
189 | override func responds(to aSelector: Selector!) -> Bool {
190 | if aSelector == #selector(showPrivacyPolicy(_:)) {
191 | return self.privacyPolicyURL != nil
192 | }
193 | return super.responds(to: aSelector)
194 | }
195 |
196 | @IBAction func showPrivacyPolicy(_ sender: Any?) {
197 | guard let privacyPolicyURL = self.privacyPolicyURL else { return }
198 | NSWorkspace.shared.open(privacyPolicyURL)
199 | }
200 |
201 | @IBAction func toggleCrashLog(_ sender: Any?) {
202 | crashLogContainerView.isHidden = !crashLogContainerView.isHidden
203 | toggleCrashLogButton.title =
204 | crashLogContainerView.isHidden ? "Show Details" : "Hide Details"
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/Sources/CrashReporter/CrashReporter.swift:
--------------------------------------------------------------------------------
1 | // Part of
2 | //
3 | // Created by Brent Simmons on 12/17/18.
4 | // Copyright © 2018 Ranchero Software. All rights reserved.
5 | // Copyright © 2019 Christian Tietze. All rights reserved.
6 | // Distributed under the MIT License.
7 |
8 | import AppKit
9 |
10 | public final class CrashReporter {
11 |
12 | public let crashReporterURL: URL
13 | public let privacyPolicyURL: URL
14 | public let userDefaults: UserDefaults
15 | public let defaultsKeys: DefaultsKeys
16 |
17 | /// Convenience accessor for `sendCrashLogsAutomaticallyKey` defaults.
18 | ///
19 | /// Can be wrapped for KVC/Cocoa bindings in your preference panes when you access it through
20 | /// a property wrapping it as a `@objc dynamic var`:
21 | ///
22 | /// let var crashReporter = // ...
23 | ///
24 | /// @objc dynamic var sendCrashReportsAutomatically: Bool {
25 | /// get {
26 | /// return crashReporter.sendCrashReportsAutomatically
27 | /// }
28 | /// set {
29 | /// crashReporter.sendCrashReportsAutomatically = newValue
30 | /// }
31 | /// }
32 | ///
33 | public var sendCrashReportsAutomatically: Bool {
34 | get {
35 | return userDefaults.bool(forKey: defaultsKeys.sendCrashLogsAutomaticallyKey)
36 | }
37 | set {
38 | userDefaults.set(newValue, forKey: defaultsKeys.sendCrashLogsAutomaticallyKey)
39 | }
40 | }
41 |
42 | /// - param crashReporterURL: Server endpoint to send the crash log to.
43 | /// - param privacyPolicyURL: Web address that points to your privacy policy with details on how you handle crash log data.
44 | /// - param userDefaults: `UserDefaults` to store the last crash info in. Useful for defaults in app groups. Default is `UserDefaults.standard`.
45 | /// - param defaultsKeys: Configuration of the defaults keys to use. Default is `DefaultsKeys.standard`.
46 | public init(
47 | crashReporterURL: URL,
48 | privacyPolicyURL: URL,
49 | userDefaults: UserDefaults = .standard,
50 | defaultsKeys: DefaultsKeys = DefaultsKeys.standard) {
51 | self.crashReporterURL = crashReporterURL
52 | self.privacyPolicyURL = privacyPolicyURL
53 | self.userDefaults = userDefaults
54 | self.defaultsKeys = defaultsKeys
55 | }
56 |
57 | // MARK: - Testing seams
58 |
59 | /// Testing seam reading the `lastSeenCrashLogTimeSince1970Key` from user defaults.
60 | internal var lastSeenCrashLogDate: Date {
61 | return Date(timeIntervalSince1970: userDefaults.double(forKey: defaultsKeys.lastSeenCrashLogTimeSince1970Key))
62 | }
63 |
64 | /// Testing seam reading the `sendCrashLogsAutomaticallyKey` from user defaults.
65 | internal var shouldSendCrashLogsAutomatically: Bool {
66 | return userDefaults.bool(forKey: defaultsKeys.sendCrashLogsAutomaticallyKey)
67 | }
68 |
69 | /// Testing seam reading the `lastSeenCrashLogMD5Key` from user defaults.
70 | internal var lastSeenCrashLogMD5Hash: String? {
71 | return userDefaults.string(forKey: defaultsKeys.lastSeenCrashLogMD5Key)
72 | }
73 |
74 | /// Testing seam and configuration option to fetch reports.
75 | /// Default is `"~/Library/Logs/DiagnosticReports/"`
76 | internal lazy var crashReportFolderURL: URL = self.fileManager.diagnosticsReportsFolderURL
77 |
78 | /// Testing seam.
79 | internal var fileManager: FileManager {
80 | return .default
81 | }
82 |
83 | // MARK: - Check for crashes
84 |
85 | /// Look in ~/Library/Logs/DiagnosticReports/ for a new crash log for this app.
86 | ///
87 | /// Checks for crashes using the `CFBundleName` from the main app bundle as `appName`.
88 | ///
89 | /// - Parameters:
90 | /// - collectEmailAddress: Ask users for their email addresses when sending in reports. Default is true.
91 | /// - alwaysShowCrashReporterWindow: Overrides the user setting `shouldSendCrashLogsAutomaticallyKey`. Default is false.
92 | /// - displayCrashReporterWindowAsModal: Whether to show the window on top of other app windows. Default is false.
93 | /// - Note: When `collectEmailAddress` is disabled, you will not even get previously stored value from user defaults.
94 | public func check(collectEmailAddress: Bool = true,
95 | alwaysShowCrashReporterWindow: Bool = false,
96 | displayCrashReporterWindowAsModal: Bool = false) {
97 | self.check(
98 | appName: Bundle.main.infos[.bundleName]!,
99 | collectEmailAddress: collectEmailAddress,
100 | alwaysShowCrashReporterWindow: alwaysShowCrashReporterWindow,
101 | displayCrashReporterWindowAsModal: displayCrashReporterWindowAsModal)
102 | }
103 |
104 | /// Look in ~/Library/Logs/DiagnosticReports/ for a new crash log for `appName`.
105 | ///
106 | /// If a new crash log was found, show the reporter window or automatically
107 | /// upload the report, depending on the `shouldSendCrashLogsAutomaticallyKey`.
108 | /// Set `alwaysShowCrashReporterWindow` to override this behavior.
109 | ///
110 | /// - Parameters:
111 | /// - appName: Name of the application to search recent crash reports for.
112 | /// - collectEmailAddress: Ask users for their email addresses when sending in reports. Default is true.
113 | /// - alwaysShowCrashReporterWindow: Overrides the user setting `shouldSendCrashLogsAutomaticallyKey`. Default is false.
114 | /// - displayCrashReporterWindowAsModal: Whether to show the window on top of other app windows. Default is false.
115 | /// - Note: When `collectEmailAddress` is disabled, you will not even get previously stored value from user defaults.
116 | public func check(appName: String,
117 | collectEmailAddress: Bool = true,
118 | alwaysShowCrashReporterWindow: Bool = false,
119 | displayCrashReporterWindowAsModal: Bool = false) {
120 | guard let crashLog = mostRecentCrashInfo(appName: appName)?.crashLog() else { return }
121 |
122 | if hasSeen(crashLog) {
123 | return
124 | }
125 | remember(crashLog)
126 |
127 | if shouldSendCrashLogsAutomatically && alwaysShowCrashReporterWindow == false {
128 | let emailSetting = EmailAddressSetting(isVisible: false, userDefaults: self.userDefaults, emailAddressKey: self.defaultsKeys.emailAddressKey)
129 | let emailAddress = collectEmailAddress ? emailSetting.emailAddress : nil
130 | send(emailAddress: emailAddress, userProvidedDetails: nil, crashLogText: crashLog.content)
131 | } else {
132 | runCrashReporterWindow(
133 | appName: appName,
134 | crashLog: crashLog,
135 | displayAsModal: displayCrashReporterWindowAsModal,
136 | hideEmailCollection: !collectEmailAddress,
137 | hideSendReportsAutomaticallyOption: alwaysShowCrashReporterWindow)
138 | }
139 | }
140 |
141 | internal func mostRecentCrashInfo(appName: String) -> CrashInfo? {
142 | guard let fileURLs = try? allCrashReportFolderFiles() else { return nil }
143 |
144 | let relevantCrashInfos: [CrashInfo] = fileURLs
145 | .filter(matches(appName: appName))
146 | .compactMap { CrashInfo(url: $0, fileManager: fileManager) }
147 | .filter(newerThan(lastSeenCrashLogDate))
148 |
149 | return findMostRecent(crashInfos: relevantCrashInfos)
150 | }
151 |
152 | internal func allCrashReportFolderFiles() throws -> [URL] {
153 | return try fileManager.contentsOfDirectory(
154 | at: crashReportFolderURL,
155 | includingPropertiesForKeys: nil,
156 | options: [])
157 | }
158 |
159 | internal var crashReportWindowController: CrashReportWindowController?
160 |
161 | internal func runCrashReporterWindow(
162 | appName: String,
163 | crashLog: CrashLog,
164 | displayAsModal: Bool,
165 | hideEmailCollection: Bool,
166 | hideSendReportsAutomaticallyOption: Bool) {
167 | let collectEmailSetting = EmailAddressSetting(
168 | isVisible: !hideEmailCollection,
169 | userDefaults: self.userDefaults,
170 | emailAddressKey: self.defaultsKeys.emailAddressKey)
171 | let sendAutomaticallySetting = SendReportsAutomaticallySetting(
172 | isVisible: !hideSendReportsAutomaticallyOption,
173 | userDefaults: self.userDefaults,
174 | sendCrashLogsAutomaticallyKey: self.defaultsKeys.sendCrashLogsAutomaticallyKey)
175 |
176 | self.crashReportWindowController = CrashReportWindowController(
177 | appName: appName,
178 | crashLogText: crashLog.content,
179 | // Produces a retain cycle that we'll break when the window closes:
180 | crashLogSender: self,
181 | privacyPolicyURL: self.privacyPolicyURL,
182 | collectEmailSetting: collectEmailSetting,
183 | sendReportsAutomaticallySetting: sendAutomaticallySetting)
184 |
185 | // Drop reference to window after closing to eventually free memory.
186 | // Call this before potentially making the window run in a modal loop.
187 | self.crashReportWindowController?.onWindowWillClose = { [unowned self] _ in
188 | if displayAsModal {
189 | NSApp.stopModal()
190 | }
191 | self.crashReportWindowController = nil
192 | }
193 |
194 | self.crashReportWindowController?.showWindow(self)
195 | self.crashReportWindowController?.window?.makeKeyAndOrderFront(self)
196 |
197 | if displayAsModal,
198 | let window = self.crashReportWindowController?.window {
199 | NSApp.runModal(for: window)
200 | }
201 | }
202 |
203 | internal func hasSeen(_ crashLog: CrashLog) -> Bool {
204 | // No need to compare dates, because that's done in the file loop.
205 | // Check to see if we've already reported this exact crash log.
206 | return crashLog.contentHash == self.lastSeenCrashLogMD5Hash
207 | }
208 |
209 | internal func remember(_ crashLog: CrashLog) {
210 | userDefaults.set(crashLog.contentHash, forKey: defaultsKeys.lastSeenCrashLogMD5Key)
211 | userDefaults.set(crashLog.modificationDate.timeIntervalSince1970, forKey: defaultsKeys.lastSeenCrashLogTimeSince1970Key)
212 | }
213 | }
214 |
215 | // MARK: - SendsCrashLog
216 |
217 | extension CrashReporter: SendsCrashLog {
218 | internal func send(emailAddress: String?, userProvidedDetails: String?, crashLogText: String) {
219 | var request = URLRequest(url: self.crashReporterURL)
220 | request.httpMethod = "POST"
221 |
222 | let boundary = UUID().uuidString.md5
223 |
224 | let contentType = "multipart/form-data; boundary=\(boundary)"
225 | request.setValue(contentType, forHTTPHeaderField: "Content-Type")
226 |
227 | let form: [String : String?] = [
228 | "userEmail" : emailAddress,
229 | "userProvidedDetails": userProvidedDetails,
230 | "crashlog" : crashLogText
231 | ]
232 | // See for a specification.
233 | // Example:
234 | // Content-Type: multipart/form-data; boundary=AaB03x
235 | //
236 | // --AaB03x
237 | // Content-Disposition: form-data; name="submit-name"
238 | //
239 | // Larry
240 | // --AaB03x
241 | // Content-Disposition: form-data; name="files"; filename="file1.txt"
242 | // Content-Type: text/plain
243 | //
244 | // ... contents of file1.txt ...
245 | // --AaB03x--
246 | let CRLF = "\r\n"
247 | let separator = "--\(boundary)" + CRLF
248 | let terminator = "--\(boundary)--" + CRLF
249 | let formString =
250 | separator
251 | + form.compactMap { key, value -> String? in
252 | guard let value = value, !value.isEmpty else { return nil }
253 | let lines = [
254 | "Content-Disposition: form-data; name=\"\(key)\"",
255 | "", // Key and value are separated by an empty line
256 | value
257 | ]
258 | return lines.joined(separator: "\r\n") + CRLF
259 | }.joined(separator: separator) + CRLF
260 | + terminator
261 | let formData = formString.data(using: .utf8, allowLossyConversion: true)
262 | request.httpBody = formData
263 |
264 | download(request) { (data, response, error) in
265 | // Ignore result of the upload.
266 | // Uncomment to debug the server response:
267 | //print(response, data.flatMap { String(data: $0, encoding: .utf8) })
268 | return
269 | }
270 | }
271 | }
272 |
273 | // MARK: - Crash report file metadata
274 |
275 | internal struct CrashInfo {
276 | let url: URL
277 | let date: Date
278 |
279 | init(url: URL, date: Date) {
280 | self.url = url
281 | self.date = date
282 | }
283 |
284 | func crashLog() -> CrashLog? {
285 | return CrashLog(url: url, modificationDate: date)
286 | }
287 | }
288 |
289 | extension CrashInfo {
290 | init?(url: URL, fileManager: FileManager) {
291 | self.init(
292 | url: url,
293 | getModificationDate: fileManager.fileModificationDate(url:))
294 | }
295 |
296 | init?(url: URL, getModificationDate: (URL) -> Date? = FileManager.default.fileModificationDate(url:)) {
297 | guard let modificationDate = getModificationDate(url) else { return nil}
298 | self.init(url: url, date: modificationDate)
299 | }
300 | }
301 |
302 | extension FileManager {
303 | fileprivate func fileModificationDate(url: URL) -> Date? {
304 | let fileAttributes: [FileAttributeKey: Any] = (try? self.attributesOfItem(atPath: url.path)) ?? [:]
305 | return fileAttributes[.modificationDate] as? Date
306 | }
307 |
308 | fileprivate var diagnosticsReportsFolderURL: URL {
309 | let homeDirectory: URL
310 |
311 | if #available(OSX 10.12, *) {
312 | homeDirectory = self.homeDirectoryForCurrentUser
313 | } else {
314 | homeDirectory = URL(fileURLWithPath: ("~" as NSString).expandingTildeInPath)
315 | }
316 |
317 | return homeDirectory
318 | .appendingPathComponent("Library")
319 | .appendingPathComponent("Logs")
320 | .appendingPathComponent("DiagnosticReports")
321 | }
322 | }
323 |
324 | fileprivate func newerThan(_ referenceDate: Date) -> (_ crashInfo: CrashInfo) -> Bool {
325 | return { crashInfo in
326 | crashInfo.date > referenceDate
327 | }
328 | }
329 |
330 | fileprivate func matches(appName: String) -> (_ url: URL) -> Bool {
331 | let lowerAppName = appName.lowercased()
332 |
333 | var crashSuffixes: [String] = [".crash"]
334 | if #available(macOS 12, *) {
335 | crashSuffixes.append(".ips")
336 | }
337 |
338 | return { url in
339 | let filename = url.lastPathComponent
340 | return filename.lowercased().hasPrefix(lowerAppName)
341 | && crashSuffixes.contains { filename.hasSuffix($0) }
342 | }
343 | }
344 |
345 | fileprivate func findMostRecent(crashInfos: [CrashInfo]) -> CrashInfo? {
346 | // Pairwise comparison; returns the most recent of the two.
347 | func moreRecent(_ lhs: CrashInfo?, _ rhs: CrashInfo) -> CrashInfo {
348 | guard let lhs = lhs else {
349 | return rhs
350 | }
351 | if lhs.date > rhs.date {
352 | return lhs
353 | }
354 | return rhs
355 | }
356 |
357 | return crashInfos.reduce(nil) { (mostRecent: CrashInfo?, next: CrashInfo) -> CrashInfo? in
358 | return moreRecent(mostRecent, next)
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/Sources/CrashReporter/UI/CrashReporterWindow.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Help us fix crashes by submitting this crash report. You can include your email address if you agree to being contacted for more details.
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | Your email address (optional)
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Describe how the crash occurred (optional)
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
233 |
234 |
235 |
248 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
--------------------------------------------------------------------------------
/CrashReporter.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5004D23C2426216C00BD5875 /* EmailAddressSettingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5004D23B2426216C00BD5875 /* EmailAddressSettingTests.swift */; };
11 | 501E2A65295481500040C497 /* NoClippingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501E2A64295481500040C497 /* NoClippingView.swift */; };
12 | 507A3F7B2425F861006A50B6 /* EmailAddressSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507A3F7A2425F861006A50B6 /* EmailAddressSetting.swift */; };
13 | 50F832DD22C9FBD300DBD0DD /* CrashReporter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50F832D322C9FBD300DBD0DD /* CrashReporter.framework */; };
14 | 50F832E422C9FBD300DBD0DD /* CrashReporter.h in Headers */ = {isa = PBXBuildFile; fileRef = 50F832D622C9FBD300DBD0DD /* CrashReporter.h */; settings = {ATTRIBUTES = (Public, ); }; };
15 | 50F832FE22CA006600DBD0DD /* String+md5.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F832F222CA006600DBD0DD /* String+md5.swift */; };
16 | 50F832FF22CA006600DBD0DD /* URLResponse+statusIsOK.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F832F322CA006600DBD0DD /* URLResponse+statusIsOK.swift */; };
17 | 50F8330022CA006600DBD0DD /* OneShotDownload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F832F422CA006600DBD0DD /* OneShotDownload.swift */; };
18 | 50F8330122CA006600DBD0DD /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F832F522CA006600DBD0DD /* CrashReporter.swift */; };
19 | 50F8330222CA006600DBD0DD /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F832F722CA006600DBD0DD /* CrashReportWindowController.swift */; };
20 | 50F8330322CA006600DBD0DD /* CrashReporterWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50F832F822CA006600DBD0DD /* CrashReporterWindow.xib */; };
21 | 50F8330722CA00F600DBD0DD /* CrashLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8330622CA00F600DBD0DD /* CrashLog.swift */; };
22 | 50F8330922CA027B00DBD0DD /* Bundle+InfoKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8330822CA027B00DBD0DD /* Bundle+InfoKeys.swift */; };
23 | 50F8330B22CA35B800DBD0DD /* HTTPURLResponse+valueForHTTPHeaderField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8330A22CA35B800DBD0DD /* HTTPURLResponse+valueForHTTPHeaderField.swift */; };
24 | 50F8331322CA3A3F00DBD0DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8331222CA3A3F00DBD0DD /* AppDelegate.swift */; };
25 | 50F8331522CA3A4100DBD0DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50F8331422CA3A4100DBD0DD /* Assets.xcassets */; };
26 | 50F8331822CA3A4100DBD0DD /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 50F8331622CA3A4100DBD0DD /* MainMenu.xib */; };
27 | 50F8332222CA823C00DBD0DD /* SendReportsAutomaticallySetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8332122CA823C00DBD0DD /* SendReportsAutomaticallySetting.swift */; };
28 | 50F8332422CA828700DBD0DD /* CrashReporter+DefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8332322CA828700DBD0DD /* CrashReporter+DefaultsKeys.swift */; };
29 | 50F8332722CA9B4B00DBD0DD /* SendReportsAutomaticallySettingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50F8332622CA9B4B00DBD0DD /* SendReportsAutomaticallySettingTests.swift */; };
30 | /* End PBXBuildFile section */
31 |
32 | /* Begin PBXContainerItemProxy section */
33 | 50F832DE22C9FBD300DBD0DD /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 50F832CA22C9FBD300DBD0DD /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 50F832D222C9FBD300DBD0DD;
38 | remoteInfo = CrashReporter;
39 | };
40 | 50F8331E22CA46C600DBD0DD /* PBXContainerItemProxy */ = {
41 | isa = PBXContainerItemProxy;
42 | containerPortal = 50F832CA22C9FBD300DBD0DD /* Project object */;
43 | proxyType = 1;
44 | remoteGlobalIDString = 50F832D222C9FBD300DBD0DD;
45 | remoteInfo = CrashReporter;
46 | };
47 | /* End PBXContainerItemProxy section */
48 |
49 | /* Begin PBXFileReference section */
50 | 5004D23B2426216C00BD5875 /* EmailAddressSettingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressSettingTests.swift; sourceTree = ""; };
51 | 501E2A64295481500040C497 /* NoClippingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoClippingView.swift; sourceTree = ""; };
52 | 507A3F7A2425F861006A50B6 /* EmailAddressSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailAddressSetting.swift; sourceTree = ""; };
53 | 50D1A8AE22CCC7FB007DC81A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
54 | 50F832D322C9FBD300DBD0DD /* CrashReporter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CrashReporter.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 50F832D622C9FBD300DBD0DD /* CrashReporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CrashReporter.h; sourceTree = ""; };
56 | 50F832D722C9FBD300DBD0DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
57 | 50F832DC22C9FBD300DBD0DD /* CrashReporterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CrashReporterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
58 | 50F832E322C9FBD300DBD0DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | 50F832ED22C9FFBB00DBD0DD /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
60 | 50F832F222CA006600DBD0DD /* String+md5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+md5.swift"; sourceTree = ""; };
61 | 50F832F322CA006600DBD0DD /* URLResponse+statusIsOK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLResponse+statusIsOK.swift"; sourceTree = ""; };
62 | 50F832F422CA006600DBD0DD /* OneShotDownload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneShotDownload.swift; sourceTree = ""; };
63 | 50F832F522CA006600DBD0DD /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = ""; };
64 | 50F832F722CA006600DBD0DD /* CrashReportWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReportWindowController.swift; sourceTree = ""; };
65 | 50F832F822CA006600DBD0DD /* CrashReporterWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CrashReporterWindow.xib; sourceTree = ""; };
66 | 50F8330622CA00F600DBD0DD /* CrashLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashLog.swift; sourceTree = ""; };
67 | 50F8330822CA027B00DBD0DD /* Bundle+InfoKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+InfoKeys.swift"; sourceTree = ""; };
68 | 50F8330A22CA35B800DBD0DD /* HTTPURLResponse+valueForHTTPHeaderField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HTTPURLResponse+valueForHTTPHeaderField.swift"; sourceTree = ""; };
69 | 50F8331022CA3A3F00DBD0DD /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
70 | 50F8331222CA3A3F00DBD0DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
71 | 50F8331422CA3A4100DBD0DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
72 | 50F8331722CA3A4100DBD0DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
73 | 50F8331922CA3A4100DBD0DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
74 | 50F8331A22CA3A4100DBD0DD /* Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Example.entitlements; sourceTree = ""; };
75 | 50F8332122CA823C00DBD0DD /* SendReportsAutomaticallySetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReportsAutomaticallySetting.swift; sourceTree = ""; };
76 | 50F8332322CA828700DBD0DD /* CrashReporter+DefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CrashReporter+DefaultsKeys.swift"; sourceTree = ""; };
77 | 50F8332622CA9B4B00DBD0DD /* SendReportsAutomaticallySettingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReportsAutomaticallySettingTests.swift; sourceTree = ""; };
78 | /* End PBXFileReference section */
79 |
80 | /* Begin PBXFrameworksBuildPhase section */
81 | 50F832D022C9FBD300DBD0DD /* Frameworks */ = {
82 | isa = PBXFrameworksBuildPhase;
83 | buildActionMask = 2147483647;
84 | files = (
85 | );
86 | runOnlyForDeploymentPostprocessing = 0;
87 | };
88 | 50F832D922C9FBD300DBD0DD /* Frameworks */ = {
89 | isa = PBXFrameworksBuildPhase;
90 | buildActionMask = 2147483647;
91 | files = (
92 | 50F832DD22C9FBD300DBD0DD /* CrashReporter.framework in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | 50F8330D22CA3A3F00DBD0DD /* Frameworks */ = {
97 | isa = PBXFrameworksBuildPhase;
98 | buildActionMask = 2147483647;
99 | files = (
100 | );
101 | runOnlyForDeploymentPostprocessing = 0;
102 | };
103 | /* End PBXFrameworksBuildPhase section */
104 |
105 | /* Begin PBXGroup section */
106 | 50F832C922C9FBD300DBD0DD = {
107 | isa = PBXGroup;
108 | children = (
109 | 50D1A8AE22CCC7FB007DC81A /* README.md */,
110 | 50F832ED22C9FFBB00DBD0DD /* LICENSE */,
111 | 50F832D522C9FBD300DBD0DD /* CrashReporter */,
112 | 50F832E022C9FBD300DBD0DD /* CrashReporterTests */,
113 | 50F8331122CA3A3F00DBD0DD /* Example */,
114 | 50F832D422C9FBD300DBD0DD /* Products */,
115 | );
116 | sourceTree = "";
117 | };
118 | 50F832D422C9FBD300DBD0DD /* Products */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 50F832D322C9FBD300DBD0DD /* CrashReporter.framework */,
122 | 50F832DC22C9FBD300DBD0DD /* CrashReporterTests.xctest */,
123 | 50F8331022CA3A3F00DBD0DD /* Example.app */,
124 | );
125 | name = Products;
126 | sourceTree = "";
127 | };
128 | 50F832D522C9FBD300DBD0DD /* CrashReporter */ = {
129 | isa = PBXGroup;
130 | children = (
131 | 50F832D622C9FBD300DBD0DD /* CrashReporter.h */,
132 | 50F832F522CA006600DBD0DD /* CrashReporter.swift */,
133 | 50F8332322CA828700DBD0DD /* CrashReporter+DefaultsKeys.swift */,
134 | 50F8330622CA00F600DBD0DD /* CrashLog.swift */,
135 | 507A3F7A2425F861006A50B6 /* EmailAddressSetting.swift */,
136 | 50F8332122CA823C00DBD0DD /* SendReportsAutomaticallySetting.swift */,
137 | 50F832F622CA006600DBD0DD /* UI */,
138 | 50F832F122CA006600DBD0DD /* Infrastructure */,
139 | 50F832D722C9FBD300DBD0DD /* Info.plist */,
140 | );
141 | name = CrashReporter;
142 | path = Sources/CrashReporter;
143 | sourceTree = "";
144 | };
145 | 50F832E022C9FBD300DBD0DD /* CrashReporterTests */ = {
146 | isa = PBXGroup;
147 | children = (
148 | 50F832E322C9FBD300DBD0DD /* Info.plist */,
149 | 50F8332622CA9B4B00DBD0DD /* SendReportsAutomaticallySettingTests.swift */,
150 | 5004D23B2426216C00BD5875 /* EmailAddressSettingTests.swift */,
151 | );
152 | name = CrashReporterTests;
153 | path = Tests/CrashReporterTests;
154 | sourceTree = "";
155 | };
156 | 50F832F122CA006600DBD0DD /* Infrastructure */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 50F8330822CA027B00DBD0DD /* Bundle+InfoKeys.swift */,
160 | 50F8330A22CA35B800DBD0DD /* HTTPURLResponse+valueForHTTPHeaderField.swift */,
161 | 50F832F422CA006600DBD0DD /* OneShotDownload.swift */,
162 | 50F832F222CA006600DBD0DD /* String+md5.swift */,
163 | 50F832F322CA006600DBD0DD /* URLResponse+statusIsOK.swift */,
164 | );
165 | path = Infrastructure;
166 | sourceTree = "";
167 | };
168 | 50F832F622CA006600DBD0DD /* UI */ = {
169 | isa = PBXGroup;
170 | children = (
171 | 50F832F722CA006600DBD0DD /* CrashReportWindowController.swift */,
172 | 501E2A64295481500040C497 /* NoClippingView.swift */,
173 | 50F832F822CA006600DBD0DD /* CrashReporterWindow.xib */,
174 | );
175 | path = UI;
176 | sourceTree = "";
177 | };
178 | 50F8331122CA3A3F00DBD0DD /* Example */ = {
179 | isa = PBXGroup;
180 | children = (
181 | 50F8331222CA3A3F00DBD0DD /* AppDelegate.swift */,
182 | 50F8331422CA3A4100DBD0DD /* Assets.xcassets */,
183 | 50F8331622CA3A4100DBD0DD /* MainMenu.xib */,
184 | 50F8331922CA3A4100DBD0DD /* Info.plist */,
185 | 50F8331A22CA3A4100DBD0DD /* Example.entitlements */,
186 | );
187 | path = Example;
188 | sourceTree = "";
189 | };
190 | /* End PBXGroup section */
191 |
192 | /* Begin PBXHeadersBuildPhase section */
193 | 50F832CE22C9FBD300DBD0DD /* Headers */ = {
194 | isa = PBXHeadersBuildPhase;
195 | buildActionMask = 2147483647;
196 | files = (
197 | 50F832E422C9FBD300DBD0DD /* CrashReporter.h in Headers */,
198 | );
199 | runOnlyForDeploymentPostprocessing = 0;
200 | };
201 | /* End PBXHeadersBuildPhase section */
202 |
203 | /* Begin PBXNativeTarget section */
204 | 50F832D222C9FBD300DBD0DD /* CrashReporter */ = {
205 | isa = PBXNativeTarget;
206 | buildConfigurationList = 50F832E722C9FBD300DBD0DD /* Build configuration list for PBXNativeTarget "CrashReporter" */;
207 | buildPhases = (
208 | 50F832CE22C9FBD300DBD0DD /* Headers */,
209 | 50F832CF22C9FBD300DBD0DD /* Sources */,
210 | 50F832D022C9FBD300DBD0DD /* Frameworks */,
211 | 50F832D122C9FBD300DBD0DD /* Resources */,
212 | );
213 | buildRules = (
214 | );
215 | dependencies = (
216 | );
217 | name = CrashReporter;
218 | productName = CrashReporter;
219 | productReference = 50F832D322C9FBD300DBD0DD /* CrashReporter.framework */;
220 | productType = "com.apple.product-type.framework";
221 | };
222 | 50F832DB22C9FBD300DBD0DD /* CrashReporterTests */ = {
223 | isa = PBXNativeTarget;
224 | buildConfigurationList = 50F832EA22C9FBD300DBD0DD /* Build configuration list for PBXNativeTarget "CrashReporterTests" */;
225 | buildPhases = (
226 | 50F832D822C9FBD300DBD0DD /* Sources */,
227 | 50F832D922C9FBD300DBD0DD /* Frameworks */,
228 | 50F832DA22C9FBD300DBD0DD /* Resources */,
229 | );
230 | buildRules = (
231 | );
232 | dependencies = (
233 | 50F832DF22C9FBD300DBD0DD /* PBXTargetDependency */,
234 | );
235 | name = CrashReporterTests;
236 | productName = CrashReporterTests;
237 | productReference = 50F832DC22C9FBD300DBD0DD /* CrashReporterTests.xctest */;
238 | productType = "com.apple.product-type.bundle.unit-test";
239 | };
240 | 50F8330F22CA3A3F00DBD0DD /* Example */ = {
241 | isa = PBXNativeTarget;
242 | buildConfigurationList = 50F8331B22CA3A4100DBD0DD /* Build configuration list for PBXNativeTarget "Example" */;
243 | buildPhases = (
244 | 50F8330C22CA3A3F00DBD0DD /* Sources */,
245 | 50F8330D22CA3A3F00DBD0DD /* Frameworks */,
246 | 50F8330E22CA3A3F00DBD0DD /* Resources */,
247 | );
248 | buildRules = (
249 | );
250 | dependencies = (
251 | 50F8331F22CA46C600DBD0DD /* PBXTargetDependency */,
252 | );
253 | name = Example;
254 | productName = Example;
255 | productReference = 50F8331022CA3A3F00DBD0DD /* Example.app */;
256 | productType = "com.apple.product-type.application";
257 | };
258 | /* End PBXNativeTarget section */
259 |
260 | /* Begin PBXProject section */
261 | 50F832CA22C9FBD300DBD0DD /* Project object */ = {
262 | isa = PBXProject;
263 | attributes = {
264 | LastSwiftUpdateCheck = 1020;
265 | LastUpgradeCheck = 1420;
266 | ORGANIZATIONNAME = "Christian Tietze";
267 | TargetAttributes = {
268 | 50F832D222C9FBD300DBD0DD = {
269 | CreatedOnToolsVersion = 10.2;
270 | LastSwiftMigration = 1020;
271 | };
272 | 50F832DB22C9FBD300DBD0DD = {
273 | CreatedOnToolsVersion = 10.2;
274 | LastSwiftMigration = 1020;
275 | };
276 | 50F8330F22CA3A3F00DBD0DD = {
277 | CreatedOnToolsVersion = 10.2;
278 | SystemCapabilities = {
279 | com.apple.Sandbox = {
280 | enabled = 0;
281 | };
282 | };
283 | };
284 | };
285 | };
286 | buildConfigurationList = 50F832CD22C9FBD300DBD0DD /* Build configuration list for PBXProject "CrashReporter" */;
287 | compatibilityVersion = "Xcode 9.3";
288 | developmentRegion = en;
289 | hasScannedForEncodings = 0;
290 | knownRegions = (
291 | en,
292 | Base,
293 | );
294 | mainGroup = 50F832C922C9FBD300DBD0DD;
295 | productRefGroup = 50F832D422C9FBD300DBD0DD /* Products */;
296 | projectDirPath = "";
297 | projectRoot = "";
298 | targets = (
299 | 50F832D222C9FBD300DBD0DD /* CrashReporter */,
300 | 50F832DB22C9FBD300DBD0DD /* CrashReporterTests */,
301 | 50F8330F22CA3A3F00DBD0DD /* Example */,
302 | );
303 | };
304 | /* End PBXProject section */
305 |
306 | /* Begin PBXResourcesBuildPhase section */
307 | 50F832D122C9FBD300DBD0DD /* Resources */ = {
308 | isa = PBXResourcesBuildPhase;
309 | buildActionMask = 2147483647;
310 | files = (
311 | 50F8330322CA006600DBD0DD /* CrashReporterWindow.xib in Resources */,
312 | );
313 | runOnlyForDeploymentPostprocessing = 0;
314 | };
315 | 50F832DA22C9FBD300DBD0DD /* Resources */ = {
316 | isa = PBXResourcesBuildPhase;
317 | buildActionMask = 2147483647;
318 | files = (
319 | );
320 | runOnlyForDeploymentPostprocessing = 0;
321 | };
322 | 50F8330E22CA3A3F00DBD0DD /* Resources */ = {
323 | isa = PBXResourcesBuildPhase;
324 | buildActionMask = 2147483647;
325 | files = (
326 | 50F8331522CA3A4100DBD0DD /* Assets.xcassets in Resources */,
327 | 50F8331822CA3A4100DBD0DD /* MainMenu.xib in Resources */,
328 | );
329 | runOnlyForDeploymentPostprocessing = 0;
330 | };
331 | /* End PBXResourcesBuildPhase section */
332 |
333 | /* Begin PBXSourcesBuildPhase section */
334 | 50F832CF22C9FBD300DBD0DD /* Sources */ = {
335 | isa = PBXSourcesBuildPhase;
336 | buildActionMask = 2147483647;
337 | files = (
338 | 50F8330122CA006600DBD0DD /* CrashReporter.swift in Sources */,
339 | 501E2A65295481500040C497 /* NoClippingView.swift in Sources */,
340 | 50F8330922CA027B00DBD0DD /* Bundle+InfoKeys.swift in Sources */,
341 | 50F8330B22CA35B800DBD0DD /* HTTPURLResponse+valueForHTTPHeaderField.swift in Sources */,
342 | 50F8330222CA006600DBD0DD /* CrashReportWindowController.swift in Sources */,
343 | 50F832FF22CA006600DBD0DD /* URLResponse+statusIsOK.swift in Sources */,
344 | 50F8332422CA828700DBD0DD /* CrashReporter+DefaultsKeys.swift in Sources */,
345 | 50F8330022CA006600DBD0DD /* OneShotDownload.swift in Sources */,
346 | 50F8330722CA00F600DBD0DD /* CrashLog.swift in Sources */,
347 | 50F8332222CA823C00DBD0DD /* SendReportsAutomaticallySetting.swift in Sources */,
348 | 50F832FE22CA006600DBD0DD /* String+md5.swift in Sources */,
349 | 507A3F7B2425F861006A50B6 /* EmailAddressSetting.swift in Sources */,
350 | );
351 | runOnlyForDeploymentPostprocessing = 0;
352 | };
353 | 50F832D822C9FBD300DBD0DD /* Sources */ = {
354 | isa = PBXSourcesBuildPhase;
355 | buildActionMask = 2147483647;
356 | files = (
357 | 5004D23C2426216C00BD5875 /* EmailAddressSettingTests.swift in Sources */,
358 | 50F8332722CA9B4B00DBD0DD /* SendReportsAutomaticallySettingTests.swift in Sources */,
359 | );
360 | runOnlyForDeploymentPostprocessing = 0;
361 | };
362 | 50F8330C22CA3A3F00DBD0DD /* Sources */ = {
363 | isa = PBXSourcesBuildPhase;
364 | buildActionMask = 2147483647;
365 | files = (
366 | 50F8331322CA3A3F00DBD0DD /* AppDelegate.swift in Sources */,
367 | );
368 | runOnlyForDeploymentPostprocessing = 0;
369 | };
370 | /* End PBXSourcesBuildPhase section */
371 |
372 | /* Begin PBXTargetDependency section */
373 | 50F832DF22C9FBD300DBD0DD /* PBXTargetDependency */ = {
374 | isa = PBXTargetDependency;
375 | target = 50F832D222C9FBD300DBD0DD /* CrashReporter */;
376 | targetProxy = 50F832DE22C9FBD300DBD0DD /* PBXContainerItemProxy */;
377 | };
378 | 50F8331F22CA46C600DBD0DD /* PBXTargetDependency */ = {
379 | isa = PBXTargetDependency;
380 | target = 50F832D222C9FBD300DBD0DD /* CrashReporter */;
381 | targetProxy = 50F8331E22CA46C600DBD0DD /* PBXContainerItemProxy */;
382 | };
383 | /* End PBXTargetDependency section */
384 |
385 | /* Begin PBXVariantGroup section */
386 | 50F8331622CA3A4100DBD0DD /* MainMenu.xib */ = {
387 | isa = PBXVariantGroup;
388 | children = (
389 | 50F8331722CA3A4100DBD0DD /* Base */,
390 | );
391 | name = MainMenu.xib;
392 | sourceTree = "";
393 | };
394 | /* End PBXVariantGroup section */
395 |
396 | /* Begin XCBuildConfiguration section */
397 | 50F832E522C9FBD300DBD0DD /* Debug */ = {
398 | isa = XCBuildConfiguration;
399 | buildSettings = {
400 | ALWAYS_SEARCH_USER_PATHS = NO;
401 | CLANG_ANALYZER_NONNULL = YES;
402 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
403 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
404 | CLANG_CXX_LIBRARY = "libc++";
405 | CLANG_ENABLE_MODULES = YES;
406 | CLANG_ENABLE_OBJC_ARC = YES;
407 | CLANG_ENABLE_OBJC_WEAK = YES;
408 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
409 | CLANG_WARN_BOOL_CONVERSION = YES;
410 | CLANG_WARN_COMMA = YES;
411 | CLANG_WARN_CONSTANT_CONVERSION = YES;
412 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
414 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
415 | CLANG_WARN_EMPTY_BODY = YES;
416 | CLANG_WARN_ENUM_CONVERSION = YES;
417 | CLANG_WARN_INFINITE_RECURSION = YES;
418 | CLANG_WARN_INT_CONVERSION = YES;
419 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
420 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
421 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
422 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
423 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
424 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
425 | CLANG_WARN_STRICT_PROTOTYPES = YES;
426 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
427 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
428 | CLANG_WARN_UNREACHABLE_CODE = YES;
429 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
430 | CODE_SIGN_STYLE = Manual;
431 | COPY_PHASE_STRIP = NO;
432 | CURRENT_PROJECT_VERSION = 1;
433 | DEAD_CODE_STRIPPING = YES;
434 | DEBUG_INFORMATION_FORMAT = dwarf;
435 | ENABLE_STRICT_OBJC_MSGSEND = YES;
436 | ENABLE_TESTABILITY = YES;
437 | GCC_C_LANGUAGE_STANDARD = gnu11;
438 | GCC_DYNAMIC_NO_PIC = NO;
439 | GCC_NO_COMMON_BLOCKS = YES;
440 | GCC_OPTIMIZATION_LEVEL = 0;
441 | GCC_PREPROCESSOR_DEFINITIONS = (
442 | "DEBUG=1",
443 | "$(inherited)",
444 | );
445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
447 | GCC_WARN_UNDECLARED_SELECTOR = YES;
448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
449 | GCC_WARN_UNUSED_FUNCTION = YES;
450 | GCC_WARN_UNUSED_VARIABLE = YES;
451 | MACOSX_DEPLOYMENT_TARGET = 10.13;
452 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
453 | MTL_FAST_MATH = YES;
454 | ONLY_ACTIVE_ARCH = YES;
455 | SDKROOT = macosx;
456 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
457 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
458 | VERSIONING_SYSTEM = "apple-generic";
459 | VERSION_INFO_PREFIX = "";
460 | };
461 | name = Debug;
462 | };
463 | 50F832E622C9FBD300DBD0DD /* Release */ = {
464 | isa = XCBuildConfiguration;
465 | buildSettings = {
466 | ALWAYS_SEARCH_USER_PATHS = NO;
467 | CLANG_ANALYZER_NONNULL = YES;
468 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
469 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
470 | CLANG_CXX_LIBRARY = "libc++";
471 | CLANG_ENABLE_MODULES = YES;
472 | CLANG_ENABLE_OBJC_ARC = YES;
473 | CLANG_ENABLE_OBJC_WEAK = YES;
474 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
475 | CLANG_WARN_BOOL_CONVERSION = YES;
476 | CLANG_WARN_COMMA = YES;
477 | CLANG_WARN_CONSTANT_CONVERSION = YES;
478 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
479 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
480 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
481 | CLANG_WARN_EMPTY_BODY = YES;
482 | CLANG_WARN_ENUM_CONVERSION = YES;
483 | CLANG_WARN_INFINITE_RECURSION = YES;
484 | CLANG_WARN_INT_CONVERSION = YES;
485 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
486 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
487 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
488 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
489 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
490 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
491 | CLANG_WARN_STRICT_PROTOTYPES = YES;
492 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
493 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
494 | CLANG_WARN_UNREACHABLE_CODE = YES;
495 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
496 | CODE_SIGN_STYLE = Manual;
497 | COPY_PHASE_STRIP = NO;
498 | CURRENT_PROJECT_VERSION = 1;
499 | DEAD_CODE_STRIPPING = YES;
500 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
501 | ENABLE_NS_ASSERTIONS = NO;
502 | ENABLE_STRICT_OBJC_MSGSEND = YES;
503 | GCC_C_LANGUAGE_STANDARD = gnu11;
504 | GCC_NO_COMMON_BLOCKS = YES;
505 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
506 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
507 | GCC_WARN_UNDECLARED_SELECTOR = YES;
508 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
509 | GCC_WARN_UNUSED_FUNCTION = YES;
510 | GCC_WARN_UNUSED_VARIABLE = YES;
511 | MACOSX_DEPLOYMENT_TARGET = 10.13;
512 | MTL_ENABLE_DEBUG_INFO = NO;
513 | MTL_FAST_MATH = YES;
514 | SDKROOT = macosx;
515 | SWIFT_COMPILATION_MODE = wholemodule;
516 | SWIFT_OPTIMIZATION_LEVEL = "-O";
517 | VERSIONING_SYSTEM = "apple-generic";
518 | VERSION_INFO_PREFIX = "";
519 | };
520 | name = Release;
521 | };
522 | 50F832E822C9FBD300DBD0DD /* Debug */ = {
523 | isa = XCBuildConfiguration;
524 | buildSettings = {
525 | CLANG_ENABLE_MODULES = YES;
526 | COMBINE_HIDPI_IMAGES = YES;
527 | DEAD_CODE_STRIPPING = YES;
528 | DEFINES_MODULE = YES;
529 | DYLIB_COMPATIBILITY_VERSION = 1;
530 | DYLIB_CURRENT_VERSION = 1;
531 | DYLIB_INSTALL_NAME_BASE = "@rpath";
532 | FRAMEWORK_VERSION = A;
533 | INFOPLIST_FILE = Sources/CrashReporter/Info.plist;
534 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
535 | LD_RUNPATH_SEARCH_PATHS = (
536 | "$(inherited)",
537 | "@executable_path/../Frameworks",
538 | "@loader_path/Frameworks",
539 | );
540 | PRODUCT_BUNDLE_IDENTIFIER = de.christiantietze.CrashReporter;
541 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
542 | SKIP_INSTALL = YES;
543 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
544 | SWIFT_VERSION = 5.0;
545 | };
546 | name = Debug;
547 | };
548 | 50F832E922C9FBD300DBD0DD /* Release */ = {
549 | isa = XCBuildConfiguration;
550 | buildSettings = {
551 | CLANG_ENABLE_MODULES = YES;
552 | COMBINE_HIDPI_IMAGES = YES;
553 | DEAD_CODE_STRIPPING = YES;
554 | DEFINES_MODULE = YES;
555 | DYLIB_COMPATIBILITY_VERSION = 1;
556 | DYLIB_CURRENT_VERSION = 1;
557 | DYLIB_INSTALL_NAME_BASE = "@rpath";
558 | FRAMEWORK_VERSION = A;
559 | INFOPLIST_FILE = Sources/CrashReporter/Info.plist;
560 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
561 | LD_RUNPATH_SEARCH_PATHS = (
562 | "$(inherited)",
563 | "@executable_path/../Frameworks",
564 | "@loader_path/Frameworks",
565 | );
566 | PRODUCT_BUNDLE_IDENTIFIER = de.christiantietze.CrashReporter;
567 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
568 | SKIP_INSTALL = YES;
569 | SWIFT_VERSION = 5.0;
570 | };
571 | name = Release;
572 | };
573 | 50F832EB22C9FBD300DBD0DD /* Debug */ = {
574 | isa = XCBuildConfiguration;
575 | buildSettings = {
576 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
577 | CLANG_ENABLE_MODULES = YES;
578 | COMBINE_HIDPI_IMAGES = YES;
579 | DEAD_CODE_STRIPPING = YES;
580 | INFOPLIST_FILE = Tests/CrashReporterTests/Info.plist;
581 | LD_RUNPATH_SEARCH_PATHS = (
582 | "$(inherited)",
583 | "@executable_path/../Frameworks",
584 | "@loader_path/../Frameworks",
585 | );
586 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
587 | PRODUCT_BUNDLE_IDENTIFIER = de.christiantietze.CrashReporterTests;
588 | PRODUCT_NAME = "$(TARGET_NAME)";
589 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
590 | SWIFT_VERSION = 5.0;
591 | };
592 | name = Debug;
593 | };
594 | 50F832EC22C9FBD300DBD0DD /* Release */ = {
595 | isa = XCBuildConfiguration;
596 | buildSettings = {
597 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
598 | CLANG_ENABLE_MODULES = YES;
599 | COMBINE_HIDPI_IMAGES = YES;
600 | DEAD_CODE_STRIPPING = YES;
601 | INFOPLIST_FILE = Tests/CrashReporterTests/Info.plist;
602 | LD_RUNPATH_SEARCH_PATHS = (
603 | "$(inherited)",
604 | "@executable_path/../Frameworks",
605 | "@loader_path/../Frameworks",
606 | );
607 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
608 | PRODUCT_BUNDLE_IDENTIFIER = de.christiantietze.CrashReporterTests;
609 | PRODUCT_NAME = "$(TARGET_NAME)";
610 | SWIFT_VERSION = 5.0;
611 | };
612 | name = Release;
613 | };
614 | 50F8331C22CA3A4100DBD0DD /* Debug */ = {
615 | isa = XCBuildConfiguration;
616 | buildSettings = {
617 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
618 | CODE_SIGN_IDENTITY = "-";
619 | COMBINE_HIDPI_IMAGES = YES;
620 | DEAD_CODE_STRIPPING = YES;
621 | INFOPLIST_FILE = Example/Info.plist;
622 | LD_RUNPATH_SEARCH_PATHS = (
623 | "$(inherited)",
624 | "@executable_path/../Frameworks",
625 | );
626 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
627 | PRODUCT_BUNDLE_IDENTIFIER = de.christiantietze.CrashReporter.Example;
628 | PRODUCT_NAME = "$(TARGET_NAME)";
629 | SWIFT_VERSION = 5.0;
630 | };
631 | name = Debug;
632 | };
633 | 50F8331D22CA3A4100DBD0DD /* Release */ = {
634 | isa = XCBuildConfiguration;
635 | buildSettings = {
636 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
637 | CODE_SIGN_IDENTITY = "-";
638 | COMBINE_HIDPI_IMAGES = YES;
639 | DEAD_CODE_STRIPPING = YES;
640 | INFOPLIST_FILE = Example/Info.plist;
641 | LD_RUNPATH_SEARCH_PATHS = (
642 | "$(inherited)",
643 | "@executable_path/../Frameworks",
644 | );
645 | MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
646 | PRODUCT_BUNDLE_IDENTIFIER = de.christiantietze.CrashReporter.Example;
647 | PRODUCT_NAME = "$(TARGET_NAME)";
648 | SWIFT_VERSION = 5.0;
649 | };
650 | name = Release;
651 | };
652 | /* End XCBuildConfiguration section */
653 |
654 | /* Begin XCConfigurationList section */
655 | 50F832CD22C9FBD300DBD0DD /* Build configuration list for PBXProject "CrashReporter" */ = {
656 | isa = XCConfigurationList;
657 | buildConfigurations = (
658 | 50F832E522C9FBD300DBD0DD /* Debug */,
659 | 50F832E622C9FBD300DBD0DD /* Release */,
660 | );
661 | defaultConfigurationIsVisible = 0;
662 | defaultConfigurationName = Release;
663 | };
664 | 50F832E722C9FBD300DBD0DD /* Build configuration list for PBXNativeTarget "CrashReporter" */ = {
665 | isa = XCConfigurationList;
666 | buildConfigurations = (
667 | 50F832E822C9FBD300DBD0DD /* Debug */,
668 | 50F832E922C9FBD300DBD0DD /* Release */,
669 | );
670 | defaultConfigurationIsVisible = 0;
671 | defaultConfigurationName = Release;
672 | };
673 | 50F832EA22C9FBD300DBD0DD /* Build configuration list for PBXNativeTarget "CrashReporterTests" */ = {
674 | isa = XCConfigurationList;
675 | buildConfigurations = (
676 | 50F832EB22C9FBD300DBD0DD /* Debug */,
677 | 50F832EC22C9FBD300DBD0DD /* Release */,
678 | );
679 | defaultConfigurationIsVisible = 0;
680 | defaultConfigurationName = Release;
681 | };
682 | 50F8331B22CA3A4100DBD0DD /* Build configuration list for PBXNativeTarget "Example" */ = {
683 | isa = XCConfigurationList;
684 | buildConfigurations = (
685 | 50F8331C22CA3A4100DBD0DD /* Debug */,
686 | 50F8331D22CA3A4100DBD0DD /* Release */,
687 | );
688 | defaultConfigurationIsVisible = 0;
689 | defaultConfigurationName = Release;
690 | };
691 | /* End XCConfigurationList section */
692 | };
693 | rootObject = 50F832CA22C9FBD300DBD0DD /* Project object */;
694 | }
695 |
--------------------------------------------------------------------------------
/Example/Base.lproj/MainMenu.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
--------------------------------------------------------------------------------