fastlane-plugin-bugsee 1.0.2 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dcd43503f4914135fd4896586c603e23d1019d943290b73577c1522c5394b0e3
|
4
|
+
data.tar.gz: 62a93a23e7a84337d5598437fbc73e16970d8dabc076bb1c6aba709b64fcb79e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b022387a002f3758bfba89b44209d1bb2dfb57a2d700deca442cbcd08c24e09ed2d54d0d201343a4844b64f16dcae2c7b43a942632424f10b7b25b0457d30b0c
|
7
|
+
data.tar.gz: b7565396b0650e915c5166b8a5e4d5d05199bbe7e641370e6d24afc8d414ed176452de1dd740c5acb3d4d8aab03c84a642df8e87f5e8ec89908db44a597120c1
|
data/BugseeAgent
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
|
3
|
+
# Copyright 2016-2023 Bugsee. All rights reserved.
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
# * Start editing your scheme by going to Product -> Scheme -> Edit Scheme
|
7
|
+
# * Add an extra "Run Script" build phase to "Post-actions" stage of your scheme
|
8
|
+
# * Click "+" button in the bottom left corner.
|
9
|
+
# * Uncomment and paste the following script. Don't forget to replace <APP_TOKEN> with your actual application token
|
10
|
+
#
|
11
|
+
# --- INVOCATION SCRIPT BEGIN ---
|
12
|
+
# SCRIPT_SRC=$(find "$PROJECT_DIR" -name 'BugseeAgent' | head -1)
|
13
|
+
# if [ ! "${SCRIPT_SRC}" ]; then
|
14
|
+
# echo "Error: Bugsee build phase script not found. Make sure that you're including Bugsee.bundle in your project directory"
|
15
|
+
# exit 1
|
16
|
+
# fi
|
17
|
+
# python3 "${SCRIPT_SRC}" <APP_TOKEN>
|
18
|
+
# --- INVOCATION SCRIPT END ---
|
19
|
+
|
20
|
+
import os
|
21
|
+
import subprocess
|
22
|
+
import zipfile
|
23
|
+
import tempfile
|
24
|
+
import sys
|
25
|
+
import urllib.request, urllib.error, urllib.parse
|
26
|
+
import re
|
27
|
+
import json
|
28
|
+
import hashlib
|
29
|
+
import shutil
|
30
|
+
from optparse import OptionParser
|
31
|
+
import fnmatch
|
32
|
+
|
33
|
+
def isInUploadedList(images, imageList):
|
34
|
+
for image in images:
|
35
|
+
if (image in imageList):
|
36
|
+
return True
|
37
|
+
return False
|
38
|
+
|
39
|
+
def saveUploadedList(images):
|
40
|
+
print("Storing identifiers so we won't upload them again")
|
41
|
+
with open(os.path.expanduser("~/.bugseeUploadList"), 'w+') as data_file:
|
42
|
+
json.dump(images, data_file)
|
43
|
+
return
|
44
|
+
|
45
|
+
def loadUploadedList():
|
46
|
+
try:
|
47
|
+
with open(os.path.expanduser("~/.bugseeUploadList")) as data_file:
|
48
|
+
return json.load(data_file)
|
49
|
+
except Exception as error:
|
50
|
+
return []
|
51
|
+
|
52
|
+
def parseDSYM(fullPath):
|
53
|
+
images = []
|
54
|
+
try:
|
55
|
+
out = subprocess.run(['/usr/bin/dwarfdump', '-u', fullPath], check=True, capture_output=True, text=True).stdout
|
56
|
+
# UUID: 598A8EC3-B348-36C6-8B3A-0390B247EFF2 (arm64) /Users/finik/Downloads/BugseeDev
|
57
|
+
lines = out.splitlines()
|
58
|
+
|
59
|
+
for line in lines:
|
60
|
+
searchObj = re.search(r'UUID: (.*) \((\w+)\)', line)
|
61
|
+
if (searchObj):
|
62
|
+
images.append(searchObj.group(1))
|
63
|
+
|
64
|
+
except subprocess.CalledProcessError as e:
|
65
|
+
return images
|
66
|
+
|
67
|
+
return images
|
68
|
+
|
69
|
+
def deobfuscateDSYM(fullPath, mapsPath):
|
70
|
+
try:
|
71
|
+
out = subprocess.run(['/usr/bin/dsymutil', '--symbol-map', mapsPath, fullPath], check=True, capture_output=True, text=True).stdout
|
72
|
+
except subprocess.CalledProcessError as e:
|
73
|
+
return
|
74
|
+
return
|
75
|
+
|
76
|
+
def getIcon():
|
77
|
+
if not options.from_xcode:
|
78
|
+
# No icon extraction when run outside of XCode
|
79
|
+
# TODO: Get it from fastlane if we run after build?
|
80
|
+
return None
|
81
|
+
try:
|
82
|
+
info_file_path = os.path.join(options.build_dir, os.environ['INFOPLIST_PATH'])
|
83
|
+
info_file_dir = os.path.dirname(info_file_path)
|
84
|
+
# p = subprocess.Popen('/usr/libexec/PlistBuddy -c "Print :CFBundleIcons:CFBundlePrimaryIcon:CFBundleIconFiles" %s' % info_file_path,
|
85
|
+
# stdout=subprocess.PIPE, shell=True)
|
86
|
+
|
87
|
+
# stdout, stderr = p.communicate()
|
88
|
+
# icons = stdout.split()
|
89
|
+
# if len(icons) > 4:
|
90
|
+
# return icons[2:-1]
|
91
|
+
icons = [
|
92
|
+
'114x114',
|
93
|
+
'120x120', 'AppIcon60x60@2x', 'AppIcon40x40@3x',
|
94
|
+
'144x144',
|
95
|
+
'180x180', 'AppIcon60x60@3x',
|
96
|
+
'87x87', 'AppIcon29x29@3x',
|
97
|
+
'80x80', 'AppIcon40x40@2x',
|
98
|
+
'72x72',
|
99
|
+
'58x58', 'AppIcon29x29@2x',
|
100
|
+
'57x57',
|
101
|
+
'29x29'
|
102
|
+
]
|
103
|
+
|
104
|
+
for icon in icons:
|
105
|
+
path = os.path.join(info_file_dir, icon + '.png')
|
106
|
+
if os.path.isfile(path):
|
107
|
+
return path
|
108
|
+
|
109
|
+
except Exception as error:
|
110
|
+
return None
|
111
|
+
|
112
|
+
return None
|
113
|
+
|
114
|
+
def getVersionAndBuild(zipFile):
|
115
|
+
version = None
|
116
|
+
build = None
|
117
|
+
if options.dsym_list:
|
118
|
+
searchObj = re.search(r'-([\w\.]+)-(\d+).dSYM.zip$', zipFile)
|
119
|
+
if (searchObj):
|
120
|
+
version = searchObj.group(1)
|
121
|
+
build = searchObj.group(2)
|
122
|
+
else:
|
123
|
+
try:
|
124
|
+
info_file_path = os.path.join(options.build_dir, os.environ['INFOPLIST_PATH'])
|
125
|
+
cmd = '/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" -c "Print :CFBundleVersion" "' + info_file_path + '"'
|
126
|
+
p = subprocess.Popen([cmd], stdout=subprocess.PIPE, shell=True)
|
127
|
+
stdout, stderr = p.communicate()
|
128
|
+
version, build = stdout.decode().split()
|
129
|
+
except Exception as error:
|
130
|
+
return (None, None)
|
131
|
+
|
132
|
+
return (version, build)
|
133
|
+
|
134
|
+
def uncrushIcon(icon, tempDir):
|
135
|
+
try:
|
136
|
+
dest = os.path.join(tempDir, 'icon.png')
|
137
|
+
print("Uncrushing Icon PNG file to %s" % dest)
|
138
|
+
cmd = '/usr/bin/xcrun pngcrush -revert-iphone-optimizations "'+ icon + '" "' + dest + '"'
|
139
|
+
p = subprocess.Popen([cmd], stdout=subprocess.PIPE, shell=True)
|
140
|
+
|
141
|
+
stdout, stderr = p.communicate()
|
142
|
+
except Exception as error:
|
143
|
+
return None
|
144
|
+
|
145
|
+
return dest
|
146
|
+
|
147
|
+
def requestEndPoint(version, build):
|
148
|
+
encoded_data = json.dumps({
|
149
|
+
'version': version,
|
150
|
+
'build': build
|
151
|
+
}).encode()
|
152
|
+
|
153
|
+
req = urllib.request.Request(options.endpoint + '/apps/' + APP_TOKEN + '/symbols', data=encoded_data)
|
154
|
+
req.add_header('Content-Type', 'application/json')
|
155
|
+
response = urllib.request.urlopen(req)
|
156
|
+
|
157
|
+
text = response.read()
|
158
|
+
|
159
|
+
return json.loads(text.decode())
|
160
|
+
|
161
|
+
def uploadBundle(endpoint, filePath):
|
162
|
+
# TODO: Change it to urllib2 as well
|
163
|
+
cmd = 'curl -v -T "' + filePath + '" "' + endpoint + '"' + ' --write-out %{http_code} --silent --output /dev/null'
|
164
|
+
p = subprocess.Popen([cmd],
|
165
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
166
|
+
stdout, stderr = p.communicate()
|
167
|
+
code = stdout.decode()
|
168
|
+
if code == '200':
|
169
|
+
return True
|
170
|
+
|
171
|
+
return False
|
172
|
+
|
173
|
+
def updateStatus(symbolId):
|
174
|
+
encoded_data = json.dumps({
|
175
|
+
'status': 'uploading',
|
176
|
+
}).encode()
|
177
|
+
|
178
|
+
req = urllib.request.Request(options.endpoint + '/symbols/' + symbolId + '/status', data=encoded_data)
|
179
|
+
req.add_header('Content-Type', 'application/json')
|
180
|
+
response = urllib.request.urlopen(req)
|
181
|
+
|
182
|
+
text = response.read()
|
183
|
+
|
184
|
+
r = json.loads(text.decode())
|
185
|
+
if (r and r.get('ok')):
|
186
|
+
return True
|
187
|
+
return False
|
188
|
+
|
189
|
+
def uploadZipFile(zipFileLocation):
|
190
|
+
if options.version or options.build:
|
191
|
+
version = options.version
|
192
|
+
build = options.build
|
193
|
+
else:
|
194
|
+
version, build = getVersionAndBuild(zipFileLocation)
|
195
|
+
|
196
|
+
r = requestEndPoint(version, build)
|
197
|
+
if (r.get('ok') and r.get('endpoint')):
|
198
|
+
print("Uploading to %s" % r.get('endpoint'))
|
199
|
+
retries = 0
|
200
|
+
while retries < 5:
|
201
|
+
upload_result = uploadBundle(r.get('endpoint'), zipFileLocation)
|
202
|
+
if upload_result:
|
203
|
+
return True
|
204
|
+
print("Uploading to %s failed. Retrying" % r.get('endpoint'))
|
205
|
+
retries += 1
|
206
|
+
|
207
|
+
return False
|
208
|
+
|
209
|
+
def main():
|
210
|
+
tempDir = tempfile.mkdtemp()
|
211
|
+
print("Processing in " + tempDir)
|
212
|
+
zipFileLocation = os.path.join(tempDir, 'symbols.zip')
|
213
|
+
dwarfs = []
|
214
|
+
uploadedImages = loadUploadedList()
|
215
|
+
|
216
|
+
if options.dsym_list:
|
217
|
+
options.dsym_folder = tempDir
|
218
|
+
for f in args[1:]:
|
219
|
+
if (os.path.islink(f)):
|
220
|
+
continue
|
221
|
+
if (os.stat(f).st_size == 0):
|
222
|
+
continue
|
223
|
+
with zipfile.ZipFile(f, 'r') as zipf:
|
224
|
+
zipf.extractall(tempDir)
|
225
|
+
|
226
|
+
os.chdir(options.dsym_folder)
|
227
|
+
for root, dirs, files in os.walk(options.dsym_folder):
|
228
|
+
if not root.endswith('dSYM/Contents/Resources/DWARF'):
|
229
|
+
continue
|
230
|
+
|
231
|
+
print(root)
|
232
|
+
for f in files:
|
233
|
+
if (os.path.islink(os.path.join(root, f))):
|
234
|
+
continue
|
235
|
+
if (os.stat(os.path.join(root, f)).st_size == 0):
|
236
|
+
continue
|
237
|
+
images = parseDSYM(os.path.join(root, f))
|
238
|
+
if (len(images) == 0):
|
239
|
+
continue
|
240
|
+
if isInUploadedList(images, uploadedImages):
|
241
|
+
print("Already uploaded %s, skipping" % f)
|
242
|
+
continue
|
243
|
+
if options.symbol_maps:
|
244
|
+
deobfuscateDSYM(os.path.join(root, f), options.symbol_maps)
|
245
|
+
dwarfs.append(os.path.join(root, f))
|
246
|
+
uploadedImages.extend(images)
|
247
|
+
|
248
|
+
if len(dwarfs) > 0:
|
249
|
+
with zipfile.ZipFile(zipFileLocation, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
250
|
+
for dwarf in dwarfs:
|
251
|
+
zipf.write(dwarf, os.path.relpath(dwarf, options.dsym_folder), zipfile.ZIP_DEFLATED)
|
252
|
+
|
253
|
+
icon = getIcon()
|
254
|
+
if icon:
|
255
|
+
icon = uncrushIcon(icon, tempDir)
|
256
|
+
if icon and os.path.isfile(icon):
|
257
|
+
zipf.write(icon, 'icon.png', zipfile.ZIP_DEFLATED)
|
258
|
+
|
259
|
+
zipf.close()
|
260
|
+
|
261
|
+
result = uploadZipFile(zipFileLocation)
|
262
|
+
if result:
|
263
|
+
saveUploadedList(uploadedImages)
|
264
|
+
|
265
|
+
|
266
|
+
# cleanup
|
267
|
+
shutil.rmtree(tempDir, ignore_errors=True)
|
268
|
+
|
269
|
+
|
270
|
+
if __name__ == "__main__":
|
271
|
+
usage = "usage: %prog [options] token [dsym1 dsym2 dsym3]"
|
272
|
+
parser = OptionParser(usage=usage, description="Uploads symbol files to Bugsee server")
|
273
|
+
parser.add_option("-e", "--endpoint", dest="endpoint",
|
274
|
+
help="Use custom API endpoint for uploading", default="https://api.bugsee.com")
|
275
|
+
parser.add_option("-f", "--folder", dest="dsym_folder",
|
276
|
+
help="Use custom folder to scan for dSYMs", default=os.environ.get('DWARF_DSYM_FOLDER_PATH'))
|
277
|
+
parser.add_option("-m", "--maps", dest="symbol_maps",
|
278
|
+
help="Use folder containing symbol maps to deobfuscate dSYM files")
|
279
|
+
parser.add_option("-l", "--list", dest="dsym_list", action="store_true", default=False,
|
280
|
+
help="Use dsyms from the command line instead of parsing folder")
|
281
|
+
parser.add_option("-x", "--external", dest="from_xcode", action="store_false", default=True,
|
282
|
+
help="The agent is being run not from XCode build phase")
|
283
|
+
parser.add_option("-v", "--version", dest="version",
|
284
|
+
help="Set the version of the application dSYM corresponds to")
|
285
|
+
parser.add_option("-b", "--build", dest="build",
|
286
|
+
help="Set the version of the application dSYM corresponds to")
|
287
|
+
parser.add_option("-d", "--build_dir", dest="build_dir",
|
288
|
+
help="Use for custom TARGET_BUILD_DIR", default=os.environ.get('TARGET_BUILD_DIR'))
|
289
|
+
(options, args) = parser.parse_args()
|
290
|
+
|
291
|
+
if options.from_xcode:
|
292
|
+
# do the UNIX double-fork magic, see Stevens' "Advanced
|
293
|
+
# Programming in the UNIX Environment" for details (ISBN 0201563177)
|
294
|
+
try:
|
295
|
+
pid = os.fork()
|
296
|
+
if pid > 0:
|
297
|
+
# exit first parent
|
298
|
+
sys.exit(0)
|
299
|
+
except OSError as e:
|
300
|
+
print("fork #1 failed: %d (%s)" % (e.errno, e.strerror), file=sys.stderr)
|
301
|
+
sys.exit(1)
|
302
|
+
|
303
|
+
# decouple from parent environment
|
304
|
+
os.chdir("/")
|
305
|
+
os.setsid()
|
306
|
+
os.umask(0)
|
307
|
+
|
308
|
+
# do second fork
|
309
|
+
try:
|
310
|
+
pid = os.fork()
|
311
|
+
if pid > 0:
|
312
|
+
# exit from second parent, print eventual PID before
|
313
|
+
print("Daemon PID %d" % pid)
|
314
|
+
sys.exit(0)
|
315
|
+
except OSError as e:
|
316
|
+
print("fork #2 failed: %d (%s)" % (e.errno, e.strerror), file=sys.stderr)
|
317
|
+
sys.exit(1)
|
318
|
+
|
319
|
+
# redirect standard file descriptors
|
320
|
+
outputFile = os.path.join(os.environ['PROJECT_TEMP_DIR'], "BugseeAgent.log")
|
321
|
+
# this log will not show since xcode 10
|
322
|
+
print("Detaching STDOUT, logs can be found in %s" % (outputFile))
|
323
|
+
sys.stdout.flush()
|
324
|
+
sys.stderr.flush()
|
325
|
+
si = open("/dev/null", 'r')
|
326
|
+
so = open(outputFile, 'w+')
|
327
|
+
se = open("/dev/null", 'w')
|
328
|
+
os.dup2(si.fileno(), sys.stdin.fileno())
|
329
|
+
os.dup2(so.fileno(), sys.stdout.fileno())
|
330
|
+
os.dup2(se.fileno(), sys.stderr.fileno())
|
331
|
+
|
332
|
+
if os.environ.get('DEBUG_INFORMATION_FORMAT') != 'dwarf-with-dsym':
|
333
|
+
print("Bugsee: DEBUG_INFORMATION_FORMAT is not set. Have you enabled debug symbols in your build? See: https://docs.bugsee.com/sdk/ios/symbolication/")
|
334
|
+
exit(0)
|
335
|
+
|
336
|
+
if os.environ.get('EFFECTIVE_PLATFORM_NAME') == '-iphonesimulator':
|
337
|
+
print("Bugsee: Bugsee is not supoorted in iOS simulator. Will not upload debug symbols for i386!")
|
338
|
+
exit(0)
|
339
|
+
|
340
|
+
|
341
|
+
if (len(args) < 1):
|
342
|
+
print("Bugsee: Not initialized with app token. Must be passed as a parameter")
|
343
|
+
exit(1)
|
344
|
+
|
345
|
+
APP_TOKEN = args[0]
|
346
|
+
|
347
|
+
if not options.build_dir:
|
348
|
+
print('Target build directory was not specified. Either provide it with the "-d" option or set "TARGET_BUILD_DIR" environment variable')
|
349
|
+
exit(1)
|
350
|
+
|
351
|
+
if options.dsym_list:
|
352
|
+
dsym_list = args[1:]
|
353
|
+
if len(dsym_list) < 1:
|
354
|
+
print("Bugsee: --list option is provided, but no dSYM files can be found in the command line")
|
355
|
+
exit(1)
|
356
|
+
else:
|
357
|
+
if not options.dsym_folder:
|
358
|
+
print("Bugsee: Can not find dSYM folder (expecting either a -f option or DWARF_DSYM_FOLDER_PATH)")
|
359
|
+
exit(1)
|
360
|
+
|
361
|
+
# start the daemon main loop
|
362
|
+
main()
|
@@ -2,10 +2,14 @@ module Fastlane
|
|
2
2
|
module Actions
|
3
3
|
|
4
4
|
class UploadSymbolsToBugseeAction < Action
|
5
|
+
|
6
|
+
BUGSEE_AGENT_PATH = File.expand_path(
|
7
|
+
File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'BugseeAgent'))
|
8
|
+
|
5
9
|
def self.run(params)
|
6
10
|
app_token = params[:app_token]
|
7
11
|
host = params[:host] || "https://api.bugsee.com"
|
8
|
-
agent_path = params[:agent_path] ||
|
12
|
+
agent_path = params[:agent_path] || BUGSEE_AGENT_PATH
|
9
13
|
build_dir = params[:build_dir] || "./"
|
10
14
|
|
11
15
|
UI.user_error!("Please provide an app token using app_token:") unless app_token
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-bugsee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Fink
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -94,12 +94,13 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 2.2.0
|
97
|
-
description:
|
97
|
+
description:
|
98
98
|
email: finik@bugsee.net
|
99
99
|
executables: []
|
100
100
|
extensions: []
|
101
101
|
extra_rdoc_files: []
|
102
102
|
files:
|
103
|
+
- BugseeAgent
|
103
104
|
- LICENSE
|
104
105
|
- README.md
|
105
106
|
- lib/fastlane/plugin/bugsee.rb
|
@@ -110,7 +111,7 @@ homepage: https://github.com/bugsee/fastlane-plugin-bugsee
|
|
110
111
|
licenses:
|
111
112
|
- MIT
|
112
113
|
metadata: {}
|
113
|
-
post_install_message:
|
114
|
+
post_install_message:
|
114
115
|
rdoc_options: []
|
115
116
|
require_paths:
|
116
117
|
- lib
|
@@ -125,9 +126,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
126
|
- !ruby/object:Gem::Version
|
126
127
|
version: '0'
|
127
128
|
requirements: []
|
128
|
-
|
129
|
-
|
130
|
-
signing_key:
|
129
|
+
rubygems_version: 3.2.22
|
130
|
+
signing_key:
|
131
131
|
specification_version: 4
|
132
132
|
summary: Bugsee symbols uploader
|
133
133
|
test_files: []
|