Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
CocoaPods/lib/cocoapods/installer/user_project_integrator.rb
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
280 lines (251 sloc)
10.6 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'xcodeproj/workspace' | |
require 'xcodeproj/project' | |
require 'active_support/core_ext/string/inflections' | |
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 | |
# projects. | |
# | |
class UserProjectIntegrator | |
autoload :TargetIntegrator, 'cocoapods/installer/user_project_integrator/target_integrator' | |
# @return [Podfile] the podfile that should be integrated with the user | |
# projects. | |
# | |
attr_reader :podfile | |
# @return [Sandbox] The sandbox used for this installation. | |
# | |
attr_reader :sandbox | |
# @return [Pathname] the path of the installation. | |
# | |
# @todo This is only used to compute the workspace path in case that it | |
# should be inferred by the project. If the workspace should be in | |
# the same dir of the project, this could be removed. | |
# | |
attr_reader :installation_root | |
# @return [Array<AggregateTarget>] the targets represented in the Podfile. | |
# | |
attr_reader :targets | |
# @return [Array<AggregateTarget>] the targets that require integration. This will always be equal or a smaller | |
# subset of #targets. | |
# | |
attr_reader :targets_to_integrate | |
# @return [Boolean] whether to use input/output paths for build phase scripts | |
# | |
attr_reader :use_input_output_paths | |
alias use_input_output_paths? use_input_output_paths | |
# Initialize a new instance | |
# | |
# @param [Podfile] podfile @see #podfile | |
# @param [Sandbox] sandbox @see #sandbox | |
# @param [Pathname] installation_root @see #installation_root | |
# @param [Array<AggregateTarget>] targets @see #targets | |
# @param [Array<AggregateTarget>] targets_to_integrate @see #targets_to_integrate | |
# @param [Boolean] use_input_output_paths @see #use_input_output_paths | |
# | |
def initialize(podfile, sandbox, installation_root, targets, targets_to_integrate, use_input_output_paths: true) | |
@podfile = podfile | |
@sandbox = sandbox | |
@installation_root = installation_root | |
@targets = targets | |
@targets_to_integrate = targets_to_integrate | |
@use_input_output_paths = use_input_output_paths | |
end | |
# Integrates the user projects associated with the {TargetDefinitions} | |
# with the Pods project and its products. | |
# | |
# @return [void] | |
# | |
def integrate! | |
create_workspace | |
deintegrated_projects = deintegrate_removed_targets | |
integrate_user_targets | |
warn_about_xcconfig_overrides | |
projects_to_save = (user_projects_to_integrate + deintegrated_projects).uniq | |
save_projects(projects_to_save) | |
end | |
#-----------------------------------------------------------------------# | |
private | |
# @!group Integration steps | |
# Creates and saved the workspace containing the Pods project and the | |
# user projects, if needed. | |
# | |
# @note If the workspace already contains the projects it is not saved | |
# to avoid Xcode from displaying the revert dialog: `Do you want to | |
# keep the Xcode version or revert to the version on disk?` | |
# | |
# @return [void] | |
# | |
def create_workspace | |
all_projects = user_project_paths.sort.push(sandbox.project_path).uniq | |
file_references = all_projects.map do |path| | |
relative_path = path.relative_path_from(workspace_path.dirname).to_s | |
Xcodeproj::Workspace::FileReference.new(relative_path, 'group') | |
end | |
if workspace_path.exist? | |
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path) | |
new_file_references = file_references - workspace.file_references | |
unless new_file_references.empty? | |
new_file_references.each { |fr| workspace << fr } | |
workspace.save_as(workspace_path) | |
end | |
else | |
UI.notice "Please close any current Xcode sessions and use `#{workspace_path.basename}` for this project from now on." | |
workspace = Xcodeproj::Workspace.new(*file_references) | |
workspace.save_as(workspace_path) | |
end | |
end | |
# Deintegrates the targets of the user projects that are no longer part of the installation. | |
# | |
# @return [Array<Xcodeproj::PBXProject>] The list of projects that were deintegrated. | |
# | |
def deintegrate_removed_targets | |
Config.instance.with_changes(:silent => true) do | |
deintegrator = Deintegrator.new | |
all_project_targets = user_projects.flat_map(&:native_targets).uniq | |
all_native_targets = targets.flat_map(&:user_targets).uniq | |
targets_to_deintegrate = all_project_targets - all_native_targets | |
targets_to_deintegrate.each do |target| | |
deintegrator.deintegrate_target(target) | |
end | |
return targets_to_deintegrate.map(&:project).select(&:dirty?).uniq | |
end | |
end | |
# Integrates the targets of the user projects with the libraries | |
# generated from the {Podfile}. | |
# | |
# @note {TargetDefinition} without dependencies are skipped prevent | |
# creating empty libraries for targets definitions which are only | |
# wrappers for others. | |
# | |
# @return [void] | |
# | |
def integrate_user_targets | |
target_integrators = targets_to_integrate.sort_by(&:name).map do |target| | |
TargetIntegrator.new(target, :use_input_output_paths => use_input_output_paths?) | |
end | |
target_integrators.each(&:integrate!) | |
end | |
# Save all user projects. | |
# | |
# @param [Array<Xcodeproj::PBXProject>] projects The projects to save. | |
# | |
# @return [void] | |
# | |
def save_projects(projects) | |
projects.each do |project| | |
if project.dirty? | |
project.save | |
else | |
# There is a bug in Xcode where the process of deleting and | |
# re-creating the xcconfig files used in the build | |
# configuration cause building the user project to fail until | |
# Xcode is relaunched. | |
# | |
# Touching/saving the project causes Xcode to reload these. | |
# | |
# https://github.com/CocoaPods/CocoaPods/issues/2665 | |
FileUtils.touch(project.path + 'project.pbxproj') | |
end | |
end | |
end | |
IGNORED_KEYS = %w(CODE_SIGN_IDENTITY).freeze | |
INHERITED_FLAGS = %w($(inherited) ${inherited}).freeze | |
# Checks whether the settings of the CocoaPods generated xcconfig are | |
# overridden by the build configuration of a target and prints a | |
# warning to inform the user if needed. | |
# | |
def warn_about_xcconfig_overrides | |
targets_to_integrate.each do |aggregate_target| | |
aggregate_target.user_targets.each do |user_target| | |
user_target.build_configurations.each do |config| | |
xcconfig = aggregate_target.xcconfigs[config.name] | |
if xcconfig | |
(xcconfig.to_hash.keys - IGNORED_KEYS).each do |key| | |
target_values = config.build_settings[key] | |
if target_values && | |
!INHERITED_FLAGS.any? { |flag| target_values.include?(flag) } | |
print_override_warning(aggregate_target, user_target, config, key) | |
end | |
end | |
end | |
end | |
end | |
end | |
end | |
private | |
# @!group Private Helpers | |
#-----------------------------------------------------------------------# | |
# @return [Pathname] the path where the workspace containing the Pods | |
# project and the user projects should be saved. | |
# | |
def workspace_path | |
if podfile.workspace_path | |
declared_path = podfile.workspace_path | |
path_with_ext = File.extname(declared_path) == '.xcworkspace' ? declared_path : "#{declared_path}.xcworkspace" | |
podfile_dir = File.dirname(podfile.defined_in_file || '') | |
absolute_path = File.expand_path(path_with_ext, podfile_dir) | |
Pathname.new(absolute_path) | |
elsif user_project_paths.count == 1 | |
project = user_project_paths.first.basename('.xcodeproj') | |
installation_root + "#{project}.xcworkspace" | |
else | |
raise Informative, 'Could not automatically select an Xcode ' \ | |
"workspace. Specify one in your Podfile like so:\n\n" \ | |
" workspace 'path/to/Workspace.xcworkspace'\n" | |
end | |
end | |
# @return [Array<Xcodeproj::Project>] the projects of all the targets that require integration. | |
# | |
# @note Empty target definitions are ignored. | |
# | |
def user_projects_to_integrate | |
targets_to_integrate.map(&:user_project).compact.uniq | |
end | |
# @return [Array<Xcodeproj::Project>] the projects of all the targets regardless of whether they are integrated | |
# or not. | |
# | |
# @note Empty target definitions are ignored. | |
# | |
def user_projects | |
targets.map(&:user_project).compact.uniq | |
end | |
# @return [Array<Pathname>] the paths of all the user projects from all targets regardless of whether they are | |
# integrated or not. | |
# | |
# @note Empty target definitions are ignored. | |
# | |
def user_project_paths | |
targets.map(&:user_project_path).compact.uniq | |
end | |
# Prints a warning informing the user that a build configuration of | |
# the integrated target is overriding the CocoaPods build settings. | |
# | |
# @param [Target::AggregateTarget] aggregate_target | |
# The umbrella target. | |
# | |
# @param [Xcodeproj::PBXNativeTarget] user_target | |
# The native target. | |
# | |
# @param [Xcodeproj::XCBuildConfiguration] config | |
# The build configuration. | |
# | |
# @param [String] key | |
# The key of the overridden build setting. | |
# | |
def print_override_warning(aggregate_target, user_target, config, key) | |
actions = [ | |
'Use the `$(inherited)` flag, or', | |
'Remove the build settings from the target.', | |
] | |
message = "The `#{user_target.name} [#{config.name}]` " \ | |
"target overrides the `#{key}` build setting defined in " \ | |
"`#{aggregate_target.xcconfig_relative_path(config.name)}'. " \ | |
'This can lead to problems with the CocoaPods installation' | |
UI.warn(message, actions) | |
end | |
#-----------------------------------------------------------------------# | |
end | |
end | |
end |