Command 基類在 lib/claide/command.rb 中,提供了大量基礎功能,包括 run 、 options、 help 等。當每次執(zhí)行 pod xxx 命令時候,會執(zhí)行 bin 目錄下的可執(zhí)行文件 pod:
require 'cocoapods'if profile_filename = ENV['PROFILE']# 忽略不相關(guān)內(nèi)容...elsePod::Command.run(ARGV)
end
這里實際上是 Pod 模塊從 CLAide 繼承子類 Command < CLAide::Command,執(zhí)行 Pod 命令時候,就會調(diào)用:
def self.run(argv)help! 'You cannot run CocoaPods as root.' if Process.uid ==0verify_minimum_git_version!verify_xcode_license_approved!super(argv)
ensureUI.print_warnings
end
這里最核心的就是 cocoapods_plugin.rb,前面分析過,執(zhí)行 pod 命令時候會主動加載所有 cocoapods_plugin.rb 文件,那么只要將需要擴展的類加到這里面,執(zhí)行命令時候就會生效:
class Test < Commandself.summary = 'Short description of cocoapods-test.'self.description =<<-DESCLonger description of cocoapods-test.DESCself.arguments ='NAME'def initialize(argv)@name = argv.shift_argumentsuperenddef validate!superhelp!'A Pod name is required.' unless @nameenddef runUI.puts "Add your implementation for the cocoapods-test plugin in #{__FILE__}"end
end
CocoaPods 為開發(fā)者提供了插件注冊功能,可以使用 pod plugins create NAME 命令創(chuàng)建插件,并在 Podfile 中通過 plugin ‘NAME’ 語句引入插件。雖然在一般情況下很少使用這個功能,但在某些場景下,利用插件能比較方便快捷地解決問題,比如清除 input,output 文件、創(chuàng)建 Podfile DSL 等。
首先,由于 pod install 過程會涉及到插件的加載,因此直接查看 installer.rb 文件:
#Runs the registered callbacks for the plugins post install hooks.#defrun_plugins_post_install_hookscontext = PostInstallHooksContext.generate(sandbox, aggregate_targets)HooksManager.run(:post_install, context, plugins)
end#Runs the registered callbacks for the plugins pre install hooks.
#
# @return[void]#defrun_plugins_pre_install_hookscontext = PreInstallHooksContext.generate(sandbox, podfile, lockfile)HooksManager.run(:pre_install, context, plugins)
end#Ensures that all plugins specified in the {#podfile} are loaded.
#
# @return[void]#defensure_plugins_are_installed!require 'claide/command/plugin_manager'loaded_plugins = Command::PluginManager.specifications.map(&:name)podfile.plugins.keys.each do|plugin|unless loaded_plugins.include? pluginraise Informative,"Your Podfile requires that the plugin `#{plugin}` be installed. Please install it and try installation again."endend
end
# @return[Array<Gem::Specification>] Loads plugins via RubyGems looking
#forfiles named after the `PLUGIN_PREFIX_plugin` and returns the#specificationsof the gems loaded successfully.#Plugins are required safely.#defself.load_plugins(plugin_prefix)loaded_plugins[plugin_prefix]||=plugin_gems_for_prefix(plugin_prefix).map do|spec, paths|spec ifsafe_activate_and_require(spec, paths)end.compact
end# @group Helper Methods# @return[Array<[Gem::Specification, Array<String>]>]#Returns an array of tuples containing the specifications and#pluginfiles to require for a given plugin prefix.#defself.plugin_gems_for_prefix(prefix)glob ="#{prefix}_plugin#{Gem.suffix_pattern}"Gem::Specification.latest_specs(true).map do|spec|matches = spec.matches_for_glob(glob)[spec, matches] unless matches.empty?end.compact
end#Activates the given spec and requires the given paths.#If any exception occurs it is caught and an#informativemessage is printed.
#
# @param [Gem::Specification] spec
#The spec to be activated.
#
# @param [String] paths
#The paths to require.
#
# @return[Bool] Whether activation and requiring succeeded.#defself.safe_activate_and_require(spec, paths)spec.activatepaths.each {|path|require(path)}true
rescue Exception => exception # rubocop:disable RescueExceptionmessage ="\n---------------------------------------------"message <<"\nError loading the plugin `#{spec.full_name}`.\n"message <<"\n#{exception.class} - #{exception.message}"message <<"\n#{exception.backtrace.join("\n")}"message <<"\n---------------------------------------------\n"warn message.ansi.yellowfalse
end
以上代碼調(diào)用幾個的 Gem::Specification 方法如下:
# 獲取最新 spec 集合
#Return the latest specs, optionally including prerelease specs if prerelease is true.latest_specs(prerelease = false) # 獲取 gem 中匹配的文件路徑
#Return all files in this gem that match for glob.matches_for_glob(glob) # 激活 spec,注冊并將其 lib 路徑添加到 $LOAD_PATH ($LOAD_PATH 環(huán)境變量存儲 require 文件時查找的路徑)
#Activate this spec, registering it as a loaded spec and adding it's lib paths to $LOAD_PATH. Returns true if the spec was activated, false if it was previously activated. Freaks out if there are conflicts upon activation.activate()
另外 CLAide::Command 類會在 run 類方法中加載所有插件,然后根據(jù)解析后的信息,執(zhí)行對應的命令:
# @param [Array, ARGV] argv
#A list of(remaining) parameters.
#
# @return[Command] An instance of the command class that was matched by
#goingthrough the arguments in the parameters and drilling down#commandclasses.#defself.run(argv =[])plugin_prefixes.each do|plugin_prefix|PluginManager.load_plugins(plugin_prefix)endargv = ARGV.coerce(argv)command =parse(argv)ANSI.disabled =!command.ansi_output?unless command.handle_root_options(argv)command.validate!command.runend
rescue Object => exceptionhandle_exception(command, exception)
end
#Handles plugin related logic logic for the `Command` class.
#
#Plugins are loaded the first time a command run and are identified by the#prefixspecified in the command class. Plugins must adopt the following#conventions:
#
# - Support being loaded by a file located under the
# `lib/#{plugin_prefix}_plugin` relative path.
# - Be stored in a folder named after the plugin.# - 支持通過 `lib/#{plugin_prefix}_plugin` 路徑的文件加載
# (也就是說,如果要對外暴露插件內(nèi)部存的方法,需要在此文件中 require 之,比如自定義的 Podfile DSL 文件)
# - 保存在以插件命名的文件夾中
Pod::HooksManager.register('cocoapods-lebbay',:source_provider)do|context|sources_manager = Pod::Config.instance.sources_managercontext.add_source(sources_manager.private_source)context.add_source(sources_manager.public_source)
end
module Podclass Sourceclass Manager# 私有源 sourcedef private_sourceurl = 'https://xyz.com/ios/pod-specs.git'source =source_with_url(url)return source if sourceCommand::Repo::Add.parse(['lebbay-spec', url,'master']).runsource_with_url(url)end# 公有源 sourcedef public_sourceurl = 'https://github.com/CocoaPods/Specs.git'source =source_with_url(url)return source if sourceCommand::Repo::Add.parse(['master', url,'master']).runsource_with_url(url)endendend
end
② Podfile 內(nèi)提供 dev_pods 自定義方法用于提測過程中實時拉取組件分支最新 commit
在組件開發(fā)過程中經(jīng)常會修改幾個 pod 的代碼,需要一個個的將 pod 指向本地開發(fā)目錄,在項目測試過程中又要將 pod 一個個指向提測分支,比如:
# 開發(fā)階段
pod 'PodA',:path =>'../PodA'
pod 'PodB',:path =>'../PodB'# 測試階段
pod 'PodA',:git =>'https://xyz.com/ios/PodA.git',:branch =>'release/1.0.0'
pod 'PodB',:git =>'https://xyz.com/ios/PodB.git',:branch =>'release/1.0.0'
關(guān)于這個問題的具體信息請查看:If there are one pod using xcassets via resources spec syntax, then all xcassets will be copied and compied twice during [CP] Copy Pods Resources build phase #8431,雖然目前沒有 pod 在使用 resources 命令,但為了避免三方庫會引入這個問題,還是需要左規(guī)避,在 post_install 的 hook 時機里修復 CocoaPods 的腳本,之前是將 post_install 的 hook 寫在了 Podfile 里,現(xiàn)在放在了插件里來做: