
在本文中,我将告诉您程序包管理器在内部结构,操作算法上有哪些相似之处,以及它们之间的根本区别是什么。 我查看了专为在iOS / OS X下进行开发而设计的程序包管理器,但是本文的内容带有一些假设,而其他情况也适用。
各种依赖管理器
- 系统依赖性管理器-在操作系统中安装缺少的实用程序。 例如, Homebrew 。
- 语言依赖管理器-将以一种编程语言编写的源代码收集到最终的可执行程序中。 例如, 转到build 。
- 项目依赖性管理器-在特定项目的上下文中管理依赖性。 也就是说,他们的任务包括描述依赖关系,下载,更新其源代码。 这些是例如Cocoapods 。
他们之间的主要区别是他们“服务谁”。 为用户提供系统MH,为开发人员提供项目MH,并为两者提供语言MH。
接下来,我将考虑项目依赖管理器-我们最常使用它们,而且它们更易于理解。
使用依赖项管理器时的项目方案
考虑一下流行的包装
经理 Cocoapods。
通常,我们执行条件
pod install命令,然后依赖项管理器会为我们做所有事情。 考虑项目应该包括什么,以使该团队成功完成。

- 在我们的代码中,我们使用了这种依赖性,即Alamofire库。
- 从清单文件中,依赖项管理器知道我们在源代码中使用了哪些依赖项。 如果我们忘记指出那里的任何库,则将不会建立依赖关系,并且最终将不会组装项目。
- 锁定文件-由依赖项管理器生成的某种格式的文件,列出了成功安装在项目中的所有依赖项。
- 依赖关系代码是依赖关系管理器“提取”的外部源代码,将从我们的代码中调用它。
没有在依赖项安装命令之后每次都运行的特定算法,这将是不可能的。
依次列出所有4个组件,例如 下一个组件是在前一个组件的基础上形成的。

并非所有的依赖项管理器都具有全部4个组件,但是考虑到依赖项管理器的功能,最好是全部存在。
安装依赖项后,根据语言,所有4个组件都进入编译器或解释器的输入。

我还注意到,开发人员负责前两个组件-我们编写此代码,其余两个组件负责依赖管理器-它生成文件并下载依赖项的源代码。

依赖管理器工作流程
随着组件的整理或多或少,现在让我们继续进行MOH的算法部分。
典型的工作算法如下所示:
- 验证项目和环境。 名为分析器的对象对此负责。
- 建立图。 在依赖关系中,卫生部应绘制图表。 解析器对象执行此操作。
- 下载依赖项。 显然,必须下载依赖项的源代码,以便我们在源代码中使用它。
- 依赖关系集成。 依赖项的源代码位于磁盘上的相邻目录中这一事实可能还不够,因此仍需要将它们附加到我们的项目中。
- 依赖关系更新。 步骤4之后不会立即执行此步骤,但如有必要,请升级到新版本的库。 这里有一些特殊之处,所以我在一个单独的步骤中将它们选出来-稍后再详细介绍。
验证项目和环境
验证包括检查OS版本,依赖项管理器所需的辅助实用程序,以及链接项目设置和清单文件:从语法检查到不兼容的设置。
样本
podfilesource 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/RedMadRobot/cocoapods-specs' platform :ios, '10.0' use_frameworks! project 'Project.xcodeproj' workspace 'Project.xcworkspace' target 'Project' do project 'Project.xcodeproj' pod 'Alamofire' pod 'Fabric' pod 'GoogleMaps' end
检查Podfile时可能的警告和错误:
- 在任何规范存储库中都没有找到依赖项;
- 没有明确指定操作系统和版本;
- 无效的工作空间或项目名称。
建立依赖图
由于我们项目所需的依赖项可能具有自己的依赖项,而这些依赖项又可能具有自己的嵌套依赖项或子依赖项,因此管理器使用了正确的版本。 从原理上讲,所有依赖关系都应在有
向无环图中对齐 。

有向无环图的构造减少了拓扑排序的问题。 她有几种决策算法。
- 卡恩算法 -顶点枚举,复杂度O(n)。
- Tarjan的算法 -基于深度搜索,复杂度O(n)。
- Demucron的算法是分层图分区。
- 使用多项式处理器的并行算法。 在这种情况下,复杂度将“降至” O(log(n)^ 2)
任务本身是NP完成的;在编译器和机器学习中使用相同的算法。
解决方案的结果是创建了锁定文件,该文件完整描述了依赖关系之间的关系。

该算法工作时可能会出现什么问题? 考虑一个示例:有一个项目具有依赖项A,B,E和嵌套的依赖项C,F,D。

依赖项A和B具有共同的依赖项C。这里,C必须满足依赖项A和B的要求。某些依赖项管理器允许在必要时安装单独的版本,但例如cocoapods则不允许。 因此,在需求不兼容的情况下:A要求版本等于C依赖性的2.0,而B要求版本1.0,则安装将失败。 并且,如果依赖项A需要版本1.0或更高版本至版本2.0,并且依赖项B版本1.2或更低版本至1.0,则将安装A和B版本1.2的最兼容版本。 不要忘记,即使不是直接发生循环依赖的情况,在这种情况下,安装也会失败。

让我们看看它在iOS最受欢迎的依赖管理器的代码中的外观。
迦太基
typealias DependencyGraph = [Dependency: Set<Dependency>] public enum Dependency { /// A repository hosted on GitHub.com or GitHub Enterprise. case gitHub(Server, Repository) /// An arbitrary Git repository. case git(GitURL) /// A binary-only framework case binary(URL) } /// Protocol for resolving acyclic dependency graphs. public protocol ResolverProtocol { init( versionsForDependency: @escaping (Dependency) -> SignalProducer<PinnedVersion, CarthageError>, dependenciesForDependency: @escaping (Dependency, PinnedVersion) -> SignalProducer<(Dependency, VersionSpecifier), CarthageError>, resolvedGitReference: @escaping (Dependency, String) -> SignalProducer<PinnedVersion, CarthageError> ) func resolve( dependencies: [Dependency: VersionSpecifier], lastResolved: [Dependency: PinnedVersion]?, dependenciesToUpdate: [String]? ) -> SignalProducer<[Dependency: PinnedVersion], CarthageError> }
Resolver的实现在
这里 ,NewResolver在
这里 ,Analyzer不在。
椰子足
图构造算法的实现被分配给一个独立的
仓库 。 这是
图和
解析器的实现 。 在
分析器中,您可以找到它检查系统的cocoapods版本和锁定文件的一致性。
def validate_lockfile_version! if lockfile && lockfile.cocoapods_version > Version.new(VERSION) STDERR.puts '[!] The version of CocoaPods used to generate ' \ "the lockfile (#{lockfile.cocoapods_version}) is "\ "higher than the version of the current executable (#{VERSION}). " \ 'Incompatibility issues may arise.'.yellow end end
从源头还可以看到,Analyzer为依赖关系生成了目标。
一个典型的cocoapods锁定文件如下所示:
PODS: - Alamofire (4.7.0) - Fabric (1.7.5) - GoogleMaps (2.6.0): - GoogleMaps/Maps (= 2.6.0) - GoogleMaps/Base (2.6.0) - GoogleMaps/Maps (2.6.0): - GoogleMaps/Base SPEC CHECKSUMS: Alamofire: 907e0a98eb68cdb7f9d1f541a563d6ac5dc77b25 Fabric: ae7146a5f505ea370a1e44820b4b1dc8890e2890 GoogleMaps: 42f91c68b7fa2f84d5c86597b18ceb99f5414c7f PODFILE CHECKSUM: 5294972c5dd60a892bfcc35329cae74e46aac47b COCOAPODS: 1.4.0
PODS部分列出了指示版本的直接和嵌套依赖项,然后分别并一起计算它们的校验和,并指示用于安装的可可足类动物的版本。
下载依赖项
成功构建图形并创建锁定文件后,依赖关系管理器将继续下载它们。 不一定是源代码,也可以是可执行文件或编译框架。 同样,所有依赖项管理器通常都支持在本地路径上安装的功能。

从链接下载它们没有什么复杂的事情(当然,您需要从某个地方下载),因此我不会告诉您下载本身是如何发生的,而是关注集中化和安全性问题。
集中化
简单来说,依赖性管理器在下载依赖性时有两种方法:
- 转到一些可用依赖项列表,并按名称获取下载链接。
- 我们必须在清单文件中为每个依赖项明确指定源。
集中式依赖管理器沿着第一个路径,去中心化沿着第二个路径。

安全性
如果通过https或ssh下载依赖项,则可以安然入睡。 但是,开发人员通常会提供指向其官方库的http链接。 当攻击者欺骗源代码,可执行文件或框架时,我们可能会遇到
中间人攻击。 有些依赖项管理器不受此保护,有些则按以下方式进行保护。
家酿
检查旧版OS X的
卷曲 。
def check_for_bad_curl return unless MacOS.version <= "10.8" return if Formula["curl"].installed? <<~EOS The system curl on 10.8 and below is often incapable of supporting modern secure connections & will fail on fetching formulae. We recommend you: brew install curl EOS end
通过http下载时,还会进行
SHA256哈希检查 。
def curl_http_content_headers_and_checksum(url, hash_needed: false, user_agent: :default) max_time = hash_needed ? "600" : "25" output, = curl_output( "--connect-timeout", "15", "--include", "--max-time", max_time, "--location", url, user_agent: user_agent ) status_code = :unknown while status_code == :unknown || status_code.to_s.start_with?("3") headers, _, output = output.partition("\r\n\r\n") status_code = headers[%r{HTTP\/.* (\d+)}, 1] end output_hash = Digest::SHA256.digest(output) if hash_needed { status: status_code, etag: headers[%r{ETag: ([wW]\/)?"(([^"]|\\")*)"}, 2], content_length: headers[/Content-Length: (\d+)/, 1], file_hash: output_hash, file: output, } end
您还可以禁用对http的不安全重定向(
HOMEBREW_NO_INSECURE_REDIRECT变量)。
迦太基和可可足
这里的一切都比较简单-
您不能在可执行文件上
使用 http。
guard binaryURL.scheme == "file" || binaryURL.scheme == "https" else { return .failure(BinaryJSONError.nonHTTPSURL(binaryURL)) }
def validate_source_url(spec) return if spec.source.nil? || spec.source[:http].nil? url = URI(spec.source[:http]) return if url.scheme == 'https' || url.scheme == 'file' warning('http', "The URL (`#{url}`) doesn't use the encrypted HTTPs protocol. " \ 'It is crucial for Pods to be transferred over a secure protocol to protect your users from man-in-the-middle attacks. '\ 'This will be an error in future releases. Please update the URL to use https.') end
完整代码
在这里 。
迅捷包管理器
目前,未找到与安全性相关的任何信息,但是在开发建议中,仅简短提及
了使用证书
对软件包
进行签名
的机制 。
依赖整合
通过集成,我的意思是将依赖项连接到项目,以便我们可以自由使用它们,并使用主应用程序代码对其进行编译。
集成可以是手动(迦太基)或自动(Cocoapods)。 自动操作的优点是开发人员的手势很少,但是可以向项目添加很多魔术。
例如,在使用手册的情况下,您可以按照
此迦太基指令完全控制将依赖项添加到项目的过程。 可靠,但是更长。
依赖关系更新
您可以使用其版本控制项目中依赖项的源代码。
依赖项管理器使用3种方法:
- 库版本。 最方便,最常用的方式。 您可以指定特定版本和间隔。 只要作者正确地对库进行了版本控制,这是支持依赖项兼容性的一种完全可预测的方法。
- 分公司 在更新分支并进一步更新依赖项时,我们无法预测会发生什么变化。
- 提交或标记。 当执行update命令时,带有指向特定提交或标记(如果未更改)链接的依赖关系将永远不会更新。
结论
在本文中,我对依赖项管理器的内部结构有一个肤浅的理解。 如果您想了解更多信息,则应深入研究软件包管理器的源代码。 查找以熟悉的语言编写的内容的最简单方法。 所描述的方案是典型的,但在特定的依赖项管理器中,可能会丢失某些内容,或者相反,可能会出现新的内容。
欢迎发表评论并在评论中进行讨论。