blob: eb78b3433652d6863bef29ee13baf8861f96138f [file] [log] [blame]
module Pod
class Installer
# Validate the podfile before installing to catch errors and
# problems
#
class PodfileValidator
# @return [Podfile] The podfile being validated
#
attr_reader :podfile
# @return [Array<String>] any errors that have occured during the validation
#
attr_reader :errors
# @return [Array<String>] any warnings that have occured during the validation
#
attr_reader :warnings
# Initialize a new instance
# @param [Podfile] podfile
# The podfile to validate
#
def initialize(podfile)
@podfile = podfile
@errors = []
@warnings = []
@validated = false
end
# Validate the podfile
# Errors are added to the errors array
#
def validate
validate_pod_directives
validate_no_abstract_only_pods!
validate_dependencies_are_present!
validate_no_duplicate_targets!
@validated = true
end
# Wether the podfile is valid is not
# NOTE: Will execute `validate` if the podfile
# has not yet been validated
#
def valid?
validate unless @validated
@validated && errors.empty?
end
# A message describing any errors in the
# validation
#
def message
errors.join("\n")
end
private
def add_error(error)
errors << error
end
def add_warning(warning)
warnings << warning
end
def validate_pod_directives
dependencies = podfile.target_definitions.flat_map do |_, target|
target.dependencies
end.uniq
dependencies.each do |dependency|
validate_conflicting_external_sources!(dependency)
end
end
def validate_conflicting_external_sources!(dependency)
external_source = dependency.external_source
return false if external_source.nil?
available_downloaders = Downloader.downloader_class_by_key.keys
specified_downloaders = external_source.select { |key| available_downloaders.include?(key) }
if specified_downloaders.size > 1
add_error "The dependency `#{dependency.name}` specifies more than one download strategy(#{specified_downloaders.keys.join(',')})." \
'Only one is allowed'
end
pod_spec_or_path = external_source[:podspec].present? || external_source[:path].present?
if pod_spec_or_path && specified_downloaders.size > 0
add_error "The dependency `#{dependency.name}` specifies `podspec` or `path` in combination with other" \
' download strategies. This is not allowed'
end
end
# Warns the user if the podfile is empty.
#
# @note The workspace is created in any case and all the user projects
# are added to it, however the projects are not integrated as
# there is no way to discern between target definitions which are
# empty and target definitions which just serve the purpose to
# wrap other ones. This is not an issue because empty target
# definitions generate empty libraries.
#
# @return [void]
#
def validate_dependencies_are_present!
if podfile.target_definitions.values.all?(&:empty?)
add_warning 'The Podfile does not contain any dependencies.'
end
end
# Verifies that no dependencies in the Podfile will end up not being built
# at all. In other words, all dependencies _must_ belong to a non-abstract
# target, or be inherited by a target where `inheritance == complete`.
#
def validate_no_abstract_only_pods!
all_dependencies = podfile.dependencies
concrete_dependencies = podfile.target_definition_list.reject(&:abstract?).flat_map(&:dependencies).uniq
abstract_only_dependencies = all_dependencies - concrete_dependencies
abstract_only_dependencies.each do |dep|
add_error "The dependency `#{dep}` is not used in any concrete target."
end
end
def validate_no_duplicate_targets!
podfile.target_definition_list.group_by { |td| [td.name, td.user_project_path] }.
each do |(name, project), definitions|
next unless definitions.size > 1
error = "The target `#{name}` is declared twice"
error << " for the project `#{project}`" if project
add_error(error << '.')
end
end
end
end
end