Skip to content

Commit

Permalink
Add Android and iOS app_id/package_name guessers with ability to chec…
Browse files Browse the repository at this point in the history
…k config files (#10477)

* Add Android and iOS app_id/package_name guessers with ability to check config files

* Add clarifying comment to argument value accessing code.

* Rubocop fixes to improve iteration skipping.

* More clarifying comments.
  • Loading branch information
mpirri committed Oct 2, 2017
1 parent 51c005a commit 13d2ab9
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 35 deletions.
74 changes: 74 additions & 0 deletions fastlane_core/lib/fastlane_core/android_package_name_guesser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module FastlaneCore
class AndroidPackageNameGuesser
class << self
def android_package_name_arg?(gem_name, arg)
return arg == "--package_name" ||
arg == "--app_package_name" ||
(arg == '-p' && gem_name == 'supply') ||
(arg == '-a' && gem_name == 'screengrab')
end

def guess_package_name_from_args(gem_name, args)
# args example: ["-a", "com.krausefx.app"]
package_name = nil
args.each_with_index do |current, index|
next unless android_package_name_arg?(gem_name, current)
# argument names are followed by argument values in the args array;
# use [index + 1] to find the package name (range check the array
# to avoid array bounds errors)
package_name = args[index + 1] if args.count > index
break
end
package_name
end

def guess_package_name_from_environment
package_name = nil
package_name ||= ENV["SUPPLY_PACKAGE_NAME"] if FastlaneCore::Env.truthy?("SUPPLY_PACKAGE_NAME")
package_name ||= ENV["SCREENGRAB_APP_PACKAGE_NAME"] if FastlaneCore::Env.truthy?("SCREENGRAB_APP_PACKAGE_NAME")
package_name
end

def guess_package_name_from_appfile
CredentialsManager::AppfileConfig.try_fetch_value(:package_name)
end

def fetch_package_name_from_file(file_name, package_name_key)
# we only care about the package name item in the configuration file, so
# build an options array & Configuration with just that one key and it will
# be fetched if it is present in the config file
genericfile_options = [FastlaneCore::ConfigItem.new(key: package_name_key)]
options = FastlaneCore::Configuration.create(genericfile_options, {})
# pass the empty proc to disable options validation, otherwise this will fail
# when the other (non-package name) keys are encountered in the config file;
# 3rd parameter "true" disables the printout of the contents of the
# configuration file, which is noisy and confusing in this case
options.load_configuration_file(file_name, proc {}, true)
return options[package_name_key]
rescue
# any option/file error here should just be treated as identifier not found
nil
end

def guess_package_name_from_config_files
package_name = nil
package_name ||= fetch_package_name_from_file("Supplyfile", :package_name)
package_name ||= fetch_package_name_from_file("Screengrabfile", :app_package_name)
package_name
end

# make a best-guess for the package_name for this project, using most-reliable signals
# first and then using less accurate ones afterwards; because this method only returns
# a GUESS for the package_name, it is only useful for metrics or other places where
# absolute accuracy is not required
def guess_package_name(gem_name, args)
package_name = nil
package_name ||= guess_package_name_from_args(gem_name, args)
package_name ||= guess_package_name_from_environment
package_name ||= guess_package_name_from_appfile
package_name ||= guess_package_name_from_config_files
package_name
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def verify_default_value_matches_verify_block
# @param config_file_name [String] The name of the configuration file to use (optional)
# @param block_for_missing [Block] A ruby block that is called when there is an unknown method
# in the configuration file
def load_configuration_file(config_file_name = nil, block_for_missing = nil)
def load_configuration_file(config_file_name = nil, block_for_missing = nil, skip_printing_values = false)
return unless config_file_name

self.config_file_name = config_file_name
Expand All @@ -171,7 +171,7 @@ def load_configuration_file(config_file_name = nil, block_for_missing = nil)
return if paths.count == 0

path = paths.first
configuration_file = ConfigurationFile.new(self, path, block_for_missing)
configuration_file = ConfigurationFile.new(self, path, block_for_missing, skip_printing_values)
verify_conflicts # important, since user can set conflicting options in configuration file
configuration_file
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ConfigurationFile

# @param config [FastlaneCore::Configuration] is stored to save the resulting values
# @param path [String] The path to the configuration file to use
def initialize(config, path, block_for_missing)
def initialize(config, path, block_for_missing, skip_printing_values = false)
self.config = config
self.configfile_path = path

Expand All @@ -29,7 +29,7 @@ def initialize(config, path, block_for_missing)
eval(content) # this is okay in this case
# rubocop:enable Security/Eval

print_resulting_config_values # only on success
print_resulting_config_values unless skip_printing_values # only on success
rescue SyntaxError => ex
line = ex.to_s.match(/\(eval\):(\d+)/)[1]
UI.user_error!("Syntax error in your configuration file '#{path}' on line #{line}: #{ex}")
Expand Down
66 changes: 66 additions & 0 deletions fastlane_core/lib/fastlane_core/ios_app_identifier_guesser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module FastlaneCore
class IOSAppIdentifierGuesser
class << self
def guess_app_identifier_from_args(args)
# args example: ["-a", "com.krausefx.app", "--team_id", "5AA97AAHK2"]
args.each_with_index do |current, index|
next unless current == "-a" || current == "--app_identifier"
# argument names are followed by argument values in the args array;
# use [index + 1] to find the package name (range check the array
# to avoid array bounds errors)
return args[index + 1] if args.count > index
end
nil
end

def guess_app_identifier_from_environment
["FASTLANE", "DELIVER", "PILOT", "PRODUCE", "PEM", "SIGH", "SNAPSHOT", "MATCH"].each do |current|
return ENV["#{current}_APP_IDENTIFIER"] if FastlaneCore::Env.truthy?("#{current}_APP_IDENTIFIER")
end
nil
end

def guess_app_identifier_from_appfile
CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
end

def fetch_app_identifier_from_file(file_name)
# we only care about the app_identifier item in the configuration file, so
# build an options array & Configuration with just that one key and it will
# be fetched if it is present in the config file
genericfile_options = [FastlaneCore::ConfigItem.new(key: :app_identifier)]
options = FastlaneCore::Configuration.create(genericfile_options, {})
# pass the empty proc to disable options validation, otherwise this will fail
# when the other (non-app_identifier) keys are encountered in the config file;
# 3rd parameter "true" disables the printout of the contents of the
# configuration file, which is noisy and confusing in this case
options.load_configuration_file(file_name, proc {}, true)
return options[:app_identifier]
rescue
# any option/file error here should just be treated as identifier not found
nil
end

def guess_app_identifier_from_config_files
["Deliverfile", "Gymfile", "Snapfile", "Matchfile"].each do |current|
app_identifier = self.fetch_app_identifier_from_file(current)
return app_identifier if app_identifier
end
nil
end

# make a best-guess for the app_identifier for this project, using most-reliable signals
# first and then using less accurate ones afterwards; because this method only returns
# a GUESS for the app_identifier, it is only useful for metrics or other places where
# absolute accuracy is not required
def guess_app_identifier(args)
app_identifier = nil
app_identifier ||= guess_app_identifier_from_args(args)
app_identifier ||= guess_app_identifier_from_environment
app_identifier ||= guess_app_identifier_from_appfile
app_identifier ||= guess_app_identifier_from_config_files
app_identifier
end
end
end
end
35 changes: 4 additions & 31 deletions fastlane_core/lib/fastlane_core/update_checker/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
require 'digest'

require 'fastlane_core/update_checker/changelog'
require 'fastlane_core/ios_app_identifier_guesser'
require 'fastlane_core/android_package_name_guesser'

module FastlaneCore
# Verifies, the user runs the latest version of this gem
Expand Down Expand Up @@ -121,18 +123,7 @@ def self.generate_fetch_url(gem_name)

# (optional) Returns the app identifier for the current tool
def self.ios_app_identifier(args)
# args example: ["-a", "com.krausefx.app", "--team_id", "5AA97AAHK2"]
args.each_with_index do |current, index|
if current == "-a" || current == "--app_identifier"
return args[index + 1] if args.count > index
end
end

["FASTLANE", "DELIVER", "PILOT", "PRODUCE", "PEM", "SIGH", "SNAPSHOT", "MATCH"].each do |current|
return ENV["#{current}_APP_IDENTIFIER"] if FastlaneCore::Env.truthy?("#{current}_APP_IDENTIFIER")
end

return CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
return FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier(args)
rescue
nil # we don't want this method to cause a crash
end
Expand All @@ -143,32 +134,14 @@ def self.ios_app_identifier(args)
# fastlane supply --skip_upload_screenshots -a beta -p com.test.app should return com.test.app
# screengrab -a com.test.app should return com.test.app
def self.android_app_identifier(args, gem_name)
app_identifier = nil
# args example: ["-a", "com.krausefx.app"]
args.each_with_index do |current, index|
if android_app_identifier_arg?(gem_name, current)
app_identifier = args[index + 1] if args.count > index
break
end
end

app_identifier ||= ENV["SUPPLY_PACKAGE_NAME"] if FastlaneCore::Env.truthy?("SUPPLY_PACKAGE_NAME")
app_identifier ||= ENV["SCREENGRAB_APP_PACKAGE_NAME"] if FastlaneCore::Env.truthy?("SCREENGRAB_APP_PACKAGE_NAME")
app_identifier ||= CredentialsManager::AppfileConfig.try_fetch_value(:package_name)
app_identifier = FastlaneCore::AndroidPackageNameGuesser.guess_package_name(gem_name, args)

# Add Android prefix to prevent collisions if there is an iOS app with the same identifier
app_identifier ? "android_project_#{app_identifier}" : nil
rescue
nil # we don't want this method to cause a crash
end

def self.android_app_identifier_arg?(gem_name, arg)
return arg == "--package_name" ||
arg == "--app_package_name" ||
(arg == '-p' && gem_name == 'supply') ||
(arg == '-a' && gem_name == 'screengrab')
end

# To not count the same projects multiple time for the number of launches
# Learn more at https://github.com/fastlane/fastlane#metrics
# Use the `FASTLANE_OPT_OUT_USAGE` variable to opt out
Expand Down
70 changes: 70 additions & 0 deletions fastlane_core/spec/android_package_name_guesser_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
require 'spec_helper'

require 'fastlane_core/android_package_name_guesser'

describe FastlaneCore::AndroidPackageNameGuesser do
it 'returns nil if no clues' do
# this might also fail if the environment or config files are not clean
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('fastlane', [])).to be_nil
end

describe 'guessing from command line args' do
it 'returns Android package_name if specified with --package_name' do
args = ["--package_name", "com.krausefx.app"]
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('fastlane', args)).to eq("com.krausefx.app")
end

it 'returns Android package_name if specified with --app_package_name' do
args = ["--app_package_name", "com.krausefx.app"]
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('fastlane', args)).to eq("com.krausefx.app")
end

it 'returns Android package_name if specified for supply gem with -p' do
args = ["-p", "com.krausefx.app"]
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('supply', args)).to eq("com.krausefx.app")
end

it 'returns Android package_name if specified for screengrab gem with -p' do
args = ["-a", "com.krausefx.app"]
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('screengrab', args)).to eq("com.krausefx.app")
end
end

describe 'guessing from environment' do
it 'returns Android package_name present in environment' do
["SUPPLY", "SCREENGRAB_APP"].each do |current|
env_var_name = "#{current}_PACKAGE_NAME"
package_name = "#{current}.bundle.id"
ENV[env_var_name] = package_name
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('fastlane', [])).to eq(package_name)
ENV.delete(env_var_name)
end
end
end

describe 'guessing from configuration files' do
def allow_non_target_configuration_file(file_name)
allow_any_instance_of(FastlaneCore::Configuration).to receive(:load_configuration_file).with(file_name, any_args) do |configuration, config_file_name|
nil
end
end

def allow_target_configuration_file(file_name, package_name_key)
allow_any_instance_of(FastlaneCore::Configuration).to receive(:load_configuration_file).with(file_name, any_args) do |configuration, config_file_name|
configuration[package_name_key] = "#{config_file_name}.bundle.id"
end
end

it 'returns iOS app_identifier found in Supplyfile' do
allow_target_configuration_file("Supplyfile", :package_name)
allow_non_target_configuration_file("Screengrabfile")
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('supply', [])).to eq("Supplyfile.bundle.id")
end

it 'returns iOS app_identifier found in Screengrabfile' do
allow_target_configuration_file("Screengrabfile", :app_package_name)
allow_non_target_configuration_file("Supplyfile")
expect(FastlaneCore::AndroidPackageNameGuesser.guess_package_name('screengrab', [])).to eq("Screengrabfile.bundle.id")
end
end
end
87 changes: 87 additions & 0 deletions fastlane_core/spec/ios_app_identifier_guesser_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
require 'spec_helper'

require 'fastlane_core/ios_app_identifier_guesser'

describe FastlaneCore::IOSAppIdentifierGuesser do
it 'returns nil if no clues' do
# this might also fail if the environment or config files are not clean
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to be_nil
end

describe 'guessing from command line args' do
it 'returns iOS app_identifier if specified with -a' do
args = ["-a", "com.krausefx.app"]
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier(args)).to eq("com.krausefx.app")
end

it 'returns iOS app_identifier if specified with --app_identifier' do
args = ["--app_identifier", "com.krausefx.app"]
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier(args)).to eq("com.krausefx.app")
end
end

describe 'guessing from environment' do
it 'returns iOS app_identifier present in environment' do
["FASTLANE", "DELIVER", "PILOT", "PRODUCE", "PEM", "SIGH", "SNAPSHOT", "MATCH"].each do |current|
env_var_name = "#{current}_APP_IDENTIFIER"
app_identifier = "#{current}.bundle.id"
ENV[env_var_name] = app_identifier
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to eq(app_identifier)
ENV.delete(env_var_name)
end
end
end

describe 'guessing from Appfile' do
it 'returns iOS app_identifier found in Appfile' do
expect(CredentialsManager::AppfileConfig).to receive(:try_fetch_value).with(:app_identifier).and_return("Appfile.bundle.id")
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to eq("Appfile.bundle.id")
end
end

describe 'guessing from configuration files' do
def allow_non_target_configuration_file(file_name)
allow_any_instance_of(FastlaneCore::Configuration).to receive(:load_configuration_file).with(file_name, any_args) do |configuration, config_file_name|
nil
end
end

def allow_target_configuration_file(file_name)
allow_any_instance_of(FastlaneCore::Configuration).to receive(:load_configuration_file).with(file_name, any_args) do |configuration, config_file_name|
configuration[:app_identifier] = "#{config_file_name}.bundle.id"
end
end

it 'returns iOS app_identifier found in Deliverfile' do
allow_target_configuration_file("Deliverfile")
allow_non_target_configuration_file("Gymfile")
allow_non_target_configuration_file("Matchfile")
allow_non_target_configuration_file("Snapfile")
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to eq("Deliverfile.bundle.id")
end

it 'returns iOS app_identifier found in Gymfile' do
allow_target_configuration_file("Gymfile")
allow_non_target_configuration_file("Deliverfile")
allow_non_target_configuration_file("Matchfile")
allow_non_target_configuration_file("Snapfile")
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to eq("Gymfile.bundle.id")
end

it 'returns iOS app_identifier found in Snapfile' do
allow_target_configuration_file("Snapfile")
allow_non_target_configuration_file("Deliverfile")
allow_non_target_configuration_file("Gymfile")
allow_non_target_configuration_file("Matchfile")
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to eq("Snapfile.bundle.id")
end

it 'returns iOS app_identifier found in Matchfile' do
allow_target_configuration_file("Matchfile")
allow_non_target_configuration_file("Deliverfile")
allow_non_target_configuration_file("Gymfile")
allow_non_target_configuration_file("Snapfile")
expect(FastlaneCore::IOSAppIdentifierGuesser.guess_app_identifier([])).to eq("Matchfile.bundle.id")
end
end
end

0 comments on commit 13d2ab9

Please sign in to comment.