匿了很长一段时间了,但是仍然一直处于学习ing...刚好最近项目也需要用到cocoapods的一些技巧,且有次面试也问我是否看过cocoapods源码...于是决定看下...
1、cocoapods 源码
1. cocoapods 包含多个模块
2. cocoapods-1.5.3(cocoapods) 目录
➜ CocoaPods git:(master) ls -l
total 624
-rw-r--r-- 1 xiongzenghui staff 257140 7 24 10:31 CHANGELOG.md
-rw-r--r-- 1 xiongzenghui staff 3387 7 24 10:31 CODE_OF_CONDUCT.md
-rw-r--r-- 1 xiongzenghui staff 5966 7 24 10:31 CONTRIBUTING.md
-rw-r--r-- 1 xiongzenghui staff 1367 7 24 10:31 Dangerfile
-rw-r--r-- 1 xiongzenghui staff 1892 7 24 10:31 Gemfile
-rw-r--r-- 1 xiongzenghui staff 6770 7 24 10:31 Gemfile.lock
-rw-r--r-- 1 xiongzenghui staff 1591 7 24 10:31 LICENSE
-rw-r--r-- 1 xiongzenghui staff 3585 7 24 10:31 NOMENCLATURE.md
-rw-r--r-- 1 xiongzenghui staff 6043 7 24 10:31 README.md
-rw-r--r-- 1 xiongzenghui staff 11531 7 24 10:31 Rakefile
drwxr-xr-x 4 xiongzenghui staff 128 7 24 10:31 bin
-rw-r--r-- 1 xiongzenghui staff 3172 7 24 10:31 cocoapods.gemspec
drwxr-xr-x 10 xiongzenghui staff 320 7 24 10:31 examples
drwxr-xr-x 4 xiongzenghui staff 128 7 24 10:31 lib
drwxr-xr-x 10 xiongzenghui staff 320 7 24 10:31 spec
➜ CocoaPods git:(master)
2、cocoapods.gemspec
# encoding: UTF-8
require File.expand_path('../lib/cocoapods/gem_version', __FILE__)
require 'date'
Gem::Specification.new do |s|
s.name = "cocoapods"
s.version = Pod::VERSION
s.date = Date.today
s.license = "MIT"
s.email = ["eloy.de.enige@gmail.com", "fabiopelosin@gmail.com", "kyle@fuller.li", "segiddins@segiddins.me"]
s.homepage = "https://github.com/CocoaPods/CocoaPods"
s.authors = ["Eloy Duran", "Fabio Pelosin", "Kyle Fuller", "Samuel Giddins"]
s.summary = "The Cocoa library package manager."
s.description = "CocoaPods manages library dependencies for your Xcode project.\n\n" \
"You specify the dependencies for your project in one easy text file. " \
"CocoaPods resolves dependencies between libraries, fetches source " \
"code for the dependencies, and creates and maintains an Xcode " \
"workspace to build your project.\n\n" \
"Ultimately, the goal is to improve discoverability of, and engagement " \
"in, third party open-source libraries, by creating a more centralized " \
"ecosystem."
s.files = Dir["lib/**/*.rb"] + %w{ bin/pod bin/sandbox-pod README.md LICENSE CHANGELOG.md }
s.executables = %w{ pod sandbox-pod }
s.require_paths = %w{ lib }
# Link with the version of CocoaPods-Core
s.add_runtime_dependency 'cocoapods-core', "= #{Pod::VERSION}"
s.add_runtime_dependency 'claide', '>= 1.0.2', '< 2.0'
s.add_runtime_dependency 'cocoapods-deintegrate', '>= 1.0.2', '< 2.0'
s.add_runtime_dependency 'cocoapods-downloader', '>= 1.2.1', '< 2.0'
s.add_runtime_dependency 'cocoapods-plugins', '>= 1.0.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-search', '>= 1.0.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-stats', '>= 1.0.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-trunk', '>= 1.3.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-try', '>= 1.1.0', '< 2.0'
s.add_runtime_dependency 'molinillo', '~> 0.6.5'
s.add_runtime_dependency 'xcodeproj', '>= 1.5.8', '< 2.0'
## Version 5 needs Ruby 2.2, so we specify an upper bound to stay compatible with system ruby
s.add_runtime_dependency 'activesupport', '>= 4.0.2', '< 5'
s.add_runtime_dependency 'colored2', '~> 3.1'
s.add_runtime_dependency 'escape', '~> 0.0.4'
s.add_runtime_dependency 'fourflusher', '~> 2.0.1'
s.add_runtime_dependency 'gh_inspector', '~> 1.0'
s.add_runtime_dependency 'nap', '~> 1.0'
s.add_runtime_dependency 'ruby-macho', '~> 1.2'
s.add_development_dependency 'bacon', '~> 1.1'
s.add_development_dependency 'bundler', '~> 1.3'
s.add_development_dependency 'rake', '~> 10.0'
## Make sure you can build the gem on older versions of RubyGems too:
s.rubygems_version = "1.6.2"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.required_ruby_version = '>= 2.0.0'
s.specification_version = 3 if s.respond_to? :specification_version
end
- 1、
lib/**/*.rb
- 2、pod、sandbox-pod
- 3、cocoapods 组成结构
s.add_runtime_dependency 'cocoapods-core', "= #{Pod::VERSION}"
s.add_runtime_dependency 'claide', '>= 1.0.2', '< 2.0'
s.add_runtime_dependency 'cocoapods-deintegrate', '>= 1.0.2', '< 2.0'
s.add_runtime_dependency 'cocoapods-downloader', '>= 1.2.1', '< 2.0'
s.add_runtime_dependency 'cocoapods-plugins', '>= 1.0.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-search', '>= 1.0.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-stats', '>= 1.0.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-trunk', '>= 1.3.0', '< 2.0'
s.add_runtime_dependency 'cocoapods-try', '>= 1.1.0', '< 2.0'
s.add_runtime_dependency 'molinillo', '~> 0.6.5'
s.add_runtime_dependency 'xcodeproj', '>= 1.5.8', '< 2.0'
3、Gemfile
SKIP_UNRELEASED_VERSIONS = false
# Declares a dependency to the git repo of CocoaPods gem. This declaration is
# compatible with the local git repos feature of Bundler.
#
def cp_gem(name, repo_name, branch = 'master', path: false)
# 1、调用gem()直接从rubygems服务器下载库代码
return gem name if SKIP_UNRELEASED_VERSIONS
# return gem(name) if SKIP_UNRELEASED_VERSIONS
# 2.1 构造参数Hash
opts = if path
# 指定从【本地目录】读取依赖的库代码
{ :path => "../#{repo_name}" }
else
# 指定从【cocoapods服务器】下载依赖的库代码
url = "https://github.com/CocoaPods/#{repo_name}.git"
{ :git => url, :branch => branch }
end
# 3、gem()
gem name, opts
# gem(name, opts)
end
source 'https://rubygems.org'
gemspec
# This is the version that ships with OS X 10.10, so be sure we test against it.
# At the same time, the 1.7.7 version won't install cleanly on Ruby > 2.2,
# so we use a fork that makes a trivial change to a macro invocation.
gem 'json', :git => 'https://github.com/segiddins/json.git', :branch => 'seg-1.7.7-ruby-2.2'
group :development do
cp_gem 'claide', 'CLAide'
cp_gem 'cocoapods-core', 'Core'
cp_gem 'cocoapods-deintegrate', 'cocoapods-deintegrate'
cp_gem 'cocoapods-downloader', 'cocoapods-downloader'
cp_gem 'cocoapods-plugins', 'cocoapods-plugins'
cp_gem 'cocoapods-search', 'cocoapods-search'
cp_gem 'cocoapods-stats', 'cocoapods-stats'
cp_gem 'cocoapods-trunk', 'cocoapods-trunk'
cp_gem 'cocoapods-try', 'cocoapods-try'
cp_gem 'molinillo', 'Molinillo'
cp_gem 'nanaimo', 'Nanaimo'
cp_gem 'xcodeproj', 'Xcodeproj'
gem 'cocoapods-dependencies', '~> 1.0.beta.1'
gem 'bacon'
gem 'mocha'
gem 'mocha-on-bacon'
gem 'prettybacon'
gem 'webmock'
# Integration tests
gem 'diffy'
gem 'clintegracon'
# Code Quality
gem 'inch_by_inch'
gem 'rubocop'
gem 'danger'
end
group :debugging do
gem 'cocoapods_debug'
gem 'rb-fsevent'
gem 'kicker'
gem 'awesome_print'
gem 'ruby-prof', :platforms => [:ruby]
end
- 1、group :development
- 2、group :debugging
4、bin目录包含2个可执行文件
➜ bin git:(master) ls -l
total 24
-rwxr-xr-x@ 1 xiongzenghui staff 1443 7 24 10:31 pod
-rwxr-xr-x@ 1 xiongzenghui staff 4266 7 24 10:31 sandbox-pod
➜ bin git:(master)
5、pod
0. 本地rubygems包存储目录
/Users/xiongzenghui/.rvm/gems/ruby-2.3.0@global/gems/cocoapods-1.5.3/bin
1. rb源文件
- 1、就是一个ruby源文件
- 2、通过
#!/usr/bin/env ruby
指定搜索PATH环境变量中的ruby解释器进行解释执行rb文件
#!/usr/bin/env ruby
# 1、如果编码不是UTF-8,则输出错误信息到stderr
if Encoding.default_external != Encoding::UTF_8
if ARGV.include? '--no-ansi'
STDERR.puts <<-DOC
WARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
Consider adding the following to ~/.profile:
export LANG=en_US.UTF-8
DOC
else
STDERR.puts <<-DOC
\e[33mWARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
Consider adding the following to ~/.profile:
export LANG=en_US.UTF-8
\e[0m
DOC
end
end
# 2、确定从哪里导入cocoapods库
# - 1)本地:../../lib/
# - 2)rubygems服务器:https://rubygems.org
if $PROGRAM_NAME == __FILE__ && !ENV['COCOAPODS_NO_BUNDLER']
# 找到 Gemfile 文件,添加到ENV环境变量map中
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
# 导入系统rb库
require 'rubygems'
require 'bundler/setup'
# 将cocoapods源码根目录下的lib目录,添加到$LOAD_PATH记录的加载rb文件的搜索目录集合中
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
elsif ENV['COCOAPODS_NO_BUNDLER']
require 'rubygems'
gem 'cocoapods' # 从 https://rubygems.org 共有仓库,下载cocoapods库
end
# 3、
STDOUT.sync = true if ENV['CP_STDOUT_SYNC'] == 'TRUE'
# 4、导入 cocoapods.rb
require 'cocoapods'
# 5、执行pod命令时,是否带有 PROFILE=xxx 选项
# - 1)有:
# - 调用 ruby-prof 分析rb代码的执行效率
# - 再调用 Pod::Command.run(ARGV) 解析命令行参数
# - https://ruby-china.org/topics/25959
# - 2)没有:
# - 直接 Pod::Command.run(ARGV) 解析命令行参数
#
# - 3)Pod::Command.run(ARGV) 负责调用claide构建命令行选项
if profile_filename = ENV['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) # Pod模块中的Command类的self.run()类方法
}).print(io)
end
else
Pod::Command.run(ARGV) # Pod模块中的Command类的self.run()类方法
end
2. 要点梳理
1. PROFILE 命令行参数
PROFILE=tmp.txt pod install
2. ENV['COCOAPODS_NO_BUNDLER'] == 1 执行如下逻辑
elsif ENV['COCOAPODS_NO_BUNDLER']
require 'rubygems'
gem 'cocoapods' # 从 https://rubygems.org 共有仓库,下载安装cocoapods库
end
3. 调用claide构建命令行选项
Pod::Command.run(ARGV)
构造pod命令行选项如下
➜ ~ pod
Usage:
$ pod COMMAND
CocoaPods, the Cocoa library package manager.
Commands:
+ cache Manipulate the CocoaPods cache
+ deintegrate Deintegrate CocoaPods from your project
+ env Display pod environment
+ init Generate a Podfile for the current directory
+ install Install project dependencies according to versions from a
Podfile.lock
+ ipc Inter-process communication
+ lib Develop pods
+ list List pods
+ outdated Show outdated project dependencies
+ plugins Show available CocoaPods plugins
+ repo Manage spec-repositories
+ search Search for pods
+ setup Setup the CocoaPods environment
+ spec Manage pod specs
+ trunk Interact with the CocoaPods API (e.g. publishing new specs)
+ try Try a Pod!
+ update Update outdated project dependencies and create new Podfile.lock
Options:
--silent Show nothing
--version Show the version of the tool
--verbose Show more debugging information
--no-ansi Show output without ANSI codes
--help Show help banner of specified command
➜ ~
6、sandbox-pod
0. 本地rubygems包存储目录
/Users/xiongzenghui/.rvm/gems/ruby-2.3.0@global/gems/cocoapods-1.5.3/bin
1. rb源文件
#!/usr/bin/env ruby
# encoding: utf-8
# This bin wrapper runs the `pod` command in a OS X sandbox. The reason for this
# is to ensure that people can’t use malicious code from pod specifications.
#
# It does this by creating a ‘seatbelt’ profile on the fly and executing the
# given command through `/usr/bin/sandbox-exec`. This profile format is an
# undocumented format, which uses TinyScheme to implement its DSL.
#
# Even though it uses a undocumented format, it’s actually very self-explanatory.
# Because we use a whitelist approach, `(deny default)`, any action that is
# denied is logged to `/var/log/system.log`. So tailing that should provide
# enough information on steps that need to be take to get something to work.
#
# For more information see:
#
# * https://github.com/CocoaPods/CocoaPods/issues/939
# * http://reverse.put.as/wp-content/uploads/2011/08/The-Apple-Sandbox-BHDC2011-Slides.pdf
# * http://reverse.put.as/wp-content/uploads/2011/08/The-Apple-Sandbox-BHDC2011-Paper.pdf
# * https://github.com/s7ephen/OSX-Sandbox--Seatbelt--Profiles
# * `$ man sandbox-exec`
# * `$ ls /usr/share/sandbox`
# 1、单独执行 sandbox-pod(rb文件)
if $0 == __FILE__
$:.unshift File.expand_path('../../lib', __FILE__) # 追加本地的lib目录为require搜索路径
end
# 2、require xxx.rb
require 'pathname'
require 'cocoapods/config' # lib/cocoapods/config.rb
require 'rbconfig'
require 'erb'
PROFILE_ERB_TEMPLATE = <<-EOS
(version 1)
(debug allow)
(import "mDNSResponder.sb")
(allow file-ioctl)
(allow sysctl-read)
(allow mach-lookup)
(allow ipc-posix-shm)
(allow process-fork)
(allow system-socket)
; TODO make this stricter if possible
(allow network-outbound)
(allow process-exec
(literal
"<%= pod_bin %>"
"<%= ruby_bin %>"
)
(regex
<% prefixes.each do |prefix| %>
#"^<%= prefix %>/*"
<% end %>
)
)
(allow file-read-metadata)
(allow file-read*
; This is currenly only added because using `xcodebuild` to build a resource
; bundle target starts a FSEvents stream on `/`. No idea why that would be
; needed, but for now it doesn’t seem like a real problem.
(literal "/")
(regex
; TODO see if we can restrict this more, but it's going to be hard
#"^/Users/[^.]+/*"
;#"^/Users/[^.]+/.netrc"
;#"^/Users/[^.]+/.gemrc"
;#"^/Users/[^.]+/.gem/*"
;#"^/Users/[^.]+/Library/.*"
#"^/Library/*"
#"^/System/Library/*"
#"^/usr/lib/*"
#"^/usr/share/*"
#"^/private/*"
#"^/dev/*"
#"^<%= ruby_prefix %>"
#"^<%= pod_prefix %>"
#"^<%= xcode_app_path %>"
#"^<%= Pod::Config.instance.repos_dir %>"
<% prefixes.each do |prefix| %>
#"^<%= prefix %>/*"
<% end %>
)
)
(allow file-write*
(literal
"/dev/dtracehelper"
"/dev/null"
)
(regex
#"^<%= Pod::Config.instance.project_root %>"
#"^<%= Pod::Config.instance.repos_dir %>"
#"^/Users/[^.]+/Library/Caches/CocoaPods/*"
#"^/dev/tty"
#"^/private/var"
)
)
(deny default)
EOS
class Profile
def pod_bin
File.expand_path('../pod', __FILE__)
end
def pod_prefix
File.expand_path('../..', pod_bin)
end
def ruby_bin
File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
end
def ruby_prefix
RbConfig::CONFIG['prefix']
end
def prefix_from_bin(bin_name)
unless (path = `which #{bin_name}`.strip).empty?
File.dirname(File.dirname(path))
end
end
def prefixes
prefixes = ['/bin', '/usr/bin', '/usr/libexec', xcode_app_path]
prefixes << `brew --prefix`.strip unless `which brew`.strip.empty?
# From asking people, it seems MacPorts does not have a `prefix` command, like
# Homebrew does, so make an educated guess:
if port_prefix = prefix_from_bin('port')
prefixes << port_prefix
end
if rbenv_prefix = prefix_from_bin('rbenv')
prefixes << rbenv_prefix
end
prefixes
end
def developer_prefix
`xcode-select --print-path`.strip
end
def xcode_app_path
File.expand_path('../..', developer_prefix)
end
# TODO: raise SAFE level (0) to 4 if possible.
def generate
ERB.new(PROFILE_ERB_TEMPLATE, 0, '>').result(binding)
end
end
# Ensure the `pod` bin doesn’t think it needs to use Bundler.
ENV['COCOAPODS_NO_BUNDLER'] = '1'
profile = Profile.new
# puts profile.generate
# exec() 执行数组保证的shell命令+选项+选项值
command = ['/usr/bin/sandbox-exec', '-p', profile.generate, profile.pod_bin, *ARGV]
exec(*command)
2. 要点梳理
- 1、This bin wrapper runs the
pod
command in a OS X sandbox - 2、any action that is denied is logged to
/var/log/system.log
- 3、ENV['COCOAPODS_NO_BUNDLER'] = '1' ,则会影响 pod
- 4、最终调用 /usr/bin/sandbox-exec
- 5、exec() 执行数组保证的shell命令+选项+选项值