关于ios:1-版本管理工具及-Ruby-工具链环境

7次阅读

共计 9129 个字符,预计需要花费 23 分钟才能阅读完成。

CocoaPods 历险记 这个专题是 Edmond 冬瓜 是对于 iOS / macOS 工程中版本管理工具 CocoaPods 的实现细节、原理、源码、实际与教训的分享记录,旨在帮忙大家可能更加理解这个依赖管理工具,而不仅局限于 pod installpod update

本文常识目录

背景

CocoaPods 作为业界规范,各位 iOS 开发同学应该都不生疏。不过很多同学对 CocoaPods 的应用根本停留在 pod installpod update 上。一旦我的项目组件化,各业务线逻辑拆分到独立的 Pod 中后,光理解几个简略 Pod 命令是无奈满足需要的,同时还面临开发环境的一致性,Pod 命令执行中的各种异样谬误,都要求咱们对其有更深层的认知和 ????。

对于 CocoaPods 深刻的文章有很多,举荐 ObjC China 的这篇,深刻了解 CocoaPods,而本文心愿从依赖管理工具的角度来谈谈 CocoaPods 的治理理念。

Version Control System (VCS)

Version control systems are a category of software tools that help a software team manage changes to source code over time. Version control software keeps track of every modification to the code in a special kind of database.

软件工程中,版本控制系统是麻利开发的重要一环,为后续的继续集成提供了保障。Source Code Manager (SCM) 源码治理就属于 VCS 的范畴之中,熟知的工具有如 Git。而 CocoaPods 这种针对各种语言所提供的 Package Manger (PM)也能够看作是 SCM 的一种。

而像 GitSVN 是针对我的项目的单个文件的进行版本控制,而 PM 则是以每个独立的 Package 作为最小的治理单元。包管理工具都是联合 SCM 来实现管理工作,对于被 PM 接管的依赖库的文件,通常会在 Git.ignore 文件中抉择疏忽它们。

例如:在 Node 我的项目中个别会把 node_modules 目录下的文件 ignore 掉,在 iOS / macOS 我的项目则是 Pods

Git Submodule

Git submodules allow you to keep a git repository as a subdirectory of another git repository. Git submodules are simply a reference to another repository at a particular snapshot in time. Git submodules enable a Git repository to incorporate and track version history of external code.

Git Submodules 能够算是 PM 的“青春版”,它将独自的 git 仓库以子目录的模式嵌入在工作目录中。它不具备 PM 工具所特有的语义化版本治理、无奈解决依赖共享与抵触等。只能保留每个依赖仓库的文件状态。

Git 在提交更新时,会对所有文件制作一个快照并将其存在数据库中。Git 治理的文件存在 3 种状态:

  • working director: 工作目录,即咱们肉眼可见的文件
  • stage area: 暂存区 (或称 index area ),存在 .git/index 目录下,保留的是执行 git add 相干命令后从工作目录增加的文件。
  • commit history: 提交历史,存在 .git/ 目录下,到这个状态的文件改变算是入库胜利,根本不会失落了。

Git submodule 是依赖 .gitmodules 文件来记录子模块的。

[submodule "ReactNative"]
    path = ReactNative
    url = https://github.com/facebook/ReactNative.git

.gitmodules 仅记录了 path 和 url 以及模块名称的根本信息,然而咱们还须要记录每个 Submodule Repo 的 commit 信息,而这 commit 信息是记录在 .git/modules 目录下。同时被增加到 .gitmodules 中的 path 也会被 git 间接 ignore 掉。

Package Manger

作为 Git Submodule 的强化版,PM 根本都具备了语义化的版本查看能力,依赖递归查找,依赖抵触解决,以及针对具体依赖的构建能力和二进制包等。简略比照如下:

Key File Git submodule CocoaPods SPM npm
形容文件 .gitmodules Podfile Package.swift Package.json
锁存文件 .git/modules Podfile.lock Package.resolved package-lock.json

从 ???? 可见,PM 工具根本围绕这个两个文件来事实包治理:

  • 形容文件:申明了我的项目中存在哪些依赖,版本限度;
  • 锁存文件(Lock 文件):记录了依赖包最初一次更新时的全版本列表。

除了这两个文件之外,中心化的 PM 个别会提供依赖包的托管服务,比方 npm 提供的 npmjs.com 能够集中查找和下载 npm 包。如果是去中心化的 PM 比方 iOSCarthageSPM 就只能通过 Git 仓库的地址了。

CocoaPods

CocoaPods  是开发 iOS/macOS 应用程序的一个第三方库的依赖管理工具。 利用 CocoaPods,能够定义本人的依赖关系(简称 Pods),以及在整个开发环境中对第三方库的版本治理十分不便。

上面咱们以 CocoaPods 为例。

Podfile

Podfile 是一个文件,以 DSL(其实间接用了 Ruby 的语法)来形容依赖关系,用于定义我的项目所须要应用的第三方库。该文件反对高度定制,你能够依据集体爱好对其做出定制。更多相干信息,请查阅 Podfile 指南。

Podfile.lock

这是 CocoaPods 创立的最重要的文件之一。它记录了须要被装置的 Pod 的每个已装置的版本。如果你想晓得已装置的 Pod 是哪个版本,能够查看这个文件。举荐将 Podfile.lock 文件退出到版本控制中,这有助于整个团队的一致性。

Manifest.lock

这是每次运行 pod install 命令时创立的 Podfile.lock 文件的正本。如果你遇见过这样的谬误 沙盒文件与 Podfile.lock 文件不同步 (The sandbox is not in sync with the Podfile.lock),这是因为 Manifest.lock 文件和 Podfile.lock 文件不统一所引起。因为 Pods 所在的目录并不总在版本控制之下,这样能够保障开发者运行 App 之前都能更新他们的 Pods,否则 App 可能会 crash,或者在一些不太显著的中央编译失败。

Master Specs Repo

Ultimately, the goal is to improve discoverability of, and engagement in, third party open-source libraries, by creating a more centralized ecosystem.

作为包管理工具,CocoaPods 的指标是为咱们提供一个更加集中的生态系统,来进步依赖库的可发现性和参与度。实质上是为了提供更好的检索和查问性能,惋惜成为了它的问题之一。因为 CocoaPods 通过官网的 Spec 仓库来治理这些注册的依赖库。随着一直新增的依赖库导致 Spec 的更新和保护成为了使用者的包袱。

好在这个问题在 1.7.2 版本中曾经解决了,CocoaPods 提供了 Mater Repo CDN,能够间接 CDN 到对应的 Pod 地址而无需在通过本地的 Spec 仓库了。同时在 1.8 版本中,官网默认的 Spec 仓库已替换为 CDN,其地址为  https://cdn.cocoapods.org。

Ruby 生态及工具链

对于一部分仅接触过 CocoaPods 的同学,其 PM 可能并不相熟。其实 CocoaPods 的思维借鉴了其余语言的 PM 工具,例:RubyGems, Bundler, npmGradle

咱们晓得 CocoaPods 是通过 Ruby 语言实现的。它自身就是一个 Gem 包。了解了 Ruby 的依赖治理有助于咱们更好的治理不同版本的 CocoaPods 和其余 Gem。同时可能保障团队中的所有共事的工具是在同一个版本,这也算是麻利开发的保障吧。

RVM & rbenv

RVMrbenv 都是治理多个 Ruby 环境的工具,它们都能提供不同版本的 Ruby 环境治理和切换。

具体哪个更好要看集体习惯。 当然 rbenv 官网是这么说的 Why rbenv。本文后续的试验也都是是应用 rbenv 进行演示。

RubyGems

The RubyGems software allows you to easily download, install, and use ruby software packages on your system. The software package is called a“gem”which contains a packaged Ruby application or library.

RubyGems 是 Ruby 的一个包管理工具,这外面治理着用 Ruby 编写的工具或依赖咱们称之为 Gem。

并且 RubyGems 还提供了 Ruby 组件的托管服务,能够集中式的查找和装置 library 和 apps。当咱们应用 gem install xxx 时,会通过 rubygems.org 来查问对应的 Gem Package。而 iOS 日常中的很多工具都是 Gem 提供的,例:BundlerfastlanejazzyCocoaPods 等。

在默认状况下 Gems 总是下载 library 的最新版本,这无奈确保所装置的 library 版本合乎咱们预期。因而咱们还缺一个工具。

Bundler

Bundler 是治理 Gem 依赖的工具,能够隔离不同我的项目中 Gem 的版本和依赖环境的差别,也是一个 Gem。

Bundler 通过读取我的项目中的依赖形容文件 Gemfile,来确定各个 Gems 的版本号或者范畴,来提供了稳固的应用环境。当咱们应用 bundle install 它会生成 Gemfile.lock 将以后 librarys 应用的具体版本号写入其中。之后,别人再通过 bundle install 来装置 libaray 时则会读取 Gemfile.lock 中的 librarys、版本信息等。

Gemfile

能够说 CocoaPods 其实是 iOS 版的 RubyGems + Bundler 组合。Bundler 根据我的项目中的 Gemfile 文件来治理 Gem,而 CocoaPods 通过 Podfile 来治理 Pod。

Gemfile 配置如下:

source 'https://gems.example.com' do
  gem 'cocoapods', '1.8.4' 是治理 Gem 依赖的工具
  gem 'another_gem', :git => 'https://looseyi.github.io.git', :branch => 'master'
end

可见,Podfile 的 DSL 写法和 Gemfile 一模一样。那什么状况会用到 Gemfile 呢?

CocoaPods 每年都会有一些重大版本的降级,后面聊到过 CocoaPodsinstall 过程中会对我的项目的 .xcodeproj 文件进行批改,不同版本其有所不同,这些在变更都可能导致大量 conflicts,解决不好,我的项目就不能失常运行了。我想你肯定不违心去批改 .xcodeproj 的抵触。

如果我的项目是基于 fastlane 来进行继续集成的相干工作以及 App 的打包工作等,也须要其版本治理等性能。

如何装置一套可管控的 Ruby 工具链?

讲完了这些工具的分工,而后来说说理论的使用。咱们能够应用 homebrew + rbenv + RubyGems + Bundler 这一整套工具链来管制一个工程中 Ruby 工具的版本依赖。

以下是我认为比拟可控的 Ruby 工具链分层治理图。上面咱们逐个讲述每一层的治理形式,以及理论的操作方法。

1. 应用 homebrew 装置 rbenv

$ brew install rbenv

装置胜利后输出 rbenv  就能够看到相干提醒:

$ rbenv

rbenv 1.1.2
Usage: rbenv <command> [<args>]

Some useful rbenv commands are:
   commands    List all available rbenv commands
   local       Set or show the local application-specific Ruby version
   global      Set or show the global Ruby version
   shell       Set or show the shell-specific Ruby version
   install     Install a Ruby version using ruby-build
   uninstall   Uninstall a specific Ruby version
   rehash      Rehash rbenv shims (run this after installing executables)
   version     Show the current Ruby version and its origin
   versions    List installed Ruby versions
   which       Display the full path to an executable
   whence      List all Ruby versions that contain the given executable

See `rbenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/rbenv/rbenv#readme

2. 应用 rbenv 治理 Ruby 版本

应用 rbenv  来装置一个 Ruby 版本,这里我应用刚刚 release Ruby 2.7:

$ rbenv install 2.7.0

这个装置过程有些长,因为要下载 openssl 和 Ruby 的解释器,大略要 20 分钟左右。

装置胜利后,咱们让其在本地环境中失效:

$ rbenv shell 2.7.0

输出上述命令后,可能会有报错。rbenv  提醒咱们在 .zshrc  中减少一行 eval "$(rbenv init -)"  语句来对 rbenv  环境进行初始化。如果报错,咱们减少并重启终端即可。

$ ruby --version
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]
$ which ruby
/Users/gua/.rbenv/shims/ruby

切换之后咱们发现 Ruby 曾经切换到 rbenv  的治理版本,并且其启动 PATH  也曾经变成 rbenv  治理下的 Ruby。并且咱们能够看一下 Ruby 捆绑的 Gem  的 PATH

$ which gem
/Users/bytedance/.rbenv/shims/gem

对应的 Gem  也曾经变成 rbenv  中的 PATH

3. 查问零碎级 Gem 依赖

如此,咱们应用 rbenv 曾经对 Ruby 及其 Gem  环境在版本上进行了环境隔离。咱们能够通过 gem list  命令来查问以后零碎环境下所有的 Gem  依赖:

$ gem list

*** LOCAL GEMS ***

activesupport (4.2.11.3)
...
claide (1.0.3)
cocoapods (1.9.3)
cocoapods-core (1.9.3)
cocoapods-deintegrate (1.0.4)
cocoapods-downloader (1.3.0)
cocoapods-plugins (1.0.0)
cocoapods-search (1.0.0)
cocoapods-stats (1.1.0)
cocoapods-trunk (1.5.0)
cocoapods-try (1.2.0)

记住这里的 CocoaPods 版本,咱们前面我的项目中还会查问。

如此咱们曾经实现了全副的 Ruby、Gem 环境的配置,咱们通过一张漏斗图再来梳理一下:

如何应用 Bundler 治理工程中的 Gem 环境

上面咱们来实际一下,如何应用 Bundler 来锁定我的项目中的 Gem 环境,从而让整个团队对立 Gem 环境中的所有 Ruby 工具版本。从而防止文件抵触和不必要的谬误。

上面是在工程中对于 Gem 环境的层级图,咱们能够在我的项目中减少一个 Gemfile 形容,从而锁定以后我的项目中的 Gem 依赖环境。

以下也会逐个讲述每一层的治理形式,以及理论的操作方法。

1. 在 iOS 工程中初始化 Bundler 环境

首先咱们有一个 iOS Demo 工程 – GuaDemo

$ ls -al
total 0
drwxr-xr-x   4 gua  staff   128 Jun 10 14:47 .
drwxr-xr-x@ 52 gua  staff  1664 Jun 10 14:47 ..
drwxr-xr-x   8 gua  staff   256 Jun 10 14:47 GuaDemo
drwxr-xr-x@  5 gua  staff   160 Jun 10 14:47 GuaDemo.xcodeproj

首先先来初始化一个 Bundler  环境(其实就是主动创立一个 Gemfile 文件):

$ bundle init

Writing new Gemfile to /Users/Gua/GuaDemo/Gemfile

2. 在 Gemfile  中申明应用的 CocoaPods 版本并装置

之后咱们编辑一下这个 Gemfile 文件,退出咱们以后环境中须要应用 CocoaPods 1.5.3 这个版本,则应用 Gemfile  的 DSL 编写以下内容:

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# gem "rails"
gem "cocoapods", "1.5.3"

编写之后执行一下 bundle install

$ bundle install
Fetching gem metadata from https://gems.ruby-china.com/............
Resolving dependencies...
...
Fetching cocoapods 1.5.3
Installing cocoapods 1.5.3
Bundle complete! 1 Gemfile dependency, 30 gems now installed.

发现 CocoaPods 1.5.3  这个指定版本曾经装置胜利,并且还保留了一份 Gemfile.lock  文件用来锁存这次的依赖后果。

3. 应用以后环境下的 CocoaPods 版本操作 iOS 工程

此时咱们能够检查一下以后 Bundler 环境下的 Gem  列表:

$ bundle exec gem list

*** LOCAL GEMS ***

activesupport (4.2.11.3)
atomos (0.1.3)
bundler (2.1.4)
CFPropertyList (3.0.2)
claide (1.0.3)
cocoapods (1.5.3)
...

发现相比于全局 Gem 列表,这个列表精简了许多,并且也只是根底 Gem 依赖和 CocoaPodsGem 依赖。此时咱们应用 bundle exec pod install  来执行 Install 这个操作,就能够应用 CocoaPods 1.5.3  版本来执行 Pod  操作了(当然,前提是你还须要写一个 Podfile,大家都是 iOSer 这里就略过了)。

$ bundle exec pod install
Analyzing dependencies
Downloading dependencies
Installing SnapKit (5.0.1)
Integrating client project
[!] Please close any current Xcode sessions and use `GuaDemo.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

能够再来看一下 Podfile.lock  文件:

cat Podfile.lock
PODS:
  - SnapKit (5.0.1)

DEPENDENCIES:
  - SnapKit (~> 5.0.0)

SPEC REPOS:
  https://github.com/cocoapods/specs.git:
    - SnapKit

SPEC CHECKSUMS:
  SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb

PODFILE CHECKSUM: 1a4b05aaf43554bc31c90f8dac5c2dc0490203e8

COCOAPODS: 1.5.3

发现应用的 CocoaPods 的版本的确是 1.5.3。而当咱们不应用 bundle exec  执行前缀,则会应用零碎环境中的 CocoaPods 版本。如此咱们也就验证了工程中的 Gem 环境和零碎中的环境能够通过 Bundler 进行隔离。

总结

  • 通过版本管理工具演进的角度能够看出,CocoaPods 的诞生并非欲速不达,也是一直地借鉴其余管理工具的长处,一点点的倒退起来的。VCS 工具从晚期的 SVNGit,再细分出 Git Submodule,再到各个语言的 Package Manager 也是始终在倒退的。
  • 尽管 CocoaPods 作为包管理工具管制着 iOS 我的项目的各种依赖库,但其本身同样遵循着严格的版本控制并一直迭代。心愿大家能够从本文中意识到版本治理的重要性。
  • 通过实操 Bundler 治理工程的全流程,学习了 Bundler 根底,并学习了如何管制一个我的项目中的 Gem 版本信息。

后续咱们将会围绕 CocoaPods,从工具链逐步深刻到细节,依据咱们的应用教训,逐个解说。

知识点问题梳理

这里列举了四个问题用来考查你是否曾经把握了这篇文章,如果没有倡议你退出 珍藏 再次浏览:

  1. PM 是如何进行依赖库的版本治理?
  2. RubyRVM/rbenv 之间的关系是什么?
  3. GemBundlerCocaPods 之间的关系是什么?
  4. 如何通过 Bundler 来治理工程中的 Gem 环境?如何锁死工程外部的 CocoaPods 版本?
正文完
 0