Commit 7646e92b authored by Daniel Tomlinson's avatar Daniel Tomlinson

Move Pods project generation to an Xcode namespace

* Fix Dangerfile Rubocop violations

* Update CHANGELOG.md

* Update docs

* Don't use Type = Type::Type in tests
parent 2782866f
...@@ -4,9 +4,6 @@ inherit_from: ...@@ -4,9 +4,6 @@ inherit_from:
#- CocoaPods ------------------------------------------------------------------ #- CocoaPods ------------------------------------------------------------------
ParameterLists:
CountKeywordArgs: false
AllCops: AllCops:
Include: Include:
- Dangerfile - Dangerfile
......
...@@ -14,6 +14,9 @@ Documentation: ...@@ -14,6 +14,9 @@ Documentation:
#- CocoaPods -----------------------------------------------------------------# #- CocoaPods -----------------------------------------------------------------#
ParameterLists:
CountKeywordArgs: false
# We adopted raise instead of fail. # We adopted raise instead of fail.
SignalException: SignalException:
EnforcedStyle: only_raise EnforcedStyle: only_raise
......
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config` # `rubocop --auto-gen-config`
# on 2016-03-03 16:16:26 -0600 using RuboCop version 0.37.2. # on 2016-06-09 08:12:30 -0500 using RuboCop version 0.37.2.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again. # versions of RuboCop, may require this file to be generated again.
# Offense count: 4 # Offense count: 7
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect. # Configuration parameters: AlignWith, SupportedStyles, AutoCorrect.
# SupportedStyles: keyword, variable, start_of_line # SupportedStyles: keyword, variable, start_of_line
...@@ -22,21 +22,38 @@ Lint/IneffectiveAccessModifier: ...@@ -22,21 +22,38 @@ Lint/IneffectiveAccessModifier:
- 'lib/cocoapods/installer/analyzer/locking_dependency_analyzer.rb' - 'lib/cocoapods/installer/analyzer/locking_dependency_analyzer.rb'
- 'lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb' - 'lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb'
# Offense count: 1
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect.
Lint/LiteralInInterpolation:
Exclude:
- 'Dangerfile'
# Offense count: 1 # Offense count: 1
Lint/NonLocalExitFromIterator: Lint/NonLocalExitFromIterator:
Exclude: Exclude:
- 'spec/unit/installer_spec.rb' - 'spec/unit/installer_spec.rb'
# Offense count: 1
Lint/UnreachableCode:
Exclude:
- 'Dangerfile'
# Offense count: 40 # Offense count: 40
Lint/UselessAccessModifier: Lint/UselessAccessModifier:
Enabled: false Enabled: false
# Offense count: 1853 # Offense count: 2035
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes. # Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
# URISchemes: http, https # URISchemes: http, https
Metrics/LineLength: Metrics/LineLength:
Max: 279 Max: 279
# Offense count: 1
# Configuration parameters: CountKeywordArgs.
ParameterLists:
Max: 6
# Offense count: 5 # Offense count: 5
# Cop supports --auto-correct. # Cop supports --auto-correct.
Performance/Casecmp: Performance/Casecmp:
...@@ -55,20 +72,12 @@ Performance/RedundantBlockCall: ...@@ -55,20 +72,12 @@ Performance/RedundantBlockCall:
- 'lib/cocoapods/installer/analyzer/pod_variant_set.rb' - 'lib/cocoapods/installer/analyzer/pod_variant_set.rb'
- 'spec/unit/validator_spec.rb' - 'spec/unit/validator_spec.rb'
# Offense count: 2
# Cop supports --auto-correct.
Performance/RedundantMatch:
Exclude:
- 'spec/**/*'
- 'lib/cocoapods/command/lib.rb'
- 'lib/cocoapods/external_sources/podspec_source.rb'
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: MaxKeyValuePairs. # Configuration parameters: MaxKeyValuePairs.
Performance/RedundantMerge: Performance/RedundantMerge:
Exclude: Exclude:
- 'lib/cocoapods/installer/target_installer.rb' - 'lib/cocoapods/installer/xcode/pods_project_generator/target_installer.rb'
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
...@@ -76,7 +85,7 @@ Performance/StringReplacement: ...@@ -76,7 +85,7 @@ Performance/StringReplacement:
Exclude: Exclude:
- 'spec/functional/command/spec_spec.rb' - 'spec/functional/command/spec_spec.rb'
# Offense count: 23 # Offense count: 22
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles. # Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: prefer_alias, prefer_alias_method # SupportedStyles: prefer_alias, prefer_alias_method
...@@ -106,12 +115,11 @@ Style/AlignParameters: ...@@ -106,12 +115,11 @@ Style/AlignParameters:
Exclude: Exclude:
- 'lib/cocoapods/project.rb' - 'lib/cocoapods/project.rb'
# Offense count: 7 # Offense count: 6
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: SingleLineConditionsOnly. # Configuration parameters: SingleLineConditionsOnly.
Style/ConditionalAssignment: Style/ConditionalAssignment:
Exclude: Exclude:
- 'lib/cocoapods/command/init.rb'
- 'lib/cocoapods/command/repo/push.rb' - 'lib/cocoapods/command/repo/push.rb'
- 'lib/cocoapods/external_sources/abstract_external_source.rb' - 'lib/cocoapods/external_sources/abstract_external_source.rb'
- 'lib/cocoapods/sandbox/file_accessor.rb' - 'lib/cocoapods/sandbox/file_accessor.rb'
...@@ -137,32 +145,30 @@ Style/IfInsideElse: ...@@ -137,32 +145,30 @@ Style/IfInsideElse:
Style/IndentArray: Style/IndentArray:
EnforcedStyle: consistent EnforcedStyle: consistent
# Offense count: 27 # Offense count: 24
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: aligned, indented # SupportedStyles: aligned, indented
Style/MultilineMethodCallIndentation: Style/MultilineMethodCallIndentation:
Enabled: false Enabled: false
# Offense count: 1 # Offense count: 3
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
# SupportedStyles: aligned, indented # SupportedStyles: aligned, indented
Style/MultilineOperationIndentation: Style/MultilineOperationIndentation:
Enabled: false Enabled: false
# Offense count: 16 # Offense count: 12
# Cop supports --auto-correct. # Cop supports --auto-correct.
Style/MutableConstant: Style/MutableConstant:
Exclude: Exclude:
- 'bin/sandbox-pod' - 'bin/sandbox-pod'
- 'lib/cocoapods/command/inter_process_communication.rb'
- 'lib/cocoapods/command/lib.rb'
- 'lib/cocoapods/config.rb' - 'lib/cocoapods/config.rb'
- 'lib/cocoapods/generator/copy_resources_script.rb' - 'lib/cocoapods/generator/copy_resources_script.rb'
- 'lib/cocoapods/installer.rb' - 'lib/cocoapods/installer.rb'
- 'lib/cocoapods/installer/target_installer/pod_target_installer.rb'
- 'lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb' - 'lib/cocoapods/installer/user_project_integrator/target_integrator/xcconfig_integrator.rb'
- 'lib/cocoapods/installer/xcode/pods_project_generator/pod_target_installer.rb'
- 'lib/cocoapods/project.rb' - 'lib/cocoapods/project.rb'
- 'spec/unit/installer/user_project_integrator/target_integrator/xcconfig_integrator_spec.rb' - 'spec/unit/installer/user_project_integrator/target_integrator/xcconfig_integrator_spec.rb'
...@@ -173,16 +179,8 @@ Style/NestedParenthesizedCalls: ...@@ -173,16 +179,8 @@ Style/NestedParenthesizedCalls:
# Offense count: 1 # Offense count: 1
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: AllowSafeAssignment.
Style/ParenthesesAroundCondition:
Exclude:
- 'lib/cocoapods/command/inter_process_communication.rb'
# Offense count: 2
# Cop supports --auto-correct.
Style/RedundantParentheses: Style/RedundantParentheses:
Exclude: Exclude:
- 'lib/cocoapods/sources_manager.rb'
- 'spec/unit/sandbox_spec.rb' - 'spec/unit/sandbox_spec.rb'
# Offense count: 2 # Offense count: 2
...@@ -191,15 +189,14 @@ Style/RedundantSelf: ...@@ -191,15 +189,14 @@ Style/RedundantSelf:
Exclude: Exclude:
- 'lib/cocoapods/command.rb' - 'lib/cocoapods/command.rb'
# Offense count: 16 # Offense count: 13
# Cop supports --auto-correct. # Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed # SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral: Style/RegexpLiteral:
Exclude: Exclude:
- 'lib/cocoapods/installer/file_references_installer.rb' - 'lib/cocoapods/installer/xcode/pods_project_generator/file_references_installer.rb'
- 'lib/cocoapods/sandbox/path_list.rb' - 'lib/cocoapods/sandbox/path_list.rb'
- 'lib/cocoapods/sources_manager.rb'
- 'lib/cocoapods/validator.rb' - 'lib/cocoapods/validator.rb'
- 'spec/integration.rb' - 'spec/integration.rb'
- 'spec/unit/external_sources/path_source_spec.rb' - 'spec/unit/external_sources/path_source_spec.rb'
...@@ -228,10 +225,8 @@ Style/UnneededInterpolation: ...@@ -228,10 +225,8 @@ Style/UnneededInterpolation:
- 'lib/cocoapods/validator.rb' - 'lib/cocoapods/validator.rb'
- 'spec/spec_helper/temporary_repos.rb' - 'spec/spec_helper/temporary_repos.rb'
# Offense count: 4 # Offense count: 2
Style/ZeroLengthPredicate: Style/ZeroLengthPredicate:
Exclude: Exclude:
- 'lib/cocoapods/command/init.rb' - 'lib/cocoapods/command/init.rb'
- 'lib/cocoapods/command/project.rb'
- 'lib/cocoapods/installer/podfile_validator.rb' - 'lib/cocoapods/installer/podfile_validator.rb'
- 'lib/cocoapods/sources_manager.rb'
...@@ -8,6 +8,10 @@ To install release candidates run `[sudo] gem install cocoapods --pre` ...@@ -8,6 +8,10 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
##### Enhancements ##### Enhancements
* Move Pods Project generation to an `Xcode` Namespace.
[Daniel Tomlinson](https://github.com/dantoml)
[#5480](https://github.com/CocoaPods/CocoaPods/pull/5480)
* Add the ability to inhibit swift warnings. * Add the ability to inhibit swift warnings.
[Peter Ryszkiewicz](https://github.com/pRizz) [Peter Ryszkiewicz](https://github.com/pRizz)
[#5414](https://github.com/CocoaPods/CocoaPods/pull/5414) [#5414](https://github.com/CocoaPods/CocoaPods/pull/5414)
......
...@@ -11,13 +11,13 @@ end ...@@ -11,13 +11,13 @@ end
# Ensure a clean commits history # Ensure a clean commits history
if commits.any? { |c| c.message =~ /^Merge branch '#{branch_for_merge}'/ } if commits.any? { |c| c.message =~ /^Merge branch '#{branch_for_merge}'/ }
fail("Please rebase to get rid of the merge commits in this PR") fail('Please rebase to get rid of the merge commits in this PR')
end end
# Request a CHANGELOG entry, and give an example # Request a CHANGELOG entry, and give an example
has_app_changes = !modified_files.grep(/lib/).empty? has_app_changes = !modified_files.grep(/lib/).empty?
if !modified_files.include?("CHANGELOG.md") && has_app_changes if !modified_files.include?('CHANGELOG.md') && has_app_changes
fail("Please include a CHANGELOG entry to credit yourself! \nYou can find it at [CHANGELOG.md](https://github.com/CocoaPods/CocoaPods/blob/master/CHANGELOG.md).", sticky: false) fail('Please include a CHANGELOG entry to credit yourself! \nYou can find it at [CHANGELOG.md](https://github.com/CocoaPods/CocoaPods/blob/master/CHANGELOG.md).', :sticky => false)
markdown <<-MARKDOWN markdown <<-MARKDOWN
Here's an example of your CHANGELOG entry: Here's an example of your CHANGELOG entry:
......
...@@ -29,9 +29,7 @@ module Pod ...@@ -29,9 +29,7 @@ module Pod
# source control. # source control.
# #
class Installer class Installer
autoload :AggregateTargetInstaller, 'cocoapods/installer/target_installer/aggregate_target_installer'
autoload :Analyzer, 'cocoapods/installer/analyzer' autoload :Analyzer, 'cocoapods/installer/analyzer'
autoload :FileReferencesInstaller, 'cocoapods/installer/file_references_installer'
autoload :InstallationOptions, 'cocoapods/installer/installation_options' autoload :InstallationOptions, 'cocoapods/installer/installation_options'
autoload :PostInstallHooksContext, 'cocoapods/installer/post_install_hooks_context' autoload :PostInstallHooksContext, 'cocoapods/installer/post_install_hooks_context'
autoload :PreInstallHooksContext, 'cocoapods/installer/pre_install_hooks_context' autoload :PreInstallHooksContext, 'cocoapods/installer/pre_install_hooks_context'
...@@ -40,9 +38,8 @@ module Pod ...@@ -40,9 +38,8 @@ module Pod
autoload :PodfileValidator, 'cocoapods/installer/podfile_validator' autoload :PodfileValidator, 'cocoapods/installer/podfile_validator'
autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer' autoload :PodSourceInstaller, 'cocoapods/installer/pod_source_installer'
autoload :PodSourcePreparer, 'cocoapods/installer/pod_source_preparer' autoload :PodSourcePreparer, 'cocoapods/installer/pod_source_preparer'
autoload :PodTargetInstaller, 'cocoapods/installer/target_installer/pod_target_installer'
autoload :TargetInstaller, 'cocoapods/installer/target_installer'
autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator' autoload :UserProjectIntegrator, 'cocoapods/installer/user_project_integrator'
autoload :Xcode, 'cocoapods/installer/xcode'
include Config::Mixin include Config::Mixin
include InstallationOptions::Mixin include InstallationOptions::Mixin
...@@ -163,15 +160,25 @@ module Pod ...@@ -163,15 +160,25 @@ module Pod
end end
end end
def generate_pods_project #-------------------------------------------------------------------------#
# @!group Pods Project Generation
private
def create_generator
Xcode::PodsProjectGenerator.new(aggregate_targets, sandbox, pod_targets, analysis_result, installation_options, config)
end
# Generate the 'Pods/Pods.xcodeproj' project.
#
def generate_pods_project(generator = create_generator)
UI.section 'Generating Pods project' do UI.section 'Generating Pods project' do
prepare_pods_project generator.generate!
install_file_references @pods_project = generator.project
install_libraries
set_target_dependencies
run_podfile_post_install_hooks run_podfile_post_install_hooks
write_pod_project generator.write
share_development_pod_schemes generator.share_development_pod_schemes
write_lockfiles write_lockfiles
end end
end end
...@@ -551,181 +558,6 @@ module Pod ...@@ -551,181 +558,6 @@ module Pod
end end
end end
# Creates the Pods project from scratch if it doesn't exists.
#
# @return [void]
#
# @todo Clean and modify the project if it exists.
#
def prepare_pods_project
UI.message '- Creating Pods project' do
@pods_project = if object_version = aggregate_targets.map(&:user_project).compact.map { |p| p.object_version.to_i }.min
Pod::Project.new(sandbox.project_path, false, object_version)
else
Pod::Project.new(sandbox.project_path)
end
analysis_result.all_user_build_configurations.each do |name, type|
@pods_project.add_build_configuration(name, type)
end
pod_names = pod_targets.map(&:pod_name).uniq
pod_names.each do |pod_name|
local = sandbox.local?(pod_name)
path = sandbox.pod_dir(pod_name)
was_absolute = sandbox.local_path_was_absolute?(pod_name)
@pods_project.add_pod_group(pod_name, path, local, was_absolute)
end
if config.podfile_path
@pods_project.add_podfile(config.podfile_path)
end
sandbox.project = @pods_project
platforms = aggregate_targets.map(&:platform)
osx_deployment_target = platforms.select { |p| p.name == :osx }.map(&:deployment_target).min
ios_deployment_target = platforms.select { |p| p.name == :ios }.map(&:deployment_target).min
watchos_deployment_target = platforms.select { |p| p.name == :watchos }.map(&:deployment_target).min
tvos_deployment_target = platforms.select { |p| p.name == :tvos }.map(&:deployment_target).min
@pods_project.build_configurations.each do |build_configuration|
build_configuration.build_settings['MACOSX_DEPLOYMENT_TARGET'] = osx_deployment_target.to_s if osx_deployment_target
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = ios_deployment_target.to_s if ios_deployment_target
build_configuration.build_settings['WATCHOS_DEPLOYMENT_TARGET'] = watchos_deployment_target.to_s if watchos_deployment_target
build_configuration.build_settings['TVOS_DEPLOYMENT_TARGET'] = tvos_deployment_target.to_s if tvos_deployment_target
build_configuration.build_settings['STRIP_INSTALLED_PRODUCT'] = 'NO'
build_configuration.build_settings['CLANG_ENABLE_OBJC_ARC'] = 'YES'
end
end
end
# Installs the file references in the Pods project. This is done once per
# Pod as the same file reference might be shared by multiple aggregate
# targets.
#
# @return [void]
#
def install_file_references
installer = FileReferencesInstaller.new(sandbox, pod_targets, pods_project)
installer.install!
end
# Installs the aggregate targets of the Pods projects and generates their
# support files.
#
# @return [void]
#
def install_libraries
UI.message '- Installing targets' do
pod_targets.sort_by(&:name).each do |pod_target|
target_installer = PodTargetInstaller.new(sandbox, pod_target)
target_installer.install!
end
aggregate_targets.sort_by(&:name).each do |target|
target_installer = AggregateTargetInstaller.new(sandbox, target)
target_installer.install!
end
# TODO: Move and add specs
pod_targets.sort_by(&:name).each do |pod_target|
pod_target.file_accessors.each do |file_accessor|
file_accessor.spec_consumer.frameworks.each do |framework|
if pod_target.should_build?
pod_target.native_target.add_system_framework(framework)
end
end
end
end
end
end
# Adds a target dependency for each pod spec to each aggregate target and
# links the pod targets among each other.
#
# @return [void]
#
def set_target_dependencies
frameworks_group = pods_project.frameworks_group
aggregate_targets.each do |aggregate_target|
is_app_extension = !(aggregate_target.user_targets.map(&:symbol_type) &
[:app_extension, :watch_extension, :watch2_extension, :tv_extension]).empty?
is_app_extension ||= aggregate_target.user_targets.any? { |ut| ut.common_resolved_build_setting('APPLICATION_EXTENSION_API_ONLY') == 'YES' }
aggregate_target.pod_targets.each do |pod_target|
configure_app_extension_api_only_for_target(aggregate_target) if is_app_extension
unless pod_target.should_build?
pod_target.resource_bundle_targets.each do |resource_bundle_target|
aggregate_target.native_target.add_dependency(resource_bundle_target)
end
next
end
aggregate_target.native_target.add_dependency(pod_target.native_target)
configure_app_extension_api_only_for_target(pod_target) if is_app_extension
pod_target.dependent_targets.each do |pod_dependency_target|
next unless pod_dependency_target.should_build?
pod_target.native_target.add_dependency(pod_dependency_target.native_target)
configure_app_extension_api_only_for_target(pod_dependency_target) if is_app_extension
if pod_target.requires_frameworks?
product_ref = frameworks_group.files.find { |f| f.path == pod_dependency_target.product_name } ||
frameworks_group.new_product_ref_for_target(pod_dependency_target.product_basename, pod_dependency_target.product_type)
pod_target.native_target.frameworks_build_phase.add_file_reference(product_ref, true)
end
end
end
end
end
# Writes the Pods project to the disk.
#
# @return [void]
#
def write_pod_project
UI.message "- Writing Xcode project file to #{UI.path sandbox.project_path}" do
pods_project.pods.remove_from_project if pods_project.pods.empty?
pods_project.development_pods.remove_from_project if pods_project.development_pods.empty?
pods_project.sort(:groups_position => :below)
if installation_options.deterministic_uuids?
UI.message('- Generating deterministic UUIDs') { pods_project.predictabilize_uuids }
end
pods_project.recreate_user_schemes(false)
pods_project.save
end
end
# Shares schemes of development Pods.
#
# @return [void]
#
def share_development_pod_schemes
development_pod_targets.select(&:should_build?).each do |pod_target|
next unless share_scheme_for_development_pod?(pod_target.pod_name)
Xcodeproj::XCScheme.share_scheme(pods_project.path, pod_target.label)
end
end
# @param [String] pod The root name of the development pod.
#
# @return [Bool] whether the scheme for the given development pod should be
# shared.
#
def share_scheme_for_development_pod?(pod)
case dev_pods_to_share = installation_options.share_schemes_for_development_pods
when TrueClass, FalseClass, NilClass
dev_pods_to_share
when Array
dev_pods_to_share.any? { |dev_pod| dev_pod === pod } # rubocop:disable Style/CaseEquality
else
raise Informative, 'Unable to handle share_schemes_for_development_pods ' \
"being set to #{dev_pods_to_share.inspect} -- please set it to true, " \
'false, or an array of pods to share schemes for.'
end
end
# Writes the Podfile and the lock files. # Writes the Podfile and the lock files.
# #
# @todo Pass the checkout options to the Lockfile. # @todo Pass the checkout options to the Lockfile.
...@@ -859,15 +691,6 @@ module Pod ...@@ -859,15 +691,6 @@ module Pod
analysis_result.sandbox_state analysis_result.sandbox_state
end end
# Sets the APPLICATION_EXTENSION_API_ONLY build setting to YES for all
# configurations of the given target
#
def configure_app_extension_api_only_for_target(target)
target.native_target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES'
end
end
#-------------------------------------------------------------------------# #-------------------------------------------------------------------------#
end end
end end
module Pod
class Installer
# Controller class responsible of installing the file references of the
# specifications in the Pods project.
#
class FileReferencesInstaller
# @return [Sandbox] The sandbox of the installation.
#
attr_reader :sandbox
# @return [Array<PodTarget>] The pod targets of the installation.
#
attr_reader :pod_targets
# @return [Project] The Pods project.
#
attr_reader :pods_project
# Initialize a new instance
#
# @param [Sandbox] sandbox @see sandbox
# @param [Array<PodTarget>] pod_targets @see pod_targets
# @param [Project] pods_project @see pod_project
#
def initialize(sandbox, pod_targets, pods_project)
@sandbox = sandbox
@pod_targets = pod_targets
@pods_project = pods_project
end
# Installs the file references.
#
# @return [void]
#
def install!
refresh_file_accessors
add_source_files_references
add_frameworks_bundles
add_vendored_libraries
add_resources
link_headers
end
#-----------------------------------------------------------------------#
private
# @!group Installation Steps
# Reads the file accessors contents from the file system.
#
# @note The contents of the file accessors are modified by the clean
# step of the #{PodSourceInstaller} and by the pre install hooks.
#
# @return [void]
#
def refresh_file_accessors
file_accessors.each do |fa|
fa.path_list.read_file_system
end
end
# Adds the source files of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively).
#
# @return [void]
#
def add_source_files_references
UI.message '- Adding source files to Pods project' do
add_file_accessors_paths_to_pods_group(:source_files, nil, true)
end
end
# Adds the bundled frameworks to the Pods project
#
# @return [void]
#
def add_frameworks_bundles
UI.message '- Adding frameworks to Pods project' do
add_file_accessors_paths_to_pods_group(:vendored_frameworks, :frameworks)
end
end
# Adds the bundled libraries to the Pods project
#
# @return [void]
#
def add_vendored_libraries
UI.message '- Adding libraries to Pods project' do
add_file_accessors_paths_to_pods_group(:vendored_libraries, :frameworks)
end
end
# Adds the resources of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively) in the resources group.
#
# @return [void]
#
def add_resources
UI.message '- Adding resources to Pods project' do
add_file_accessors_paths_to_pods_group(:resources, :resources, true)
add_file_accessors_paths_to_pods_group(:resource_bundle_files, :resources, true)
end
end
# Creates the link to the headers of the Pod in the sandbox.
#
# @return [void]
#
def link_headers
UI.message '- Linking headers' do
pod_targets.each do |pod_target|
pod_target.file_accessors.each do |file_accessor|
framework_exp = /\.framework\//
headers_sandbox = Pathname.new(file_accessor.spec.root.name)
# When integrating Pod as frameworks, built Pods are built into
# frameworks, whose headers are included inside the built
# framework. Those headers do not need to be linked from the
# sandbox.
unless pod_target.requires_frameworks? && pod_target.should_build?
pod_target.build_headers.add_search_path(headers_sandbox, pod_target.platform)
sandbox.public_headers.add_search_path(headers_sandbox, pod_target.platform)
header_mappings(headers_sandbox, file_accessor, file_accessor.headers).each do |namespaced_path, files|
pod_target.build_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
end
header_mappings(headers_sandbox, file_accessor, file_accessor.public_headers).each do |namespaced_path, files|
sandbox.public_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
end
end
unless pod_target.requires_frameworks?
vendored_frameworks_header_mappings(headers_sandbox, file_accessor).each do |namespaced_path, files|
sandbox.public_headers.add_files(namespaced_path, files)
end
end
end
end
end
end
#-----------------------------------------------------------------------#
private
# @!group Private Helpers
# @return [Array<Sandbox::FileAccessor>] The file accessors for all the
# specs platform combinations.
#
def file_accessors
@file_accessors ||= pod_targets.map(&:file_accessors).flatten.compact
end
# Adds file references to the list of the paths returned by the file
# accessor with the given key to the given group of the Pods project.
#
# @param [Symbol] file_accessor_key
# The method of the file accessor which would return the list of
# the paths.
#
# @param [Symbol] group_key
# The key of the group of the Pods project.
#
# @param [Bool] reflect_file_system_structure_for_development
# Whether organizing a local pod's files in subgroups inside
# the pod's group is allowed.
#
# @return [void]
#
def add_file_accessors_paths_to_pods_group(file_accessor_key, group_key = nil, reflect_file_system_structure_for_development = false)
file_accessors.each do |file_accessor|
pod_name = file_accessor.spec.name
local = sandbox.local?(pod_name)
paths = file_accessor.send(file_accessor_key)
paths = allowable_project_paths(paths)
paths.each do |path|
group = pods_project.group_for_spec(file_accessor.spec.name, group_key)
pods_project.add_file_reference(path, group, local && reflect_file_system_structure_for_development)
end
end
end
# Filters a list of paths down to those paths which can be added to
# the Xcode project. Some paths are intermediates and only their children
# should be added, while some paths are treated as bundles and their
# children should not be added directly.
#
# @param [Array<Pathname>] paths
# The paths to files or directories on disk.
#
# @return [Array<Pathname>] The paths which can be added to the Xcode project
#
def allowable_project_paths(paths)
lproj_paths = Set.new
lproj_paths_with_files = Set.new
allowable_paths = paths.select do |path|
path_str = path.to_s
# We add the directory for a Core Data model, but not the items in it.
next if path_str =~ /.*\.xcdatamodeld\/.+/i
# We add the directory for a Core Data migration mapping, but not the items in it.
next if path_str =~ /.*\.xcmappingmodel\/.+/i
# We add the directory for an asset catalog, but not the items in it.
next if path_str =~ /.*\.xcassets\/.+/i
if path_str =~ /\.lproj(\/|$)/i
# If the element is an .lproj directory then save it and potentially
# add it later if we don't find any contained items.
if path_str =~ /\.lproj$/i && path.directory?
lproj_paths << path
next
end
# Collect the paths for the .lproj directories that contain files.
lproj_path = /(^.*\.lproj)\/.*/i.match(path_str)[1]
lproj_paths_with_files << Pathname(lproj_path)
# Directories nested within an .lproj directory are added as file
# system references so their contained items are not added directly.
next if path.dirname.dirname == lproj_path
end
true
end
# Only add the path for the .lproj directories that do not have anything
# within them added as well. This generally happens if the glob within the
# resources directory was not a recursive glob.
allowable_paths + lproj_paths.subtract(lproj_paths_with_files).to_a
end
# Computes the destination sub-directory in the sandbox
#
# @param [Pathname] headers_sandbox
# The sandbox where the header links should be stored for this
# Pod.
#
# @param [Sandbox::FileAccessor] file_accessor
# The consumer file accessor for which the headers need to be
# linked.
#
# @param [Array<Pathname>] headers
# The absolute paths of the headers which need to be mapped.
#
# @return [Hash{Pathname => Array<Pathname>}] A hash containing the
# headers folders as the keys and the absolute paths of the
# header files as the values.
#
def header_mappings(headers_sandbox, file_accessor, headers)
consumer = file_accessor.spec_consumer
dir = headers_sandbox
dir += consumer.header_dir if consumer.header_dir
mappings = {}
headers.each do |header|
sub_dir = dir
if consumer.header_mappings_dir
header_mappings_dir = file_accessor.path_list.root + consumer.header_mappings_dir
relative_path = header.relative_path_from(header_mappings_dir)
sub_dir += relative_path.dirname
end
mappings[sub_dir] ||= []
mappings[sub_dir] << header
end
mappings
end
# Computes the destination sub-directory in the sandbox for headers
# from inside vendored frameworks.
#
# @param [Pathname] headers_sandbox
# The sandbox where the header links should be stored for this
# Pod.
#
# @param [Sandbox::FileAccessor] file_accessor
# The consumer file accessor for which the headers need to be
# linked.
#
def vendored_frameworks_header_mappings(headers_sandbox, file_accessor)
mappings = {}
file_accessor.vendored_frameworks.each do |framework|
headers_dir = Sandbox::FileAccessor.vendored_frameworks_headers_dir(framework)
headers = Sandbox::FileAccessor.vendored_frameworks_headers(framework)
framework_name = framework.basename(framework.extname)
dir = headers_sandbox + framework_name
headers.each do |header|
# the relative path of framework headers should be kept,
# not flattened like is done for most public headers.
relative_path = header.relative_path_from(headers_dir)
sub_dir = dir + relative_path.dirname
mappings[sub_dir] ||= []
mappings[sub_dir] << header
end
end
mappings
end
#-----------------------------------------------------------------------#
end
end
end
module Pod
class Installer
# Controller class responsible of creating and configuring the static
# library target in Pods project. It also creates the support file needed
# by the target.
#
class TargetInstaller
# @return [Sandbox] sandbox the sandbox where the support files should
# be generated.
#
attr_reader :sandbox
# @return [Target] The library whose target needs to be generated.
#
attr_reader :target
# @param [Project] project @see project
# @param [Target] target @see target
#
def initialize(sandbox, target)
@sandbox = sandbox
@target = target
end
private
#-----------------------------------------------------------------------#
# @!group Installation steps
# Adds the target for the library to the Pods project with the
# appropriate build configurations.
#
# @note The `PODS_HEADERS_SEARCH_PATHS` overrides the xcconfig.
#
# @return [void]
#
def add_target
product_type = target.product_type
name = target.label
platform = target.platform.name
language = target.uses_swift? ? :swift : :objc
@native_target = project.new_target(product_type, name, platform, deployment_target, nil, language)
product_name = target.product_name
product = @native_target.product_reference
product.name = product_name
target.user_build_configurations.each do |bc_name, type|
@native_target.add_build_configuration(bc_name, type)
end
@native_target.build_configurations.each do |configuration|
configuration.build_settings.merge!(custom_build_settings)
end
target.native_target = @native_target
end
# @return [String] The deployment target.
#
def deployment_target
target.platform.deployment_target.to_s
end
# Returns the customized build settings which are overridden in the build
# settings of the user target.
#
# @return [Hash{String => String}]
#
def custom_build_settings
settings = {}
unless target.archs.empty?
settings['ARCHS'] = target.archs
end
if target.requires_frameworks?
settings['PRODUCT_NAME'] = target.product_module_name
else
settings.merge!('OTHER_LDFLAGS' => '', 'OTHER_LIBTOOLFLAGS' => '')
end
settings
end
# Creates the directory where to store the support files of the target.
#
def create_support_files_dir
target.support_files_dir.mkdir
end
# Creates the Info.plist file which sets public framework attributes
#
# @return [void]
#
def create_info_plist_file
path = target.info_plist_path
UI.message "- Generating Info.plist file at #{UI.path(path)}" do
generator = Generator::InfoPlistFile.new(target)
generator.save_as(path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(sandbox.root)
c.build_settings['INFOPLIST_FILE'] = relative_path.to_s
end
end
end
# Creates the module map file which ensures that the umbrella header is
# recognized with a customized path
#
# @yield_param [Generator::ModuleMap]
# yielded once to configure the private headers
#
# @return [void]
#
def create_module_map
path = target.module_map_path
UI.message "- Generating module map file at #{UI.path(path)}" do
generator = Generator::ModuleMap.new(target)
yield generator if block_given?
generator.save_as(path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(sandbox.root)
c.build_settings['MODULEMAP_FILE'] = relative_path.to_s
end
end
end
# Generates a header which ensures that all header files are exported
# in the module map
#
# @yield_param [Generator::UmbrellaHeader]
# yielded once to configure the imports
#
def create_umbrella_header
path = target.umbrella_header_path
UI.message "- Generating umbrella header at #{UI.path(path)}" do
generator = Generator::UmbrellaHeader.new(target)
yield generator if block_given?
generator.save_as(path)
# Add the file to the support group and the native target,
# so it will been added to the header build phase
file_ref = add_file_to_support_group(path)
native_target.add_file_references([file_ref])
# Make the umbrella header public
build_file = native_target.headers_build_phase.build_file(file_ref)
build_file.settings ||= {}
build_file.settings['ATTRIBUTES'] = ['Public']
end
end
# Generates a dummy source file for each target so libraries that contain
# only categories build.
#
# @return [void]
#
def create_dummy_source
path = target.dummy_source_path
generator = Generator::DummySource.new(target.label)
generator.save_as(path)
file_reference = add_file_to_support_group(path)
native_target.source_build_phase.add_file_reference(file_reference)
end
# @return [PBXNativeTarget] the target generated by the installation
# process.
#
# @note Generated by the {#add_target} step.
#
attr_reader :native_target
private
#-----------------------------------------------------------------------#
# @!group Private helpers.
# @return [Project] the Pods project of the sandbox.
#
def project
sandbox.project
end
# @return [PBXGroup] the group where the file references to the support
# files should be stored.
#
attr_reader :support_files_group
# Adds a reference to the given file in the support group of this target.
#
# @param [Pathname] path
# The path of the file to which the reference should be added.
#
# @return [PBXFileReference] the file reference of the added file.
#
def add_file_to_support_group(path)
support_files_group.new_file(path)
end
#-----------------------------------------------------------------------#
end
end
end
module Pod
class Installer
# Creates the targets which aggregate the Pods libraries in the Pods
# project and the relative support files.
#
class AggregateTargetInstaller < TargetInstaller
# Creates the target in the Pods project and the relative support files.
#
# @return [void]
#
def install!
UI.message "- Installing target `#{target.name}` #{target.platform}" do
add_target
create_support_files_dir
create_support_files_group
create_xcconfig_file
if target.requires_frameworks?
create_info_plist_file
create_module_map
create_umbrella_header
end
create_embed_frameworks_script
create_bridge_support_file
create_copy_resources_script
create_acknowledgements
create_dummy_source
end
end
#-----------------------------------------------------------------------#
private
# @return [TargetDefinition] the target definition of the library.
#
def target_definition
target.target_definition
end
# Ensure that vendored static frameworks and libraries are not linked
# twice to the aggregate target, which shares the xcconfig of the user
# target.
#
def custom_build_settings
settings = {
'MACH_O_TYPE' => 'staticlib',
'OTHER_LDFLAGS' => '',
'OTHER_LIBTOOLFLAGS' => '',
'PODS_ROOT' => '$(SRCROOT)',
'PRODUCT_BUNDLE_IDENTIFIER' => 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}',
'SKIP_INSTALL' => 'YES',
}
super.merge(settings)
end
# Creates the group that holds the references to the support files
# generated by this installer.
#
# @return [void]
#
def create_support_files_group
parent = project.support_files_group
name = target.name
dir = target.support_files_dir
@support_files_group = parent.new_group(name, dir)
end
# Generates the contents of the xcconfig file and saves it to disk.
#
# @return [void]
#
def create_xcconfig_file
native_target.build_configurations.each do |configuration|
path = target.xcconfig_path(configuration.name)
gen = Generator::XCConfig::AggregateXCConfig.new(target, configuration.name)
gen.save_as(path)
target.xcconfigs[configuration.name] = gen.xcconfig
xcconfig_file_ref = add_file_to_support_group(path)
configuration.base_configuration_reference = xcconfig_file_ref
end
end
# Generates the bridge support metadata if requested by the {Podfile}.
#
# @note The bridge support metadata is added to the resources of the
# target because it is needed for environments interpreted at
# runtime.
#
# @return [void]
#
def create_bridge_support_file
if target.podfile.generate_bridge_support?
path = target.bridge_support_path
headers = native_target.headers_build_phase.files.map { |bf| sandbox.root + bf.file_ref.path }
generator = Generator::BridgeSupport.new(headers)
generator.save_as(path)
add_file_to_support_group(path)
@bridge_support_file = path.relative_path_from(sandbox.root)
end
end
# Uniqued Resources grouped by config
#
# @return [Hash{ Symbol => Array<Pathname> }]
#
def resources_by_config
library_targets = target.pod_targets.reject do |pod_target|
pod_target.should_build? && pod_target.requires_frameworks?
end
target.user_build_configurations.keys.each_with_object({}) do |config, resources_by_config|
resources_by_config[config] = library_targets.flat_map do |library_target|
next [] unless library_target.include_in_build_config?(target_definition, config)
resource_paths = library_target.file_accessors.flat_map do |accessor|
accessor.resources.flat_map { |res| res.relative_path_from(project.path.dirname) }
end
resource_bundles = library_target.file_accessors.flat_map do |accessor|
accessor.resource_bundles.keys.map { |name| "#{library_target.configuration_build_dir}/#{name.shellescape}.bundle" }
end
(resource_paths + resource_bundles + [bridge_support_file].compact).uniq
end
end
end
# Creates a script that copies the resources to the bundle of the client
# target.
#
# @note The bridge support file needs to be created before the prefix
# header, otherwise it will not be added to the resources script.
#
# @return [void]
#
def create_copy_resources_script
path = target.copy_resources_script_path
generator = Generator::CopyResourcesScript.new(resources_by_config, target.platform)
generator.save_as(path)
add_file_to_support_group(path)
end
# Creates a script that embeds the frameworks to the bundle of the client
# target.
#
# @note We can't use Xcode default copy bundle resource phase, because
# we need to ensure that we only copy the resources, which are
# relevant for the current build configuration.
#
# @return [void]
#
def create_embed_frameworks_script
path = target.embed_frameworks_script_path
frameworks_by_config = {}
target.user_build_configurations.keys.each do |config|
relevant_pod_targets = target.pod_targets.select do |pod_target|
pod_target.include_in_build_config?(target_definition, config)
end
frameworks_by_config[config] = relevant_pod_targets.flat_map do |pod_target|
frameworks = pod_target.file_accessors.flat_map(&:vendored_dynamic_artifacts).map { |fw| "${PODS_ROOT}/#{fw.relative_path_from(sandbox.root)}" }
frameworks << pod_target.build_product_path('$BUILT_PRODUCTS_DIR') if pod_target.should_build? && pod_target.requires_frameworks?
frameworks
end
end
generator = Generator::EmbedFrameworksScript.new(frameworks_by_config)
generator.save_as(path)
add_file_to_support_group(path)
end
# Generates the acknowledgement files (markdown and plist) for the target.
#
# @return [void]
#
def create_acknowledgements
basepath = target.acknowledgements_basepath
Generator::Acknowledgements.generators.each do |generator_class|
path = generator_class.path_from_basepath(basepath)
file_accessors = target.pod_targets.map(&:file_accessors).flatten
generator = generator_class.new(file_accessors)
generator.save_as(path)
add_file_to_support_group(path)
end
end
# @return [Pathname] the path of the bridge support file relative to the
# sandbox.
#
# @return [Nil] if no bridge support file was generated.
#
attr_reader :bridge_support_file
#-----------------------------------------------------------------------#
end
end
end
module Pod
class Installer
# Creates the target for the Pods libraries in the Pods project and the
# relative support files.
#
class PodTargetInstaller < TargetInstaller
# Creates the target in the Pods project and the relative support files.
#
# @return [void]
#
def install!
unless target.should_build?
add_resources_bundle_targets
return
end
UI.message "- Installing target `#{target.name}` #{target.platform}" do
add_target
create_support_files_dir
add_resources_bundle_targets
add_files_to_build_phases
create_xcconfig_file
if target.requires_frameworks?
create_info_plist_file
create_module_map
create_umbrella_header do |generator|
generator.imports += if header_mappings_dir
target.file_accessors.flat_map(&:public_headers).map do |pathname|
pathname.relative_path_from(header_mappings_dir)
end
else
target.file_accessors.flat_map(&:public_headers).map(&:basename)
end
end
create_build_phase_to_symlink_header_folders
end
create_prefix_header
create_dummy_source
end
end
private
# Remove the default headers folder path settings for static library pod
# targets.
#
# @return [Hash{String => String}]
#
def custom_build_settings
settings = super
unless target.requires_frameworks?
settings['PRIVATE_HEADERS_FOLDER_PATH'] = ''
settings['PUBLIC_HEADERS_FOLDER_PATH'] = ''
end
settings
end
#-----------------------------------------------------------------------#
SOURCE_FILE_EXTENSIONS = Sandbox::FileAccessor::SOURCE_FILE_EXTENSIONS
# Adds the build files of the pods to the target and adds a reference to
# the frameworks of the Pods.
#
# @note The Frameworks are used only for presentation purposes as the
# xcconfig is the authoritative source about their information.
#
# @note Core Data model directories (.xcdatamodeld) defined in the `resources`
# property are currently added to the `Copy Resources` build phase like
# all other resources. The Xcode UI adds these to the `Compile Sources`
# build phase, but they will compile correctly either way.
#
# @return [void]
#
def add_files_to_build_phases
target.file_accessors.each do |file_accessor|
consumer = file_accessor.spec_consumer
headers = file_accessor.headers
public_headers = file_accessor.public_headers
private_headers = file_accessor.private_headers
other_source_files = file_accessor.source_files.reject { |sf| SOURCE_FILE_EXTENSIONS.include?(sf.extname) }
{
true => file_accessor.arc_source_files,
false => file_accessor.non_arc_source_files,
}.each do |arc, files|
files = files - headers - other_source_files
flags = compiler_flags_for_consumer(consumer, arc)
regular_file_refs = files.map { |sf| project.reference_for_path(sf) }
native_target.add_file_references(regular_file_refs, flags)
end
header_file_refs = headers.map { |sf| project.reference_for_path(sf) }
native_target.add_file_references(header_file_refs) do |build_file|
add_header(build_file, public_headers, private_headers)
end
other_file_refs = other_source_files.map { |sf| project.reference_for_path(sf) }
native_target.add_file_references(other_file_refs, nil)
next unless target.requires_frameworks?
resource_refs = file_accessor.resources.flatten.map do |res|
project.reference_for_path(res)
end
# Some nested files are not directly present in the Xcode project, such as the contents
# of an .xcdatamodeld directory. These files will return nil file references.
resource_refs.compact!
native_target.add_resources(resource_refs)
end
end
# Adds the resources of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively) in the resources group.
#
# @note Core Data model directories (.xcdatamodeld) are currently added to the
# `Copy Resources` build phase like all other resources. The Xcode UI adds
# these to the `Compile Sources` build phase, but they will compile
# correctly either way.
#
# @return [void]
#
def add_resources_bundle_targets
target.file_accessors.each do |file_accessor|
file_accessor.resource_bundles.each do |bundle_name, paths|
file_references = paths.map do |path|
ref = project.reference_for_path(path)
# Some nested files are not directly present in the Xcode project, such as the contents
# of an .xcdatamodeld directory. These files are implicitly included by including their
# parent directory.
next if ref.nil?
# For variant groups, the variant group itself is added, not its members.
next ref.parent if ref.parent.is_a?(Xcodeproj::Project::Object::PBXVariantGroup)
ref
end
file_references = file_references.uniq.compact
label = target.resources_bundle_target_label(bundle_name)
bundle_target = project.new_resources_bundle(label, file_accessor.spec_consumer.platform_name)
bundle_target.product_reference.tap do |bundle_product|
bundle_file_name = "#{bundle_name}.bundle"
bundle_product.name = bundle_file_name
bundle_product.path = bundle_file_name
end
bundle_target.add_resources(file_references)
target.user_build_configurations.each do |bc_name, type|
bundle_target.add_build_configuration(bc_name, type)
end
bundle_target.deployment_target = deployment_target
target.resource_bundle_targets << bundle_target
if target.should_build?
native_target.add_dependency(bundle_target)
if target.requires_frameworks?
native_target.add_resources([bundle_target.product_reference])
end
end
# Create Info.plist file for bundle
path = target.info_plist_path
path.dirname.mkdir unless path.dirname.exist?
info_plist_path = path.dirname + "ResourceBundle-#{bundle_name}-#{path.basename}"
generator = Generator::InfoPlistFile.new(target, :bundle_package_type => :bndl)
generator.save_as(info_plist_path)
add_file_to_support_group(info_plist_path)
bundle_target.build_configurations.each do |c|
c.build_settings['PRODUCT_NAME'] = bundle_name
relative_info_plist_path = info_plist_path.relative_path_from(sandbox.root)
c.build_settings['INFOPLIST_FILE'] = relative_info_plist_path.to_s
c.build_settings['CONFIGURATION_BUILD_DIR'] = target.configuration_build_dir('$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)')
# Set the correct device family for this bundle, based on the platform
device_family_by_platform = {
:ios => '1,2',
:tvos => '3',
:watchos => '1,2' # The device family for watchOS is 4, but Xcode creates watchkit-compatible bundles as 1,2
}
if family = device_family_by_platform[target.platform.name]
c.build_settings['TARGETED_DEVICE_FAMILY'] = family
end
end
end
end
end
# Generates the contents of the xcconfig file and saves it to disk.
#
# @return [void]
#
def create_xcconfig_file
path = target.xcconfig_path
xcconfig_gen = Generator::XCConfig::PodXCConfig.new(target)
xcconfig_gen.save_as(path)
xcconfig_file_ref = add_file_to_support_group(path)
native_target.build_configurations.each do |c|
c.base_configuration_reference = xcconfig_file_ref
end
# also apply the private config to resource targets
target.resource_bundle_targets.each do |rsrc_target|
rsrc_target.build_configurations.each do |rsrc_bc|
rsrc_bc.base_configuration_reference = xcconfig_file_ref
end
end
end
# Creates a build phase which links the versioned header folders
# of the OS X into the framework bundle's root root directory.
# This is only necessary because the way how headers are copied
# via custom copy file build phases in combination with
# header_mappings_dir interferes with xcodebuild's expectations
# about the existence of private or public headers.
#
# @return [void]
#
def create_build_phase_to_symlink_header_folders
return unless target.platform.name == :osx && header_mappings_dir
build_phase = native_target.new_shell_script_build_phase('Create Symlinks to Header Folders')
build_phase.shell_script = <<-eos.strip_heredoc
cd "$CONFIGURATION_BUILD_DIR/$WRAPPER_NAME"
ln -fs ${PUBLIC_HEADERS_FOLDER_PATH\#$WRAPPER_NAME/} ${PUBLIC_HEADERS_FOLDER_PATH\#\$CONTENTS_FOLDER_PATH/}
ln -fs ${PRIVATE_HEADERS_FOLDER_PATH\#\$WRAPPER_NAME/} ${PRIVATE_HEADERS_FOLDER_PATH\#\$CONTENTS_FOLDER_PATH/}
eos
end
# Creates a prefix header file which imports `UIKit` or `Cocoa` according
# to the platform of the target. This file also include any prefix header
# content reported by the specification of the pods.
#
# @return [void]
#
def create_prefix_header
path = target.prefix_header_path
generator = Generator::PrefixHeader.new(target.file_accessors, target.platform)
generator.save_as(path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(project.path.dirname)
c.build_settings['GCC_PREFIX_HEADER'] = relative_path.to_s
end
end
ENABLE_OBJECT_USE_OBJC_FROM = {
:ios => Version.new('6'),
:osx => Version.new('10.8'),
:watchos => Version.new('2.0'),
:tvos => Version.new('9.0'),
}
# Returns the compiler flags for the source files of the given specification.
#
# The following behavior is regarding the `OS_OBJECT_USE_OBJC` flag. When
# set to `0`, it will allow code to use `dispatch_release()` on >= iOS 6.0
# and OS X 10.8.
#
# * New libraries that do *not* require ARC don’t need to care about this
# issue at all.
#
# * New libraries that *do* require ARC _and_ have a deployment target of
# >= iOS 6.0 or OS X 10.8:
#
# These no longer use `dispatch_release()` and should *not* have the
# `OS_OBJECT_USE_OBJC` flag set to `0`.
#
# **Note:** this means that these libraries *have* to specify the
# deployment target in order to function well.
#
# * New libraries that *do* require ARC, but have a deployment target of
# < iOS 6.0 or OS X 10.8:
#
# These contain `dispatch_release()` calls and as such need the
# `OS_OBJECT_USE_OBJC` flag set to `1`.
#
# **Note:** libraries that do *not* specify a platform version are
# assumed to have a deployment target of < iOS 6.0 or OS X 10.8.
#
# For more information, see: http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h
#
# @param [Specification::Consumer] consumer
# The consumer for the specification for which the compiler flags
# are needed.
#
# @return [String] The compiler flags.
#
def compiler_flags_for_consumer(consumer, arc)
flags = consumer.compiler_flags.dup
if !arc
flags << '-fno-objc-arc'
else
platform_name = consumer.platform_name
spec_deployment_target = consumer.spec.deployment_target(platform_name)
if spec_deployment_target.nil? || Version.new(spec_deployment_target) < ENABLE_OBJECT_USE_OBJC_FROM[platform_name]
flags << '-DOS_OBJECT_USE_OBJC=0'
end
end
if target.inhibit_warnings?
flags << '-w -Xanalyzer -analyzer-disable-all-checks'
end
flags * ' '
end
# Adds a reference to the given file in the support group of this target.
#
# @param [Pathname] path
# The path of the file to which the reference should be added.
#
# @return [PBXFileReference] the file reference of the added file.
#
def add_file_to_support_group(path)
pod_name = target.pod_name
dir = target.support_files_dir
group = project.pod_support_files_group(pod_name, dir)
group.new_file(path)
end
def create_module_map
return super unless custom_module_map
path = target.module_map_path
UI.message "- Copying module map file to #{UI.path(path)}" do
FileUtils.cp(custom_module_map, path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(sandbox.root)
c.build_settings['MODULEMAP_FILE'] = relative_path.to_s
end
end
end
def create_umbrella_header
return super unless custom_module_map
end
def custom_module_map
@custom_module_map ||= target.file_accessors.first.module_map
end
def header_mappings_dir
return @header_mappings_dir if defined?(@header_mappings_dir)
file_accessor = target.file_accessors.first
@header_mappings_dir = if dir = file_accessor.spec_consumer.header_mappings_dir
file_accessor.path_list.root + dir
end
end
def add_header(build_file, public_headers, private_headers)
file_ref = build_file.file_ref
acl = if public_headers.include?(file_ref.real_path)
'Public'
elsif private_headers.include?(file_ref.real_path)
'Private'
else
'Project'
end
if target.requires_frameworks? && header_mappings_dir && acl != 'Project'
relative_path = file_ref.real_path.relative_path_from(header_mappings_dir)
sub_dir = relative_path.dirname
copy_phase_name = "Copy #{sub_dir} #{acl} Headers"
copy_phase = native_target.copy_files_build_phases.find { |bp| bp.name == copy_phase_name } ||
native_target.new_copy_files_build_phase(copy_phase_name)
copy_phase.symbol_dst_subfolder_spec = :products_directory
copy_phase.dst_path = "$(#{acl.upcase}_HEADERS_FOLDER_PATH)/#{sub_dir}"
copy_phase.add_file_reference(file_ref, true)
else
build_file.settings ||= {}
build_file.settings['ATTRIBUTES'] = [acl]
end
end
#-----------------------------------------------------------------------#
end
end
end
module Pod
class Installer
class Xcode
autoload :PodsProjectGenerator, 'cocoapods/installer/xcode/pods_project_generator'
end
end
end
module Pod
class Installer
class Xcode
# The {PodsProjectGenerator} handles generation of the 'Pods/Pods.xcodeproj'
#
class PodsProjectGenerator
require 'cocoapods/installer/xcode/pods_project_generator/target_installer'
require 'cocoapods/installer/xcode/pods_project_generator/pod_target_installer'
require 'cocoapods/installer/xcode/pods_project_generator/file_references_installer'
require 'cocoapods/installer/xcode/pods_project_generator/aggregate_target_installer'
# @return [Pod::Project] the `Pods/Pods.xcodeproj` project.
#
attr_reader :project
# @return [Array<AggregateTarget>] The model representations of an
# aggregation of pod targets generated for a target definition
# in the Podfile.
#
attr_reader :aggregate_targets
# @return [Sandbox] The sandbox where the Pods should be installed.
#
attr_reader :sandbox
# @return [Array<PodTarget>] The model representations of pod targets.
#
attr_reader :pod_targets
# @return [Analyzer] the analyzer which provides the information about what
# needs to be installed.
#
attr_reader :analysis_result
# @return [InstallationOptions] the installation options from the Podfile.
#
attr_reader :installation_options
# @return [Config] the global CocoaPods configuration.
#
attr_reader :config
# Initialize a new instance
#
# @param [Array<AggregateTarget>] aggregate_targets @see aggregate_targets
# @param [Sandbox] sandbox @see sandbox
# @param [Array<PodTarget>] pod_targets @see pod_targets
# @param [Analyzer] analysis_result @see analysis_result
# @param [InstallationOptions] installation_options @see installation_options
# @param [Config] config @see config
#
def initialize(aggregate_targets, sandbox, pod_targets, analysis_result, installation_options, config)
@aggregate_targets = aggregate_targets
@sandbox = sandbox
@pod_targets = pod_targets
@analysis_result = analysis_result
@installation_options = installation_options
@config = config
end
def generate!
prepare
install_file_references
install_libraries
set_target_dependencies
end
def write
UI.message "- Writing Xcode project file to #{UI.path sandbox.project_path}" do
project.pods.remove_from_project if project.pods.empty?
project.development_pods.remove_from_project if project.development_pods.empty?
project.sort(:groups_position => :below)
if installation_options.deterministic_uuids?
UI.message('- Generating deterministic UUIDs') { project.predictabilize_uuids }
end
project.recreate_user_schemes(false)
project.save
end
end
# Shares schemes of development Pods.
#
# @return [void]
#
def share_development_pod_schemes
development_pod_targets.select(&:should_build?).each do |pod_target|
next unless share_scheme_for_development_pod?(pod_target.pod_name)
Xcodeproj::XCScheme.share_scheme(project.path, pod_target.label)
end
end
private
def create_project
if object_version = aggregate_targets.map(&:user_project).compact.map { |p| p.object_version.to_i }.min
Pod::Project.new(sandbox.project_path, false, object_version)
else
Pod::Project.new(sandbox.project_path)
end
end
# Creates the Pods project from scratch if it doesn't exists.
#
# @return [void]
#
# @todo Clean and modify the project if it exists.
#
def prepare
UI.message '- Creating Pods project' do
@project = create_project
analysis_result.all_user_build_configurations.each do |name, type|
@project.add_build_configuration(name, type)
end
pod_names = pod_targets.map(&:pod_name).uniq
pod_names.each do |pod_name|
local = sandbox.local?(pod_name)
path = sandbox.pod_dir(pod_name)
was_absolute = sandbox.local_path_was_absolute?(pod_name)
@project.add_pod_group(pod_name, path, local, was_absolute)
end
if config.podfile_path
@project.add_podfile(config.podfile_path)
end
sandbox.project = @project
platforms = aggregate_targets.map(&:platform)
osx_deployment_target = platforms.select { |p| p.name == :osx }.map(&:deployment_target).min
ios_deployment_target = platforms.select { |p| p.name == :ios }.map(&:deployment_target).min
watchos_deployment_target = platforms.select { |p| p.name == :watchos }.map(&:deployment_target).min
tvos_deployment_target = platforms.select { |p| p.name == :tvos }.map(&:deployment_target).min
@project.build_configurations.each do |build_configuration|
build_configuration.build_settings['MACOSX_DEPLOYMENT_TARGET'] = osx_deployment_target.to_s if osx_deployment_target
build_configuration.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = ios_deployment_target.to_s if ios_deployment_target
build_configuration.build_settings['WATCHOS_DEPLOYMENT_TARGET'] = watchos_deployment_target.to_s if watchos_deployment_target
build_configuration.build_settings['TVOS_DEPLOYMENT_TARGET'] = tvos_deployment_target.to_s if tvos_deployment_target
build_configuration.build_settings['STRIP_INSTALLED_PRODUCT'] = 'NO'
build_configuration.build_settings['CLANG_ENABLE_OBJC_ARC'] = 'YES'
end
end
end
def install_file_references
installer = FileReferencesInstaller.new(sandbox, pod_targets, project)
installer.install!
end
def install_libraries
UI.message '- Installing targets' do
pod_targets.sort_by(&:name).each do |pod_target|
target_installer = PodTargetInstaller.new(sandbox, pod_target)
target_installer.install!
end
aggregate_targets.sort_by(&:name).each do |target|
target_installer = AggregateTargetInstaller.new(sandbox, target)
target_installer.install!
end
add_system_framework_dependencies
end
end
def add_system_framework_dependencies
# @TODO: Add Specs
pod_targets.sort_by(&:name).each do |pod_target|
pod_target.file_accessors.each do |file_accessor|
file_accessor.spec_consumer.frameworks.each do |framework|
if pod_target.should_build?
pod_target.native_target.add_system_framework(framework)
end
end
end
end
end
# Adds a target dependency for each pod spec to each aggregate target and
# links the pod targets among each other.
#
# @return [void]
#
def set_target_dependencies
frameworks_group = project.frameworks_group
aggregate_targets.each do |aggregate_target|
is_app_extension = !(aggregate_target.user_targets.map(&:symbol_type) &
[:app_extension, :watch_extension, :watch2_extension, :tv_extension]).empty?
is_app_extension ||= aggregate_target.user_targets.any? { |ut| ut.common_resolved_build_setting('APPLICATION_EXTENSION_API_ONLY') == 'YES' }
aggregate_target.pod_targets.each do |pod_target|
configure_app_extension_api_only_for_target(aggregate_target) if is_app_extension
unless pod_target.should_build?
pod_target.resource_bundle_targets.each do |resource_bundle_target|
aggregate_target.native_target.add_dependency(resource_bundle_target)
end
next
end
aggregate_target.native_target.add_dependency(pod_target.native_target)
configure_app_extension_api_only_for_target(pod_target) if is_app_extension
pod_target.dependent_targets.each do |pod_dependency_target|
next unless pod_dependency_target.should_build?
pod_target.native_target.add_dependency(pod_dependency_target.native_target)
configure_app_extension_api_only_for_target(pod_dependency_target) if is_app_extension
if pod_target.requires_frameworks?
product_ref = frameworks_group.files.find { |f| f.path == pod_dependency_target.product_name } ||
frameworks_group.new_product_ref_for_target(pod_dependency_target.product_basename, pod_dependency_target.product_type)
pod_target.native_target.frameworks_build_phase.add_file_reference(product_ref, true)
end
end
end
end
end
# @param [String] pod The root name of the development pod.
#
# @return [Bool] whether the scheme for the given development pod should be
# shared.
#
def share_scheme_for_development_pod?(pod)
case dev_pods_to_share = installation_options.share_schemes_for_development_pods
when TrueClass, FalseClass, NilClass
dev_pods_to_share
when Array
dev_pods_to_share.any? { |dev_pod| dev_pod === pod } # rubocop:disable Style/CaseEquality
else
raise Informative, 'Unable to handle share_schemes_for_development_pods ' \
"being set to #{dev_pods_to_share.inspect} -- please set it to true, " \
'false, or an array of pods to share schemes for.'
end
end
# @return [Array<Library>] The targets of the development pods generated by
# the installation process.
#
def development_pod_targets
pod_targets.select do |pod_target|
sandbox.development_pods.keys.include?(pod_target.pod_name)
end
end
#------------------------------------------------------------------------#
# @! group Private Helpers
private
# Sets the APPLICATION_EXTENSION_API_ONLY build setting to YES for all
# configurations of the given target
#
def configure_app_extension_api_only_for_target(target)
target.native_target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES'
end
end
end
end
end
end
module Pod
class Installer
class Xcode
class PodsProjectGenerator
# Creates the targets which aggregate the Pods libraries in the Pods
# project and the relative support files.
#
class AggregateTargetInstaller < TargetInstaller
# Creates the target in the Pods project and the relative support files.
#
# @return [void]
#
def install!
UI.message "- Installing target `#{target.name}` #{target.platform}" do
add_target
create_support_files_dir
create_support_files_group
create_xcconfig_file
if target.requires_frameworks?
create_info_plist_file
create_module_map
create_umbrella_header
end
create_embed_frameworks_script
create_bridge_support_file
create_copy_resources_script
create_acknowledgements
create_dummy_source
end
end
#-----------------------------------------------------------------------#
private
# @return [TargetDefinition] the target definition of the library.
#
def target_definition
target.target_definition
end
# Ensure that vendored static frameworks and libraries are not linked
# twice to the aggregate target, which shares the xcconfig of the user
# target.
#
def custom_build_settings
settings = {
'MACH_O_TYPE' => 'staticlib',
'OTHER_LDFLAGS' => '',
'OTHER_LIBTOOLFLAGS' => '',
'PODS_ROOT' => '$(SRCROOT)',
'PRODUCT_BUNDLE_IDENTIFIER' => 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}',
'SKIP_INSTALL' => 'YES',
}
super.merge(settings)
end
# Creates the group that holds the references to the support files
# generated by this installer.
#
# @return [void]
#
def create_support_files_group
parent = project.support_files_group
name = target.name
dir = target.support_files_dir
@support_files_group = parent.new_group(name, dir)
end
# Generates the contents of the xcconfig file and saves it to disk.
#
# @return [void]
#
def create_xcconfig_file
native_target.build_configurations.each do |configuration|
path = target.xcconfig_path(configuration.name)
gen = Generator::XCConfig::AggregateXCConfig.new(target, configuration.name)
gen.save_as(path)
target.xcconfigs[configuration.name] = gen.xcconfig
xcconfig_file_ref = add_file_to_support_group(path)
configuration.base_configuration_reference = xcconfig_file_ref
end
end
# Generates the bridge support metadata if requested by the {Podfile}.
#
# @note The bridge support metadata is added to the resources of the
# target because it is needed for environments interpreted at
# runtime.
#
# @return [void]
#
def create_bridge_support_file
if target.podfile.generate_bridge_support?
path = target.bridge_support_path
headers = native_target.headers_build_phase.files.map { |bf| sandbox.root + bf.file_ref.path }
generator = Generator::BridgeSupport.new(headers)
generator.save_as(path)
add_file_to_support_group(path)
@bridge_support_file = path.relative_path_from(sandbox.root)
end
end
# Uniqued Resources grouped by config
#
# @return [Hash{ Symbol => Array<Pathname> }]
#
def resources_by_config
library_targets = target.pod_targets.reject do |pod_target|
pod_target.should_build? && pod_target.requires_frameworks?
end
target.user_build_configurations.keys.each_with_object({}) do |config, resources_by_config|
resources_by_config[config] = library_targets.flat_map do |library_target|
next [] unless library_target.include_in_build_config?(target_definition, config)
resource_paths = library_target.file_accessors.flat_map do |accessor|
accessor.resources.flat_map { |res| res.relative_path_from(project.path.dirname) }
end
resource_bundles = library_target.file_accessors.flat_map do |accessor|
accessor.resource_bundles.keys.map { |name| "#{library_target.configuration_build_dir}/#{name.shellescape}.bundle" }
end
(resource_paths + resource_bundles + [bridge_support_file].compact).uniq
end
end
end
# Creates a script that copies the resources to the bundle of the client
# target.
#
# @note The bridge support file needs to be created before the prefix
# header, otherwise it will not be added to the resources script.
#
# @return [void]
#
def create_copy_resources_script
path = target.copy_resources_script_path
generator = Generator::CopyResourcesScript.new(resources_by_config, target.platform)
generator.save_as(path)
add_file_to_support_group(path)
end
# Creates a script that embeds the frameworks to the bundle of the client
# target.
#
# @note We can't use Xcode default copy bundle resource phase, because
# we need to ensure that we only copy the resources, which are
# relevant for the current build configuration.
#
# @return [void]
#
def create_embed_frameworks_script
path = target.embed_frameworks_script_path
frameworks_by_config = {}
target.user_build_configurations.keys.each do |config|
relevant_pod_targets = target.pod_targets.select do |pod_target|
pod_target.include_in_build_config?(target_definition, config)
end
frameworks_by_config[config] = relevant_pod_targets.flat_map do |pod_target|
frameworks = pod_target.file_accessors.flat_map(&:vendored_dynamic_artifacts).map { |fw| "${PODS_ROOT}/#{fw.relative_path_from(sandbox.root)}" }
frameworks << pod_target.build_product_path('$BUILT_PRODUCTS_DIR') if pod_target.should_build? && pod_target.requires_frameworks?
frameworks
end
end
generator = Generator::EmbedFrameworksScript.new(frameworks_by_config)
generator.save_as(path)
add_file_to_support_group(path)
end
# Generates the acknowledgement files (markdown and plist) for the target.
#
# @return [void]
#
def create_acknowledgements
basepath = target.acknowledgements_basepath
Generator::Acknowledgements.generators.each do |generator_class|
path = generator_class.path_from_basepath(basepath)
file_accessors = target.pod_targets.map(&:file_accessors).flatten
generator = generator_class.new(file_accessors)
generator.save_as(path)
add_file_to_support_group(path)
end
end
# @return [Pathname] the path of the bridge support file relative to the
# sandbox.
#
# @return [Nil] if no bridge support file was generated.
#
attr_reader :bridge_support_file
#-----------------------------------------------------------------------#
end
end
end
end
end
module Pod
class Installer
class Xcode
class PodsProjectGenerator
# Controller class responsible of installing the file references of the
# specifications in the Pods project.
#
class FileReferencesInstaller
# @return [Sandbox] The sandbox of the installation.
#
attr_reader :sandbox
# @return [Array<PodTarget>] The pod targets of the installation.
#
attr_reader :pod_targets
# @return [Project] The Pods project.
#
attr_reader :pods_project
# Initialize a new instance
#
# @param [Sandbox] sandbox @see sandbox
# @param [Array<PodTarget>] pod_targets @see pod_targets
# @param [Project] pods_project @see pod_project
#
def initialize(sandbox, pod_targets, pods_project)
@sandbox = sandbox
@pod_targets = pod_targets
@pods_project = pods_project
end
# Installs the file references.
#
# @return [void]
#
def install!
refresh_file_accessors
add_source_files_references
add_frameworks_bundles
add_vendored_libraries
add_resources
link_headers
end
#-----------------------------------------------------------------------#
private
# @!group Installation Steps
# Reads the file accessors contents from the file system.
#
# @note The contents of the file accessors are modified by the clean
# step of the #{PodSourceInstaller} and by the pre install hooks.
#
# @return [void]
#
def refresh_file_accessors
file_accessors.each do |fa|
fa.path_list.read_file_system
end
end
# Adds the source files of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively).
#
# @return [void]
#
def add_source_files_references
UI.message '- Adding source files to Pods project' do
add_file_accessors_paths_to_pods_group(:source_files, nil, true)
end
end
# Adds the bundled frameworks to the Pods project
#
# @return [void]
#
def add_frameworks_bundles
UI.message '- Adding frameworks to Pods project' do
add_file_accessors_paths_to_pods_group(:vendored_frameworks, :frameworks)
end
end
# Adds the bundled libraries to the Pods project
#
# @return [void]
#
def add_vendored_libraries
UI.message '- Adding libraries to Pods project' do
add_file_accessors_paths_to_pods_group(:vendored_libraries, :frameworks)
end
end
# Adds the resources of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively) in the resources group.
#
# @return [void]
#
def add_resources
UI.message '- Adding resources to Pods project' do
add_file_accessors_paths_to_pods_group(:resources, :resources, true)
add_file_accessors_paths_to_pods_group(:resource_bundle_files, :resources, true)
end
end
# Creates the link to the headers of the Pod in the sandbox.
#
# @return [void]
#
def link_headers
UI.message '- Linking headers' do
pod_targets.each do |pod_target|
pod_target.file_accessors.each do |file_accessor|
framework_exp = /\.framework\//
headers_sandbox = Pathname.new(file_accessor.spec.root.name)
# When integrating Pod as frameworks, built Pods are built into
# frameworks, whose headers are included inside the built
# framework. Those headers do not need to be linked from the
# sandbox.
unless pod_target.requires_frameworks? && pod_target.should_build?
pod_target.build_headers.add_search_path(headers_sandbox, pod_target.platform)
sandbox.public_headers.add_search_path(headers_sandbox, pod_target.platform)
header_mappings(headers_sandbox, file_accessor, file_accessor.headers).each do |namespaced_path, files|
pod_target.build_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
end
header_mappings(headers_sandbox, file_accessor, file_accessor.public_headers).each do |namespaced_path, files|
sandbox.public_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
end
end
unless pod_target.requires_frameworks?
vendored_frameworks_header_mappings(headers_sandbox, file_accessor).each do |namespaced_path, files|
sandbox.public_headers.add_files(namespaced_path, files)
end
end
end
end
end
end
#-----------------------------------------------------------------------#
private
# @!group Private Helpers
# @return [Array<Sandbox::FileAccessor>] The file accessors for all the
# specs platform combinations.
#
def file_accessors
@file_accessors ||= pod_targets.map(&:file_accessors).flatten.compact
end
# Adds file references to the list of the paths returned by the file
# accessor with the given key to the given group of the Pods project.
#
# @param [Symbol] file_accessor_key
# The method of the file accessor which would return the list of
# the paths.
#
# @param [Symbol] group_key
# The key of the group of the Pods project.
#
# @param [Bool] reflect_file_system_structure_for_development
# Whether organizing a local pod's files in subgroups inside
# the pod's group is allowed.
#
# @return [void]
#
def add_file_accessors_paths_to_pods_group(file_accessor_key, group_key = nil, reflect_file_system_structure_for_development = false)
file_accessors.each do |file_accessor|
pod_name = file_accessor.spec.name
local = sandbox.local?(pod_name)
paths = file_accessor.send(file_accessor_key)
paths = allowable_project_paths(paths)
paths.each do |path|
group = pods_project.group_for_spec(file_accessor.spec.name, group_key)
pods_project.add_file_reference(path, group, local && reflect_file_system_structure_for_development)
end
end
end
# Filters a list of paths down to those paths which can be added to
# the Xcode project. Some paths are intermediates and only their children
# should be added, while some paths are treated as bundles and their
# children should not be added directly.
#
# @param [Array<Pathname>] paths
# The paths to files or directories on disk.
#
# @return [Array<Pathname>] The paths which can be added to the Xcode project
#
def allowable_project_paths(paths)
lproj_paths = Set.new
lproj_paths_with_files = Set.new
allowable_paths = paths.select do |path|
path_str = path.to_s
# We add the directory for a Core Data model, but not the items in it.
next if path_str =~ /.*\.xcdatamodeld\/.+/i
# We add the directory for a Core Data migration mapping, but not the items in it.
next if path_str =~ /.*\.xcmappingmodel\/.+/i
# We add the directory for an asset catalog, but not the items in it.
next if path_str =~ /.*\.xcassets\/.+/i
if path_str =~ /\.lproj(\/|$)/i
# If the element is an .lproj directory then save it and potentially
# add it later if we don't find any contained items.
if path_str =~ /\.lproj$/i && path.directory?
lproj_paths << path
next
end
# Collect the paths for the .lproj directories that contain files.
lproj_path = /(^.*\.lproj)\/.*/i.match(path_str)[1]
lproj_paths_with_files << Pathname(lproj_path)
# Directories nested within an .lproj directory are added as file
# system references so their contained items are not added directly.
next if path.dirname.dirname == lproj_path
end
true
end
# Only add the path for the .lproj directories that do not have anything
# within them added as well. This generally happens if the glob within the
# resources directory was not a recursive glob.
allowable_paths + lproj_paths.subtract(lproj_paths_with_files).to_a
end
# Computes the destination sub-directory in the sandbox
#
# @param [Pathname] headers_sandbox
# The sandbox where the header links should be stored for this
# Pod.
#
# @param [Sandbox::FileAccessor] file_accessor
# The consumer file accessor for which the headers need to be
# linked.
#
# @param [Array<Pathname>] headers
# The absolute paths of the headers which need to be mapped.
#
# @return [Hash{Pathname => Array<Pathname>}] A hash containing the
# headers folders as the keys and the absolute paths of the
# header files as the values.
#
def header_mappings(headers_sandbox, file_accessor, headers)
consumer = file_accessor.spec_consumer
dir = headers_sandbox
dir += consumer.header_dir if consumer.header_dir
mappings = {}
headers.each do |header|
sub_dir = dir
if consumer.header_mappings_dir
header_mappings_dir = file_accessor.path_list.root + consumer.header_mappings_dir
relative_path = header.relative_path_from(header_mappings_dir)
sub_dir += relative_path.dirname
end
mappings[sub_dir] ||= []
mappings[sub_dir] << header
end
mappings
end
# Computes the destination sub-directory in the sandbox for headers
# from inside vendored frameworks.
#
# @param [Pathname] headers_sandbox
# The sandbox where the header links should be stored for this
# Pod.
#
# @param [Sandbox::FileAccessor] file_accessor
# The consumer file accessor for which the headers need to be
# linked.
#
def vendored_frameworks_header_mappings(headers_sandbox, file_accessor)
mappings = {}
file_accessor.vendored_frameworks.each do |framework|
headers_dir = Sandbox::FileAccessor.vendored_frameworks_headers_dir(framework)
headers = Sandbox::FileAccessor.vendored_frameworks_headers(framework)
framework_name = framework.basename(framework.extname)
dir = headers_sandbox + framework_name
headers.each do |header|
# the relative path of framework headers should be kept,
# not flattened like is done for most public headers.
relative_path = header.relative_path_from(headers_dir)
sub_dir = dir + relative_path.dirname
mappings[sub_dir] ||= []
mappings[sub_dir] << header
end
end
mappings
end
#-----------------------------------------------------------------------#
end
end
end
end
end
module Pod
class Installer
class Xcode
class PodsProjectGenerator
# Creates the target for the Pods libraries in the Pods project and the
# relative support files.
#
class PodTargetInstaller < TargetInstaller
# Creates the target in the Pods project and the relative support files.
#
# @return [void]
#
def install!
unless target.should_build?
add_resources_bundle_targets
return
end
UI.message "- Installing target `#{target.name}` #{target.platform}" do
add_target
create_support_files_dir
add_resources_bundle_targets
add_files_to_build_phases
create_xcconfig_file
if target.requires_frameworks?
create_info_plist_file
create_module_map
create_umbrella_header do |generator|
generator.imports += if header_mappings_dir
target.file_accessors.flat_map(&:public_headers).map do |pathname|
pathname.relative_path_from(header_mappings_dir)
end
else
target.file_accessors.flat_map(&:public_headers).map(&:basename)
end
end
create_build_phase_to_symlink_header_folders
end
create_prefix_header
create_dummy_source
end
end
private
# Remove the default headers folder path settings for static library pod
# targets.
#
# @return [Hash{String => String}]
#
def custom_build_settings
settings = super
unless target.requires_frameworks?
settings['PRIVATE_HEADERS_FOLDER_PATH'] = ''
settings['PUBLIC_HEADERS_FOLDER_PATH'] = ''
end
settings
end
#-----------------------------------------------------------------------#
SOURCE_FILE_EXTENSIONS = Sandbox::FileAccessor::SOURCE_FILE_EXTENSIONS
# Adds the build files of the pods to the target and adds a reference to
# the frameworks of the Pods.
#
# @note The Frameworks are used only for presentation purposes as the
# xcconfig is the authoritative source about their information.
#
# @note Core Data model directories (.xcdatamodeld) defined in the `resources`
# property are currently added to the `Copy Resources` build phase like
# all other resources. The Xcode UI adds these to the `Compile Sources`
# build phase, but they will compile correctly either way.
#
# @return [void]
#
def add_files_to_build_phases
target.file_accessors.each do |file_accessor|
consumer = file_accessor.spec_consumer
headers = file_accessor.headers
public_headers = file_accessor.public_headers
private_headers = file_accessor.private_headers
other_source_files = file_accessor.source_files.reject { |sf| SOURCE_FILE_EXTENSIONS.include?(sf.extname) }
{
true => file_accessor.arc_source_files,
false => file_accessor.non_arc_source_files,
}.each do |arc, files|
files = files - headers - other_source_files
flags = compiler_flags_for_consumer(consumer, arc)
regular_file_refs = files.map { |sf| project.reference_for_path(sf) }
native_target.add_file_references(regular_file_refs, flags)
end
header_file_refs = headers.map { |sf| project.reference_for_path(sf) }
native_target.add_file_references(header_file_refs) do |build_file|
add_header(build_file, public_headers, private_headers)
end
other_file_refs = other_source_files.map { |sf| project.reference_for_path(sf) }
native_target.add_file_references(other_file_refs, nil)
next unless target.requires_frameworks?
resource_refs = file_accessor.resources.flatten.map do |res|
project.reference_for_path(res)
end
# Some nested files are not directly present in the Xcode project, such as the contents
# of an .xcdatamodeld directory. These files will return nil file references.
resource_refs.compact!
native_target.add_resources(resource_refs)
end
end
# Adds the resources of the Pods to the Pods project.
#
# @note The source files are grouped by Pod and in turn by subspec
# (recursively) in the resources group.
#
# @note Core Data model directories (.xcdatamodeld) are currently added to the
# `Copy Resources` build phase like all other resources. The Xcode UI adds
# these to the `Compile Sources` build phase, but they will compile
# correctly either way.
#
# @return [void]
#
def add_resources_bundle_targets
target.file_accessors.each do |file_accessor|
file_accessor.resource_bundles.each do |bundle_name, paths|
file_references = paths.map do |path|
ref = project.reference_for_path(path)
# Some nested files are not directly present in the Xcode project, such as the contents
# of an .xcdatamodeld directory. These files are implicitly included by including their
# parent directory.
next if ref.nil?
# For variant groups, the variant group itself is added, not its members.
next ref.parent if ref.parent.is_a?(Xcodeproj::Project::Object::PBXVariantGroup)
ref
end
file_references = file_references.uniq.compact
label = target.resources_bundle_target_label(bundle_name)
bundle_target = project.new_resources_bundle(label, file_accessor.spec_consumer.platform_name)
bundle_target.product_reference.tap do |bundle_product|
bundle_file_name = "#{bundle_name}.bundle"
bundle_product.name = bundle_file_name
bundle_product.path = bundle_file_name
end
bundle_target.add_resources(file_references)
target.user_build_configurations.each do |bc_name, type|
bundle_target.add_build_configuration(bc_name, type)
end
bundle_target.deployment_target = deployment_target
target.resource_bundle_targets << bundle_target
if target.should_build?
native_target.add_dependency(bundle_target)
if target.requires_frameworks?
native_target.add_resources([bundle_target.product_reference])
end
end
# Create Info.plist file for bundle
path = target.info_plist_path
path.dirname.mkdir unless path.dirname.exist?
info_plist_path = path.dirname + "ResourceBundle-#{bundle_name}-#{path.basename}"
generator = Generator::InfoPlistFile.new(target, :bundle_package_type => :bndl)
generator.save_as(info_plist_path)
add_file_to_support_group(info_plist_path)
bundle_target.build_configurations.each do |c|
c.build_settings['PRODUCT_NAME'] = bundle_name
relative_info_plist_path = info_plist_path.relative_path_from(sandbox.root)
c.build_settings['INFOPLIST_FILE'] = relative_info_plist_path.to_s
c.build_settings['CONFIGURATION_BUILD_DIR'] = target.configuration_build_dir('$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)')
# Set the correct device family for this bundle, based on the platform
device_family_by_platform = {
:ios => '1,2',
:tvos => '3',
:watchos => '1,2' # The device family for watchOS is 4, but Xcode creates watchkit-compatible bundles as 1,2
}
if family = device_family_by_platform[target.platform.name]
c.build_settings['TARGETED_DEVICE_FAMILY'] = family
end
end
end
end
end
# Generates the contents of the xcconfig file and saves it to disk.
#
# @return [void]
#
def create_xcconfig_file
path = target.xcconfig_path
xcconfig_gen = Generator::XCConfig::PodXCConfig.new(target)
xcconfig_gen.save_as(path)
xcconfig_file_ref = add_file_to_support_group(path)
native_target.build_configurations.each do |c|
c.base_configuration_reference = xcconfig_file_ref
end
# also apply the private config to resource targets
target.resource_bundle_targets.each do |rsrc_target|
rsrc_target.build_configurations.each do |rsrc_bc|
rsrc_bc.base_configuration_reference = xcconfig_file_ref
end
end
end
# Creates a build phase which links the versioned header folders
# of the OS X into the framework bundle's root root directory.
# This is only necessary because the way how headers are copied
# via custom copy file build phases in combination with
# header_mappings_dir interferes with xcodebuild's expectations
# about the existence of private or public headers.
#
# @return [void]
#
def create_build_phase_to_symlink_header_folders
return unless target.platform.name == :osx && header_mappings_dir
build_phase = native_target.new_shell_script_build_phase('Create Symlinks to Header Folders')
build_phase.shell_script = <<-eos.strip_heredoc
cd "$CONFIGURATION_BUILD_DIR/$WRAPPER_NAME"
ln -fs ${PUBLIC_HEADERS_FOLDER_PATH\#$WRAPPER_NAME/} ${PUBLIC_HEADERS_FOLDER_PATH\#\$CONTENTS_FOLDER_PATH/}
ln -fs ${PRIVATE_HEADERS_FOLDER_PATH\#\$WRAPPER_NAME/} ${PRIVATE_HEADERS_FOLDER_PATH\#\$CONTENTS_FOLDER_PATH/}
eos
end
# Creates a prefix header file which imports `UIKit` or `Cocoa` according
# to the platform of the target. This file also include any prefix header
# content reported by the specification of the pods.
#
# @return [void]
#
def create_prefix_header
path = target.prefix_header_path
generator = Generator::PrefixHeader.new(target.file_accessors, target.platform)
generator.save_as(path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(project.path.dirname)
c.build_settings['GCC_PREFIX_HEADER'] = relative_path.to_s
end
end
ENABLE_OBJECT_USE_OBJC_FROM = {
:ios => Version.new('6'),
:osx => Version.new('10.8'),
:watchos => Version.new('2.0'),
:tvos => Version.new('9.0'),
}
# Returns the compiler flags for the source files of the given specification.
#
# The following behavior is regarding the `OS_OBJECT_USE_OBJC` flag. When
# set to `0`, it will allow code to use `dispatch_release()` on >= iOS 6.0
# and OS X 10.8.
#
# * New libraries that do *not* require ARC don’t need to care about this
# issue at all.
#
# * New libraries that *do* require ARC _and_ have a deployment target of
# >= iOS 6.0 or OS X 10.8:
#
# These no longer use `dispatch_release()` and should *not* have the
# `OS_OBJECT_USE_OBJC` flag set to `0`.
#
# **Note:** this means that these libraries *have* to specify the
# deployment target in order to function well.
#
# * New libraries that *do* require ARC, but have a deployment target of
# < iOS 6.0 or OS X 10.8:
#
# These contain `dispatch_release()` calls and as such need the
# `OS_OBJECT_USE_OBJC` flag set to `1`.
#
# **Note:** libraries that do *not* specify a platform version are
# assumed to have a deployment target of < iOS 6.0 or OS X 10.8.
#
# For more information, see: http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h
#
# @param [Specification::Consumer] consumer
# The consumer for the specification for which the compiler flags
# are needed.
#
# @return [String] The compiler flags.
#
def compiler_flags_for_consumer(consumer, arc)
flags = consumer.compiler_flags.dup
if !arc
flags << '-fno-objc-arc'
else
platform_name = consumer.platform_name
spec_deployment_target = consumer.spec.deployment_target(platform_name)
if spec_deployment_target.nil? || Version.new(spec_deployment_target) < ENABLE_OBJECT_USE_OBJC_FROM[platform_name]
flags << '-DOS_OBJECT_USE_OBJC=0'
end
end
if target.inhibit_warnings?
flags << '-w -Xanalyzer -analyzer-disable-all-checks'
end
flags * ' '
end
# Adds a reference to the given file in the support group of this target.
#
# @param [Pathname] path
# The path of the file to which the reference should be added.
#
# @return [PBXFileReference] the file reference of the added file.
#
def add_file_to_support_group(path)
pod_name = target.pod_name
dir = target.support_files_dir
group = project.pod_support_files_group(pod_name, dir)
group.new_file(path)
end
def create_module_map
return super unless custom_module_map
path = target.module_map_path
UI.message "- Copying module map file to #{UI.path(path)}" do
FileUtils.cp(custom_module_map, path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(sandbox.root)
c.build_settings['MODULEMAP_FILE'] = relative_path.to_s
end
end
end
def create_umbrella_header
return super unless custom_module_map
end
def custom_module_map
@custom_module_map ||= target.file_accessors.first.module_map
end
def header_mappings_dir
return @header_mappings_dir if defined?(@header_mappings_dir)
file_accessor = target.file_accessors.first
@header_mappings_dir = if dir = file_accessor.spec_consumer.header_mappings_dir
file_accessor.path_list.root + dir
end
end
def add_header(build_file, public_headers, private_headers)
file_ref = build_file.file_ref
acl = if public_headers.include?(file_ref.real_path)
'Public'
elsif private_headers.include?(file_ref.real_path)
'Private'
else
'Project'
end
if target.requires_frameworks? && header_mappings_dir && acl != 'Project'
relative_path = file_ref.real_path.relative_path_from(header_mappings_dir)
sub_dir = relative_path.dirname
copy_phase_name = "Copy #{sub_dir} #{acl} Headers"
copy_phase = native_target.copy_files_build_phases.find { |bp| bp.name == copy_phase_name } ||
native_target.new_copy_files_build_phase(copy_phase_name)
copy_phase.symbol_dst_subfolder_spec = :products_directory
copy_phase.dst_path = "$(#{acl.upcase}_HEADERS_FOLDER_PATH)/#{sub_dir}"
copy_phase.add_file_reference(file_ref, true)
else
build_file.settings ||= {}
build_file.settings['ATTRIBUTES'] = [acl]
end
end
#-----------------------------------------------------------------------#
end
end
end
end
end
module Pod
class Installer
class Xcode
class PodsProjectGenerator
# Controller class responsible of creating and configuring the static
# library target in Pods project. It also creates the support file needed
# by the target.
#
class TargetInstaller
# @return [Sandbox] sandbox the sandbox where the support files should
# be generated.
#
attr_reader :sandbox
# @return [Target] The library whose target needs to be generated.
#
attr_reader :target
# @param [Project] project @see project
# @param [Target] target @see target
#
def initialize(sandbox, target)
@sandbox = sandbox
@target = target
end
private
#-----------------------------------------------------------------------#
# @!group Installation steps
# Adds the target for the library to the Pods project with the
# appropriate build configurations.
#
# @note The `PODS_HEADERS_SEARCH_PATHS` overrides the xcconfig.
#
# @return [void]
#
def add_target
product_type = target.product_type
name = target.label
platform = target.platform.name
language = target.uses_swift? ? :swift : :objc
@native_target = project.new_target(product_type, name, platform, deployment_target, nil, language)
product_name = target.product_name
product = @native_target.product_reference
product.name = product_name
target.user_build_configurations.each do |bc_name, type|
@native_target.add_build_configuration(bc_name, type)
end
@native_target.build_configurations.each do |configuration|
configuration.build_settings.merge!(custom_build_settings)
end
target.native_target = @native_target
end
# @return [String] The deployment target.
#
def deployment_target
target.platform.deployment_target.to_s
end
# Returns the customized build settings which are overridden in the build
# settings of the user target.
#
# @return [Hash{String => String}]
#
def custom_build_settings
settings = {}
unless target.archs.empty?
settings['ARCHS'] = target.archs
end
if target.requires_frameworks?
settings['PRODUCT_NAME'] = target.product_module_name
else
settings.merge!('OTHER_LDFLAGS' => '', 'OTHER_LIBTOOLFLAGS' => '')
end
settings
end
# Creates the directory where to store the support files of the target.
#
def create_support_files_dir
target.support_files_dir.mkdir
end
# Creates the Info.plist file which sets public framework attributes
#
# @return [void]
#
def create_info_plist_file
path = target.info_plist_path
UI.message "- Generating Info.plist file at #{UI.path(path)}" do
generator = Generator::InfoPlistFile.new(target)
generator.save_as(path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(sandbox.root)
c.build_settings['INFOPLIST_FILE'] = relative_path.to_s
end
end
end
# Creates the module map file which ensures that the umbrella header is
# recognized with a customized path
#
# @yield_param [Generator::ModuleMap]
# yielded once to configure the private headers
#
# @return [void]
#
def create_module_map
path = target.module_map_path
UI.message "- Generating module map file at #{UI.path(path)}" do
generator = Generator::ModuleMap.new(target)
yield generator if block_given?
generator.save_as(path)
add_file_to_support_group(path)
native_target.build_configurations.each do |c|
relative_path = path.relative_path_from(sandbox.root)
c.build_settings['MODULEMAP_FILE'] = relative_path.to_s
end
end
end
# Generates a header which ensures that all header files are exported
# in the module map
#
# @yield_param [Generator::UmbrellaHeader]
# yielded once to configure the imports
#
def create_umbrella_header
path = target.umbrella_header_path
UI.message "- Generating umbrella header at #{UI.path(path)}" do
generator = Generator::UmbrellaHeader.new(target)
yield generator if block_given?
generator.save_as(path)
# Add the file to the support group and the native target,
# so it will been added to the header build phase
file_ref = add_file_to_support_group(path)
native_target.add_file_references([file_ref])
# Make the umbrella header public
build_file = native_target.headers_build_phase.build_file(file_ref)
build_file.settings ||= {}
build_file.settings['ATTRIBUTES'] = ['Public']
end
end
# Generates a dummy source file for each target so libraries that contain
# only categories build.
#
# @return [void]
#
def create_dummy_source
path = target.dummy_source_path
generator = Generator::DummySource.new(target.label)
generator.save_as(path)
file_reference = add_file_to_support_group(path)
native_target.source_build_phase.add_file_reference(file_reference)
end
# @return [PBXNativeTarget] the target generated by the installation
# process.
#
# @note Generated by the {#add_target} step.
#
attr_reader :native_target
private
#-----------------------------------------------------------------------#
# @!group Private helpers.
# @return [Project] the Pods project of the sandbox.
#
def project
sandbox.project
end
# @return [PBXGroup] the group where the file references to the support
# files should be stored.
#
attr_reader :support_files_group
# Adds a reference to the given file in the support group of this target.
#
# @param [Pathname] path
# The path of the file to which the reference should be added.
#
# @return [PBXFileReference] the file reference of the added file.
#
def add_file_to_support_group(path)
support_files_group.new_file(path)
end
#-----------------------------------------------------------------------#
end
end
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Installer::FileReferencesInstaller do
before do
@pod_target = fixture_pod_target('banana-lib/BananaLib.podspec')
@file_accessor = @pod_target.file_accessors.first
@project = Project.new(config.sandbox.project_path)
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@installer = Installer::FileReferencesInstaller.new(config.sandbox, [@pod_target], @project)
end
#-------------------------------------------------------------------------#
describe 'Installation With Flat Resources Glob' do
it 'adds the files references of the source files the Pods project' do
@file_accessor.path_list.read_file_system
@file_accessor.path_list.expects(:read_file_system)
@installer.install!
end
it 'adds the files references of the source files the Pods project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Banana.m']
file_ref.should.be.not.nil
file_ref.path.should == 'Classes/Banana.m'
end
it 'adds the file references of the frameworks of the project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Frameworks/Bananalib.framework']
file_ref.should.be.not.nil
file_ref.path.should == 'Bananalib.framework'
end
it 'adds the file references of the libraries of the project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Frameworks/libBananalib.a']
file_ref.should.be.not.nil
file_ref.path.should == 'libBananalib.a'
end
it 'adds files references for the resources of the Pods project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/logo-sidebar.png']
file_ref.should.be.not.nil
file_ref.path.should == 'Resources/logo-sidebar.png'
end
it "adds file references for localization directories if glob doesn't include contained files" do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/en.lproj']
file_ref.should.be.not.nil
file_ref.path.should == 'Resources/en.lproj'
end
it 'adds file references for files within CoreData directories' do
@installer.install!
model_ref = @installer.pods_project['Pods/BananaLib/Resources/Sample.xcdatamodeld']
model_ref.should.be.not.nil
model_ref.path.should == 'Resources/Sample.xcdatamodeld'
# Files within the .xcdatamodeld directory are added automatically by adding the .xcdatamodeld directory.
file_ref = @installer.pods_project['Pods/BananaLib/Resources/Sample.xcdatamodeld/Sample.xcdatamodel']
file_ref.should.be.not.nil
file_ref.path.should == 'Sample.xcdatamodel'
file_ref.source_tree.should == '<group>'
end
it 'links the headers required for building the pod target' do
@installer.install!
headers_root = @pod_target.build_headers.root
public_headers = [headers_root + 'BananaLib/Banana.h', headers_root + 'BananaLib/MoreBanana.h']
private_header = headers_root + 'BananaLib/BananaPrivate.h'
framework_header = headers_root + 'BananaLib/Bananalib/Bananalib.h'
public_headers.each { |public_header| public_header.should.exist }
private_header.should.exist
framework_header.should.not.exist
end
it 'links the public headers meant for the user' do
@installer.install!
headers_root = config.sandbox.public_headers.root
public_headers = [headers_root + 'BananaLib/Banana.h', headers_root + 'BananaLib/MoreBanana.h']
private_header = headers_root + 'BananaLib/BananaPrivate.h'
framework_header = headers_root + 'BananaLib/Bananalib/Bananalib.h'
framework_subdir_header = headers_root + 'BananaLib/Bananalib/SubDir/SubBananalib.h'
public_headers.each { |public_header| public_header.should.exist }
private_header.should.not.exist
framework_header.should.exist
framework_subdir_header.should.exist
end
it 'links the public headers meant for the user, but only for Pods that are not built' do
Target.any_instance.stubs(:requires_frameworks?).returns(true)
pod_target_one = fixture_pod_target('banana-lib/BananaLib.podspec')
pod_target_two = fixture_pod_target('monkey/monkey.podspec')
project = Project.new(config.sandbox.project_path)
project.add_pod_group('BananaLib', fixture('banana-lib'))
project.add_pod_group('monkey', fixture('monkey'))
installer = Installer::FileReferencesInstaller.new(config.sandbox, [pod_target_one, pod_target_two], project)
installer.install!
headers_root = config.sandbox.public_headers.root
banana_headers = [headers_root + 'BananaLib/Banana.h', headers_root + 'BananaLib/MoreBanana.h']
banana_headers.each { |banana_header| banana_header.should.not.exist }
monkey_header = headers_root + 'monkey/monkey.h'
monkey_header.should.exist
end
it "doesn't link public headers from vendored framework, when frameworks required" do
Target.any_instance.stubs(:requires_frameworks?).returns(true)
@installer.install!
headers_root = config.sandbox.public_headers.root
framework_header = headers_root + 'BananaLib/Bananalib/Bananalib.h'
framework_header.should.not.exist
end
end
#-------------------------------------------------------------------------#
describe 'Installation With Recursive Resources Glob' do
before do
spec = fixture_spec('banana-lib/BananaLib.podspec')
spec.resources = 'Resources/**/*'
@pod_target = fixture_pod_target(spec)
@file_accessor = @pod_target.file_accessors.first
@project = Project.new(config.sandbox.project_path)
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@installer = Installer::FileReferencesInstaller.new(config.sandbox, [@pod_target], @project)
end
it "doesn't add file references for localization directories themselves" \
'if glob includes contained files' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/en.lproj']
file_ref.should.be.nil
end
it 'creates file system reference variant groups for nested localized resource directories' do
@installer.install!
ref = @installer.pods_project['Pods/BananaLib/Resources/nested']
ref.should.be.not.nil
ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup).should.be.true
end
it "doesn't add file references for nested localized resources" do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/en.lproj/nested/logo-nested.png']
file_ref.should.be.nil
end
it "doesn't add file references for files within Asset Catalogs" do
@installer.install!
resources_group_ref = @installer.pods_project['Pods/BananaLib/Resources']
catalog_path = 'Resources/Images.xcassets'
# The asset catalog should be a "PBXFileReference" and therefore doesn't have children.
resources_group_ref.files.any? { |ref| ref.path == catalog_path }.should.be.true
# The asset catalog should not also be a "PBXGroup".
resources_group_ref.groups.any? { |ref| ref.path == catalog_path }.should.be.false
# None of the children of the catalog directory should be present directly.
resources_group_ref.files.any? { |ref| ref.path.start_with?(catalog_path + '/') }.should.be.false
end
it "doesn't add file references for files within CoreData migration mappings" do
@installer.install!
resources_group_ref = @installer.pods_project['Pods/BananaLib/Resources']
mapping_path = 'Resources/Migration.xcmappingmodel'
# The mapping model should be a "PBXFileReference" and therefore doesn't have children.
resources_group_ref.files.any? { |ref| ref.path == mapping_path }.should.be.true
# The mapping model should not also be a "PBXGroup".
resources_group_ref.groups.any? { |ref| ref.path == mapping_path }.should.be.false
# None of the children of the mapping model directory should be present directly.
resources_group_ref.files.any? { |ref| ref.path.start_with?(mapping_path + '/') }.should.be.false
end
end
#-------------------------------------------------------------------------#
describe 'Private Helpers' do
describe '#file_accessors' do
it 'returns the file accessors' do
pod_target_1 = PodTarget.new([stub('Spec')], [fixture_target_definition], config.sandbox)
pod_target_1.file_accessors = [fixture_file_accessor('banana-lib/BananaLib.podspec')]
pod_target_2 = PodTarget.new([stub('Spec')], [fixture_target_definition], config.sandbox)
pod_target_2.file_accessors = [fixture_file_accessor('banana-lib/BananaLib.podspec')]
installer = Installer::FileReferencesInstaller.new(config.sandbox, [pod_target_1, pod_target_2], @project)
roots = installer.send(:file_accessors).map { |fa| fa.path_list.root }
roots.should == [fixture('banana-lib'), fixture('banana-lib')]
end
it 'handles pods without file accessors' do
pod_target_1 = PodTarget.new([stub('Spec')], [fixture_target_definition], config.sandbox)
pod_target_1.file_accessors = []
installer = Installer::FileReferencesInstaller.new(config.sandbox, [pod_target_1], @project)
installer.send(:file_accessors).should == []
end
end
describe '#header_mappings' do
it 'returns the header mappings' do
headers_sandbox = Pathname.new('BananaLib')
headers = [Pathname.new('BananaLib/Banana.h')]
mappings = @installer.send(:header_mappings, headers_sandbox, @file_accessor, headers)
mappings.should == {
headers_sandbox => headers,
}
end
it 'takes into account the header dir specified in the spec' do
headers_sandbox = Pathname.new('BananaLib')
headers = [Pathname.new('BananaLib/Banana.h')]
@file_accessor.spec_consumer.stubs(:header_dir).returns('Sub_dir')
mappings = @installer.send(:header_mappings, headers_sandbox, @file_accessor, headers)
mappings.should == {
(headers_sandbox + 'Sub_dir') => headers,
}
end
it 'takes into account the header mappings dir specified in the spec' do
headers_sandbox = Pathname.new('BananaLib')
header_1 = @file_accessor.root + 'BananaLib/sub_dir/dir_1/banana_1.h'
header_2 = @file_accessor.root + 'BananaLib/sub_dir/dir_2/banana_2.h'
headers = [header_1, header_2]
@file_accessor.spec_consumer.stubs(:header_mappings_dir).returns('BananaLib/sub_dir')
mappings = @installer.send(:header_mappings, headers_sandbox, @file_accessor, headers)
mappings.should == {
(headers_sandbox + 'dir_1') => [header_1],
(headers_sandbox + 'dir_2') => [header_2],
}
end
end
describe '#vendored_frameworks_header_mappings' do
it 'returns the vendored frameworks header mappings' do
headers_sandbox = Pathname.new('BananaLib')
header = @file_accessor.root + 'Bananalib.framework/Versions/A/Headers/Bananalib.h'
header_subdir = @file_accessor.root + 'Bananalib.framework/Versions/A/Headers/SubDir/SubBananalib.h'
mappings = @installer.send(:vendored_frameworks_header_mappings, headers_sandbox, @file_accessor)
mappings.should == {
(headers_sandbox + 'Bananalib') => [header],
(headers_sandbox + 'Bananalib/SubDir') => [header_subdir],
}
end
end
end
#-------------------------------------------------------------------------#
end
end
require File.expand_path('../../../../spec_helper', __FILE__)
module Pod
describe Installer::AggregateTargetInstaller do
describe 'In General' do
before do
config.sandbox.prepare
@podfile = Podfile.new do
platform :ios, '6.0'
project 'SampleProject/SampleProject'
target 'SampleProject'
end
@target_definition = @podfile.target_definitions['SampleProject']
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
path_list = Sandbox::PathList.new(fixture('banana-lib'))
@spec = fixture_spec('banana-lib/BananaLib.podspec')
file_accessor = Sandbox::FileAccessor.new(path_list, @spec.consumer(:ios))
@project.add_pod_group('BananaLib', fixture('banana-lib'))
group = @project.group_for_spec('BananaLib')
file_accessor.source_files.each do |file|
@project.add_file_reference(file, group)
end
@target = AggregateTarget.new(@target_definition, config.sandbox)
@target.client_root = config.sandbox.root.dirname
@target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
@pod_target = PodTarget.new([@spec], [@target_definition], config.sandbox)
@pod_target.user_build_configurations = @target.user_build_configurations
@pod_target.file_accessors = [file_accessor]
@target.pod_targets = [@pod_target]
@installer = Installer::AggregateTargetInstaller.new(config.sandbox, @target)
@spec.prefix_header_contents = '#import "BlocksKit.h"'
end
it 'adds file references for the support files of the target' do
@installer.install!
group = @project.support_files_group['Pods-SampleProject']
group.children.map(&:display_name).sort.should == [
'Pods-SampleProject-acknowledgements.markdown',
'Pods-SampleProject-acknowledgements.plist',
'Pods-SampleProject-dummy.m',
'Pods-SampleProject-frameworks.sh',
'Pods-SampleProject-resources.sh',
'Pods-SampleProject.appstore.xcconfig',
'Pods-SampleProject.debug.xcconfig',
'Pods-SampleProject.release.xcconfig',
'Pods-SampleProject.test.xcconfig',
]
end
#--------------------------------------#
it 'adds the target for the static library to the project' do
@installer.install!
@project.targets.count.should == 1
@project.targets.first.name.should == @target_definition.label
end
it 'sets the platform and the deployment target for iOS targets' do
@installer.install!
target = @project.targets.first
target.platform_name.should == :ios
target.deployment_target.should == '6.0'
target.build_settings('Debug')['IPHONEOS_DEPLOYMENT_TARGET'].should == '6.0'
target.build_settings('AppStore')['IPHONEOS_DEPLOYMENT_TARGET'].should == '6.0'
end
it 'sets the platform and the deployment target for OS X targets' do
@target.stubs(:platform).returns(Platform.new(:osx, '10.8'))
@installer.install!
target = @project.targets.first
target.platform_name.should == :osx
target.deployment_target.should == '10.8'
target.build_settings('Debug')['MACOSX_DEPLOYMENT_TARGET'].should == '10.8'
target.build_settings('AppStore')['MACOSX_DEPLOYMENT_TARGET'].should == '10.8'
end
it "adds the user's build configurations to the target" do
@installer.install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w( AppStore Debug Release Test )
end
it 'it creates different hash instances for the build settings of various build configurations' do
@installer.install!
build_settings = @project.targets.first.build_configurations.map(&:build_settings)
build_settings.map(&:object_id).uniq.count.should == 4
end
it 'does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should.be.nil
end
end
it 'will be built as static library' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['MACH_O_TYPE'].should == 'staticlib'
end
end
it 'will be skipped when installing' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['SKIP_INSTALL'].should == 'YES'
end
end
it 'has a PRODUCT_BUNDLE_IDENTIFIER set' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'].should == 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}'
end
end
#--------------------------------------#
it 'creates the xcconfig file' do
@installer.install!
file = config.sandbox.root + @target.xcconfig_path('Release')
xcconfig = Xcodeproj::Config.new(file)
xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}/Pods'
end
it 'creates a bridge support file' do
Podfile.any_instance.stubs(:generate_bridge_support? => true)
Generator::BridgeSupport.any_instance.expects(:save_as).once
@installer.install!
end
it 'creates a create copy resources script' do
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-resources.sh'
script.read.should.include?('logo-sidebar.png')
end
it 'does not add framework resources to copy resources script' do
@pod_target.stubs(:requires_frameworks? => true)
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-resources.sh'
script.read.should.not.include?('logo-sidebar.png')
end
it 'adds the resources bundles to the copy resources script' do
@pod_target.file_accessors.first.stubs(:resource_bundles).returns(
'Trees' => [Pathname('palm.jpg')],
'Leafs' => [Pathname('leaf.jpg')],
)
resources_by_config = @installer.send(:resources_by_config)
resources_by_config.each_value do |resources|
resources.should.include '$PODS_CONFIGURATION_BUILD_DIR/BananaLib/Trees.bundle'
resources.should.include '$PODS_CONFIGURATION_BUILD_DIR/BananaLib/Leafs.bundle'
end
end
it 'adds the bridge support file to the copy resources script, if one was created' do
@installer.stubs(:bridge_support_file).returns(@installer.target.bridge_support_path)
resources_by_config = @installer.send(:resources_by_config)
resources_by_config.each_value do |resources|
resources.should.include @installer.target.bridge_support_path
end
end
it 'does add pods to the embed frameworks script' do
@pod_target.stubs(:requires_frameworks? => true)
@target.stubs(:requires_frameworks? => true)
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-frameworks.sh'
script.read.should.include?('BananaLib.framework')
end
it 'uniques resources by config' do
a_path = Pathname.new(@project.path.dirname + '/duplicated/path.jpg')
duplicated_paths = [a_path, a_path]
@installer.target.pod_targets.each do |pod_target|
pod_target.file_accessors.each do |accessor|
accessor.stubs(:resources => duplicated_paths)
end
end
resources_by_config = @installer.send(:resources_by_config)
resources_by_config.each_value do |resources|
resources.length.should == 1
resources[0].basename.should == a_path.basename
end
end
it 'does not add pods to the embed frameworks script if they are not to be built' do
@pod_target.stubs(:should_build? => false)
@pod_target.stubs(:requires_frameworks? => true)
@target.stubs(:requires_frameworks? => true)
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-frameworks.sh'
script.read.should.not.include?('BananaLib.framework')
end
it 'creates the acknowledgements files ' do
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
markdown = support_files_dir + 'Pods-SampleProject-acknowledgements.markdown'
markdown.read.should.include?('Permission is hereby granted')
plist = support_files_dir + 'Pods-SampleProject-acknowledgements.plist'
plist.read.should.include?('Permission is hereby granted')
end
it 'creates a dummy source to ensure the creation of a single base library' do
@installer.install!
build_files = @installer.target.native_target.source_build_phase.files
build_file = build_files.find { |bf| bf.file_ref.path.include?('Pods-SampleProject-dummy.m') }
build_file.should.be.not.nil
build_file.file_ref.path.should == 'Pods-SampleProject-dummy.m'
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
dummy = support_files_dir + 'Pods-SampleProject-dummy.m'
dummy.read.should.include?('@interface PodsDummy_Pods')
end
end
end
end
require File.expand_path('../../../../spec_helper', __FILE__)
module Pod
describe Installer::PodTargetInstaller do
describe 'In General' do
before do
config.sandbox.prepare
@podfile = Podfile.new do
platform :ios, '6.0'
project 'SampleProject/SampleProject'
target 'SampleProject'
end
@target_definition = @podfile.target_definitions['SampleProject']
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
path_list = Sandbox::PathList.new(fixture('banana-lib'))
@spec = fixture_spec('banana-lib/BananaLib.podspec')
file_accessor = Sandbox::FileAccessor.new(path_list, @spec.consumer(:ios))
@project.add_pod_group('BananaLib', fixture('banana-lib'))
group = @project.group_for_spec('BananaLib')
file_accessor.source_files.each do |file|
@project.add_file_reference(file, group)
end
file_accessor.resources.each do |resource|
@project.add_file_reference(resource, group)
end
@pod_target = PodTarget.new([@spec], [@target_definition], config.sandbox)
@pod_target.file_accessors = [file_accessor]
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release }
@installer = Installer::PodTargetInstaller.new(config.sandbox, @pod_target)
@spec.prefix_header_contents = '#import "BlocksKit.h"'
end
it 'uses the maximum of all spec deployment targets' do
spec_1 = Pod::Specification.new { |s| s.ios.deployment_target = '10.10' }
spec_2 = Pod::Specification.new { |s| s.ios.deployment_target = '10.9' }
spec_3 = Pod::Specification.new
@pod_target.stubs(:specs).returns([spec_1, spec_2, spec_3])
@installer.send(:deployment_target).should == '10.10'
end
it 'sets the platform and the deployment target for iOS targets' do
@installer.install!
target = @project.targets.first
target.platform_name.should == :ios
target.deployment_target.should == '4.3'
target.build_settings('Debug')['IPHONEOS_DEPLOYMENT_TARGET'].should == '4.3'
end
it 'sets the platform and the deployment target for iOS targets that require frameworks' do
@pod_target.stubs(:requires_frameworks?).returns(true)
@installer.install!
target = @project.targets.first
target.platform_name.should == :ios
target.deployment_target.should == '8.0'
target.build_settings('Debug')['IPHONEOS_DEPLOYMENT_TARGET'].should == '8.0'
end
it 'sets the platform and the deployment target for OS X targets' do
@pod_target.target_definitions.first.stubs(:platform).returns(Platform.new(:osx, '10.8'))
@installer.install!
target = @project.targets.first
target.platform_name.should == :osx
target.deployment_target.should == '10.6'
target.build_settings('Debug')['MACOSX_DEPLOYMENT_TARGET'].should == '10.6'
end
it "adds the user's build configurations to the target" do
@pod_target.user_build_configurations.merge!('AppStore' => :release, 'Test' => :debug)
@installer.install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w( AppStore Debug Release Test )
end
it 'it creates different hash instances for the build settings of various build configurations' do
@installer.install!
build_settings = @project.targets.first.build_configurations.map(&:build_settings)
build_settings.map(&:object_id).uniq.count.should == 2
end
it 'does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should.be.nil
end
end
#--------------------------------------#
describe 'headers folder paths' do
it 'does not set them for framework targets' do
@pod_target.stubs(:requires_frameworks? => true)
@installer.install!
@project.targets.first.build_configurations.each do |config|
config.build_settings['PUBLIC_HEADERS_FOLDER_PATH'].should.be.nil
config.build_settings['PRIVATE_HEADERS_FOLDER_PATH'].should.be.nil
end
end
it 'empties them for non-framework targets' do
@installer.install!
@project.targets.first.build_configurations.each do |config|
config.build_settings['PUBLIC_HEADERS_FOLDER_PATH'].should.be.empty
config.build_settings['PRIVATE_HEADERS_FOLDER_PATH'].should.be.empty
end
end
end
#--------------------------------------#
it 'adds the source files of each pod to the target of the Pod library' do
@installer.install!
names = @installer.target.native_target.source_build_phase.files.map { |bf| bf.file_ref.display_name }
names.should.include('Banana.m')
end
#--------------------------------------#
it 'adds framework resources to the framework target' do
@pod_target.stubs(:requires_frameworks? => true)
@installer.install!
resources = @project.targets.first.resources_build_phase.files
resources.count.should > 0
resource = resources.find { |res| res.file_ref.path.include?('logo-sidebar.png') }
resource.should.be.not.nil
resource = resources.find { |res| res.file_ref.path.include?('en.lproj') }
resource.should.be.not.nil
end
#--------------------------------------#
describe 'with a scoped pod target' do
before do
@pod_target = @pod_target.scoped.first
@installer = Installer::PodTargetInstaller.new(config.sandbox, @pod_target)
end
it 'adds file references for the support files of the target' do
@installer.install!
group = @project['Pods/BananaLib/Support Files']
group.children.map(&:display_name).sort.should == [
'BananaLib-Pods-SampleProject-dummy.m',
'BananaLib-Pods-SampleProject-prefix.pch',
'BananaLib-Pods-SampleProject.xcconfig',
]
end
it 'adds the target for the static library to the project' do
@installer.install!
@project.targets.count.should == 1
@project.targets.first.name.should == 'BananaLib-Pods-SampleProject'
end
describe 'resource bundle targets' do
before do
@pod_target.file_accessors.first.stubs(:resource_bundles).returns('banana_bundle' => [])
@installer.install!
@bundle_target = @project.targets.find { |t| t.name == 'BananaLib-Pods-SampleProject-banana_bundle' }
end
it 'adds the resource bundle targets' do
@bundle_target.should.be.an.instance_of Xcodeproj::Project::Object::PBXNativeTarget
@bundle_target.product_reference.name.should == 'banana_bundle.bundle'
@bundle_target.product_reference.path.should == 'banana_bundle.bundle'
@bundle_target.platform_name.should == :ios
@bundle_target.deployment_target.should == '4.3'
end
it 'adds the build configurations to the resources bundle targets' do
file = config.sandbox.root + @pod_target.xcconfig_path
@bundle_target.build_configurations.each do |bc|
bc.base_configuration_reference.real_path.should == file
end
end
it 'sets the correct product name' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['PRODUCT_NAME'].should == 'banana_bundle'
end
end
it 'sets the correct Info.plist file path' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['INFOPLIST_FILE'].should == 'Target Support Files/BananaLib-Pods-SampleProject/ResourceBundle-banana_bundle-Info.plist'
end
end
it 'sets the correct build dir' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['CONFIGURATION_BUILD_DIR'].should == '$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/BananaLib-Pods-SampleProject'
end
end
it 'sets the correct targeted device family for the resource bundle targets' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['TARGETED_DEVICE_FAMILY'].should == '1,2'
end
end
end
end
#--------------------------------------#
describe 'with an unscoped pod target' do
it 'adds file references for the support files of the target' do
@installer.install!
@project.support_files_group
group = @project['Pods/BananaLib/Support Files']
group.children.map(&:display_name).sort.should == [
'BananaLib-dummy.m',
'BananaLib-prefix.pch',
'BananaLib.xcconfig',
]
end
it 'adds the target for the static library to the project' do
@installer.install!
@project.targets.count.should == 1
@project.targets.first.name.should == 'BananaLib'
end
describe 'resource bundle targets' do
before do
@pod_target.file_accessors.first.stubs(:resource_bundles).returns('banana_bundle' => [])
@installer.install!
@bundle_target = @project.targets.find { |t| t.name == 'BananaLib-banana_bundle' }
end
it 'adds the resource bundle targets' do
@bundle_target.should.be.an.instance_of Xcodeproj::Project::Object::PBXNativeTarget
@bundle_target.product_reference.name.should == 'banana_bundle.bundle'
@bundle_target.product_reference.path.should == 'banana_bundle.bundle'
end
it 'adds the build configurations to the resources bundle targets' do
file = config.sandbox.root + @pod_target.xcconfig_path
@bundle_target.build_configurations.each do |bc|
bc.base_configuration_reference.real_path.should == file
end
end
end
end
#--------------------------------------#
it 'creates the xcconfig file' do
@installer.install!
file = config.sandbox.root + @pod_target.xcconfig_path
xcconfig = Xcodeproj::Config.new(file)
xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}'
end
it "creates a prefix header, including the contents of the specification's prefix header" do
@spec.prefix_header_contents = '#import "BlocksKit.h"'
@installer.install!
generated = @pod_target.prefix_header_path.read
expected = <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
#import <BananaTree/BananaTree.h>
EOS
generated.should == expected
end
it 'creates a dummy source to ensure the compilation of libraries with only categories' do
@installer.install!
dummy_source_basename = @pod_target.dummy_source_path.basename.to_s
build_files = @installer.target.native_target.source_build_phase.files
build_file = build_files.find { |bf| bf.file_ref.display_name == dummy_source_basename }
build_file.should.be.not.nil
build_file.file_ref.path.should == dummy_source_basename
@pod_target.dummy_source_path.read.should.include?('@interface PodsDummy_BananaLib')
end
#--------------------------------------------------------------------------------#
it 'does not create a target if the specification does not define source files' do
@pod_target.file_accessors.first.stubs(:source_files).returns([])
@installer.install!
@project.targets.should == []
end
#--------------------------------------------------------------------------------#
describe 'concerning header_mappings_dirs' do
before do
@project.add_pod_group('snake', fixture('snake'))
@pod_target = fixture_pod_target('snake/snake.podspec', [@target_definition])
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release }
@pod_target.stubs(:requires_frameworks? => true)
group = @project.group_for_spec('snake')
@pod_target.file_accessors.first.source_files.each do |file|
@project.add_file_reference(file, group)
end
@installer.stubs(:target).returns(@pod_target)
end
it 'creates custom copy files phases for framework pods' do
@installer.install!
target = @project.native_targets.first
target.name.should == 'snake'
header_build_phase_file_refs = target.headers_build_phase.files.
reject { |build_file| build_file.settings.nil? }.
map { |build_file| build_file.file_ref.path }
header_build_phase_file_refs.should == %w(
Code/C/Boa.h
Code/C/Garden.h
Code/C/Rattle.h
snake-umbrella.h
)
copy_files_build_phases = target.copy_files_build_phases.sort_by(&:name)
copy_files_build_phases.map(&:name).should == [
'Copy . Public Headers',
'Copy A Public Headers',
'Copy B Private Headers',
]
copy_files_build_phases.map(&:symbol_dst_subfolder_spec).should == Array.new(3, :products_directory)
copy_files_build_phases.map(&:dst_path).should == [
'$(PUBLIC_HEADERS_FOLDER_PATH)/.',
'$(PUBLIC_HEADERS_FOLDER_PATH)/A',
'$(PRIVATE_HEADERS_FOLDER_PATH)/B',
]
copy_files_build_phases.map { |phase| phase.files_references.map(&:path) }.should == [
['Code/snake.h'],
['Code/A/Boa.h', 'Code/A/Garden.h', 'Code/A/Rattle.h'],
['Code/B/Boa.h', 'Code/B/Garden.h', 'Code/B/Rattle.h'],
]
end
it 'uses relative file paths to generate umbrella header' do
@installer.install!
content = @pod_target.umbrella_header_path.read
content.should =~ %r{"A/Boa.h"}
content.should =~ %r{"A/Garden.h"}
content.should =~ %r{"A/Rattle.h"}
end
it 'creates a build phase to symlink header folders on OS X' do
@pod_target.stubs(:platform).returns(Platform.osx)
@installer.install!
target = @project.native_targets.first
build_phase = target.shell_script_build_phases.find do |bp|
bp.name == 'Create Symlinks to Header Folders'
end
build_phase.should.not.be.nil
end
end
it "doesn't create a build phase to symlink header folders by default on OS X" do
@pod_target.stubs(:platform).returns(Platform.osx)
@installer.install!
target = @project.native_targets.first
target.shell_script_build_phases.should == []
end
#--------------------------------------------------------------------------------#
describe 'concerning compiler flags' do
before do
@spec = Pod::Spec.new
end
it 'flags should not be added to dtrace files' do
@installer.target.target_definitions.first.stubs(:inhibits_warnings_for_pod?).returns(true)
@installer.install!
dtrace_files = @installer.target.native_target.source_build_phase.files.select do |sf|
File.extname(sf.file_ref.path) == '.d'
end
dtrace_files.each do |dt|
dt.settings.should.be.nil
end
end
it 'adds -w per pod if target definition inhibits warnings for that pod' do
@installer.target.target_definitions.first.stubs(:inhibits_warnings_for_pod?).returns(true)
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.include?('-w')
end
it "doesn't inhibit warnings by default" do
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.not.include?('-w')
end
it 'adds -Xanalyzer -analyzer-disable-checker per pod' do
@installer.target.target_definitions.first.stubs(:inhibits_warnings_for_pod?).returns(true)
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.include?('-Xanalyzer -analyzer-disable-all-checks')
end
it "doesn't inhibit analyzer warnings by default" do
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.not.include?('-Xanalyzer -analyzer-disable-all-checks')
end
describe 'concerning ARC before and after iOS 6.0 and OS X 10.8' do
it 'does not do anything if ARC is *not* required' do
@spec.ios.deployment_target = '5'
@spec.osx.deployment_target = '10.6'
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), false)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), false)
ios_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
end
it 'does *not* disable the `OS_OBJECT_USE_OBJC` flag if ARC is required and has a deployment target of >= iOS 6.0 or OS X 10.8' do
@spec.ios.deployment_target = '6'
@spec.osx.deployment_target = '10.8'
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), false)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), false)
ios_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
end
it '*does* disable the `OS_OBJECT_USE_OBJC` flag if ARC is required but has a deployment target < iOS 6.0 or OS X 10.8' do
@spec.ios.deployment_target = '5.1'
@spec.osx.deployment_target = '10.7.2'
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), true)
ios_flags.should.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.include '-DOS_OBJECT_USE_OBJC'
end
it '*does* disable the `OS_OBJECT_USE_OBJC` flag if ARC is required and *no* deployment target is specified' do
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), true)
ios_flags.should.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.include '-DOS_OBJECT_USE_OBJC'
end
end
end
describe 'concerning resource bundles' do
before do
config.sandbox.prepare
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
@spec = fixture_spec('banana-lib/BananaLib.podspec')
@spec.resources = nil
@spec.resource_bundle = { 'banana_bundle' => ['Resources/**/*'] }
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@pod_target = fixture_pod_target(@spec)
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release }
target_installer = Installer::PodTargetInstaller.new(config.sandbox, @pod_target)
# Use a file references installer to add the files so that the correct ones are added.
file_ref_installer = Installer::FileReferencesInstaller.new(config.sandbox, [@pod_target], @project)
file_ref_installer.install!
target_installer.install!
@bundle_target = @project.targets.find { |t| t.name == 'BananaLib-banana_bundle' }
@bundle_target.should.be.not.nil
end
it 'adds variant groups directly to resources' do
# The variant group item should be present.
group_build_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path == 'Resources' && bf.file_ref.name == 'Main.storyboard'
end
group_build_file.should.be.not.nil
group_build_file.file_ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup).should.be.true
# An item within the variant group should not be present.
strings_build_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path == 'Resources/en.lproj/Main.strings'
end
strings_build_file.should.be.nil
end
it 'adds Core Data models directly to resources' do
# The model directory item should be present.
dir_build_file = @bundle_target.resources_build_phase.files.find { |bf| bf.file_ref.path == 'Resources/Sample.xcdatamodeld' }
dir_build_file.should.be.not.nil
# An item within the model directory should not be present.
version_build_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path =~ %r{Resources/Sample.xcdatamodeld/Sample.xcdatamodel}i
end
version_build_file.should.be.nil
end
it 'adds Core Data migration mapping models directly to resources' do
# The model directory item should be present.
dir_build_file = @bundle_target.resources_build_phase.files.find { |bf| bf.file_ref.path == 'Resources/Migration.xcmappingmodel' }
dir_build_file.should.be.not.nil
# An item within the model directory should not be present.
xml_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path =~ %r{Resources/Migration\.xcmappingmodel/.*}i
end
xml_file.should.be.nil
end
end
end
end
end
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Installer::TargetInstaller do
before do
@podfile = Podfile.new do
platform :ios
project 'SampleProject/SampleProject'
target 'SampleProject'
end
@target_definition = @podfile.target_definitions['SampleProject']
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
path_list = Sandbox::PathList.new(fixture('banana-lib'))
@spec = fixture_spec('banana-lib/BananaLib.podspec')
file_accessor = Sandbox::FileAccessor.new(path_list, @spec.consumer(:ios))
@project.add_pod_group('BananaLib', fixture('banana-lib'))
group = @project.group_for_spec('BananaLib')
file_accessor.source_files.each do |file|
@project.add_file_reference(file, group)
end
@pod_target = PodTarget.new([@spec], [@target_definition], config.sandbox)
@pod_target.stubs(:platform).returns(Platform.new(:ios, '6.0'))
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
@pod_target.file_accessors = [file_accessor]
@installer = Installer::TargetInstaller.new(config.sandbox, @pod_target)
end
it 'adds the architectures to the custom build configurations of the user target' do
@pod_target.archs = '$(ARCHS_STANDARD_64_BIT)'
@installer.send(:add_target)
@installer.send(:native_target).resolved_build_setting('ARCHS').should == {
'Release' => '$(ARCHS_STANDARD_64_BIT)',
'Debug' => '$(ARCHS_STANDARD_64_BIT)',
'AppStore' => '$(ARCHS_STANDARD_64_BIT)',
'Test' => '$(ARCHS_STANDARD_64_BIT)',
}
end
it 'always clears the OTHER_LDFLAGS and OTHER_LIBTOOLFLAGS, because these lib targets do not ever need any' do
@installer.send(:add_target)
@installer.send(:native_target).resolved_build_setting('OTHER_LDFLAGS').values.uniq.should == ['']
@installer.send(:native_target).resolved_build_setting('OTHER_LIBTOOLFLAGS').values.uniq.should == ['']
end
it 'adds Swift-specific build settings to the build settings' do
@pod_target.stubs(:requires_frameworks?).returns(true)
@pod_target.stubs(:uses_swift?).returns(true)
@installer.send(:add_target)
@installer.send(:native_target).resolved_build_setting('SWIFT_OPTIMIZATION_LEVEL').should == {
'Release' => nil,
'Debug' => '-Onone',
'Test' => nil,
'AppStore' => nil,
}
end
end
end
require File.expand_path('../../../../../spec_helper', __FILE__)
module Pod
class Installer
class Xcode
class PodsProjectGenerator
describe AggregateTargetInstaller do
describe 'In General' do
before do
config.sandbox.prepare
@podfile = Podfile.new do
platform :ios, '6.0'
project 'SampleProject/SampleProject'
target 'SampleProject'
end
@target_definition = @podfile.target_definitions['SampleProject']
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
path_list = Sandbox::PathList.new(fixture('banana-lib'))
@spec = fixture_spec('banana-lib/BananaLib.podspec')
file_accessor = Sandbox::FileAccessor.new(path_list, @spec.consumer(:ios))
@project.add_pod_group('BananaLib', fixture('banana-lib'))
group = @project.group_for_spec('BananaLib')
file_accessor.source_files.each do |file|
@project.add_file_reference(file, group)
end
@target = AggregateTarget.new(@target_definition, config.sandbox)
@target.client_root = config.sandbox.root.dirname
@target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
@pod_target = PodTarget.new([@spec], [@target_definition], config.sandbox)
@pod_target.user_build_configurations = @target.user_build_configurations
@pod_target.file_accessors = [file_accessor]
@target.pod_targets = [@pod_target]
@installer = AggregateTargetInstaller.new(config.sandbox, @target)
@spec.prefix_header_contents = '#import "BlocksKit.h"'
end
it 'adds file references for the support files of the target' do
@installer.install!
group = @project.support_files_group['Pods-SampleProject']
group.children.map(&:display_name).sort.should == [
'Pods-SampleProject-acknowledgements.markdown',
'Pods-SampleProject-acknowledgements.plist',
'Pods-SampleProject-dummy.m',
'Pods-SampleProject-frameworks.sh',
'Pods-SampleProject-resources.sh',
'Pods-SampleProject.appstore.xcconfig',
'Pods-SampleProject.debug.xcconfig',
'Pods-SampleProject.release.xcconfig',
'Pods-SampleProject.test.xcconfig',
]
end
#--------------------------------------#
it 'adds the target for the static library to the project' do
@installer.install!
@project.targets.count.should == 1
@project.targets.first.name.should == @target_definition.label
end
it 'sets the platform and the deployment target for iOS targets' do
@installer.install!
target = @project.targets.first
target.platform_name.should == :ios
target.deployment_target.should == '6.0'
target.build_settings('Debug')['IPHONEOS_DEPLOYMENT_TARGET'].should == '6.0'
target.build_settings('AppStore')['IPHONEOS_DEPLOYMENT_TARGET'].should == '6.0'
end
it 'sets the platform and the deployment target for OS X targets' do
@target.stubs(:platform).returns(Platform.new(:osx, '10.8'))
@installer.install!
target = @project.targets.first
target.platform_name.should == :osx
target.deployment_target.should == '10.8'
target.build_settings('Debug')['MACOSX_DEPLOYMENT_TARGET'].should == '10.8'
target.build_settings('AppStore')['MACOSX_DEPLOYMENT_TARGET'].should == '10.8'
end
it "adds the user's build configurations to the target" do
@installer.install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w( AppStore Debug Release Test )
end
it 'it creates different hash instances for the build settings of various build configurations' do
@installer.install!
build_settings = @project.targets.first.build_configurations.map(&:build_settings)
build_settings.map(&:object_id).uniq.count.should == 4
end
it 'does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should.be.nil
end
end
it 'will be built as static library' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['MACH_O_TYPE'].should == 'staticlib'
end
end
it 'will be skipped when installing' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['SKIP_INSTALL'].should == 'YES'
end
end
it 'has a PRODUCT_BUNDLE_IDENTIFIER set' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'].should == 'org.cocoapods.${PRODUCT_NAME:rfc1034identifier}'
end
end
#--------------------------------------#
it 'creates the xcconfig file' do
@installer.install!
file = config.sandbox.root + @target.xcconfig_path('Release')
xcconfig = Xcodeproj::Config.new(file)
xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}/Pods'
end
it 'creates a bridge support file' do
Podfile.any_instance.stubs(:generate_bridge_support? => true)
Generator::BridgeSupport.any_instance.expects(:save_as).once
@installer.install!
end
it 'creates a create copy resources script' do
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-resources.sh'
script.read.should.include?('logo-sidebar.png')
end
it 'does not add framework resources to copy resources script' do
@pod_target.stubs(:requires_frameworks? => true)
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-resources.sh'
script.read.should.not.include?('logo-sidebar.png')
end
it 'adds the resources bundles to the copy resources script' do
@pod_target.file_accessors.first.stubs(:resource_bundles).returns(
'Trees' => [Pathname('palm.jpg')],
'Leafs' => [Pathname('leaf.jpg')],
)
resources_by_config = @installer.send(:resources_by_config)
resources_by_config.each_value do |resources|
resources.should.include '$PODS_CONFIGURATION_BUILD_DIR/BananaLib/Trees.bundle'
resources.should.include '$PODS_CONFIGURATION_BUILD_DIR/BananaLib/Leafs.bundle'
end
end
it 'adds the bridge support file to the copy resources script, if one was created' do
@installer.stubs(:bridge_support_file).returns(@installer.target.bridge_support_path)
resources_by_config = @installer.send(:resources_by_config)
resources_by_config.each_value do |resources|
resources.should.include @installer.target.bridge_support_path
end
end
it 'does add pods to the embed frameworks script' do
@pod_target.stubs(:requires_frameworks? => true)
@target.stubs(:requires_frameworks? => true)
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-frameworks.sh'
script.read.should.include?('BananaLib.framework')
end
it 'uniques resources by config' do
a_path = Pathname.new(@project.path.dirname + '/duplicated/path.jpg')
duplicated_paths = [a_path, a_path]
@installer.target.pod_targets.each do |pod_target|
pod_target.file_accessors.each do |accessor|
accessor.stubs(:resources => duplicated_paths)
end
end
resources_by_config = @installer.send(:resources_by_config)
resources_by_config.each_value do |resources|
resources.length.should == 1
resources[0].basename.should == a_path.basename
end
end
it 'does not add pods to the embed frameworks script if they are not to be built' do
@pod_target.stubs(:should_build? => false)
@pod_target.stubs(:requires_frameworks? => true)
@target.stubs(:requires_frameworks? => true)
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
script = support_files_dir + 'Pods-SampleProject-frameworks.sh'
script.read.should.not.include?('BananaLib.framework')
end
it 'creates the acknowledgements files ' do
@installer.install!
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
markdown = support_files_dir + 'Pods-SampleProject-acknowledgements.markdown'
markdown.read.should.include?('Permission is hereby granted')
plist = support_files_dir + 'Pods-SampleProject-acknowledgements.plist'
plist.read.should.include?('Permission is hereby granted')
end
it 'creates a dummy source to ensure the creation of a single base library' do
@installer.install!
build_files = @installer.target.native_target.source_build_phase.files
build_file = build_files.find { |bf| bf.file_ref.path.include?('Pods-SampleProject-dummy.m') }
build_file.should.be.not.nil
build_file.file_ref.path.should == 'Pods-SampleProject-dummy.m'
support_files_dir = config.sandbox.target_support_files_dir('Pods-SampleProject')
dummy = support_files_dir + 'Pods-SampleProject-dummy.m'
dummy.read.should.include?('@interface PodsDummy_Pods')
end
end
end
end
end
end
end
require File.expand_path('../../../../../spec_helper', __FILE__)
module Pod
class Installer
class Xcode
class PodsProjectGenerator
describe FileReferencesInstaller do
before do
@pod_target = fixture_pod_target('banana-lib/BananaLib.podspec')
@file_accessor = @pod_target.file_accessors.first
@project = Project.new(config.sandbox.project_path)
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@installer = FileReferencesInstaller.new(config.sandbox, [@pod_target], @project)
end
#-------------------------------------------------------------------------#
describe 'Installation With Flat Resources Glob' do
it 'adds the files references of the source files the Pods project' do
@file_accessor.path_list.read_file_system
@file_accessor.path_list.expects(:read_file_system)
@installer.install!
end
it 'adds the files references of the source files the Pods project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Banana.m']
file_ref.should.be.not.nil
file_ref.path.should == 'Classes/Banana.m'
end
it 'adds the file references of the frameworks of the project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Frameworks/Bananalib.framework']
file_ref.should.be.not.nil
file_ref.path.should == 'Bananalib.framework'
end
it 'adds the file references of the libraries of the project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Frameworks/libBananalib.a']
file_ref.should.be.not.nil
file_ref.path.should == 'libBananalib.a'
end
it 'adds files references for the resources of the Pods project' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/logo-sidebar.png']
file_ref.should.be.not.nil
file_ref.path.should == 'Resources/logo-sidebar.png'
end
it "adds file references for localization directories if glob doesn't include contained files" do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/en.lproj']
file_ref.should.be.not.nil
file_ref.path.should == 'Resources/en.lproj'
end
it 'adds file references for files within CoreData directories' do
@installer.install!
model_ref = @installer.pods_project['Pods/BananaLib/Resources/Sample.xcdatamodeld']
model_ref.should.be.not.nil
model_ref.path.should == 'Resources/Sample.xcdatamodeld'
# Files within the .xcdatamodeld directory are added automatically by adding the .xcdatamodeld directory.
file_ref = @installer.pods_project['Pods/BananaLib/Resources/Sample.xcdatamodeld/Sample.xcdatamodel']
file_ref.should.be.not.nil
file_ref.path.should == 'Sample.xcdatamodel'
file_ref.source_tree.should == '<group>'
end
it 'links the headers required for building the pod target' do
@installer.install!
headers_root = @pod_target.build_headers.root
public_headers = [headers_root + 'BananaLib/Banana.h', headers_root + 'BananaLib/MoreBanana.h']
private_header = headers_root + 'BananaLib/BananaPrivate.h'
framework_header = headers_root + 'BananaLib/Bananalib/Bananalib.h'
public_headers.each { |public_header| public_header.should.exist }
private_header.should.exist
framework_header.should.not.exist
end
it 'links the public headers meant for the user' do
@installer.install!
headers_root = config.sandbox.public_headers.root
public_headers = [headers_root + 'BananaLib/Banana.h', headers_root + 'BananaLib/MoreBanana.h']
private_header = headers_root + 'BananaLib/BananaPrivate.h'
framework_header = headers_root + 'BananaLib/Bananalib/Bananalib.h'
framework_subdir_header = headers_root + 'BananaLib/Bananalib/SubDir/SubBananalib.h'
public_headers.each { |public_header| public_header.should.exist }
private_header.should.not.exist
framework_header.should.exist
framework_subdir_header.should.exist
end
it 'links the public headers meant for the user, but only for Pods that are not built' do
Target.any_instance.stubs(:requires_frameworks?).returns(true)
pod_target_one = fixture_pod_target('banana-lib/BananaLib.podspec')
pod_target_two = fixture_pod_target('monkey/monkey.podspec')
project = Project.new(config.sandbox.project_path)
project.add_pod_group('BananaLib', fixture('banana-lib'))
project.add_pod_group('monkey', fixture('monkey'))
installer = FileReferencesInstaller.new(config.sandbox, [pod_target_one, pod_target_two], project)
installer.install!
headers_root = config.sandbox.public_headers.root
banana_headers = [headers_root + 'BananaLib/Banana.h', headers_root + 'BananaLib/MoreBanana.h']
banana_headers.each { |banana_header| banana_header.should.not.exist }
monkey_header = headers_root + 'monkey/monkey.h'
monkey_header.should.exist
end
it "doesn't link public headers from vendored framework, when frameworks required" do
Target.any_instance.stubs(:requires_frameworks?).returns(true)
@installer.install!
headers_root = config.sandbox.public_headers.root
framework_header = headers_root + 'BananaLib/Bananalib/Bananalib.h'
framework_header.should.not.exist
end
end
#-------------------------------------------------------------------------#
describe 'Installation With Recursive Resources Glob' do
before do
spec = fixture_spec('banana-lib/BananaLib.podspec')
spec.resources = 'Resources/**/*'
@pod_target = fixture_pod_target(spec)
@file_accessor = @pod_target.file_accessors.first
@project = Project.new(config.sandbox.project_path)
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@installer = FileReferencesInstaller.new(config.sandbox, [@pod_target], @project)
end
it "doesn't add file references for localization directories themselves" \
'if glob includes contained files' do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/en.lproj']
file_ref.should.be.nil
end
it 'creates file system reference variant groups for nested localized resource directories' do
@installer.install!
ref = @installer.pods_project['Pods/BananaLib/Resources/nested']
ref.should.be.not.nil
ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup).should.be.true
end
it "doesn't add file references for nested localized resources" do
@installer.install!
file_ref = @installer.pods_project['Pods/BananaLib/Resources/en.lproj/nested/logo-nested.png']
file_ref.should.be.nil
end
it "doesn't add file references for files within Asset Catalogs" do
@installer.install!
resources_group_ref = @installer.pods_project['Pods/BananaLib/Resources']
catalog_path = 'Resources/Images.xcassets'
# The asset catalog should be a "PBXFileReference" and therefore doesn't have children.
resources_group_ref.files.any? { |ref| ref.path == catalog_path }.should.be.true
# The asset catalog should not also be a "PBXGroup".
resources_group_ref.groups.any? { |ref| ref.path == catalog_path }.should.be.false
# None of the children of the catalog directory should be present directly.
resources_group_ref.files.any? { |ref| ref.path.start_with?(catalog_path + '/') }.should.be.false
end
it "doesn't add file references for files within CoreData migration mappings" do
@installer.install!
resources_group_ref = @installer.pods_project['Pods/BananaLib/Resources']
mapping_path = 'Resources/Migration.xcmappingmodel'
# The mapping model should be a "PBXFileReference" and therefore doesn't have children.
resources_group_ref.files.any? { |ref| ref.path == mapping_path }.should.be.true
# The mapping model should not also be a "PBXGroup".
resources_group_ref.groups.any? { |ref| ref.path == mapping_path }.should.be.false
# None of the children of the mapping model directory should be present directly.
resources_group_ref.files.any? { |ref| ref.path.start_with?(mapping_path + '/') }.should.be.false
end
end
#-------------------------------------------------------------------------#
describe 'Private Helpers' do
describe '#file_accessors' do
it 'returns the file accessors' do
pod_target_1 = PodTarget.new([stub('Spec')], [fixture_target_definition], config.sandbox)
pod_target_1.file_accessors = [fixture_file_accessor('banana-lib/BananaLib.podspec')]
pod_target_2 = PodTarget.new([stub('Spec')], [fixture_target_definition], config.sandbox)
pod_target_2.file_accessors = [fixture_file_accessor('banana-lib/BananaLib.podspec')]
installer = FileReferencesInstaller.new(config.sandbox, [pod_target_1, pod_target_2], @project)
roots = installer.send(:file_accessors).map { |fa| fa.path_list.root }
roots.should == [fixture('banana-lib'), fixture('banana-lib')]
end
it 'handles pods without file accessors' do
pod_target_1 = PodTarget.new([stub('Spec')], [fixture_target_definition], config.sandbox)
pod_target_1.file_accessors = []
installer = FileReferencesInstaller.new(config.sandbox, [pod_target_1], @project)
installer.send(:file_accessors).should == []
end
end
describe '#header_mappings' do
it 'returns the header mappings' do
headers_sandbox = Pathname.new('BananaLib')
headers = [Pathname.new('BananaLib/Banana.h')]
mappings = @installer.send(:header_mappings, headers_sandbox, @file_accessor, headers)
mappings.should == {
headers_sandbox => headers,
}
end
it 'takes into account the header dir specified in the spec' do
headers_sandbox = Pathname.new('BananaLib')
headers = [Pathname.new('BananaLib/Banana.h')]
@file_accessor.spec_consumer.stubs(:header_dir).returns('Sub_dir')
mappings = @installer.send(:header_mappings, headers_sandbox, @file_accessor, headers)
mappings.should == {
(headers_sandbox + 'Sub_dir') => headers,
}
end
it 'takes into account the header mappings dir specified in the spec' do
headers_sandbox = Pathname.new('BananaLib')
header_1 = @file_accessor.root + 'BananaLib/sub_dir/dir_1/banana_1.h'
header_2 = @file_accessor.root + 'BananaLib/sub_dir/dir_2/banana_2.h'
headers = [header_1, header_2]
@file_accessor.spec_consumer.stubs(:header_mappings_dir).returns('BananaLib/sub_dir')
mappings = @installer.send(:header_mappings, headers_sandbox, @file_accessor, headers)
mappings.should == {
(headers_sandbox + 'dir_1') => [header_1],
(headers_sandbox + 'dir_2') => [header_2],
}
end
end
describe '#vendored_frameworks_header_mappings' do
it 'returns the vendored frameworks header mappings' do
headers_sandbox = Pathname.new('BananaLib')
header = @file_accessor.root + 'Bananalib.framework/Versions/A/Headers/Bananalib.h'
header_subdir = @file_accessor.root + 'Bananalib.framework/Versions/A/Headers/SubDir/SubBananalib.h'
mappings = @installer.send(:vendored_frameworks_header_mappings, headers_sandbox, @file_accessor)
mappings.should == {
(headers_sandbox + 'Bananalib') => [header],
(headers_sandbox + 'Bananalib/SubDir') => [header_subdir],
}
end
end
end
#-------------------------------------------------------------------------#
end
end
end
end
end
require File.expand_path('../../../../../spec_helper', __FILE__)
module Pod
class Installer
class Xcode
class PodsProjectGenerator
describe PodTargetInstaller do
describe 'In General' do
before do
config.sandbox.prepare
@podfile = Podfile.new do
platform :ios, '6.0'
project 'SampleProject/SampleProject'
target 'SampleProject'
end
@target_definition = @podfile.target_definitions['SampleProject']
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
path_list = Sandbox::PathList.new(fixture('banana-lib'))
@spec = fixture_spec('banana-lib/BananaLib.podspec')
file_accessor = Sandbox::FileAccessor.new(path_list, @spec.consumer(:ios))
@project.add_pod_group('BananaLib', fixture('banana-lib'))
group = @project.group_for_spec('BananaLib')
file_accessor.source_files.each do |file|
@project.add_file_reference(file, group)
end
file_accessor.resources.each do |resource|
@project.add_file_reference(resource, group)
end
@pod_target = PodTarget.new([@spec], [@target_definition], config.sandbox)
@pod_target.file_accessors = [file_accessor]
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release }
@installer = PodTargetInstaller.new(config.sandbox, @pod_target)
@spec.prefix_header_contents = '#import "BlocksKit.h"'
end
it 'uses the maximum of all spec deployment targets' do
spec_1 = Pod::Specification.new { |s| s.ios.deployment_target = '10.10' }
spec_2 = Pod::Specification.new { |s| s.ios.deployment_target = '10.9' }
spec_3 = Pod::Specification.new
@pod_target.stubs(:specs).returns([spec_1, spec_2, spec_3])
@installer.send(:deployment_target).should == '10.10'
end
it 'sets the platform and the deployment target for iOS targets' do
@installer.install!
target = @project.targets.first
target.platform_name.should == :ios
target.deployment_target.should == '4.3'
target.build_settings('Debug')['IPHONEOS_DEPLOYMENT_TARGET'].should == '4.3'
end
it 'sets the platform and the deployment target for iOS targets that require frameworks' do
@pod_target.stubs(:requires_frameworks?).returns(true)
@installer.install!
target = @project.targets.first
target.platform_name.should == :ios
target.deployment_target.should == '8.0'
target.build_settings('Debug')['IPHONEOS_DEPLOYMENT_TARGET'].should == '8.0'
end
it 'sets the platform and the deployment target for OS X targets' do
@pod_target.target_definitions.first.stubs(:platform).returns(Platform.new(:osx, '10.8'))
@installer.install!
target = @project.targets.first
target.platform_name.should == :osx
target.deployment_target.should == '10.6'
target.build_settings('Debug')['MACOSX_DEPLOYMENT_TARGET'].should == '10.6'
end
it "adds the user's build configurations to the target" do
@pod_target.user_build_configurations.merge!('AppStore' => :release, 'Test' => :debug)
@installer.install!
@project.targets.first.build_configurations.map(&:name).sort.should == %w( AppStore Debug Release Test )
end
it 'it creates different hash instances for the build settings of various build configurations' do
@installer.install!
build_settings = @project.targets.first.build_configurations.map(&:build_settings)
build_settings.map(&:object_id).uniq.count.should == 2
end
it 'does not enable the GCC_WARN_INHIBIT_ALL_WARNINGS flag by default' do
@installer.install!
@installer.target.native_target.build_configurations.each do |config|
config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'].should.be.nil
end
end
#--------------------------------------#
describe 'headers folder paths' do
it 'does not set them for framework targets' do
@pod_target.stubs(:requires_frameworks? => true)
@installer.install!
@project.targets.first.build_configurations.each do |config|
config.build_settings['PUBLIC_HEADERS_FOLDER_PATH'].should.be.nil
config.build_settings['PRIVATE_HEADERS_FOLDER_PATH'].should.be.nil
end
end
it 'empties them for non-framework targets' do
@installer.install!
@project.targets.first.build_configurations.each do |config|
config.build_settings['PUBLIC_HEADERS_FOLDER_PATH'].should.be.empty
config.build_settings['PRIVATE_HEADERS_FOLDER_PATH'].should.be.empty
end
end
end
#--------------------------------------#
it 'adds the source files of each pod to the target of the Pod library' do
@installer.install!
names = @installer.target.native_target.source_build_phase.files.map { |bf| bf.file_ref.display_name }
names.should.include('Banana.m')
end
#--------------------------------------#
it 'adds framework resources to the framework target' do
@pod_target.stubs(:requires_frameworks? => true)
@installer.install!
resources = @project.targets.first.resources_build_phase.files
resources.count.should > 0
resource = resources.find { |res| res.file_ref.path.include?('logo-sidebar.png') }
resource.should.be.not.nil
resource = resources.find { |res| res.file_ref.path.include?('en.lproj') }
resource.should.be.not.nil
end
#--------------------------------------#
describe 'with a scoped pod target' do
before do
@pod_target = @pod_target.scoped.first
@installer = PodTargetInstaller.new(config.sandbox, @pod_target)
end
it 'adds file references for the support files of the target' do
@installer.install!
group = @project['Pods/BananaLib/Support Files']
group.children.map(&:display_name).sort.should == [
'BananaLib-Pods-SampleProject-dummy.m',
'BananaLib-Pods-SampleProject-prefix.pch',
'BananaLib-Pods-SampleProject.xcconfig',
]
end
it 'adds the target for the static library to the project' do
@installer.install!
@project.targets.count.should == 1
@project.targets.first.name.should == 'BananaLib-Pods-SampleProject'
end
describe 'resource bundle targets' do
before do
@pod_target.file_accessors.first.stubs(:resource_bundles).returns('banana_bundle' => [])
@installer.install!
@bundle_target = @project.targets.find { |t| t.name == 'BananaLib-Pods-SampleProject-banana_bundle' }
end
it 'adds the resource bundle targets' do
@bundle_target.should.be.an.instance_of Xcodeproj::Project::Object::PBXNativeTarget
@bundle_target.product_reference.name.should == 'banana_bundle.bundle'
@bundle_target.product_reference.path.should == 'banana_bundle.bundle'
@bundle_target.platform_name.should == :ios
@bundle_target.deployment_target.should == '4.3'
end
it 'adds the build configurations to the resources bundle targets' do
file = config.sandbox.root + @pod_target.xcconfig_path
@bundle_target.build_configurations.each do |bc|
bc.base_configuration_reference.real_path.should == file
end
end
it 'sets the correct product name' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['PRODUCT_NAME'].should == 'banana_bundle'
end
end
it 'sets the correct Info.plist file path' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['INFOPLIST_FILE'].should == 'Target Support Files/BananaLib-Pods-SampleProject/ResourceBundle-banana_bundle-Info.plist'
end
end
it 'sets the correct build dir' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['CONFIGURATION_BUILD_DIR'].should == '$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/BananaLib-Pods-SampleProject'
end
end
it 'sets the correct targeted device family for the resource bundle targets' do
@bundle_target.build_configurations.each do |bc|
bc.build_settings['TARGETED_DEVICE_FAMILY'].should == '1,2'
end
end
end
end
#--------------------------------------#
describe 'with an unscoped pod target' do
it 'adds file references for the support files of the target' do
@installer.install!
@project.support_files_group
group = @project['Pods/BananaLib/Support Files']
group.children.map(&:display_name).sort.should == [
'BananaLib-dummy.m',
'BananaLib-prefix.pch',
'BananaLib.xcconfig',
]
end
it 'adds the target for the static library to the project' do
@installer.install!
@project.targets.count.should == 1
@project.targets.first.name.should == 'BananaLib'
end
describe 'resource bundle targets' do
before do
@pod_target.file_accessors.first.stubs(:resource_bundles).returns('banana_bundle' => [])
@installer.install!
@bundle_target = @project.targets.find { |t| t.name == 'BananaLib-banana_bundle' }
end
it 'adds the resource bundle targets' do
@bundle_target.should.be.an.instance_of Xcodeproj::Project::Object::PBXNativeTarget
@bundle_target.product_reference.name.should == 'banana_bundle.bundle'
@bundle_target.product_reference.path.should == 'banana_bundle.bundle'
end
it 'adds the build configurations to the resources bundle targets' do
file = config.sandbox.root + @pod_target.xcconfig_path
@bundle_target.build_configurations.each do |bc|
bc.base_configuration_reference.real_path.should == file
end
end
end
end
#--------------------------------------#
it 'creates the xcconfig file' do
@installer.install!
file = config.sandbox.root + @pod_target.xcconfig_path
xcconfig = Xcodeproj::Config.new(file)
xcconfig.to_hash['PODS_ROOT'].should == '${SRCROOT}'
end
it "creates a prefix header, including the contents of the specification's prefix header" do
@spec.prefix_header_contents = '#import "BlocksKit.h"'
@installer.install!
generated = @pod_target.prefix_header_path.read
expected = <<-EOS.strip_heredoc
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#endif
#import "BlocksKit.h"
#import <BananaTree/BananaTree.h>
EOS
generated.should == expected
end
it 'creates a dummy source to ensure the compilation of libraries with only categories' do
@installer.install!
dummy_source_basename = @pod_target.dummy_source_path.basename.to_s
build_files = @installer.target.native_target.source_build_phase.files
build_file = build_files.find { |bf| bf.file_ref.display_name == dummy_source_basename }
build_file.should.be.not.nil
build_file.file_ref.path.should == dummy_source_basename
@pod_target.dummy_source_path.read.should.include?('@interface PodsDummy_BananaLib')
end
#--------------------------------------------------------------------------------#
it 'does not create a target if the specification does not define source files' do
@pod_target.file_accessors.first.stubs(:source_files).returns([])
@installer.install!
@project.targets.should == []
end
#--------------------------------------------------------------------------------#
describe 'concerning header_mappings_dirs' do
before do
@project.add_pod_group('snake', fixture('snake'))
@pod_target = fixture_pod_target('snake/snake.podspec', [@target_definition])
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release }
@pod_target.stubs(:requires_frameworks? => true)
group = @project.group_for_spec('snake')
@pod_target.file_accessors.first.source_files.each do |file|
@project.add_file_reference(file, group)
end
@installer.stubs(:target).returns(@pod_target)
end
it 'creates custom copy files phases for framework pods' do
@installer.install!
target = @project.native_targets.first
target.name.should == 'snake'
header_build_phase_file_refs = target.headers_build_phase.files.
reject { |build_file| build_file.settings.nil? }.
map { |build_file| build_file.file_ref.path }
header_build_phase_file_refs.should == %w(
Code/C/Boa.h
Code/C/Garden.h
Code/C/Rattle.h
snake-umbrella.h
)
copy_files_build_phases = target.copy_files_build_phases.sort_by(&:name)
copy_files_build_phases.map(&:name).should == [
'Copy . Public Headers',
'Copy A Public Headers',
'Copy B Private Headers',
]
copy_files_build_phases.map(&:symbol_dst_subfolder_spec).should == Array.new(3, :products_directory)
copy_files_build_phases.map(&:dst_path).should == [
'$(PUBLIC_HEADERS_FOLDER_PATH)/.',
'$(PUBLIC_HEADERS_FOLDER_PATH)/A',
'$(PRIVATE_HEADERS_FOLDER_PATH)/B',
]
copy_files_build_phases.map { |phase| phase.files_references.map(&:path) }.should == [
['Code/snake.h'],
['Code/A/Boa.h', 'Code/A/Garden.h', 'Code/A/Rattle.h'],
['Code/B/Boa.h', 'Code/B/Garden.h', 'Code/B/Rattle.h'],
]
end
it 'uses relative file paths to generate umbrella header' do
@installer.install!
content = @pod_target.umbrella_header_path.read
content.should =~ %r{"A/Boa.h"}
content.should =~ %r{"A/Garden.h"}
content.should =~ %r{"A/Rattle.h"}
end
it 'creates a build phase to symlink header folders on OS X' do
@pod_target.stubs(:platform).returns(Platform.osx)
@installer.install!
target = @project.native_targets.first
build_phase = target.shell_script_build_phases.find do |bp|
bp.name == 'Create Symlinks to Header Folders'
end
build_phase.should.not.be.nil
end
end
it "doesn't create a build phase to symlink header folders by default on OS X" do
@pod_target.stubs(:platform).returns(Platform.osx)
@installer.install!
target = @project.native_targets.first
target.shell_script_build_phases.should == []
end
#--------------------------------------------------------------------------------#
describe 'concerning compiler flags' do
before do
@spec = Pod::Spec.new
end
it 'flags should not be added to dtrace files' do
@installer.target.target_definitions.first.stubs(:inhibits_warnings_for_pod?).returns(true)
@installer.install!
dtrace_files = @installer.target.native_target.source_build_phase.files.select do |sf|
File.extname(sf.file_ref.path) == '.d'
end
dtrace_files.each do |dt|
dt.settings.should.be.nil
end
end
it 'adds -w per pod if target definition inhibits warnings for that pod' do
@installer.target.target_definitions.first.stubs(:inhibits_warnings_for_pod?).returns(true)
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.include?('-w')
end
it "doesn't inhibit warnings by default" do
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.not.include?('-w')
end
it 'adds -Xanalyzer -analyzer-disable-checker per pod' do
@installer.target.target_definitions.first.stubs(:inhibits_warnings_for_pod?).returns(true)
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.include?('-Xanalyzer -analyzer-disable-all-checks')
end
it "doesn't inhibit analyzer warnings by default" do
flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
flags.should.not.include?('-Xanalyzer -analyzer-disable-all-checks')
end
describe 'concerning ARC before and after iOS 6.0 and OS X 10.8' do
it 'does not do anything if ARC is *not* required' do
@spec.ios.deployment_target = '5'
@spec.osx.deployment_target = '10.6'
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), false)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), false)
ios_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
end
it 'does *not* disable the `OS_OBJECT_USE_OBJC` flag if ARC is required and has a deployment target of >= iOS 6.0 or OS X 10.8' do
@spec.ios.deployment_target = '6'
@spec.osx.deployment_target = '10.8'
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), false)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), false)
ios_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.not.include '-DOS_OBJECT_USE_OBJC'
end
it '*does* disable the `OS_OBJECT_USE_OBJC` flag if ARC is required but has a deployment target < iOS 6.0 or OS X 10.8' do
@spec.ios.deployment_target = '5.1'
@spec.osx.deployment_target = '10.7.2'
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), true)
ios_flags.should.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.include '-DOS_OBJECT_USE_OBJC'
end
it '*does* disable the `OS_OBJECT_USE_OBJC` flag if ARC is required and *no* deployment target is specified' do
ios_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:ios), true)
osx_flags = @installer.send(:compiler_flags_for_consumer, @spec.consumer(:osx), true)
ios_flags.should.include '-DOS_OBJECT_USE_OBJC'
osx_flags.should.include '-DOS_OBJECT_USE_OBJC'
end
end
end
describe 'concerning resource bundles' do
before do
config.sandbox.prepare
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
@spec = fixture_spec('banana-lib/BananaLib.podspec')
@spec.resources = nil
@spec.resource_bundle = { 'banana_bundle' => ['Resources/**/*'] }
@project.add_pod_group('BananaLib', fixture('banana-lib'))
@pod_target = fixture_pod_target(@spec)
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release }
target_installer = PodTargetInstaller.new(config.sandbox, @pod_target)
# Use a file references installer to add the files so that the correct ones are added.
file_ref_installer = Installer::Xcode::PodsProjectGenerator::FileReferencesInstaller.new(config.sandbox, [@pod_target], @project)
file_ref_installer.install!
target_installer.install!
@bundle_target = @project.targets.find { |t| t.name == 'BananaLib-banana_bundle' }
@bundle_target.should.be.not.nil
end
it 'adds variant groups directly to resources' do
# The variant group item should be present.
group_build_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path == 'Resources' && bf.file_ref.name == 'Main.storyboard'
end
group_build_file.should.be.not.nil
group_build_file.file_ref.is_a?(Xcodeproj::Project::Object::PBXVariantGroup).should.be.true
# An item within the variant group should not be present.
strings_build_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path == 'Resources/en.lproj/Main.strings'
end
strings_build_file.should.be.nil
end
it 'adds Core Data models directly to resources' do
# The model directory item should be present.
dir_build_file = @bundle_target.resources_build_phase.files.find { |bf| bf.file_ref.path == 'Resources/Sample.xcdatamodeld' }
dir_build_file.should.be.not.nil
# An item within the model directory should not be present.
version_build_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path =~ %r{Resources/Sample.xcdatamodeld/Sample.xcdatamodel}i
end
version_build_file.should.be.nil
end
it 'adds Core Data migration mapping models directly to resources' do
# The model directory item should be present.
dir_build_file = @bundle_target.resources_build_phase.files.find { |bf| bf.file_ref.path == 'Resources/Migration.xcmappingmodel' }
dir_build_file.should.be.not.nil
# An item within the model directory should not be present.
xml_file = @bundle_target.resources_build_phase.files.find do |bf|
bf.file_ref.path =~ %r{Resources/Migration\.xcmappingmodel/.*}i
end
xml_file.should.be.nil
end
end
end
end
end
end
end
end
require File.expand_path('../../../../../spec_helper', __FILE__)
module Pod
class Installer
class Xcode
class PodsProjectGenerator
describe TargetInstaller do
before do
@podfile = Podfile.new do
platform :ios
project 'SampleProject/SampleProject'
target 'SampleProject'
end
@target_definition = @podfile.target_definitions['SampleProject']
@project = Project.new(config.sandbox.project_path)
config.sandbox.project = @project
path_list = Sandbox::PathList.new(fixture('banana-lib'))
@spec = fixture_spec('banana-lib/BananaLib.podspec')
file_accessor = Sandbox::FileAccessor.new(path_list, @spec.consumer(:ios))
@project.add_pod_group('BananaLib', fixture('banana-lib'))
group = @project.group_for_spec('BananaLib')
file_accessor.source_files.each do |file|
@project.add_file_reference(file, group)
end
@pod_target = PodTarget.new([@spec], [@target_definition], config.sandbox)
@pod_target.stubs(:platform).returns(Platform.new(:ios, '6.0'))
@pod_target.user_build_configurations = { 'Debug' => :debug, 'Release' => :release, 'AppStore' => :release, 'Test' => :debug }
@pod_target.file_accessors = [file_accessor]
@installer = TargetInstaller.new(config.sandbox, @pod_target)
end
it 'adds the architectures to the custom build configurations of the user target' do
@pod_target.archs = '$(ARCHS_STANDARD_64_BIT)'
@installer.send(:add_target)
@installer.send(:native_target).resolved_build_setting('ARCHS').should == {
'Release' => '$(ARCHS_STANDARD_64_BIT)',
'Debug' => '$(ARCHS_STANDARD_64_BIT)',
'AppStore' => '$(ARCHS_STANDARD_64_BIT)',
'Test' => '$(ARCHS_STANDARD_64_BIT)',
}
end
it 'always clears the OTHER_LDFLAGS and OTHER_LIBTOOLFLAGS, because these lib targets do not ever need any' do
@installer.send(:add_target)
@installer.send(:native_target).resolved_build_setting('OTHER_LDFLAGS').values.uniq.should == ['']
@installer.send(:native_target).resolved_build_setting('OTHER_LIBTOOLFLAGS').values.uniq.should == ['']
end
it 'adds Swift-specific build settings to the build settings' do
@pod_target.stubs(:requires_frameworks?).returns(true)
@pod_target.stubs(:uses_swift?).returns(true)
@installer.send(:add_target)
@installer.send(:native_target).resolved_build_setting('SWIFT_OPTIMIZATION_LEVEL').should == {
'Release' => nil,
'Debug' => '-Onone',
'Test' => nil,
'AppStore' => nil,
}
end
end
end
end
end
end
require File.expand_path('../../../../spec_helper', __FILE__)
module Pod
class Installer
class Xcode
describe PodsProjectGenerator 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 [Podfile]
#
def generate_podfile(pods = ['JSONKit'])
Pod::Podfile.new do
platform :ios
project SpecHelper.create_sample_app_copy_from_fixture('SampleProject'), 'Test' => :debug, 'App Store' => :release
target 'SampleProject' do
pods.each { |name| pod name }
target 'SampleProjectTests' do
inherit! :search_paths
end
end
end
end
# @return [Podfile]
#
def generate_local_podfile
Pod::Podfile.new do
platform :ios
project SpecHelper.fixture('SampleProject/SampleProject'), 'Test' => :debug, 'App Store' => :release
target 'SampleProject' do
pod 'Reachability', :path => SpecHelper.fixture('integration/Reachability')
target 'SampleProjectTests' do
inherit! :search_paths
end
end
end
end
describe 'Generating Pods Project' do
before do
podfile = generate_podfile
lockfile = generate_lockfile
@installer = Pod::Installer.new(config.sandbox, podfile, lockfile)
@installer.send(:prepare)
@installer.send(:analyze)
@generator = @installer.send(:create_generator)
end
describe 'Preparing' do
before do
@generator.send(:prepare)
end
it "creates build configurations for all of the user's targets" do
@generator.project.build_configurations.map(&:name).sort.should == ['App Store', 'Debug', 'Release', 'Test']
end
it 'sets STRIP_INSTALLED_PRODUCT to NO for all configurations for the whole project' do
@generator.project.build_settings('Debug')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
@generator.project.build_settings('Test')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
@generator.project.build_settings('Release')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
@generator.project.build_settings('App Store')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
end
it 'creates the Pods project' do
@generator.send(:prepare)
@generator.project.class.should == Pod::Project
end
it 'preserves Pod paths specified as absolute or rooted to home' do
local_podfile = generate_local_podfile
local_installer = Pod::Installer.new(config.sandbox, local_podfile)
local_installer.send(:analyze)
local_generator = local_installer.send(:create_generator)
local_generator.send(:prepare)
group = local_generator.project.group_for_spec('Reachability')
Pathname.new(group.path).should.be.absolute
end
it 'adds the Podfile to the Pods project' do
config.stubs(:podfile_path).returns(Pathname.new('/Podfile'))
@generator.send(:prepare)
@generator.project['Podfile'].should.be.not.nil
end
it 'sets the deployment target for the whole project' do
target_definition_osx = fixture_target_definition('OSX Target', Platform.new(:osx, '10.8'))
target_definition_ios = fixture_target_definition('iOS Target', Platform.new(:ios, '6.0'))
aggregate_target_osx = AggregateTarget.new(target_definition_osx, config.sandbox)
aggregate_target_ios = AggregateTarget.new(target_definition_ios, config.sandbox)
@generator.stubs(:aggregate_targets).returns([aggregate_target_osx, aggregate_target_ios])
@generator.stubs(:pod_targets).returns([])
@generator.send(:prepare)
build_settings = @generator.project.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['MACOSX_DEPLOYMENT_TARGET'].should == '10.8'
build_setting['IPHONEOS_DEPLOYMENT_TARGET'].should == '6.0'
end
end
end
#-------------------------------------#
describe '#install_file_references' do
it 'installs the file references' do
@generator.stubs(:pod_targets).returns([])
PodsProjectGenerator::FileReferencesInstaller.any_instance.expects(:install!)
@generator.send(:install_file_references)
end
end
#-------------------------------------#
describe '#install_libraries' do
it 'install the targets of the Pod project' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
target_definition = Podfile::TargetDefinition.new(:default, nil)
target_definition.abstract = false
target_definition.store_pod('BananaLib')
pod_target = PodTarget.new([spec], [target_definition], config.sandbox)
@generator.stubs(:aggregate_targets).returns([])
@generator.stubs(:pod_targets).returns([pod_target])
PodsProjectGenerator::PodTargetInstaller.any_instance.expects(:install!)
@generator.send(:install_libraries)
end
it 'does not skip empty pod targets' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
target_definition = Podfile::TargetDefinition.new(:default, nil)
target_definition.abstract = false
pod_target = PodTarget.new([spec], [target_definition], config.sandbox)
@generator.stubs(:aggregate_targets).returns([])
@generator.stubs(:pod_targets).returns([pod_target])
PodsProjectGenerator::PodTargetInstaller.any_instance.expects(:install!).once
@generator.send(:install_libraries)
end
it 'adds the frameworks required by the pod to the project for informative purposes' do
Specification::Consumer.any_instance.stubs(:frameworks).returns(['QuartzCore'])
@installer.send(:install!)
names = @installer.pods_project['Frameworks']['iOS'].children.map(&:name)
names.sort.should == ['Foundation.framework', 'QuartzCore.framework']
end
end
#-------------------------------------#
describe '#set_target_dependencies' do
def test_extension_target(symbol_type)
mock_user_target = mock('usertarget', :symbol_type => symbol_type)
@target.stubs(:user_targets).returns([mock_user_target])
build_settings = {}
mock_configuration = mock('buildconfiguration', :build_settings => build_settings)
@mock_target.stubs(:build_configurations).returns([mock_configuration])
@generator.send(:set_target_dependencies)
build_settings.should == { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
end
before do
spec = fixture_spec('banana-lib/BananaLib.podspec')
target_definition = Podfile::TargetDefinition.new(:default, @installer.podfile.root_target_definitions.first)
@pod_target = PodTarget.new([spec], [target_definition], config.sandbox)
@target = AggregateTarget.new(target_definition, config.sandbox)
@mock_target = mock('PodNativeTarget')
mock_project = mock('PodsProject', :frameworks_group => mock('FrameworksGroup'))
@generator.stubs(:project).returns(mock_project)
@target.stubs(:native_target).returns(@mock_target)
@target.stubs(:pod_targets).returns([@pod_target])
@generator.stubs(:aggregate_targets).returns([@target])
end
it 'sets resource bundles for not build pods as target dependencies of the user target' do
@pod_target.stubs(:resource_bundle_targets).returns(['dummy'])
@pod_target.stubs(:should_build? => false)
@mock_target.expects(:add_dependency).with('dummy')
@generator.send(:set_target_dependencies)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for app extension targets' do
test_extension_target(:app_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for watch extension targets' do
test_extension_target(:watch_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for watchOS 2 extension targets' do
test_extension_target(:watch2_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for tvOS extension targets' do
test_extension_target(:tv_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for targets where the user target has it set' do
mock_user_target = mock('UserTarget', :symbol_type => :application)
mock_user_target.expects(:common_resolved_build_setting).with('APPLICATION_EXTENSION_API_ONLY').returns('YES')
@target.stubs(:user_targets).returns([mock_user_target])
build_settings = {}
mock_configuration = mock('BuildConfiguration', :build_settings => build_settings)
@mock_target.stubs(:build_configurations).returns([mock_configuration])
@generator.send(:set_target_dependencies)
build_settings.should == { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
end
it 'does not try to set APPLICATION_EXTENSION_API_ONLY if there are no pod targets' do
lambda do
mock_user_target = mock('UserTarget', :symbol_type => :app_extension)
@target.stubs(:user_targets).returns([mock_user_target])
@target.stubs(:native_target).returns(nil)
@target.stubs(:pod_targets).returns([])
@generator.send(:set_target_dependencies)
end.should.not.raise NoMethodError
end
end
#--------------------------------------#
describe '#write' do
before do
@generator.stubs(:aggregate_targets).returns([])
@generator.stubs(:analysis_result).returns(stub(:all_user_build_configurations => {}, :target_inspections => nil))
@generator.send(:prepare)
end
it 'recursively sorts the project' do
Xcodeproj::Project.any_instance.stubs(:recreate_user_schemes)
@generator.project.main_group.expects(:sort)
@generator.send(:write)
end
it 'saves the project to the given path' do
Xcodeproj::Project.any_instance.stubs(:recreate_user_schemes)
temporary_directory + 'Pods/Pods.xcodeproj'
@generator.project.expects(:save)
@generator.send(:write)
end
it "uses the user project's object version for the pods project" do
tmp_directory = Pathname(Dir.tmpdir) + 'CocoaPods'
FileUtils.mkdir_p(tmp_directory)
proj = Xcodeproj::Project.new(tmp_directory + 'Yolo.xcodeproj', false, 1)
proj.save
aggregate_target = AggregateTarget.new(fixture_target_definition, config.sandbox)
aggregate_target.user_project = proj
@generator.stubs(:aggregate_targets).returns([aggregate_target])
@generator.send(:prepare)
@generator.project.object_version.should == '1'
FileUtils.rm_rf(tmp_directory)
end
describe 'sharing schemes of development pods' do
before do
spec = fixture_spec('banana-lib/BananaLib.podspec')
pod_target = fixture_pod_target(spec)
@generator.stubs(:pod_targets).returns([pod_target])
@generator.sandbox.stubs(:development_pods).returns('BananaLib' => nil)
end
it 'does not share by default' do
Xcodeproj::XCScheme.expects(:share_scheme).never
@generator.send(:share_development_pod_schemes)
end
it 'can share all schemes' do
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns(true)
Xcodeproj::XCScheme.expects(:share_scheme).with(
@generator.project.path,
'BananaLib')
@generator.send(:share_development_pod_schemes)
end
it 'allows opting out' do
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns(false)
Xcodeproj::XCScheme.expects(:share_scheme).never
@generator.send(:share_development_pod_schemes)
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns(nil)
Xcodeproj::XCScheme.expects(:share_scheme).never
@generator.send(:share_development_pod_schemes)
end
it 'allows specifying strings of pods to share' do
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns(%w(BananaLib))
Xcodeproj::XCScheme.expects(:share_scheme).with(
@generator.project.path,
'BananaLib')
@generator.send(:share_development_pod_schemes)
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns(%w(orange-framework))
Xcodeproj::XCScheme.expects(:share_scheme).never
@generator.send(:share_development_pod_schemes)
end
it 'allows specifying regular expressions of pods to share' do
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns([/bAnaNalIb/i, /B*/])
Xcodeproj::XCScheme.expects(:share_scheme).with(
@generator.project.path,
'BananaLib')
@generator.send(:share_development_pod_schemes)
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns([/banana$/, /[^\A]BananaLib/])
Xcodeproj::XCScheme.expects(:share_scheme).never
@generator.send(:share_development_pod_schemes)
end
it 'raises when an invalid type is set' do
@generator.installation_options.
stubs(:share_schemes_for_development_pods).
returns(Pathname('foo'))
Xcodeproj::XCScheme.expects(:share_scheme).never
e = should.raise(Informative) { @generator.send(:share_development_pod_schemes) }
e.message.should.match /share_schemes_for_development_pods.*set it to true, false, or an array of pods to share schemes for/
end
end
end
end
end
end
end
end
...@@ -69,6 +69,9 @@ module Pod ...@@ -69,6 +69,9 @@ module Pod
@installer.stubs(:run_plugins_post_install_hooks) @installer.stubs(:run_plugins_post_install_hooks)
@installer.stubs(:ensure_plugins_are_installed!) @installer.stubs(:ensure_plugins_are_installed!)
@installer.stubs(:perform_post_install_actions) @installer.stubs(:perform_post_install_actions)
Installer::Xcode::PodsProjectGenerator.any_instance.stubs(:share_development_pod_schemes)
Installer::Xcode::PodsProjectGenerator.any_instance.stubs(:generate!)
Installer::Xcode::PodsProjectGenerator.any_instance.stubs(:write)
end end
it 'in runs the pre-install hooks before cleaning the Pod sources' do it 'in runs the pre-install hooks before cleaning the Pod sources' do
...@@ -86,21 +89,19 @@ module Pod ...@@ -86,21 +89,19 @@ module Pod
end end
it 'in runs the post-install hooks before serializing the Pods project' do it 'in runs the post-install hooks before serializing the Pods project' do
@installer.stubs(:prepare_pods_project)
@installer.stubs(:run_podfile_pre_install_hooks) @installer.stubs(:run_podfile_pre_install_hooks)
@installer.stubs(:install_file_references)
@installer.stubs(:install_libraries)
@installer.stubs(:set_target_dependencies)
@installer.stubs(:write_lockfiles) @installer.stubs(:write_lockfiles)
@installer.stubs(:aggregate_targets).returns([]) @installer.stubs(:aggregate_targets).returns([])
@installer.unstub(:generate_pods_project) @installer.unstub(:generate_pods_project)
def @installer.run_podfile_post_install_hooks generator = @installer.send(:create_generator)
@hook_called = true @installer.stubs(:create_generator).returns(generator)
end generator.stubs(:generate!)
generator.stubs(:share_development_pod_schemes)
hooks = sequence('hooks')
@installer.expects(:run_podfile_post_install_hooks).once.in_sequence(hooks)
generator.expects(:write).once.in_sequence(hooks)
def @installer.write_pod_project
@hook_called.should.be.true
end
@installer.install! @installer.install!
end end
...@@ -537,325 +538,6 @@ module Pod ...@@ -537,325 +538,6 @@ module Pod
#-------------------------------------------------------------------------# #-------------------------------------------------------------------------#
describe 'Generating pods project' do describe 'Generating pods project' do
describe '#prepare_pods_project' do
before do
@installer.stubs(:aggregate_targets).returns([])
end
it "creates build configurations for all of the user's targets" do
@installer.installation_options.integrate_targets = true
@installer.send(:analyze)
@installer.send(:prepare_pods_project)
@installer.pods_project.build_configurations.map(&:name).sort.should == ['App Store', 'Debug', 'Release', 'Test']
end
it 'sets STRIP_INSTALLED_PRODUCT to NO for all configurations for the whole project' do
@installer.installation_options.integrate_targets = true
@installer.send(:analyze)
@installer.send(:prepare_pods_project)
@installer.pods_project.build_settings('Debug')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
@installer.pods_project.build_settings('Test')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
@installer.pods_project.build_settings('Release')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
@installer.pods_project.build_settings('App Store')['STRIP_INSTALLED_PRODUCT'].should == 'NO'
end
before do
@installer.stubs(:analysis_result).returns(stub(:all_user_build_configurations => {}, :target_inspections => nil))
end
it 'creates the Pods project' do
@installer.send(:prepare_pods_project)
@installer.pods_project.class.should == Pod::Project
end
it 'preserves Pod paths specified as absolute or rooted to home' do
local_podfile = generate_local_podfile
local_installer = Installer.new(config.sandbox, local_podfile)
local_installer.send(:analyze)
local_installer.send(:prepare_pods_project)
group = local_installer.pods_project.group_for_spec('Reachability')
Pathname.new(group.path).should.be.absolute
end
it 'adds the Podfile to the Pods project' do
config.stubs(:podfile_path).returns(Pathname.new('/Podfile'))
@installer.send(:prepare_pods_project)
@installer.pods_project['Podfile'].should.be.not.nil
end
it 'sets the deployment target for the whole project' do
target_definition_osx = fixture_target_definition('OSX Target', Platform.new(:osx, '10.8'))
target_definition_ios = fixture_target_definition('iOS Target', Platform.new(:ios, '6.0'))
aggregate_target_osx = AggregateTarget.new(target_definition_osx, config.sandbox)
aggregate_target_ios = AggregateTarget.new(target_definition_ios, config.sandbox)
@installer.stubs(:aggregate_targets).returns([aggregate_target_osx, aggregate_target_ios])
@installer.stubs(:pod_targets).returns([])
@installer.send(:prepare_pods_project)
build_settings = @installer.pods_project.build_configurations.map(&:build_settings)
build_settings.each do |build_setting|
build_setting['MACOSX_DEPLOYMENT_TARGET'].should == '10.8'
build_setting['IPHONEOS_DEPLOYMENT_TARGET'].should == '6.0'
end
end
end
#--------------------------------------#
describe '#install_file_references' do
it 'installs the file references' do
@installer.stubs(:pod_targets).returns([])
Installer::FileReferencesInstaller.any_instance.expects(:install!)
@installer.send(:install_file_references)
end
end
#--------------------------------------#
describe '#install_libraries' do
it 'install the targets of the Pod project' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
target_definition = Podfile::TargetDefinition.new(:default, nil)
target_definition.abstract = false
target_definition.store_pod('BananaLib')
pod_target = PodTarget.new([spec], [target_definition], config.sandbox)
@installer.stubs(:aggregate_targets).returns([])
@installer.stubs(:pod_targets).returns([pod_target])
Installer::PodTargetInstaller.any_instance.expects(:install!)
@installer.send(:install_libraries)
end
it 'does not skip empty pod targets' do
spec = fixture_spec('banana-lib/BananaLib.podspec')
target_definition = Podfile::TargetDefinition.new(:default, nil)
target_definition.abstract = false
pod_target = PodTarget.new([spec], [target_definition], config.sandbox)
@installer.stubs(:aggregate_targets).returns([])
@installer.stubs(:pod_targets).returns([pod_target])
Installer::PodTargetInstaller.any_instance.expects(:install!).once
@installer.send(:install_libraries)
end
it 'adds the frameworks required by to the pod to the project for informative purposes' do
Specification::Consumer.any_instance.stubs(:frameworks).returns(['QuartzCore'])
@installer.install!
names = @installer.sandbox.project['Frameworks']['iOS'].children.map(&:name)
names.sort.should == ['Foundation.framework', 'QuartzCore.framework']
end
end
#--------------------------------------#
describe '#set_target_dependencies' do
def test_extension_target(symbol_type)
mock_user_target = mock('UserTarget', :symbol_type => symbol_type)
@target.stubs(:user_targets).returns([mock_user_target])
build_settings = {}
mock_configuration = mock('BuildConfiguration', :build_settings => build_settings)
@mock_target.stubs(:build_configurations).returns([mock_configuration])
@installer.send(:set_target_dependencies)
build_settings.should == { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
end
before do
spec = fixture_spec('banana-lib/BananaLib.podspec')
target_definition = Podfile::TargetDefinition.new(:default, @installer.podfile.root_target_definitions.first)
@pod_target = PodTarget.new([spec], [target_definition], config.sandbox)
@target = AggregateTarget.new(target_definition, config.sandbox)
@mock_target = mock('PodNativeTarget')
mock_project = mock('PodsProject', :frameworks_group => mock('FrameworksGroup'))
@installer.stubs(:pods_project).returns(mock_project)
@target.stubs(:native_target).returns(@mock_target)
@target.stubs(:pod_targets).returns([@pod_target])
@installer.stubs(:aggregate_targets).returns([@target])
end
it 'sets resource bundles for not build pods as target dependencies of the user target' do
@pod_target.stubs(:resource_bundle_targets).returns(['dummy'])
@pod_target.stubs(:should_build? => false)
@mock_target.expects(:add_dependency).with('dummy')
@installer.send(:set_target_dependencies)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for app extension targets' do
test_extension_target(:app_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for watch extension targets' do
test_extension_target(:watch_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for watchOS 2 extension targets' do
test_extension_target(:watch2_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for tvOS extension targets' do
test_extension_target(:tv_extension)
end
it 'configures APPLICATION_EXTENSION_API_ONLY for targets where the user target has it set' do
mock_user_target = mock('UserTarget', :symbol_type => :application)
mock_user_target.expects(:common_resolved_build_setting).with('APPLICATION_EXTENSION_API_ONLY').returns('YES')
@target.stubs(:user_targets).returns([mock_user_target])
build_settings = {}
mock_configuration = mock('BuildConfiguration', :build_settings => build_settings)
@mock_target.stubs(:build_configurations).returns([mock_configuration])
@installer.send(:set_target_dependencies)
build_settings.should == { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
end
it 'does not try to set APPLICATION_EXTENSION_API_ONLY if there are no pod targets' do
lambda do
mock_user_target = mock('UserTarget', :symbol_type => :app_extension)
@target.stubs(:user_targets).returns([mock_user_target])
@target.stubs(:native_target).returns(nil)
@target.stubs(:pod_targets).returns([])
@installer.send(:set_target_dependencies)
end.should.not.raise NoMethodError
end
end
#--------------------------------------#
describe '#write_pod_project' do
before do
@installer.stubs(:aggregate_targets).returns([])
@installer.stubs(:analysis_result).returns(stub(:all_user_build_configurations => {}, :target_inspections => nil))
@installer.send(:prepare_pods_project)
end
it 'recursively sorts the project' do
Xcodeproj::Project.any_instance.stubs(:recreate_user_schemes)
@installer.pods_project.main_group.expects(:sort)
@installer.send(:write_pod_project)
end
it 'saves the project to the given path' do
Xcodeproj::Project.any_instance.stubs(:recreate_user_schemes)
temporary_directory + 'Pods/Pods.xcodeproj'
@installer.pods_project.expects(:save)
@installer.send(:write_pod_project)
end
describe 'sharing schemes of development pods' do
before do
spec = fixture_spec('banana-lib/BananaLib.podspec')
pod_target = fixture_pod_target(spec)
@installer.stubs(:pod_targets).returns([pod_target])
@installer.sandbox.stubs(:development_pods).returns('BananaLib' => nil)
end
it 'does not share by default' do
Xcodeproj::XCScheme.expects(:share_scheme).never
@installer.send(:share_development_pod_schemes)
end
it 'can share all schemes' do
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns(true)
Xcodeproj::XCScheme.expects(:share_scheme).with(
@installer.pods_project.path,
'BananaLib')
@installer.send(:share_development_pod_schemes)
end
it 'allows opting out' do
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns(false)
Xcodeproj::XCScheme.expects(:share_scheme).never
@installer.send(:share_development_pod_schemes)
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns(nil)
Xcodeproj::XCScheme.expects(:share_scheme).never
@installer.send(:share_development_pod_schemes)
end
it 'allows specifying strings of pods to share' do
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns(%w(BananaLib))
Xcodeproj::XCScheme.expects(:share_scheme).with(
@installer.pods_project.path,
'BananaLib')
@installer.send(:share_development_pod_schemes)
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns(%w(orange-framework))
Xcodeproj::XCScheme.expects(:share_scheme).never
@installer.send(:share_development_pod_schemes)
end
it 'allows specifying regular expressions of pods to share' do
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns([/bAnaNalIb/i, /B*/])
Xcodeproj::XCScheme.expects(:share_scheme).with(
@installer.pods_project.path,
'BananaLib')
@installer.send(:share_development_pod_schemes)
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns([/banana$/, /[^\A]BananaLib/])
Xcodeproj::XCScheme.expects(:share_scheme).never
@installer.send(:share_development_pod_schemes)
end
it 'raises when an invalid type is set' do
@installer.installation_options.
stubs(:share_schemes_for_development_pods).
returns(Pathname('foo'))
Xcodeproj::XCScheme.expects(:share_scheme).never
e = should.raise(Informative) { @installer.send(:share_development_pod_schemes) }
e.message.should.match /share_schemes_for_development_pods.*set it to true, false, or an array of pods to share schemes for/
end
end
it "uses the user project's object version for the pods project" do
tmp_directory = Pathname(Dir.tmpdir) + 'CocoaPods'
FileUtils.mkdir_p(tmp_directory)
proj = Xcodeproj::Project.new(tmp_directory + 'Yolo.xcodeproj', false, 1)
proj.save
aggregate_target = AggregateTarget.new(fixture_target_definition, config.sandbox)
aggregate_target.user_project = proj
@installer.stubs(:aggregate_targets).returns([aggregate_target])
@installer.send(:prepare_pods_project)
@installer.pods_project.object_version.should == '1'
FileUtils.rm_rf(tmp_directory)
end
end
#--------------------------------------#
describe '#write_lockfiles' do describe '#write_lockfiles' do
before do before do
@analysis_result = Installer::Analyzer::AnalysisResult.new @analysis_result = Installer::Analyzer::AnalysisResult.new
......
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