Skip to content

Improve GoogleServices-Info.plist support #388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 7, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -142,34 +142,6 @@ def self.test_password_from_params(params)
test_password && test_password.sub(/\r?\n$/, "")
end

def self.app_id_from_params(params)
if params[:app]
app_id = params[:app]
elsif xcode_archive_path
plist_path = params[:googleservice_info_plist_path]
app_id = get_ios_app_id_from_archive_plist(xcode_archive_path, plist_path)
end
if app_id.nil?
UI.crash!(ErrorMessage::MISSING_APP_ID)
end
app_id
end

def self.xcode_archive_path
# prevents issues on cross-platform build environments where an XCode build happens within
# the same lane
return nil if lane_platform == :android

# rubocop:disable Require/MissingRequireStatement
Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE]
# rubocop:enable Require/MissingRequireStatement
end

def self.lane_platform
# to_sym shouldn't be necessary, but possibly fixes #376
Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]&.to_sym
end

def self.platform_from_app_id(app_id)
if app_id.include?(':ios:')
:ios
Expand Down Expand Up @@ -492,7 +464,7 @@ def self.available_options
optional: true),
FastlaneCore::ConfigItem.new(key: :googleservice_info_plist_path,
env_name: "GOOGLESERVICE_INFO_PLIST_PATH",
description: "Path to your GoogleService-Info.plist file, relative to the archived product path",
description: "Path to your GoogleService-Info.plist file, relative to the archived product path (or directly, if no archived product path is found)",
default_value: "GoogleService-Info.plist",
optional: true,
type: String),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,23 @@ def self.run(params)
client = Google::Apis::FirebaseappdistributionV1::FirebaseAppDistributionService.new
client.authorization = get_authorization(params[:service_credentials_file], params[:firebase_cli_token], params[:service_credentials_json_data], params[:debug])

UI.message("⏳ Fetching latest release for app #{params[:app]}...")

parent = app_name_from_app_id(params[:app])
app_id = app_id_from_params(params)
UI.message("⏳ Fetching latest release for app #{app_id}...")
parent = app_name_from_app_id(app_id)

begin
releases = client.list_project_app_releases(parent, page_size: 1).releases
rescue Google::Apis::Error => err
if err.status_code.to_i == 404
UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{params[:app]}")
UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
else
UI.crash!(err)
end
end

if releases.nil? || releases.empty?
latest_release = nil
UI.important("No releases for app #{params[:app]} found in App Distribution. Returning nil and setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE].")
UI.important("No releases for app #{app_id} found in App Distribution. Returning nil and setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE].")
else
# latest_release = append_json_style_fields(response.releases[0].to_h)
latest_release = map_release_hash(releases[0])
Expand Down Expand Up @@ -80,10 +80,19 @@ def self.details

def self.available_options
[
# iOS Specific
FastlaneCore::ConfigItem.new(key: :googleservice_info_plist_path,
env_name: "GOOGLESERVICE_INFO_PLIST_PATH",
description: "Path to your GoogleService-Info.plist file, relative to the archived product path (or directly, if no archived product path is found)",
default_value: "GoogleService-Info.plist",
optional: true,
type: String),

# General
FastlaneCore::ConfigItem.new(key: :app,
env_name: "FIREBASEAPPDISTRO_APP",
description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
optional: false,
optional: true,
type: String),
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
description: "Auth token generated using Firebase CLI's login:ci command",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'fastlane/action'
require 'fastlane_core/ui/ui'
require 'cfpropertylist'
require 'google/apis/core'
Expand Down Expand Up @@ -36,15 +37,52 @@ def string_to_array(string, delimiter = /[,\n]/)
string.strip.split(delimiter).map(&:strip)
end

def app_id_from_params(params)
plist_path = params[:googleservice_info_plist_path]
if params[:app]
UI.message("Using app ID from 'app' parameter")
app_id = params[:app]
elsif xcode_archive_path
UI.message("Using app ID from the GoogleService-Info.plist file located using the 'googleservice_info_plist_path' parameter, relative to the archive path: #{xcode_archive_path}")
app_id = get_ios_app_id_from_archive_plist(xcode_archive_path, plist_path)
elsif plist_path
UI.message("Using app ID from the GoogleService-Info.plist file located using the 'googleservice_info_plist_path' parameter directly since no archive path was found")
app_id = get_ios_app_id_from_plist(plist_path)
end
if app_id.nil?
UI.crash!(ErrorMessage::MISSING_APP_ID)
end
app_id
end

def lane_platform
# to_sym shouldn't be necessary, but possibly fixes #376
Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]&.to_sym
end

def xcode_archive_path
# prevents issues on cross-platform build environments where an XCode build happens within
# the same lane
return nil if lane_platform == :android

# rubocop:disable Require/MissingRequireStatement
Actions.lane_context[Actions::SharedValues::XCODEBUILD_ARCHIVE]
# rubocop:enable Require/MissingRequireStatement
end

def parse_plist(path)
CFPropertyList.native_types(CFPropertyList::List.new(file: path).value)
end

def get_ios_app_id_from_archive_plist(archive_path, plist_path)
def get_ios_app_id_from_archive_plist(archive_path, relative_plist_path)
app_path = parse_plist("#{archive_path}/Info.plist")["ApplicationProperties"]["ApplicationPath"]
UI.shell_error!("can't extract application path from Info.plist at #{archive_path}") if app_path.empty?
identifier = parse_plist("#{archive_path}/Products/#{app_path}/#{plist_path}")["GOOGLE_APP_ID"]
UI.shell_error!("can't extract GOOGLE_APP_ID") if identifier.empty?
return get_ios_app_id_from_plist("#{archive_path}/Products/#{app_path}/#{relative_plist_path}")
end

def get_ios_app_id_from_plist(plist_path)
identifier = parse_plist(plist_path)["GOOGLE_APP_ID"]
UI.shell_error!("can't extract GOOGLE_APP_ID from #{plist_path}") if identifier.empty?
return identifier
end

Expand Down
64 changes: 0 additions & 64 deletions spec/firebase_app_distribution_action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,70 +107,6 @@
end
end

describe '#xcode_archive_path' do
it 'returns the archive path is set, and platform is not Android' do
allow(Fastlane::Actions).to receive(:lane_context).and_return({
XCODEBUILD_ARCHIVE: '/path/to/archive'
})
expect(action.xcode_archive_path).to eq('/path/to/archive')
end

it 'returns nil if platform is Android' do
allow(Fastlane::Actions).to receive(:lane_context).and_return({
XCODEBUILD_ARCHIVE: '/path/to/archive',
PLATFORM_NAME: :android
})
expect(action.xcode_archive_path).to be_nil
end

it 'returns nil if the archive path is not set' do
allow(Fastlane::Actions).to receive(:lane_context).and_return({})
expect(action.xcode_archive_path).to be_nil
end
end

describe '#app_id_from_params' do
it 'returns the app id from the app parameter if set' do
expect(action).not_to(receive(:xcode_archive_path))

params = { app: 'app-id' }
result = action.app_id_from_params(params)

expect(result).to eq('app-id')
end

it 'raises if the app parameter is not set and there is no archive path' do
allow(action).to receive(:xcode_archive_path).and_return(nil)

params = {}
expect { action.app_id_from_params(params) }
.to raise_error(ErrorMessage::MISSING_APP_ID)
end

it 'returns the app id from the plist if the archive path is set' do
allow(action).to receive(:xcode_archive_path).and_return('/path/to/archive')
allow(action).to receive(:get_ios_app_id_from_archive_plist)
.with('/path/to/archive', '/path/to/plist')
.and_return('app-id-from-plist')

params = { googleservice_info_plist_path: '/path/to/plist' }
result = action.app_id_from_params(params)

expect(result).to eq('app-id-from-plist')
end

it 'raises if the app parameter is not set and the plist is empty' do
allow(action).to receive(:xcode_archive_path).and_return('/path/to/archive')
allow(action).to receive(:get_ios_app_id_from_archive_plist)
.with('/path/to/archive', '/path/to/plist')
.and_return(nil)

params = { googleservice_info_plist_path: '/path/to/plist' }
expect { action.app_id_from_params(params) }
.to raise_error(ErrorMessage::MISSING_APP_ID)
end
end

describe '#release_notes' do
before do
allow(Fastlane::Actions.lane_context).to receive('[]')
Expand Down
76 changes: 76 additions & 0 deletions spec/firebase_app_distribution_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,80 @@
end.to raise_error("Unsupported distribution file format, should be .ipa, .apk or .aab")
end
end

describe '#xcode_archive_path' do
it 'returns the archive path is set, and platform is not Android' do
allow(Fastlane::Actions).to receive(:lane_context).and_return({
XCODEBUILD_ARCHIVE: '/path/to/archive'
})
expect(helper.xcode_archive_path).to eq('/path/to/archive')
end

it 'returns nil if platform is Android' do
allow(Fastlane::Actions).to receive(:lane_context).and_return({
XCODEBUILD_ARCHIVE: '/path/to/archive',
PLATFORM_NAME: :android
})
expect(helper.xcode_archive_path).to be_nil
end

it 'returns nil if the archive path is not set' do
allow(Fastlane::Actions).to receive(:lane_context).and_return({})
expect(helper.xcode_archive_path).to be_nil
end
end

describe '#app_id_from_params' do
it 'returns the app id from the app parameter if set' do
expect(helper).not_to(receive(:xcode_archive_path))

params = { app: 'app-id' }
result = helper.app_id_from_params(params)

expect(result).to eq('app-id')
end

it 'raises if the app parameter is not set and there is no archive path' do
allow(helper).to receive(:xcode_archive_path).and_return(nil)

params = {}
expect { helper.app_id_from_params(params) }
.to raise_error(ErrorMessage::MISSING_APP_ID)
end

it 'returns the app id from the plist if the archive path is set' do
allow(helper).to receive(:xcode_archive_path).and_return('/path/to/archive')
allow(helper).to receive(:get_ios_app_id_from_archive_plist)
.with('/path/to/archive', '/path/to/plist')
.and_return('app-id-from-plist')

params = { googleservice_info_plist_path: '/path/to/plist' }
result = helper.app_id_from_params(params)

expect(result).to eq('app-id-from-plist')
end

it 'returns the app id from the plist if there is no archive path and it is found directly at the given path' do
allow(helper).to receive(:xcode_archive_path).and_return(nil)
allow(helper).to receive(:get_ios_app_id_from_plist)
.with('/path/to/plist')
.and_return('app-id-from-plist')

params = { googleservice_info_plist_path: '/path/to/plist' }
result = helper.app_id_from_params(params)

expect(result).to eq('app-id-from-plist')
end

it 'raises if the app parameter is not set and the plist is empty' do
allow(helper).to receive(:xcode_archive_path).and_return('/path/to/archive')
allow(helper).to receive(:get_ios_app_id_from_archive_plist)
.with('/path/to/archive', '/path/to/plist')
.and_return(nil)

params = { googleservice_info_plist_path: '/path/to/plist' }
expect { helper.app_id_from_params(params) }
.to raise_error(ErrorMessage::MISSING_APP_ID)
end
end
end