Commit cc958fde authored by Fabio Pelosin's avatar Fabio Pelosin

[UserProjectIntegrator] Clean up.

parent 2421ef03
......@@ -6,69 +6,101 @@ require 'active_support/core_ext/array/conversions'
module Pod
class Installer
# The {UserProjectIntegrator} integrates the libraries generated by
# TargetDefinitions of the {Podfile} with their correspondent user project.
#
class UserProjectIntegrator
include Pod::Config::Mixin
# @return [Podfile] the podfile that should be integrated with the user
# projects.
#
attr_reader :podfile
# @param [Podfile] podfile @see #podfile.
#
def initialize(podfile)
@podfile = podfile
end
# Integrates the user projects associated with the {TargetDefinitions}
# with the Pods project and its products.
#
# @return [void]
#
def integrate!
create_workspace!
# Only need to write out the user's project if any of the target
# integrators actually did some work.
target_integrators.map(&:integrate!)
end
# creates the workspace containing the Pods project and the user projects
# should be saved.
#
# @return [void]
#
def create_workspace!
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
[pods_project_path, *user_project_paths].each do |project_path|
project_path = project_path.relative_path_from(config.project_root).to_s
workspace << project_path unless workspace.include?(project_path)
end
workspace.save_as(workspace_path)
UI.notice "From now on use `#{workspace_path.basename}'." unless workspace_path.exist?
end
# @return [Pathname] the path where the workspace containing the Pods
# project and the user projects should be saved.
#
def workspace_path
@podfile.workspace || raise(Informative, "Could not automatically select an Xcode workspace. " \
podfile.workspace || raise(Informative, "Could not automatically select an Xcode workspace. " \
"Specify one in your Podfile.")
end
# @return [Pathname] the path of the Pods project.
#
def pods_project_path
config.project_root + "Pods/Pods.xcodeproj"
end
# @return [Array<TargetIntegrator>] the target integrators for the non
# empty target definitions.
#
def target_integrators
@target_integrators ||= @podfile.target_definitions.values.map do |definition|
TargetIntegrator.new(definition) unless definition.empty?
end.compact
end
# @return [Array<Pathname>] the paths of all the user projects referenced
# by the target definitons.
#
def user_project_paths
@podfile.target_definitions.values.map do |td|
next if td.empty?
td.user_project.path #|| raise(Informative, "Could not resolve the Xcode project in which the " \
# "`#{td.name}' target should be integrated.")
end.compact
td.user_project.path
end.compact.uniq
end
def create_workspace!
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
[pods_project_path, *user_project_paths].each do |project_path|
project_path = project_path.relative_path_from(config.project_root).to_s
workspace << project_path unless workspace.include?(project_path)
end
unless workspace_path.exist? || config.silent?
UI.notice "From now on use `#{workspace_path.basename}'."
end
workspace.save_as(workspace_path)
end
#-------------------------------------------------------------------------#
# This class is responsible for integrating the library generated by a
# {TargetDefinition} with its destination project.
#
class TargetIntegrator
include Pod::Config::Mixin
# @return [TargetDefinition]
# the target definition whose library should be integrated.
#
attr_reader :target_definition
# @param [TargetDefinition] target_definition @see #target_definition
#
def initialize(target_definition)
@target_definition = target_definition
end
def inspect
"#<#{self.class} for target `#{@target_definition.label}'>"
end
# Integrates the user project targets. Only the targets that do **not**
# already have the Pods library in their frameworks build phase are
# processed.
......@@ -77,112 +109,162 @@ module Pod
#
def integrate!
return if targets.empty?
UI.section("Integrating `#{@target_definition.lib_name}' into #{'target'.pluralize(targets.size)} " \
"`#{targets.map(&:name).to_sentence}' of Xcode project #{UI.path user_project_path}.") do
message = "Integrating `#{target_definition.lib_name}' into " \
"#{'target'.pluralize(targets.size)} `#{targets.map(&:name).to_sentence}' " \
"of project #{UI.path user_project_path}."
UI.section(message) do
add_xcconfig_base_configuration
add_pods_library
add_copy_resources_script_phase
user_project.save_as(@target_definition.user_project.path)
user_project.save_as(target_definition.user_project.path)
end
end
# @return [Pathname] the path of the user project.
# @return [Xcodeproj::Project]
# the project that will be integrated.
#
# @raises If the path doesn't exits.
def user_project
@user_project ||= Xcodeproj::Project.new(user_project_path)
end
# Returns the path of the user project that the {TargetDefinition}
# should integrate.
#
# @raises If the project is implicit and there are multiple projects.
#
# @raises If the path doesn't exits.
#
# @return [Pathname] the path of the user project.
#
def user_project_path
if path = @target_definition.user_project.path
path = target_definition.user_project.path
unless path
raise Informative, "Could not automatically select an Xcode project.\n" \
"Specify one in your Podfile like so:\n\n xcodeproj 'path/to/NAME.xcodeproj'"
end
unless path.exist?
raise Informative, "The Xcode project `#{path}' does not exist."
end
path
else
raise Informative, "Could not automatically select an Xcode project.\n" \
"Specify one in your Podfile like so:\n\n" \
" xcodeproj 'path/to/XcodeProject'"
end
end
# @return [Xcodeproj::Project] Returns the project of the user.
# Returns a list of the targets from the project of {TargetDefinition}
# that needs to be integrated.
#
def user_project
@user_project ||= Xcodeproj::Project.new(user_project_path)
end
# This returns a list of the targets from the user’s project to which
# this Pods static library should be linked. If no explicit target was
# specified, then the first encountered target is assumed.
# The method first looks if there is a target specified with the
# `link_with` option of the {TargetDefinition}. Otherwise it looks for
# the target that has the same name of the target definition.
# Finally if no target was found the first encountered target is
# returned (it is assumed to be the one to integrate in simple
# projects).
#
# In addition this will only return targets that do **not** already
# have the Pods library in their frameworks build phase.
#
# @return [Array<PBXNativeTarget>] Returns the list of targets that
# the Pods lib should be linked with.
# @return [Array<PBXNativeTarget>]
# the list of targets that the Pods lib should be linked with.
#
def targets
@targets ||= begin
if link_with = @target_definition.link_with
# Find explicitly linked targets.
user_project.targets.select do |target|
link_with.include? target.name
end
elsif @target_definition.name != :default
# Find the target with the matching name.
target = user_project.targets.find { |target| target.name == @target_definition.name.to_s }
raise Informative, "Unable to find a target named `#{@target_definition.name.to_s}'" unless target
[target]
unless @targets
if link_with = target_definition.link_with
targets = user_project.targets.select { |t| link_with.include? t.name }
raise Informative, "Unable to find a target named `#{link_with.to_sentence}` to link with target definition `#{target_definition.name}`" if targets.empty?
elsif target_definition.name != :default
target = user_project.targets.find { |t| t.name == target_definition.name.to_s }
targets = [target]
raise Informative, "Unable to find a target named `#{target_definition.name.to_s}`" unless target
else
# Default to the first, which in a simple project is probably an app target.
[user_project.targets.first]
end.reject do |target|
# Reject any target that already has this Pods library in one of its frameworks build phases
target.frameworks_build_phase.files.any? { |build_file| build_file.file_ref.name == @target_definition.lib_name }
targets = [user_project.targets.first]
raise Informative, "Unable to find a target" if targets.empty?
end
@targets = targets.reject do |target|
target.frameworks_build_phase.files.any? { |bf| bf.file_ref.name == target_definition.lib_name }
end
end
@targets
end
#@!group Integration
# Adds the `xcconfig` configurations files generated for the current
# {TargetDefinition} to the build configurations of the targets that
# should be integrated.
#
# It also checks if any build setting of the build configurations
# overrides the `xcconfig` file and warns the user.
#
# TODO: If the xcconfig is already set don't override it and inform the
# user.
#
# @return [void]
#
def add_xcconfig_base_configuration
xcconfig = user_project.new_file(@target_definition.xcconfig_relative_path)
xcconfig_ref = user_project.new_file(target_definition.xcconfig_relative_path)
targets.each do |target|
config_build_names_by_overriden_key = {}
check_overridden_build_settings(target_definition.xcconfig, target)
target.build_configurations.each do |config|
config_name = config.name
if @target_definition.xcconfig
@target_definition.xcconfig.attributes.each do |key, value|
target_value = config.build_settings[key]
if target_value && !target_value.include?('$(inherited)')
config_build_names_by_overriden_key[key] ||= []
config_build_names_by_overriden_key[key] << config_name
config.base_configuration_reference = xcconfig_ref
end
end
end
config.base_configuration_reference = xcconfig
# Informs the user about any build setting of the target which might
# override the given xcconfig file.
#
# @return [void]
#
def check_overridden_build_settings(xcconfig, target)
return unless xcconfig
configs_by_overridden_key = {}
target.build_configurations.each do |config|
xcconfig.attributes.keys.each do |key|
configs_by_overridden_key[key] ||= []
target_value = config.build_settings[key]
if target_value && !target_value.include?('$(inherited)')
configs_by_overridden_key[key] << config.name
end
end
config_build_names_by_overriden_key.each do |key, config_build_names|
name = "#{target.name} [#{config_build_names.join(' - ')}]"
configs_by_overridden_key.each do |key, config_names|
name = "#{target.name} [#{config_names.join(' - ')}]"
actions = [ "Use the `$(inherited)' flag, or", "Remove the build settings from the target." ]
UI.warn("The target `#{name}' overrides the `#{key}' build setting defined in `#{@target_definition.xcconfig_relative_path}'.", actions)
UI.warn("The target `#{name}' overrides the `#{key}' build setting defined in `#{target_definition.xcconfig_relative_path}'.", actions)
end
end
end
# Adds a file reference to the library of the {TargetDefinition} and
# adds it to the frameworks build phase of the targets.
#
# @return [void]
#
def add_pods_library
frameworks = user_project.frameworks_group
pods_library = frameworks.new_static_library(@target_definition.label)
pods_library = frameworks.new_static_library(target_definition.label)
targets.each do |target|
target.frameworks_build_phase.add_file_reference(pods_library)
end
end
# Adds a shell script build phase responsible to copy the resources
# generated by the TargetDefinition to the bundle of the product of the
# targets.
#
# @return [void]
#
def add_copy_resources_script_phase
targets.each do |target|
phase = target.new_shell_script_build_phase('Copy Pods Resources')
phase.shell_script = %{"#{@target_definition.copy_resources_script_relative_path}"\n}
phase.shell_script = %{"#{target_definition.copy_resources_script_relative_path}"\n}
end
end
def inspect
"#<#{self.class} for target `#{target_definition.label}'>"
end
end
end
......
......@@ -3,24 +3,13 @@ require File.expand_path('../../spec_helper', __FILE__)
describe Pod::Installer::UserProjectIntegrator do
extend SpecHelper::TemporaryDirectory
def integrate!
@integrator = Pod::Installer::UserProjectIntegrator.new(@podfile)
@integrator.integrate!
@sample_project = Xcodeproj::Project.new(@sample_project_path)
end
before do
config.silent = true
@sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
config.project_root = @sample_project_path.dirname
sample_project_path = @sample_project_path
sample_project_path = SpecHelper.create_sample_app_copy_from_fixture('SampleProject')
config.project_root = sample_project_path.dirname
@podfile = Pod::Podfile.new do
platform :ios
xcodeproj sample_project_path, 'Test' => :debug
link_with 'SampleProject' # this is an app target!
pod 'JSONKit'
target :test_runner, :exclusive => true do
......@@ -28,12 +17,10 @@ describe Pod::Installer::UserProjectIntegrator do
pod 'Kiwi'
end
end
@sample_project = Xcodeproj::Project.new(@sample_project_path)
end
before do
integrate!
@sample_project_path = sample_project_path
@integrator = Pod::Installer::UserProjectIntegrator.new(@podfile)
@integrator.integrate!
@sample_project = Xcodeproj::Project.new(sample_project_path)
end
it 'adds references to the Pods static libraries to the Frameworks group' 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