Skip to content

Commit 6a0f7f2

Browse files
authored
Merge pull request #487 from swiftlang/automerge/merge-main-2025-05-05_09-02
Merge `main` into `release/6.2`
2 parents 7ce677e + 316345e commit 6a0f7f2

38 files changed

+815
-288
lines changed

.github/workflows/pull_request.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,52 @@ jobs:
1414
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
1515
with:
1616
linux_os_versions: '["noble", "jammy", "focal", "rhel-ubi9"]'
17-
linux_pre_build_command: command -v apt >/dev/null 2>&1 && apt update && apt install -y libsqlite3-dev libncurses-dev || (command -v yum >/dev/null 2>&1 && yum update -y && yum install -y sqlite-devel ncurses-devel)
17+
linux_pre_build_command: |
18+
if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy, focal
19+
apt-get update -y
20+
21+
# Build dependencies
22+
apt-get install -y libsqlite3-dev libncurses-dev
23+
24+
# Debug symbols
25+
apt-get install -y libc6-dbg
26+
elif command -v dnf >/dev/null 2>&1 ; then # rhel-ubi9
27+
dnf update -y
28+
29+
# Build dependencies
30+
dnf install -y sqlite-devel ncurses-devel
31+
32+
# Debug symbols
33+
dnf debuginfo-install -y glibc
34+
elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2
35+
yum update -y
36+
37+
# Build dependencies
38+
yum install -y sqlite-devel ncurses-devel
39+
40+
# Debug symbols
41+
yum install -y yum-utils
42+
debuginfo-install -y glibc
43+
fi
1844
linux_build_command: 'swift test --no-parallel'
1945
linux_swift_versions: '["nightly-main", "nightly-6.2"]'
2046
windows_swift_versions: '["nightly-main"]'
2147
windows_build_command: 'swift test --no-parallel'
48+
cmake-smoke-test:
49+
name: cmake-smoke-test
50+
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
51+
with:
52+
linux_os_versions: '["noble"]'
53+
linux_pre_build_command: |
54+
apt-get update -y
55+
56+
# Build dependencies
57+
apt-get install -y libsqlite3-dev libncurses-dev
58+
59+
apt-get install -y cmake ninja-build
60+
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`'
61+
linux_swift_versions: '["nightly-main"]'
62+
windows_swift_versions: '[]'
2263
soundness:
2364
name: Soundness
2465
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main

Plugins/cmake-smoke-test/cmake-smoke-test.swift

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,24 @@ struct CMakeSmokeTest: CommandPlugin {
2424
}
2525

2626
guard let cmakePath = args.extractOption(named: "cmake-path").last else { throw Errors.missingRequiredOption("--cmake-path") }
27-
print("using cmake at \(cmakePath)")
27+
Diagnostics.progress("using cmake at \(cmakePath)")
2828
let cmakeURL = URL(filePath: cmakePath)
2929
guard let ninjaPath = args.extractOption(named: "ninja-path").last else { throw Errors.missingRequiredOption("--ninja-path") }
30-
print("using ninja at \(ninjaPath)")
30+
Diagnostics.progress("using ninja at \(ninjaPath)")
3131
let ninjaURL = URL(filePath: ninjaPath)
3232
let sysrootPath = args.extractOption(named: "sysroot-path").last
3333
if let sysrootPath {
34-
print("using sysroot at \(sysrootPath)")
34+
Diagnostics.progress("using sysroot at \(sysrootPath)")
3535
}
3636

37+
let extraCMakeArgs = args.extractOption(named: "extra-cmake-arg")
38+
Diagnostics.progress("Extra cmake args: \(extraCMakeArgs.joined(separator: " "))")
39+
3740
let moduleCachePath = context.pluginWorkDirectoryURL.appending(component: "module-cache").path()
3841

3942
let swiftBuildURL = context.package.directoryURL
4043
let swiftBuildBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-build")
41-
print("swift-build: \(swiftBuildURL.path())")
44+
Diagnostics.progress("swift-build: \(swiftBuildURL.path())")
4245

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

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

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

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

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

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

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

118121
func findDependency(_ name: String, pluginContext: PluginContext) throws -> URL {
@@ -132,7 +135,7 @@ struct CMakeSmokeTest: CommandPlugin {
132135
throw Errors.missingRepository(name)
133136
}
134137
let dependencyURL = dependency.directoryURL
135-
print("\(name): \(dependencyURL.path())")
138+
Diagnostics.progress("\(name): \(dependencyURL.path())")
136139
guard FileManager.default.fileExists(atPath: dependencyURL.path()) else {
137140
throw Errors.missingRepository(dependencyURL.path())
138141
}
@@ -145,6 +148,7 @@ enum Errors: Error {
145148
case missingRequiredOption(String)
146149
case missingRepository(String)
147150
case unimplementedForHostOS
151+
case miscError(String)
148152
}
149153

150154
enum OS {
@@ -182,7 +186,53 @@ extension Process {
182186
}
183187

184188
static func checkNonZeroExit(url: URL, arguments: [String], workingDirectory: URL, environment: [String: String]? = nil) async throws {
185-
print("\(url.path()) \(arguments.joined(separator: " "))")
189+
Diagnostics.progress("\(url.path()) \(arguments.joined(separator: " "))")
190+
#if USE_PROCESS_SPAWNING_WORKAROUND
191+
Diagnostics.progress("Using process spawning workaround")
192+
// Linux workaround for https://github.com/swiftlang/swift-corelibs-foundation/issues/4772
193+
// Foundation.Process on Linux seems to inherit the Process.run()-calling thread's signal mask, creating processes that even have SIGTERM blocked
194+
// This manifests as CMake hanging when invoking 'uname' with incorrectly configured signal handlers.
195+
var fileActions = posix_spawn_file_actions_t()
196+
defer { posix_spawn_file_actions_destroy(&fileActions) }
197+
var attrs: posix_spawnattr_t = posix_spawnattr_t()
198+
defer { posix_spawnattr_destroy(&attrs) }
199+
posix_spawn_file_actions_init(&fileActions)
200+
posix_spawn_file_actions_addchdir_np(&fileActions, workingDirectory.path())
201+
202+
posix_spawnattr_init(&attrs)
203+
posix_spawnattr_setpgroup(&attrs, 0)
204+
var noSignals = sigset_t()
205+
sigemptyset(&noSignals)
206+
posix_spawnattr_setsigmask(&attrs, &noSignals)
207+
208+
var mostSignals = sigset_t()
209+
sigemptyset(&mostSignals)
210+
for i in 1 ..< SIGSYS {
211+
if i == SIGKILL || i == SIGSTOP {
212+
continue
213+
}
214+
sigaddset(&mostSignals, i)
215+
}
216+
posix_spawnattr_setsigdefault(&attrs, &mostSignals)
217+
posix_spawnattr_setflags(&attrs, numericCast(POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK))
218+
var pid: pid_t = -1
219+
try withArrayOfCStrings([url.path()] + arguments) { arguments in
220+
try withArrayOfCStrings((environment ?? [:]).map { key, value in "\(key)=\(value)" }) { environment in
221+
let spawnResult = posix_spawn(&pid, url.path(), /*file_actions=*/&fileActions, /*attrp=*/&attrs, arguments, nil);
222+
var exitCode: Int32 = -1
223+
var result = wait4(pid, &exitCode, 0, nil);
224+
while (result == -1 && errno == EINTR) {
225+
result = wait4(pid, &exitCode, 0, nil)
226+
}
227+
guard result != -1 else {
228+
throw Errors.miscError("wait failed")
229+
}
230+
guard exitCode == 0 else {
231+
throw Errors.miscError("exit code nonzero")
232+
}
233+
}
234+
}
235+
#else
186236
let process = Process()
187237
process.executableURL = url
188238
process.arguments = arguments
@@ -192,5 +242,44 @@ extension Process {
192242
if process.terminationStatus != 0 {
193243
throw Errors.processError(terminationReason: process.terminationReason, terminationStatus: process.terminationStatus)
194244
}
245+
#endif
246+
}
247+
}
248+
249+
func scan<S: Sequence, U>(_ seq: S, _ initial: U, _ combine: (U, S.Element) -> U) -> [U] {
250+
var result: [U] = []
251+
result.reserveCapacity(seq.underestimatedCount)
252+
var runningResult = initial
253+
for element in seq {
254+
runningResult = combine(runningResult, element)
255+
result.append(runningResult)
256+
}
257+
return result
258+
}
259+
260+
func withArrayOfCStrings<T>(
261+
_ args: [String],
262+
_ body: (UnsafePointer<UnsafeMutablePointer<Int8>?>) throws -> T
263+
) throws -> T {
264+
let argsCounts = Array(args.map { $0.utf8.count + 1 })
265+
let argsOffsets = [0] + scan(argsCounts, 0, +)
266+
let argsBufferSize = argsOffsets.last!
267+
var argsBuffer: [UInt8] = []
268+
argsBuffer.reserveCapacity(argsBufferSize)
269+
for arg in args {
270+
argsBuffer.append(contentsOf: arg.utf8)
271+
argsBuffer.append(0)
272+
}
273+
return try argsBuffer.withUnsafeMutableBufferPointer {
274+
(argsBuffer) in
275+
let ptr = UnsafeRawPointer(argsBuffer.baseAddress!).bindMemory(
276+
to: Int8.self, capacity: argsBuffer.count)
277+
var cStrings: [UnsafePointer<Int8>?] = argsOffsets.map { ptr + $0 }
278+
cStrings[cStrings.count - 1] = nil
279+
return try cStrings.withUnsafeMutableBufferPointer {
280+
let unsafeString = UnsafeMutableRawPointer($0.baseAddress!).bindMemory(
281+
to: UnsafeMutablePointer<Int8>?.self, capacity: $0.count)
282+
return try body(unsafeString)
195283
}
284+
}
196285
}

Sources/SWBBuildService/BuildDependencyInfo.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ package struct BuildDependencyInfo: Codable {
117117
package enum LibraryType: String, Codable, Sendable {
118118
case dynamic
119119
case `static`
120+
case upward
120121
case unknown
121122
}
122123

@@ -434,9 +435,11 @@ extension BuildDependencyInfo {
434435
/// - remark: This is written somewhat generically (with the callback blocks) in the hopes that `LinkageDependencyResolver.dependencies(for:...)` can someday adopt it, as the general approach was stolen from there.
435436
package static func findLinkedInputsFromBuildSettings(_ settings: Settings, addFramework: @Sendable (TargetDependencyInfo.Input) async -> Void, addLibrary: @Sendable (TargetDependencyInfo.Input) async -> Void, addError: @Sendable (String) async -> Void) async {
436437
await LdLinkerSpec.processLinkerSettingsForLibraryOptions(settings: settings) { macro, flag, stem in
437-
await addFramework(TargetDependencyInfo.Input(inputType: .framework, name: .stem(stem), linkType: .searchPath, libraryType: .dynamic))
438+
let libType: TargetDependencyInfo.Input.LibraryType = (flag == "-upward_framework") ? .upward : .dynamic
439+
await addFramework(TargetDependencyInfo.Input(inputType: .framework, name: .stem(stem), linkType: .searchPath, libraryType: libType))
438440
} addLibrary: { macro, flag, stem in
439-
await addLibrary(TargetDependencyInfo.Input(inputType: .library, name: .stem(stem), linkType: .searchPath, libraryType: .unknown))
441+
let libType: TargetDependencyInfo.Input.LibraryType = (flag == "-upward-l") ? .upward : .unknown
442+
await addLibrary(TargetDependencyInfo.Input(inputType: .library, name: .stem(stem), linkType: .searchPath, libraryType: libType))
440443
} addError: { error in
441444
await addError(error)
442445
}

Sources/SWBBuildSystem/BuildOperation.swift

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ package final class BuildOperation: BuildSystemOperation {
419419
}
420420

421421
// Perform any needed steps before we kick off the build.
422-
if let (warnings, errors) = prepareForBuilding() {
422+
if let (warnings, errors) = await prepareForBuilding() {
423423
// Emit any warnings and errors. If there were any errors, then bail out.
424424
for message in warnings { buildOutputDelegate.warning(message) }
425425
for message in errors { buildOutputDelegate.error(message) }
@@ -809,7 +809,7 @@ package final class BuildOperation: BuildSystemOperation {
809809
return delegate.buildComplete(self, status: effectiveStatus, delegate: buildOutputDelegate, metrics: .init(counters: aggregatedCounters))
810810
}
811811

812-
func prepareForBuilding() -> ([String], [String])? {
812+
func prepareForBuilding() async -> ([String], [String])? {
813813
let warnings = [String]() // Not presently used
814814
var errors = [String]()
815815

@@ -829,9 +829,61 @@ package final class BuildOperation: BuildSystemOperation {
829829
}
830830
}
831831

832+
if UserDefaults.enableCASValidation {
833+
for info in buildDescription.casValidationInfos {
834+
do {
835+
try await validateCAS(info)
836+
} catch {
837+
errors.append("cas validation failed for \(info.options.casPath.str)")
838+
}
839+
}
840+
}
841+
832842
return (warnings.count > 0 || errors.count > 0) ? (warnings, errors) : nil
833843
}
834844

845+
func validateCAS(_ info: BuildDescription.CASValidationInfo) async throws {
846+
assert(UserDefaults.enableCASValidation)
847+
848+
let casPath = info.options.casPath
849+
let ruleInfo = "ValidateCAS \(casPath.str) \(info.llvmCasExec.str)"
850+
851+
let signatureCtx = InsecureHashContext()
852+
signatureCtx.add(string: "ValidateCAS")
853+
signatureCtx.add(string: casPath.str)
854+
signatureCtx.add(string: info.llvmCasExec.str)
855+
let signature = signatureCtx.signature
856+
857+
let activityId = delegate.beginActivity(self, ruleInfo: ruleInfo, executionDescription: "Validate CAS contents at \(casPath.str)", signature: signature, target: nil, parentActivity: nil)
858+
var status: BuildOperationTaskEnded.Status = .failed
859+
defer {
860+
delegate.endActivity(self, id: activityId, signature: signature, status: status)
861+
}
862+
863+
var commandLine = [
864+
info.llvmCasExec.str,
865+
"-cas", casPath.str,
866+
"-validate-if-needed",
867+
"-check-hash",
868+
"-allow-recovery",
869+
]
870+
if let pluginPath = info.options.pluginPath {
871+
commandLine.append(contentsOf: [
872+
"-fcas-plugin-path", pluginPath.str
873+
])
874+
}
875+
let result: Processes.ExecutionResult = try await clientDelegate.executeExternalTool(commandLine: commandLine)
876+
// In a task we might use a discovered tool info to detect if the tool supports validation, but without that scaffolding, just check the specific error.
877+
if result.exitStatus == .exit(1) && result.stderr.contains(ByteString("Unknown command line argument '-validate-if-needed'")) {
878+
delegate.emit(data: ByteString("validation not supported").bytes, for: activityId, signature: signature)
879+
status = .succeeded
880+
} else {
881+
delegate.emit(data: ByteString(result.stderr).bytes, for: activityId, signature: signature)
882+
delegate.emit(data: ByteString(result.stdout).bytes, for: activityId, signature: signature)
883+
status = result.exitStatus.isSuccess ? .succeeded : result.exitStatus.wasCanceled ? .cancelled : .failed
884+
}
885+
}
886+
835887
/// Cancel the executing build operation.
836888
package func cancel() {
837889
queue.blocking_sync() {
@@ -1850,15 +1902,19 @@ internal final class OperationSystemAdaptor: SWBLLBuild.BuildSystemDelegate, Act
18501902
return
18511903
}
18521904

1905+
guard let outputDelegate = (queue.blocking_sync { self.commandOutputDelegates.removeValue(forKey: command) }) else {
1906+
// If there's no outputDelegate, the command never started (i.e. it was skipped by shouldCommandStart().
1907+
return
1908+
}
1909+
1910+
// We can call this here because we're on an llbuild worker thread. This shouldn't be used while on `self.queue` because we have Swift async work elsewhere which blocks on that queue.
1911+
let sandboxViolations = task.isSandboxed && result == .failed ? task.extractSandboxViolationMessages_ASYNC_UNSAFE(startTime: outputDelegate.startTime) : []
1912+
18531913
queue.async {
1854-
// Find the output delegate, and remove it from the active set.
1855-
guard let outputDelegate = self.commandOutputDelegates.removeValue(forKey: command) else {
1856-
// If there's no outputDelegate, the command never started (i.e. it was skipped by shouldCommandStart().
1857-
return
1914+
for message in sandboxViolations {
1915+
outputDelegate.emit(Diagnostic(behavior: .error, location: .unknown, data: DiagnosticData(message)))
18581916
}
18591917

1860-
outputDelegate.emitSandboxingViolations(task: task, commandResult: result)
1861-
18621918
// This may be updated by commandProcessFinished if it was an
18631919
// ExternalCommand, so only update the exit status in output delegate if
18641920
// it is nil. However, always update the status if the result is failed,

0 commit comments

Comments
 (0)