如何阅读 Swift 标准库中的源码

做者:Ole Begemann,原文连接,原文日期:2016-10-28
译者:X140Yu;校对:walkingway;定稿:CMBhtml

在进行完 GYP 预处理后,阅读 Swift 标准库源码的最简单的一种方式是执行一次完整的 Swift 编译。(另外一种是写一小段 shell 脚本。能够看下面的更新)git

若是你想要开始阅读 Swift 源码,那它的标准库应该是首先开始阅读的地方。标准库中的代码是和每个使用 Swift 的开发者都息息相关的,若是你也曾经对某个 API 的表现和性能有过怀疑,那么直接阅读对应的源码会是解决问题最快的方式。github

标准库也是 Swift 项目中最容易接触的地方。其中一点理由是,它由 Swift 写的,而不是 C++。由于你天天都用它,因此对它的 API 也很是熟悉。这就意味着,在源码中找到你想要的那段代码不是特别困难。若是你只是没有目标随便看看,那么在源码中你可能会发现或者这块金子。shell

在哪里能找到标准库的源码?

标准库的代码在 stdlib/public/core,GitHub 上的 Swift 仓库中。你能够在里面找到全部 public types,protocols 和 functions。你能够在线或者把代码 clone 到本地阅读。可是,一个比较复杂的地方在于,大约 1/3 的文件都是 .swift.gyb 的后缀,若是你打开其中一个文件,例如:FixedPoint.swift.gyb(Int 类型被定义的地方),你会发现一种和 Swift 混合在一块儿的模版语言:GYB。macos

gyb 表明 Generate Your Boilerplate(生成你的样板文件)。 它是由 Swift 团队开发的预处理的一个玩意。若是须要编译十个很是类似的 Int,那就得把相同的代码复制粘贴十次。若是你打开某个 gyb 文件,你会发现其中大部分都是 Swift 的代码,可是也有一些是 Python 代码。这个预处理器在 Swift 的代码仓库中的 utils/gyb,虽然大部分的代码在 utils/gyb.pyswift

Brent Royal-Gordonxcode

咱们不但愿看到 太多的 GYB,而更想要 Swift 代码,由于它更具备表达性,可是如今,咱们不得不面对它们的混和。bash

处理 GYB

若是你只想要阅读源码(而不是向 Swift 贡献代码),GYB 则弊大于利。那么怎么来预处理这些文件呢?你能够直接运行 gyb 脚本,可是它依赖于一个被 build 脚本建立的特殊环境。最好的方式是执行一次完整的 Swift build。也许对于阅读源码来讲,build 一次可能会有点过了,可是我发现 build 之后,源码阅读起来会更容易一些。app

更新: Toni Suter 指出 gyb 的脚本只依赖于一个你能够更改的变量(64-bit 和 32-bit 差异),若是你只想要处理 gyb, 这个小脚本比完整编译一次 Swift 要好不少。函数

bash
#!/bin/bash
for f in `ls *.gyb`
do
    echo "Processing $f"
    name=${f%.gyb}
    ../../../utils/gyb -D CMAKE_SIZEOF_VOID_P=8 -o $name $f --line-directive ""
done

它会把全部的 .gyb 文件处理完毕后放到相同的位置并去掉 .gyb 后缀。去除 --line-directive "" 在处理完毕的文件中添加 source location 的注释(就像 Swift build 中处理的同样)。

从源码编译 Swift

环境的搭建能够阅读 Swift 仓库中的 readme。 若是在 Mac 机器上,按照下面的步骤进行操做(使用 Homebrew 安装各类依赖),可是别忘记检查这些步骤的正确性:

bash
# Install build tools
brew install cmake ninja
# Create base directory
mkdir swift-source
cd swift-source
# Clone Swift
git clone https://github.com/apple/swift.git
# Clone dependencies (LLVM, Clang, etc.)
./swift/utils/update-checkout --clone

最后一句命令会把 build Swift 须要的其它部分的 repo 给 clone 下来,好比 LLVM,Clang,LLDB 等等。就像对于 Linux 的 Foundation 和 libdispatch 模块同样。在这一步事后,你的 swift-source 文件夹看起来是这样:

bash
du -h -d 1
250M    ./clang
4,7M    ./cmark
 47M    ./compiler-rt
 15M    ./llbuild
197M    ./lldb
523M    ./llvm
221M    ./swift
 26M    ./swift-corelibs-foundation
7,8M    ./swift-corelibs-libdispatch
1,1M    ./swift-corelibs-xctest
316K    ./swift-integration-tests
960K    ./swift-xcode-playground-support
7,0M    ./swiftpm
1,3G    .

如今就能够开始运行 build 脚本了,它会先开始 build LLVM,而后构建 Swift:

bash
./swift/utils/build-script -x -R

参数是很重要的:

  • -x 会生成一个 Xcode project,你就能够在这个 project 中使用 Xcode 阅读源码了。

  • -R 表明 release 模式的编译。它会比 debug 模式更快,在我 2.6 GHz 的 core i7 2013 电脑上,25 分 vs 70 分。占用的空间也更少,2GB vs 24GB。

Orienting yourself

当 build 构建完成后,你能够在 ./build/Xcode-ReleaseAssert/swift-macosx-x86_64/ 的子文件夹中 swift-source 找到结果。其中会有一个 Swift.xcodeproj Xcode 项目,已经处理好的标准库代码在 ./stdlib/public/code/8/ 中。注意,这个文件夹中只有从 .gyb 文件处理过来的文件,以前以 .swift 结尾的文件还在原来的位置。

不幸的是, 在 Xcode 中使用 Open Quickly (⇧⌘O) 打不开特定的 API. 我一般使用 Find in Project (⇧⌘F) 来进行导航。若是你使用只出如今函数定义时的字符串来搜索,那就很容易搜索到了。好比要搜索 print 函数的定义,搜索 “func print(“ 而不是 “print”。

你也能够运行 swift REPL 或者 swiftc 编译器了。它们都在 ./Release/bin/ 中。若是还想测试一些以前在 release 中出现,可是在 master 中已经被修复的 bug,就会很方便。

更新

若是你想在之后更新本地的 clone,从新运行 update-checkout 脚本,而且 rebuild:

bash
./swift/utils/update-checkout
./swift/utils/build-script -x -R

这些都是增量编译,比第一次要快得多。

切换到指定的版本

若是你想要验证一个在生产环境中已经使用的 Swift 特定版本 API 的表现,你就须要查看那个版本的 Swift 代码,而不是当前 master 分支。可是简单地切换分支并不能解决问题,由于若是一些依赖的版本对不上的话,编译是会失败的。

update-checkout 脚本可以让你指定一个特定的 tag 或者 branch。它会帮你切换全部依赖的版本:

bash
# Either
./swift/utils/update-checkout --tag swift-3.0-RELEASE
# or
./swift/utils/update-checkout --scheme swift-3.0-branch

swift-3.0-RELEASE tag 和 swift-3.0-branch branch 的区别是,tag 至关于一个 mailstone,表明 Swift 的某个特定的 release 分支。然而分支是会伴随着 bug 修复和功能改进不断更新的。如今来看,在官方 release Xcode 8.1 的 Swift 3.0.1 时,swift-3.0-branch 分支已经包含了一些 Swift 3.0.2 中的修复。

不幸的是,我发现 update-checkout --scheme 的命令很是脆弱(--tag 在我看来能好一些)。这个脚本会对代码进行 rebase 操做,而且切换到指定的分支,这会在子项目中带来合并冲突,然而我并无对代码做出任何更改。我不明白为何这个脚本会这样。

本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 http://swift.gg