Skip to content

Commit

Permalink
feat(crashlytics): add automatic Crashlytics symbol uploads for iOS &…
Browse files Browse the repository at this point in the history
… macOS apps (#8157)

Co-authored-by: Mike Diarmid <mike.diarmid@gmail.com>
  • Loading branch information
elenadoty and Salakar committed Mar 31, 2022
1 parent 3e884f2 commit c4a3eaa
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 15 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/e2e_tests.yaml
Expand Up @@ -84,15 +84,15 @@ jobs:
- uses: hendrikmuhs/ccache-action@v1
name: Xcode Compile Cache
with:
key: ${{ runner.os }}-ios-v2
key: ${{ runner.os }}-ios-v3
max-size: 700M
- uses: actions/cache@v2
name: Pods Cache
id: pods-cache
with:
path: tests/ios/Pods
key: ${{ runner.os }}-pods-v2-${{ hashFiles('tests/ios/Podfile.lock') }}
restore-keys: ${{ runner.os }}-ios-pods-v1
key: ${{ runner.os }}-pods-v3-${{ hashFiles('tests/ios/Podfile.lock') }}
restore-keys: ${{ runner.os }}-ios-pods-v2
- name: Firebase Emulator Cache
uses: actions/cache@v2
with:
Expand Down
@@ -0,0 +1,82 @@
require 'xcodeproj'
require 'optparse'

# Dictionary to hold command line arguments
options_dict = {}
options_dict[:flutter_project] = false
options_dict[:additional_options] = ""

# Parse command line arguments into options_dict
OptionParser.new do |options|
options.banner = "Adds the Crashlytics upload-symbols tool to an Xcode target's build phase. Usage: crashlytics_upload_symbols [options]"

options.on("-p", "--projectDirectory=DIRECTORY", String, "Directory of the Xcode project") do |dir|
options_dict[:project_dir] = dir
end

options.on("-n", "--projectName=NAME", String, "Name of the Xcode project (ex: Runner.xcodeproj)") do |name|
options_dict[:project_name] = name
end

options.on("-o", "--additionalOptions=OPTIONS", String, "Additional arguments to pass to upload-symbols (quote if multiple args)") do |opts|
options_dict[:additional_options] = opts
end

options.on("-f", "--flutter", "Use flutter firebase_app_id_file.json") do |fl|
options_dict[:flutter_project] = true
end
end.parse!

# Minimum required arguments are a project directory and project name
unless (options_dict[:project_dir] and options_dict[:project_name])
abort("Must provide a project directory and project name.\n")
end

# Path to the Xcode project to modify
project_path = File.join(options_dict[:project_dir], options_dict[:project_name])

unless (File.exist?(project_path))
abort("Project at #{project_path} does not exist. Please check paths or incorporate Crashlytics upload symbols manually.\n");
end

# If this is a Flutter project, upload-symbols will use the firebase_app_id_file.json to get the app's ID. If this file doesn't exist, upload-symbols may not be
# able to upload symbols correctly.
if(options_dict[:flutter_project])
unless File.exist?("#{options_dict[:project_dir]}/firebase_app_id_file.json")
puts("Warning: firebase_app_id_file.json file does not exist. This may cause issues in upload-symbols. If this error is unexpected, try running flutterfire configure again.")
end
end

if(options_dict[:flutter_project])
upload_symbols_args = "--flutter-project \"$PROJECT_DIR/firebase_app_id_file.json\" #{options_dict[:additional_options]}"
else
upload_symbols_args = options_dict[:additional_options]
end

# Actually open and modify the project
project = Xcodeproj::Project.open(project_path)
project.targets.each do |target|
if (target.name == "Runner")
# We need to make sure that we're not adding more than one run script to upload-symbols (or overwriting custom upload-symbols scripts).
target.shell_script_build_phases().each { |phase|
if (phase.shell_script.include? "FirebaseCrashlytics/upload-symbols")
puts("Run script to upload symbols already exists.")
exit(0)
end
if (phase.shell_script.include? "FirebaseCrashlytics/run")
puts("Run script to upload symbols already exists.")
exit(0)
end
}
phase = target.shell_script_build_phases().find {|item| item.name == "[firebase_crashlytics] Crashlytics Upload Symbols"}

# If no existing run scripts exist, then create one.
if (phase.nil?)
phase = target.new_shell_script_build_phase("[firebase_crashlytics] Crashlytics Upload Symbols")
phase.shell_script = "\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" #{upload_symbols_args}"
phase.input_paths = ["\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}\"", "\"$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)\""]
project.save()
end
end
end

Expand Up @@ -3,6 +3,12 @@ require 'yaml'
pubspec = YAML.load_file(File.join('..', 'pubspec.yaml'))
library_version = pubspec['version'].gsub('+', '-')

# Add upload-symbols script to the Flutter target.
current_dir = Dir.pwd
calling_dir = File.dirname(__FILE__)
project_dir = calling_dir.slice(0..(calling_dir.index('/.symlinks')))
system("ruby #{current_dir}/crashlytics_add_upload_symbols -f -p #{project_dir} -n Runner.xcodeproj")

if defined?($FirebaseSDKVersion)
Pod::UI.puts "#{pubspec['name']}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'"
firebase_sdk_version = $FirebaseSDKVersion
Expand Down Expand Up @@ -40,5 +46,4 @@ Pod::Spec.new do |s|
'DEFINES_MODULE' => 'YES'
}
s.user_target_xcconfig = { 'DEBUG_INFORMATION_FORMAT' => 'dwarf-with-dsym' }

end
Expand Up @@ -3,6 +3,16 @@ require 'yaml'
pubspec = YAML.load_file(File.join('..', 'pubspec.yaml'))
library_version = pubspec['version'].gsub('+', '-')

# Add upload-symbols script to the Flutter target.
current_dir = Dir.pwd
calling_dir = File.dirname(__FILE__)
project_dir = calling_dir.slice(0..(calling_dir.index('/.symlinks')))
if project_dir.include? "Flutter/ephemeral"
# Note: macOS CWD can be based in <macosProjectDir>/Flutter/ephemeral - so we need to go up two levels.
project_dir = File.expand_path(File.join(project_dir, '..', '..'))
end
system("ruby #{current_dir}/crashlytics_add_upload_symbols -f -p #{project_dir} -n Runner.xcodeproj")

if defined?($FirebaseSDKVersion)
Pod::UI.puts "#{pubspec['name']}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'"
firebase_sdk_version = $FirebaseSDKVersion
Expand Down
7 changes: 7 additions & 0 deletions tests/android/firebase_app_id_file.json
@@ -0,0 +1,7 @@
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:406099696497:android:0d4ed619c031c0ac3574d0",
"FIREBASE_PROJECT_ID": "flutterfire-e2e-tests",
"GCM_SENDER_ID": "406099696497"
}
24 changes: 22 additions & 2 deletions tests/ios/Runner.xcodeproj/project.pbxproj
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 51;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -76,7 +76,6 @@
56B65A6814148F63F80E1BCD /* Pods-Runner.release.xcconfig */,
A3CCB64392E4AE128B41755D /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -141,6 +140,7 @@
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
AC660AAB7696B4DD121B84D0 /* [CP] Embed Pods Frameworks */,
AFACAC31EEE064D50881C495 /* [CP] Copy Pods Resources */,
5F39B4E393588B005ADEE74A /* [firebase_crashlytics] Crashlytics Upload Symbols */,
);
buildRules = (
);
Expand Down Expand Up @@ -213,6 +213,26 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
5F39B4E393588B005ADEE74A /* [firebase_crashlytics] Crashlytics Upload Symbols */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}\"",
"\"$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)\"",
);
name = "[firebase_crashlytics] Crashlytics Upload Symbols";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --flutter-project \"$PROJECT_DIR/firebase_app_id_file.json\" ";
};
73AF29AE49A9551E6E30720C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Expand Down
7 changes: 7 additions & 0 deletions tests/ios/firebase_app_id_file.json
@@ -0,0 +1,7 @@
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:406099696497:ios:acd9c8e17b5e620e3574d0",
"FIREBASE_PROJECT_ID": "flutterfire-e2e-tests",
"GCM_SENDER_ID": "406099696497"
}
@@ -1,5 +1,5 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
Expand All @@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart'
///
/// Example:
/// ```dart
/// import 'firebase_default_options.dart';
/// import 'firebase_options.dart';
/// // ...
/// await Firebase.initializeApp(
/// options: DefaultFirebaseOptions.currentPlatform,
Expand All @@ -19,19 +19,18 @@ class DefaultFirebaseOptions {
if (kIsWeb) {
return web;
}
// ignore: missing_enum_constant_in_switch
switch (defaultTargetPlatform) {
case TargetPlatform.android:
return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
return macos;
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}

throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
}

static const FirebaseOptions web = FirebaseOptions(
Expand All @@ -43,6 +42,7 @@ class DefaultFirebaseOptions {
databaseURL:
'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'flutterfire-e2e-tests.appspot.com',
measurementId: 'G-JN95N1JV2E',
);

static const FirebaseOptions android = FirebaseOptions(
Expand All @@ -63,6 +63,8 @@ class DefaultFirebaseOptions {
databaseURL:
'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'flutterfire-e2e-tests.appspot.com',
androidClientId:
'406099696497-tvtvuiqogct1gs1s6lh114jeps7hpjm5.apps.googleusercontent.com',
iosClientId:
'406099696497-taeapvle10rf355ljcvq5dt134mkghmp.apps.googleusercontent.com',
iosBundleId: 'io.flutter.plugins.firebase.tests',
Expand All @@ -76,6 +78,8 @@ class DefaultFirebaseOptions {
databaseURL:
'https://flutterfire-e2e-tests-default-rtdb.europe-west1.firebasedatabase.app',
storageBucket: 'flutterfire-e2e-tests.appspot.com',
androidClientId:
'406099696497-tvtvuiqogct1gs1s6lh114jeps7hpjm5.apps.googleusercontent.com',
iosClientId:
'406099696497-taeapvle10rf355ljcvq5dt134mkghmp.apps.googleusercontent.com',
iosBundleId: 'io.flutter.plugins.firebase.tests',
Expand Down
2 changes: 1 addition & 1 deletion tests/lib/main.dart
@@ -1,7 +1,7 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'firebase_default_options.dart';
import 'firebase_options.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
Expand Down
23 changes: 22 additions & 1 deletion tests/macos/Runner.xcodeproj/project.pbxproj
Expand Up @@ -94,7 +94,6 @@
16E3DE49D099A914502EAEAC /* Pods-Runner.release.xcconfig */,
F2588DFC05D5B5F41376BEBC /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
Expand Down Expand Up @@ -186,6 +185,7 @@
3399D490228B24CF009A79C7 /* ShellScript */,
B6FDA406E9A0EB363B9CC88C /* [CP] Embed Pods Frameworks */,
741DA0581CD7F52CFA7F2590 /* [CP] Copy Pods Resources */,
76D37D3A2A5275D5E0B8B1B9 /* [firebase_crashlytics] Crashlytics Upload Symbols */,
);
buildRules = (
);
Expand Down Expand Up @@ -331,6 +331,26 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
76D37D3A2A5275D5E0B8B1B9 /* [firebase_crashlytics] Crashlytics Upload Symbols */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}\"",
"\"$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)\"",
);
name = "[firebase_crashlytics] Crashlytics Upload Symbols";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --flutter-project \"$PROJECT_DIR/firebase_app_id_file.json\" ";
};
B6FDA406E9A0EB363B9CC88C /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -565,6 +585,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand Down
7 changes: 7 additions & 0 deletions tests/macos/firebase_app_id_file.json
@@ -0,0 +1,7 @@
{
"file_generated_by": "FlutterFire CLI",
"purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory",
"GOOGLE_APP_ID": "1:406099696497:ios:acd9c8e17b5e620e3574d0",
"FIREBASE_PROJECT_ID": "flutterfire-e2e-tests",
"GCM_SENDER_ID": "406099696497"
}

0 comments on commit c4a3eaa

Please sign in to comment.