Skip to content

Commit 679a934

Browse files
authored
Merge pull request #89 from bugsnag/je/plat-14602-create-build-cli
Use the Bugsnag CLI to send build information
2 parents dcd85e9 + 4c97a5a commit 679a934

File tree

6 files changed

+235
-227
lines changed

6 files changed

+235
-227
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
require 'os'
2+
require 'rbconfig'
3+
4+
class BugsnagCli
5+
def self.get_bugsnag_cli_path(params)
6+
bundled_bugsnag_cli_path = self.get_bundled_path
7+
bundled_bugsnag_cli_version = Gem::Version.new(`#{bundled_bugsnag_cli_path} --version`.scan(/(?:\d+\.?){3}/).first)
8+
9+
if params[:bugsnag_cli_path]
10+
bugsnag_cli_path = params[:bugsnag_cli_path] || bundled_bugsnag_cli_path
11+
12+
bugsnag_cli_version = Gem::Version.new(`#{bugsnag_cli_path} --version`.scan(/(?:\d+\.?){3}/).first)
13+
14+
if bugsnag_cli_version < bundled_bugsnag_cli_version
15+
FastlaneCore::UI.warning("The installed bugsnag-cli at #{bugsnag_cli_path} is outdated (#{bugsnag_cli_version}). The current bundled version is: #{bundled_bugsnag_cli_version}. It is recommended that you either update your installed version or use the bundled version.")
16+
end
17+
FastlaneCore::UI.verbose("Using bugsnag-cli from path: #{bugsnag_cli_path}")
18+
bugsnag_cli_path
19+
else
20+
FastlaneCore::UI.verbose("Using bundled bugsnag-cli from path: #{bundled_bugsnag_cli_path}")
21+
bundled_bugsnag_cli_path
22+
end
23+
end
24+
25+
def self.get_bundled_path
26+
host_cpu = RbConfig::CONFIG['host_cpu']
27+
if OS.mac?
28+
if host_cpu =~ /arm|aarch64/
29+
self.bin_folder('arm64-macos-bugsnag-cli')
30+
else
31+
self.bin_folder('x86_64-macos-bugsnag-cli')
32+
end
33+
elsif OS.windows?
34+
if OS.bits == 64
35+
self.bin_folder('x86_64-windows-bugsnag-cli.exe')
36+
else
37+
self.bin_folder('i386-windows-bugsnag-cli.exe')
38+
end
39+
else
40+
if host_cpu =~ /arm|aarch64/
41+
self.bin_folder('arm64-linux-bugsnag-cli')
42+
elsif OS.bits == 64
43+
self.bin_folder('x86_64-linux-bugsnag-cli')
44+
else
45+
self.bin_folder('i386-linux-bugsnag-cli')
46+
end
47+
end
48+
end
49+
50+
def self.bin_folder(filename)
51+
File.expand_path("../../../../../bin/#{filename}", File.dirname(__FILE__))
52+
end
53+
54+
def self.create_build_args(api_key, version_name, version_code, bundle_version, release_stage, builder, revision, repository, provider, auto_assign_release, metadata, retries, timeout, endpoint)
55+
args = []
56+
args += ["--api-key", api_key] unless api_key.nil?
57+
args += ["--version-name", version_name] unless version_name.nil?
58+
args += ["--version-code", version_code] unless version_code.nil?
59+
args += ["--bundle-version", bundle_version] unless bundle_version.nil?
60+
args += ["--release-stage", release_stage] unless release_stage.nil?
61+
args += ["--builder-name", builder] unless builder.nil?
62+
args += ["--revision", revision] unless revision.nil?
63+
args += ["--repository", repository] unless repository.nil?
64+
args += ["--provider", provider] unless provider.nil?
65+
args += ["--auto-assign-release"] if auto_assign_release
66+
unless metadata.nil?
67+
if metadata.is_a?(String)
68+
#
69+
args += ["--metadata", metadata]
70+
elsif metadata.is_a?(Hash)
71+
formatted_metadata = metadata.map { |k, v| %Q{"#{k}"="#{v}"} }.join(",")
72+
args += ["--metadata", formatted_metadata]
73+
end
74+
end
75+
args += ["--retries", retries] unless retries.nil?
76+
args += ["--timeout", timeout] unless timeout.nil?
77+
args += ["--build-api-root-url", endpoint] unless endpoint.nil?
78+
args += ["--verbose"] if FastlaneCore::Globals.verbose?
79+
args
80+
end
81+
82+
def self.upload_args dir, upload_url, project_root, api_key, ignore_missing_dwarf, ignore_empty_dsym, dryrun, log_level, port, retries, timeout, configuration, scheme, plist, xcode_project
83+
args = []
84+
args += ["--verbose"] if FastlaneCore::Globals.verbose?
85+
args += ["--ignore-missing-dwarf"] if ignore_missing_dwarf
86+
args += ["--ignore-empty-dsym"] if ignore_empty_dsym
87+
args += ["--api-key", api_key] unless api_key.nil?
88+
args += ["--upload-api-root-url", upload_url] unless upload_url.nil?
89+
args += ["--project-root", project_root] unless project_root.nil?
90+
args += ["--dry-run"] if dryrun
91+
args += ["--log-level", log_level] unless log_level.nil?
92+
args += ["--port", port] unless port.nil?
93+
args += ["--retries", retries] unless retries.nil?
94+
args += ["--timeout", timeout] unless timeout.nil?
95+
args += ["--configuration", configuration] unless configuration.nil?
96+
args += ["--scheme", scheme] unless scheme.nil?
97+
args += ["--plist", plist] unless plist.nil?
98+
args += ["--xcode-project", xcode_project] unless xcode_project.nil?
99+
args << dir
100+
args
101+
end
102+
103+
def self.upload_dsym cli_path, args
104+
bugsnag_cli_command = "#{cli_path} upload dsym #{args.join(' ')}"
105+
FastlaneCore::UI.verbose("Running command: #{bugsnag_cli_command}")
106+
Kernel.system(bugsnag_cli_command)
107+
end
108+
109+
def self.create_build cli_path, args
110+
bugsnag_cli_command = "#{cli_path} create-build #{args.join(' ')}"
111+
FastlaneCore::UI.verbose("Running command: #{bugsnag_cli_command}")
112+
Kernel.system(bugsnag_cli_command)
113+
end
114+
end
115+
116+

tools/fastlane-plugin/lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb

Lines changed: 68 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,66 @@
11
require "xmlsimple"
22
require "json"
33
require_relative "find_info_plist_path"
4+
require_relative "bugsnag_cli"
45

56
module Fastlane
67
module Actions
78
class SendBuildToBugsnagAction < Action
8-
9-
BUILD_TOOL = "bugsnag-fastlane-plugin"
10-
119
def self.run(params)
12-
payload = {buildTool: BUILD_TOOL, sourceControl: {}}
10+
bugsnag_cli_path = BugsnagCli.get_bugsnag_cli_path(params)
1311

1412
# If a configuration file was found or was specified, load in the options:
13+
config_options = {}
1514
if params[:config_file]
1615
UI.message("Loading build information from #{params[:config_file]}")
1716
config_options = load_config_file_options(params[:config_file])
18-
19-
# for each of the config options, if it's not been overriden by any
20-
# input to the lane, write it to the payload:
21-
payload[:apiKey] = params[:api_key] || config_options[:apiKey]
22-
payload[:appVersion] = params[:app_version] || config_options[:appVersion]
23-
payload[:appVersionCode] = params[:android_version_code] || config_options[:appVersionCode]
24-
payload[:appBundleVersion] = params[:ios_bundle_version] || config_options[:appBundleVersion]
25-
payload[:releaseStage] = params[:release_stage] || config_options[:releaseStage] || "production"
26-
else
27-
# No configuration file was found or specified, use the input parameters:
28-
payload[:apiKey] = params[:api_key]
29-
payload[:appVersion] = params[:app_version]
30-
payload[:appVersionCode] = params[:android_version_code]
31-
payload[:appBundleVersion] = params[:ios_bundle_version]
32-
payload[:releaseStage] = params[:release_stage] || "production"
3317
end
3418

35-
# If builder, or source control information has been provided into
36-
# Fastlane, apply it to the payload here.
37-
payload[:builderName] = params[:builder] if params[:builder]
38-
payload[:sourceControl][:revision] = params[:revision] if params[:revision]
39-
payload[:sourceControl][:repository] = params[:repository] if params[:repository]
40-
payload[:sourceControl][:provider] = params[:provider] if params[:provider]
41-
42-
# If provided apply metadata to payload.
43-
payload[:metadata] = params[:metadata]
44-
45-
payload.reject! {|k,v| v == nil || (v.is_a?(Hash) && v.empty?)}
46-
47-
if payload[:apiKey].nil? || !payload[:apiKey].is_a?(String)
19+
api_key = params[:api_key] || config_options[:apiKey] unless (params[:api_key].nil? && config_options[:apiKey].nil?)
20+
version_name = params[:app_version] || config_options[:appVersion] unless (params[:app_version].nil? && config_options[:appVersion].nil?)
21+
version_code = params[:android_version_code] || config_options[:appVersionCode] unless (params[:android_version_code].nil? && config_options[:appVersionCode].nil?)
22+
bundle_version = params[:ios_bundle_version] || config_options[:appBundleVersion] unless (params[:ios_bundle_version].nil? && config_options[:appBundleVersion].nil?)
23+
release_stage = params[:release_stage] || config_options[:releaseStage] || "production" unless (params[:release_stage].nil? && config_options[:releaseStage].nil?)
24+
builder = params[:builder] unless params[:builder].nil?
25+
revision = params[:revision] unless params[:revision].nil?
26+
repository = params[:repository] unless params[:repository].nil?
27+
provider = params[:provider] unless params[:provider].nil?
28+
auto_assign_release = params[:auto_assign_release] unless params[:auto_assign_release].nil?
29+
metadata = params[:metadata] unless params[:metadata].nil?
30+
retries = params[:retries] unless params[:retries].nil?
31+
timeout = params[:timeout] unless params[:timeout].nil?
32+
endpoint = params[:endpoint] unless params[:endpoint].nil?
33+
34+
35+
if api_key.nil? || !api_key.is_a?(String)
4836
UI.user_error! missing_api_key_message(params)
4937
end
50-
if payload[:appVersion].nil?
38+
if version_name.nil?
5139
UI.user_error! missing_app_version_message(params)
5240
end
5341

54-
# If verbose flag is enabled (`--verbose`), display the payload debug info
55-
UI.verbose("Sending build to Bugsnag with payload:")
56-
payload.each do |param|
57-
UI.verbose(" #{param[0].to_s.rjust(18)}: #{param[1]}")
42+
args = BugsnagCli.create_build_args(
43+
api_key,
44+
version_name,
45+
version_code,
46+
bundle_version,
47+
release_stage,
48+
builder,
49+
revision,
50+
repository,
51+
provider,
52+
auto_assign_release,
53+
metadata,
54+
retries,
55+
timeout,
56+
endpoint
57+
)
58+
success = BugsnagCli.create_build bugsnag_cli_path, args
59+
if success
60+
UI.success("Build successfully sent to Bugsnag")
61+
else
62+
UI.user_error!("Failed to send build to Bugsnag.")
5863
end
59-
60-
send_notification(params[:endpoint], ::JSON.dump(payload))
6164
end
6265

6366
def self.missing_api_key_message(params)
@@ -146,6 +149,11 @@ def self.available_options
146149
FastlaneCore::ConfigItem.new(key: :release_stage,
147150
description: "Release stage being built, i.e. staging, production",
148151
optional: true),
152+
FastlaneCore::ConfigItem.new(key: :auto_assign_release,
153+
description: "Whether to automatically associate this build with any new error events and sessions that are received for",
154+
optional: true,
155+
default_value: false,
156+
is_string: false),
149157
FastlaneCore::ConfigItem.new(key: :builder,
150158
description: "The name of the entity triggering the build",
151159
optional: true,
@@ -170,13 +178,28 @@ def self.available_options
170178
end),
171179
FastlaneCore::ConfigItem.new(key: :endpoint,
172180
description: "Bugsnag deployment endpoint",
173-
optional: true,
174-
default_value: "https://build.bugsnag.com"),
181+
default_value: nil,
182+
optional: true),
175183
FastlaneCore::ConfigItem.new(key: :metadata,
176184
description: "Metadata",
177185
optional:true,
178-
type: Object,
179-
default_value: nil)
186+
type: Object,
187+
default_value: nil),
188+
FastlaneCore::ConfigItem.new(key: :retries,
189+
description: "The number of retry attempts before failing an upload request",
190+
optional: true,
191+
is_string: false),
192+
FastlaneCore::ConfigItem.new(key: :timeout,
193+
description: "The number of seconds to wait before failing an upload request",
194+
optional: true,
195+
is_string: false),
196+
FastlaneCore::ConfigItem.new(key: :bugsnag_cli_path,
197+
env_name: "BUGSNAG_CLI_PATH",
198+
description: "Path to your bugsnag-cli",
199+
optional: true,
200+
verify_block: proc do |value|
201+
UI.user_error! "'#{value}' is not executable" unless FastlaneCore::Helper.executable?(value)
202+
end)
180203
]
181204
end
182205

@@ -194,7 +217,8 @@ def self.load_git_remote_options
194217
git_options[:repository] = origin.url
195218
git_options[:revision] = repo.revparse("HEAD")
196219
end
197-
rescue
220+
rescue => e
221+
UI.verbose("Could not load git options: #{e.message}")
198222
end
199223
return git_options
200224
end
@@ -316,45 +340,6 @@ def self.map_meta_data(meta_data)
316340
end
317341
output
318342
end
319-
320-
def self.parse_response_body(response)
321-
begin
322-
JSON.load(response.body)
323-
rescue
324-
nil
325-
end
326-
end
327-
328-
def self.send_notification(url, body)
329-
require "net/http"
330-
uri = URI.parse(url)
331-
http = Net::HTTP.new(uri.host, uri.port)
332-
http.read_timeout = 15
333-
http.open_timeout = 15
334-
335-
http.use_ssl = uri.scheme == "https"
336-
337-
uri.path == "" ? "/" : uri.path
338-
request = Net::HTTP::Post.new(uri, {"Content-Type" => "application/json"})
339-
request.body = body
340-
begin
341-
response = http.request(request)
342-
rescue => e
343-
UI.user_error! "Failed to notify Bugsnag of a new build: #{e}"
344-
end
345-
if body = parse_response_body(response)
346-
if body.has_key? "errors"
347-
errors = body["errors"].map {|error| "\n * #{error}"}.join
348-
UI.user_error! "The following errors occurred while notifying Bugsnag:#{errors}.\n\nPlease update your lane config and retry."
349-
elsif response.code != "200"
350-
UI.user_error! "Failed to notify Bugsnag of a new build. Please retry. HTTP status code: #{response.code}"
351-
end
352-
if body.has_key? "warnings"
353-
warnings = body["warnings"].map {|warn| "\n * #{warn}"}.join
354-
UI.important "Sending the build to Bugsnag succeeded with the following warnings:#{warnings}\n\nPlease update your lane config."
355-
end
356-
end
357-
end
358343
end
359344
end
360345
end

0 commit comments

Comments
 (0)