|
10 | 10 | // |
11 | 11 | //===----------------------------------------------------------------------===// |
12 | 12 |
|
13 | | -import PackagePlugin |
14 | 13 | import Foundation |
| 14 | +import PackagePlugin |
15 | 15 |
|
16 | 16 | @main |
17 | 17 | struct CMakeSmokeTest: CommandPlugin { |
18 | | - func performCommand(context: PluginContext, arguments: [String]) async throws { |
19 | | - var args = ArgumentExtractor(arguments) |
| 18 | + func performCommand(context: PluginContext, arguments: [String]) async throws { |
| 19 | + var args = ArgumentExtractor(arguments) |
20 | 20 |
|
21 | | - guard args.extractFlag(named: "disable-sandbox") > 0 else { |
22 | | - throw Errors.missingRequiredOption("--disable-sandbox") |
23 | | - } |
24 | | - |
25 | | - guard let cmakePath = args.extractOption(named: "cmake-path").last else { throw Errors.missingRequiredOption("--cmake-path") } |
26 | | - Diagnostics.progress("using cmake at \(cmakePath)") |
27 | | - let cmakeURL = URL(filePath: cmakePath) |
28 | | - guard let ninjaPath = args.extractOption(named: "ninja-path").last else { throw Errors.missingRequiredOption("--ninja-path") } |
29 | | - Diagnostics.progress("using ninja at \(ninjaPath)") |
30 | | - let ninjaURL = URL(filePath: ninjaPath) |
31 | | - let sysrootPath = args.extractOption(named: "sysroot-path").last |
32 | | - if let sysrootPath { |
33 | | - Diagnostics.progress("using sysroot at \(sysrootPath)") |
34 | | - } |
| 21 | + guard args.extractFlag(named: "disable-sandbox") > 0 else { |
| 22 | + throw Errors.missingRequiredOption("--disable-sandbox") |
| 23 | + } |
35 | 24 |
|
36 | | - let extraCMakeArgs = args.extractOption(named: "extra-cmake-arg") |
37 | | - Diagnostics.progress("Extra cmake args: \(extraCMakeArgs.joined(separator: " "))") |
| 25 | + guard let cmakePath = args.extractOption(named: "cmake-path").last else { |
| 26 | + throw Errors.missingRequiredOption("--cmake-path") |
| 27 | + } |
| 28 | + Diagnostics.progress("using cmake at \(cmakePath)") |
| 29 | + let cmakeURL = URL(filePath: cmakePath) |
| 30 | + guard let ninjaPath = args.extractOption(named: "ninja-path").last else { |
| 31 | + throw Errors.missingRequiredOption("--ninja-path") |
| 32 | + } |
| 33 | + Diagnostics.progress("using ninja at \(ninjaPath)") |
| 34 | + let ninjaURL = URL(filePath: ninjaPath) |
| 35 | + let sysrootPath = args.extractOption(named: "sysroot-path").last |
| 36 | + if let sysrootPath { |
| 37 | + Diagnostics.progress("using sysroot at \(sysrootPath)") |
| 38 | + } |
38 | 39 |
|
39 | | - let moduleCachePath = try context.pluginWorkDirectoryURL.appending(component: "module-cache").filePath |
| 40 | + let extraCMakeArgs = args.extractOption(named: "extra-cmake-arg") |
| 41 | + Diagnostics.progress("Extra cmake args: \(extraCMakeArgs.joined(separator: " "))") |
40 | 42 |
|
41 | | - let swiftToolsProtocolsURL = context.package.directoryURL |
42 | | - let swiftToolsProtocolsBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-build") |
43 | | - try Diagnostics.progress("swift-tools-protocols: \(swiftToolsProtocolsURL.filePath)") |
| 43 | + let moduleCachePath = try context.pluginWorkDirectoryURL.appending(component: "module-cache").filePath |
44 | 44 |
|
45 | | - try FileManager.default.createDirectory(at: swiftToolsProtocolsBuildURL, withIntermediateDirectories: true) |
| 45 | + let swiftToolsProtocolsURL = context.package.directoryURL |
| 46 | + let swiftToolsProtocolsBuildURL = context.pluginWorkDirectoryURL.appending(component: "swift-build") |
| 47 | + try Diagnostics.progress("swift-tools-protocols: \(swiftToolsProtocolsURL.filePath)") |
46 | 48 |
|
47 | | - var sharedSwiftFlags = [ |
48 | | - "-module-cache-path", moduleCachePath |
49 | | - ] |
| 49 | + try FileManager.default.createDirectory(at: swiftToolsProtocolsBuildURL, withIntermediateDirectories: true) |
50 | 50 |
|
51 | | - if let sysrootPath { |
52 | | - sharedSwiftFlags += ["-sdk", sysrootPath] |
53 | | - } |
| 51 | + var sharedSwiftFlags = [ |
| 52 | + "-module-cache-path", moduleCachePath, |
| 53 | + ] |
54 | 54 |
|
55 | | - let sharedCMakeArgs = [ |
56 | | - "-G", "Ninja", |
57 | | - "-DCMAKE_MAKE_PROGRAM=\(ninjaPath)", |
58 | | - "-DCMAKE_BUILD_TYPE:=Debug", |
59 | | - "-DCMAKE_Swift_FLAGS='\(sharedSwiftFlags.joined(separator: " "))'" |
60 | | - ] + extraCMakeArgs |
61 | | - |
62 | | - Diagnostics.progress("Building swift-tools-protocols") |
63 | | - try await Process.checkNonZeroExit(url: cmakeURL, arguments: sharedCMakeArgs + [swiftToolsProtocolsURL.filePath], workingDirectory: swiftToolsProtocolsBuildURL) |
64 | | - try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftToolsProtocolsBuildURL) |
65 | | - Diagnostics.progress("Built swift-tools-protocols") |
| 55 | + if let sysrootPath { |
| 56 | + sharedSwiftFlags += ["-sdk", sysrootPath] |
66 | 57 | } |
| 58 | + |
| 59 | + let sharedCMakeArgs = |
| 60 | + [ |
| 61 | + "-G", "Ninja", |
| 62 | + "-DCMAKE_MAKE_PROGRAM=\(ninjaPath)", |
| 63 | + "-DCMAKE_BUILD_TYPE:=Debug", |
| 64 | + "-DCMAKE_Swift_FLAGS='\(sharedSwiftFlags.joined(separator: " "))'", |
| 65 | + ] + extraCMakeArgs |
| 66 | + |
| 67 | + Diagnostics.progress("Building swift-tools-protocols") |
| 68 | + try await Process.checkNonZeroExit( |
| 69 | + url: cmakeURL, |
| 70 | + arguments: sharedCMakeArgs + [swiftToolsProtocolsURL.filePath], |
| 71 | + workingDirectory: swiftToolsProtocolsBuildURL |
| 72 | + ) |
| 73 | + try await Process.checkNonZeroExit(url: ninjaURL, arguments: [], workingDirectory: swiftToolsProtocolsBuildURL) |
| 74 | + Diagnostics.progress("Built swift-tools-protocols") |
| 75 | + } |
67 | 76 | } |
68 | 77 |
|
69 | 78 | enum Errors: Error { |
70 | | - case processError(terminationReason: Process.TerminationReason, terminationStatus: Int32) |
71 | | - case missingRequiredOption(String) |
72 | | - case miscError(String) |
| 79 | + case processError(terminationReason: Process.TerminationReason, terminationStatus: Int32) |
| 80 | + case missingRequiredOption(String) |
| 81 | + case miscError(String) |
73 | 82 | } |
74 | 83 |
|
75 | 84 | extension URL { |
76 | | - var filePath: String { |
77 | | - get throws { |
78 | | - try withUnsafeFileSystemRepresentation { path in |
79 | | - guard let path else { |
80 | | - throw Errors.miscError("cannot get file path for URL: \(self)") |
81 | | - } |
82 | | - return String(cString: path) |
83 | | - } |
| 85 | + var filePath: String { |
| 86 | + get throws { |
| 87 | + try withUnsafeFileSystemRepresentation { path in |
| 88 | + guard let path else { |
| 89 | + throw Errors.miscError("cannot get file path for URL: \(self)") |
84 | 90 | } |
| 91 | + return String(cString: path) |
| 92 | + } |
85 | 93 | } |
| 94 | + } |
86 | 95 | } |
87 | 96 |
|
88 | 97 | extension Process { |
89 | | - func run() async throws { |
90 | | - try await withCheckedThrowingContinuation { continuation in |
91 | | - terminationHandler = { _ in |
92 | | - continuation.resume() |
93 | | - } |
94 | | - |
95 | | - do { |
96 | | - try run() |
97 | | - } catch { |
98 | | - terminationHandler = nil |
99 | | - continuation.resume(throwing: error) |
100 | | - } |
101 | | - } |
| 98 | + func run() async throws { |
| 99 | + try await withCheckedThrowingContinuation { continuation in |
| 100 | + terminationHandler = { _ in |
| 101 | + continuation.resume() |
| 102 | + } |
| 103 | + |
| 104 | + do { |
| 105 | + try run() |
| 106 | + } catch { |
| 107 | + terminationHandler = nil |
| 108 | + continuation.resume(throwing: error) |
| 109 | + } |
102 | 110 | } |
| 111 | + } |
103 | 112 |
|
104 | | - static func checkNonZeroExit(url: URL, arguments: [String], workingDirectory: URL, environment: [String: String]? = nil) async throws { |
105 | | - try Diagnostics.progress("\(url.filePath) \(arguments.joined(separator: " "))") |
106 | | - #if USE_PROCESS_SPAWNING_WORKAROUND && !os(Windows) |
107 | | - Diagnostics.progress("Using process spawning workaround") |
108 | | - // Linux workaround for https://github.com/swiftlang/swift-corelibs-foundation/issues/4772 |
109 | | - // Foundation.Process on Linux seems to inherit the Process.run()-calling thread's signal mask, creating processes that even have SIGTERM blocked |
110 | | - // This manifests as CMake getting stuck when invoking 'uname' with incorrectly configured signal handlers. |
111 | | - var fileActions = posix_spawn_file_actions_t() |
112 | | - defer { posix_spawn_file_actions_destroy(&fileActions) } |
113 | | - var attrs: posix_spawnattr_t = posix_spawnattr_t() |
114 | | - defer { posix_spawnattr_destroy(&attrs) } |
115 | | - posix_spawn_file_actions_init(&fileActions) |
116 | | - try posix_spawn_file_actions_addchdir_np(&fileActions, workingDirectory.filePath) |
117 | | - |
118 | | - posix_spawnattr_init(&attrs) |
119 | | - posix_spawnattr_setpgroup(&attrs, 0) |
120 | | - var noSignals = sigset_t() |
121 | | - sigemptyset(&noSignals) |
122 | | - posix_spawnattr_setsigmask(&attrs, &noSignals) |
123 | | - |
124 | | - var mostSignals = sigset_t() |
125 | | - sigemptyset(&mostSignals) |
126 | | - for i in 1 ..< SIGSYS { |
127 | | - if i == SIGKILL || i == SIGSTOP { |
128 | | - continue |
129 | | - } |
130 | | - sigaddset(&mostSignals, i) |
| 113 | + static func checkNonZeroExit( |
| 114 | + url: URL, |
| 115 | + arguments: [String], |
| 116 | + workingDirectory: URL, |
| 117 | + environment: [String: String]? = nil |
| 118 | + ) async throws { |
| 119 | + try Diagnostics.progress("\(url.filePath) \(arguments.joined(separator: " "))") |
| 120 | + #if USE_PROCESS_SPAWNING_WORKAROUND && !os(Windows) |
| 121 | + Diagnostics.progress("Using process spawning workaround") |
| 122 | + // Linux workaround for https://github.com/swiftlang/swift-corelibs-foundation/issues/4772 |
| 123 | + // Foundation.Process on Linux seems to inherit the Process.run()-calling thread's signal mask, creating processes that even have SIGTERM blocked |
| 124 | + // This manifests as CMake getting stuck when invoking 'uname' with incorrectly configured signal handlers. |
| 125 | + var fileActions = posix_spawn_file_actions_t() |
| 126 | + defer { posix_spawn_file_actions_destroy(&fileActions) } |
| 127 | + var attrs: posix_spawnattr_t = posix_spawnattr_t() |
| 128 | + defer { posix_spawnattr_destroy(&attrs) } |
| 129 | + posix_spawn_file_actions_init(&fileActions) |
| 130 | + try posix_spawn_file_actions_addchdir_np(&fileActions, workingDirectory.filePath) |
| 131 | + |
| 132 | + posix_spawnattr_init(&attrs) |
| 133 | + posix_spawnattr_setpgroup(&attrs, 0) |
| 134 | + var noSignals = sigset_t() |
| 135 | + sigemptyset(&noSignals) |
| 136 | + posix_spawnattr_setsigmask(&attrs, &noSignals) |
| 137 | + |
| 138 | + var mostSignals = sigset_t() |
| 139 | + sigemptyset(&mostSignals) |
| 140 | + for i in 1..<SIGSYS { |
| 141 | + if i == SIGKILL || i == SIGSTOP { |
| 142 | + continue |
| 143 | + } |
| 144 | + sigaddset(&mostSignals, i) |
| 145 | + } |
| 146 | + posix_spawnattr_setsigdefault(&attrs, &mostSignals) |
| 147 | + posix_spawnattr_setflags( |
| 148 | + &attrs, |
| 149 | + numericCast(POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK) |
| 150 | + ) |
| 151 | + var pid: pid_t = -1 |
| 152 | + try withArrayOfCStrings([url.filePath] + arguments) { arguments in |
| 153 | + try withArrayOfCStrings((environment ?? [:]).map { key, value in "\(key)=\(value)" }) { environment in |
| 154 | + let spawnResult = try posix_spawn( |
| 155 | + &pid, |
| 156 | + url.filePath, /*file_actions=*/ |
| 157 | + &fileActions, /*attrp=*/ |
| 158 | + &attrs, |
| 159 | + arguments, |
| 160 | + nil |
| 161 | + ); |
| 162 | + var exitCode: Int32 = -1 |
| 163 | + var result = wait4(pid, &exitCode, 0, nil); |
| 164 | + while result == -1 && errno == EINTR { |
| 165 | + result = wait4(pid, &exitCode, 0, nil) |
131 | 166 | } |
132 | | - posix_spawnattr_setsigdefault(&attrs, &mostSignals) |
133 | | - posix_spawnattr_setflags(&attrs, numericCast(POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK)) |
134 | | - var pid: pid_t = -1 |
135 | | - try withArrayOfCStrings([url.filePath] + arguments) { arguments in |
136 | | - try withArrayOfCStrings((environment ?? [:]).map { key, value in "\(key)=\(value)" }) { environment in |
137 | | - let spawnResult = try posix_spawn(&pid, url.filePath, /*file_actions=*/&fileActions, /*attrp=*/&attrs, arguments, nil); |
138 | | - var exitCode: Int32 = -1 |
139 | | - var result = wait4(pid, &exitCode, 0, nil); |
140 | | - while (result == -1 && errno == EINTR) { |
141 | | - result = wait4(pid, &exitCode, 0, nil) |
142 | | - } |
143 | | - guard result != -1 else { |
144 | | - throw Errors.miscError("wait failed") |
145 | | - } |
146 | | - guard exitCode == 0 else { |
147 | | - throw Errors.miscError("exit code nonzero") |
148 | | - } |
149 | | - } |
| 167 | + guard result != -1 else { |
| 168 | + throw Errors.miscError("wait failed") |
150 | 169 | } |
151 | | - #else |
152 | | - let process = Process() |
153 | | - process.executableURL = url |
154 | | - process.arguments = arguments |
155 | | - process.currentDirectoryURL = workingDirectory |
156 | | - process.environment = environment |
157 | | - try await process.run() |
158 | | - if process.terminationStatus != 0 { |
159 | | - throw Errors.processError(terminationReason: process.terminationReason, terminationStatus: process.terminationStatus) |
| 170 | + guard exitCode == 0 else { |
| 171 | + throw Errors.miscError("exit code nonzero") |
160 | 172 | } |
161 | | - #endif |
| 173 | + } |
162 | 174 | } |
| 175 | + #else |
| 176 | + let process = Process() |
| 177 | + process.executableURL = url |
| 178 | + process.arguments = arguments |
| 179 | + process.currentDirectoryURL = workingDirectory |
| 180 | + process.environment = environment |
| 181 | + try await process.run() |
| 182 | + if process.terminationStatus != 0 { |
| 183 | + throw Errors.processError( |
| 184 | + terminationReason: process.terminationReason, |
| 185 | + terminationStatus: process.terminationStatus |
| 186 | + ) |
| 187 | + } |
| 188 | + #endif |
| 189 | + } |
163 | 190 | } |
164 | 191 |
|
165 | 192 | #if USE_PROCESS_SPAWNING_WORKAROUND && !os(Windows) |
@@ -190,12 +217,16 @@ func withArrayOfCStrings<T>( |
190 | 217 | return try argsBuffer.withUnsafeMutableBufferPointer { |
191 | 218 | (argsBuffer) in |
192 | 219 | let ptr = UnsafeRawPointer(argsBuffer.baseAddress!).bindMemory( |
193 | | - to: Int8.self, capacity: argsBuffer.count) |
| 220 | + to: Int8.self, |
| 221 | + capacity: argsBuffer.count |
| 222 | + ) |
194 | 223 | var cStrings: [UnsafePointer<Int8>?] = argsOffsets.map { ptr + $0 } |
195 | 224 | cStrings[cStrings.count - 1] = nil |
196 | 225 | return try cStrings.withUnsafeMutableBufferPointer { |
197 | 226 | let unsafeString = UnsafeMutableRawPointer($0.baseAddress!).bindMemory( |
198 | | - to: UnsafeMutablePointer<Int8>?.self, capacity: $0.count) |
| 227 | + to: UnsafeMutablePointer<Int8>?.self, |
| 228 | + capacity: $0.count |
| 229 | + ) |
199 | 230 | return try body(unsafeString) |
200 | 231 | } |
201 | 232 | } |
|
0 commit comments