pod install安装流程
我们先看一下install入口
在命令行中输入which pod,找到pod命令所在的目录
➜ [/Users] ✗ open /usr/local/bin/pod
➜ [/Users] ✗ open /usr/local/bin
根据路径,我们打开pod的脚本,可以看到这个脚本是用来唤起cocoapods的,流程是利用Gem.activate_bin_path
找到 CocoaPods 的安装目录 cocoapods/bin
,然后使用 Gem.bin_path
来加载该目录下的 /pod
文件,然后我们打开pod文件,看到Pod::Command.run(ARGV)
,最终通过pod下面的run方法来进入install。
#!/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'cocoapods' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0.a"
str = ARGV.first
if str
str = str.b[/\A_(.*)_\z/, 1]
if str and Gem::Version.correct?(str)
version = str
ARGV.shift
end
end
if Gem.respond_to?(:activate_bin_path)
load Gem.activate_bin_path('cocoapods', 'pod', version)
else
gem "cocoapods", version
load Gem.bin_path("cocoapods", "pod", version)
end
//Pod文件下面的部分代码
require 'cocoapods'
if profile_filename = ENV['COCOAPODS_PROFILE']
require 'ruby-prof'
reporter =
case (profile_extname = File.extname(profile_filename))
when '.txt'
RubyProf::FlatPrinterWithLineNumbers
when '.html'
RubyProf::GraphHtmlPrinter
when '.callgrind'
RubyProf::CallTreePrinter
else
raise "Unknown profiler format indicated by extension: #{profile_extname}"
end
File.open(profile_filename, 'w') do |io|
reporter.new(RubyProf.profile { Pod::Command.run(ARGV) }).print(io)
end
else
Pod::Command.run(ARGV)
end
下面,我们看看Pod的run函数
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.deployment = @deployment
installer.clean_install = @clean_install
installer.install!
end
end
- 判断podfile是否存在,不存在报错"No `Podfile’ found in the project directory."
- 从podfile和lock文件中获取参数,创建installer
def installer_for_config
Installer.new(config.sandbox, config.podfile, config.lockfile)
end
- 判断spec文件是否应该被更新
- 不请求更新pod,默认不更新
# @return [Hash, Boolean, nil] Pods that have been requested to be
# updated or true if all Pods should be updated.
# If all Pods should been updated the contents of the Lockfile are
# not taken into account for deciding what Pods to install.
#
attr_accessor :update
- installation是否验证podfile或者lockfile有更新,默认不验证
- installation是否忽略peoject的缓存内容,默认不忽略
- 安装pods,开始执行pod isntall的流程
def install!
puts 'zyb'
prepare
resolve_dependencies
download_dependencies
validate_targets
if installation_options.skip_pods_project_generation?
show_skip_pods_project_generation_message
else
integrate
end
write_lockfiles
perform_post_install_actions
end
安装过程是线性的,但是需要注意以下两点
- 为例比main返回旧的podspec文件,在执行解析之前需要清理缓存的podspec文件
- reslocer可能会触发歪脖的source的下载,需要递归检测外部source 的spec文件
如上所述,在installer组装完毕之后,调用install!方法,进入pod install的流程中,下面我们看看pod install的流程,看看它做了哪些操作
(1)install的准备–prepare
将 pod install
的环境准备完成,包括版本一致性、目录结构以及将 pre-install 的装载插件脚本全部取出,并执行对应的 pre_install
hook。
def prepare
# 如果检测出当前目录是 Pods,直接 raise 终止
if Dir.pwd.start_with?(sandbox.root.to_path)
message = 'Command should be run from a directory outside Pods directory.'
message << "\n\n\tCurrent directory is #{UI.path(Pathname.pwd)}\n"
raise Informative, message
end
UI.message 'Preparing' do
# 如果 lock 文件的 CocoaPods 主版本和当前版本不同,将以新版本的配置对 xcodeproj 工程文件进行更新
deintegrate_if_different_major_version
# 对 sandbox(Pods) 目录建立子目录结构
sandbox.prepare
# 检测 PluginManager 是否有 pre-install 的 plugin
ensure_plugins_are_installed!
# 执行插件中 pre-install 的所有 hooks 方法
run_plugins_pre_install_hooks
end
end
(2)解决依赖冲突–resolve_dependencies
依赖解析过程就是通过 Podfile
、Podfile.lock
以及沙盒中的 manifest
生成 Analyzer 对象。Analyzer内部会使用 Molinillo (具体的是 Molinillo::DependencyGraph
图算法)解析得到一张依赖关系表。
(1)通过 Analyzer 能获取到很多依赖信息,例如 Podfile 文件的依赖分析结果,也可以从 specs_by_target 来查看各个 target 相关的 specs。
(2)analyze 的过程中有一个 pre_download 的阶段,即在 –verbose 下看到的 Fetching external sources 过程。这个 pre_download 阶段不属于依赖下载过程,而是在当前的依赖分析阶段。
def resolve_dependencies
# 获取plugin的Sources
plugin_sources = run_source_provider_hooks
# 通过 Podfile、Podfile.lock 以及沙盒中的 manifest 生成 Analyzer 对象
analyzer = create_analyzer(plugin_sources)
# 如果带有 repo_update 标记
UI.section 'Updating local specs repositories' do
# 执行 Analyzer 的更新 Repo 操作
analyzer.update_repositories
end if repo_update?
UI.section 'Analyzing dependencies' do
# 从 analyzer 取出最新的分析结果,@analysis_result,@aggregate_targets,@pod_targets
analyze(analyzer)
# 拼写错误降级识别,确保在白名单内的构建配置没有拼写错误
validate_build_configurations
end
# 如果 deployment 为 true,会验证 podfile & lockfile 是否需要更新
UI.section 'Verifying no changes' do
verify_no_podfile_changes!
verify_no_lockfile_changes!
end if deployment?
analyzer
end
插曲:pod repo update:用来更新pod资源目录,也就是更新master下的资源,例如,有一个第三方库发布了一个最新的版本,如果不执行pod repo update,那么你的本地是不会知道有一个最新版本的,还会一直以你本地的资源目录为准。不执行这个命令就拿不到这个库的最新版本
我们开发时不使用这个命令时因为pod update是默认会执行一遍pod repo update 。
(3)下载依赖文件–download_dependencies
先执行install_pod_sources
过程,它会调用对应 Pod 的 install!
方法进行资源下载;然后执行 podfile 定义的 pre install 的 hooks,最后根据配置清理 pod sources 信息,主要是清理无用 platform 相关内容
def download_dependencies
UI.section 'Downloading dependencies' do
# 下载、安装文档并清理需要安装的Pod的源代码
install_pod_sources
# 运行已安装specs和 Podfile 的pre_install的钩子。
run_podfile_pre_install_hooks
#清理pod源
clean_pod_sources
end
end
(4) 验证 targets–validate_targets
验证之前流程中的产物 (pod 所生成的 Targets) 的合法性
def validate_targets
#构造TargetValidator
validator = Xcode::TargetValidator.new(aggregate_targets, pod_targets, installation_options)
#用来验证提供的聚合和pod是否正确,否则报错
validator.validate!
end
def validate!
verify_no_duplicate_framework_and_library_names
verify_no_static_framework_transitive_dependencies
verify_swift_pods_swift_version
verify_swift_pods_have_module_dependencies
verify_no_multiple_project_names if installation_options.generate_multiple_pod_projects?
end
validate方法,用来验证提供的聚合和pod是否正确,否则报错,下面是对方法内五个函数的介绍
- verify_no_duplicate_framework_and_library_names
验证是否有重名的 framework
,如果有冲突会直接抛出 frameworks with conflicting names
异常。
- verify_no_static_framework_transitive_dependencies
验证动态库中是否有静态链接库 (.a
或者 .framework
) 依赖,如果存在则会触发 transitive dependencies that include static binaries...
错误。假设存在以下场景:
- 组件 A 和组件 B 同时依赖了组件 C,C 为静态库,如
Weibo_SDK
- 组件 A 依赖组件 B,而组件 B 的
.podspec
文件中存在以下设置时,组件 B 将被判定为存在静态库依赖:
- podspec 设置了
s.static_framework = true
- podspec 以
s.dependency 'xxx_SDK
依赖了静态库xxx_SDK
- podspec 以
s.vendored_libraries = 'libxxx.a'
方式内嵌了静态库libxxx
此时如果项目的 Podfile
设置了 use_framework!
以动态链接方式打包的时,则会触发该错误。
问题原因
Podfile 中不使用 use_frameworks!
时,每个 pod 是会生成相应的 .a(静态链接库)文件,然后通过 static libraries 来管理 pod 代码,在 Linked 时会包含该 pod 引用的其他的 pod 的 .a 文件。 Podfile 中使用 use_frameworks!
时是会生成相应的 .framework 文件,然后通过 dynamic frameworks 的方式来管理 pod 代码,在 Linked 时会包含该 pod 引用的其他的 pod 的 .framework 文件。 上述场景中虽然以 framework 的方式引用了 B 组件,然而 B 组件实际上是一个静态库,需要拷贝并链接到该 pod 中,然而 dynamic frameworks 方式并不会这么做,所以就报错了。
解决方案
- 修改 pod 库中 podspec,增加 pod_target_xcconfig,定义好 FRAMEWORK_SEARCH_PATHS 和 OTHER_LDFLAGS 两个环境变量;
- hook verify_no_static_framework_transitive_dependencies 的方法,将其干掉!对应 issue
- 修改 pod 库中 podspec,开启 static_framework 配置 s.static_framework = true
- verify_swift_pods_swift_version
确保 Swift Pod 的 Swift 版本正确配置且互相兼容的。
- verify_swift_pods_have_module_dependencies
检测 Swift 库的依赖库是否支持了 module,这里的 module 主要是针对 Objective-C 库而言。 首先,Swift 是天然支持 module 系统来管理代码的,Swift Module 是构建在 LLVM Module 之上的模块系统。Swift 库在解析后会生成对应的 modulemap
和 umbrella.h
文件,这是 LLVM Module 的标配,同样 Objective-C 也是支持 LLVM Module。当我们以 Dynamic Framework 的方式引入 Objective-C 库时,Xcode 支持配置并生成 header,而静态库 .a 需要自己编写对应的 umbrella.h
和 modulemap
。 其次,如果你的 Swift Pod 依赖了 Objective-C 库,又希望以静态链接的方式来打包 Swift Pod 时,就需要保证 Objective-C 库启用了 modular_headers
,这样 CocoaPods 会为我们生成对应 modulemap
和 umbrella.h
来支持 LLVM Module。你可以从这个地址 - http://blog.cocoapods.org/CocoaPods-1.5.0/
- verify_no_pods_used_with_multiple_swift_versions
检测是否所有的 Pod Target 中版本一致性问题。
(5)写入依赖–write_lockfiles
将依赖更新写入 Podfile.lock
和 Manifest.lock
def write_lockfiles
@lockfile = generate_lockfile
UI.message "- Writing Lockfile in #{UI.path config.lockfile_path}" do
# No need to invoke Sandbox#update_changed_file here since this logic already handles checking if the
# contents of the file are the same.
@lockfile.write_to_disk(config.lockfile_path)
end
UI.message "- Writing Manifest in #{UI.path sandbox.manifest_path}" do
# No need to invoke Sandbox#update_changed_file here since this logic already handles checking if the
# contents of the file are the same.
@lockfile.write_to_disk(sandbox.manifest_path)
end
end
(7)结束回调–perform_post_install_action
install的收尾工作,为所有插件提供 post-installation 操作以及 hook。
def perform_post_install_actions
#执行已安装的specs和podfile里面的post install的钩子
run_plugins_post_install_hooks
# Prints a warning for any pods that are deprecated
warn_for_deprecations
# Prints a warning for any pods that included script phases
warn_for_installed_script_phases
# Prints a warning if the project is not explicitly using the git based master specs repo.
warn_for_removing_git_master_specs_repo
print_post_install_message
end
参考:https://www.zhihu.com/people/tu-tu-edmondmu/posts