编译前提与注意事项
对于源码的版本要与Xcode的swift版本一致,对于macOS的版本参照一下CI说明文档
提供两个宝藏网站以帮你查询你的Xcode与swift的version信息:
Xcode Releases
Swift Version
当前环境
terminal
MacOS Version: Sonoma 14.1 beta(23B5056e) (Apple M2)
Xcode Version: 15.0
python3 Version: 3.9.6
cmark Version: 0.30.3
ninja Version: 1.11.1(非必需)
sccache Version: 0.5.4(非必需)
编译过程
为项目创建文件夹
terminal
mkdir swift-project
cd swift-project
clone swift-5.9-RELEASE源码(推荐SSH方式,设置SSH请参考此链接)
C++
git clone --branch swift-5.9-RELEASE git@github.com:apple/swift.git swift
通过update-checkout脚本对编译swift依赖库进行clone
C++
utils/update-checkout --tag swift-5.9-RELEASE --clone-with-ssh
此步骤失败几率很大,原因大都是由于网络导致的,如果在公司编译,建议早晨或者晚上稍晚一些,尽量规避一些网络问题。此前在测试编译过swift-5.3.1出现过python2.7问题,但是如果编译swift-5.9则可以完全使用python3,目前还没有遇到关于python的错误。
update-checkout执行成功的结果如图:
如果安装了sccache
,将其运行。
C++
sccache --start-server
Sccache默认为10GB的缓存大小,与构建工件相比相对较小。您可以提高它,例如通过在dotfile中设置export SCCACHE_CACHE_SIZE="50G"
。有关更多详细信息,请参阅Sccache README。
通过Xcode方式build
sh
bblv at xiaoBtongxuedeMac-mini in ~/Desktop/swift-project/swift (tags/swift-5.9-RELEASE)
utils/build-script --swift-darwin-supported-archs="$(uname -m)" \
--release-debuginfo --debug-swift-stdlib \
--skip-ios --skip-watchos --skip-tvos \
--skip-early-swiftsyntax --skip-build-benchmarks \
--sccache --xcode
参数说明:
--swift-darwin-supported-archs
:设置构建平台,如果不设置,默认全平台构建
$(uname -m)
:获取当前mac的架构,我的mac为M2的arm64的架构
--release-debuginfo
:构建所有的内容RelWithDebInfo(包含debug和release)带有调试信息
--debug-swift-stdlib
: 编译带有调试信息的 Swift标准库stdlib --skip-ios --skip-watchos --skip-tvos
:跳过iOS、watchos、tvos相关内容
--skip-early-swiftsyntax
: 表示跳过earlyswiftsyntax
, 这个不加会编译出错 。
--skip-build-benchmarks
:跳过构建swift基准测试套件
--sccache
:使用缓存工具,当删除构建目录重新构建的时候提高构建速度
--xcode
:使用Xcode方式构建
build-script --help
: 更多参数请参考help
在之前我编译Swift-5.5.1-RELEASE的过程中,build-script过程编译成功大约需要
50G
的空间。如果编译失败,原因基本是参数传入的问题(推测是某些参数构建需要特定环境支持),根据所需选择适当的参数。如果只是想在本地运行一些,调试代码,对测试没有过多要求,上述参数是我验证最优解了。
此次编译Swift-5.9-Release,我将build生成的产物Cmark、LLVM、Swift的.xcodeproj都进行了编译,一共使用了113G的空间
build-script编译成功如下图:
Build之后的排错操作
我之前在编译成功的时候是直接运行/Users/bblv/Desktop/swift-project/build/Xcode-RelWithDebInfoAssert+stdlib-DebugAssert/swift-macosx-arm64/Swift.xcodeproj
这个xcodproj,然后会出现各种各样的问题,虽然不多,但是对于小白排查起来还是需要一些时间的。
小白都可以完成的不会报错的操作,一把直接出现 Build Succeed 的小锤子
顺序很重要!顺序很重要!顺序很重要!
- cmark-gfm.xcodeproj
- LLVM.xcodeproj
- Swift.xcodeproj
通过Xcode打开之后的操作都是一致的,点击Manually Manage Schemes
,千万不要选择Automatically Create Schemes
会生成大量的scheme,我们的目的只是探索Swift,自动生成的scheme只会增加电脑的负载。通过编译多个版本的源码的经验,请不要点。
因为我们选择手动,所以要创建Scheme,选择All_BUILD
Scheme,全量编译。把Build Configuration
改成RelWithDebInfo
上面的3个项目都要做一遍这个操作,到目前为止,最后的Swift.xcodeproj的build会直接一把过,刺不刺激!惊不惊喜!意不意外!
Cmark
LLVM
LLVM强调一下,在编译过程中会爆i386架构的问题,请你不用理会,虽然工程最终build以fail结束,但是Swift编译所需的arm64已经准备好了,这个是瑕疵,目前不会处理,在研究。
Swift
到目前为止,编译期间的操作全部完成。
创建Debug工程
Text
1、创建新的Target-->BBlvProbe
2、为BBlvProbe添加依赖ALL_BUILD(Build Phases -> Dependencies -> +)
3、打开BBlvProbe的Build Settings,设置ENABLE_HARDENED_RUNTIME的值为NO
4、打开BBlvProbe的Build Settings,设置Swift Compiler的Optimization Level为No Optimization
5、打开BBlvProbe的Build Settings,设置Apple Compiler的Optimization Level为None
设定第4、第5步的原因是上面将Build Configuration设置为了RelWithDebInfo
,用作动态调试。如果你使用的是默认的Debug那么就不需要设置了。
至此,所有关于环境设定相关都已经完成。
BBlvProbe-debug调试
目前源码在手总得干点什么吧,那我们就来探索一类的结构吧。 在BBlvProbe的main函数里面创建一个类,简单编写一些测试代码
Swift
//
// main.swift
// BBlvProbe
//
// Created by 小B同学 on 2023/10/12.
//
import Foundation
print("Hello, World!")
class BBLvHobby {
var hobby = "girl"
var name = "BBLv"
}
var p = BBLvPerson()
print(p)
我在Swift源码工程里面创建了一个macOS的Command Line Tool工程,在里面创建了一个BBLvHobby的class,通过初始化来了解class的数据结构
我用简单的源码来说明继承链
Swift
HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
//👇查找,找到HeapMetadata
using HeapMetadata = TargetHeapMetadata<InProcess>;
//👇查找,找到TargetHeapMetadata
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
};
//👇查找,找到TargetMetadata
template <typename Runtime>
struct TargetMetadata {
...
private:
/// The kind. Only valid for non-class metadata; getKind() must be used to get
/// the kind value.
StoredPointer Kind;
...
- 我通过给bt找到了class初始化的切入点swift_allocObject
- 在swift_allocObject中找到了刻意变量metadata,通过打印得知了metadata就是我定义的class
- 通过metadata的数据类型HeapMetadata向下追踪,找到了末班类型TargetHeapMetadata
- 通过追踪TargetHeapMetadata找到了class的最终数据结构,是结构体TargetMetadata
- TargetMetadata的主要数据结构我列出了Kind,这个是引用计数
通过上述对Swift源码的初步探索就可以了解到在Swift中的class的数据结构了,以及为什么推荐在Swift中尽量使用struct,在源码层面可以解释为struct在下层实现最终会转化为struct类型的TargetMetadata。后续还会更深入的探索class的数据结构,这里就不做过多解释了
总结
之前通过ninja构建过swift-5.2.4-RELEASE版本,通过vscode和lldb插件来调试过swift源码。相比之下对于iOS开发者来讲可能使用Xcode调试会更加的舒服。对于Xcode的使用也更加的娴熟。自定义一些类也更加的方便。虽然xcode有许多许多的问题,通过Xcode构建过swift-5.5.1-RELEASE版本,每次在动态调试的时候都会出现控制台log:
Swift
warning: LibswiftCore.dylib was compiled with optimization - stepping may behave oddly; variables may not be available
本次使用MacOS14以及Xcode15.0构建最新版的swift-5.9-RELEASE版本,是一次非常完美的构建,调试过程中未出现系统Log
至此关于swift-5.9-RELEASE版本相关的编译问题以及如何调试研究基本讲解透彻,新手小白可以直接上手。