Commit 1e9a9e35 authored by Danielle Tomlinson's avatar Danielle Tomlinson

[Installer] Create Xcode::TargetValidator

parent 3fb285c7
...@@ -12,6 +12,10 @@ To install release candidates run `[sudo] gem install cocoapods --pre` ...@@ -12,6 +12,10 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
[Danielle Tomlinson](https://github.com/dantoml) [Danielle Tomlinson](https://github.com/dantoml)
[#5635](https://github.com/CocoaPods/CocoaPods/pull/5635) [#5635](https://github.com/CocoaPods/CocoaPods/pull/5635)
* Move Installer target verification into the Xcode namespace
[Danielle Tomlinson](https://github.com/DanToml)
[#5607](https://github.com/CocoaPods/CocoaPods/pull/5607)
##### Bug Fixes ##### Bug Fixes
* None. * None.
......
...@@ -109,9 +109,7 @@ module Pod ...@@ -109,9 +109,7 @@ module Pod
prepare prepare
resolve_dependencies resolve_dependencies
download_dependencies download_dependencies
verify_no_duplicate_framework_and_library_names validate_targets
verify_no_static_framework_transitive_dependencies
verify_framework_usage
generate_pods_project generate_pods_project
if installation_options.integrate_targets? if installation_options.integrate_targets?
integrate_user_project integrate_user_project
...@@ -286,19 +284,11 @@ module Pod ...@@ -286,19 +284,11 @@ module Pod
end end
end end
# TODO: the file accessor should be initialized by the sandbox as they # @return [void] In this step we create the file accessors for the pod
# created by the Pod source installer as well. # targets.
# #
def create_file_accessors def create_file_accessors
pod_targets.each do |pod_target| sandbox.create_file_accessors(pod_targets)
pod_root = sandbox.pod_dir(pod_target.root_spec.name)
path_list = Sandbox::PathList.new(pod_root)
file_accessors = pod_target.specs.map do |spec|
Sandbox::FileAccessor.new(path_list, spec.consumer(pod_target.platform))
end
pod_target.file_accessors ||= []
pod_target.file_accessors.concat(file_accessors)
end
end end
# Downloads, installs the documentation and cleans the sources of the Pods # Downloads, installs the documentation and cleans the sources of the Pods
...@@ -391,67 +381,9 @@ module Pod ...@@ -391,67 +381,9 @@ module Pod
end end
end end
def verify_no_duplicate_framework_and_library_names def validate_targets
aggregate_targets.each do |aggregate_target| validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets)
aggregate_target.user_build_configurations.keys.each do |config| validator.validate!
pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
file_accessors = pod_targets.flat_map(&:file_accessors)
frameworks = file_accessors.flat_map(&:vendored_frameworks).uniq.map(&:basename)
frameworks += pod_targets.select { |pt| pt.should_build? && pt.requires_frameworks? }.map(&:product_module_name)
verify_no_duplicate_names(frameworks, aggregate_target.label, 'frameworks')
libraries = file_accessors.flat_map(&:vendored_libraries).uniq.map(&:basename)
libraries += pod_targets.select { |pt| pt.should_build? && !pt.requires_frameworks? }.map(&:product_name)
verify_no_duplicate_names(libraries, aggregate_target.label, 'libraries')
end
end
end
def verify_no_duplicate_names(names, label, type)
duplicates = names.map { |n| n.to_s.downcase }.group_by { |f| f }.select { |_, v| v.size > 1 }.keys
unless duplicates.empty?
raise Informative, "The '#{label}' target has " \
"#{type} with conflicting names: #{duplicates.to_sentence}."
end
end
def verify_no_static_framework_transitive_dependencies
aggregate_targets.each do |aggregate_target|
next unless aggregate_target.requires_frameworks?
aggregate_target.user_build_configurations.keys.each do |config|
pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
dependencies = pod_targets.select(&:should_build?).flat_map(&:dependencies)
dependended_upon_targets = pod_targets.select { |t| dependencies.include?(t.pod_name) && !t.should_build? }
static_libs = dependended_upon_targets.flat_map(&:file_accessors).flat_map(&:vendored_static_artifacts)
unless static_libs.empty?
raise Informative, "The '#{aggregate_target.label}' target has " \
"transitive dependencies that include static binaries: (#{static_libs.to_sentence})"
end
end
end
end
def verify_framework_usage
aggregate_targets.each do |aggregate_target|
next if aggregate_target.requires_frameworks?
aggregate_target.user_build_configurations.keys.each do |config|
pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
swift_pods = pod_targets.select(&:uses_swift?)
unless swift_pods.empty?
raise Informative, 'Pods written in Swift can only be integrated as frameworks; ' \
'add `use_frameworks!` to your Podfile or target to opt into using it. ' \
"The Swift #{swift_pods.size == 1 ? 'Pod being used is' : 'Pods being used are'}: " +
swift_pods.map(&:name).to_sentence
end
end
end
end end
# Runs the registered callbacks for the plugins pre install hooks. # Runs the registered callbacks for the plugins pre install hooks.
......
...@@ -2,6 +2,7 @@ module Pod ...@@ -2,6 +2,7 @@ module Pod
class Installer class Installer
class Xcode class Xcode
autoload :PodsProjectGenerator, 'cocoapods/installer/xcode/pods_project_generator' autoload :PodsProjectGenerator, 'cocoapods/installer/xcode/pods_project_generator'
autoload :TargetValidator, 'cocoapods/installer/xcode/target_validator'
end end
end end
end end
module Pod
class Installer
class Xcode
# The {Xcode::TargetValidator} ensures that the pod and aggregate target
# configuration is valid for installation.
#
class TargetValidator
# @return [Array<AggregateTarget>] The aggregate targets that should be
# validated.
#
attr_reader :aggregate_targets
# @return [Array<PodTarget>] The pod targets that should be validated.
#
attr_reader :pod_targets
# Create a new TargetValidator with aggregate and pod targets to
# validate.
#
# @param [Array<AggregateTarget>] aggregate_targets
# The aggregate targets to validate.
#
# @param [Array<PodTarget>] pod_targets
# The pod targets to validate.
#
def initialize(aggregate_targets, pod_targets)
@aggregate_targets = aggregate_targets
@pod_targets = pod_targets
end
# Perform the validation steps for the provided aggregate and pod
# targets.
#
def validate!
verify_no_duplicate_framework_and_library_names
verify_no_static_framework_transitive_dependencies
verify_framework_usage
end
private
def verify_no_duplicate_framework_and_library_names
aggregate_targets.each do |aggregate_target|
aggregate_target.user_build_configurations.keys.each do |config|
pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
file_accessors = pod_targets.flat_map(&:file_accessors)
frameworks = file_accessors.flat_map(&:vendored_frameworks).uniq.map(&:basename)
frameworks += pod_targets.select { |pt| pt.should_build? && pt.requires_frameworks? }.map(&:product_module_name)
verify_no_duplicate_names(frameworks, aggregate_target.label, 'frameworks')
libraries = file_accessors.flat_map(&:vendored_libraries).uniq.map(&:basename)
libraries += pod_targets.select { |pt| pt.should_build? && !pt.requires_frameworks? }.map(&:product_name)
verify_no_duplicate_names(libraries, aggregate_target.label, 'libraries')
end
end
end
def verify_no_duplicate_names(names, label, type)
duplicates = names.map { |n| n.to_s.downcase }.group_by { |f| f }.select { |_, v| v.size > 1 }.keys
unless duplicates.empty?
raise Informative, "The '#{label}' target has " \
"#{type} with conflicting names: #{duplicates.to_sentence}."
end
end
def verify_no_static_framework_transitive_dependencies
aggregate_targets.each do |aggregate_target|
next unless aggregate_target.requires_frameworks?
aggregate_target.user_build_configurations.keys.each do |config|
pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
dependencies = pod_targets.select(&:should_build?).flat_map(&:dependencies)
dependended_upon_targets = pod_targets.select { |t| dependencies.include?(t.pod_name) && !t.should_build? }
static_libs = dependended_upon_targets.flat_map(&:file_accessors).flat_map(&:vendored_static_artifacts)
unless static_libs.empty?
raise Informative, "The '#{aggregate_target.label}' target has " \
"transitive dependencies that include static binaries: (#{static_libs.to_sentence})"
end
end
end
end
def verify_framework_usage
aggregate_targets.each do |aggregate_target|
next if aggregate_target.requires_frameworks?
aggregate_target.user_build_configurations.keys.each do |config|
pod_targets = aggregate_target.pod_targets_for_build_configuration(config)
swift_pods = pod_targets.select(&:uses_swift?)
unless swift_pods.empty?
raise Informative, 'Pods written in Swift can only be integrated as frameworks; ' \
'add `use_frameworks!` to your Podfile or target to opt into using it. ' \
"The Swift #{swift_pods.size == 1 ? 'Pod being used is' : 'Pods being used are'}: " +
swift_pods.map(&:name).to_sentence
end
end
end
end
end
end
end
end
...@@ -386,5 +386,28 @@ module Pod ...@@ -386,5 +386,28 @@ module Pod
end end
#-------------------------------------------------------------------------# #-------------------------------------------------------------------------#
public
# @!group Pods Helpers
# Creates the file accessors for the given Pod Targets.
#
# @param [Array<PodTarget>] pod_targets
# The Pod Targets to create file accessors for.
#
def create_file_accessors(pod_targets)
pod_targets.each do |pod_target|
pod_root = pod_dir(pod_target.root_spec.name)
path_list = PathList.new(pod_root)
file_accessors = pod_target.specs.map do |spec|
FileAccessor.new(path_list, spec.consumer(pod_target.platform))
end
pod_target.file_accessors ||= []
pod_target.file_accessors.concat(file_accessors)
end
end
#-------------------------------------------------------------------------#
end end
end end
...@@ -434,9 +434,7 @@ module Pod ...@@ -434,9 +434,7 @@ module Pod
# for all available platforms with xcodebuild. # for all available platforms with xcodebuild.
# #
def install_pod def install_pod
%i(verify_no_duplicate_framework_and_library_names %i(validate_targets generate_pods_project integrate_user_project
verify_no_static_framework_transitive_dependencies
verify_framework_usage generate_pods_project integrate_user_project
perform_post_install_actions).each { |m| @installer.send(m) } perform_post_install_actions).each { |m| @installer.send(m) }
deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name) deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
......
require File.expand_path('../../../../spec_helper', __FILE__)
module Pod
class Installer
class Xcode
describe TargetValidator do
# @return [Lockfile]
#
def generate_lockfile(lockfile_version: Pod::VERSION)
hash = {}
hash['PODS'] = []
hash['DEPENDENCIES'] = []
hash['SPEC CHECKSUMS'] = {}
hash['COCOAPODS'] = lockfile_version
Pod::Lockfile.new(hash)
end
# @return [AnalysisResult]
#
def create_validator(sandbox, podfile, lockfile)
installation_options = Installer::InstallationOptions.new.tap do |options|
options.integrate_targets = false
end
@analyzer = Analyzer.new(config.sandbox, podfile, lockfile).tap do |analyzer|
analyzer.installation_options = installation_options
end
result = @analyzer.analyze
aggregate_targets = result.targets
pod_targets = aggregate_targets.map(&:pod_targets).flatten.uniq
sandbox.create_file_accessors(pod_targets)
TargetValidator.new(aggregate_targets, pod_targets)
end
describe '#verify_no_duplicate_framework_and_library_names' do
it 'detects duplicate library names' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([Pathname('a/libBananalib.a')])
Pod::Specification.any_instance.stubs(:dependencies).returns([])
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@validator = create_validator(config.sandbox, podfile, lockfile)
should.raise(Informative) { @validator.validate! }.message.should.match /conflict.*bananalib/
end
it 'detects duplicate framework names' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_frameworks).
returns([Pathname('a/monkey.framework')]).then.
returns([Pathname('b/monkey.framework')]).then.
returns([Pathname('c/monkey.framework')])
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
pod 'OrangeFramework', :path => (fixture_path + 'orange-framework').to_s
pod 'matryoshka', :path => (fixture_path + 'matryoshka').to_s
pod 'monkey', :path => (fixture_path + 'monkey').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@validator = create_validator(config.sandbox, podfile, lockfile)
should.raise(Informative) { @validator.validate! }.message.should.match /conflict.*monkey/
end
it 'allows duplicate references to the same expanded framework path' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_frameworks).returns([fixture('monkey/dynamic-monkey.framework')])
Sandbox::FileAccessor.any_instance.stubs(:dynamic_binary?).returns(true)
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
use_frameworks!
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
pod 'monkey', :path => (fixture_path + 'monkey').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@validator = create_validator(config.sandbox, podfile, lockfile)
should.not.raise(Informative) { @validator.validate! }
end
end
#-------------------------------------------------------------------------#
describe '#verify_no_static_framework_transitive_dependencies' do
before do
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
@podfile = Pod::Podfile.new do
install! 'cocoapods', 'integrate_targets' => false
platform :ios, '8.0'
project 'SampleProject/SampleProject'
use_frameworks!
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
pod 'OrangeFramework', :path => (fixture_path + 'orange-framework').to_s
pod 'matryoshka', :path => (fixture_path + 'matryoshka').to_s
pod 'monkey', :path => (fixture_path + 'monkey').to_s
target 'SampleProject'
end
@lockfile = generate_lockfile
@file = Pathname('/yolo.m')
@file.stubs(:realpath).returns(@file)
@lib_thing = Pathname('/libThing.a')
@lib_thing.stubs(:realpath).returns(@lib_thing)
end
it 'detects transitive static dependencies which are linked directly to the user target' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([@lib_thing])
@validator = create_validator(config.sandbox, @podfile, @lockfile)
should.raise(Informative) { @validator.validate! }.message.should.match /transitive.*libThing/
end
it 'allows transitive static dependencies which contain other source code' do
Sandbox::FileAccessor.any_instance.stubs(:source_files).returns([@file])
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([@lib_thing])
@validator = create_validator(config.sandbox, @podfile, @lockfile)
should.not.raise(Informative) { @validator.validate! }
end
it 'allows transitive static dependencies when both dependencies are linked against the user target' do
PodTarget.any_instance.stubs(:should_build? => false)
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([@lib_thing])
@validator = create_validator(config.sandbox, @podfile, @lockfile)
should.not.raise(Informative) { @validator.validate! }
end
end
#-------------------------------------------------------------------------#
describe '#verify_framework_usage' do
it 'raises when Swift pods are used without explicit `use_frameworks!`' do
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
pod 'OrangeFramework', :path => (fixture_path + 'orange-framework').to_s
pod 'matryoshka', :path => (fixture_path + 'matryoshka').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@validator = create_validator(config.sandbox, podfile, lockfile)
should.raise(Informative) { @validator.validate! }.message.should.match /use_frameworks/
end
end
end
end
end
end
...@@ -61,9 +61,7 @@ module Pod ...@@ -61,9 +61,7 @@ module Pod
before do before do
@installer.stubs(:resolve_dependencies) @installer.stubs(:resolve_dependencies)
@installer.stubs(:download_dependencies) @installer.stubs(:download_dependencies)
@installer.stubs(:verify_no_duplicate_framework_and_library_names) @installer.stubs(:validate_targets)
@installer.stubs(:verify_no_static_framework_transitive_dependencies)
@installer.stubs(:verify_framework_usage)
@installer.stubs(:generate_pods_project) @installer.stubs(:generate_pods_project)
@installer.stubs(:integrate_user_project) @installer.stubs(:integrate_user_project)
@installer.stubs(:run_plugins_post_install_hooks) @installer.stubs(:run_plugins_post_install_hooks)
...@@ -259,139 +257,6 @@ module Pod ...@@ -259,139 +257,6 @@ module Pod
#-------------------------------------------------------------------------# #-------------------------------------------------------------------------#
describe '#verify_no_duplicate_framework_and_library_names' do
it 'detects duplicate library names' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([Pathname('a/libBananalib.a')])
Pod::Specification.any_instance.stubs(:dependencies).returns([])
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@installer = Installer.new(config.sandbox, podfile, lockfile)
@installer.installation_options.integrate_targets = false
should.raise(Informative) { @installer.install! }.message.should.match /conflict.*bananalib/
end
it 'detects duplicate framework names' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_frameworks).
returns([Pathname('a/monkey.framework')]).then.
returns([Pathname('b/monkey.framework')]).then.
returns([Pathname('c/monkey.framework')])
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
pod 'OrangeFramework', :path => (fixture_path + 'orange-framework').to_s
pod 'matryoshka', :path => (fixture_path + 'matryoshka').to_s
pod 'monkey', :path => (fixture_path + 'monkey').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@installer = Installer.new(config.sandbox, podfile, lockfile)
@installer.installation_options.integrate_targets = false
should.raise(Informative) { @installer.install! }.message.should.match /conflict.*monkey/
end
it 'allows duplicate references to the same expanded framework path' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_frameworks).returns([fixture('monkey/dynamic-monkey.framework')])
Sandbox::FileAccessor.any_instance.stubs(:dynamic_binary?).returns(true)
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
use_frameworks!
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
pod 'monkey', :path => (fixture_path + 'monkey').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@installer = Installer.new(config.sandbox, podfile, lockfile)
@installer.installation_options.integrate_targets = false
should.not.raise(Informative) { @installer.install! }
end
end
#-------------------------------------------------------------------------#
describe '#verify_no_static_framework_transitive_dependencies' do
before do
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
@podfile = Pod::Podfile.new do
install! 'cocoapods', 'integrate_targets' => false
platform :ios, '8.0'
project 'SampleProject/SampleProject'
use_frameworks!
pod 'BananaLib', :path => (fixture_path + 'banana-lib').to_s
pod 'OrangeFramework', :path => (fixture_path + 'orange-framework').to_s
pod 'matryoshka', :path => (fixture_path + 'matryoshka').to_s
pod 'monkey', :path => (fixture_path + 'monkey').to_s
target 'SampleProject'
end
@lockfile = generate_lockfile
@file = Pathname('/yolo.m')
@file.stubs(:realpath).returns(@file)
@lib_thing = Pathname('/libThing.a')
@lib_thing.stubs(:realpath).returns(@lib_thing)
end
it 'detects transitive static dependencies which are linked directly to the user target' do
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([@lib_thing])
@installer = Installer.new(config.sandbox, @podfile, @lockfile)
should.raise(Informative) { @installer.install! }.message.should.match /transitive.*libThing/
end
it 'allows transitive static dependencies which contain other source code' do
Sandbox::FileAccessor.any_instance.stubs(:source_files).returns([@file])
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([@lib_thing])
@installer = Installer.new(config.sandbox, @podfile, @lockfile)
should.not.raise(Informative) { @installer.install! }
end
it 'allows transitive static dependencies when both dependencies are linked against the user target' do
PodTarget.any_instance.stubs(:should_build? => false)
Sandbox::FileAccessor.any_instance.stubs(:vendored_libraries).returns([@lib_thing])
@installer = Installer.new(config.sandbox, @podfile, @lockfile)
should.not.raise(Informative) { @installer.install! }
end
end
#-------------------------------------------------------------------------#
describe '#verify_framework_usage' do
it 'raises when Swift pods are used without explicit `use_frameworks!`' do
fixture_path = ROOT + 'spec/fixtures'
config.repos_dir = fixture_path + 'spec-repos'
podfile = Pod::Podfile.new do
platform :ios, '8.0'
project 'SampleProject/SampleProject'
pod 'OrangeFramework', :path => (fixture_path + 'orange-framework').to_s
pod 'matryoshka', :path => (fixture_path + 'matryoshka').to_s
target 'SampleProject'
end
lockfile = generate_lockfile
@installer = Installer.new(config.sandbox, podfile, lockfile)
@installer.installation_options.integrate_targets = false
should.raise(Informative) { @installer.install! }.message.should.match /use_frameworks/
end
end
#-------------------------------------------------------------------------#
describe 'Dependencies Resolution' do describe 'Dependencies Resolution' do
describe 'updating spec repos' do describe 'updating spec repos' do
it 'does not updates the repositories by default' do it 'does not updates the repositories by default' do
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment