fastlane-plugin-bugsee 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []
|