目录
-
xcconfig文件
-
CocoaPod的实现
-
pod install的过程
-
xcconfig文件
作用:工程的配置文件。
可以通过在文件中的代码指定工程中依赖的库(OTHER_LDFLAGS),header_search_path(HEADER_SEARCH_PATHS)。在cocoapods里面还有其他的功能
参考:https://www.objc.io/issues/6-build-tools/cocoapods-under-the-hood/
CocoaPod的实现
源码位置:/Library/Ruby/Gems/#gem版本号#/gems/cocoapods-#版本号#
pod install的过程
首先在CocoaPods中,所有的命令都会由Command类派发到将对应的类,而真正执行pod install的类就是install:
module Pod
class Command
class Install < Command
def run
verify_podfile_exists!
installer = installer_for_config
installer.repo_update = repo_update?(:default => false)
installer.update = false
installer.install!
end
end
end
end
这里面会从配置类的事例config中获取一个Installer的事例,然后执行install!方法,这里的installer有一个update属性,而这也就是pod install 和 update之间最大的区别,其中后者会无视已有的Podfile.lock文件,重新对依赖进行分析:
module Pod
class Command
class Install < Command
def run
verify_podfile_exists!
installer = installer_for_config
installer.repo_update = repo_update?(:default => true)
installer.update = true
installer.install!
end
end
end
end
Podfile的解析:
通过ruby的eval将读取到的文件字符串转换成ruby代码,然后有CocoaPods-Core这个模块来完成。而这个过程早在installer_for_config中就已经开始了:
def installer_for_config
Installer.new(config.sandbox, config.podfile, config.lockfile)
end
这个方法会从config.podfile中取出一个podfile类的实例:
def podfile
@podfile ||= Podfile.from_file(podfile_path) if podfile_path
end
类方法Podfile.from_file就定义在CocoaPods-Core这个库中,用于分析Podfile中定义的依赖,这个方法会根据Podfile不同的类型选择不同的调用路径:
Podfile.from_file
`-- Podfile.from_ruby
|-- File.open
`-- eval
from_ruby类方法就是将podfile文件读取到数据中,然后使用eval直接将文件中的内容当做Ruby代码来执行。
def self.from_ruby(path, contents = nil)
contents ||= File.open(path, 'r:utf-8', &:read)
podfile = Podfile.new(path) do
begin
eval(contents, nil, path.to_s)
rescue Exception => e
message = "Invalid `#{path.basename}` file: #{e.message}"
raise DSLError.new(message, path, e, contents)
end
end
podfile
end
在Podfile这个类的顶部,我们使用Ruby的Mixin的语法来混入Podfile中代码执行所需要的上下文:
include Pod::Podfile::DSL
Podfile中的所有你见到的方法都是定义在DSL这个模块下面的:
module Pod
class Podfile
module DSL
def pod(name = nil, *requirements) end
def target(name, options = nil) end
def platform(name, target = nil) end
def inhibit_all_warnings! end
def use_frameworks!(flag = true) end
def source(source) end
...
end
end
end
这里定义了很多Podfile中使用的方法,当使用eval执行文件中的代码时,就会执行这个模块里的方法,在这里简单看一下其中几个方法的实现,比如说source方法:
def source(source)
hash_sources = get_hash_value('sources') || []
hash_sources << source
set_hash_value('sources', hash_sources.uniq)
end
该方法会将新的source加入已有的源数组中,然后更新原有的source对应的值。
稍微复杂一些的是target方法:
def target(name, options = nil)
if options
raise Informative, "Unsupported options `#{options}` for " \
"target `#{name}`."
end
parent = current_target_definition
definition = TargetDefinition.new(name, parent)
self.current_target_definition = definition
yield if block_given?
ensure
self.current_target_definition = parent
end
这个方法会创建一个 TargetDefinition 类的实例,然后将当前环境系的 target_definition 设置成这个刚刚创建的实例。这样,之后使用 pod 定义的依赖都会填充到当前的 TargetDefinition 中:
def pod(name = nil, *requirements)
unless name
raise StandardError, 'A dependency requires a name.'
end
current_target_definition.store_pod(name, *requirements)
end
当pod方法被调用时,会执行store_pod将依赖存储到当前target中的dependencies数组中:
def store_pod(name, *requirements)
return if parse_subspecs(name, requirements)
parse_inhibit_warnings(name, requirements)
parse_configuration_whitelist(name, requirements)
if requirements && !requirements.empty?
pod = { name => requirements }
else
pod = name
end
get_hash_value('dependencies', []) << pod
nil
end
上面的流程就完成了对podfile的解析。
安装依赖的过程
Podfile被解析后的内容会被转化成一个Podfile类的实例,而Installer的实例方法install!就会使用这些安装当前工程的依赖,而整个安装依赖的过程大约有四个部分:
- 解析Podfile中的依赖
- 下载依赖
- 创建Pods.xcodeproj工程
- 继承workspace
解析Podfile中的依赖
def install!
resolve_dependencies
download_dependencies
generate_pods_project
integrate_user_project
end
在上面的install方法调用的resolve_dependencies会创建一个Analyzer类的实例,在这个方法中,你会看到一些非常熟悉的字符串:
def resolve_dependencies
analyzer = create_analyzer
plugin_sources = run_source_provider_hooks
analyzer.sources.insert(0, *plugin_sources)
UI.section 'Updating local specs repositories' do
analyzer.update_repositories
end if repo_update?
UI.section 'Analyzing dependencies' do
analyze(analyzer)
validate_build_configurations
clean_sandbox
end
end
在使用CocoaPods中经常出现的Updating local specs repositories以Analyzing dependencies 就是从这里输出到终端的。
下载依赖
在依赖关系解决返回了一系列Specification对象之后,就到了Pod install 的第二部分,下载依赖:
def install_pod_sources
@installed_specs = []
pods_to_install = sandbox_state.added | sandbox_state.changed
title_options = { :verbose_prefix => '-> '.green }
root_specs.sort_by(&:name).each do |spec|
if pods_to_install.include?(spec.name)
if sandbox_state.changed.include?(spec.name) && sandbox.manifest
previous = sandbox.manifest.version(spec.name)
title = "Installing #{spec.name} #{spec.version} (was #{previous})"
else
title = "Installing #{spec}"
end
UI.titled_section(title.green, title_options) do
install_source_of_pod(spec.name)
end
else
UI.titled_section("Using #{spec}", title_options) do
create_pod_installer(spec.name)
end
end
end
end
在这个方法中你会看到更多熟悉的提示,CocoaPods会使用沙盒(sandbox)存储已有依赖的数据,在更新现有的依赖时,会根据依赖的的不同状态显示不同的提示信息:
-> Using AFNetworking (3.1.0)
-> Using AKPickerView (0.2.7)
-> Using BlocksKit (2.2.5) was (2.2.4)
-> Installing MBProgressHUD (1.0.0)
...
下载的方法在CocoaPods-Download中:
def self.download_source(target, params)
FileUtils.rm_rf(target)
downloader = Downloader.for_target(target, params)
downloader.download
target.mkpath
if downloader.options_specific?
params
else
downloader.checkout_options
end
end
方法中调用的for_target根据不同的源会创建一个下载器,因为依赖可能通过不同的协议或者方式进行下载,比如说Git/HTTP/SVN等等,组件CocoaPods-Downloader就会根据Podfile中依赖的参数选项使用不同的方法下载依赖。
大部分的依赖都会被下载到~/Library/Caches/CocoaPos/Pods/Release这个文件夹中,然后从这个这里复制到项目工程目录下的./Pods中,这也就完成了整个CocoaPods的下载流程。
生成Pods.xcodeproj
CocoaPods通过组件CocoaPods-Downloader已经成功将所有的依赖下载到了当前的工程中,这里会将所有的依赖打包到Pods.xcodeproj中:
def generate_pods_project(generator = create_generator)
UI.section 'Generating Pods project' do
generator.generate!
@pods_project = generator.project
run_podfile_post_install_hooks
generator.write
generator.share_development_pod_schemes
write_lockfiles
end
end
generate_pods_project中会执行PodsProjectGenerator的实例方法generate!:
def generate!
prepare
install_file_references
install_libraries
set_target_dependencies
end
这个方法做了几件小事:
- 生成Pods.xcodeproj工程
- 将依赖中的文件加入工程
- 将依赖中的Library加入工程
- 设置目标依赖(Target Dependencies)
这几件事情都离不开CocoaPods的另外一个组件Xcodeproj,这个一个可以操作一个Xcode工程中的Group以及文件的组件,我们都知道对对Xcode工程的修改大多数情况下都是对一个名为project.pdxproj的文件进行修改,而Xcodeproj这个组件就是CocoaPods团队开发的用于操作这个文件的第三方库。
生成workspace
最后的这一部分与生成Pods.xcodeproj的过程有一些相似,这里使用的类是UserProjectIntegrator,调用方法integrate!时,就会开始集成工程所需要的Target:
def integrate!
create_workspace
integrate_user_targets
warn_about_xcconfig_overrides
save_projects
end
整个Pod install的过程就结束了。