Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
outputs:
proceed: ${{ steps.check.outputs.proceed }}
is_prerelease: ${{ steps.check.outputs.is_prerelease }}
sentry_environment: ${{ steps.check.outputs.sentry_environment }}
steps:
- name: Inspect tag
id: check
Expand All @@ -48,19 +49,28 @@ jobs:
# Strict shape: vX.Y.Z with all parts numeric, no suffix.
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::notice::Tag $TAG is not a plain vX.Y.Z tag; skipping release."
echo "proceed=false" >> $GITHUB_OUTPUT
echo "is_prerelease=false" >> $GITHUB_OUTPUT
{
echo "proceed=false"
echo "is_prerelease=false"
echo "sentry_environment=internal"
} >> "$GITHUB_OUTPUT"
exit 0
fi
PATCH=$(echo "$TAG" | cut -d. -f3)
if [ "$PATCH" -eq 0 ]; then
echo "::notice::Tag $TAG has PATCH=0; running production-candidate release."
echo "proceed=true" >> $GITHUB_OUTPUT
echo "is_prerelease=false" >> $GITHUB_OUTPUT
{
echo "proceed=true"
echo "is_prerelease=false"
echo "sentry_environment=production"
} >> "$GITHUB_OUTPUT"
else
echo "::notice::Tag $TAG has PATCH=$PATCH; running internal release."
echo "proceed=true" >> $GITHUB_OUTPUT
echo "is_prerelease=true" >> $GITHUB_OUTPUT
{
echo "proceed=true"
echo "is_prerelease=true"
echo "sentry_environment=internal"
} >> "$GITHUB_OUTPUT"
fi

store-metadata-preflight:
Expand Down Expand Up @@ -172,6 +182,9 @@ jobs:
working-directory: android
env:
NEW_VERSION: ${{ github.ref_name }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_ENVIRONMENT: ${{ needs.guard.outputs.sentry_environment }}

- name: Upload APK artifact
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -269,6 +282,9 @@ jobs:
working-directory: ios
env:
NEW_VERSION: ${{ github.ref_name }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_ENVIRONMENT: ${{ needs.guard.outputs.sentry_environment }}

- name: Upload IPA artifact
uses: actions/upload-artifact@v4
Expand Down
104 changes: 83 additions & 21 deletions android/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,51 @@
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

require "json"
require "tempfile"

default_platform(:android)

# Keeps the DSN out of the process arguments Fastlane prints. Flutter reads
# both compile-time values from this short-lived, mode-0600 JSON file.
def with_crash_reporting_defines
dsn = ENV.fetch("SENTRY_DSN", "")
UI.user_error!("SENTRY_DSN is required for store release builds") if dsn.empty?

environment = ENV.fetch("SENTRY_ENVIRONMENT", "production")
file = Tempfile.new(["sentry-defines", ".json"])
File.chmod(0o600, file.path)
file.write(JSON.generate({
"SENTRY_DSN" => dsn,
"SENTRY_ENVIRONMENT" => environment,
}))
file.flush
yield file.path
ensure
file&.close!
end

def upload_sentry_symbols(marketing_version:, version_code:, symbols_path:, dart_symbol_map_path:)
ENV["SENTRY_RELEASE"] = "swiss.realunit.app@#{marketing_version}+#{version_code}"
ENV["SENTRY_DIST"] = version_code.to_s
Dir.chdir("../..") do
# The plugin only warns and continues on missing input, which would ship
# a release with degraded (or no) crash symbolication silently. Fail the
# build instead.
if Dir.glob("#{symbols_path}/**/*").select { |f| File.file?(f) }.empty?
UI.user_error!("Sentry upload: no native debug symbols found under #{symbols_path}")
end
unless File.exist?(dart_symbol_map_path)
UI.user_error!("Sentry upload: Dart obfuscation map not found at #{dart_symbol_map_path}")
end

sh("dart", "run", "sentry_dart_plugin",
"--sentry-define=symbols_path=#{symbols_path}",
"--sentry-define=dart_symbol_map_path=#{dart_symbol_map_path}"
)
end
end

# Runs tool/generate_release_info.dart (single source of truth for tag →
# marketing version + version code) and reads the values back from the
# generated Dart file. The generated file is the canonical artefact —
Expand Down Expand Up @@ -61,29 +104,48 @@ platform :android do
marketing_version = info["marketing_version"]
version_code = info["version_code"]

Dir.chdir("..") do
sh("flutter", "build", "appbundle",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info"
)
sh("flutter", "build", "apk",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info"
)
File.rename(
"../build/app/outputs/flutter-apk/app-release.apk",
"../build/app/outputs/flutter-apk/realunit-#{tag_name}.apk"
)
with_crash_reporting_defines do |defines_file|
Dir.chdir("..") do
sh("flutter", "build", "appbundle",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--dart-define-from-file=#{defines_file}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info/appbundle",
"--extra-gen-snapshot-options=--save-obfuscation-map=build/appbundle-obfuscation-map.json"
)
sh("flutter", "build", "apk",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--dart-define-from-file=#{defines_file}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info/apk",
"--extra-gen-snapshot-options=--save-obfuscation-map=build/apk-obfuscation-map.json"
)
File.rename(
"../build/app/outputs/flutter-apk/app-release.apk",
"../build/app/outputs/flutter-apk/realunit-#{tag_name}.apk"
)
end
end

upload_sentry_symbols(
marketing_version: marketing_version,
version_code: version_code,
symbols_path: "build/debug_info/appbundle",
dart_symbol_map_path: "build/appbundle-obfuscation-map.json"
)
upload_sentry_symbols(
marketing_version: marketing_version,
version_code: version_code,
symbols_path: "build/debug_info/apk",
dart_symbol_map_path: "build/apk-obfuscation-map.json"
)

# Upload the binary + changelog only. The changelog is tied to this
# build's version code (taken from the AAB); the listing metadata and
# images are pushed by the dedicated call below, keeping the two
Expand Down
67 changes: 60 additions & 7 deletions ios/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,45 @@
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

require "json"
require "tempfile"

default_platform(:ios)

# Keeps the DSN out of the process arguments Fastlane prints. Flutter writes
# the encoded defines into Generated.xcconfig before gym invokes xcodebuild.
def with_crash_reporting_defines
dsn = ENV.fetch("SENTRY_DSN", "")
UI.user_error!("SENTRY_DSN is required for store release builds") if dsn.empty?

environment = ENV.fetch("SENTRY_ENVIRONMENT", "production")
file = Tempfile.new(["sentry-defines", ".json"])
File.chmod(0o600, file.path)
file.write(JSON.generate({
"SENTRY_DSN" => dsn,
"SENTRY_ENVIRONMENT" => environment,
}))
file.flush
yield file.path
ensure
file&.close!
end

def upload_sentry_symbols(marketing_version:, version_code:)
ENV["SENTRY_RELEASE"] = "swiss.realunit.app@#{marketing_version}+#{version_code}"
ENV["SENTRY_DIST"] = version_code.to_s
Dir.chdir("../..") do
# The plugin only warns and continues on missing input, which would ship
# a release with degraded (or no) crash symbolication silently. Fail the
# build instead.
if Dir.glob("build/ios/archive/**/*.dSYM").empty?
UI.user_error!("Sentry upload: no dSYMs found under build/ios/archive")
end

sh("dart", "run", "sentry_dart_plugin")
end
end

def get_api_key
app_store_connect_api_key(
key_id: ENV['FASTLANE_APPLE_API_KEY_ID'],
Expand Down Expand Up @@ -110,13 +147,29 @@ platform :ios do
xcodeproj: "Runner.xcodeproj"
)

gym(
workspace: "Runner.xcworkspace",
scheme: "Runner",
output_name: "realunit-#{tag_name}.ipa",
xcargs: "-verbose",
verbose: true
)
with_crash_reporting_defines do |defines_file|
Dir.chdir("../..") do
sh("flutter", "build", "ios",
"--config-only",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--dart-define-from-file=#{defines_file}"
)
end
gym(
workspace: "Runner.xcworkspace",
scheme: "Runner",
archive_path: File.expand_path("../../build/ios/archive/Runner.xcarchive", __dir__),
output_name: "realunit-#{tag_name}.ipa",
xcargs: "-verbose",
verbose: true
)
upload_sentry_symbols(
marketing_version: marketing_version,
version_code: version_code
)
end

upload_to_testflight(
api_key: get_api_key,
Expand Down
48 changes: 48 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.3"
globbing:
dependency: transitive
description:
name: globbing
sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
go_router:
dependency: "direct main"
description:
Expand Down Expand Up @@ -791,6 +799,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.2"
injector:
dependency: transitive
description:
name: injector
sha256: ed389bed5b48a699d5b9561c985023d0d5cc88dd5ff2237aadcce5a5ab433e4e
url: "https://pub.dev"
source: hosted
version: "3.0.0"
intl:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1183,6 +1199,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.3"
process:
dependency: transitive
description:
name: process
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
url: "https://pub.dev"
source: hosted
version: "5.0.5"
properties:
dependency: transitive
description:
name: properties
sha256: "333f427dd4ed07bdbe8c75b9ff864a1e70b5d7a8426a2e8bdd457b65ae5ac598"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
provider:
dependency: transitive
description:
Expand Down Expand Up @@ -1239,6 +1271,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sentry_dart_plugin:
dependency: "direct dev"
description:
name: sentry_dart_plugin
sha256: da9c1d0b3c87a251bfc36301f16af090a88c2d59128fe9a6f908f5ac20340c97
url: "https://pub.dev"
source: hosted
version: "3.4.0"
shared_preferences:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1412,6 +1452,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
system_info2:
dependency: transitive
description:
name: system_info2
sha256: b937736ecfa63c45b10dde1ceb6bb30e5c0c340e14c441df024150679d65ac43
url: "https://pub.dev"
source: hosted
version: "4.1.0"
term_glyph:
dependency: transitive
description:
Expand Down
12 changes: 12 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ dev_dependencies:
# Used by test/packages/config/legal_documents_config_test.dart to swap in a
# recording fake for url_launcher without touching the platform channel.
plugin_platform_interface: ^2.1.8
sentry_dart_plugin: 3.4.0
url_launcher_platform_interface: ^2.3.2

dependency_overrides:
Expand All @@ -111,6 +112,17 @@ dependency_overrides:
url: https://github.com/cake-tech/web3dart.git
ref: aa3f932dbf54eda651b7bd01ad00204acf998bd0

sentry:
upload_debug_symbols: true
upload_source_maps: false
upload_sources: false
project: realunitchapp
org: sentry
url: https://sentry.dfxserve.com/
wait_for_processing: true
log_level: error
commits: false

hooks:
user_defines:
sqlite3:
Expand Down
Loading