美文网首页iOS
pod install和pod update背后那点事

pod install和pod update背后那点事

作者: Startry | 来源:发表于2016-02-24 11:33 被阅读1103次

    Cocoapods对于绝大部分的iOS开发者们应该不陌生吧~ 如果你不知道什么是CocoaPods, 请移步CocoaPods的官方Guide去学习使用指南哇~

    pod installpod update应该大部分iOS以及OSX开发者最常用的两个命令了, 那么大家是否都知道pod installpod update在执行中主要做了哪些事情呢? 我们来一起探究一下呗~

    初步窥探

    通过CoocaPods的终端log输出, 我们也可以推测出pod install的大致行为, 如果需要更多的信息, 可以使用--verbose参数输出更多的信息, 被提炼后大致输出log如下:

    1. Updating local specs repositories - 更新本地Spec索引
    2. CocoaPods xxx is available. - 新版本试用提示
    3. Analyzing dependencies - 分析依赖
    4. Downloading dependencies - 下载依赖库
    5. Generating Pods project - 生成Pods项目
    6. Integrating client project - 整合项目

    深入窥探

    其实呢... 深入了解那自然最好看源码啦~ 问题是怎么跟踪去跟踪源码和学习源码... 我写这篇文章的时候CocoaPods最新的Tag是0.39.0.beta.4, 那我基于该tag来进行源码分析和追踪。

    我们想要了解两个命令pod installpod update, 我们可以从命令行入口进行跟踪学习。现在我们以pod install最为学习入口进行跟踪。

    我们可以用终端自带的which命令去跟踪pod命名源码文件所在地作为跟踪入口。执行命令可以发现pod命令原来放置在用户目录下的.rvm隐藏目录下。

    which pod
    # output: /Users/[UserName]/.rvm/gems/ruby-2.2.1/bin/pod
    

    使用文本编辑器打开pod脚本文件, 我们可以发现如下代码:

    load Gem.bin_path('cocoapods', 'pod', version)
    

    =。= 这脚本只是负责加载一个在源码目录bin下面的pod文件, 那么我们继续跟踪源码bin下面的pod文件。不多说, 赶紧进入CocoaPods项目地址拉源代码了。

    进入CocoaPods的开源项目可以发现CocoaPods时由下图所示的多个项目组成的。

    CocoaPods依赖子项目

    除去主工程CocoaPods和索引库Master Repo, 还依赖了5个工程。这给源码跟踪增加了不少难度, 不过没关系, 我们不是直接阅读源代码, 只需要关注主工程CocoaPods, 依赖的库在用到或者必须跟踪的时候再去跟踪。

    回归主题哇~

    主工程CocoaPods源码的下的pod文件做了一些环境变量的条件控制, 主要是区分COCOAPODS_NO_BUNDLERPROFILE环境变量, 这些不是本文的关键, 直接找核心代码:

    Pod::Command.run(ARGV)
    

    看到这个。。又要继续去跟踪Command的类了, ARGV是前面脚本传进来的参数。Command类在lib/cocoapods/command.rb下。

    在Command类下找到初始化函数入口run方法。

    class Command < CLAide::Command
      # ...
      def self.run(argv)
        help! 'You cannot run CocoaPods as root.' if Process.uid == 0
        verify_xcode_license_approved!
    
        super(argv)
      ensure
        UI.print_warnings
      end
      # ...
    end
    

    在Command类的run方法里, 我们可以看到主要是进行了非root用户检验以及验证xcode使用协议是否已经同意, 然后执行了父类的run方法。什么? 父类? Command的父类是CLAide下的Command类, CLAide可是CoocoaPods依赖的另外一个项目啊... 不多说了, 乖乖去拉CLAide项目的源代码。

    因为前面的CocoaPods主库是基于tag 0.39.0.bete.4分析的, 那么我们要该库对应使用CLAide的Tag分支。在CocoaPods的cocoapods.gemspec中有所有CocoaPods依赖的项目的制定版本信息, 通过查看该文件我们可以定位对应的CLAide项目使用版本是0.9.1。

    找到CLAide项目下的Command类的run方法:

    def self.run(argv = [])
      plugin_prefixes.each do |plugin_prefix|
        PluginManager.load_plugins(plugin_prefix)
      end
          
      argv = ARGV.coerce(argv)
      command = parse(argv)
      ANSI.disabled = !command.ansi_output?
      unless command.handle_root_options(argv)
        command.validate!
        command.run
      end
      rescue Object => exception
        handle_exception(command, exception)
      end
    end
    

    我们可以发现此处主要任务是根据插件前缀预加载插件到管理器中, 通过ARGV类去转换生产一个参数对象, 然后通过参数对象放置转换函数中转换成为一个实际Command类并执行。

    我们跟踪Command类中的parse方法, 可以发现parse方法取了参数中的第一个参数去调用[find_subcommand], 即用``install`去匹配command的名字, command属性的定义中描述如下:

    @return [String] The name of the command. Defaults to a snake-cased

    那么接下来的任务就是寻找类名为install子类的run方法了。赶紧在CoocaPods主工程中搜索install.rb这个文件,额。。没有找到。。这个源码跟踪的就断线了, 莫非一定要用Debug的方式去跟踪?

    既然直接搜名字不行, 那就试试用别的途径, 从前面的跟踪推测Install总得是个Command的子类吧, 那我们尝试用Install < Command去搜索。Pingo!! Install类果然被我们找到了, 藏在了project.rb文件下, 原来CocoaPods的开发者们把Install和Update类都放置在了project.rb中了。不多说,继续上代码:

    class Install < Command
      include Project
      # ...
      def run
        verify_podfile_exists!
        run_install_with_update(false)
      end
    end
    

    Install类的run方法终于进入到了前面初探的执行步骤了哇, 前面这么一大堆行为都是为了处理命令哇。

    到这里我们可以猜想一下Update类的run方法是否就是在调用run_install_with_update方法时候传入参数的不同呢? 哈哈, 其实不是, Update类的run方法还要检查是否所有的pods都被install过了, 如果没有的话会主动抛错, 检查通过的才会调用run_install_with_update方法。

    verify_podfile_exists在这里就不做赘述, 我们直接进入run_install_with_update方法。

    def run_install_with_update(update)
      installer = Installer.new(config.sandbox, config.podfile, config.lockfile)
      installer.update = update
      installer.install!
    end
    

    这个大家可能会有个困惑: config是什么时候加载的呢? 这个大家可以自己去发掘喔~ 不然本文就要改名为CocoaPods源码分析之一二三四了。

    run_install_with_update的核心代码是调用了Installer类的install方法, 继续贴代码:

    def install!
      prepare
      resolve_dependencies
      download_dependencies
      determine_dependency_product_types
      verify_no_duplicate_framework_names
      verify_no_static_framework_transitive_dependencies
      verify_framework_usage
      generate_pods_project
      integrate_user_project if config.integrate_targets?
      perform_post_install_actions
    end
    

    哇塞, 我要找所有东西都几乎全列在这个方法里了, 大致的行为动作如下哈, 我会每一个都粗略的介绍一番。

    背后那点事

    通过签名的代码跟踪,我们可以总结出pod install命令背后执行了这么十件大事:

    1. 准备工作
    2. 查找依赖库
    3. 下载依赖库
    4. 决定依赖库的类型
    5. 验证没有重名的framework
    6. 验证静态库的传递依赖
    7. 验证framwoke的使用
    8. 生成工程
    9. 整合用户项目
    10. 执行install后的行为

    准备工作

    准备工作(prepare)主要做了以下事情:

    1. 沙盒的准备 - 一些文件以及目录的删除以及创建
    2. 确保Podfile指定的插件都已经安装(不然抛错)
    3. 迁移沙盒中部分文件(区分Pods版本迁移地址不同)
    4. 执行pre_install的Hook

    查找依赖库

    查找依赖库(resolve_dependencies
    )
    主要做了以下事情:

    1. 通过HookManager添加插件源
    2. 如果config的skip_repo_update参数没有设置的时候执行Analyzer类的update_repositories方法来更新本地索引库 (这里大家其实可以看出--no-repo-update的作用了吧)
    3. 验证Build Configurations参数的有效性
    4. 准备版本兼容的遗留问题处理(0.39.0.beta.4属于空方法)
    5. 清理沙盒

    下载依赖库

    下载依赖库(run_podfile_pre_install_hooks)做了如下事情:

    1. 准备沙盒文件访问器
    2. 下载安装Pods依赖库源文件
    3. 执行Pods依赖库的pre install的执行钩子
    4. 根据Config和Installers参数清理Pods的源文件

    决定依赖库的类型

    决定依赖库的类型(determine_dependency_product_types)方法的作用主要是预判断库的host_requires_frameworks存储在pod_target属性中给后续使用。 那么主要决定什么内容呢? 参考源码中的注释:

    Determines if the dependencies need to be built as dynamic frameworks or if they can be built as static libraries by checking for the Swift source presence.

    主要是判断库是否需要支持动态Framework以及是否可以被Swift使用过的静态库。

    验证没有重名的framework

    (verify_no_duplicate_framework_names)主要是验证了目标工程集合和Pods库工程没有命名冲突的Framework, 重点是检查了Framework的名字是否冲突; 如果冲突会抛出frameworks with conflicting names异常

    验证静态库的传递依赖

    (verify_no_static_framework_transitive_dependencies)检查了静态库里是否包含了引用的静态库, 形成传递依赖。静态库的传递依赖如果形成会主动抛出transitive dependencies that include static binaries异常。

    验证framework的使用

    (verify_framework_usage)检查是否引用了Switf书写的framework, 并且Podfile中没有指定use framework!。如果验证不通过, 主动抛出异常。

    生成工程

    (generate_pods_project
    )
    指定了八件任务, 执行分析过程相对比较复杂, 并且大部分执行动作都涉及CocoaPods依赖的另外一个工程Xcodeproj

    1. 准备Pods工程(prepare_pods_project)
    2. 安装文件引用(install_file_references)
    3. 安装库(install_libraries)
    4. 为Target设置依赖(set_target_dependencies)
    5. 执行pod项目的post install的钩子(run_podfile_post_install_hooks)
    6. 执行Project类的Save方法保存配置(write_pod_project)
    7. Pods工程配置共享依赖库的Target Scheme(share_development_pod_schemes)
    8. 修改Pods工程的LockFile文件(write_lockfiles)

    整合用户项目

    整合项目主要是依赖UserProjectIntegrator类的integrate方法, 该方法主要是做了两件事情:

    1. 负责创建xcode的workspace, 并整合所有的target到新的workspace中.
    2. 抛出Podfile空项目依赖和xcconfig是否被原有的xcconfig所覆盖依赖相关的警告。<font style='color:orange'>最常见的xcconfig override警告就是这里抛出来的哦</font>

    执行install后的行为

    install后的行为分为四段:

    1. unLock Pods库下的文件以便执行post install的钩子逻辑
    2. 执行Post Install的钩子逻辑
    3. 抛出签名执行收集的Spec废弃警告
    4. 重新锁定Lock Pods库下的文件防止用户误修改

    到目前为止, pod install背后的十件大事都粗略的介绍完了哈。

    总结

    本文从pod install作为入口, 跟踪CocoaPods的实现源码, 并粗略根据tag 0.39.0.beta.4的源码将pod install背后主要执行的十个任务罗列出来。通过源码跟踪, 能够更深入的去了解Pods背后的工作, 能够更轻易排查因为Pods使用产生的问题。

    另: 源码跟踪还是要自己动手更有效果哇, 因为篇幅问题, 更多细节会在后续文章中补全~~

    PS: 本人水平有限, 如果有错误的地方, 请及时指出纠正, 谢谢哇!

    PS: 转载请注明出处哦~~

    参考文献

    1. CocoaPods官方Docs
    2. Github CocoaPods源码库

    相关文章

      网友评论

        本文标题:pod install和pod update背后那点事

        本文链接:https://www.haomeiwen.com/subject/qmhikttx.html