2020/5/27更新:大家现在可以直接从Saleem Abdulrasool的Azure Pipeline中下载打包好的Swift Windows SDK了,在他的GitHub中找到swift-build项目,里面的CI链接里找到Artifacts就可以直接下载(类似windows-toolchain-amd64.msi这样的文件)。考虑到5.3版本的Swift将支持Windows平台,因此以下文章也许已经成为历史,大家看看就好了。
----------------------------------------- 以下是原文 -----------------------------------------
大家五一节快乐。五一这两天杭州非常的热,吉祥宅在家里也不想开发引擎,便换了个模式,了解了一下由Apple于2014年推出的Swift语言。作者身边大多数人对Swift了解得并不多,对其的印象其实和ObjC差不多:由Apple开发,且只能用在Apple家族的设备上。作者在之前也一直抱有这个想法,但是这一次仔细地研究了一下,发现并不是这样。事实上,Swift是一门完全开源免费的语言,以Apache 2.0(包括运行库例外)协议授权,并且和微软的C#一样提供跨平台支持,并不是只能用在苹果自家的设备上。然而,尽管Swift自称是跨平台语言,作者却从未见过有人在Windows上使用Swift编程,而在Swift的官网上,我们可以看到其也只是提供了XCode和Ubuntu系统的下载链接,似乎在Windows平台上,除了使用微软的Linux Subsystem之外,没有办法直接使用Swift。
但是经过又一番搜索,作者发现Swift其实是可以在Windows上使用的,只不过官方并没有提供Windows的二进制下载包,用户得自己在Windows上编译Swift的工具链。事实上,Swift在很早的时候就已经可以用于Windows和Android系统上,这两个系统上的工具链是由社区(主要是Google的大佬们)维护的,官网对此只字未提,作者也只能在Swift语言的GitHub页面中找到Swift在Windows上编译的一些零碎的教程。在Windows上编译和使用Swift需要进行十分繁琐的配置环境、编译、部署工作,作者花了昨天一个下午加上今天一个早上才真正完成整体的环境搭建,但是它确实能够运行起来,并且达到我们“在Windows上用Swift开发原生Windows程序”的目的。由于GitHub的官方教程有一些细节问题,并且作者在自己作死编译Swift的时候又遇到了一系列新的问题,耽误了很多时间,而作者找遍了全网也没有找到第二篇关于在Windows上编译Swift的教程,因此作者将自己在Windows上编译Swift的实践结果记录在这篇文章中,希望能够让有同样需求的大家少踩一些已经踩过的坑。
本文主要分为以下三个部分:
- 前期准备(下载项目,准备编译环境)
- 按顺序编译各个Swift组件
- 用一个简单的swift文件测试一下
官方原版教程的在这里:
前期准备
首先我们需要安装用于编译Swift的开发环境,包括以下内容:
- 安装Visual Studio 2019或者更新版本。2017也许可以,这里没有测试过。
- 在VS Installer的工作负载中,在“桌面应用和移动应用”中,选择“使用C++的桌面开发”和“通用Windows平台开发”。作者建议直接把这两个勾上,按照默认配置安装就完事,如果需要单独选的话,至少需要选择以下几项:
- MSVC v14x 工具链。
- Windows 10 SDK 版本在10.0.17763以上。
- CMake和Ninja,在勾选了“适用于Windows的C++ CMake工具”以后会自动安装,如果已有的话,将其添加到Path中也一样有效。
- ATL(适用于最新v14x生成工具的C++ ATL)。
- Google Test 测试适配器。
- 在单个组件里面,单独勾选安装Python 2.7,注意32位的路径是C:\Python27\,64位的路径是C:\Python27amd64\,在后面执行命令的时候如果有提示找不到Python,请检查是否需要添加或者移除amd64后缀。
在安装好所有的以后,按照官方文档的步骤打开PC的开发者模式。
如果您的系统为中文系统,还需要进行一步额外的操作,即在控制面板->区域->管理->非Unicode程序的语言 中,勾选“"Beta 版" : 使用Unicode UTF-8提供全球语言支持”,并且将当前系统区域设置改为“英语(美国)”。由于Swift的源代码使用UTF-8编码,并且包含特殊字符(作者不知道为啥要这么做),因此如果您在这里不配置的话会导致因为字符无法识别而导致的编译失败。
在完成以上操作后,我们需要创建一个文件夹,以放置所有Swift工具链相关的内容,以下的代码和说明均以D:\Swift\文件夹为例,如您的文件夹位于别处,可以手动替换所有的D:\Swift\路径至您的路径积可。
在创建完目录以后,我们需要进入到目录下,并克隆所有需要的Swift项目源代码。使用管理器权限打开VS自带的x64 Native Tools Command Prompt(直接在开始菜单中搜索“x64”应该就会出现),然后执行以下代码:
git clone --depth=1 https://github.com/apple/llvm-project --branch swift/master toolchain
git clone -c core.autocrlf=input -c core.symlinks=true --depth=1 https://github.com/apple/swift toolchain/swift
git clone --depth=1 https://github.com/apple/swift-cmark toolchain/cmark
git clone --depth=1 https://github.com/apple/swift-corelibs-libdispatch swift-corelibs-libdispatch
git clone --depth=1 https://github.com/apple/swift-corelibs-foundation swift-corelibs-foundation
git clone --depth=1 https://github.com/apple/swift-corelibs-xctest swift-corelibs-xctest
git clone --depth=1 https://github.com/apple/swift-llbuild llbuild
git clone --depth=1 https://github.com/apple/swift-tools-support-core swift-tools-support-core
git clone -c core.autocrlf=input --depth=1 https://github.com/apple/swift-package-manager swiftpm
git clone --depth=1 https://github.com/compnerd/swift-build swift-build
由于我们只是需要下载文件,所以我在所有克隆请求中都加了--depth=1来加快下载速度。全部下载完成后的目录应该包括以下目录:
- toolchain
- swift
- cmark
- swift-corelibs-libdispatch
- swift-corelibs-foundation
- swift-corelibs-xctest
- llbuild
- swift-tools-support-core
- swiftpm
- swift-build
然后我们需要下载Swift的依赖库。在D:\Swift\下新建一个目录Library,然后按照官方文档的支持去AZure页面下载最新的daily build文件。
以SQLite为例,我们找到Runs中最新一次成功的build,点开详情也没,在Stages中找到windows,在弹出的详情页面中,我们可以在右边黑色日志窗口的最后一行文字中看到类似`1 artifact produced`的提示。点击artifact进入Artifacts的页面,选择合适的版本,这里是sqlite-windows-x64,然后点击右方的下载按钮即可下载打包好的压缩包。
在解压所有压缩包以后,Library目录应该如下所示:
/Library
┝ icu-64
│ ┕ usr/...
├ libcurl-development
│ ┕ usr/...
├ libxml2-development
│ ┕ usr/...
├ sqlite-3.30.0
│ ┕ usr/...
┕ zlib-1.2.11
┕ usr/...
需要注意,如果您下载的库的版本号或者名字与上图不符,请以实际下载的为准,并在后面的脚本中修改对应的路径以匹配实际的版本号。
在完成依赖库下载以后,按照官方文档的指示将模块正确设置:
mklink "%UniversalCRTSdkDir%\Include\%UCRTVersion%\ucrt\module.modulemap" D:\Swift\toolchain\swift\stdlib\public\Platform\ucrt.modulemap
mklink "%UniversalCRTSdkDir%\Include\%UCRTVersion%\um\module.modulemap" D:\Swift\toolchain\swift\stdlib\public\Platform\winsdk.modulemap
mklink "%VCToolsInstallDir%\include\module.modulemap" D:\Swift\toolchain\swift\stdlib\public\Platform\visualc.modulemap
mklink "%VCToolsInstallDir%\include\visualc.apinotes" D:\Swift\toolchain\swift\stdlib\public\Platform\visualc.apinotes
编译组件
首先开始编译LLVM和Swift核心编译器。在确认一切准备都妥当以后,执行以下命令:
md "D:\Swift\b\toolchain"
cmake -B "D:\Swift\b\toolchain" ^
-C D:\Swift\swift-build\cmake\caches\windows-x86_64.cmake ^
-C D:\Swift\swift-build\cmake\caches\org.compnerd.dt.cmake ^
-D CMAKE_BUILD_TYPE=Release ^
-D LLVM_ENABLE_ASSERTIONS=YES ^
-D LLVM_ENABLE_PROJECTS="clang;clang-tools-extra;cmark;swift;lldb;lld" ^
-D LLVM_EXTERNAL_PROJECTS="cmark;swift" ^
-D SWIFT_PATH_TO_LIBDISPATCH_SOURCE=D:\Swift\swift-corelibs-libdispatch ^
-D LLVM_ENABLE_PDB=YES ^
-D LLVM_ENABLE_LIBEDIT=NO ^
-D LLDB_ENABLE_PYTHON=YES ^
-D SWIFT_WINDOWS_x86_64_ICU_UC_INCLUDE="D:/Swift/Library/icu-64/usr/include" ^
-D SWIFT_WINDOWS_x86_64_ICU_UC="D:/Swift/Library/icu-64/usr/lib/icuuc64.lib" ^
-D SWIFT_WINDOWS_x86_64_ICU_I18N_INCLUDE="D:/Swift/Library/icu-64/usr/include" ^
-D SWIFT_WINDOWS_x86_64_ICU_I18N="D:/Swift/Library/icu-64/usr/lib/icuin64.lib" ^
-D CMAKE_INSTALL_PREFIX="C:\Library\Developer\Toolchains\unknown-Asserts-development.xctoolchain\usr" ^
-D PYTHON_EXECUTABLE=C:\Python27amd64\python.exe ^
-D SWIFT_BUILD_DYNAMIC_STDLIB=YES ^
-D SWIFT_BUILD_DYNAMIC_SDK_OVERLAY=YES ^
-G Ninja ^
-S D:\Swift\toolchain\llvm
其中,PYTHON_EXECUTABLE变量请按照实际情况设置为C:\Python27\python.exe或者C:\Python27amd64\python.exe。
在CMake执行完毕后,执行以下命令开始编译Swift工具链:
ninja -C D:\Swift\b\toolchain
总共需要编译5000多个C++文件,16G内存4核处理器约需2个小时。由于Ninja使用并行编译,这段时间您的电脑基本上是废了,CPU和内存均被占满,请准备好零食瓜子和iPad开始看剧(笑)。
作者在构建的时候遇到了以下几个错误:
- MSVC warning C4819: The file contains a character that cannot be represented in the current code page (number). Save the file in Unicode format to prevent data loss.
出现这个错误基本上是因为您在编译之前没有按照上面的指示设置系统使用UTF-8页面,如果出现该提示,请立即停止编译,完成设置以后,删除之前编译好的文件并且重新开始编译。
- Linker fatal error:LNK1102 Out of memory
如果您的内存小于等于16GB,在编译的最后阶段可能会产生这个操作。由于链接lib生成exe的时候平均每一项任务都需要6-7GB的内存,因此如果该错误出现,您需要在Ninja中设置不允许并行编译,避免多个exe一起链接导致内存耗尽:
ninja -C D:\Swift\b\toolchain -j 1
在构建完工具链之后,接着构建libdispatch模块
cmake -B D:\Swift\b\libdispatch ^
-D CMAKE_BUILD_TYPE=RelWithDebInfo ^
-D CMAKE_C_COMPILER=D:/Swift/b/toolchain/bin/clang-cl.exe ^
-D CMAKE_CXX_COMPILER=D:/Swift/b/toolchain/bin/clang-cl.exe ^
-D CMAKE_Swift_COMPILER=D:/Swift/b/toolchain/bin/swiftc.exe ^
-D ENABLE_SWIFT=YES ^
-G Ninja ^
-S D:\Swift\swift-corelibs-libdispatch
使用Ninja编译:
ninja -C D:\Swift\b\libdispatch
然后是foundation模块
cmake -B D:\Swift\b\foundation -D CMAKE_BUILD_TYPE=RelWithDebInfo ^
-D CMAKE_C_COMPILER=D:/Swift/b/toolchain/bin/clang-cl.exe ^
-D CMAKE_Swift_COMPILER=D:/Swift/b/toolchain/bin/swiftc.exe ^
-D CURL_LIBRARY="D:/Swift/Library/libcurl-development/usr/lib/libcurl.lib" ^
-D CURL_INCLUDE_DIR="D:/Swift/Library/libcurl-development/usr/include" ^
-D ICU_ROOT="D:/Swift/Library/icu-64" ^
-D ICU_INCLUDE_DIR=D:/Swift/Library/icu-64/usr/include ^
-D LIBXML2_LIBRARY="D:/Swift/Library/libxml2-development/usr/lib/libxml2s.lib" ^
-D LIBXML2_INCLUDE_DIR="D:/Swift/Library/libxml2-development/usr/include/libxml2" ^
-D ENABLE_TESTING=NO ^
-D dispatch_DIR=D:/Swift/b/libdispatch/cmake/modules ^
-G Ninja ^
-S D:\Swift\swift-corelibs-foundation
编译:
ninja -C D:\Swift\b\foundation
在Foundation模块编译的时候会提示未解决的外部引用,这是一个已知问题,目前我的解决办法是在D:\Swift\b\foundation\build.ninja的Foundation.dll的构建规则中,在LINK_LIBRARIES项中手动添加D:\Swift\Library\icu-64\usr\lib\icuin64.lib D:\Swift\Library\icu-64\usr\lib\icuuc64.lib两项,这只是一个quick-and-dirty的方法。
编译完成以后添加path:
path D:\Swift\b\foundation\Foundation;%PATH%
然后是xctest模块:
cmake -B D:\Swift\b\xctest ^
-D CMAKE_BUILD_TYPE=RelWithDebInfo ^
-D CMAKE_Swift_COMPILER=D:/Swift/b/toolchain/bin/swiftc.exe ^
-D dispatch_DIR=D:\Swift\b\dispatch\cmake\modules ^
-D Foundation_DIR=D:\Swift\b\foundation\cmake\modules ^
-D LIT_COMMAND=D:\Swift\toolchain\llvm\utils\lit\lit.py ^
-D PYTHON_EXECUTABLE=C:\Python27amd64\python.exe ^
-G Ninja -S D:\Swift\swift-corelibs-xctest
编译:
ninja -C D:\Swift\b\xctest
完事以后添加path:
path D:\Swift\b\xctest;%PATH%
编译llbuild:
set AR=llvm-ar
cmake -B D:\Swift\b\llbuild ^
-D CMAKE_BUILD_TYPE=RelWithDebInfo ^
-D CMAKE_CXX_COMPILER=cl ^
-D CMAKE_Swift_COMPILER=D:/Swift/b/toolchain/bin/swiftc.exe ^
-D Foundation_DIR=D:/Swift/b/foundation/cmake/modules ^
-D dispatch_DIR=D:/Swift/b/libdispatch/cmake/modules ^
-D SQLite3_INCLUDE_DIR=D:\Swift\Library\sqlite-3.28.0\usr\include ^
-D SQLite3_LIBRARY=D:\Swift\Library\sqlite-3.28.0\usr\lib\sqlite3.lib ^
-D LLBUILD_SUPPORT_BINDINGS=Swift -G Ninja -S D:\Swift\llbuild
ninja -C D:\Swift\b\llbuild
此处记得检查sqlite库的目录是否正确。
编译swift-tools-core-support:
cmake -B D:\Swift\b\tsc ^
-D CMAKE_BUILD_TYPE=RelWithDebInfo ^
-D CMAKE_C_COMPILER=cl ^
-D CMAKE_Swift_COMPILER=D:/Swift/b/toolchain/bin/swiftc.exe ^
-D Foundation_DIR=D:/Swift/b/foundation/cmake/modules ^
-D dispatch_DIR=D:/Swift/b/libdispatch/cmake/modules ^
-G Ninja -S D:\Swift\swift-tools-support-core
ninja -C D:\Swift\b\tsc
编译swift-package-manager:
cmake -B D:\Swift\b\spm ^
-D CMAKE_BUILD_TYPE=RelWithDebInfo ^
-D CMAKE_C_COMPILER=D:/Swift/b/toolchain/bin/clang-cl.exe ^
-D CMAKE_CXX_COMPILER=D:/Swift/b/toolchain/bin/clang-cl.exe ^
-D CMAKE_Swift_COMPILER=D:/Swift/b/toolchain/bin/swiftc.exe ^
-D USE_VENDORED_TSC=YES ^
-D Foundation_DIR=D:/Swift/b/foundation/cmake/modules ^
-D dispatch_DIR=D:/Swift/b/libdispatch/cmake/modules ^
-D LLBuild_DIR=D:/Swift/b/llbuild/cmake/modules ^
-D TSC_DIR=D:/Swift/b/tsc/cmake/modules ^
-G Ninja -S D:\Swift\swiftpm
ninja -C D:\Swift\b\spm
以上所有操作成功后,恭喜你,已经获得了在Windows上可用的Swift版本了。
由于经过实际测试,官方教程里的默认install方法无效,因此我们可以通过将以下路径添加到PATH中来手动安装:
D:\Swift\Library\icu-64\usr\bin
D:\Swift\Library\zlib-1.2.11\usr\bin
D:\Swift\b\toolchain\bin
D:\Swift\b\libdispatch
D:\Swift\b\llbuild\bin
D:\Swift\b\foundation\Sources\Foundation
D:\Swift\b\foundation\Sources\FoundationXML
D:\Swift\b\foundation\Sources\FoundationNetworking
D:\Swift\b\spm\bin
D:\Swift\b\tsc\bin
写一个脚本测试一下吧
随便找个位置建立一个文件,我们叫它test.swift好了。打开这个文件,输入最简单代码:
var myString = "Hello World!";
print(myString)
把D:\Swift\b\toolchain\bin添加到PATH,然后打开控制台,编译文件:
swiftc test.swift -o test.exe
swiftc稍微有点水土不服,所以必须要在输出文件中加上.exe后缀,否则不会自动添加。
在控制台里运行,就可以看到输出的Hello World!了