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
- 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: []