@@ -57,13 +57,16 @@ func detectProductName() throws -> String {
5757enum DetectionError : Error , CustomStringConvertible {
5858 case noExecutableTarget
5959 case noWasmSDK
60+ case toolchainInstallFailed( version: String )
6061
6162 var description : String {
6263 switch self {
6364 case . noExecutableTarget:
6465 return " No executableTarget found in Package.swift. Use --product to specify one. "
6566 case . noWasmSDK:
6667 return " No WASM SDK found. Install one with: swift sdk install <url> "
68+ case . toolchainInstallFailed( let version) :
69+ return " Failed to install Swift \( version) toolchain. Please install manually from https://swift.org/install "
6770 }
6871 }
6972}
@@ -132,11 +135,25 @@ func extractVersion(from sdk: String) -> String? {
132135 return String ( match. 1 )
133136}
134137
138+ /// Detects the current Swift compiler version (e.g. "6.2" or "6.2.4").
139+ func detectSwiftVersion( ) -> String ? {
140+ guard let output = try ? shellOutput ( " /usr/bin/env " , arguments: [ " swift " , " --version " ] ) else {
141+ return nil
142+ }
143+ let pattern = #/Swift version (\d+\.\d+(?:\.\d+)?)/#
144+ guard let match = output. firstMatch ( of: pattern) else { return nil }
145+ return String ( match. 1 )
146+ }
147+
135148/// Finds a matching toolchain's CFBundleIdentifier for the given SDK version.
136149/// Searches both system and user toolchain directories.
137150func detectToolchainIdentifier( for sdk: String ) -> String ? {
138151 guard let version = extractVersion ( from: sdk) else { return nil }
152+ return findToolchainIdentifier ( version: version)
153+ }
139154
155+ /// Searches for an xctoolchain bundle by version and returns its CFBundleIdentifier.
156+ func findToolchainIdentifier( version: String ) -> String ? {
140157 let toolchainName = " swift- \( version) -RELEASE.xctoolchain "
141158 let searchPaths = [
142159 NSHomeDirectory ( ) + " /Library/Developer/Toolchains/ " + toolchainName,
@@ -154,3 +171,47 @@ func detectToolchainIdentifier(for sdk: String) -> String? {
154171 }
155172 return nil
156173}
174+
175+ /// Downloads and installs a Swift toolchain using the macOS installer.
176+ /// Installs to /Library/Developer/Toolchains/ (requires administrator privileges).
177+ func installMatchingToolchain( version: String ) throws {
178+ let fm = FileManager . default
179+ let toolchainName = " swift- \( version) -RELEASE.xctoolchain "
180+
181+ // Skip if already installed (check both user and system paths)
182+ let checkPaths = [
183+ NSHomeDirectory ( ) + " /Library/Developer/Toolchains/ " + toolchainName,
184+ " /Library/Developer/Toolchains/ " + toolchainName,
185+ ]
186+ for path in checkPaths {
187+ if fm. fileExists ( atPath: path) { return }
188+ }
189+
190+ let url = " https://download.swift.org/swift- \( version) -release/xcode/swift- \( version) -RELEASE/swift- \( version) -RELEASE-osx.pkg "
191+ let tmpDir = NSTemporaryDirectory ( ) + " skiaui-toolchain- \( version) "
192+ let pkgPath = tmpDir + " /swift.pkg "
193+
194+ defer {
195+ try ? fm. removeItem ( atPath: tmpDir)
196+ }
197+
198+ try fm. createDirectory ( atPath: tmpDir, withIntermediateDirectories: true )
199+
200+ print ( " " )
201+ print ( " Swift compiler ( \( detectSwiftVersion ( ) ?? " unknown " ) ) does not match WASM SDK ( \( version) ). " )
202+ print ( " Auto-installing Swift \( version) toolchain... " )
203+ print ( " " )
204+
205+ try shellExec ( " /usr/bin/curl " , arguments: [
206+ " -f " , " -L " , " --progress-bar " ,
207+ " -o " , pkgPath, url,
208+ ] )
209+
210+ print ( " Installing toolchain (administrator password may be required)... " )
211+ try shellExec ( " /usr/bin/sudo " , arguments: [
212+ " /usr/sbin/installer " , " -pkg " , pkgPath, " -target " , " / " ,
213+ ] )
214+
215+ print ( " Installed Swift \( version) toolchain. " )
216+ print ( " " )
217+ }
0 commit comments