Skip to content
Merged
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
18 changes: 10 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ var targets: [Target] = [
.target(
name: "SKLogging",
dependencies: [
"ToolsProtocolsSwiftExtensions",
"ToolsProtocolsSwiftExtensions"
],
exclude: ["CMakeLists.txt"],
swiftSettings: globalSwiftSettings + lspLoggingSwiftSettings
Expand All @@ -123,7 +123,7 @@ var targets: [Target] = [
swiftSettings: globalSwiftSettings + lspLoggingSwiftSettings + [
.unsafeFlags([
"-module-alias", "ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin",
]),
])
]
),

Expand Down Expand Up @@ -178,11 +178,13 @@ var targets: [Target] = [

// MARK: Command plugins
.plugin(
name: "cmake-smoke-test",
capability: .command(intent: .custom(
verb: "cmake-smoke-test",
description: "Build Swift Build using CMake for validation purposes"
))
name: "cmake-smoke-test",
capability: .command(
intent: .custom(
verb: "cmake-smoke-test",
description: "Build Swift Build using CMake for validation purposes"
)
)
),
]

Expand Down Expand Up @@ -254,7 +256,7 @@ var dependencies: [Package.Dependency] {

return [
// Not a build dependency. Used so the "Format Source Code" command plugin can be used to format sourcekit-lsp
.package(url: "https://github.com/swiftlang/swift-format.git", branch: relatedDependenciesBranch),
.package(url: "https://github.com/swiftlang/swift-format.git", branch: relatedDependenciesBranch)
]
}
}
Expand Down
275 changes: 153 additions & 122 deletions Plugins/cmake-smoke-test/cmake-smoke-test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,156 +10,183 @@
//
//===----------------------------------------------------------------------===//

import PackagePlugin
import Foundation
import PackagePlugin

@main
struct CMakeSmokeTest: CommandPlugin {
func performCommand(context: PluginContext, arguments: [String]) async throws {
var args = ArgumentExtractor(arguments)
func performCommand(context: PluginContext, arguments: [String]) async throws {
var args = ArgumentExtractor(arguments)

guard args.extractFlag(named: "disable-sandbox") > 0 else {
throw Errors.missingRequiredOption("--disable-sandbox")
}

guard let cmakePath = args.extractOption(named: "cmake-path").last else { throw Errors.missingRequiredOption("--cmake-path") }
Diagnostics.progress("using cmake at \(cmakePath)")
let cmakeURL = URL(filePath: cmakePath)
guard let ninjaPath = args.extractOption(named: "ninja-path").last else { throw Errors.missingRequiredOption("--ninja-path") }
Diagnostics.progress("using ninja at \(ninjaPath)")
let ninjaURL = URL(filePath: ninjaPath)
let sysrootPath = args.extractOption(named: "sysroot-path").last
if let sysrootPath {
Diagnostics.progress("using sysroot at \(sysrootPath)")
}
guard args.extractFlag(named: "disable-sandbox") > 0 else {
throw Errors.missingRequiredOption("--disable-sandbox")
}

let extraCMakeArgs = args.extractOption(named: "extra-cmake-arg")
Diagnostics.progress("Extra cmake args: \(extraCMakeArgs.joined(separator: " "))")
guard let cmakePath = args.extractOption(named: "cmake-path").last else {
throw Errors.missingRequiredOption("--cmake-path")
}
Diagnostics.progress("using cmake at \(cmakePath)")
let cmakeURL = URL(filePath: cmakePath)
guard let ninjaPath = args.extractOption(named: "ninja-path").last else {
throw Errors.missingRequiredOption("--ninja-path")
}
Diagnostics.progress("using ninja at \(ninjaPath)")
let ninjaURL = URL(filePath: ninjaPath)
let sysrootPath = args.extractOption(named: "sysroot-path").last
if let sysrootPath {
Diagnostics.progress("using sysroot at \(sysrootPath)")
}

let moduleCachePath = try context.pluginWorkDirectoryURL.appending(component: "module-cache").filePath
let extraCMakeArgs = args.extractOption(named: "extra-cmake-arg")
Diagnostics.progress("Extra cmake args: \(extraCMakeArgs.joined(separator: " "))")

let swiftToolsProtocolsURL = context.package.directoryURL
let swiftToolsProtocolsBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-build")
try Diagnostics.progress("swift-tools-protocols: \(swiftToolsProtocolsURL.filePath)")
let moduleCachePath = try context.pluginWorkDirectoryURL.appending(component: "module-cache").filePath

try FileManager.default.createDirectory(at: swiftToolsProtocolsBuildURL, withIntermediateDirectories: true)
let swiftToolsProtocolsURL = context.package.directoryURL
let swiftToolsProtocolsBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-build")
try Diagnostics.progress("swift-tools-protocols: \(swiftToolsProtocolsURL.filePath)")

var sharedSwiftFlags = [
"-module-cache-path", moduleCachePath
]
try FileManager.default.createDirectory(at: swiftToolsProtocolsBuildURL, withIntermediateDirectories: true)

if let sysrootPath {
sharedSwiftFlags += ["-sdk", sysrootPath]
}
var sharedSwiftFlags = [
"-module-cache-path", moduleCachePath,
]

let sharedCMakeArgs = [
"-G", "Ninja",
"-DCMAKE_MAKE_PROGRAM=\(ninjaPath)",
"-DCMAKE_BUILD_TYPE:=Debug",
"-DCMAKE_Swift_FLAGS='\(sharedSwiftFlags.joined(separator: " "))'"
] + extraCMakeArgs

Diagnostics.progress("Building swift-tools-protocols")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftToolsProtocolsURL.filePath], workingDirectory: swiftToolsProtocolsBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftToolsProtocolsBuildURL)
Diagnostics.progress("Built swift-tools-protocols")
if let sysrootPath {
sharedSwiftFlags += ["-sdk", sysrootPath]
}

let sharedCMakeArgs =
[
"-G", "Ninja",
"-DCMAKE_MAKE_PROGRAM=\(ninjaPath)",
"-DCMAKE_BUILD_TYPE:=Debug",
"-DCMAKE_Swift_FLAGS='\(sharedSwiftFlags.joined(separator: " "))'",
] + extraCMakeArgs

Diagnostics.progress("Building swift-tools-protocols")
try await Process.checkNonZeroExit(
url: cmakeURL,
arguments: sharedCMakeArgs + [swiftToolsProtocolsURL.filePath],
workingDirectory: swiftToolsProtocolsBuildURL
)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftToolsProtocolsBuildURL)
Diagnostics.progress("Built swift-tools-protocols")
}
}

enum Errors: Error {
case processError(terminationReason: Process.TerminationReason, terminationStatus: Int32)
case missingRequiredOption(String)
case miscError(String)
case processError(terminationReason: Process.TerminationReason, terminationStatus: Int32)
case missingRequiredOption(String)
case miscError(String)
}

extension URL {
var filePath: String {
get throws {
try withUnsafeFileSystemRepresentation { path in
guard let path else {
throw Errors.miscError("cannot get file path for URL: \(self)")
}
return String(cString: path)
}
var filePath: String {
get throws {
try withUnsafeFileSystemRepresentation { path in
guard let path else {
throw Errors.miscError("cannot get file path for URL: \(self)")
}
return String(cString: path)
}
}
}
}

extension Process {
func run() async throws {
try await withCheckedThrowingContinuation { continuation in
terminationHandler = { _ in
continuation.resume()
}

do {
try run()
} catch {
terminationHandler = nil
continuation.resume(throwing: error)
}
}
func run() async throws {
try await withCheckedThrowingContinuation { continuation in
terminationHandler = { _ in
continuation.resume()
}

do {
try run()
} catch {
terminationHandler = nil
continuation.resume(throwing: error)
}
}
}

static func checkNonZeroExit(url: URL, arguments: [String], workingDirectory: URL, environment: [String: String]? = nil) async throws {
try Diagnostics.progress("\(url.filePath) \(arguments.joined(separator: " "))")
#if USE_PROCESS_SPAWNING_WORKAROUND && !os(Windows)
Diagnostics.progress("Using process spawning workaround")
// Linux workaround for https://github.com/swiftlang/swift-corelibs-foundation/issues/4772
// Foundation.Process on Linux seems to inherit the Process.run()-calling thread's signal mask, creating processes that even have SIGTERM blocked
// This manifests as CMake getting stuck when invoking 'uname' with incorrectly configured signal handlers.
var fileActions = posix_spawn_file_actions_t()
defer { posix_spawn_file_actions_destroy(&fileActions) }
var attrs: posix_spawnattr_t = posix_spawnattr_t()
defer { posix_spawnattr_destroy(&attrs) }
posix_spawn_file_actions_init(&fileActions)
try posix_spawn_file_actions_addchdir_np(&fileActions, workingDirectory.filePath)

posix_spawnattr_init(&attrs)
posix_spawnattr_setpgroup(&attrs, 0)
var noSignals = sigset_t()
sigemptyset(&noSignals)
posix_spawnattr_setsigmask(&attrs, &noSignals)

var mostSignals = sigset_t()
sigemptyset(&mostSignals)
for i in 1 ..< SIGSYS {
if i == SIGKILL || i == SIGSTOP {
continue
}
sigaddset(&mostSignals, i)
static func checkNonZeroExit(
url: URL,
arguments: [String],
workingDirectory: URL,
environment: [String: String]? = nil
) async throws {
try Diagnostics.progress("\(url.filePath) \(arguments.joined(separator: " "))")
#if USE_PROCESS_SPAWNING_WORKAROUND && !os(Windows)
Diagnostics.progress("Using process spawning workaround")
// Linux workaround for https://github.com/swiftlang/swift-corelibs-foundation/issues/4772
// Foundation.Process on Linux seems to inherit the Process.run()-calling thread's signal mask, creating processes that even have SIGTERM blocked
// This manifests as CMake getting stuck when invoking 'uname' with incorrectly configured signal handlers.
var fileActions = posix_spawn_file_actions_t()
defer { posix_spawn_file_actions_destroy(&fileActions) }
var attrs: posix_spawnattr_t = posix_spawnattr_t()
defer { posix_spawnattr_destroy(&attrs) }
posix_spawn_file_actions_init(&fileActions)
try posix_spawn_file_actions_addchdir_np(&fileActions, workingDirectory.filePath)

posix_spawnattr_init(&attrs)
posix_spawnattr_setpgroup(&attrs, 0)
var noSignals = sigset_t()
sigemptyset(&noSignals)
posix_spawnattr_setsigmask(&attrs, &noSignals)

var mostSignals = sigset_t()
sigemptyset(&mostSignals)
for i in 1..<SIGSYS {
if i == SIGKILL || i == SIGSTOP {
continue
}
sigaddset(&mostSignals, i)
}
posix_spawnattr_setsigdefault(&attrs, &mostSignals)
posix_spawnattr_setflags(
&attrs,
numericCast(POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK)
)
var pid: pid_t = -1
try withArrayOfCStrings([url.filePath] + arguments) { arguments in
try withArrayOfCStrings((environment ?? [:]).map { key, value in "\(key)=\(value)" }) { environment in
let spawnResult = try posix_spawn(
&pid,
url.filePath, /*file_actions=*/
&fileActions, /*attrp=*/
&attrs,
arguments,
nil
);
var exitCode: Int32 = -1
var result = wait4(pid, &exitCode, 0, nil);
while result == -1 && errno == EINTR {
result = wait4(pid, &exitCode, 0, nil)
}
posix_spawnattr_setsigdefault(&attrs, &mostSignals)
posix_spawnattr_setflags(&attrs, numericCast(POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK))
var pid: pid_t = -1
try withArrayOfCStrings([url.filePath] + arguments) { arguments in
try withArrayOfCStrings((environment ?? [:]).map { key, value in "\(key)=\(value)" }) { environment in
let spawnResult = try posix_spawn(&pid, url.filePath, /*file_actions=*/&fileActions, /*attrp=*/&attrs, arguments, nil);
var exitCode: Int32 = -1
var result = wait4(pid, &exitCode, 0, nil);
while (result == -1 && errno == EINTR) {
result = wait4(pid, &exitCode, 0, nil)
}
guard result != -1 else {
throw Errors.miscError("wait failed")
}
guard exitCode == 0 else {
throw Errors.miscError("exit code nonzero")
}
}
guard result != -1 else {
throw Errors.miscError("wait failed")
}
#else
let process = Process()
process.executableURL = url
process.arguments = arguments
process.currentDirectoryURL = workingDirectory
process.environment = environment
try await process.run()
if process.terminationStatus != 0 {
throw Errors.processError(terminationReason: process.terminationReason, terminationStatus: process.terminationStatus)
guard exitCode == 0 else {
throw Errors.miscError("exit code nonzero")
}
#endif
}
}
#else
let process = Process()
process.executableURL = url
process.arguments = arguments
process.currentDirectoryURL = workingDirectory
process.environment = environment
try await process.run()
if process.terminationStatus != 0 {
throw Errors.processError(
terminationReason: process.terminationReason,
terminationStatus: process.terminationStatus
)
}
#endif
}
}

#if USE_PROCESS_SPAWNING_WORKAROUND && !os(Windows)
Expand Down Expand Up @@ -190,12 +217,16 @@ func withArrayOfCStrings<T>(
return try argsBuffer.withUnsafeMutableBufferPointer {
(argsBuffer) in
let ptr = UnsafeRawPointer(argsBuffer.baseAddress!).bindMemory(
to: Int8.self, capacity: argsBuffer.count)
to: Int8.self,
capacity: argsBuffer.count
)
var cStrings: [UnsafePointer<Int8>?] = argsOffsets.map { ptr + $0 }
cStrings[cStrings.count - 1] = nil
return try cStrings.withUnsafeMutableBufferPointer {
let unsafeString = UnsafeMutableRawPointer($0.baseAddress!).bindMemory(
to: UnsafeMutablePointer<Int8>?.self, capacity: $0.count)
to: UnsafeMutablePointer<Int8>?.self,
capacity: $0.count
)
return try body(unsafeString)
}
}
Expand Down
Loading
Loading