Skip to content

Setup a basic run of cmake-smoke-test in CI #480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 5, 2025
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
15 changes: 15 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,21 @@ jobs:
linux_swift_versions: '["nightly-main", "nightly-6.2"]'
windows_swift_versions: '["nightly-main"]'
windows_build_command: 'swift test --no-parallel'
cmake-smoke-test:
name: cmake-smoke-test
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
with:
linux_os_versions: '["noble"]'
linux_pre_build_command: |
apt-get update -y

# Build dependencies
apt-get install -y libsqlite3-dev libncurses-dev

apt-get install -y cmake ninja-build
linux_build_command: 'swift package -Xbuild-tools-swiftc -DUSE_PROCESS_SPAWNING_WORKAROUND cmake-smoke-test --disable-sandbox --cmake-path `which cmake` --ninja-path `which ninja` --extra-cmake-arg -DCMAKE_C_COMPILER=`which clang` --extra-cmake-arg -DCMAKE_CXX_COMPILER=`which clang++` --extra-cmake-arg -DCMAKE_Swift_COMPILER=`which swiftc`'
linux_swift_versions: '["nightly-main"]'
windows_swift_versions: '[]'
soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
Expand Down
127 changes: 108 additions & 19 deletions Plugins/cmake-smoke-test/cmake-smoke-test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,24 @@ struct CMakeSmokeTest: CommandPlugin {
}

guard let cmakePath = args.extractOption(named: "cmake-path").last else { throw Errors.missingRequiredOption("--cmake-path") }
print("using cmake at \(cmakePath)")
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") }
print("using ninja at \(ninjaPath)")
Diagnostics.progress("using ninja at \(ninjaPath)")
let ninjaURL = URL(filePath: ninjaPath)
let sysrootPath = args.extractOption(named: "sysroot-path").last
if let sysrootPath {
print("using sysroot at \(sysrootPath)")
Diagnostics.progress("using sysroot at \(sysrootPath)")
}

let extraCMakeArgs = args.extractOption(named: "extra-cmake-arg")
Diagnostics.progress("Extra cmake args: \(extraCMakeArgs.joined(separator: " "))")

let moduleCachePath = context.pluginWorkDirectoryURL.appending(component: "module-cache").path()

let swiftBuildURL = context.package.directoryURL
let swiftBuildBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-build")
print("swift-build: \(swiftBuildURL.path())")
Diagnostics.progress("swift-build: \(swiftBuildURL.path())")

let swiftToolsSupportCoreURL = try findDependency("swift-tools-support-core", pluginContext: context)
let swiftToolsSupportCoreBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-tools-support-core")
Expand Down Expand Up @@ -80,39 +83,39 @@ struct CMakeSmokeTest: CommandPlugin {
"-DCMAKE_MAKE_PROGRAM=\(ninjaPath)",
"-DCMAKE_BUILD_TYPE:=Debug",
"-DCMAKE_Swift_FLAGS='\(sharedSwiftFlags.joined(separator: " "))'"
] + cMakeProjectArgs
] + cMakeProjectArgs + extraCMakeArgs

print("Building swift-tools-support-core")
Diagnostics.progress("Building swift-tools-support-core")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftToolsSupportCoreURL.path()], workingDirectory: swiftToolsSupportCoreBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftToolsSupportCoreBuildURL)
print("Built swift-tools-support-core")
Diagnostics.progress("Built swift-tools-support-core")

if hostOS != .macOS {
print("Building swift-system")
Diagnostics.progress("Building swift-system")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftSystemURL.path()], workingDirectory: swiftSystemBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftSystemBuildURL)
print("Built swift-system")
Diagnostics.progress("Built swift-system")
}

print("Building llbuild")
Diagnostics.progress("Building llbuild")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + ["-DLLBUILD_SUPPORT_BINDINGS:=Swift", llbuildURL.path()], workingDirectory: llbuildBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: llbuildBuildURL)
print("Built llbuild")
Diagnostics.progress("Built llbuild")

print("Building swift-argument-parser")
Diagnostics.progress("Building swift-argument-parser")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + ["-DBUILD_TESTING=NO", "-DBUILD_EXAMPLES=NO", swiftArgumentParserURL.path()], workingDirectory: swiftArgumentParserBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftArgumentParserBuildURL)
print("Built swift-argument-parser")
Diagnostics.progress("Built swift-argument-parser")

print("Building swift-driver")
Diagnostics.progress("Building swift-driver")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftDriverURL.path()], workingDirectory: swiftDriverBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftDriverBuildURL)
print("Built swift-driver")
Diagnostics.progress("Built swift-driver")

print("Building swift-build in \(swiftBuildBuildURL)")
Diagnostics.progress("Building swift-build in \(swiftBuildBuildURL)")
try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftBuildURL.path()], workingDirectory: swiftBuildBuildURL)
try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftBuildBuildURL)
print("Built swift-build")
Diagnostics.progress("Built swift-build")
}

func findDependency(_ name: String, pluginContext: PluginContext) throws -> URL {
Expand All @@ -132,7 +135,7 @@ struct CMakeSmokeTest: CommandPlugin {
throw Errors.missingRepository(name)
}
let dependencyURL = dependency.directoryURL
print("\(name): \(dependencyURL.path())")
Diagnostics.progress("\(name): \(dependencyURL.path())")
guard FileManager.default.fileExists(atPath: dependencyURL.path()) else {
throw Errors.missingRepository(dependencyURL.path())
}
Expand All @@ -145,6 +148,7 @@ enum Errors: Error {
case missingRequiredOption(String)
case missingRepository(String)
case unimplementedForHostOS
case miscError(String)
}

enum OS {
Expand Down Expand Up @@ -182,7 +186,53 @@ extension Process {
}

static func checkNonZeroExit(url: URL, arguments: [String], workingDirectory: URL, environment: [String: String]? = nil) async throws {
print("\(url.path()) \(arguments.joined(separator: " "))")
Diagnostics.progress("\(url.path()) \(arguments.joined(separator: " "))")
#if USE_PROCESS_SPAWNING_WORKAROUND
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 hanging 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)
posix_spawn_file_actions_addchdir_np(&fileActions, workingDirectory.path())

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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..<SIGRTMAX?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think anything is using the realtime signals, and there could be a gap between SIGSYS and SIGRTMIN on some implementations

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.path()] + arguments) { arguments in
try withArrayOfCStrings((environment ?? [:]).map { key, value in "\(key)=\(value)" }) { environment in
let spawnResult = posix_spawn(&pid, url.path(), /*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")
}
}
}
#else
let process = Process()
process.executableURL = url
process.arguments = arguments
Expand All @@ -192,5 +242,44 @@ extension Process {
if process.terminationStatus != 0 {
throw Errors.processError(terminationReason: process.terminationReason, terminationStatus: process.terminationStatus)
}
#endif
}
}

func scan<S: Sequence, U>(_ seq: S, _ initial: U, _ combine: (U, S.Element) -> U) -> [U] {
var result: [U] = []
result.reserveCapacity(seq.underestimatedCount)
var runningResult = initial
for element in seq {
runningResult = combine(runningResult, element)
result.append(runningResult)
}
return result
}

func withArrayOfCStrings<T>(
_ args: [String],
_ body: (UnsafePointer<UnsafeMutablePointer<Int8>?>) throws -> T
) throws -> T {
let argsCounts = Array(args.map { $0.utf8.count + 1 })
let argsOffsets = [0] + scan(argsCounts, 0, +)
let argsBufferSize = argsOffsets.last!
var argsBuffer: [UInt8] = []
argsBuffer.reserveCapacity(argsBufferSize)
for arg in args {
argsBuffer.append(contentsOf: arg.utf8)
argsBuffer.append(0)
}
return try argsBuffer.withUnsafeMutableBufferPointer {
(argsBuffer) in
let ptr = UnsafeRawPointer(argsBuffer.baseAddress!).bindMemory(
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)
return try body(unsafeString)
}
}
}
Loading