refactor(symbols): share upload flow

This commit is contained in:
edde746
2026-07-12 08:42:23 +02:00
parent 0135318ded
commit a0f153e5ab
4 changed files with 314 additions and 229 deletions
+2 -115
View File
@@ -16,124 +16,11 @@ $Root = Split-Path -Parent $ScriptDir
Set-Location $Root
if ([string]::IsNullOrEmpty($SourceRoot)) {
$SearchRoot = $Root
}
elseif ([System.IO.Path]::IsPathRooted($SourceRoot)) {
$SearchRoot = $SourceRoot
& dart run scripts/upload_symbols.dart $Platform
}
else {
$SearchRoot = [System.IO.Path]::GetFullPath((Join-Path $Root $SourceRoot))
& dart run scripts/upload_symbols.dart $Platform $SourceRoot
}
$BuildRoot = Join-Path $SearchRoot 'build'
$SymbolRoot = Join-Path (Join-Path $SearchRoot 'debug-info') $Platform
$DryRun = -not [string]::IsNullOrEmpty($env:BUGS_UPLOAD_DRY_RUN)
$SearchRoots = [System.Collections.Generic.List[string]]::new()
function Add-ExistingRoot([string]$Path) {
if (Test-Path -Path $Path -PathType Container) {
$SearchRoots.Add($Path)
}
}
Add-ExistingRoot $SymbolRoot
switch ($Platform) {
{ $_ -eq 'windows-x64' -or $_ -eq 'windows-arm64' } {
Add-ExistingRoot (Join-Path $BuildRoot 'windows')
}
default {
Write-Error "unknown platform: $Platform"
exit 2
}
}
function Has-SymbolFile {
foreach ($RootPath in $SearchRoots) {
$First = Get-ChildItem -Path $RootPath -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
if ($null -ne $First) {
return $true
}
}
return $false
}
if (-not (Has-SymbolFile)) {
Write-Error "no symbols found for platform $Platform"
exit 3
}
if ([string]::IsNullOrEmpty($env:SENTRY_URL)) {
$env:SENTRY_URL = if ([string]::IsNullOrEmpty($env:BUGS_URL)) { 'https://bugs.plezy.app' } else { $env:BUGS_URL }
}
if ([string]::IsNullOrEmpty($env:SENTRY_RELEASE)) {
$env:SENTRY_RELEASE = "plezy@$(git rev-parse --short HEAD)"
}
if ([string]::IsNullOrEmpty($env:SENTRY_LOG_LEVEL)) {
$env:SENTRY_LOG_LEVEL = 'info'
}
if ([string]::IsNullOrEmpty($env:SENTRY_AUTH_TOKEN) -and -not [string]::IsNullOrEmpty($env:BUGS_ADMIN_TOKEN)) {
$env:SENTRY_AUTH_TOKEN = $env:BUGS_ADMIN_TOKEN
}
if (-not $DryRun -and [string]::IsNullOrEmpty($env:SENTRY_AUTH_TOKEN)) {
Write-Error 'SENTRY_AUTH_TOKEN or BUGS_ADMIN_TOKEN env var required'
exit 1
}
$DartSymbolMapPath = $env:SENTRY_DART_SYMBOL_MAP_PATH
if ([string]::IsNullOrEmpty($DartSymbolMapPath)) {
$Candidates = @(
(Join-Path $SymbolRoot 'obfuscation.map.json'),
(Join-Path (Join-Path $BuildRoot 'app\obfuscation') "$Platform.map.json"),
(Join-Path (Join-Path $BuildRoot 'app') 'obfuscation.map.json')
)
foreach ($Candidate in $Candidates) {
if (Test-Path -Path $Candidate -PathType Leaf) {
$DartSymbolMapPath = $Candidate
break
}
}
}
$PluginArgs = @(
"--sentry-define=release=$($env:SENTRY_RELEASE)",
"--sentry-define=url=$($env:SENTRY_URL)",
"--sentry-define=build_path=$BuildRoot"
)
if (-not [string]::IsNullOrEmpty($env:SENTRY_DIST)) {
$PluginArgs += "--sentry-define=dist=$($env:SENTRY_DIST)"
}
if (Test-Path -Path $SymbolRoot -PathType Container) {
$PluginArgs += "--sentry-define=symbols_path=$SymbolRoot"
}
if (-not [string]::IsNullOrEmpty($DartSymbolMapPath)) {
$PluginArgs += "--sentry-define=dart_symbol_map_path=$DartSymbolMapPath"
}
if ($DryRun) {
Write-Host "dry-run: would upload symbols for $Platform"
Write-Host "dry-run: release=$($env:SENTRY_RELEASE)"
Write-Host "dry-run: dist=$($env:SENTRY_DIST)"
Write-Host "dry-run: source_root=$SearchRoot"
Write-Host "dry-run: build_path=$BuildRoot"
Write-Host "dry-run: symbols_path=$SymbolRoot"
Write-Host "dry-run: dart_symbol_map_path=$DartSymbolMapPath"
foreach ($RootPath in $SearchRoots) {
Get-ChildItem -Path $RootPath -File -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Write-Host $_.FullName }
}
exit 0
}
Write-Host "uploading symbols for $Platform release $($env:SENTRY_RELEASE) dist $($env:SENTRY_DIST)"
& dart run sentry_dart_plugin @PluginArgs
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
+2 -114
View File
@@ -5,121 +5,9 @@
# Platforms: macos | ios | android-apk | android-aab | linux-x64 | linux-arm64
set -euo pipefail
PLATFORM="${1:?platform arg required}"
DRY_RUN="${BUGS_UPLOAD_DRY_RUN:-}"
: "${1:?platform arg required}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$ROOT"
SOURCE_ROOT="${2:-$ROOT}"
if [[ "$SOURCE_ROOT" != /* ]]; then
SOURCE_ROOT="$ROOT/$SOURCE_ROOT"
fi
BUILD_ROOT="$SOURCE_ROOT/build"
SYMBOL_ROOT="$SOURCE_ROOT/debug-info/$PLATFORM"
DART_SYMBOL_MAP_PATH="${SENTRY_DART_SYMBOL_MAP_PATH:-}"
if [ -z "$DART_SYMBOL_MAP_PATH" ]; then
for candidate in \
"$SYMBOL_ROOT/obfuscation.map.json" \
"$BUILD_ROOT/app/obfuscation/$PLATFORM.map.json" \
"$BUILD_ROOT/app/obfuscation.map.json"; do
if [ -f "$candidate" ]; then
DART_SYMBOL_MAP_PATH="$candidate"
break
fi
done
fi
SEARCH_ROOTS=()
add_existing_root() {
if [ -d "$1" ]; then
SEARCH_ROOTS+=("$1")
fi
}
add_existing_root "$SYMBOL_ROOT"
case "$PLATFORM" in
macos)
add_existing_root "$BUILD_ROOT/macos"
;;
ios)
add_existing_root "$BUILD_ROOT/ios"
add_existing_root "$SOURCE_ROOT/ios/build"
;;
linux-x64|linux-arm64)
add_existing_root "$BUILD_ROOT/linux"
;;
android-apk|android-aab)
add_existing_root "$BUILD_ROOT/app"
;;
*)
echo "unknown platform: $PLATFORM" >&2
exit 2
;;
esac
found_symbol_file() {
local first
if [ "${#SEARCH_ROOTS[@]}" -eq 0 ]; then
return 1
fi
first="$(find "${SEARCH_ROOTS[@]}" -type f -print -quit 2>/dev/null || true)"
[ -n "$first" ]
}
if ! found_symbol_file; then
echo "no symbols found for platform ${PLATFORM}" >&2
exit 3
fi
export SENTRY_URL="${SENTRY_URL:-${BUGS_URL:-https://bugs.plezy.app}}"
export SENTRY_RELEASE="${SENTRY_RELEASE:-plezy@$(git rev-parse --short HEAD)}"
export SENTRY_LOG_LEVEL="${SENTRY_LOG_LEVEL:-info}"
if [ -z "${SENTRY_AUTH_TOKEN:-}" ] && [ -n "${BUGS_ADMIN_TOKEN:-}" ]; then
export SENTRY_AUTH_TOKEN="$BUGS_ADMIN_TOKEN"
fi
if [ -z "$DRY_RUN" ] && [ -z "${SENTRY_AUTH_TOKEN:-}" ]; then
echo "SENTRY_AUTH_TOKEN or BUGS_ADMIN_TOKEN env var required" >&2
exit 1
fi
PLUGIN_ARGS=(
"--sentry-define=release=${SENTRY_RELEASE}"
"--sentry-define=url=${SENTRY_URL}"
"--sentry-define=build_path=${BUILD_ROOT}"
)
if [ -n "${SENTRY_DIST:-}" ]; then
PLUGIN_ARGS+=("--sentry-define=dist=${SENTRY_DIST}")
fi
if [ -d "$SYMBOL_ROOT" ]; then
PLUGIN_ARGS+=("--sentry-define=symbols_path=${SYMBOL_ROOT}")
fi
if [ -n "$DART_SYMBOL_MAP_PATH" ]; then
PLUGIN_ARGS+=("--sentry-define=dart_symbol_map_path=${DART_SYMBOL_MAP_PATH}")
fi
if [ -n "$DRY_RUN" ]; then
echo "dry-run: would upload symbols for ${PLATFORM}"
echo "dry-run: release=${SENTRY_RELEASE}"
echo "dry-run: dist=${SENTRY_DIST:-}"
echo "dry-run: source_root=${SOURCE_ROOT}"
echo "dry-run: build_path=${BUILD_ROOT}"
echo "dry-run: symbols_path=${SYMBOL_ROOT}"
echo "dry-run: dart_symbol_map_path=${DART_SYMBOL_MAP_PATH}"
find "${SEARCH_ROOTS[@]}" -type f -print 2>/dev/null || true
exit 0
fi
echo "uploading symbols for ${PLATFORM} release ${SENTRY_RELEASE} dist ${SENTRY_DIST:-}"
dart run sentry_dart_plugin "${PLUGIN_ARGS[@]}"
exec dart run scripts/upload_symbols.dart "$@"
+203
View File
@@ -0,0 +1,203 @@
import 'dart:io';
import 'package:path/path.dart' as path;
typedef SymbolUploader =
Future<int> Function({
required List<String> arguments,
required Map<String, String> environment,
required String workingDirectory,
});
const _defaultSentryUrl = 'https://bugs.plezy.app';
Future<void> main(List<String> arguments) async {
final scriptDirectory = File.fromUri(Platform.script).parent;
final result = await runUploadSymbols(arguments, repositoryRoot: scriptDirectory.parent);
if (result != 0) {
exitCode = result;
}
}
Future<int> runUploadSymbols(
List<String> arguments, {
required Directory repositoryRoot,
Map<String, String>? environment,
SymbolUploader uploader = _runSentryPlugin,
Future<String?> Function()? revisionProvider,
StringSink? output,
StringSink? errors,
}) async {
final out = output ?? stdout;
final err = errors ?? stderr;
if (arguments.isEmpty || arguments.first.isEmpty) {
err.writeln('platform arg required');
return 1;
}
final platform = arguments.first;
final root = path.normalize(path.absolute(repositoryRoot.path));
final sourceArgument = arguments.length > 1 ? arguments[1] : '';
final sourceRoot = sourceArgument.isEmpty
? root
: path.normalize(path.isAbsolute(sourceArgument) ? sourceArgument : path.join(root, sourceArgument));
final buildRoot = path.join(sourceRoot, 'build');
final symbolRoot = path.join(sourceRoot, 'debug-info', platform);
final searchRoots = <String>[];
void addExistingRoot(String candidate) {
if (Directory(candidate).existsSync()) {
searchRoots.add(candidate);
}
}
addExistingRoot(symbolRoot);
switch (platform) {
case 'macos':
addExistingRoot(path.join(buildRoot, 'macos'));
case 'ios':
addExistingRoot(path.join(buildRoot, 'ios'));
addExistingRoot(path.join(sourceRoot, 'ios', 'build'));
case 'linux-x64' || 'linux-arm64':
addExistingRoot(path.join(buildRoot, 'linux'));
case 'android-apk' || 'android-aab':
addExistingRoot(path.join(buildRoot, 'app'));
case 'windows-x64' || 'windows-arm64':
addExistingRoot(path.join(buildRoot, 'windows'));
default:
err.writeln('unknown platform: $platform');
return 2;
}
final symbolFiles = _discoverFiles(searchRoots);
if (symbolFiles.isEmpty) {
err.writeln('no symbols found for platform $platform');
return 3;
}
final childEnvironment = Map<String, String>.of(environment ?? Platform.environment);
final dryRun = _isNotEmpty(childEnvironment['BUGS_UPLOAD_DRY_RUN']);
childEnvironment['SENTRY_URL'] = _firstNotEmpty([
childEnvironment['SENTRY_URL'],
childEnvironment['BUGS_URL'],
_defaultSentryUrl,
]);
childEnvironment['SENTRY_LOG_LEVEL'] = _firstNotEmpty([childEnvironment['SENTRY_LOG_LEVEL'], 'info']);
if (!_isNotEmpty(childEnvironment['SENTRY_AUTH_TOKEN']) && _isNotEmpty(childEnvironment['BUGS_ADMIN_TOKEN'])) {
childEnvironment['SENTRY_AUTH_TOKEN'] = childEnvironment['BUGS_ADMIN_TOKEN']!;
}
if (!dryRun && !_isNotEmpty(childEnvironment['SENTRY_AUTH_TOKEN'])) {
err.writeln('SENTRY_AUTH_TOKEN or BUGS_ADMIN_TOKEN env var required');
return 1;
}
if (!_isNotEmpty(childEnvironment['SENTRY_RELEASE'])) {
final revision = await (revisionProvider?.call() ?? _gitRevision(root));
if (!_isNotEmpty(revision)) {
err.writeln('failed to determine SENTRY_RELEASE from git');
return 1;
}
childEnvironment['SENTRY_RELEASE'] = 'plezy@$revision';
}
var dartSymbolMapPath = childEnvironment['SENTRY_DART_SYMBOL_MAP_PATH'] ?? '';
if (dartSymbolMapPath.isEmpty) {
for (final candidate in [
path.join(symbolRoot, 'obfuscation.map.json'),
path.join(buildRoot, 'app', 'obfuscation', '$platform.map.json'),
path.join(buildRoot, 'app', 'obfuscation.map.json'),
]) {
if (File(candidate).existsSync()) {
dartSymbolMapPath = candidate;
break;
}
}
}
final release = childEnvironment['SENTRY_RELEASE']!;
final dist = childEnvironment['SENTRY_DIST'] ?? '';
final pluginArguments = <String>[
'--sentry-define=release=$release',
'--sentry-define=url=${childEnvironment['SENTRY_URL']}',
'--sentry-define=build_path=$buildRoot',
if (dist.isNotEmpty) '--sentry-define=dist=$dist',
if (Directory(symbolRoot).existsSync()) '--sentry-define=symbols_path=$symbolRoot',
if (dartSymbolMapPath.isNotEmpty) '--sentry-define=dart_symbol_map_path=$dartSymbolMapPath',
];
if (dryRun) {
out.writeln('dry-run: would upload symbols for $platform');
out.writeln('dry-run: release=$release');
out.writeln('dry-run: dist=$dist');
out.writeln('dry-run: source_root=$sourceRoot');
out.writeln('dry-run: build_path=$buildRoot');
out.writeln('dry-run: symbols_path=$symbolRoot');
out.writeln('dry-run: dart_symbol_map_path=$dartSymbolMapPath');
for (final file in symbolFiles) {
out.writeln(file);
}
return 0;
}
out.writeln('uploading symbols for $platform release $release dist $dist');
try {
final result = await uploader(arguments: pluginArguments, environment: childEnvironment, workingDirectory: root);
if (result != 0) {
err.writeln('symbol upload failed for $platform (exit code $result)');
}
return result;
} on ProcessException catch (error) {
err.writeln('failed to start symbol upload: $error');
return 1;
}
}
List<String> _discoverFiles(List<String> roots) {
final files = <String>[];
for (final root in roots) {
try {
files.addAll(
Directory(root)
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.map((file) => path.normalize(path.absolute(file.path))),
);
} on FileSystemException {
// Match the wrappers' previous best-effort discovery behavior.
}
}
files.sort();
return files;
}
Future<String?> _gitRevision(String root) async {
try {
final result = await Process.run('git', const ['rev-parse', '--short', 'HEAD'], workingDirectory: root);
if (result.exitCode == 0) {
return (result.stdout as String).trim();
}
} on ProcessException {
return null;
}
return null;
}
Future<int> _runSentryPlugin({
required List<String> arguments,
required Map<String, String> environment,
required String workingDirectory,
}) async {
final process = await Process.start(
Platform.resolvedExecutable,
['run', 'sentry_dart_plugin', ...arguments],
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: false,
mode: ProcessStartMode.inheritStdio,
);
return process.exitCode;
}
bool _isNotEmpty(String? value) => value != null && value.isNotEmpty;
String _firstNotEmpty(List<String?> values) => values.firstWhere(_isNotEmpty)!;