fastlane-plugin-bugsnag 1.4.2 → 2.2.1
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 +4 -4
- data/bugsnag-dsym-upload +65 -21
- data/lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb +100 -87
- data/lib/fastlane/plugin/bugsnag/actions/upload_symbols_to_bugsnag.rb +85 -37
- data/lib/fastlane/plugin/bugsnag/version.rb +1 -1
- data/spec/bugsnag_upload_dsym_action_spec.rb +57 -40
- data/spec/fixtures/ios_proj/FirstRealFolder/Info.plist +7 -6
- data/spec/fixtures/ios_proj/Project/Info.plist +7 -5
- data/spec/fixtures/ios_proj/aaTests/Info.plist +5 -3
- data/spec/fixtures/ios_proj_legacy/Project/Info.plist +47 -0
- data/spec/send_build_to_bugsnag_spec.rb +54 -38
- metadata +24 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9db2dd9937b3d36dfd9e9ec8d8d49e22e9fc5bb4e9450adcbc0b76dfa301ebee
|
|
4
|
+
data.tar.gz: 87dc97643eaa14270da1eda4fe8d8028f24bccc60afc1180e9490bc7a51a2d1b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cef53d8d700b3ead9b320c57df1616c3bd5d3f8b349e9cec806b7dda5a3449be146b2eebf3983acc2e6e6011c721752d386c830efc8a639f8c112c32b037052
|
|
7
|
+
data.tar.gz: 3c44e6932d491420bcf7a715f3d3d91bfadeae5bd48bea179085f02a4b87539ae1d33f731f28bed061dd8512b1cba82e489b0caf1150acff71ffc699a6f24ba7
|
data/bugsnag-dsym-upload
CHANGED
|
@@ -13,18 +13,22 @@
|
|
|
13
13
|
function print_usage() {
|
|
14
14
|
echo "Usage: $0 [--symbol-maps DIR] dSYMS_PATH"
|
|
15
15
|
echo
|
|
16
|
-
echo "-h, --help
|
|
17
|
-
echo "-v, --verbose
|
|
18
|
-
echo "--api-key API_KEY
|
|
19
|
-
echo "--symbol-maps DIR
|
|
20
|
-
echo "
|
|
21
|
-
echo "
|
|
22
|
-
echo "--upload-server URL
|
|
23
|
-
echo "
|
|
24
|
-
echo "--project-root DIR
|
|
25
|
-
echo "
|
|
26
|
-
echo "
|
|
27
|
-
echo "
|
|
16
|
+
echo "-h, --help Displays this message"
|
|
17
|
+
echo "-v, --verbose Print verbose logging output during execution"
|
|
18
|
+
echo "--api-key API_KEY The API key of the project the dSYM should be applied to"
|
|
19
|
+
echo "--symbol-maps DIR Path to a directory of bitcode symbol maps. The"
|
|
20
|
+
echo " dSYM files will be restored with symbols prior to"
|
|
21
|
+
echo " upload. Requires dsymutil."
|
|
22
|
+
echo "--upload-server URL The server receiving dSYM files. Set this value if"
|
|
23
|
+
echo " using an on-premise Bugsnag installation"
|
|
24
|
+
echo "--project-root DIR The root directory of the project. This will help to"
|
|
25
|
+
echo " group error reports by project"
|
|
26
|
+
echo "--ignore-missing-dwarf Throw warnings instead of errors when a dSYM with missing"
|
|
27
|
+
echo " DWARF data is found"
|
|
28
|
+
echo "--ignore-empty-dsym Throw warnings instead of errors when a *.dSYM file is found"
|
|
29
|
+
echo " rather than the expected *.dSYM directory"
|
|
30
|
+
echo "dSYMS_PATH A directory or .zip file containing *.dSYM bundles to"
|
|
31
|
+
echo " upload"
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
function exit_with_usage() {
|
|
@@ -50,6 +54,8 @@ upload_server=https://upload.bugsnag.com
|
|
|
50
54
|
unset symbol_maps
|
|
51
55
|
unset dsym_dir
|
|
52
56
|
unset verbose
|
|
57
|
+
unset ignore_empty_dsym
|
|
58
|
+
unset ignore_missing_dwarf
|
|
53
59
|
unset silent
|
|
54
60
|
unset project_root
|
|
55
61
|
unset api_key
|
|
@@ -65,6 +71,12 @@ while [[ $# -gt 0 ]]; do
|
|
|
65
71
|
-v|--verbose)
|
|
66
72
|
verbose=1
|
|
67
73
|
shift;;
|
|
74
|
+
--ignore-missing-dwarf)
|
|
75
|
+
ignore_missing_dwarf=1
|
|
76
|
+
shift;;
|
|
77
|
+
--ignore-empty-dsym)
|
|
78
|
+
ignore_empty_dsym=1
|
|
79
|
+
shift;;
|
|
68
80
|
--symbol-maps)
|
|
69
81
|
symbol_maps=$2
|
|
70
82
|
shift
|
|
@@ -117,11 +129,27 @@ fi
|
|
|
117
129
|
|
|
118
130
|
log_verbose "Uploading files to $upload_server"
|
|
119
131
|
success_count=0
|
|
132
|
+
warning_count=0
|
|
120
133
|
fail_count=0
|
|
121
134
|
|
|
122
|
-
|
|
135
|
+
# Find all files and directories matching *.dSYM, except those in dir __MAXCOSX generated when using macOS Archive Utility
|
|
136
|
+
for dsym in $(find $dsym_dir -name "*.dSYM" ! -path "*/__MACOSX/*"); do
|
|
123
137
|
log_verbose "Preparing to upload $dsym"
|
|
124
138
|
|
|
139
|
+
# check if the .dSYM is a file. Throw an error (warning if --ignore-empty-dsym set) if detected as such;
|
|
140
|
+
# it should be a directory. This can happen due to a bug in Xcode: https://developer.apple.com/forums/thread/659187
|
|
141
|
+
if [ -f $dsym ]; then
|
|
142
|
+
dsym_size=$(wc -c < "$dsym" | xargs)
|
|
143
|
+
if [[ $ignore_empty_dsym == 1 ]]; then
|
|
144
|
+
log "[WARNING] Skipping $dsym as it is a file ($dsym_size bytes), not a directory with DWARF data";
|
|
145
|
+
warning_count=$((warning_count+1))
|
|
146
|
+
else
|
|
147
|
+
log "[ERROR] Skipping $dsym as it is a file ($dsym_size bytes), not a directory with DWARF data";
|
|
148
|
+
fail_count=$((fail_count+1))
|
|
149
|
+
fi
|
|
150
|
+
continue
|
|
151
|
+
fi
|
|
152
|
+
|
|
125
153
|
if [[ -d $symbol_maps ]]; then
|
|
126
154
|
log_verbose "Updating file with bitcode symbol maps in $symbol_maps"
|
|
127
155
|
dsymutil "$dsym" --symbol-map "$symbol_maps"
|
|
@@ -129,8 +157,13 @@ for dsym in $dsym_dir/*.dSYM; do
|
|
|
129
157
|
|
|
130
158
|
dwarf_data=$dsym/Contents/Resources/DWARF
|
|
131
159
|
if [[ ! -d $dwarf_data ]]; then
|
|
132
|
-
|
|
133
|
-
|
|
160
|
+
if [[ $ignore_missing_dwarf == 1 ]]; then
|
|
161
|
+
log "[WARNING] Skipping file missing DWARF data: $dsym"
|
|
162
|
+
warning_count=$((warning_count+1))
|
|
163
|
+
else
|
|
164
|
+
log "[ERROR] Skipping file missing DWARF data: $dsym"
|
|
165
|
+
fail_count=$((fail_count+1))
|
|
166
|
+
fi
|
|
134
167
|
continue
|
|
135
168
|
fi
|
|
136
169
|
for file in $dwarf_data/*; do
|
|
@@ -150,29 +183,40 @@ for dsym in $dsym_dir/*.dSYM; do
|
|
|
150
183
|
|
|
151
184
|
# We need to shell out to perform the curl as there seems to be some indirect
|
|
152
185
|
# wrapping of this script which causes the command to fail if called directly.
|
|
153
|
-
curl_cmd="curl --silent --show-error --http1.1 $upload_server -F dsym=@\"$file\" $args"
|
|
186
|
+
curl_cmd="curl --fail --silent --show-error --http1.1 $upload_server -F 'dsym=@\"$file\"' $args"
|
|
154
187
|
output=$(sh -c "$curl_cmd")
|
|
155
188
|
|
|
156
189
|
if [ $? -eq 0 ] && [ "$output" != "invalid apiKey" ]; then
|
|
157
190
|
success_count=$((success_count+1))
|
|
158
191
|
else
|
|
159
192
|
fail_count=$((fail_count+1))
|
|
160
|
-
|
|
193
|
+
log "[ERROR] Failed to upload file: $file"
|
|
161
194
|
fi
|
|
162
195
|
echo $output | grep -v '^OK$'
|
|
163
196
|
log
|
|
164
197
|
else
|
|
165
|
-
|
|
198
|
+
log "[ERROR] Skipping file without UUID: $file"
|
|
166
199
|
fail_count=$((fail_count+1))
|
|
167
200
|
fi
|
|
168
201
|
done
|
|
169
202
|
done
|
|
170
203
|
|
|
204
|
+
exit_code=0
|
|
171
205
|
if [ $success_count -gt 0 ]; then
|
|
172
|
-
log "$success_count
|
|
206
|
+
log "$success_count file(s) uploaded successfully"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
if [ $warning_count -gt 0 ]; then
|
|
210
|
+
log "$warning_count file(s) failed to upload with warnings"
|
|
173
211
|
fi
|
|
174
212
|
|
|
175
213
|
if [ $fail_count -gt 0 ]; then
|
|
176
|
-
|
|
177
|
-
|
|
214
|
+
exit_code=1
|
|
215
|
+
log "$fail_count file(s) failed to upload with errors"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
if [[ $fail_count -gt 0 || $warning_count -gt 0 ]] && [[ $verbose != 1 ]]; then
|
|
219
|
+
log "Re-run the bugsnag-dsym-upload tool with the --verbose option for more information"
|
|
178
220
|
fi
|
|
221
|
+
|
|
222
|
+
exit $exit_code
|
|
@@ -9,32 +9,31 @@ module Fastlane
|
|
|
9
9
|
|
|
10
10
|
def self.run(params)
|
|
11
11
|
payload = {buildTool: BUILD_TOOL, sourceControl: {}}
|
|
12
|
-
if lane_context[:PLATFORM_NAME] == :android
|
|
13
|
-
payload.merge!(options_from_android_manifest(params[:config_file])) if params[:config_file]
|
|
14
|
-
else
|
|
15
|
-
payload.merge!(options_from_info_plist(params[:config_file])) if params[:config_file]
|
|
16
|
-
end
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
payload[:
|
|
26
|
-
payload[:
|
|
13
|
+
# If a configuration file was found or was specified, load in the options:
|
|
14
|
+
if params[:config_file]
|
|
15
|
+
UI.message("Loading build information from #{params[:config_file]}")
|
|
16
|
+
config_options = load_config_file_options(params[:config_file])
|
|
17
|
+
|
|
18
|
+
# for each of the config options, if it's not been overriden by any
|
|
19
|
+
# input to the lane, write it to the payload:
|
|
20
|
+
payload[:apiKey] = params[:api_key] || config_options[:apiKey]
|
|
21
|
+
payload[:appVersion] = params[:app_version] || config_options[:appVersion]
|
|
22
|
+
payload[:appVersionCode] = params[:android_version_code] || config_options[:appVersionCode]
|
|
23
|
+
payload[:appBundleVersion] = params[:ios_bundle_version] || config_options[:appBundleVersion]
|
|
24
|
+
payload[:releaseStage] = params[:release_stage] || config_options[:releaseStage] || "production"
|
|
27
25
|
else
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
# No configuration file was found or specified, use the input parameters:
|
|
27
|
+
payload[:apiKey] = params[:api_key]
|
|
28
|
+
payload[:appVersion] = params[:app_version]
|
|
29
|
+
payload[:appVersionCode] = params[:android_version_code]
|
|
30
|
+
payload[:appBundleVersion] = params[:ios_bundle_version]
|
|
31
|
+
payload[:releaseStage] = params[:release_stage] || "production"
|
|
31
32
|
end
|
|
32
|
-
payload.delete(:config_file)
|
|
33
|
-
|
|
34
|
-
# Overwrite automated options with configured if set
|
|
35
|
-
payload[:releaseStage] = params[:release_stage] unless params[:release_stage].nil?
|
|
36
|
-
payload[:builderName] = params[:builder]
|
|
37
33
|
|
|
34
|
+
# If builder, or source control information has been provided into
|
|
35
|
+
# Fastlane, apply it to the payload here.
|
|
36
|
+
payload[:builderName] = params[:builder] if params[:builder]
|
|
38
37
|
payload[:sourceControl][:revision] = params[:revision] if params[:revision]
|
|
39
38
|
payload[:sourceControl][:repository] = params[:repository] if params[:repository]
|
|
40
39
|
payload[:sourceControl][:provider] = params[:provider] if params[:provider]
|
|
@@ -47,6 +46,13 @@ module Fastlane
|
|
|
47
46
|
if payload[:appVersion].nil?
|
|
48
47
|
UI.user_error! missing_app_version_message(params)
|
|
49
48
|
end
|
|
49
|
+
|
|
50
|
+
# If verbose flag is enabled (`--verbose`), display the payload debug info
|
|
51
|
+
UI.verbose("Sending build to Bugsnag with payload:")
|
|
52
|
+
payload.each do |param|
|
|
53
|
+
UI.verbose(" #{param[0].to_s.rjust(18)}: #{param[1]}")
|
|
54
|
+
end
|
|
55
|
+
|
|
50
56
|
send_notification(params[:endpoint], ::JSON.dump(payload))
|
|
51
57
|
end
|
|
52
58
|
|
|
@@ -60,16 +66,16 @@ module Fastlane
|
|
|
60
66
|
end
|
|
61
67
|
else
|
|
62
68
|
if params[:config_file]
|
|
63
|
-
message << "Set
|
|
69
|
+
message << "Set bugsnag.apiKey in your Info.plist file to detect API key automatically."
|
|
64
70
|
else
|
|
65
|
-
message << "Set the config_file option with the path to your Info.plist and set
|
|
71
|
+
message << "Set the config_file option with the path to your Info.plist and set bugsnag.apiKey in it to detect API key automatically."
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
message
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
def self.missing_app_version_message(params)
|
|
72
|
-
message = "An app version must be specified release a build."
|
|
78
|
+
message = "An app version must be specified release a build. "
|
|
73
79
|
if lane_context[:PLATFORM_NAME] == :android
|
|
74
80
|
if params[:config_file]
|
|
75
81
|
message << "Set com.bugsnag.android.APP_VERSION in your AndroidManifest.xml to detect this value automatically."
|
|
@@ -115,32 +121,27 @@ module Fastlane
|
|
|
115
121
|
end
|
|
116
122
|
|
|
117
123
|
def self.available_options
|
|
118
|
-
|
|
124
|
+
git_options = load_git_remote_options
|
|
119
125
|
[
|
|
120
126
|
FastlaneCore::ConfigItem.new(key: :config_file,
|
|
121
127
|
description: "AndroidManifest.xml/Info.plist location",
|
|
122
128
|
optional: true,
|
|
123
|
-
default_value:
|
|
129
|
+
default_value: default_config_file_path),
|
|
124
130
|
FastlaneCore::ConfigItem.new(key: :api_key,
|
|
125
131
|
description: "Bugsnag API Key",
|
|
126
|
-
optional: true,
|
|
127
|
-
default_value: options[:apiKey]),
|
|
132
|
+
optional: true),
|
|
128
133
|
FastlaneCore::ConfigItem.new(key: :app_version,
|
|
129
134
|
description: "App version being built",
|
|
130
|
-
optional: true,
|
|
131
|
-
default_value: options[:appVersion]),
|
|
135
|
+
optional: true),
|
|
132
136
|
FastlaneCore::ConfigItem.new(key: :android_version_code,
|
|
133
137
|
description: "Android app version code",
|
|
134
|
-
optional: true,
|
|
135
|
-
default_value: options[:appVersionCode]),
|
|
138
|
+
optional: true),
|
|
136
139
|
FastlaneCore::ConfigItem.new(key: :ios_bundle_version,
|
|
137
140
|
description: "iOS/macOS/tvOS bundle version",
|
|
138
|
-
optional: true,
|
|
139
|
-
default_value: options[:appBundleVersion]),
|
|
141
|
+
optional: true),
|
|
140
142
|
FastlaneCore::ConfigItem.new(key: :release_stage,
|
|
141
143
|
description: "Release stage being built, i.e. staging, production",
|
|
142
|
-
optional: true,
|
|
143
|
-
default_value: options[:releaseStage] || "production"),
|
|
144
|
+
optional: true),
|
|
144
145
|
FastlaneCore::ConfigItem.new(key: :builder,
|
|
145
146
|
description: "The name of the entity triggering the build",
|
|
146
147
|
optional: true,
|
|
@@ -148,19 +149,19 @@ module Fastlane
|
|
|
148
149
|
FastlaneCore::ConfigItem.new(key: :repository,
|
|
149
150
|
description: "The source control repository URL for this application",
|
|
150
151
|
optional: true,
|
|
151
|
-
default_value:
|
|
152
|
+
default_value: git_options[:repository]),
|
|
152
153
|
FastlaneCore::ConfigItem.new(key: :revision,
|
|
153
154
|
description: "The source control revision id",
|
|
154
155
|
optional: true,
|
|
155
|
-
default_value:
|
|
156
|
+
default_value: git_options[:revision]),
|
|
156
157
|
FastlaneCore::ConfigItem.new(key: :provider,
|
|
157
158
|
description: "The source control provider, one of 'github-enterprise', 'gitlab-onpremise', or 'bitbucket-server', if any",
|
|
158
159
|
optional: true,
|
|
159
160
|
default_value: nil,
|
|
160
161
|
verify_block: proc do |value|
|
|
161
|
-
valid = ['github-enterprise', 'gitlab-onpremise', 'bitbucket-server'].include? value
|
|
162
|
+
valid = ['github', 'github-enterprise', 'gitlab', 'gitlab-onpremise', 'bitbucket', 'bitbucket-server'].include? value
|
|
162
163
|
unless valid
|
|
163
|
-
UI.user_error!("Provider must be one of 'github-enterprise', 'gitlab-onpremise', 'bitbucket-server', or unspecified")
|
|
164
|
+
UI.user_error!("Provider must be one of 'github', 'github-enterprise', 'gitlab', 'gitlab-onpremise', 'bitbucket', 'bitbucket-server', or unspecified")
|
|
164
165
|
end
|
|
165
166
|
end),
|
|
166
167
|
FastlaneCore::ConfigItem.new(key: :endpoint,
|
|
@@ -172,69 +173,98 @@ module Fastlane
|
|
|
172
173
|
|
|
173
174
|
private
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
# Get any Git options (remote repo, and revision) from the directory
|
|
177
|
+
def self.load_git_remote_options
|
|
178
|
+
git_options = {repository:nil, revision:nil}
|
|
179
|
+
require "git"
|
|
180
|
+
begin
|
|
181
|
+
repo = Git.open(Dir.pwd)
|
|
182
|
+
origin = repo.remotes.detect {|r| r.name == "origin"}
|
|
183
|
+
origin = repo.remotes.first unless origin
|
|
184
|
+
if origin
|
|
185
|
+
git_options[:repository] = origin.url
|
|
186
|
+
git_options[:revision] = repo.revparse("HEAD")
|
|
187
|
+
end
|
|
188
|
+
rescue
|
|
189
|
+
end
|
|
190
|
+
return git_options
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Used to get a default configuration file (AndroidManifest.xml or Info.plist)
|
|
194
|
+
def self.default_config_file_path
|
|
177
195
|
case lane_context[:PLATFORM_NAME]
|
|
178
196
|
when nil
|
|
179
197
|
if file_path = default_android_manifest_path
|
|
180
|
-
|
|
198
|
+
return file_path
|
|
181
199
|
elsif file_path = default_info_plist_path
|
|
182
|
-
|
|
200
|
+
return file_path
|
|
183
201
|
end
|
|
184
202
|
when :android
|
|
185
203
|
if file_path = default_android_manifest_path
|
|
186
|
-
|
|
204
|
+
return file_path
|
|
187
205
|
end
|
|
188
206
|
else
|
|
189
207
|
if file_path = default_info_plist_path
|
|
190
|
-
|
|
208
|
+
return file_path
|
|
191
209
|
end
|
|
192
210
|
end
|
|
211
|
+
end
|
|
193
212
|
|
|
194
|
-
|
|
195
|
-
|
|
213
|
+
def self.load_config_file_options config_file
|
|
214
|
+
options = {}
|
|
215
|
+
case File.extname(config_file)
|
|
216
|
+
when ".xml"
|
|
217
|
+
options = load_options_from_xml(config_file)
|
|
218
|
+
return options
|
|
219
|
+
when ".plist"
|
|
220
|
+
options = load_options_from_plist(config_file)
|
|
221
|
+
return options
|
|
222
|
+
else
|
|
223
|
+
UI.user_error("File type of '#{config_file}' was not recognised. This should be .xml for Android and .plist for Cococa")
|
|
196
224
|
end
|
|
197
|
-
options
|
|
198
225
|
end
|
|
199
226
|
|
|
200
|
-
def self.
|
|
201
|
-
|
|
202
|
-
build_gradle_path = Dir.glob("{android/,}app/build.gradle").first
|
|
203
|
-
build_gradle_path ||= Dir.glob("build.gradle").first
|
|
204
|
-
options.merge!(options_from_build_gradle(build_gradle_path)) if build_gradle_path
|
|
205
|
-
options
|
|
227
|
+
def self.default_info_plist_path
|
|
228
|
+
Dir.glob("./{ios/,}*/Info.plist").reject {|path| path =~ /build|test/i }.sort.first
|
|
206
229
|
end
|
|
207
230
|
|
|
208
|
-
def self.
|
|
209
|
-
|
|
231
|
+
def self.load_options_from_plist file_path
|
|
232
|
+
options = {}
|
|
233
|
+
plist_getter = Fastlane::Actions::GetInfoPlistValueAction
|
|
234
|
+
bugsnag_dict = plist_getter.run(path: file_path, key: "bugsnag")
|
|
235
|
+
api_key = bugsnag_dict["apiKey"] unless bugsnag_dict.nil?
|
|
236
|
+
release_stage = bugsnag_dict["releaseStage"] unless bugsnag_dict.nil?
|
|
237
|
+
if api_key.nil?
|
|
238
|
+
api_key = plist_getter.run(path: file_path, key: "BugsnagAPIKey")
|
|
239
|
+
end
|
|
240
|
+
options[:apiKey] = api_key
|
|
241
|
+
options[:releaseStage] = release_stage
|
|
242
|
+
options[:appVersion] = plist_getter.run(path: file_path, key: "CFBundleShortVersionString")
|
|
243
|
+
options[:appBundleVersion] = plist_getter.run(path: file_path, key: "CFBundleVersion")
|
|
244
|
+
return options
|
|
210
245
|
end
|
|
211
246
|
|
|
212
|
-
def self.
|
|
213
|
-
Dir.glob("./{
|
|
247
|
+
def self.default_android_manifest_path
|
|
248
|
+
Dir.glob("./{android/,}{app,}/src/main/AndroidManifest.xml").sort.first
|
|
214
249
|
end
|
|
215
250
|
|
|
216
|
-
def self.
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
appBundleVersion: plist_getter.run(path: file_path, key: "CFBundleVersion"),
|
|
222
|
-
config_file: file_path,
|
|
223
|
-
}
|
|
251
|
+
def self.load_options_from_xml file_path
|
|
252
|
+
options = options_from_android_manifest(file_path)
|
|
253
|
+
build_gradle_path = Dir.glob("{android/,}app/build.gradle").sort.first || Dir.glob("build.gradle").sort.first
|
|
254
|
+
options.merge!(options_from_build_gradle(build_gradle_path)) if build_gradle_path
|
|
255
|
+
return options
|
|
224
256
|
end
|
|
225
257
|
|
|
226
258
|
def self.options_from_android_manifest file_path
|
|
227
259
|
options = {}
|
|
228
260
|
begin
|
|
229
261
|
meta_data = parse_android_manifest_options(XmlSimple.xml_in(file_path))
|
|
230
|
-
|
|
231
262
|
options[:apiKey] = meta_data["com.bugsnag.android.API_KEY"]
|
|
232
263
|
options[:appVersion] = meta_data["com.bugsnag.android.APP_VERSION"]
|
|
233
264
|
options[:releaseStage] = meta_data["com.bugsnag.android.RELEASE_STAGE"]
|
|
234
265
|
rescue ArgumentError
|
|
235
266
|
nil
|
|
236
267
|
end
|
|
237
|
-
options[:config_file] = file_path
|
|
238
268
|
options
|
|
239
269
|
end
|
|
240
270
|
|
|
@@ -253,23 +283,6 @@ module Fastlane
|
|
|
253
283
|
options
|
|
254
284
|
end
|
|
255
285
|
|
|
256
|
-
def self.git_remote_options
|
|
257
|
-
require "git"
|
|
258
|
-
begin
|
|
259
|
-
repo = Git.open(Dir.pwd)
|
|
260
|
-
origin = repo.remotes.detect {|r| r.name == "origin"}
|
|
261
|
-
origin = repo.remotes.first unless origin
|
|
262
|
-
if origin
|
|
263
|
-
return {
|
|
264
|
-
repository: origin.url,
|
|
265
|
-
revision: repo.revparse("HEAD"),
|
|
266
|
-
}
|
|
267
|
-
end
|
|
268
|
-
rescue
|
|
269
|
-
end
|
|
270
|
-
nil
|
|
271
|
-
end
|
|
272
|
-
|
|
273
286
|
def self.parse_android_manifest_options config_hash
|
|
274
287
|
map_meta_data(get_meta_data(config_hash))
|
|
275
288
|
end
|
|
@@ -6,21 +6,27 @@ module Fastlane
|
|
|
6
6
|
File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'bugsnag-dsym-upload'))
|
|
7
7
|
|
|
8
8
|
def self.run(params)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
else
|
|
16
|
-
# Print which file is populating version and API key information since the value has been
|
|
17
|
-
# overridden
|
|
18
|
-
UI.message("Loading API key from #{params[:config_file]}")
|
|
9
|
+
# If we have not explicitly set an API key through env, or parameter
|
|
10
|
+
# input in Fastfile, find an API key in the Info.plist in config_file param
|
|
11
|
+
api_key = params[:api_key]
|
|
12
|
+
if params[:config_file] && params[:api_key] == nil
|
|
13
|
+
UI.message("Using the API Key from #{params[:config_file]}")
|
|
14
|
+
api_key = options_from_info_plist(params[:config_file])[:apiKey]
|
|
19
15
|
end
|
|
20
16
|
|
|
17
|
+
# If verbose flag is enabled (`--verbose`), display the plugin action debug info
|
|
18
|
+
# Store the verbose flag for use in the upload arguments.
|
|
19
|
+
verbose = UI.verbose("Uploading dSYMs to Bugsnag with the following parameters:")
|
|
20
|
+
rjust = 30 # set justification width for keys for the list of parameters to output
|
|
21
|
+
params.values.each do |param|
|
|
22
|
+
UI.verbose(" #{param[0].to_s.rjust(rjust)}: #{param[1]}")
|
|
23
|
+
end
|
|
24
|
+
UI.verbose(" #{"SharedValues::DSYM_PATHS".to_s.rjust(rjust)}: #{gym_dsyms? ? Actions.lane_context[SharedValues::DSYM_PATHS] : "not set"}")
|
|
25
|
+
UI.verbose(" #{"SharedValues::DSYM_OUTPUT_PATH".to_s.rjust(rjust)}: #{download_dsym_dsyms? ? Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH] : "not set"}")
|
|
26
|
+
|
|
21
27
|
parse_dsym_paths(params[:dsym_path]).each do |dsym_path|
|
|
22
28
|
if dsym_path.end_with?(".zip") or File.directory?(dsym_path)
|
|
23
|
-
args = upload_args(dsym_path, params[:symbol_maps_path], params[:upload_url], params[:project_root],
|
|
29
|
+
args = upload_args(dsym_path, params[:symbol_maps_path], params[:upload_url], params[:project_root], api_key, verbose, params[:ignore_missing_dwarf], params[:ignore_empty_dsym])
|
|
24
30
|
success = Kernel.system(UPLOAD_SCRIPT_PATH, *args)
|
|
25
31
|
if success
|
|
26
32
|
UI.success("Uploaded dSYMs in #{dsym_path}")
|
|
@@ -34,11 +40,11 @@ module Fastlane
|
|
|
34
40
|
end
|
|
35
41
|
|
|
36
42
|
def self.description
|
|
37
|
-
"Uploads symbol files to Bugsnag"
|
|
43
|
+
"Uploads dSYM debug symbol files to Bugsnag"
|
|
38
44
|
end
|
|
39
45
|
|
|
40
46
|
def self.authors
|
|
41
|
-
["kattrali"]
|
|
47
|
+
["kattrali", "xander-jones"]
|
|
42
48
|
end
|
|
43
49
|
|
|
44
50
|
def self.return_value
|
|
@@ -66,33 +72,31 @@ module Fastlane
|
|
|
66
72
|
end
|
|
67
73
|
validate_symbol_maps = proc do |path|
|
|
68
74
|
return if path.nil?
|
|
69
|
-
|
|
70
75
|
unless File.exist?(path) and File.directory?(path)
|
|
71
76
|
UI.user_error!("Symbol maps file needs to be a directory containing symbol map files")
|
|
72
77
|
end
|
|
73
78
|
end
|
|
74
79
|
validate_api_key = proc do |key|
|
|
75
80
|
return if key.nil?
|
|
76
|
-
|
|
77
81
|
unless !key[/\H/] and key.length == 32
|
|
78
82
|
UI.user_error!("API key should be a 32 character hexadecimal string")
|
|
79
83
|
end
|
|
80
84
|
end
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
# If the Info.plist is in a default location, we'll get API key here
|
|
87
|
+
# This will be overwritten if you pass in an API key parameter in your
|
|
88
|
+
# Fastfile, or have an API key environment variable set.
|
|
84
89
|
[
|
|
85
90
|
FastlaneCore::ConfigItem.new(key: :api_key,
|
|
86
91
|
env_name: "BUGSNAG_API_KEY",
|
|
87
92
|
description: "Bugsnag API Key",
|
|
88
93
|
optional: true,
|
|
89
|
-
default_value: options[:apiKey],
|
|
90
94
|
verify_block: validate_api_key),
|
|
91
95
|
FastlaneCore::ConfigItem.new(key: :dsym_path,
|
|
92
96
|
type: Array,
|
|
93
97
|
env_name: "BUGSNAG_DSYM_PATH",
|
|
94
|
-
description: "Path to the
|
|
95
|
-
default_value:
|
|
98
|
+
description: "Path to the dSYM directory, file, or zip to upload",
|
|
99
|
+
default_value: default_dsym_paths,
|
|
96
100
|
optional: true,
|
|
97
101
|
verify_block: validate_dsym_path),
|
|
98
102
|
FastlaneCore::ConfigItem.new(key: :upload_url,
|
|
@@ -111,34 +115,53 @@ module Fastlane
|
|
|
111
115
|
description: "Root path of the project",
|
|
112
116
|
default_value: Dir::pwd,
|
|
113
117
|
optional: true),
|
|
118
|
+
FastlaneCore::ConfigItem.new(key: :ignore_missing_dwarf,
|
|
119
|
+
env_name: "BUGSNAG_IGNORE_MISSING_DWARF",
|
|
120
|
+
description: "Throw warnings instead of errors when a dSYM with missing DWARF data is found",
|
|
121
|
+
optional: true,
|
|
122
|
+
default_value: false,
|
|
123
|
+
is_string: false),
|
|
124
|
+
FastlaneCore::ConfigItem.new(key: :ignore_empty_dsym,
|
|
125
|
+
env_name: "BUGSNAG_IGNORE_EMPTY_DSYM",
|
|
126
|
+
description: "Throw warnings instead of errors when a *.dSYM file is found rather than the expected *.dSYM directory",
|
|
127
|
+
optional: true,
|
|
128
|
+
default_value: false,
|
|
129
|
+
is_string: false),
|
|
114
130
|
FastlaneCore::ConfigItem.new(key: :config_file,
|
|
131
|
+
env_name: "BUGSNAG_CONFIG_FILE",
|
|
115
132
|
description: "Info.plist location",
|
|
116
133
|
optional: true,
|
|
117
|
-
default_value:
|
|
118
|
-
FastlaneCore::ConfigItem.new(key: :verbose,
|
|
119
|
-
env_name: "BUGSNAG_VERBOSE",
|
|
120
|
-
description: "Print helpful debug info",
|
|
121
|
-
skip_type_validation: true,
|
|
122
|
-
optional: true),
|
|
134
|
+
default_value: default_info_plist_path)
|
|
123
135
|
]
|
|
124
136
|
end
|
|
125
137
|
|
|
126
138
|
private
|
|
127
139
|
|
|
128
140
|
def self.default_info_plist_path
|
|
129
|
-
|
|
141
|
+
# Find first 'Info.plist' in the current working directory
|
|
142
|
+
# ignoring any in 'build', or 'test' folders
|
|
143
|
+
return Dir.glob("./{ios/,}*/Info.plist").reject{|path| path =~ /build|test/i }.sort.first
|
|
130
144
|
end
|
|
131
145
|
|
|
132
146
|
def self.options_from_info_plist file_path
|
|
133
147
|
plist_getter = Fastlane::Actions::GetInfoPlistValueAction
|
|
148
|
+
bugsnag_dict = plist_getter.run(path: file_path, key: "bugsnag")
|
|
149
|
+
api_key = bugsnag_dict["apiKey"] unless bugsnag_dict.nil?
|
|
150
|
+
# From v6.0.0 of bugsnag-cocoa, the API key is in 'bugsnag.apiKey',
|
|
151
|
+
# use 'BugsnagAPIKey' as a fallback if it exists (<v6.x.x)
|
|
152
|
+
if api_key.nil?
|
|
153
|
+
api_key = plist_getter.run(path: file_path, key: "BugsnagAPIKey")
|
|
154
|
+
end
|
|
134
155
|
{
|
|
135
|
-
apiKey:
|
|
156
|
+
apiKey: api_key,
|
|
136
157
|
config_file: file_path,
|
|
137
158
|
}
|
|
138
159
|
end
|
|
139
160
|
|
|
140
|
-
def self.upload_args dir, symbol_maps_dir, upload_url, project_root, api_key, verbose
|
|
161
|
+
def self.upload_args dir, symbol_maps_dir, upload_url, project_root, api_key, verbose, ignore_missing_dwarf, ignore_empty_dsym
|
|
141
162
|
args = [verbose ? "--verbose" : "--silent"]
|
|
163
|
+
args += ["--ignore-missing-dwarf"] if ignore_missing_dwarf
|
|
164
|
+
args += ["--ignore-empty-dsym"] if ignore_empty_dsym
|
|
142
165
|
args += ["--api-key", api_key] unless api_key.nil?
|
|
143
166
|
args += ["--upload-server", upload_url] unless upload_url.nil?
|
|
144
167
|
args += ["--symbol-maps", symbol_maps_dir] unless symbol_maps_dir.nil?
|
|
@@ -147,19 +170,44 @@ module Fastlane
|
|
|
147
170
|
args
|
|
148
171
|
end
|
|
149
172
|
|
|
173
|
+
# returns an array of unique dSYM-containing directory paths to upload
|
|
150
174
|
def self.parse_dsym_paths dsym_path
|
|
151
|
-
|
|
152
|
-
dsym_paths.compact.map do |path|
|
|
175
|
+
dsym_path.compact.map do |path|
|
|
153
176
|
path.end_with?(".dSYM") ? File.dirname(path) : path
|
|
154
177
|
end.uniq
|
|
155
178
|
end
|
|
156
179
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
# if input is an Array, return that array, else coerce the input into an array
|
|
181
|
+
def self.coerce_array dsym_path
|
|
182
|
+
dsym_path.is_a?(Array) ? dsym_path : [dsym_path]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# returns true if `gym` created some dSYMs for us to upload
|
|
186
|
+
# https://docs.fastlane.tools/actions/gym/#lane-variables
|
|
187
|
+
def self.gym_dsyms?
|
|
188
|
+
if defined?(SharedValues::DSYM_OUTPUT_PATH)
|
|
189
|
+
if Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH]
|
|
190
|
+
true
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# returns true if `download_dsyms` created some dSYMs for us to upload
|
|
196
|
+
# https://docs.fastlane.tools/actions/download_dsyms/#lane-variables
|
|
197
|
+
def self.download_dsym_dsyms?
|
|
198
|
+
if defined?(SharedValues::DSYM_PATHS)
|
|
199
|
+
if Actions.lane_context[SharedValues::DSYM_PATHS]
|
|
200
|
+
true
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def self.default_dsym_paths
|
|
206
|
+
vendor_regex = %r{\.\/vendor.*}
|
|
207
|
+
paths = Dir["./**/*.dSYM.zip"].reject{|f| f[vendor_regex] } + Dir["./**/*.dSYM"].reject{|f| f[vendor_regex] } # scrape the sub directories for zips and dSYMs
|
|
208
|
+
paths += coerce_array(Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH]) if gym_dsyms? # set by `gym` Fastlane action
|
|
209
|
+
paths += coerce_array(Actions.lane_context[SharedValues::DSYM_PATHS]) if download_dsym_dsyms? # set by `download_dsyms` Fastlane action
|
|
210
|
+
parse_dsym_paths(paths.uniq)
|
|
163
211
|
end
|
|
164
212
|
end
|
|
165
213
|
end
|
|
@@ -3,10 +3,8 @@ require 'spec_helper'
|
|
|
3
3
|
Action = Fastlane::Actions::UploadSymbolsToBugsnagAction
|
|
4
4
|
|
|
5
5
|
describe Action do
|
|
6
|
-
def
|
|
7
|
-
Action.
|
|
8
|
-
[x.key, x.default_value]
|
|
9
|
-
end.to_h
|
|
6
|
+
def run_with args
|
|
7
|
+
Action.run(Fastlane::ConfigurationHelper.parse(Action, args))
|
|
10
8
|
end
|
|
11
9
|
|
|
12
10
|
context "the packaged gem" do
|
|
@@ -27,51 +25,43 @@ describe Action do
|
|
|
27
25
|
describe '#run' do
|
|
28
26
|
it 'silences script output by default' do
|
|
29
27
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
30
|
-
"--silent",
|
|
31
|
-
"--project-root", Dir::pwd,
|
|
32
|
-
FIXTURE_PATH).and_return(true)
|
|
33
|
-
Action.run(load_default_opts.merge({dsym_path: FIXTURE_PATH}))
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it 'prints verbose if verbose flag set' do
|
|
37
|
-
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
38
|
-
"--verbose",
|
|
28
|
+
"--silent",
|
|
39
29
|
"--project-root", Dir::pwd,
|
|
40
30
|
FIXTURE_PATH).and_return(true)
|
|
41
|
-
|
|
31
|
+
run_with({dsym_path: FIXTURE_PATH})
|
|
42
32
|
end
|
|
43
33
|
|
|
44
34
|
it 'UI.user_error when script fails' do
|
|
45
35
|
expect(Fastlane::UI).to receive(:user_error!).with("Failed uploading #{FIXTURE_PATH}")
|
|
46
36
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
"--silent",
|
|
38
|
+
"--project-root", Dir::pwd,
|
|
39
|
+
FIXTURE_PATH).and_return(false)
|
|
40
|
+
run_with({dsym_path: FIXTURE_PATH})
|
|
49
41
|
end
|
|
50
42
|
|
|
51
43
|
it 'requires the dSYM file path to exist' do
|
|
52
|
-
expect(Fastlane::UI).to receive(:user_error!)
|
|
53
|
-
|
|
54
|
-
Action.run(load_default_opts.merge({dsym_path: 'fake/file/path'}))
|
|
44
|
+
expect(Fastlane::UI).to receive(:user_error!).at_least(:once)
|
|
45
|
+
run_with({dsym_path: 'fake/file/path'})
|
|
55
46
|
end
|
|
56
47
|
|
|
57
48
|
it 'rejects dSYM files which are not a .zip or a directory' do
|
|
58
|
-
expect(Fastlane::UI).to receive(:user_error!)
|
|
59
|
-
|
|
60
|
-
Action.run(load_default_opts.merge({dsym_path: File.join(FIXTURE_PATH, 'invalid_file')}))
|
|
49
|
+
expect(Fastlane::UI).to receive(:user_error!).at_least(:once)
|
|
50
|
+
run_with({dsym_path: File.join(FIXTURE_PATH, 'invalid_file')})
|
|
61
51
|
end
|
|
62
52
|
|
|
63
53
|
it 'uploads a single .dSYM file' do
|
|
64
54
|
directory = File.join(FIXTURE_PATH, 'dSYMs')
|
|
65
55
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
66
56
|
"--silent", "--project-root", Dir::pwd, directory).and_return(true)
|
|
67
|
-
|
|
57
|
+
run_with({dsym_path: File.join(FIXTURE_PATH, 'dSYMs/app.dSYM')})
|
|
68
58
|
end
|
|
69
59
|
|
|
70
60
|
it 'uploads a .zip of .dSYM files' do
|
|
71
61
|
path = File.join(FIXTURE_PATH, 'files.zip')
|
|
72
62
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
73
63
|
"--silent", "--project-root", Dir::pwd, path).and_return(true)
|
|
74
|
-
|
|
64
|
+
run_with({dsym_path: path})
|
|
75
65
|
end
|
|
76
66
|
|
|
77
67
|
it 'uploads multiple .zip files' do
|
|
@@ -81,7 +71,7 @@ describe Action do
|
|
|
81
71
|
"--silent", "--project-root", Dir::pwd, zip1).and_return(true)
|
|
82
72
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
83
73
|
"--silent", "--project-root", Dir::pwd, zip2).and_return(true)
|
|
84
|
-
|
|
74
|
+
run_with({dsym_path: [zip1, zip2]})
|
|
85
75
|
end
|
|
86
76
|
|
|
87
77
|
it 'uploads multiple .dSYM files multiple directories' do
|
|
@@ -94,7 +84,7 @@ describe Action do
|
|
|
94
84
|
"--silent", "--project-root", Dir::pwd, directories[0]).and_return(true)
|
|
95
85
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
96
86
|
"--silent", "--project-root", Dir::pwd, directories[1]).and_return(true)
|
|
97
|
-
|
|
87
|
+
run_with({dsym_path: [dsym1, dsym2, dsym3, dsym4]})
|
|
98
88
|
end
|
|
99
89
|
|
|
100
90
|
it 'uploads multiple .dSYM files in a single directory' do
|
|
@@ -103,7 +93,7 @@ describe Action do
|
|
|
103
93
|
directory = File.join(FIXTURE_PATH, 'dSYMs')
|
|
104
94
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
105
95
|
"--silent", "--project-root", Dir::pwd, directory).and_return(true)
|
|
106
|
-
|
|
96
|
+
run_with({dsym_path: [dsym1, dsym2]})
|
|
107
97
|
end
|
|
108
98
|
|
|
109
99
|
it 'accepts a project root argument' do
|
|
@@ -112,48 +102,75 @@ describe Action do
|
|
|
112
102
|
"--silent",
|
|
113
103
|
"--project-root", root_path,
|
|
114
104
|
FIXTURE_PATH).and_return(true)
|
|
115
|
-
|
|
105
|
+
run_with({dsym_path: FIXTURE_PATH, project_root: root_path})
|
|
116
106
|
end
|
|
117
107
|
|
|
118
108
|
it 'accepts an API key argument' do
|
|
119
|
-
api_key = "
|
|
109
|
+
api_key = "123456789123456789001234567890FF"
|
|
120
110
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
121
111
|
"--silent",
|
|
122
112
|
"--api-key", api_key,
|
|
123
113
|
"--project-root", Dir::pwd,
|
|
124
114
|
FIXTURE_PATH).and_return(true)
|
|
125
|
-
|
|
115
|
+
run_with({dsym_path: FIXTURE_PATH, api_key: api_key})
|
|
126
116
|
end
|
|
127
117
|
|
|
128
118
|
it 'accepts an API key argument with no project root' do
|
|
129
|
-
api_key = "
|
|
119
|
+
api_key = "123456789012345678901234567890FF"
|
|
130
120
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
131
121
|
"--silent",
|
|
132
122
|
"--api-key", api_key,
|
|
123
|
+
"--project-root", `pwd`.chomp,
|
|
133
124
|
FIXTURE_PATH).and_return(true)
|
|
134
|
-
|
|
125
|
+
run_with({dsym_path: FIXTURE_PATH, api_key: api_key, project_root: nil})
|
|
135
126
|
end
|
|
136
127
|
|
|
137
128
|
it 'uses default API key argument from plist' do
|
|
138
129
|
root_path = "/test/test/test"
|
|
130
|
+
api_key = "12345678901234567890123456789AAA" # Uses the API Key from ./spec/fixtures/ios_proj/FirstRealFolder/Info.plist
|
|
139
131
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
140
132
|
"--silent",
|
|
141
|
-
"--api-key",
|
|
133
|
+
"--api-key", api_key,
|
|
142
134
|
"--project-root", root_path,
|
|
143
135
|
FIXTURE_PATH).and_return(true)
|
|
144
136
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
145
|
-
|
|
137
|
+
run_with({dsym_path: FIXTURE_PATH, project_root: root_path})
|
|
146
138
|
end
|
|
147
139
|
end
|
|
148
|
-
|
|
149
|
-
it '
|
|
140
|
+
|
|
141
|
+
it 'uses legacy API key argument from plist' do
|
|
142
|
+
root_path = "/test/test/test"
|
|
143
|
+
api_key = "12345678901234567890123456789BBB" # Uses the API Key from ./spec/fixtures/ios_proj_legacy/Project/Info.plist
|
|
150
144
|
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
151
145
|
"--silent",
|
|
152
|
-
"--api-key",
|
|
146
|
+
"--api-key", api_key,
|
|
147
|
+
"--project-root", root_path,
|
|
148
|
+
FIXTURE_PATH).and_return(true)
|
|
149
|
+
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj_legacy')) do
|
|
150
|
+
run_with({dsym_path: FIXTURE_PATH, project_root: root_path})
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'allows option input to overwrite the config file api key' do
|
|
155
|
+
# The order of precedence is 1. option input, 2. env variable, 3. default or config file input (for api key only)
|
|
156
|
+
# The API key in ./spec/fixtures/ios_proj_legacy/Project/Info.plist is 12345678912345678900123456789AAA.
|
|
157
|
+
api_key = "12345678912345678900123456789CCC"
|
|
158
|
+
expect(Kernel).to receive(:system).with(Action::UPLOAD_SCRIPT_PATH,
|
|
159
|
+
"--silent",
|
|
160
|
+
"--api-key", api_key,
|
|
153
161
|
"--project-root", File.join(FIXTURE_PATH, 'ios_proj'),
|
|
154
162
|
FIXTURE_PATH).and_return(true)
|
|
155
163
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
156
|
-
|
|
164
|
+
run_with({dsym_path: FIXTURE_PATH, api_key: api_key, config_file: File.join('Project', 'Info.plist')})
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'reject an invalid API key' do
|
|
169
|
+
api_key = "this-is-not-a-hex-key"
|
|
170
|
+
expect(Fastlane::UI).to receive(:user_error!).with("API key should be a 32 character hexadecimal string")
|
|
171
|
+
expect(Fastlane::UI).to receive(:user_error!).with("Failed uploading #{FIXTURE_PATH}")
|
|
172
|
+
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
173
|
+
run_with({dsym_path: FIXTURE_PATH, api_key: api_key, config_file: File.join('Project', 'Info.plist')})
|
|
157
174
|
end
|
|
158
175
|
end
|
|
159
176
|
|
|
@@ -164,7 +181,7 @@ describe Action do
|
|
|
164
181
|
"--upload-server", "http://myserver.example.com",
|
|
165
182
|
"--project-root", Dir::pwd,
|
|
166
183
|
FIXTURE_PATH).and_return(true)
|
|
167
|
-
|
|
184
|
+
run_with({dsym_path: FIXTURE_PATH, upload_url: "http://myserver.example.com"})
|
|
168
185
|
end
|
|
169
186
|
end
|
|
170
187
|
|
|
@@ -176,7 +193,7 @@ describe Action do
|
|
|
176
193
|
"--symbol-maps", path,
|
|
177
194
|
"--project-root", Dir::pwd,
|
|
178
195
|
FIXTURE_PATH).and_return(true)
|
|
179
|
-
|
|
196
|
+
run_with({dsym_path: FIXTURE_PATH, symbol_maps_path: path})
|
|
180
197
|
end
|
|
181
198
|
end
|
|
182
199
|
end
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
<key>bugsnag</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>apiKey</key>
|
|
8
|
+
<string>12345678901234567890123456789AAA</string>
|
|
9
|
+
</dict>
|
|
7
10
|
<key>CFBundleDevelopmentRegion</key>
|
|
8
11
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
9
12
|
<key>CFBundleExecutable</key>
|
|
@@ -17,9 +20,9 @@
|
|
|
17
20
|
<key>CFBundlePackageType</key>
|
|
18
21
|
<string>APPL</string>
|
|
19
22
|
<key>CFBundleShortVersionString</key>
|
|
20
|
-
<string>
|
|
23
|
+
<string>2.0-other</string>
|
|
21
24
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>
|
|
25
|
+
<string>22</string>
|
|
23
26
|
<key>LSRequiresIPhoneOS</key>
|
|
24
27
|
<true/>
|
|
25
28
|
<key>UILaunchStoryboardName</key>
|
|
@@ -45,5 +48,3 @@
|
|
|
45
48
|
</array>
|
|
46
49
|
</dict>
|
|
47
50
|
</plist>
|
|
48
|
-
|
|
49
|
-
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
<key>bugsnag</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>apiKey</key>
|
|
8
|
+
<string>12345678901234567890123456789DDD</string>
|
|
9
|
+
</dict>
|
|
7
10
|
<key>CFBundleDevelopmentRegion</key>
|
|
8
11
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
9
12
|
<key>CFBundleExecutable</key>
|
|
@@ -17,9 +20,9 @@
|
|
|
17
20
|
<key>CFBundlePackageType</key>
|
|
18
21
|
<string>APPL</string>
|
|
19
22
|
<key>CFBundleShortVersionString</key>
|
|
20
|
-
<string>
|
|
23
|
+
<string>3.0-project</string>
|
|
21
24
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>
|
|
25
|
+
<string>33</string>
|
|
23
26
|
<key>LSRequiresIPhoneOS</key>
|
|
24
27
|
<true/>
|
|
25
28
|
<key>UILaunchStoryboardName</key>
|
|
@@ -45,4 +48,3 @@
|
|
|
45
48
|
</array>
|
|
46
49
|
</dict>
|
|
47
50
|
</plist>
|
|
48
|
-
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
<key>bugsnag</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>apiKey</key>
|
|
8
|
+
<string>12345678901234567890123456789EEE</string>
|
|
9
|
+
</dict>
|
|
7
10
|
<key>CFBundleDevelopmentRegion</key>
|
|
8
11
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
9
12
|
<key>CFBundleExecutable</key>
|
|
@@ -45,4 +48,3 @@
|
|
|
45
48
|
</array>
|
|
46
49
|
</dict>
|
|
47
50
|
</plist>
|
|
48
|
-
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>BugsnagAPIKey</key>
|
|
6
|
+
<string>12345678901234567890123456789BBB</string>
|
|
7
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
8
|
+
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
9
|
+
<key>CFBundleExecutable</key>
|
|
10
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
11
|
+
<key>CFBundleIdentifier</key>
|
|
12
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
13
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
14
|
+
<string>6.0</string>
|
|
15
|
+
<key>CFBundleName</key>
|
|
16
|
+
<string>$(PRODUCT_NAME)</string>
|
|
17
|
+
<key>CFBundlePackageType</key>
|
|
18
|
+
<string>APPL</string>
|
|
19
|
+
<key>CFBundleShortVersionString</key>
|
|
20
|
+
<string>4.0-project</string>
|
|
21
|
+
<key>CFBundleVersion</key>
|
|
22
|
+
<string>44</string>
|
|
23
|
+
<key>LSRequiresIPhoneOS</key>
|
|
24
|
+
<true/>
|
|
25
|
+
<key>UILaunchStoryboardName</key>
|
|
26
|
+
<string>LaunchScreen</string>
|
|
27
|
+
<key>UIMainStoryboardFile</key>
|
|
28
|
+
<string>Main</string>
|
|
29
|
+
<key>UIRequiredDeviceCapabilities</key>
|
|
30
|
+
<array>
|
|
31
|
+
<string>armv7</string>
|
|
32
|
+
</array>
|
|
33
|
+
<key>UISupportedInterfaceOrientations</key>
|
|
34
|
+
<array>
|
|
35
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
36
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
37
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
38
|
+
</array>
|
|
39
|
+
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
40
|
+
<array>
|
|
41
|
+
<string>UIInterfaceOrientationPortrait</string>
|
|
42
|
+
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
43
|
+
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
44
|
+
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
45
|
+
</array>
|
|
46
|
+
</dict>
|
|
47
|
+
</plist>
|
|
@@ -5,53 +5,66 @@ require 'fastlane/actions/get_info_plist_value'
|
|
|
5
5
|
BuildAction = Fastlane::Actions::SendBuildToBugsnagAction
|
|
6
6
|
|
|
7
7
|
describe BuildAction do
|
|
8
|
-
def
|
|
9
|
-
BuildAction.
|
|
10
|
-
[x.key, x.default_value]
|
|
11
|
-
end.to_h
|
|
8
|
+
def run_with args
|
|
9
|
+
BuildAction.run(Fastlane::ConfigurationHelper.parse(BuildAction, args))
|
|
12
10
|
end
|
|
13
11
|
|
|
14
12
|
context 'building an iOS project' do
|
|
15
13
|
it 'detects default Info.plist file excluding test dirs' do
|
|
16
14
|
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
17
15
|
payload = ::JSON.load(body)
|
|
18
|
-
expect(payload['appVersion']).to eq '
|
|
19
|
-
expect(payload['appBundleVersion']).to eq '
|
|
20
|
-
expect(payload['apiKey']).to eq '
|
|
16
|
+
expect(payload['appVersion']).to eq '2.0-other'
|
|
17
|
+
expect(payload['appBundleVersion']).to eq '22'
|
|
18
|
+
expect(payload['apiKey']).to eq '12345678901234567890123456789AAA'
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
24
|
-
|
|
22
|
+
run_with({})
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'reads api key from legacy location' do
|
|
27
|
+
# the API key is now in `bugsnag.apiKey`, it used to be in 'BugsnagAPIKey',
|
|
28
|
+
# test this can be extracted correctly from the `ios_proj_legacy`
|
|
29
|
+
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
30
|
+
payload = ::JSON.load(body)
|
|
31
|
+
expect(payload['appVersion']).to eq '4.0-project'
|
|
32
|
+
expect(payload['appBundleVersion']).to eq '44'
|
|
33
|
+
expect(payload['apiKey']).to eq '12345678901234567890123456789BBB'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj_legacy')) do
|
|
37
|
+
run_with({})
|
|
25
38
|
end
|
|
26
39
|
end
|
|
27
40
|
|
|
28
41
|
context 'using default config_file option' do
|
|
29
|
-
context 'override API key
|
|
42
|
+
context 'override API key from config' do
|
|
30
43
|
it 'reads API key from the api_key option' do
|
|
31
44
|
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
32
45
|
payload = ::JSON.load(body)
|
|
33
|
-
expect(payload['apiKey']).to eq '
|
|
46
|
+
expect(payload['apiKey']).to eq '12345678901234567890123456789FFF'
|
|
34
47
|
end
|
|
35
48
|
|
|
36
49
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
37
|
-
|
|
38
|
-
api_key: '
|
|
39
|
-
})
|
|
50
|
+
run_with({
|
|
51
|
+
api_key: '12345678901234567890123456789FFF'
|
|
52
|
+
})
|
|
40
53
|
end
|
|
41
54
|
end
|
|
42
55
|
|
|
43
|
-
it '
|
|
56
|
+
it 'uses input versions from options' do
|
|
44
57
|
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
45
58
|
payload = ::JSON.load(body)
|
|
46
|
-
expect(payload['appVersion']).to eq '
|
|
47
|
-
expect(payload['appBundleVersion']).to eq '
|
|
59
|
+
expect(payload['appVersion']).to eq '8.0.0'
|
|
60
|
+
expect(payload['appBundleVersion']).to eq '800'
|
|
48
61
|
end
|
|
49
62
|
|
|
50
63
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
51
|
-
|
|
52
|
-
app_version: '
|
|
53
|
-
ios_bundle_version: '
|
|
54
|
-
})
|
|
64
|
+
run_with({
|
|
65
|
+
app_version: '8.0.0',
|
|
66
|
+
ios_bundle_version: '800'
|
|
67
|
+
})
|
|
55
68
|
end
|
|
56
69
|
end
|
|
57
70
|
end
|
|
@@ -61,46 +74,49 @@ describe BuildAction do
|
|
|
61
74
|
it 'reads API key and version info from the config file' do
|
|
62
75
|
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
63
76
|
payload = ::JSON.load(body)
|
|
64
|
-
expect(payload['appVersion']).to eq '
|
|
65
|
-
expect(payload['appBundleVersion']).to eq '
|
|
66
|
-
expect(payload['apiKey']).to eq '
|
|
77
|
+
expect(payload['appVersion']).to eq '3.0-project'
|
|
78
|
+
expect(payload['appBundleVersion']).to eq '33'
|
|
79
|
+
expect(payload['apiKey']).to eq '12345678901234567890123456789DDD'
|
|
67
80
|
end
|
|
68
81
|
|
|
69
82
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
70
|
-
|
|
83
|
+
run_with({
|
|
71
84
|
config_file: File.join('Project', 'Info.plist')
|
|
72
|
-
})
|
|
85
|
+
})
|
|
73
86
|
end
|
|
74
87
|
end
|
|
75
88
|
|
|
76
|
-
context 'override API key
|
|
77
|
-
it '
|
|
89
|
+
context 'override API key, and config file' do
|
|
90
|
+
it 'uses the input api_key to override a non default config' do
|
|
78
91
|
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
79
92
|
payload = ::JSON.load(body)
|
|
80
|
-
expect(payload['
|
|
93
|
+
expect(payload['appVersion']).to eq '3.0-project'
|
|
94
|
+
expect(payload['appBundleVersion']).to eq '33'
|
|
95
|
+
expect(payload['apiKey']).to eq '12345678901234567890123456789EEE'
|
|
81
96
|
end
|
|
82
97
|
|
|
83
98
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
84
|
-
|
|
99
|
+
run_with({
|
|
85
100
|
config_file: File.join('Project', 'Info.plist'),
|
|
86
|
-
api_key: '
|
|
87
|
-
})
|
|
101
|
+
api_key: '12345678901234567890123456789EEE'
|
|
102
|
+
})
|
|
88
103
|
end
|
|
89
104
|
end
|
|
90
105
|
|
|
91
|
-
it '
|
|
106
|
+
it 'uses the input versions to override a non default config' do
|
|
92
107
|
expect(BuildAction).to receive(:send_notification) do |url, body|
|
|
93
108
|
payload = ::JSON.load(body)
|
|
94
|
-
expect(payload['appVersion']).to eq '
|
|
95
|
-
expect(payload['appBundleVersion']).to eq '
|
|
109
|
+
expect(payload['appVersion']).to eq '9.0.0'
|
|
110
|
+
expect(payload['appBundleVersion']).to eq '900'
|
|
111
|
+
expect(payload['apiKey']).to eq '12345678901234567890123456789DDD'
|
|
96
112
|
end
|
|
97
113
|
|
|
98
114
|
Dir.chdir(File.join(FIXTURE_PATH, 'ios_proj')) do
|
|
99
|
-
|
|
115
|
+
run_with({
|
|
100
116
|
config_file: File.join('Project', 'Info.plist'),
|
|
101
|
-
app_version: '
|
|
102
|
-
ios_bundle_version: '
|
|
103
|
-
})
|
|
117
|
+
app_version: '9.0.0',
|
|
118
|
+
ios_bundle_version: '900'
|
|
119
|
+
})
|
|
104
120
|
end
|
|
105
121
|
end
|
|
106
122
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-bugsnag
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Delisa Mason
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-01-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: xml-simple
|
|
@@ -108,6 +108,20 @@ dependencies:
|
|
|
108
108
|
- - ">="
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
110
|
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: parallel
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "<"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 1.20.0
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "<"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: 1.20.0
|
|
111
125
|
- !ruby/object:Gem::Dependency
|
|
112
126
|
name: fastlane
|
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -122,7 +136,7 @@ dependencies:
|
|
|
122
136
|
- - ">="
|
|
123
137
|
- !ruby/object:Gem::Version
|
|
124
138
|
version: 2.28.5
|
|
125
|
-
description:
|
|
139
|
+
description:
|
|
126
140
|
email: iskanamagus@gmail.com
|
|
127
141
|
executables: []
|
|
128
142
|
extensions: []
|
|
@@ -143,16 +157,17 @@ files:
|
|
|
143
157
|
- spec/fixtures/ios_proj/FirstRealFolder/Info.plist
|
|
144
158
|
- spec/fixtures/ios_proj/Project/Info.plist
|
|
145
159
|
- spec/fixtures/ios_proj/aaTests/Info.plist
|
|
160
|
+
- spec/fixtures/ios_proj_legacy/Project/Info.plist
|
|
146
161
|
- spec/fixtures/more_files.zip
|
|
147
162
|
- spec/fixtures/stuff/app.dSYM/Contents/Resources/DWARF/app
|
|
148
163
|
- spec/fixtures/stuff/app2.dSYM/Contents/Resources/DWARF/app2
|
|
149
164
|
- spec/send_build_to_bugsnag_spec.rb
|
|
150
165
|
- spec/spec_helper.rb
|
|
151
|
-
homepage: https://github.com/bugsnag/bugsnag-upload
|
|
166
|
+
homepage: https://github.com/bugsnag/bugsnag-dsym-upload
|
|
152
167
|
licenses:
|
|
153
168
|
- MIT
|
|
154
169
|
metadata: {}
|
|
155
|
-
post_install_message:
|
|
170
|
+
post_install_message:
|
|
156
171
|
rdoc_options: []
|
|
157
172
|
require_paths:
|
|
158
173
|
- lib
|
|
@@ -167,15 +182,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
167
182
|
- !ruby/object:Gem::Version
|
|
168
183
|
version: '0'
|
|
169
184
|
requirements: []
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
signing_key:
|
|
185
|
+
rubygems_version: 3.0.3
|
|
186
|
+
signing_key:
|
|
173
187
|
specification_version: 4
|
|
174
188
|
summary: Uploads dSYM files to Bugsnag
|
|
175
189
|
test_files:
|
|
176
190
|
- spec/spec_helper.rb
|
|
177
191
|
- spec/bugsnag_upload_dsym_action_spec.rb
|
|
178
192
|
- spec/send_build_to_bugsnag_spec.rb
|
|
193
|
+
- spec/fixtures/ios_proj_legacy/Project/Info.plist
|
|
179
194
|
- spec/fixtures/more_files.zip
|
|
180
195
|
- spec/fixtures/BCSymbolMaps/real.bcsymbolmap
|
|
181
196
|
- spec/fixtures/ios_proj/FirstRealFolder/Info.plist
|