@@ -18,6 +18,8 @@ public struct SwiftPkgInfo: Codable {
1818public struct MacOS: Platform {
1919 public init ( ) { }
2020
21+ private let SWIFTLY_TOOLCHAINS_DIR = " SWIFTLY_TOOLCHAINS_DIR "
22+
2123 public var defaultSwiftlyHomeDir : FilePath {
2224 fs. home / " .swiftly "
2325 }
@@ -34,7 +36,7 @@ public struct MacOS: Platform {
3436
3537 public func swiftlyToolchainsDir( _ ctx: SwiftlyCoreContext ) -> FilePath {
3638 ctx. mockedHomeDir. map { $0 / " Toolchains " }
37- ?? ProcessInfo . processInfo. environment [ " SWIFTLY_TOOLCHAINS_DIR " ] . map { FilePath ( $0) }
39+ ?? ProcessInfo . processInfo. environment [ self . SWIFTLY_TOOLCHAINS_DIR] . map { FilePath ( $0) }
3840 // This is where the installer will put the toolchains, and where Xcode can find them
3941 ?? self . defaultToolchainsDirectory
4042 }
@@ -185,6 +187,22 @@ public struct MacOS: Platform {
185187
186188 let toolchainDir = self . swiftlyToolchainsDir ( ctx) / " \( toolchain. identifier) .xctoolchain "
187189
190+ let bundleID = try await findToolchainBundleID ( ctx, toolchain)
191+
192+ try await fs. remove ( atPath: toolchainDir)
193+
194+ if let bundleID {
195+ try ? await sys. pkgutil ( . volume( fs. home) ) . forget ( pkg_id: bundleID) . run ( quiet: !verbose)
196+ }
197+ }
198+
199+ private func findToolchainBundleID( _ ctx: SwiftlyCoreContext , _ toolchain: ToolchainVersion ) async throws -> String ? {
200+ guard toolchain != . xcode else {
201+ return nil
202+ }
203+
204+ let toolchainDir = self . swiftlyToolchainsDir ( ctx) / " \( toolchain. identifier) .xctoolchain "
205+
188206 let decoder = PropertyListDecoder ( )
189207 let infoPlist = toolchainDir / " Info.plist "
190208 let data = try await fs. cat ( atPath: infoPlist)
@@ -193,9 +211,7 @@ public struct MacOS: Platform {
193211 throw SwiftlyError ( message: " could not decode plist at \( infoPlist) " )
194212 }
195213
196- try await fs. remove ( atPath: toolchainDir)
197-
198- try ? await sys. pkgutil ( . volume( fs. home) ) . forget ( pkg_id: pkgInfo. CFBundleIdentifier) . run ( quiet: !verbose)
214+ return pkgInfo. CFBundleIdentifier
199215 }
200216
201217 public func getExecutableName( ) -> String {
@@ -244,5 +260,83 @@ public struct MacOS: Platform {
244260 return self. swiftlyToolchainsDir ( ctx) / " \( toolchain. identifier) .xctoolchain "
245261 }
246262
263+ public func updateEnvironmentWithToolchain( _ ctx: SwiftlyCoreContext, _ environment: Environment, _ toolchain: ToolchainVersion, path: String) async throws -> Environment {
264+ var newEnv = environment
265+
266+ // On macOS, we try to set SDKROOT if its empty for tools like clang++ that need it to
267+ // find standard libraries that aren't in the toolchain, like libc++. Here we
268+ // use xcrun to tell us what the default sdk root should be.
269+ if ProcessInfo . processInfo. environment [ " SDKROOT " ] == nil {
270+ newEnv = newEnv. updating ( [
271+ " SDKROOT " : try ? await run (
272+ . path( SystemPackage . FilePath ( " /usr/bin/xcrun " ) ) ,
273+ arguments: [ " --show-sdk-path " ] ,
274+ output: . string( limit: 1024 * 10 )
275+ ) . standardOutput? . replacingOccurrences ( of: " \n " , with: " " ) ,
276+ ] )
277+ }
278+
279+ guard let bundleID = try await findToolchainBundleID ( ctx, toolchain) else {
280+ return newEnv
281+ }
282+
283+ // If someday the two tools in the toolchain that require xcrun were to remove their dependency on it then
284+ // we can determine the maximum toolchain version where these environment variables are needed and abort early
285+ // here.
286+
287+ let TOOLCHAINS : Environment . Key = " TOOLCHAINS "
288+ let DEVELOPER_DIR : Environment . Key = " DEVELOPER_DIR "
289+ let PATH : Environment . Key = " PATH "
290+
291+ if let existingToolchains = ProcessInfo . processInfo. environment [ TOOLCHAINS . rawValue] , existingToolchains != bundleID {
292+ throw SwiftlyError ( message: " You have already set \( TOOLCHAINS . rawValue) environment variable to \( existingToolchains) , but swiftly has picked another toolchain. Please unset it or `swiftly use xcode` to use the Xcode selection mechanism. " )
293+ }
294+ newEnv = newEnv. updating ( [ TOOLCHAINS: bundleID] )
295+
296+ // Create a compatible DEVELOPER_DIR in case of a custom swiftly toolchain location (not ~/Library/Developer/Toolchains)
297+ if let swiftlyToolchainsDir = ProcessInfo . processInfo. environment [ SWIFTLY_TOOLCHAINS_DIR] ,
298+ case let customToolchainsDir = FilePath ( swiftlyToolchainsDir) ,
299+ customToolchainsDir != defaultToolchainsDirectory
300+ {
301+ // Simulate a custom CommandLineTools within the swiftly home directory that satisfies xcrun and allows it to find
302+ // the selected toolchain on the PATH with the selected toolchain in front. This command-line tools will only have
303+ // the expected libxcrun.dylib in it and no other tools in its usr/bin directory so that none are picked up there by xcrun.
304+
305+ // We need a macOS CLT to be installed for this to work
306+ let realCltDir = FilePath ( " /Library/Developer/CommandLineTools " )
307+ if !( try await fs. exists ( atPath: realCltDir) ) {
308+ throw SwiftlyError ( message: " The macOS command line tools must be installed to support a custom SWIFTLY_TOOLCHAIN_DIR for macOS. You can install it using `xcode-select --install` " )
309+ }
310+
311+ let commandLineToolsDir = swiftlyHomeDir ( ctx) / " CommandLineTools "
312+ if !( try await fs. exists ( atPath: commandLineToolsDir) ) {
313+ try await fs. mkdir ( atPath: commandLineToolsDir)
314+ }
315+
316+ let usrLibDir = commandLineToolsDir / " usr " / " lib "
317+ if !( try await fs. exists ( atPath: usrLibDir) ) {
318+ try await fs. mkdir ( . parents, atPath: usrLibDir)
319+ }
320+
321+ let xcrunLibLink = usrLibDir / " libxcrun.dylib "
322+ if !( try await fs. exists ( atPath: xcrunLibLink) ) {
323+ try await fs. symlink ( atPath: xcrunLibLink, linkPath: realCltDir / " usr/lib/libxcrun.dylib " )
324+ }
325+
326+ let developerDir : FilePath = commandLineToolsDir
327+
328+ if let developerDirEnv = ProcessInfo . processInfo. environment [ DEVELOPER_DIR . rawValue] ,
329+ developerDirEnv != developerDir. string
330+ {
331+ throw SwiftlyError ( message: " You have set \( DEVELOPER_DIR . rawValue) environment variable to \( developerDirEnv) , but swiftly is trying to use its own location so that a toolchain can be selected. Please unset the environment variable and try again. " )
332+ }
333+
334+ newEnv = newEnv. updating ( [ DEVELOPER_DIR: developerDir. string] )
335+ newEnv = newEnv. updating ( [ PATH: path + " : " + ( realCltDir / " usr/bin " ) . string] )
336+ }
337+
338+ return newEnv
339+ }
340+
247341 public static let currentPlatform : any Platform = MacOS ( )
248342}
0 commit comments