Swift on Windows
単純に始めたいだけなら swift.org が出している始め方から進める
apple/swift自体のビルドから始めたいときにはcompnerdさんの書いてる始め方から進める
Visual Studio 2022を利用しても大丈夫
swift replを起動するときにはとりあえずx64 Native Tools Command Prompt for VS 2022から起動するといろいろはかどる
Windowsでswift replを起動しようとするとpython39.dllが見つからずにlldb.exeがエラーを吐いてそのまま終了することがある。 多分普通にPathがないだけなのでC:\Users\<name>\AppData\Local\Programs\Python\Python39\
追加する
なんとなく動いたっぽい
C:\Program Files\Microsoft Visual Studio\2022\Community>swift repl
Welcome to Swift version 5.10-dev (LLVM e8e5be8d5b39f46, Swift 99e9db868aefd99).
Type :help for assistance.
1> let a = 1
a: Int = 1
2> import Foundation
3> let b = Date()
b: Foundation.Date = 1993-02-08 12:49:03 東京 (標準時)
4> struct S { let a = 1; let b = Date() }
5> try JSONEncoder().encode(S())
Foundation.JSONEncoder:43:15: note: where 'T' = 'S'
open func encode<T>(_ value: T) throws -> Data where T : Encodable
^
error: repl.swift:5:19: error: instance method 'encode' requires that 'S' conform to 'Encodable'
try JSONEncoder().encode(S())
^
Foundation.JSONEncoder:43:15: note: where 'T' = 'S'
open func encode<T>(_ value: T) throws -> Data where T : Encodable
^
5> let s = S()
s: S = {
a = 1
b = 1993-02-08 12:50:10 東京 (標準時)
}
6> try JSONEncoder().encode(s)
Foundation.JSONEncoder:43:15: note: where 'T' = 'S'
open func encode<T>(_ value: T) throws -> Data where T : Encodable
^
error: repl.swift:6:19: error: instance method 'encode' requires that 'S' conform to 'Encodable'
try JSONEncoder().encode(s)
^
Foundation.JSONEncoder:43:15: note: where 'T' = 'S'
open func encode<T>(_ value: T) throws -> Data where T : Encodable
^
6> struct SS: Codable { let a = 1; let b = Date() }
7> try JSONEncoder().encode(SS())
$R0: Foundation.Data = 29 bytes
8> try String(data: JSONEncoder().encode(SS()), encoding: .utf8)!
$R1: String = "{\"b\":729175925.3969994,\"a\":1}"
9>
swift.org の方の導入を見ると以下のmodule.modulemapをコピペする作業はもうしなくていいらしい。でもしないと動かないとかもあったり。あとsymlinkより普通にコピペした方が自分の手元だと変なトラブル少なかった
mklink "%UniversalCRTSdkDir%\Include\%UCRTVersion%\ucrt\module.modulemap" S:\swift\stdlib\public\Platform\ucrt.modulemap
mklink "%UniversalCRTSdkDir%\Include\%UCRTVersion%\um\module.modulemap" S:\swift\stdlib\public\Platform\winsdk.modulemap
mklink "%VCToolsInstallDir%\include\module.modulemap" S:\swift\stdlib\public\Platform\vcruntime.modulemap
mklink "%VCToolsInstallDir%\include\vcruntime.apinotes" S:\swift\stdlib\public\Platform\vcruntime.apinotes
Windows開発そもそも知らんのでWindowsSDKが何者かも知らん
VSCodeでSwift Packagesのプロジェクト開くとコード補完とかも効く、いいね
VSCodeの内部ターミナルからx64 Native Tools Command Prompt for VS 2022を開くにはsetting.jsonに以下を追加してよしなに
/k 以降の文字列はx64のショートカットに書かれてた内容をそのままもってきた、やってることはショートカットと同じcmd.exeに特別なbatを食わせて環境変数とかデフォルトで追加することらしいよ
"terminal.integrated.defaultProfile.windows": "Developer Command Prompt x64",
"terminal.integrated.profiles.windows": {
"Developer Command Prompt x64": {
"args": [
"/k",
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
],
"overrideName": true,
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
]
},
},
個人的にコマンドプロンプトでファイルシステムの移動すらおぼつかないのでexplorer.exe開いて、作業したいところでVSCodeを開く。そんな感じでやるとswiftコマンドだけ内部ターミナルでたたいてpackage initしたりできる
VSCodeでinitしたところ
適当にConcurrencyなネットワーク処理をするexecutableを作ってみる。URLSessionを使うときにはもちろんFoundationだけでは足りないのでFoundationNetworkingも追加。普通に動いてるね
import Foundation
import FoundationNetworking
struct Todo: Decodable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
extension URLSession {
func data(from url: URL) async throws -> (Data, URLResponse) {
try await withCheckedThrowingContinuation { continuation in
dataTask(with: url) { data, response, error in
if let error = error {
continuation.resume(throwing: error)
} else if let data = data, let response = response {
continuation.resume(returning: (data, response))
} else {
fatalError("Neither data nor error was returned.")
}
}.resume()
}
}
}
@main
struct Main {
static func main() async throws {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
print(url)
try await Task.sleep(for: .seconds(1))
let (data, response) = try await URLSession.shared.data(from: url)
dump(response)
let todo = try JSONDecoder().decode(Todo.self, from: data)
print(todo)
}
}
Package.swiftも特に変わらず
// swift-tools-version: 5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "swift-my-app",
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "swift-my-app"),
]
)
なにか特別な設定をしなくてもVSCodeの拡張を指示通りに入れていればコード補完も効いてて書きやすい
Arc作ってるBrowserCompanyが毎日最新のSwiftのスナップショットをWindowsで使える形で配布してるのでSwift 5.11-devとか使いたかったらここから、compnerd/swift-build もあるけど今compnerdさんBrowserCompanyで開発に加わっているらしいからcompnerd/swift-buildの純粋な後継リポジトリがthebrowsercompany/swift-buildという扱いみたい
計算できるだけでも楽しいけどやっぱりWinUI/WinRT触りたいぜ、ということでお待ちかねのwindows-samplesをやってく
"C:\src\windows-samples\WinUI3AnimationsPreview\WinUI3AnimationsPreview.code-workspace"
をVSCodeで開いたらプロジェクトのセットアップは完了。CMake使ってるからConfigure, Buildでできるか試してみる。
いざ!と思ってやってみたらしょっぱなからコケる。多分なんかVSCodeの設定うまくいってないんだろね
エラー: swift タスクの検出は、次の構成のタスクに貢献しませんでした。
{
"type": "swift",
"args": [
"package",
"resolve"
],
"cwd": ".",
"problemMatcher": [],
"label": "swift: Resolve Package Dependencies",
"detail": "swift package resolve"
}
このタスクは無視されます。
エラー: swift タスクの検出は、次の構成のタスクに貢献しませんでした。
{
"type": "swift",
"args": [
"package",
"resolve"
],
"cwd": ".",
"problemMatcher": [],
"label": "swift: Resolve Package Dependencies",
"detail": "swift package resolve"
}
このタスクは無視されます。
でもまぁこれはCMakeのタスクをVSCodeにやらせようとしてるだけなのでいいでしょ、CLIから眺めていきます。cmake --preset debug
でCMakeLists.txtからConfigureするとここまではおけーい
C:\src\windows-samples\WinUI3AnimationsPreview>cmake --preset debug
Preset CMake variables:
CMAKE_BUILD_TYPE="Debug"
CMAKE_CXX_COMPILER="cl"
CMAKE_C_COMPILER="cl"
CMAKE_INSTALL_PREFIX:PATH="C:/src/windows-samples/WinUI3AnimationsPreview/out"
CMAKE_MODULE_PATH="C:/src/windows-samples/WinUI3AnimationsPreview/cmake"
利用できるインスタンスがありません。
-- Restoring NuGet package Microsoft.Windows.SDK.BuildTools.10.0.22000.194.
Adding package 'Microsoft.Windows.SDK.BuildTools.10.0.22000.194' to folder 'C:\src\windows-samples\build\NugetPackages'
Added package 'Microsoft.Windows.SDK.BuildTools.10.0.22000.194' to folder 'C:\src\windows-samples\build\NugetPackages'
使用されている NuGet Config ファイル:
C:\src\windows-samples\nuget\NuGet.config
使用されているフィード:
C:\Users\arasa\.nuget\packages\
https://api.nuget.org/v3/index.json
インストール済み:
packages.config projects に対する 1 個のパッケージ
-- Running swiftwinrt...
-- swifwinrt completed
-- Configuring done (5.4s)
-- Generating done (0.1s)
-- Build files have been written to: C:/src/windows-samples/WinUI3AnimationsPreview/build
ドキドキビルドします。cmake --build --preset debug
でビルドすると普通にコケる。
error: could not build C module 'CWinRT'とかなんとか
これは新しいSwift(今回はswift 5.10)を利用していてNuGetで少し古いコード自動生成が利用されてるからっぽい。失敗原因を見ていくとoleauto.hが云々とか出てくる。これは自動生成されたコードの中で発生したエラーなのでswift-winrtのコミットから関係ありそうなやつを探す
C:\src\windows-samples\Shared\WinRT\Sources\CWinRT\include/CppInteropWorkaround.h:58:5: error: missing '#include <oleauto.h>'; 'SysFreeString' must be declared before it is used
コミット見ていくとまずはここでoleauto.hが入ってそうじゃない?
そのあとここで消えてそうじゃない?
ほんだらswift-winrtを自前でmainからビルドしてswiftwinrt.exeをつくってくべよ
ちょっと先の話だけどWindows App SDKは利用に必要なランタイムがあるので再頒布可能パッケージとかつけたりしないといけなさそう、手元で動かす分にはいったん最新版のランタイムを適当にインストール
swift-winrtはmainブランチだとretroactiveの言語機能を利用するようになっているのでswift-buildからnightlyを利用すること
今回利用しているのは以下のバージョン
compnerd.org Swift version 5.11-dev (LLVM e238ee5c1896761, Swift db020c1486d2111)
swift-winrtのビルド時に利用するバージョンはちょっと古いので以下までおもむろに引き上げてよし
"CMAKE_SYSTEM_VERSION": "10.0.22621.0",
ここで見ているバージョンは開発ターミナルでset
して出てくるバージョンにすればいいと思うよ、手元だとこれだった
WindowsSDKVersion=10.0.22621.0\
swift-winrtの自前ビルドをcmakeで書けばいいんだけど、とりあえず動かすだけならswiftwinrt.exeを生成してcmakeでバイナリ参照先を書き換えてあげればよし
- set(SWIFTWINRT_EXE ${SWIFTWINRT_DIR}/swiftwinrt.exe)
+ set(SWIFTWINRT_EXE ${REPO_ROOT}/selfbin/swiftwinrt.exe)
これで動くはずなのでえいやえいやビルド
VSCodeだとCommand Pallete開いてcmakeのconfigureでpresetを指定するとうまく動くようになる
> cmake select configure Preset
Run build taskで動くという話だけどswiftタスクがうまく動かないのでRun task...からConfigureとBuildを選んでビルドを回す
Buildまで終わったらbin\WinUI3AnimationsPreview.exe
にバイナリが生成されているはず
ワークスペース設定を見るとlaunchにlldbを起動するようになってるので、VSCodeのRun and Debugからそのままデバッグ
"launch": {
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "WinUI3AnimationsPreview",
"program": "${workspaceFolder:WinUI3AnimationsPreview}/../build/bin/WinUI3AnimationsPreview",
"args": [],
"cwd": "${workspaceFolder:WinUI3AnimationsPreview}",
"preLaunchTask": "Build"
}
],
"compounds": []
},
デバッガーも動く
import WinUIをしてもエディタ上だとうまくコード補完が効かないので何かしら設定が必要
と思ったけどコード補完効き始めたな、なんだろ、sourcekit-lsp周りのあれこれなんかな
ここでswift-winrtとかcmake管理にしてNuGetのやつ使わないようにする感じで作る。最終的にcmake周りを自分用に整理したいけど
Windows11で今のlatest環境に近いもので構築できるようにする
このアプリケーションのビルド結果自体はボタンがあるだけで特に面白みはないかもしれない、これを構成するビルドシステムがおもしろい
VS2022のインストーラーで入れてるものたち、C++によるデスクトップ開発をベースに以下の設定。いらないもの多分いっぱいある
ここからさらにWinUI3をSwiftで書いたときにどんな見た目を作れるのかはこの人の動画を見るといい感じ。動画だとコード補完とか聞いてなさそうだしデバッグのやり方もわからないみたいなことを言ってるのでそこはこのスクラップで補ってる
参考資料、SwiftとCOMの連携とか
windowsbuildの補足わかりやすい
Windows Sandboxを利用してクリーンな環境から始めると再現性高い
$progressPreference = 'silentlyContinue'
Write-Information "Downloading WinGet and its dependencies..."
Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.7.3/Microsoft.UI.Xaml.2.7.x64.appx -OutFile Microsoft.UI.Xaml.2.7.x64.appx
Add-AppxPackage Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxPackage Microsoft.UI.Xaml.2.7.x64.appx
Add-AppxPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
以下の情報はもう古い、Swift ToolchainとVisualStudioのビルドツール入れてそのままwindows-samplesのREADME.mdに進めばOK、swift-winrtとかそこらへんはbrowsercompanyがSPMで簡単に使えるようにまとめてた
いったんまとめ、これだけで一応最小の依存関係だとSwiftを利用するツールが入る。
$ winget install --id Microsoft.VisualStudio.2022.Community --exact --force --custom "--add Microsoft.VisualStudio.Component.Windows11SDK.22621 --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
$ winget install --id Swift.Toolchain -e
$ winget install --id Kitware.CMake -e
$ winget install --id Ninja-build.Ninja -e
for one-liner
winget install --id Microsoft.VisualStudio.2022.Community --exact --force --custom "--add Microsoft.VisualStudio.Component.Windows11SDK.22621 --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64";winget install --id Swift.Toolchain -e;winget install --id Kitware.CMake -e;winget install --id Ninja-build.Ninja -e
nightlyを利用する場合にはwingetで入れたSwiftを消してこっちから入れる。wingetだとGitとVCRedistとPython39の依存が一緒に入るのでいったん実行することがおすすめ
swiftのwindows-examplesはいろいろなバージョンに気を付ける。
windows-examplesが依存しているswift-winrtは0.2.0だと古いのでmain branchを利用する。
とりあえず上記の内容をそのまま入れてnightlyを使えば動くものがこのフォークしたリポジトリにある
Windows App Runtimeは手動で入れる、wingetは古いかもしれない
Sandbox環境でやるときはterminalがadministratorになるのでspm.cmakeを適切に編集して動くようにする
次はOBSの拡張をSwiftを利用して作っていく
まぁその前にwindows-exampleをベースにもうちょっと変数などをわかりやすく切り出して躓かずにビルド&ランできるようにする
browsercompanyが全部SDK周りまとめてやる必要なくなった
やばーい、browsercompanyの成果がすごい。windows-samplesでCMakeを利用してswift-winrtを自前で管理してた部分を全部ライブラリに置き換えたバージョンがでた
GRDB.swift使いたいからビルドしてみる、SQLite3のsystemLibraryに依存してるのでこれを解決したい
とりあえずlibsqlite3-dev相当のものをWindowsで用意しないといけないのでvcpkgからsqlite3を引っ張ってみる
vcpkgのGetting Startedを進めたらvcpkg.exeが叩けるようにcloneしてきたところまでPATHを通す。
$ vcpkg integrate install
$ vcpkg install sqlite3
$ vcpkg install pkgconf
$ winget install -e --id bloodrock.pkg-config-lite
環境変数の追加
PKG_CONFIG_PATH = C:\Users\<path_to>\vcpkg\installed\x64-windows\lib\pkgconfig
いちおうsqlite3がpkg-configで探せるようになるっぽい。
そしたらspmでpkgConfigの設定を追加
.systemLibrary(
name: "CSQLite",
+ pkgConfig: "sqlite3",
providers: [.apt(["libsqlite3-dev"])]),
WALSnapshotでsqlite3_snapshot_getとかが見つからないって言われる、vcpkgでSQLITE_ENABLE_SNAPSHOTを有効にしてビルドするオプションがない。vcpkg側にコンパイルオプション追加して再ビルド
GRDB.swiftをWindowsでビルドが通るようにした、テストはまだ
GRDB.swiftがWindowsで動いた
実行ファイルにsqlite3.dllが含まれていないので普通にビルドしただけだとこけちゃうな…
pkgConfigの作り方とかミスってるのか?
spmのpkg-configだけだと出力先にsqlite3.dllを自動でコピーむりっぽ?とりあえず適当なスクリプトでmyapp.exeの隣にsqlite3.dllをコピーするような感じにしよう
GRDB.swiftでWALモードでsqlite3のsnapshotを要求していたのでvcpkgにオプション追加する形でPRを提出
GRDB.swiftのDiscussionの方でいったん公開
TCA 1.8のWindows対応
GRDB.swiftのテスト結果
Test Suite 'All tests' failed at 2024-02-25 22:40:09.815
Executed 2474 tests, with 7 tests skipped and 139 failures (129 unexpected) in 126.571 (126.571) seconds
Windowsでほとんど調整してない割には動いているものが多くて悪くない感じ、sqlite3.dllを直接openしている関係上sqlite3.dllに足りてない機能がある可能性もあるからvcpkg側の調整が必要だったりするかも、embedされてるSQLiteCustomを利用してないから意図しているテストケースにはまだなってない
とはいえ基本の入出力ができることは確認済み
The Composable Architecture 1.8.2のWindows対応できた
次はwindows-samplesにTCAとGRDB.swiftを突っ込んで適当なアプリを作成する
GRDB.swiftとTCA1.8を導入して配布するときは依存関係的にこれが最小構成
最高すぎる、普通にobserveでFeatureの状態を観測してWinUIに反映できてる
今のspmは複数のアーキテクチャ、OSなどの分岐に対してバイナリを配布することができない…?xcframeworkで吸収できる差分だったっけ
例えばwindowsappsdkだとnuget/binで直接.dllをリソースとしてコピーする形をとってる
WindowsでVSCodeを開いている状態ですでにビルド済みのマクロがある場合に、依存をアップデートなどしてマクロがリビルドされるときはVSCodeがマクロの.exeを所有しているので削除ができずにビルド失敗する
systemLibraryの仕様勘違いしてた、pkg-configでリンクされたらあとは普通に環境変数のPATHにC:<path_to>\vcpkg\installed\x64-windows\binをセットしたら動くわ
sqlite3.dllをコピー、もしくはPATHが認識できるところでいいのね
配布するときには生成したsqlite3.dllをコピーしてexeの隣においてあげないと環境によって動かないし、そもそもfts5やsnapshotがない実行ファイルを利用するとクラッシュする
XCTestとWebDriverを利用してWinAppDriverをもとに自動UIテストが実行できた
周辺環境を設定するのがめんどくさいので簡単に始められるようにrequirement.ps1をセットアップした
WinUIとTCA1.8を組み合わせた内容がPoint-Freeの人にRTされた
そろそろこのスクラップもいったんまとめの時期かもしれんな、第二弾のスクラップを作らないと見通しが悪い
x64しかまだ開発できない直接的な理由、バイナリが入ってる。じゃあどうやってarm64のものに適応させる?
どこからきたデータファイルなのかよくわからん、
parallelsとかで動かしたいなら最悪x86_64互換モードみたいなやつある気がするからそれで開発できる気がする
だめだ、Windows on Armはx86だけで64は使えないらしい