Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Sources/CoreCommands/SwiftCommandState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,7 @@ public final class SwiftCommandState {
swiftSDK: swiftSDK,
environment: self.environment,
customTargetInfo: targetInfo,
observabilityScope: self.observabilityScope,
fileSystem: self.fileSystem)
})
}()
Expand All @@ -1057,6 +1058,7 @@ public final class SwiftCommandState {
swiftSDK: hostSwiftSDK,
environment: self.environment,
customTargetInfo: targetInfo,
observabilityScope: self.observabilityScope,
fileSystem: self.fileSystem
)
})
Expand Down
34 changes: 32 additions & 2 deletions Sources/PackageModel/UserToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ public final class UserToolchain: Toolchain {
useXcrun: Bool,
environment: Environment,
searchPaths: [AbsolutePath],
fileSystem: any FileSystem
fileSystem: any FileSystem,
observabilityScope: ObservabilityScope? = nil
) throws -> SwiftCompilers {
func validateCompiler(at path: AbsolutePath?) throws {
guard let path else { return }
Expand All @@ -351,10 +352,37 @@ public final class UserToolchain: Toolchain {
}

let lookup = { UserToolchain.lookup(variable: $0, searchPaths: searchPaths, environment: environment) }

// Warn if SWIFT_EXEC or SWIFT_EXEC_MANIFEST is set but points to a non-existent or non-executable path
func warnIfInvalid(envVar: String, value: String, resolved: AbsolutePath?) {
guard resolved == nil else { return }

let message: String
if let absolutePath = try? AbsolutePath(validating: value) {
if fileSystem.exists(absolutePath) {
message = "\(envVar) is set to '\(value)' which exists but is not executable; ignoring"
} else {
message = "\(envVar) is set to '\(value)' but the file does not exist; ignoring"
}
} else {
message = "\(envVar) is set to '\(value)' but no executable was found in search paths; ignoring"
}

observabilityScope?.emit(warning: message)
}

// Get overrides.
let SWIFT_EXEC_MANIFEST = lookup("SWIFT_EXEC_MANIFEST")
let SWIFT_EXEC = lookup("SWIFT_EXEC")

// Emit warnings if environment variables are set but lookup failed
if let swiftExecValue = environment["SWIFT_EXEC"], !swiftExecValue.isEmpty {
warnIfInvalid(envVar: "SWIFT_EXEC", value: swiftExecValue, resolved: SWIFT_EXEC)
}
if let swiftExecManifestValue = environment["SWIFT_EXEC_MANIFEST"], !swiftExecManifestValue.isEmpty {
warnIfInvalid(envVar: "SWIFT_EXEC_MANIFEST", value: swiftExecManifestValue, resolved: SWIFT_EXEC_MANIFEST)
}

// Validate the overrides.
try validateCompiler(at: SWIFT_EXEC)
try validateCompiler(at: SWIFT_EXEC_MANIFEST)
Expand Down Expand Up @@ -693,6 +721,7 @@ public final class UserToolchain: Toolchain {
customTargetInfo: JSON? = nil,
customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil,
customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil,
observabilityScope: ObservabilityScope? = nil,
fileSystem: any FileSystem = localFileSystem
) throws {
self.swiftSDK = swiftSDK
Expand All @@ -716,7 +745,8 @@ public final class UserToolchain: Toolchain {
useXcrun: self.useXcrun,
environment: environment,
searchPaths: self.envSearchPaths,
fileSystem: fileSystem
fileSystem: fileSystem,
observabilityScope: observabilityScope
)
self.swiftCompilerPath = swiftCompilers.compile
self.architectures = swiftSDK.architectures
Expand Down
87 changes: 87 additions & 0 deletions Tests/PackageModelTests/PackageModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Basics
@_spi(SwiftPMInternal)
@testable import PackageModel

import _InternalTestSupport
import func TSCBasic.withTemporaryFile
import XCTest

Expand Down Expand Up @@ -199,4 +200,90 @@ final class PackageModelTests: XCTestCase {
XCTAssertEqual(compilers.compile, binDirs.first?.appending(expectedExecuable))
}
}

func testDetermineSwiftCompilersWarnsOnInvalidSWIFT_EXEC() throws {
let fs = localFileSystem
try withTemporaryDirectory(removeTreeOnDeinit: true) { tmp in
let toolchainPath = tmp.appending("swift.xctoolchain")
let toolchainBinDir = toolchainPath.appending(components: "usr", "bin")
try fs.createDirectory(toolchainBinDir, recursive: true)

#if os(Windows)
let exeSuffix = ".exe"
#else
let exeSuffix = ""
#endif

// Create a valid swiftc in the toolchain
let validSwiftc = toolchainBinDir.appending("swiftc\(exeSuffix)")
try fs.writeFileContents(validSwiftc, bytes: ByteString(Self.tinyPEBytes))
#if !os(Windows)
try fs.chmod(.executable, path: validSwiftc, options: [])
#endif

// Test 1: SWIFT_EXEC points to non-existent file
do {
let observability = ObservabilitySystem.makeForTesting()
let nonExistentPath = tmp.appending(components: "nonexistent", "path", "to", "swiftc")
let environment: Environment = ["SWIFT_EXEC": nonExistentPath.pathString]

_ = try UserToolchain.determineSwiftCompilers(
binDirectories: [toolchainBinDir],
useXcrun: false,
environment: environment,
searchPaths: [],
fileSystem: fs,
observabilityScope: observability.topScope
)

testDiagnostics(observability.diagnostics) { result in
result.check(diagnostic: .contains("SWIFT_EXEC is set to '\(nonExistentPath.pathString)' but the file does not exist; ignoring"), severity: .warning)
}
}

// Test 2: SWIFT_EXEC points to file that exists but is not executable
do {
let observability = ObservabilitySystem.makeForTesting()
let notExecutablePath = tmp.appending("not-executable")
try fs.writeFileContents(notExecutablePath, bytes: "")
#if !os(Windows)
try fs.chmod(.userUnWritable, path: notExecutablePath, options: [])
#endif

let environment: Environment = ["SWIFT_EXEC": notExecutablePath.pathString]

_ = try UserToolchain.determineSwiftCompilers(
binDirectories: [toolchainBinDir],
useXcrun: false,
environment: environment,
searchPaths: [],
fileSystem: fs,
observabilityScope: observability.topScope
)

testDiagnostics(observability.diagnostics) { result in
result.check(diagnostic: .contains("SWIFT_EXEC is set to '\(notExecutablePath.pathString)' which exists but is not executable; ignoring"), severity: .warning)
}
}

// Test 3: SWIFT_EXEC is not an absolute path and not found in search paths
do {
let observability = ObservabilitySystem.makeForTesting()
let environment: Environment = ["SWIFT_EXEC": "nonexistent-compiler"]

_ = try UserToolchain.determineSwiftCompilers(
binDirectories: [toolchainBinDir],
useXcrun: false,
environment: environment,
searchPaths: [],
fileSystem: fs,
observabilityScope: observability.topScope
)

testDiagnostics(observability.diagnostics) { result in
result.check(diagnostic: .contains("SWIFT_EXEC is set to 'nonexistent-compiler' but no executable was found in search paths; ignoring"), severity: .warning)
}
}
}
}
}