在之前的文章中,我们讲述了cocoapods
命令解析的方式和方法Cocoapods 命令解析器 CLAide,今天主要来探讨下Podfile
文件是怎么解析的。
在探讨之前,我们先了解下,Ruby
中的eval
的用法
eval
eval
和之前文章讲的instance_eval
Ruby和Cocoapods文章合集
有相似的地方,可以以string的形式执行Ruby
代码。
p eval("2 + 3") #1
eval ("def sayHello; p 'hello world'; end") #2
eval("sayHello") # 3
输出结果:
5
"hello world"
复制代码
- 1, 执行 2 + 3 ,所以最后输出 5。
- 2,定义一个
sayHello
方法,该方法输出hello world
。 - 3,执行
sayHello
方法。
Podfile文件格式
支持的格式
打开之前我们创建的cocoapods调试
工程,并设置断点,查看函数调用栈
我们可以快速的定位到self.from_file
这个类方法中:
def self.from_file(path)
path = Pathname.new(path)
unless path.exist?
raise Informative, "No Podfile exists at path `#{path}`."
end
case path.extname
when '', '.podfile', '.rb'
Podfile.from_ruby(path)
when '.yaml'
Podfile.from_yaml(path)
else
raise Informative, "Unsupported Podfile format `#{path}`."
end
end
复制代码
Podfile
如果文件的扩展名为'' .podfile .rb
中的其中一个,就将该文件识别为Ruby
文件,通过 Podfile.from_ruby
读取该文件。
如果文件的扩展名为yaml
,就通过 Podfile.from_yaml
来读取,在接下来探讨中,我们以Podfile.from_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
复制代码
我们来查看下contents
的值
contents
"platform :ios, '13.0'\n\ntarget 'HQPDemo' do\n use_frameworks!\n pod 'Alamofire'\n\n target 'HQPDemoTests' do\n inherit! :search_paths\n end\n\n target 'HQPDemoUITests' do\n end\n\nend\n\npost_install do |installer|\n installer.pods_project.targets.each do |target|\n puts \"post --\#{target.name}\"\n end\nend\n\npre_install do |installer|\n\n installer.pods_project.target.each do |target|\n puts \"pre --\#{target.name}\"\n end\n\nend \n\n\n"
复制代码
contents
是一个字符串,内容是我们在Podfile
文件里写的内容,通过eval
方法来执行,
那platform, target,use_frameworks!
方法在哪里进行定义的呢?
DSL
在cocoapods-core
中的Podfile.rb
中:
将 Pod::Podfile::DSL
module ,使用 inclue
引用进来,
在Pod::Podfile::DSL
定义了 Podfile
中需要执行的方法。采用Mix-in
的方式,让我们的Podfile
类,也有了这些方法,从而能够执行上述方法。DSL
模块来负责解析我们在Podfile文件
里的相关配置。
为方便理解,我写了一个简易版的文件解析
// Podfile
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '13.0'
target 'HQPDemo' do
use_frameworks! false
puts "开始安装第三方文件"
end
// real_eval.rb
class ReadEval
def platform(name, target)
p "平台:--- #{name}, target:----#{target}"
end
def use_frameworks!(option=true)
p "option: -- #{option}"
end
def source(source)
p "source: -- #{source}"
end
def target(name, options = nil)
p "target--name: -- #{name}"
p "target--option: -- #{options}"
yield if block_given?
end
def initialize(path ,&block)
if block
instance_eval(&block)
end
end
def self.from_ruby(path, contents = nil)
contents = File.open(path,'r:utf-8',&:read)
if contents.respond_to?(:encoding) && contents.encoding.name != 'UTF-8'
contents.encode!('UTF-8')
end
ReadEval.new(path) do
eval(contents)
end
end
end
path = Dir.pwd + "/Pod/Podfile"
ReadEval.from_ruby(path)
//执行结果:
"source: -- [https://github.com/CocoaPods/Specs.git]"
"平台:--- ios, target:----13.0"
"target--name: -- HQPDemo"
"target--option: -- "
"option: -- false" 开始安装第三方文件
复制代码
这样就完成了Podfile
文件的读取,并执行。
Podfile对象
接下来,我们来看下Podfile
对象的结构
class Podfile
inclue Pod::Podfile::DSL
attr_accessor :defined_in_file # 1
attr_accessor :root_target_definitions #2
end
复制代码
- 1,
podfile
文件的路径。 - 2,
根Target 定义
,是一个数组,在本例中,只有一个元素TargetDefinition(Pods)
,用来记录Pods
project。
Targetdefinition
Targetdefinition
是一个多叉树结构
。通过 @parent
和@children
来记录层级嵌套
关系
def initialize(name, parent, internal_hash = nil)
@internal_hash = internal_hash || {}
@parent = parent
@children = []
@label = nil
self.name ||= name
if parent.is_a?(TargetDefinition)
parent.children << self
end
end
复制代码
主要作用是根据Podfile
文件记录cocoapods
依赖的库关系,在Podfile
文件中
platform :ios, '13.0'
target 'HQPDemo' do
use_frameworks!
pod 'Alamofire'
target 'HQPDemoTests' do
inherit! :search_paths
end
target 'HQPDemoUITests' do
end
end
复制代码
相对应的Podfile
对象的TargetDefinition
结构为
即
到这里 Podfile
文件就读取完成了,存放在配置Config
中。下一步开始初始化Pod::Installer
对象,开始下载依赖的处理,这一块,在后面的文章里,我们在探讨
参考:
Podfile 的解析逻辑