fastlane-plugin-bugsee 1.0.2 → 1.1.0
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 +5 -5
- data/BugseeAgent +3962 -0
- data/README.md +102 -2
- data/lib/fastlane/plugin/bugsee/actions/upload_artifact_to_bugsee.rb +230 -0
- data/lib/fastlane/plugin/bugsee/actions/upload_mapping_to_bugsee.rb +260 -0
- data/lib/fastlane/plugin/bugsee/actions/upload_symbols_to_bugsee.rb +104 -19
- data/lib/fastlane/plugin/bugsee/helper/bugsee_handshake.rb +228 -0
- data/lib/fastlane/plugin/bugsee/helper/bugsee_uuid.rb +101 -0
- data/lib/fastlane/plugin/bugsee/version.rb +1 -1
- metadata +8 -8
data/README.md
CHANGED
|
@@ -12,8 +12,7 @@ fastlane add_plugin bugsee
|
|
|
12
12
|
|
|
13
13
|
## About bugsee
|
|
14
14
|
|
|
15
|
-
Bugsee is free crash and bug reporting with video, network and logs. Sign up for a service at [https://www.bugsee.com](https://www.bugsee.com). This plugin implements fastlane action to
|
|
16
|
-
symbol (dSYM) files to Bugsee servers.
|
|
15
|
+
Bugsee is free crash and bug reporting with video, network and logs. Sign up for a service at [https://www.bugsee.com](https://www.bugsee.com). This plugin implements a fastlane action that uploads debug symbol (dSYM) files to Bugsee, and — when invoked from an Xcode build phase — also collects and registers the project's dependency graph.
|
|
17
16
|
|
|
18
17
|
## Usage
|
|
19
18
|
|
|
@@ -42,6 +41,107 @@ lane :refresh_dsyms do
|
|
|
42
41
|
end
|
|
43
42
|
```
|
|
44
43
|
|
|
44
|
+
## How symbol upload works
|
|
45
|
+
|
|
46
|
+
Starting with `1.1.0`, symbol upload shells out to the [bugsee-cli](https://github.com/bugsee/bugsee-cli) Rust binary — the same uploader the Bugsee Android Gradle plugin uses for ProGuard/R8 mappings. One mechanism, one wire format across both platforms.
|
|
47
|
+
|
|
48
|
+
On first use the CLI is downloaded from `https://download.bugsee.com/cli`, SHA-256 verified against the published sidecar, and cached at `~/.bugsee/cli/<version>/<host-triple>/`. Subsequent runs hit the cache — no per-build network round-trip past the dSYM upload itself.
|
|
49
|
+
|
|
50
|
+
Override the auto-download with environment variables:
|
|
51
|
+
|
|
52
|
+
| Variable | Purpose |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `BUGSEE_CLI_PATH` | Path to a local `bugsee-cli` binary. Useful when developing the CLI itself or in air-gapped CI. |
|
|
55
|
+
| `BUGSEE_CLI_VERSION` | Pin or test a specific CLI release; defaults to the version bundled with this plugin release. |
|
|
56
|
+
|
|
57
|
+
Each `.dSYM` is uploaded as its own request, so the dashboard surfaces a per-framework symbol record. If a host architecture isn't supported by the published CLI (currently macOS / Linux / Windows on x86_64 + arm64, where each platform exists), the agent logs and skips — it does not fail the build.
|
|
58
|
+
|
|
59
|
+
## Dependency collection
|
|
60
|
+
|
|
61
|
+
When `BugseeAgent` runs from an Xcode build phase (where `SRCROOT` / `INFOPLIST_PATH` are set), it also scans the project for dependency lockfiles and registers the resolved graph with the build:
|
|
62
|
+
|
|
63
|
+
- **CocoaPods** — `Podfile.lock` (provides direct/transitive distinction and parent edges).
|
|
64
|
+
- **Swift Package Manager** — `Package.resolved` (both Xcode-managed and SPM CLI v2 formats).
|
|
65
|
+
- **Carthage** — `Cartfile.resolved`.
|
|
66
|
+
|
|
67
|
+
The emitted blob is wire-compatible with the Bugsee Android Gradle plugin's `DependencyCollector`, so iOS and Android deps render identically in the dashboard.
|
|
68
|
+
|
|
69
|
+
No configuration is required — if a lockfile exists, it's collected.
|
|
70
|
+
|
|
71
|
+
## Android mapping (ProGuard / R8) upload
|
|
72
|
+
|
|
73
|
+
Starting with `1.1.0`, this plugin also supports uploading Android `mapping.txt` files via a separate `upload_mapping_to_bugsee` action. The canonical path remains the [Bugsee Android Gradle plugin](https://github.com/bugsee/bugsee-android-gradle-plugin), which does the upload automatically as part of every gradle build. Reach for this fastlane action only when one of the following applies:
|
|
74
|
+
|
|
75
|
+
- **Your CI splits build (no token) from publish (production token).** The Gradle plugin builds the APK on machine A without uploading; this action uploads the mapping on machine B with the production token. The action looks for the Gradle plugin's `build-uuid.txt` (under `**/build/intermediates/bugsee/*/build-uuid.txt`) so the UUID matches what the SDK already has baked into the APK.
|
|
76
|
+
- **The host app is not instrumented by the Bugsee Gradle plugin** — *and* uses Bugsee Android SDK 7.0.0-beta13+. The action synthesises a UUID Ruby-side from `(app_token, version, build)`; the SDK reproduces the same UUID at runtime via its third BUILD_UUID fallback (added in 7.0.0-beta13), so symbolication still works.
|
|
77
|
+
|
|
78
|
+
If neither applies — i.e. you're using the Gradle plugin in the standard way — there's nothing to do; the Gradle plugin handles the upload as part of the existing `gradle` action.
|
|
79
|
+
|
|
80
|
+
Example invocation:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
upload_mapping_to_bugsee(
|
|
84
|
+
app_token: ENV["BUGSEE_APP_TOKEN"],
|
|
85
|
+
mapping_path: "./app/build/outputs/mapping/release/mapping.txt",
|
|
86
|
+
version: "1.2.3", # android:versionName
|
|
87
|
+
build: "42", # android:versionCode
|
|
88
|
+
# Optional:
|
|
89
|
+
# uuid: "...", # explicit override
|
|
90
|
+
# build_uuid_path: "app/build/intermediates/bugsee/release/build-uuid.txt",
|
|
91
|
+
# icon_path: "app/src/main/res/mipmap-xxxhdpi/ic_launcher.png",
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**UUID resolution chain** (most authoritative to fallback):
|
|
96
|
+
|
|
97
|
+
1. Explicit `:uuid` if passed.
|
|
98
|
+
2. `:build_uuid_path` if given and the file exists.
|
|
99
|
+
3. The Bugsee Gradle plugin's `build-uuid.txt`, auto-globbed under `**/build/intermediates/bugsee/*/build-uuid.txt`.
|
|
100
|
+
4. Ruby-side synthesis: `nameUUIDFromBytes(app_token + 0x1F + version + 0x1F + build)` — matches the SDK's Channel 3 runtime fallback (7.0.0-beta13+).
|
|
101
|
+
|
|
102
|
+
All four branches produce a UUID the SDK can independently reproduce, so server-side mapping lookup resolves crashes correctly regardless of which branch fired.
|
|
103
|
+
|
|
104
|
+
The action `is_supported?(:android)` only.
|
|
105
|
+
|
|
106
|
+
## iOS artefact upload (size analysis)
|
|
107
|
+
|
|
108
|
+
This plugin also exposes an `upload_artifact_to_bugsee` action that packages a built `.app` into a byte-deterministic synthetic `.ipa` and uploads it to Bugsee for size analysis. The canonical path remains the [Bugsee iOS SDK](https://github.com/bugsee/bugsee-ios)'s `tools.bundle/BugseeAgent` build phase, which runs size analysis automatically when `BUGSEE_BUILD_INFO_ENABLED` is on (default). Reach for this fastlane action only when one of the following applies:
|
|
109
|
+
|
|
110
|
+
- **Your CI doesn't integrate the SDK's build phase.** Binary-only CI, alternative build systems (Bazel, Tuist with custom phases), or pipelines that pre-build the `.app` and only invoke fastlane for publish.
|
|
111
|
+
- **Your CI splits build (no token) from publish (production token).** The build machine produces the `.app` or `.xcarchive`; the publish machine runs this action with the production token.
|
|
112
|
+
- **You want the size-trend chart without shipping bytes.** Pass `build_info_only: true` to record `artifact_size` on the server (so the dashboard's size-trend chart works) without uploading the `.ipa` bytes themselves. Useful for firewalled CI and privacy-sensitive setups.
|
|
113
|
+
|
|
114
|
+
If the SDK's build phase already runs in the same Xcode build, the action's cross-producer handshake skips by default (the SDK writes a manifest at `build/bugsee/build-actions.json`; the action reads it and short-circuits when `artifact_upload: true`). Pass `force: true` to override.
|
|
115
|
+
|
|
116
|
+
Example invocation:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
upload_artifact_to_bugsee(
|
|
120
|
+
app_token: ENV["BUGSEE_APP_TOKEN"],
|
|
121
|
+
xcarchive_path: ENV["XCARCHIVE_PATH"],
|
|
122
|
+
# OR pass the .app directly:
|
|
123
|
+
# app_path: "build/Products/Release-iphoneos/MyApp.app",
|
|
124
|
+
# Optional:
|
|
125
|
+
# version: "1.2.3", # CFBundleShortVersionString (else read from Info.plist)
|
|
126
|
+
# build: "42", # CFBundleVersion (else read from Info.plist)
|
|
127
|
+
# build_info_only: true, # record size, skip the .ipa bytes upload
|
|
128
|
+
# force: true, # override the cross-producer handshake skip
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**App path resolution:**
|
|
133
|
+
|
|
134
|
+
1. Explicit `:app_path` if passed.
|
|
135
|
+
2. `:xcarchive_path` → the single `.app` under `<archive>/Products/Applications/`. Multiple `.app` siblings raise a `user_error` so the user can disambiguate.
|
|
136
|
+
|
|
137
|
+
**Env var aliases** (all are also exposed via fastlane's `available_options`):
|
|
138
|
+
|
|
139
|
+
- `BUGSEE_APP_TOKEN`, `BUGSEE_APP_PATH`, `BUGSEE_XCARCHIVE_PATH`
|
|
140
|
+
- `BUGSEE_APP_VERSION`, `BUGSEE_APP_BUILD`
|
|
141
|
+
- `BUGSEE_BUILD_INFO_ONLY`, `BUGSEE_FORCE`
|
|
142
|
+
|
|
143
|
+
The action `is_supported?(:ios)` only.
|
|
144
|
+
|
|
45
145
|
## Documentation
|
|
46
146
|
|
|
47
147
|
Further documentation about Bugsee crash symbolication is available at https://docs.bugsee.com
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
require 'fastlane/plugin/bugsee/helper/bugsee_handshake'
|
|
2
|
+
|
|
3
|
+
module Fastlane
|
|
4
|
+
module Actions
|
|
5
|
+
# Upload an iOS build artefact (`.app`) to Bugsee for size analysis.
|
|
6
|
+
#
|
|
7
|
+
# When and why to use this:
|
|
8
|
+
#
|
|
9
|
+
# The Bugsee iOS SDK's `tools.bundle/BugseeAgent` already runs
|
|
10
|
+
# size analysis as a post-action build phase when
|
|
11
|
+
# `BUGSEE_BUILD_INFO_ENABLED` is on (default). The most common
|
|
12
|
+
# scenario for THIS fastlane action is CI pipelines that don't
|
|
13
|
+
# integrate the SDK's build phase (binary-only CI, alternative
|
|
14
|
+
# build systems) or that explicitly split build (machine A) from
|
|
15
|
+
# publish (machine B, has the production token).
|
|
16
|
+
#
|
|
17
|
+
# What it does:
|
|
18
|
+
#
|
|
19
|
+
# 1. Locates the `.app` — explicit `:app_path`, OR resolved from
|
|
20
|
+
# `:xcarchive_path` via `Products/Applications/<App>.app`.
|
|
21
|
+
# 2. Shells to `BugseeAgent --upload-artifact` which:
|
|
22
|
+
# - Packages the `.app` into a synthetic byte-deterministic
|
|
23
|
+
# `.ipa` (same posture as the SDK side and the Android
|
|
24
|
+
# Gradle plugin so the back-end's content-hash dedup works
|
|
25
|
+
# across producers).
|
|
26
|
+
# - POSTs build registration to /v2/apps/<token>/builds with
|
|
27
|
+
# `request_artifact_upload: true`, getting back a presigned
|
|
28
|
+
# S3 PUT URL.
|
|
29
|
+
# - PUTs the .ipa bytes to that URL.
|
|
30
|
+
#
|
|
31
|
+
# Cross-producer handshake: if the Bugsee iOS SDK's BugseeAgent
|
|
32
|
+
# build phase already uploaded this build's artefact, skip — same
|
|
33
|
+
# gating shape as upload_symbols_to_bugsee and
|
|
34
|
+
# upload_mapping_to_bugsee.
|
|
35
|
+
class UploadArtifactToBugseeAction < Action
|
|
36
|
+
BUGSEE_AGENT_PATH = File.expand_path(
|
|
37
|
+
File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'BugseeAgent'))
|
|
38
|
+
|
|
39
|
+
def self.run(params)
|
|
40
|
+
app_token = params[:app_token]
|
|
41
|
+
host = params[:host] || "https://api.bugsee.com"
|
|
42
|
+
version = params[:version]
|
|
43
|
+
build = params[:build]
|
|
44
|
+
agent_path = params[:agent_path] || BUGSEE_AGENT_PATH
|
|
45
|
+
|
|
46
|
+
UI.user_error!("Please provide an app token via app_token:") unless app_token
|
|
47
|
+
|
|
48
|
+
agent_path = File.expand_path(agent_path)
|
|
49
|
+
UI.user_error!("BugseeAgent helper script is missing: #{agent_path}") unless File.exist?(agent_path)
|
|
50
|
+
|
|
51
|
+
# ──────────────────────────────────────────────────────
|
|
52
|
+
# Resolve the `.app` path. Priority:
|
|
53
|
+
# 1. Explicit :app_path
|
|
54
|
+
# 2. :xcarchive_path → Products/Applications/<single .app>
|
|
55
|
+
# ──────────────────────────────────────────────────────
|
|
56
|
+
app_path = resolve_app_path(params)
|
|
57
|
+
UI.user_error!(
|
|
58
|
+
"Please provide either :app_path or :xcarchive_path (with a single .app inside Products/Applications/)"
|
|
59
|
+
) unless app_path
|
|
60
|
+
UI.user_error!("App path does not exist: #{app_path}") unless File.directory?(app_path)
|
|
61
|
+
UI.user_error!(
|
|
62
|
+
"App path must end in .app: #{app_path}"
|
|
63
|
+
) unless app_path.end_with?('.app')
|
|
64
|
+
|
|
65
|
+
# ──────────────────────────────────────────────────────
|
|
66
|
+
# Cross-producer handshake
|
|
67
|
+
# ──────────────────────────────────────────────────────
|
|
68
|
+
# `artifact_upload` is the manifest action name the SDK
|
|
69
|
+
# BugseeAgent writes when it successfully shipped the IPA.
|
|
70
|
+
unless params[:force]
|
|
71
|
+
manifest = Fastlane::Bugsee::Handshake.find_manifest(
|
|
72
|
+
search_root: Dir.pwd,
|
|
73
|
+
version_name: version,
|
|
74
|
+
version_code: build,
|
|
75
|
+
)
|
|
76
|
+
if Fastlane::Bugsee::Handshake.handled_by_other?(manifest, 'artifact_upload')
|
|
77
|
+
UI.important(Fastlane::Bugsee::Handshake.skip_message(manifest, 'artifact_upload'))
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
UI.message("Bugsee: uploading artefact for #{File.basename(app_path)}")
|
|
83
|
+
|
|
84
|
+
# Shell command shape:
|
|
85
|
+
# python3 BugseeAgent -x \
|
|
86
|
+
# -e <host> [-v <ver>] [-b <build>] \
|
|
87
|
+
# --upload-artifact \
|
|
88
|
+
# --app-path <path> \
|
|
89
|
+
# <app_token>
|
|
90
|
+
cmd = []
|
|
91
|
+
cmd << agent_path.shellescape
|
|
92
|
+
cmd << "-x" # not run from Xcode — synchronous, no daemonize
|
|
93
|
+
cmd << "-e #{host.shellescape}"
|
|
94
|
+
cmd << "-v #{version.to_s.shellescape}" if version && !version.to_s.empty?
|
|
95
|
+
cmd << "-b #{build.to_s.shellescape}" if build && !build.to_s.empty?
|
|
96
|
+
cmd << "--upload-artifact"
|
|
97
|
+
cmd << "--app-path #{app_path.shellescape}"
|
|
98
|
+
# When build_info_only is true, skip the .ipa bytes upload —
|
|
99
|
+
# the registration POST still records artifact_size on the
|
|
100
|
+
# server so the dashboard's size-trend chart works, but the
|
|
101
|
+
# bytes never leave the build host. Useful for firewalled CI
|
|
102
|
+
# and privacy-sensitive setups.
|
|
103
|
+
cmd << "--build-info-only" if params[:build_info_only]
|
|
104
|
+
cmd << app_token.shellescape
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
Actions.sh(cmd.join(" "), log: false)
|
|
108
|
+
rescue => e
|
|
109
|
+
# Upload failure should NOT take the lane down — size
|
|
110
|
+
# analysis is a release-supporting feature, same posture as
|
|
111
|
+
# the other Bugsee fastlane actions.
|
|
112
|
+
UI.error(e.to_s)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @api private
|
|
117
|
+
# Resolves the `.app` path the user wants packaged. Public-ish
|
|
118
|
+
# so RSpec can exercise each branch in isolation.
|
|
119
|
+
def self.resolve_app_path(params)
|
|
120
|
+
explicit = params[:app_path]
|
|
121
|
+
return explicit if explicit && !explicit.to_s.empty?
|
|
122
|
+
|
|
123
|
+
archive_path = params[:xcarchive_path]
|
|
124
|
+
return nil unless archive_path && !archive_path.to_s.empty?
|
|
125
|
+
|
|
126
|
+
# An .xcarchive bundles the built `.app` at
|
|
127
|
+
# `<archive>/Products/Applications/<App>.app`. Usually exactly
|
|
128
|
+
# one `.app` is present; if multiple are present (extensions
|
|
129
|
+
# are bundled inside the main .app's Frameworks/ subtree, not
|
|
130
|
+
# a sibling at Products/Applications), we pick the only entry
|
|
131
|
+
# and error if ambiguous so the user can disambiguate via
|
|
132
|
+
# `:app_path`.
|
|
133
|
+
apps_dir = File.join(archive_path, 'Products', 'Applications')
|
|
134
|
+
return nil unless File.directory?(apps_dir)
|
|
135
|
+
|
|
136
|
+
matches = Dir.entries(apps_dir).select do |e|
|
|
137
|
+
e.end_with?('.app') && File.directory?(File.join(apps_dir, e))
|
|
138
|
+
end
|
|
139
|
+
if matches.length == 1
|
|
140
|
+
return File.join(apps_dir, matches.first)
|
|
141
|
+
elsif matches.length > 1
|
|
142
|
+
UI.user_error!(
|
|
143
|
+
"Multiple .app bundles found under #{apps_dir}; pass :app_path to disambiguate. Found: #{matches.inspect}"
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def self.description
|
|
150
|
+
"Upload an iOS build artefact (.app → synthetic .ipa) to Bugsee for size analysis."
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.details
|
|
154
|
+
<<~DETAILS
|
|
155
|
+
Packages a built `.app` into a byte-deterministic synthetic `.ipa`
|
|
156
|
+
and uploads it to Bugsee for size analysis. The Bugsee iOS SDK's
|
|
157
|
+
tools.bundle/BugseeAgent build phase already does this when
|
|
158
|
+
BUGSEE_BUILD_INFO_ENABLED is on (default); this action is for CI
|
|
159
|
+
pipelines that don't integrate that build phase, or for
|
|
160
|
+
split-build/publish setups where build and upload run on
|
|
161
|
+
different machines.
|
|
162
|
+
|
|
163
|
+
App path resolution: explicit :app_path > :xcarchive_path with a
|
|
164
|
+
single .app inside Products/Applications/.
|
|
165
|
+
|
|
166
|
+
Cross-producer handshake: if the SDK's BugseeAgent build phase
|
|
167
|
+
already uploaded this build's artefact (recorded in the
|
|
168
|
+
build-actions.json manifest), this action skips by default. Pass
|
|
169
|
+
force: true to override.
|
|
170
|
+
DETAILS
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def self.available_options
|
|
174
|
+
[
|
|
175
|
+
FastlaneCore::ConfigItem.new(key: :agent_path,
|
|
176
|
+
env_name: "BUGSEE_AGENT_PATH",
|
|
177
|
+
description: "The path to the BugseeAgent helper script",
|
|
178
|
+
optional: true,
|
|
179
|
+
verify_block: proc do |value|
|
|
180
|
+
UI.user_error!("Couldn't find BugseeAgent at path '#{value}'") unless File.exist?(value)
|
|
181
|
+
end),
|
|
182
|
+
FastlaneCore::ConfigItem.new(key: :host,
|
|
183
|
+
env_name: "BUGSEE_API_HOST",
|
|
184
|
+
description: "The path to API endpoint",
|
|
185
|
+
optional: true),
|
|
186
|
+
FastlaneCore::ConfigItem.new(key: :app_token,
|
|
187
|
+
env_name: "BUGSEE_APP_TOKEN",
|
|
188
|
+
description: "Bugsee iOS application token",
|
|
189
|
+
optional: false),
|
|
190
|
+
FastlaneCore::ConfigItem.new(key: :app_path,
|
|
191
|
+
env_name: "BUGSEE_APP_PATH",
|
|
192
|
+
description: "Path to the built `.app` directory. When unset, resolved from :xcarchive_path",
|
|
193
|
+
optional: true),
|
|
194
|
+
FastlaneCore::ConfigItem.new(key: :xcarchive_path,
|
|
195
|
+
env_name: "BUGSEE_XCARCHIVE_PATH",
|
|
196
|
+
description: "Path to the .xcarchive — used to resolve a single .app under Products/Applications/. Ignored when :app_path is set",
|
|
197
|
+
optional: true),
|
|
198
|
+
FastlaneCore::ConfigItem.new(key: :version,
|
|
199
|
+
env_name: "BUGSEE_APP_VERSION",
|
|
200
|
+
description: "CFBundleShortVersionString (e.g. \"1.2.3\"). Optional — read from Info.plist by BugseeAgent if absent",
|
|
201
|
+
optional: true),
|
|
202
|
+
FastlaneCore::ConfigItem.new(key: :build,
|
|
203
|
+
env_name: "BUGSEE_APP_BUILD",
|
|
204
|
+
description: "CFBundleVersion (e.g. \"42\"). Optional — read from Info.plist by BugseeAgent if absent",
|
|
205
|
+
optional: true),
|
|
206
|
+
FastlaneCore::ConfigItem.new(key: :build_info_only,
|
|
207
|
+
env_name: "BUGSEE_BUILD_INFO_ONLY",
|
|
208
|
+
description: "When true, register the build (records artifact_size for the size-trend chart) but skip shipping the .ipa bytes. Useful for firewalled CI and privacy-sensitive setups",
|
|
209
|
+
is_string: false,
|
|
210
|
+
default_value: false,
|
|
211
|
+
optional: true),
|
|
212
|
+
FastlaneCore::ConfigItem.new(key: :force,
|
|
213
|
+
env_name: "BUGSEE_FORCE",
|
|
214
|
+
description: "Skip the cross-producer handshake check and always upload, even if the Bugsee iOS SDK's BugseeAgent build phase already handled this build's artefact",
|
|
215
|
+
is_string: false,
|
|
216
|
+
default_value: false,
|
|
217
|
+
optional: true)
|
|
218
|
+
]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def self.authors
|
|
222
|
+
["bugsee"]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def self.is_supported?(platform)
|
|
226
|
+
platform == :ios
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
require 'fastlane/plugin/bugsee/helper/bugsee_uuid'
|
|
2
|
+
require 'fastlane/plugin/bugsee/helper/bugsee_handshake'
|
|
3
|
+
|
|
4
|
+
module Fastlane
|
|
5
|
+
module Actions
|
|
6
|
+
# Upload an Android ProGuard / R8 mapping.txt to Bugsee.
|
|
7
|
+
#
|
|
8
|
+
# When and why to use this:
|
|
9
|
+
#
|
|
10
|
+
# The Bugsee Android Gradle plugin already uploads mapping.txt
|
|
11
|
+
# as part of the standard gradle build. The most common
|
|
12
|
+
# scenario for this fastlane action is CI pipelines that split
|
|
13
|
+
# build (machine A, no token) from publish (machine B, has the
|
|
14
|
+
# production token) — gradle builds the APK on A; fastlane on
|
|
15
|
+
# B uploads the mapping with the production token. The action
|
|
16
|
+
# ALSO works for apps not instrumented by the Bugsee Gradle
|
|
17
|
+
# plugin, but only when paired with Bugsee Android SDK
|
|
18
|
+
# 7.0.0-beta13+ (older SDKs cannot match crashes against a
|
|
19
|
+
# synthesized UUID).
|
|
20
|
+
#
|
|
21
|
+
# UUID resolution chain (from most authoritative to fallback):
|
|
22
|
+
#
|
|
23
|
+
# 1. The :uuid ConfigItem when passed explicitly. Escape hatch.
|
|
24
|
+
# 2. The Bugsee Gradle plugin's BugseeBuildIdResolveTask
|
|
25
|
+
# output file (build/intermediates/bugsee/<variant>/
|
|
26
|
+
# build-uuid.txt). When this file is present, the Gradle
|
|
27
|
+
# plugin IS in the loop and the UUID it computed is the
|
|
28
|
+
# authoritative value that the SDK's asset / manifest
|
|
29
|
+
# channels carry at runtime.
|
|
30
|
+
# 3. Ruby-side synthesis matching the SDK's Channel 3
|
|
31
|
+
# fallback formula:
|
|
32
|
+
# UUID.nameUUIDFromBytes(
|
|
33
|
+
# app_token + 0x1F + version + 0x1F + build
|
|
34
|
+
# )
|
|
35
|
+
# Only useful when the host app uses SDK 7.0.0-beta13+
|
|
36
|
+
# AND has no Gradle plugin in the loop.
|
|
37
|
+
#
|
|
38
|
+
# All three branches produce a UUID the SDK can independently
|
|
39
|
+
# reproduce at runtime, so the server-side mapping lookup
|
|
40
|
+
# resolves crashes correctly regardless of which branch fired.
|
|
41
|
+
class UploadMappingToBugseeAction < Action
|
|
42
|
+
BUGSEE_AGENT_PATH = File.expand_path(
|
|
43
|
+
File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'BugseeAgent'))
|
|
44
|
+
|
|
45
|
+
# Glob pattern for the Bugsee Gradle plugin's
|
|
46
|
+
# BugseeBuildIdResolveTask output. Used by the UUID resolution
|
|
47
|
+
# chain when :build_uuid_path isn't explicit. The leading
|
|
48
|
+
# `**/` covers multi-module projects where the `app/` module
|
|
49
|
+
# nests under a top-level checkout.
|
|
50
|
+
BUILD_UUID_GLOB = "**/build/intermediates/bugsee/*/build-uuid.txt".freeze
|
|
51
|
+
|
|
52
|
+
def self.run(params)
|
|
53
|
+
app_token = params[:app_token]
|
|
54
|
+
mapping_path = params[:mapping_path]
|
|
55
|
+
host = params[:host] || "https://api.bugsee.com"
|
|
56
|
+
version = params[:version]
|
|
57
|
+
build = params[:build]
|
|
58
|
+
icon_path = params[:icon_path]
|
|
59
|
+
agent_path = params[:agent_path] || BUGSEE_AGENT_PATH
|
|
60
|
+
|
|
61
|
+
UI.user_error!("Please provide an app token via app_token:") unless app_token
|
|
62
|
+
UI.user_error!("Please provide a path to the Android mapping.txt via mapping_path:") unless mapping_path
|
|
63
|
+
UI.user_error!("Mapping file does not exist: #{mapping_path}") unless File.exist?(mapping_path)
|
|
64
|
+
UI.user_error!("Please provide an app version via version: (android:versionName)") if version.nil? || version.to_s.empty?
|
|
65
|
+
UI.user_error!("Please provide a build number via build: (android:versionCode)") if build.nil? || build.to_s.empty?
|
|
66
|
+
|
|
67
|
+
agent_path = File.expand_path(agent_path)
|
|
68
|
+
UI.user_error!("BugseeAgent helper script is missing: #{agent_path}") unless File.exist?(agent_path)
|
|
69
|
+
|
|
70
|
+
if icon_path && !File.exist?(icon_path)
|
|
71
|
+
UI.important("Bugsee: icon_path does not exist (#{icon_path}); proceeding without icon")
|
|
72
|
+
icon_path = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Cross-producer handshake: if the Bugsee Android Gradle
|
|
76
|
+
# plugin already uploaded this build's mapping, skip — the
|
|
77
|
+
# server would just dedupe by hash but the lane log gets
|
|
78
|
+
# noisy and CI pays for redundant bandwidth. The user can
|
|
79
|
+
# override with `force: true` for re-uploads / debugging.
|
|
80
|
+
unless params[:force]
|
|
81
|
+
manifest = Fastlane::Bugsee::Handshake.find_manifest(
|
|
82
|
+
search_root: Dir.pwd,
|
|
83
|
+
version_name: version,
|
|
84
|
+
version_code: build,
|
|
85
|
+
)
|
|
86
|
+
if Fastlane::Bugsee::Handshake.handled_by_other?(manifest, 'mapping_upload')
|
|
87
|
+
UI.important(Fastlane::Bugsee::Handshake.skip_message(manifest, 'mapping_upload'))
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
uuid = resolve_uuid(params, app_token, version, build)
|
|
93
|
+
UI.message("Bugsee: uploading mapping with UUID #{uuid}")
|
|
94
|
+
|
|
95
|
+
# Shell command shape:
|
|
96
|
+
# python3 BugseeAgent -x \
|
|
97
|
+
# -e <host> -v <version> -b <build> \
|
|
98
|
+
# --upload-mapping \
|
|
99
|
+
# --mapping-path <mapping.txt> \
|
|
100
|
+
# --mapping-uuid <uuid> \
|
|
101
|
+
# [--icon <icon>] \
|
|
102
|
+
# [--cli-path <path> | --cli-version <ver>] \
|
|
103
|
+
# <app_token>
|
|
104
|
+
cmd = []
|
|
105
|
+
cmd << agent_path.shellescape
|
|
106
|
+
cmd << "-x" # not run from Xcode — synchronous, no daemonize
|
|
107
|
+
cmd << "-e #{host.shellescape}"
|
|
108
|
+
cmd << "-v #{version.to_s.shellescape}"
|
|
109
|
+
cmd << "-b #{build.to_s.shellescape}"
|
|
110
|
+
cmd << "--upload-mapping"
|
|
111
|
+
cmd << "--mapping-path #{mapping_path.shellescape}"
|
|
112
|
+
cmd << "--mapping-uuid #{uuid.shellescape}"
|
|
113
|
+
cmd << "--icon #{icon_path.shellescape}" if icon_path
|
|
114
|
+
cmd << "--cli-path #{params[:cli_path].shellescape}" if params[:cli_path]
|
|
115
|
+
cmd << "--cli-version #{params[:cli_version].shellescape}" if params[:cli_version]
|
|
116
|
+
cmd << app_token.shellescape
|
|
117
|
+
|
|
118
|
+
begin
|
|
119
|
+
Actions.sh(cmd.join(" "), log: false)
|
|
120
|
+
rescue => e
|
|
121
|
+
# Upload failure should NOT take the lane down — the
|
|
122
|
+
# symbols upload is a release-supporting nicety, same
|
|
123
|
+
# posture as upload_symbols_to_bugsee. The Ruby UI.error
|
|
124
|
+
# surfaces the cause in the build log.
|
|
125
|
+
UI.error(e.to_s)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @api private
|
|
130
|
+
# Resolution chain implemented as documented in the class
|
|
131
|
+
# docstring above. Public-ish so the RSpec tests can exercise
|
|
132
|
+
# each branch in isolation.
|
|
133
|
+
def self.resolve_uuid(params, app_token, version, build)
|
|
134
|
+
explicit = params[:uuid]
|
|
135
|
+
return explicit if explicit && !explicit.to_s.empty?
|
|
136
|
+
|
|
137
|
+
# Branch 2: Gradle plugin's BugseeBuildIdResolveTask output.
|
|
138
|
+
# When :build_uuid_path is explicit, trust it. Otherwise glob
|
|
139
|
+
# under the current working directory (most fastlane lanes
|
|
140
|
+
# cd to the project root before invoking actions).
|
|
141
|
+
build_uuid_path = params[:build_uuid_path]
|
|
142
|
+
if build_uuid_path
|
|
143
|
+
if File.exist?(build_uuid_path)
|
|
144
|
+
return File.read(build_uuid_path).strip
|
|
145
|
+
else
|
|
146
|
+
UI.important("Bugsee: build_uuid_path given but not found: #{build_uuid_path}")
|
|
147
|
+
end
|
|
148
|
+
else
|
|
149
|
+
# Best-effort glob. Take the first match — multi-variant
|
|
150
|
+
# projects may have several; if the lane wants a specific
|
|
151
|
+
# variant, it should pass :build_uuid_path explicitly.
|
|
152
|
+
match = Dir.glob(BUILD_UUID_GLOB).first
|
|
153
|
+
return File.read(match).strip if match
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Branch 3: synthesize from (app_token, version, build).
|
|
157
|
+
# Matches the SDK's Channel 3 fallback formula byte-for-byte
|
|
158
|
+
# so a 7.0.0-beta13+ SDK at runtime computes the same UUID
|
|
159
|
+
# and the upload matches.
|
|
160
|
+
UI.message("Bugsee: no Gradle plugin build-uuid.txt found; " \
|
|
161
|
+
"synthesizing UUID from app_token+version+build. " \
|
|
162
|
+
"This requires Bugsee Android SDK >= 7.0.0-beta13 " \
|
|
163
|
+
"at runtime.")
|
|
164
|
+
Fastlane::Bugsee::Uuid.synthesize_build_uuid(app_token, version, build)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def self.description
|
|
168
|
+
"Upload an Android ProGuard / R8 mapping.txt to Bugsee."
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.details
|
|
172
|
+
<<~DETAILS
|
|
173
|
+
Uploads an Android ProGuard / R8 mapping.txt to Bugsee for crash
|
|
174
|
+
symbolication. The Bugsee Android Gradle plugin already does this
|
|
175
|
+
as part of the standard gradle build; this action is for split
|
|
176
|
+
build/publish CI pipelines and for apps not instrumented by the
|
|
177
|
+
Gradle plugin (the latter requires Bugsee Android SDK 7.0.0-beta13+).
|
|
178
|
+
|
|
179
|
+
UUID resolution: explicit :uuid > :build_uuid_path > the Gradle
|
|
180
|
+
plugin's build-uuid.txt (auto-globbed) > synthesized from
|
|
181
|
+
(app_token, version, build) matching the SDK's runtime fallback.
|
|
182
|
+
DETAILS
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def self.available_options
|
|
186
|
+
[
|
|
187
|
+
FastlaneCore::ConfigItem.new(key: :agent_path,
|
|
188
|
+
env_name: "BUGSEE_AGENT_PATH",
|
|
189
|
+
description: "The path to the BugseeAgent helper script",
|
|
190
|
+
optional: true,
|
|
191
|
+
verify_block: proc do |value|
|
|
192
|
+
UI.user_error!("Couldn't find BugseeAgent at path '#{value}'") unless File.exist?(value)
|
|
193
|
+
end),
|
|
194
|
+
FastlaneCore::ConfigItem.new(key: :host,
|
|
195
|
+
env_name: "BUGSEE_API_HOST",
|
|
196
|
+
description: "The path to API endpoint",
|
|
197
|
+
optional: true),
|
|
198
|
+
FastlaneCore::ConfigItem.new(key: :app_token,
|
|
199
|
+
env_name: "BUGSEE_APP_TOKEN",
|
|
200
|
+
description: "Bugsee Android application token",
|
|
201
|
+
optional: false),
|
|
202
|
+
FastlaneCore::ConfigItem.new(key: :mapping_path,
|
|
203
|
+
env_name: "BUGSEE_MAPPING_PATH",
|
|
204
|
+
description: "Path to the Android mapping.txt (R8 / ProGuard)",
|
|
205
|
+
optional: false),
|
|
206
|
+
# Fastlane's Android `gradle` action publishes no
|
|
207
|
+
# SharedValues equivalent of iOS's VERSION_NUMBER /
|
|
208
|
+
# BUILD_NUMBER — :version / :build remain explicit
|
|
209
|
+
# ConfigItems with no auto-default. The action validates
|
|
210
|
+
# both in .run and raises a UI.user_error if missing.
|
|
211
|
+
# Consumers typically wire these from get_version_name /
|
|
212
|
+
# get_version_code (community actions) or read them from
|
|
213
|
+
# build.gradle themselves before calling this action.
|
|
214
|
+
FastlaneCore::ConfigItem.new(key: :version,
|
|
215
|
+
env_name: "BUGSEE_APP_VERSION",
|
|
216
|
+
description: "android:versionName (e.g. \"1.2.3\")",
|
|
217
|
+
optional: true),
|
|
218
|
+
FastlaneCore::ConfigItem.new(key: :build,
|
|
219
|
+
env_name: "BUGSEE_APP_BUILD",
|
|
220
|
+
description: "android:versionCode (e.g. \"42\")",
|
|
221
|
+
optional: true),
|
|
222
|
+
FastlaneCore::ConfigItem.new(key: :uuid,
|
|
223
|
+
env_name: "BUGSEE_BUILD_UUID",
|
|
224
|
+
description: "Override BUILD_UUID. When unset, the action reads the Bugsee Gradle plugin's build-uuid.txt OR synthesizes via MD5",
|
|
225
|
+
optional: true),
|
|
226
|
+
FastlaneCore::ConfigItem.new(key: :build_uuid_path,
|
|
227
|
+
env_name: "BUGSEE_BUILD_UUID_PATH",
|
|
228
|
+
description: "Explicit path to the Bugsee Gradle plugin's build-uuid.txt. When unset, the action globs **/build/intermediates/bugsee/*/build-uuid.txt",
|
|
229
|
+
optional: true),
|
|
230
|
+
FastlaneCore::ConfigItem.new(key: :icon_path,
|
|
231
|
+
env_name: "BUGSEE_ICON_PATH",
|
|
232
|
+
description: "Optional launcher icon PNG to attach to the symbol record",
|
|
233
|
+
optional: true),
|
|
234
|
+
FastlaneCore::ConfigItem.new(key: :cli_path,
|
|
235
|
+
env_name: "BUGSEE_CLI_PATH",
|
|
236
|
+
description: "Path to a local bugsee-cli binary (developer override)",
|
|
237
|
+
optional: true),
|
|
238
|
+
FastlaneCore::ConfigItem.new(key: :cli_version,
|
|
239
|
+
env_name: "BUGSEE_CLI_VERSION",
|
|
240
|
+
description: "bugsee-cli version to auto-download",
|
|
241
|
+
optional: true),
|
|
242
|
+
FastlaneCore::ConfigItem.new(key: :force,
|
|
243
|
+
env_name: "BUGSEE_FORCE",
|
|
244
|
+
description: "Skip the cross-producer handshake check and always upload, even if the Bugsee Android Gradle plugin already handled this build's mapping",
|
|
245
|
+
is_string: false,
|
|
246
|
+
default_value: false,
|
|
247
|
+
optional: true)
|
|
248
|
+
]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def self.authors
|
|
252
|
+
["bugsee"]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def self.is_supported?(platform)
|
|
256
|
+
platform == :android
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|