Commit ce3b4a30 authored by Per Eckerdal's avatar Per Eckerdal

Add support for Xcode subprojects to podspecs

parent fd0682e5
......@@ -28,13 +28,14 @@ module Pod
#
class Installer
autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :FileReferencesInstaller, 'cocoapods/installer/file_references_installer'
autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :AggregateTargetInstaller, 'cocoapods/installer/target_installer/aggregate_target_installer'
autoload :PodTargetInstaller, 'cocoapods/installer/target_installer/pod_target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :FileReferencesInstaller, 'cocoapods/installer/file_references_installer'
autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :AggregateTargetInstaller, 'cocoapods/installer/target_installer/aggregate_target_installer'
autoload :PodTargetInstaller, 'cocoapods/installer/target_installer/pod_target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
autoload :LinkedDependenciesInstaller, 'cocoapods/installer/linked_dependencies_installer'
include Config::Mixin
......@@ -112,6 +113,7 @@ module Pod
install_file_references
install_libraries
set_target_dependencies
link_linked_dependencies
link_aggregate_target
run_post_install_hooks
write_pod_project
......@@ -392,6 +394,19 @@ module Pod
end
end
# Adds the linked dependencies to the pod targets.
#
# @return [void]
#
def link_linked_dependencies
pod_targets.each do |pod_target|
pod_root = pod_for_target(pod_target).root
installer = LinkedDependenciesInstaller.new(pods_project, pod_root, pod_target)
installer.install!
end
end
# Writes the Pods project to the disk.
#
# @return [void]
......@@ -648,6 +663,16 @@ module Pod
analysis_result.sandbox_state
end
# Finds the PodRepresentation object for a given Target.
#
# @param [Target] The target for which the representation should be found.
#
# @return [PodRepresentation] The pod for a given target.
#
def pod_for_target(pod_target)
pod_reps.find { |pod_rep| pod_rep.name == pod_target.root_spec.name }
end
#-------------------------------------------------------------------------#
end
......
module Pod
class Installer
# This class is responsible for adding linked Xcode dependencies as
# subprojects in the correct groups and link the Xcode subproject
# library targets to the respective pod targets.
#
class LinkedDependenciesInstaller
# @return [Project] The Pods project.
#
attr_reader :pods_project
# @return [Pathname] The root path of the pod.
#
attr_reader :pod_root
# @return [PodTarget] The pod target to process.
#
attr_reader :pod_target
# @param [Project] pods_project @see pods_project
# @param [Pathname] pod_root @see pod_root
# @param [PodTarget] pod_target @see pod_target
#
def initialize(pods_project, pod_root, pod_target)
@pods_project = pods_project
@pod_root = pod_root
@pod_target = pod_target
end
# Installs the linked projects for a given pod target.
#
# @return [void]
#
def install!
add_linked_projects
add_libraries_to_target
end
#-----------------------------------------------------------------------#
private
# @!group Installation Steps
# Add the Xcode projects that specs refer to as subprojects to the Pods
# Xcode project, in the appropriate groups.
#
# @return [void]
#
def add_linked_projects
linked_project_specs.each do |path, specs|
specs.each do |spec|
group = pods_project.group_for_spec(spec.name)
pods_project.add_file_reference(path, group)
end
end
end
# Add the linked project targets to the pod target.
#
# @return [void]
#
def add_libraries_to_target
pod_target.spec_consumers.each do |consumer|
xcodeproj = consumer.xcodeproj
next unless xcodeproj['project']
linked_project = open_linked_xcode_project(xcodeproj['project'])
# Hide the schemes that would be autogenerated for the subproject's targets.
linked_project.recreate_user_schemes(false)
link_targets_with_target(linked_project, xcodeproj['library_targets'] || [])
end
end
#-----------------------------------------------------------------------#
private
# @!group Private Helpers
# Open an Xcode project refered to by a specific pod. Raises an
# Informative error if it fails to open the Xcode project file.
#
# @param [String] The filename of the Xcode project, relative to
# the pod's root.
#
# @return [Xcodeproj::Project] The opened Xcode project file
#
def open_linked_xcode_project(project_filename)
linked_project = begin
Xcodeproj::Project.open(pod_root + project_filename)
rescue RuntimeError => e
raise Informative, "Could not open project #{project_filename}, specified in #{spec_file}: #{e.message}"
end
linked_project
end
# Link a number of targets within a subproject to a pod target.
#
# @param [Xcodeproj::Project] linked_project The Xcode subproject that
# contains the library targets.
# @param [Array<String>] library_target_names The names of the targets
# that should be linked against.
#
# @return [void]
#
def link_targets_with_target(linked_project, library_target_names)
library_target_names.each do |library_target_name|
library_target = find_named_native_target_in_project(linked_project, library_target_name)
link_target_with_target(pod_target.target, library_target)
end
end
# Make sure a target is linked with another target.
#
# @param [PBXNativeTarget] main_target The target that should link against
# another target.
# @param [PBXNativeTarget] library_target The target that should be linked
# against.
#
# @return [void]
#
def link_target_with_target(main_target, library_target)
frameworks = main_target.project.frameworks_group
lib_name = library_target.product_reference.path.sub(/^lib/, '').sub(/\.a$/, '')
library_ref = frameworks.new_product_ref_for_target(lib_name, :static_library)
main_target.add_dependency(library_target)
main_target.frameworks_build_phase.add_file_reference(library_ref)
end
# @return [Hash<Pathname => Array<Specification>>] A hash of the Xcode
# project paths to the specifications that refer to it.
#
def linked_project_specs
specs_by_path = {}
pod_target.spec_consumers.each do |consumer|
proj = consumer.xcodeproj
next unless proj.present?
absolute_path = pod_root + proj['project']
specs_by_path[absolute_path] ||= []
specs_by_path[absolute_path] << consumer.spec
end
specs_by_path
end
# Finds the native target in an Xcode project with a given name.
# Raises an Informative error if the native target can't be found.
#
# @param [Project] project The project to search in
# @param [String] target_name The name of the target that we're
# looking for
#
# @return [PBXNativeTarget] The native target with the given name
#
def find_named_native_target_in_project(project, target_name)
target = project.targets.find do |target|
target_name == target.name && target.isa == 'PBXNativeTarget'
end
if target.nil?
raise Informative, "Could not find native target #{target_name} in project #{project.path}, specified in #{spec_file}"
end
target
end
# @return [String] the path where the pod target's specification is
# defined, if loaded from a file. (May be nil)
#
def spec_file
pod_target.root_spec.defined_in_file
end
#-----------------------------------------------------------------------#
end
end
end
......@@ -231,6 +231,12 @@ module Pod
# @return [Array<Strings>] The paths that can be deleted.
#
def clean_paths
has_xcodeproj = specs_by_platform.any? do |platform, specs|
specs.any? { |spec| spec.consumer(platform).xcodeproj.present? }
end
return [] if has_xcodeproj
cached_used = used_files
glob_options = File::FNM_DOTMATCH | File::FNM_CASEFOLD
files = Pathname.glob(root + "**/*", glob_options).map(&:to_s)
......
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Installer::LinkedDependenciesInstaller do
before do
@target_definition = Podfile::TargetDefinition.new('Pods', nil)
@file_accessor = fixture_file_accessor('banana-lib/BananaLib.podspec')
@project = Project.new(config.sandbox.project_path)
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@pod_target = PodTarget.new([@file_accessor.spec], @target_definition, config.sandbox)
@pod_target.file_accessors = [@file_accessor]
@pod_target.target = @project.new_target(:static_library, 'Pod-BananaLib', :ios)
@installer = Installer::LinkedDependenciesInstaller.new(@project, @file_accessor.root, @pod_target)
end
#-------------------------------------------------------------------------#
describe "Installation" do
describe "#install!" do
it "adds libraries to target when installing" do
@installer.expects(:add_libraries_to_target)
@installer.stubs(:add_linked_projects)
@installer.install!
end
it "adds linked projects when installing" do
@installer.expects(:add_linked_projects)
@installer.stubs(:add_libraries_to_target)
@installer.install!
end
end
it "adds subproject libraries to targets" do
spec = @file_accessor.spec
spec.xcodeproj = {
:project => '../SampleProject/Sample Lib/Sample Lib.xcodeproj',
:library_targets => ['Sample Lib']
}
@pod_target.stubs(:spec_consumers).returns([
spec.consumer(:ios)
])
# Add the linked project to the pods project, otherwise add_libraries_to_target will fail
path = SpecHelper::Fixture.fixture('SampleProject/Sample Lib/Sample Lib.xcodeproj')
@project.add_file_reference(path, @project.main_group)
# Check that the schemes are hidden
Xcodeproj::Project.any_instance.expects(:recreate_user_schemes).with(false)
@installer.send(:add_libraries_to_target)
@pod_target.target.frameworks_build_phase.files_references.map(&:path).should.include('libSample Lib.a')
end
it "adds linked projects" do
path = SpecHelper::Fixture.fixture('SampleProject/Sample Lib/Sample Lib.xcodeproj')
@installer.stubs(:linked_project_specs).returns({
path => [@file_accessor.spec]
})
@installer.send(:add_linked_projects)
@project.reference_for_path(path).isa.should.be == 'PBXFileReference'
end
end
#-------------------------------------------------------------------------#
describe "Private Helpers" do
it "opens a linked xcode project by path" do
@installer.send(:open_linked_xcode_project, "../SampleProject/SampleProject.xcodeproj").path.basename.to_s.should == 'SampleProject.xcodeproj'
end
it "does not open a linked xcode project if the path is incorrect" do
should.raise Informative do
@installer.send(:open_linked_xcode_project, "hello")
end.message.should.match /Could not open project/
end
it "links targets by name with the pod target" do
lib_project = Xcodeproj::Project.open(SpecHelper::Fixture.fixture('SampleProject/Sample Lib/Sample Lib.xcodeproj'))
@project.add_file_reference(lib_project.path, @project.main_group)
@installer.send(:link_targets_with_target, lib_project, ['Sample Lib'])
@pod_target.target.frameworks_build_phase.files_references.map(&:path).should.include('libSample Lib.a')
end
it "links a target with another target" do
sample_project = Xcodeproj::Project.open(SpecHelper::Fixture.fixture('SampleProject/SampleProject.xcodeproj'))
lib_project = Xcodeproj::Project.open(SpecHelper::Fixture.fixture('SampleProject/Sample Lib/Sample Lib.xcodeproj'))
sample_project.main_group.new_file(lib_project.path)
app_target = @installer.send(:find_named_native_target_in_project, sample_project, 'TestRunner')
lib_target = @installer.send(:find_named_native_target_in_project, lib_project, 'Sample Lib')
# Change the name of the lib_target product in order to test that we link
# against that and not just the target name.
lib_target.product_reference.path = 'libLib.a'
@installer.send(:link_target_with_target, app_target, lib_target)
app_target.dependencies.map(&:target).should.include(lib_target)
app_target.frameworks_build_phase.files_references.map(&:path).should.include('libLib.a')
end
it "finds that no specs specifies linked projects" do
@pod_target.stubs(:spec_consumers).returns([
@file_accessor.spec.consumer(:ios)
])
@installer.send(:linked_project_specs).should.be == {}
end
it "finds specs that specify linked projects" do
spec = @file_accessor.spec
spec.xcodeproj = { :project => 'hello' }
@pod_target.stubs(:spec_consumers).returns([
spec.consumer(:ios)
])
@installer.send(:linked_project_specs).should.be == {
(@file_accessor.root + 'hello') => [spec]
}
end
it "finds a native target in a project" do
project = Xcodeproj::Project.open(SpecHelper::Fixture.fixture('SampleProject/SampleProject.xcodeproj'))
@installer.send(:find_named_native_target_in_project, project, 'SampleProject').isa.should == 'PBXNativeTarget'
end
it "does not find a nonexistent native target in a project" do
project = Xcodeproj::Project.open(SpecHelper::Fixture.fixture('SampleProject/SampleProject.xcodeproj'))
should.raise Informative do
@installer.send(:find_named_native_target_in_project, project, 'SampleProject_nonexistent')
end.message.should.match /Could not find native target/
end
it "finds the spec file path correctly" do
@installer.send(:spec_file).should.be == @file_accessor.spec.defined_in_file
end
end
#-------------------------------------------------------------------------#
end
end
......@@ -152,6 +152,14 @@ module Pod
]
end
it "returns no clean paths for specs that have linked projects" do
spec = fixture_spec('banana-lib/BananaLib.podspec')
spec.xcodeproj = { :project => 'hello' }
specs_by_platform = { :ios => [spec] }
@installer = Installer::PodSourceInstaller.new(config.sandbox, specs_by_platform)
@installer.send(:clean_paths).should.be == []
end
it "returns the used files" do
@installer.send(:download_source)
paths = @installer.send(:used_files)
......
......@@ -63,6 +63,7 @@ module Pod
@installer.stubs(:install_file_references)
@installer.stubs(:install_libraries)
@installer.stubs(:link_aggregate_target)
@installer.stubs(:link_linked_dependencies)
@installer.stubs(:write_lockfiles)
@installer.stubs(:aggregate_targets).returns([])
@installer.unstub(:generate_pods_project)
......@@ -515,6 +516,25 @@ module Pod
end
#-------------------------------------------------------------------------#
describe "Private helpers" do
before do
@installer.send(:analyze)
@specs = @installer.pod_targets.map(&:specs).flatten
@spec = @specs.find { |spec| spec && spec.name == 'JSONKit' }
end
it "finds a pod from a given pod target" do
pod_target = @installer.pod_targets.find { |x| x.specs.first.root.name == 'JSONKit' }
@installer.send(:pod_for_target, pod_target).name.should == 'JSONKit'
end
end
#-------------------------------------------------------------------------#
end
end
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