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
- SHA1:
3
- metadata.gz: fe0b4e32d8872977bb24441b162f2e55b8a34c80
4
- data.tar.gz: e0a79d830b24a3dde35454304a70caa265e5c151
2
+ SHA256:
3
+ metadata.gz: dcd43503f4914135fd4896586c603e23d1019d943290b73577c1522c5394b0e3
4
+ data.tar.gz: 62a93a23e7a84337d5598437fbc73e16970d8dabc076bb1c6aba709b64fcb79e
5
5
  SHA512:
6
- metadata.gz: 61044821a8e2df429da5e6cc49c5799b0d7a441338eb2208dd6eb873286cf237e72a09899ed2657e22df5ead6f3b97baea20dce48b9dc205c1baa3473f111e55
7
- data.tar.gz: 4e157859f2e000a61b3d13d052c4fa19c0ba7a962ae151365749c408cbccc0e377209a21e0f06fcff595058cb737b10161dfc7a0f5add557a69a8c45601f99df
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] || Dir["./**/BugseeAgent"].first
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
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Bugsee
3
- VERSION = "1.0.2"
3
+ VERSION = "1.0.4"
4
4
  end
5
5
  end
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.2
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: 2019-01-31 00:00:00.000000000 Z
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
- rubyforge_project:
129
- rubygems_version: 2.5.2.3
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: []