diff --git a/.swiftformat b/.swiftformat index 8ae09674179..4ee24410ab8 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,6 +1,6 @@ ## File options ---swiftversion 5.7 +--swiftversion 5.9 --exclude .build ## Formatting options diff --git a/Package.swift b/Package.swift index c74f42d9c9b..3a4e518ef70 100644 --- a/Package.swift +++ b/Package.swift @@ -74,7 +74,6 @@ automatic linking type with `-auto` suffix appended to product's name. */ let autoProducts = [swiftPMProduct, swiftPMDataModelProduct] - let packageModelResourcesSettings: [SwiftSetting] let packageModelResources: [Resource] if ProcessInfo.processInfo.environment["SWIFTPM_USE_LIBRARIES_METADATA"] == nil { @@ -186,7 +185,10 @@ let package = Package( .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), .product(name: "SystemPackage", package: "swift-system"), ], - exclude: ["CMakeLists.txt", "Vendor/README.md"] + exclude: ["CMakeLists.txt", "Vendor/README.md"], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + ] ), .target( @@ -717,7 +719,7 @@ package.targets.append(contentsOf: [ name: "FunctionalPerformanceTests", dependencies: [ "swift-package-manager", - "SPMTestSupport" + "SPMTestSupport", ] ), ]) diff --git a/Sources/Basics/Archiver/Archiver.swift b/Sources/Basics/Archiver/Archiver.swift index b76130b1500..e9d416ef21d 100644 --- a/Sources/Basics/Archiver/Archiver.swift +++ b/Sources/Basics/Archiver/Archiver.swift @@ -13,7 +13,7 @@ import _Concurrency /// The `Archiver` protocol abstracts away the different operations surrounding archives. -public protocol Archiver { +public protocol Archiver: Sendable { /// A set of extensions the current archiver supports. var supportedExtensions: Set { get } @@ -27,7 +27,7 @@ public protocol Archiver { func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) /// Asynchronously compress the contents of a directory to a destination archive. @@ -40,7 +40,7 @@ public protocol Archiver { func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) /// Asynchronously validates if a file is an archive. @@ -51,7 +51,7 @@ public protocol Archiver { @available(*, noasync, message: "Use the async alternative") func validate( path: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) } @@ -65,8 +65,8 @@ extension Archiver { from archivePath: AbsolutePath, to destinationPath: AbsolutePath ) async throws { - try await withCheckedThrowingContinuation { - self.extract(from: archivePath, to: destinationPath, completion: $0.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + self.extract(from: archivePath, to: destinationPath, completion: { continuation.resume(with: $0) }) } } @@ -79,8 +79,8 @@ extension Archiver { directory: AbsolutePath, to destinationPath: AbsolutePath ) async throws { - try await withCheckedThrowingContinuation { - self.compress(directory: directory, to: destinationPath, completion: $0.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + self.compress(directory: directory, to: destinationPath, completion: { continuation.resume(with: $0) }) } } @@ -91,8 +91,8 @@ extension Archiver { public func validate( path: AbsolutePath ) async throws -> Bool { - try await withCheckedThrowingContinuation { - self.validate(path: path, completion: $0.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + self.validate(path: path, completion: { continuation.resume(with: $0) }) } } } diff --git a/Sources/Basics/Archiver/TarArchiver.swift b/Sources/Basics/Archiver/TarArchiver.swift index d1da7ed0c15..99948bf82a9 100644 --- a/Sources/Basics/Archiver/TarArchiver.swift +++ b/Sources/Basics/Archiver/TarArchiver.swift @@ -47,7 +47,7 @@ public struct TarArchiver: Archiver { public func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.exists(archivePath) else { @@ -84,7 +84,7 @@ public struct TarArchiver: Archiver { public func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.isDirectory(directory) else { @@ -115,7 +115,7 @@ public struct TarArchiver: Archiver { } } - public func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { + public func validate(path: AbsolutePath, completion: @escaping @Sendable (Result) -> Void) { do { guard self.fileSystem.exists(path) else { throw FileSystemError(.noEntry, path.underlying) diff --git a/Sources/Basics/Archiver/UniversalArchiver.swift b/Sources/Basics/Archiver/UniversalArchiver.swift index cbd5d5d742f..d6ed496df97 100644 --- a/Sources/Basics/Archiver/UniversalArchiver.swift +++ b/Sources/Basics/Archiver/UniversalArchiver.swift @@ -73,7 +73,7 @@ public struct UniversalArchiver: Archiver { public func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let archiver = try archiver(for: archivePath) @@ -86,7 +86,7 @@ public struct UniversalArchiver: Archiver { public func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let archiver = try archiver(for: destinationPath) @@ -98,7 +98,7 @@ public struct UniversalArchiver: Archiver { public func validate( path: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let archiver = try archiver(for: path) diff --git a/Sources/Basics/Archiver/ZipArchiver.swift b/Sources/Basics/Archiver/ZipArchiver.swift index 9aab24e13ce..092752d8382 100644 --- a/Sources/Basics/Archiver/ZipArchiver.swift +++ b/Sources/Basics/Archiver/ZipArchiver.swift @@ -37,7 +37,7 @@ public struct ZipArchiver: Archiver, Cancellable { public func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.exists(archivePath) else { @@ -77,7 +77,7 @@ public struct ZipArchiver: Archiver, Cancellable { public func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.isDirectory(directory) else { @@ -125,7 +125,7 @@ public struct ZipArchiver: Archiver, Cancellable { } } - public func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { + public func validate(path: AbsolutePath, completion: @escaping @Sendable (Result) -> Void) { do { guard self.fileSystem.exists(path) else { throw FileSystemError(.noEntry, path.underlying) diff --git a/Sources/Basics/AuthorizationProvider.swift b/Sources/Basics/AuthorizationProvider.swift index 11e80cb79ed..89326664a3c 100644 --- a/Sources/Basics/AuthorizationProvider.swift +++ b/Sources/Basics/AuthorizationProvider.swift @@ -17,8 +17,7 @@ import struct Foundation.URL import Security #endif -public protocol AuthorizationProvider { - @Sendable +public protocol AuthorizationProvider: Sendable { func authentication(for url: URL) -> (user: String, password: String)? } @@ -80,7 +79,7 @@ extension AuthorizationProvider { // MARK: - netrc -public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { +public final class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { // marked internal for testing internal let path: AbsolutePath private let fileSystem: FileSystem @@ -202,7 +201,7 @@ public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWri // MARK: - Keychain #if canImport(Security) -public class KeychainAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { +public final class KeychainAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { private let observabilityScope: ObservabilityScope private let cache = ThreadSafeKeyValueStore() diff --git a/Sources/Basics/Cancellator.swift b/Sources/Basics/Cancellator.swift index d2c808e88ef..e1e8f8d0434 100644 --- a/Sources/Basics/Cancellator.swift +++ b/Sources/Basics/Cancellator.swift @@ -18,9 +18,9 @@ import class TSCBasic.Thread import WinSDK #endif -public typealias CancellationHandler = (DispatchTime) throws -> Void +public typealias CancellationHandler = @Sendable (DispatchTime) throws -> Void -public final class Cancellator: Cancellable { +public final class Cancellator: Cancellable, Sendable { public typealias RegistrationKey = String private let observabilityScope: ObservabilityScope? @@ -119,7 +119,7 @@ public final class Cancellator: Cancellable { } @discardableResult - public func register(name: String, handler: @escaping () throws -> Void) -> RegistrationKey? { + public func register(name: String, handler: @escaping @Sendable () throws -> Void) -> RegistrationKey? { self.register(name: name, handler: { _ in try handler() }) } diff --git a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift index 24330b3e79f..7927d2e2dec 100644 --- a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift +++ b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift @@ -49,7 +49,7 @@ extension DispatchQueue { /// Bridges between potentially blocking methods that take a result completion closure and async/await public func safe_async( - _ body: @Sendable @escaping (@Sendable @escaping (Result) -> Void) -> Void + _ body: @escaping @Sendable (@escaping @Sendable (Result) -> Void) -> Void ) async throws -> T { try await withCheckedThrowingContinuation { continuation in // It is possible that body make block indefinitely on a lock, semaphore, @@ -64,7 +64,7 @@ public func safe_async( } /// Bridges between potentially blocking methods that take a result completion closure and async/await -public func safe_async(_ body: @escaping (@escaping (Result) -> Void) -> Void) async -> T { +public func safe_async(_ body: @escaping @Sendable (@escaping (Result) -> Void) -> Void) async -> T { await withCheckedContinuation { continuation in // It is possible that body make block indefinitely on a lock, semaphore, // or similar then synchronously call the completion handler. For full safety diff --git a/Sources/Basics/Concurrency/TokenBucket.swift b/Sources/Basics/Concurrency/TokenBucket.swift index e9cf6ff4251..010da630a35 100644 --- a/Sources/Basics/Concurrency/TokenBucket.swift +++ b/Sources/Basics/Concurrency/TokenBucket.swift @@ -30,7 +30,7 @@ public actor TokenBucket { /// invocations of `withToken` will suspend until a "free" token is available. /// - Parameter body: The closure to invoke when a token is available. /// - Returns: Resulting value returned by `body`. - public func withToken( + public func withToken( _ body: @Sendable () async throws -> ReturnType ) async rethrows -> ReturnType { await self.getToken() diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index ac0c0ace718..f31604263e7 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -25,7 +25,7 @@ import var TSCBasic.localFileSystem import protocol TSCBasic.WritableByteStream public typealias FileSystem = TSCBasic.FileSystem -public var localFileSystem = TSCBasic.localFileSystem +public let localFileSystem = TSCBasic.localFileSystem // MARK: - Custom path diff --git a/Sources/Basics/FileSystem/TemporaryFile.swift b/Sources/Basics/FileSystem/TemporaryFile.swift index a8e31126448..a9f253b6f08 100644 --- a/Sources/Basics/FileSystem/TemporaryFile.swift +++ b/Sources/Basics/FileSystem/TemporaryFile.swift @@ -34,7 +34,7 @@ public func withTemporaryDirectory( fileSystem: FileSystem = localFileSystem, dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", - _ body: @Sendable @escaping (AbsolutePath, @escaping (AbsolutePath) -> Void) async throws -> Result + _ body: @escaping @Sendable (AbsolutePath, @escaping (AbsolutePath) -> Void) async throws -> Result ) throws -> Task { let temporaryDirectory = try createTemporaryDirectory(fileSystem: fileSystem, dir: dir, prefix: prefix) @@ -72,7 +72,7 @@ public func withTemporaryDirectory( dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", removeTreeOnDeinit: Bool = false, - _ body: @escaping (AbsolutePath) async throws -> Result + _ body: @escaping @Sendable (AbsolutePath) async throws -> Result ) throws -> Task { try withTemporaryDirectory(fileSystem: fileSystem, dir: dir, prefix: prefix) { path, cleanup in defer { if removeTreeOnDeinit { cleanup(path) } } diff --git a/Sources/Basics/HTTPClient/LegacyHTTPClient.swift b/Sources/Basics/HTTPClient/LegacyHTTPClient.swift index 8b53d663b4d..524459e54a9 100644 --- a/Sources/Basics/HTTPClient/LegacyHTTPClient.swift +++ b/Sources/Basics/HTTPClient/LegacyHTTPClient.swift @@ -25,9 +25,9 @@ public final class LegacyHTTPClient: Cancellable { public typealias Configuration = LegacyHTTPClientConfiguration public typealias Request = LegacyHTTPClientRequest public typealias Response = HTTPClientResponse - public typealias Handler = (Request, ProgressHandler?, @escaping (Result) -> Void) -> Void - public typealias ProgressHandler = (_ bytesReceived: Int64, _ totalBytes: Int64?) throws -> Void - public typealias CompletionHandler = (Result) -> Void + public typealias Handler = (Request, ProgressHandler?, @escaping @Sendable (Result) -> Void) -> Void + public typealias ProgressHandler = @Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) throws -> Void + public typealias CompletionHandler = @Sendable (Result) -> Void public var configuration: LegacyHTTPClientConfiguration private let underlying: Handler @@ -121,7 +121,7 @@ public final class LegacyHTTPClient: Cancellable { requestNumber: 0, observabilityScope: observabilityScope, progress: progress.map { handler in - { received, expected in + { @Sendable received, expected in // call back on the requested queue callbackQueue.async { do { @@ -312,7 +312,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .head, url: url, headers: headers, body: nil, options: options), @@ -326,7 +326,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .get, url: url, headers: headers, body: nil, options: options), @@ -341,7 +341,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .put, url: url, headers: headers, body: body, options: options), @@ -356,7 +356,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .post, url: url, headers: headers, body: body, options: options), @@ -370,7 +370,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .delete, url: url, headers: headers, body: nil, options: options), @@ -383,7 +383,7 @@ extension LegacyHTTPClient { // MARK: - LegacyHTTPClientConfiguration public struct LegacyHTTPClientConfiguration { - public typealias AuthorizationProvider = (URL) -> String? + public typealias AuthorizationProvider = @Sendable (URL) -> String? public var requestHeaders: HTTPClientHeaders? public var requestTimeout: DispatchTimeInterval? diff --git a/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift b/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift index a3d4102da64..7c27608749d 100644 --- a/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift +++ b/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift @@ -14,18 +14,41 @@ import _Concurrency import Foundation import struct TSCUtility.Versioning #if canImport(FoundationNetworking) -// FIXME: this brings OpenSSL dependency on Linux -// need to decide how to best deal with that +// FIXME: this brings OpenSSL dependency on Linux and needs to be replaced with `swift-server/async-http-client` package import FoundationNetworking #endif -final class URLSessionHTTPClient { +final class URLSessionHTTPClient: Sendable { + private let dataSession: URLSession + private let downloadSession: URLSession private let dataTaskManager: DataTaskManager private let downloadTaskManager: DownloadTaskManager init(configuration: URLSessionConfiguration = .default) { - self.dataTaskManager = DataTaskManager(configuration: configuration) - self.downloadTaskManager = DownloadTaskManager(configuration: configuration) + let dataDelegateQueue = OperationQueue() + dataDelegateQueue.name = "org.swift.swiftpm.urlsession-http-client-data-delegate" + dataDelegateQueue.maxConcurrentOperationCount = 1 + self.dataTaskManager = DataTaskManager() + self.dataSession = URLSession( + configuration: configuration, + delegate: self.dataTaskManager, + delegateQueue: dataDelegateQueue + ) + + let downloadDelegateQueue = OperationQueue() + downloadDelegateQueue.name = "org.swift.swiftpm.urlsession-http-client-download-delegate" + downloadDelegateQueue.maxConcurrentOperationCount = 1 + self.downloadTaskManager = DownloadTaskManager() + self.downloadSession = URLSession( + configuration: configuration, + delegate: self.downloadTaskManager, + delegateQueue: downloadDelegateQueue + ) + } + + deinit { + dataSession.finishTasksAndInvalidate() + downloadSession.finishTasksAndInvalidate() } @Sendable @@ -38,27 +61,34 @@ final class URLSessionHTTPClient { let task: URLSessionTask switch request.kind { case .generic: - task = self.dataTaskManager.makeTask( + let dataTask = self.dataSession.dataTask(with: urlRequest) + self.dataTaskManager.register( + task: dataTask, urlRequest: urlRequest, authorizationProvider: request.options.authorizationProvider, progress: progress, - completion: continuation.resume(with:) + completion: { continuation.resume(with: $0) } ) + task = dataTask case .download(_, let destination): - task = self.downloadTaskManager.makeTask( + let downloadTask = self.downloadSession.downloadTask(with: urlRequest) + self.downloadTaskManager.register( + task: downloadTask, urlRequest: urlRequest, - // FIXME: always using a synchronous filesystem, because `URLSessionDownloadDelegate` + // FIXME: always using synchronous filesystem, because `URLSessionDownloadDelegate` // needs temporary files to moved out of temporary locations synchronously in delegate callbacks. fileSystem: localFileSystem, destination: destination, progress: progress, - completion: continuation.resume(with:) + completion: { continuation.resume(with: $0) } ) + task = downloadTask } task.resume() } } + @Sendable public func execute( _ request: LegacyHTTPClient.Request, progress: LegacyHTTPClient.ProgressHandler?, @@ -68,101 +98,41 @@ final class URLSessionHTTPClient { let task: URLSessionTask switch request.kind { case .generic: - task = self.dataTaskManager.makeTask( + let dataTask = self.dataSession.dataTask(with: urlRequest) + self.dataTaskManager.register( + task: dataTask, urlRequest: urlRequest, authorizationProvider: request.options.authorizationProvider, progress: progress, completion: completion ) + task = dataTask case .download(let fileSystem, let destination): - task = self.downloadTaskManager.makeTask( + let downloadTask = self.downloadSession.downloadTask(with: urlRequest) + self.downloadTaskManager.register( + task: downloadTask, urlRequest: urlRequest, fileSystem: fileSystem, destination: destination, progress: progress, completion: completion ) + task = downloadTask } task.resume() } } -/// A weak wrapper around `DataTaskManager` that conforms to `URLSessionDataDelegate`. -/// -/// This ensures that we don't get a retain cycle between `DataTaskManager.session` -> `URLSession.delegate` -> `DataTaskManager`. -/// -/// The `DataTaskManager` is being kept alive by a reference from all `DataTask`s that it manages. Once all the -/// `DataTasks` have finished and are deallocated, `DataTaskManager` will get deinitialized, which invalidates the -/// session, which then lets go of `WeakDataTaskManager`. -private class WeakDataTaskManager: NSObject, URLSessionDataDelegate { - private weak var dataTaskManager: DataTaskManager? - - init(_ dataTaskManager: DataTaskManager? = nil) { - self.dataTaskManager = dataTaskManager - } - - func urlSession( - _ session: URLSession, - dataTask: URLSessionDataTask, - didReceive response: URLResponse, - completionHandler: @escaping (URLSession.ResponseDisposition) -> Void - ) { - dataTaskManager?.urlSession( - session, - dataTask: dataTask, - didReceive: response, - completionHandler: completionHandler - ) - } - - func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - dataTaskManager?.urlSession(session, dataTask: dataTask, didReceive: data) - } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - dataTaskManager?.urlSession(session, task: task, didCompleteWithError: error) - } - - func urlSession( - _ session: URLSession, - task: URLSessionTask, - willPerformHTTPRedirection response: HTTPURLResponse, - newRequest request: URLRequest, - completionHandler: @escaping (URLRequest?) -> Void - ) { - dataTaskManager?.urlSession( - session, - task: task, - willPerformHTTPRedirection: response, - newRequest: request, - completionHandler: completionHandler - ) - } -} - -private class DataTaskManager { - private var tasks = ThreadSafeKeyValueStore() - private let delegateQueue: OperationQueue - private var session: URLSession! - - public init(configuration: URLSessionConfiguration) { - self.delegateQueue = OperationQueue() - self.delegateQueue.name = "org.swift.swiftpm.urlsession-http-client-data-delegate" - self.delegateQueue.maxConcurrentOperationCount = 1 - self.session = URLSession(configuration: configuration, delegate: WeakDataTaskManager(self), delegateQueue: self.delegateQueue) - } - - deinit { - session.finishTasksAndInvalidate() - } +private final class DataTaskManager: NSObject, URLSessionDataDelegate { + private let tasks = ThreadSafeKeyValueStore() - func makeTask( + func register( + task: URLSessionDataTask, urlRequest: URLRequest, authorizationProvider: LegacyHTTPClientConfiguration.AuthorizationProvider?, progress: LegacyHTTPClient.ProgressHandler?, completion: @escaping LegacyHTTPClient.CompletionHandler - ) -> URLSessionDataTask { - let task = self.session.dataTask(with: urlRequest) + ) { self.tasks[task.taskIdentifier] = DataTask( task: task, progressHandler: progress, @@ -170,7 +140,6 @@ private class DataTaskManager { completionHandler: completion, authorizationProvider: authorizationProvider ) - return task } public func urlSession( @@ -179,11 +148,13 @@ private class DataTaskManager { didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void ) { - guard let task = self.tasks[dataTask.taskIdentifier] else { + guard var task = self.tasks[dataTask.taskIdentifier] else { return completionHandler(.cancel) } task.response = response as? HTTPURLResponse task.expectedContentLength = response.expectedContentLength + self.tasks[dataTask.taskIdentifier] = task + do { try task.progressHandler?(0, response.expectedContentLength) completionHandler(.allow) @@ -193,7 +164,7 @@ private class DataTaskManager { } public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - guard let task = self.tasks[dataTask.taskIdentifier] else { + guard var task = self.tasks[dataTask.taskIdentifier] else { return } if task.buffer != nil { @@ -201,6 +172,7 @@ private class DataTaskManager { } else { task.buffer = data } + self.tasks[dataTask.taskIdentifier] = task do { // safe since created in the line above @@ -246,7 +218,7 @@ private class DataTaskManager { completionHandler(request) } - class DataTask { + struct DataTask: Sendable { let task: URLSessionDataTask let completionHandler: LegacyHTTPClient.CompletionHandler /// A strong reference to keep the `DataTaskManager` alive so it can handle the callbacks from the @@ -277,71 +249,17 @@ private class DataTaskManager { } } -/// This uses the same pattern as `WeakDataTaskManager`. See comment on that type. -private class WeakDownloadTaskManager: NSObject, URLSessionDownloadDelegate { - private weak var downloadTaskManager: DownloadTaskManager? - - init(_ downloadTaskManager: DownloadTaskManager? = nil) { - self.downloadTaskManager = downloadTaskManager - } - - func urlSession( - _ session: URLSession, - downloadTask: URLSessionDownloadTask, - didWriteData bytesWritten: Int64, - totalBytesWritten: Int64, - totalBytesExpectedToWrite: Int64 - ) { - downloadTaskManager?.urlSession( - session, - downloadTask: downloadTask, - didWriteData: bytesWritten, - totalBytesWritten: totalBytesWritten, - totalBytesExpectedToWrite: totalBytesExpectedToWrite - ) - } +private final class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { + private let tasks = ThreadSafeKeyValueStore() - func urlSession( - _ session: URLSession, - downloadTask: URLSessionDownloadTask, - didFinishDownloadingTo location: URL - ) { - downloadTaskManager?.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location) - } - - func urlSession( - _ session: URLSession, - task downloadTask: URLSessionTask, - didCompleteWithError error: Error? - ) { - downloadTaskManager?.urlSession(session, task: downloadTask, didCompleteWithError: error) - } -} - -private class DownloadTaskManager { - private var tasks = ThreadSafeKeyValueStore() - private let delegateQueue: OperationQueue - private var session: URLSession! - - init(configuration: URLSessionConfiguration) { - self.delegateQueue = OperationQueue() - self.delegateQueue.name = "org.swift.swiftpm.urlsession-http-client-download-delegate" - self.delegateQueue.maxConcurrentOperationCount = 1 - self.session = URLSession(configuration: configuration, delegate: WeakDownloadTaskManager(self), delegateQueue: self.delegateQueue) - } - - deinit { - session.finishTasksAndInvalidate() - } - - func makeTask( + func register( + task: URLSessionDownloadTask, urlRequest: URLRequest, fileSystem: FileSystem, destination: AbsolutePath, progress: LegacyHTTPClient.ProgressHandler?, completion: @escaping LegacyHTTPClient.CompletionHandler - ) -> URLSessionDownloadTask { - let task = self.session.downloadTask(with: urlRequest) + ) { self.tasks[task.taskIdentifier] = DownloadTask( task: task, fileSystem: fileSystem, @@ -350,7 +268,6 @@ private class DownloadTaskManager { progressHandler: progress, completionHandler: completion ) - return task } func urlSession( @@ -379,7 +296,7 @@ private class DownloadTaskManager { downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) { - guard let task = self.tasks[downloadTask.taskIdentifier] else { + guard var task = self.tasks[downloadTask.taskIdentifier] else { return } @@ -392,6 +309,7 @@ private class DownloadTaskManager { try task.fileSystem.move(from: path, to: task.destination) } catch { task.moveFileError = error + self.tasks[downloadTask.taskIdentifier] = task } } @@ -419,15 +337,10 @@ private class DownloadTaskManager { } } - class DownloadTask { + struct DownloadTask: Sendable { let task: URLSessionDownloadTask let fileSystem: FileSystem let destination: AbsolutePath - /// A strong reference to keep the `DownloadTaskManager` alive so it can handle the callbacks from the - /// `URLSession`. - /// - /// See comment on `WeakDownloadTaskManager`. - private let downloadTaskManager: DownloadTaskManager let progressHandler: LegacyHTTPClient.ProgressHandler? let completionHandler: LegacyHTTPClient.CompletionHandler @@ -444,7 +357,6 @@ private class DownloadTaskManager { self.task = task self.fileSystem = fileSystem self.destination = destination - self.downloadTaskManager = downloadTaskManager self.progressHandler = progressHandler self.completionHandler = completionHandler } diff --git a/Sources/Basics/Observability.swift b/Sources/Basics/Observability.swift index 4d6e01f959b..2567c6be7ec 100644 --- a/Sources/Basics/Observability.swift +++ b/Sources/Basics/Observability.swift @@ -46,8 +46,7 @@ public class ObservabilitySystem { private struct SingleDiagnosticsHandler: ObservabilityHandlerProvider, DiagnosticsHandler { var diagnosticsHandler: DiagnosticsHandler { self } - let underlying: @Sendable (ObservabilityScope, Diagnostic) - -> Void + let underlying: @Sendable (ObservabilityScope, Diagnostic) -> Void init(_ underlying: @escaping @Sendable (ObservabilityScope, Diagnostic) -> Void) { self.underlying = underlying diff --git a/Sources/Basics/Sandbox.swift b/Sources/Basics/Sandbox.swift index f8a32d46874..16e981e540c 100644 --- a/Sources/Basics/Sandbox.swift +++ b/Sources/Basics/Sandbox.swift @@ -45,7 +45,7 @@ public enum Sandbox { /// - Parameters: /// - command: The command line to sandbox (including executable as first argument) /// - fileSystem: The file system instance to use. - /// - strictness: The basic strictness level of the standbox. + /// - strictness: The basic strictness level of the sandbox. /// - writableDirectories: Paths under which writing should be allowed, even if they would otherwise be read-only based on the strictness or paths in `readOnlyDirectories`. /// - readOnlyDirectories: Paths under which writing should be denied, even if they would have otherwise been allowed by the rules implied by the strictness level. public static func apply( diff --git a/Sources/Basics/SwiftVersion.swift b/Sources/Basics/SwiftVersion.swift index d3967783532..7d95a327ca4 100644 --- a/Sources/Basics/SwiftVersion.swift +++ b/Sources/Basics/SwiftVersion.swift @@ -16,7 +16,7 @@ import TSCclibc #endif -public struct SwiftVersion { +public struct SwiftVersion: Sendable { /// The version number. public var version: (major: Int, minor: Int, patch: Int) diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index 3df40912808..6106759a4e7 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -51,7 +51,7 @@ private struct NativeBuildSystemFactory: BuildSystemFactory { ), additionalFileRules: FileRuleDescription.swiftpmFileTypes, pkgConfigDirectories: self.swiftCommandState.options.locations.pkgConfigDirectories, - dependenciesByRootPackageIdentity: rootPackageInfo.dependecies, + dependenciesByRootPackageIdentity: rootPackageInfo.dependencies, targetsByRootPackageIdentity: rootPackageInfo.targets, outputStream: outputStream ?? self.swiftCommandState.outputStream, logLevel: logLevel ?? self.swiftCommandState.logLevel, diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift index 76e8154c6b0..8ae918c7adc 100644 --- a/Sources/CoreCommands/SwiftCommandState.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -468,7 +468,7 @@ public final class SwiftCommandState { return workspace } - public func getRootPackageInformation() throws -> (dependecies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) { + public func getRootPackageInformation() throws -> (dependencies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) { let workspace = try self.getActiveWorkspace() let root = try self.getWorkspaceRoot() let rootManifests = try temp_await { diff --git a/Sources/PackageGraph/ModulesGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift index 658e1d5d59d..716ff676695 100644 --- a/Sources/PackageGraph/ModulesGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -434,6 +434,13 @@ private func createResolvedPackages( // Track if multiple targets are found with the same name. var foundDuplicateTarget = false + for packageBuilder in packageBuilders { + for target in packageBuilder.targets { + // Record if we see a duplicate target. + foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(target.target.name).inserted + } + } + // Do another pass and establish product dependencies of each target. for packageBuilder in packageBuilders { let package = packageBuilder.package @@ -493,9 +500,6 @@ private func createResolvedPackages( // Establish dependencies in each target. for targetBuilder in packageBuilder.targets { - // Record if we see a duplicate target. - foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(targetBuilder.target.name).inserted - // Directly add all the system module dependencies. targetBuilder.dependencies += implicitSystemTargetDeps.map { .target($0, conditions: []) } @@ -524,13 +528,22 @@ private func createResolvedPackages( } else { // Find a product name from the available product dependencies that is most similar to the required product name. let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames)) + var packageContainingBestMatchedProduct: String? + if let bestMatchedProductName, productRef.name == bestMatchedProductName { + let dependentPackages = packageBuilder.dependencies.map(\.package) + for p in dependentPackages where p.targets.contains(where: { $0.name == bestMatchedProductName }) { + packageContainingBestMatchedProduct = p.identity.description + break + } + } let error = PackageGraphError.productDependencyNotFound( package: package.identity.description, targetName: targetBuilder.target.name, dependencyProductName: productRef.name, dependencyPackageName: productRef.package, dependencyProductInDecl: !declProductsAsDependency.isEmpty, - similarProductName: bestMatchedProductName + similarProductName: bestMatchedProductName, + packageContainingSimilarProduct: packageContainingBestMatchedProduct ) packageObservabilityScope.emit(error) } @@ -568,7 +581,7 @@ private func createResolvedPackages( // If a target with similar name was encountered before, we emit a diagnostic. if foundDuplicateTarget { var duplicateTargets = [String: [Package]]() - for targetName in allTargetNames.sorted() { + for targetName in Set(allTargetNames).sorted() { let packages = packageBuilders .filter({ $0.targets.contains(where: { $0.target.name == targetName }) }) .map{ $0.package } diff --git a/Sources/PackageGraph/ModulesGraph.swift b/Sources/PackageGraph/ModulesGraph.swift index 8930f477b1f..874fa8479e5 100644 --- a/Sources/PackageGraph/ModulesGraph.swift +++ b/Sources/PackageGraph/ModulesGraph.swift @@ -23,7 +23,7 @@ enum PackageGraphError: Swift.Error { case cycleDetected((path: [Manifest], cycle: [Manifest])) /// The product dependency not found. - case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool, similarProductName: String?) + case productDependencyNotFound(package: String, targetName: String, dependencyProductName: String, dependencyPackageName: String?, dependencyProductInDecl: Bool, similarProductName: String?, packageContainingSimilarProduct: String?) /// The package dependency already satisfied by a different dependency package case dependencyAlreadySatisfiedByIdentifier(package: String, dependencyLocation: String, otherDependencyURL: String, identity: PackageIdentity) @@ -238,12 +238,14 @@ extension PackageGraphError: CustomStringConvertible { (cycle.path + cycle.cycle).map({ $0.displayName }).joined(separator: " -> ") + " -> " + cycle.cycle[0].displayName - case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName): + case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName, let packageContainingSimilarProduct): if dependencyProductInDecl { return "product '\(dependencyProductName)' is declared in the same package '\(package)' and can't be used as a dependency for target '\(targetName)'." } else { var description = "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")." - if let similarProductName { + if let similarProductName, let packageContainingSimilarProduct { + description += " Did you mean '.product(name: \"\(similarProductName)\", package: \"\(packageContainingSimilarProduct)\")'?" + } else if let similarProductName { description += " Did you mean '\(similarProductName)'?" } return description diff --git a/Sources/PackageModelSyntax/AddTargetDependency.swift b/Sources/PackageModelSyntax/AddTargetDependency.swift new file mode 100644 index 00000000000..fde0a5e69e6 --- /dev/null +++ b/Sources/PackageModelSyntax/AddTargetDependency.swift @@ -0,0 +1,103 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageLoading +import PackageModel +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder + +/// Add a target dependency to a manifest's source code. +public struct AddTargetDependency { + /// The set of argument labels that can occur after the "dependencies" + /// argument in the various target initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterDependencies: Set = [ + "path", + "exclude", + "sources", + "resources", + "publicHeadersPath", + "packageAccess", + "cSettings", + "cxxSettings", + "swiftSettings", + "linkerSettings", + "plugins", + ] + + /// Produce the set of source edits needed to add the given target + /// dependency to the given manifest file. + public static func addTargetDependency( + _ dependency: TargetDescription.Dependency, + targetName: String, + to manifest: SourceFileSyntax + ) throws -> PackageEditResult { + // Make sure we have a suitable tools version in the manifest. + try manifest.checkEditManifestToolsVersion() + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + // Dig out the array of targets. + guard let targetsArgument = packageCall.findArgument(labeled: "targets"), + let targetArray = targetsArgument.expression.findArrayArgument() else { + throw ManifestEditError.cannotFindTargets + } + + // Look for a call whose name is a string literal matching the + // requested target name. + func matchesTargetCall(call: FunctionCallExprSyntax) -> Bool { + guard let nameArgument = call.findArgument(labeled: "name") else { + return false + } + + guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self), + let literalValue = stringLiteral.representedLiteralValue else { + return false + } + + return literalValue == targetName + } + + guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: matchesTargetCall) else { + throw ManifestEditError.cannotFindTarget(targetName: targetName) + } + + let newTargetCall = try addTargetDependencyLocal( + dependency, to: targetCall + ) + + return PackageEditResult( + manifestEdits: [ + .replace(targetCall, with: newTargetCall.description) + ] + ) + } + + /// Implementation of adding a target dependency to an existing call. + static func addTargetDependencyLocal( + _ dependency: TargetDescription.Dependency, + to targetCall: FunctionCallExprSyntax + ) throws -> FunctionCallExprSyntax { + try targetCall.appendingToArrayArgument( + label: "dependencies", + trailingLabels: Self.argumentLabelsAfterDependencies, + newElement: dependency.asSyntax() + ) + } +} + diff --git a/Sources/PackageModelSyntax/CMakeLists.txt b/Sources/PackageModelSyntax/CMakeLists.txt index cfab869efc1..c034d8d1705 100644 --- a/Sources/PackageModelSyntax/CMakeLists.txt +++ b/Sources/PackageModelSyntax/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(PackageModelSyntax AddPackageDependency.swift AddProduct.swift AddTarget.swift + AddTargetDependency.swift ManifestEditError.swift ManifestSyntaxRepresentable.swift PackageDependency+Syntax.swift diff --git a/Sources/PackageModelSyntax/ManifestEditError.swift b/Sources/PackageModelSyntax/ManifestEditError.swift index cba8eb520dd..aaaf3351166 100644 --- a/Sources/PackageModelSyntax/ManifestEditError.swift +++ b/Sources/PackageModelSyntax/ManifestEditError.swift @@ -18,6 +18,8 @@ import SwiftSyntax /// package manifest programattically. package enum ManifestEditError: Error { case cannotFindPackage + case cannotFindTargets + case cannotFindTarget(targetName: String) case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax) case oldManifest(ToolsVersion) } @@ -33,6 +35,10 @@ extension ManifestEditError: CustomStringConvertible { switch self { case .cannotFindPackage: "invalid manifest: unable to find 'Package' declaration" + case .cannotFindTargets: + "unable to find package targets in manifest" + case .cannotFindTarget(targetName: let name): + "unable to find target named '\(name)' in package" case .cannotFindArrayLiteralArgument(argumentName: let name, node: _): "unable to find array literal for '\(name)' argument" case .oldManifest(let version): diff --git a/Sources/PackageRegistry/RegistryClient.swift b/Sources/PackageRegistry/RegistryClient.swift index ff8ec74b5c6..96445280282 100644 --- a/Sources/PackageRegistry/RegistryClient.swift +++ b/Sources/PackageRegistry/RegistryClient.swift @@ -983,7 +983,7 @@ public final class RegistryClient: Cancellable { package: PackageIdentity, version: Version, destinationPath: AbsolutePath, - progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + progressHandler: (@Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, timeout: DispatchTimeInterval? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1009,7 +1009,7 @@ public final class RegistryClient: Cancellable { package: PackageIdentity, version: Version, destinationPath: AbsolutePath, - progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + progressHandler: (@Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, timeout: DispatchTimeInterval? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1063,7 +1063,7 @@ public final class RegistryClient: Cancellable { package: PackageIdentity.RegistryIdentity, version: Version, destinationPath: AbsolutePath, - progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + progressHandler: (@Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, timeout: DispatchTimeInterval?, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1651,7 +1651,7 @@ public final class RegistryClient: Cancellable { result.tryMap { response in observabilityScope .emit( - debug: "server response for \(request.url): \(response.statusCode) in \(start.distance(to: .now()).descriptionInSeconds)" + debug: "server response for \(url): \(response.statusCode) in \(start.distance(to: .now()).descriptionInSeconds)" ) switch response.statusCode { case 201: diff --git a/Sources/PackageRegistry/RegistryDownloadsManager.swift b/Sources/PackageRegistry/RegistryDownloadsManager.swift index 3c24234bfef..12462ff744b 100644 --- a/Sources/PackageRegistry/RegistryDownloadsManager.swift +++ b/Sources/PackageRegistry/RegistryDownloadsManager.swift @@ -163,7 +163,7 @@ public class RegistryDownloadsManager: Cancellable { observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { if let cachePath { do { diff --git a/Sources/PackageRegistry/SignatureValidation.swift b/Sources/PackageRegistry/SignatureValidation.swift index 3e5fccd64ae..4fee446f87f 100644 --- a/Sources/PackageRegistry/SignatureValidation.swift +++ b/Sources/PackageRegistry/SignatureValidation.swift @@ -91,7 +91,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { guard !self.skipSignatureValidation else { return completion(.success(.none)) @@ -138,7 +138,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let versionMetadata = try self.versionMetadataProvider(package, version) @@ -240,7 +240,7 @@ struct SignatureValidation { configuration: RegistryConfiguration.Security.Signing, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { Task { do { @@ -353,7 +353,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { guard !self.skipSignatureValidation else { return completion(.success(.none)) @@ -402,7 +402,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let manifestName = toolsVersion.map { "Package@swift-\($0).swift" } ?? Manifest.filename @@ -506,7 +506,7 @@ struct SignatureValidation { configuration: RegistryConfiguration.Security.Signing, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { Task { do { @@ -577,7 +577,7 @@ struct SignatureValidation { signatureFormat: SignatureFormat, configuration: RegistryConfiguration.Security.Signing, fileSystem: FileSystem, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { Task { do { diff --git a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift index fb15d965cb6..41df217cee2 100644 --- a/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift @@ -68,12 +68,13 @@ private func readpassword(_ prompt: String) throws -> String { #if canImport(Darwin) var buffer = [CChar](repeating: 0, count: PackageRegistryCommand.Login.passwordBufferSize) + password = try withExtendedLifetime(buffer) { + guard let passwordPtr = readpassphrase(prompt, &buffer, buffer.count, 0) else { + throw StringError("unable to read input") + } - guard let passwordPtr = readpassphrase(prompt, &buffer, buffer.count, 0) else { - throw StringError("unable to read input") + return String(cString: passwordPtr) } - - password = String(cString: passwordPtr) #else // GNU C implementation of getpass has no limit on the password length // (https://man7.org/linux/man-pages/man3/getpass.3.html) diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index ec4ef688a55..0d70ee2af0c 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -76,7 +76,6 @@ public final class WorkspaceState { self.storage.fileExists() } - /// Returns true if the state file exists on the filesystem. func reload() throws { let storedState = try self.storage.load() self.dependencies = storedState.dependencies diff --git a/Sources/XCBuildSupport/PIF.swift b/Sources/XCBuildSupport/PIF.swift index 2029bddca49..37953654c70 100644 --- a/Sources/XCBuildSupport/PIF.swift +++ b/Sources/XCBuildSupport/PIF.swift @@ -941,8 +941,6 @@ public enum PIF { case INSTALL_PATH case SUPPORTS_MACCATALYST case SWIFT_SERIALIZE_DEBUGGING_OPTIONS - case SWIFT_FORCE_STATIC_LINK_STDLIB - case SWIFT_FORCE_DYNAMIC_LINK_STDLIB case SWIFT_INSTALL_OBJC_HEADER case SWIFT_OBJC_INTERFACE_HEADER_NAME case SWIFT_OBJC_INTERFACE_HEADER_DIR diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 4406000cb12..36caba7b2aa 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -450,8 +450,6 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.EXECUTABLE_NAME] = product.name settings[.CLANG_ENABLE_MODULES] = "YES" settings[.DEFINES_MODULE] = "YES" - settings[.SWIFT_FORCE_STATIC_LINK_STDLIB] = "NO" - settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB] = "YES" if product.type == .executable || product.type == .test { settings[.LIBRARY_SEARCH_PATHS] = [ @@ -1710,6 +1708,9 @@ extension [PackageCondition] { case .watchOS: result += PIF.PlatformFilter.watchOSFilters + case .visionOS: + result += PIF.PlatformFilter.visionOSFilters + case .linux: result += PIF.PlatformFilter.linuxFilters @@ -1794,6 +1795,14 @@ extension PIF.PlatformFilter { public static let webAssemblyFilters: [PIF.PlatformFilter] = [ .init(platform: "wasi"), ] + + /// VisionOS platform filters. + public static let visionOSFilters: [PIF.PlatformFilter] = [ + .init(platform: "xros"), + .init(platform: "xros", environment: "simulator"), + .init(platform: "visionos"), + .init(platform: "visionos", environment: "simulator") + ] } extension PIF.BuildSettings { diff --git a/Tests/PackageGraphTests/ModulesGraphTests.swift b/Tests/PackageGraphTests/ModulesGraphTests.swift index efae57d71a4..420387d5d7d 100644 --- a/Tests/PackageGraphTests/ModulesGraphTests.swift +++ b/Tests/PackageGraphTests/ModulesGraphTests.swift @@ -2676,6 +2676,57 @@ final class ModulesGraphTests: XCTestCase { XCTAssertEqual(observability.diagnostics.count, 0, "unexpected diagnostics: \(observability.diagnostics.map { $0.description })") } + + func testDependencyResolutionWithErrorMessages() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/aaa/Sources/aaa/main.swift", + "/zzz/Sources/zzz/source.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "aaa", + path: "/aaa", + dependencies: [ + .localSourceControl(path: "/zzz", requirement: .upToNextMajor(from: "1.0.0")) + ], + products: [], + targets: [ + TargetDescription( + name: "aaa", + dependencies: ["zzy"], + type: .executable + ) + ]), + Manifest.createRootManifest( + displayName: "zzz", + path: "/zzz", + products: [ + ProductDescription( + name: "zzz", + type: .library(.automatic), + targets: ["zzz"] + ) + ], + targets: [ + TargetDescription( + name: "zzz" + ) + ]) + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'zzy' required by package 'aaa' target 'aaa' not found. Did you mean 'zzz'?", + severity: .error + ) + } + } } diff --git a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift index 812b72bfe84..614ca912d3e 100644 --- a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift +++ b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift @@ -611,6 +611,46 @@ class ManifestEditTests: XCTestCase { ) } } + + func testAddTargetDependency() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + ], + targets: [ + .testTarget( + name: "MyTest" + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + ], + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + .product(name: "Testing", package: "swift-testing"), + ] + ), + ] + ) + """) { manifest in + try AddTargetDependency.addTargetDependency( + .product(name: "Testing", package: "swift-testing"), + targetName: "MyTest", + to: manifest + ) + } + } } diff --git a/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift b/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift index 42828726b2b..bcb2e58d62b 100644 --- a/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift +++ b/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift @@ -22,7 +22,7 @@ class MinimumDeploymentTargetTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let result = ProcessResult(arguments: [], - environment: [:], + environmentBlock: [:], exitStatus: .terminated(code: 0), output: "".asResult, stderrOutput: "xcodebuild: error: SDK \"macosx\" cannot be located.".asResult) @@ -36,7 +36,7 @@ class MinimumDeploymentTargetTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let result = ProcessResult(arguments: [], - environment: [:], + environmentBlock: [:], exitStatus: .terminated(code: 0), output: "some string".asResult, stderrOutput: "".asResult) @@ -50,7 +50,7 @@ class MinimumDeploymentTargetTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let result = ProcessResult(arguments: [], - environment: [:], + environmentBlock: [:], exitStatus: .terminated(code: 0), output: .failure(DummyError()), stderrOutput: "".asResult) diff --git a/Tests/XCBuildSupportTests/PIFBuilderTests.swift b/Tests/XCBuildSupportTests/PIFBuilderTests.swift index 783af14ad4c..56111e06d67 100644 --- a/Tests/XCBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/XCBuildSupportTests/PIFBuilderTests.swift @@ -539,8 +539,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "foo") XCTAssertEqual( @@ -569,8 +567,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "foo") XCTAssertEqual( @@ -616,8 +612,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cfoo") XCTAssertEqual( settings[.LIBRARY_SEARCH_PATHS], @@ -649,8 +643,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cfoo") XCTAssertEqual( settings[.LIBRARY_SEARCH_PATHS], @@ -688,8 +680,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_NAME], "bar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "4.2") XCTAssertEqual(settings[.TARGET_NAME], "bar") XCTAssertEqual( @@ -712,8 +702,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_NAME], "bar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "4.2") XCTAssertEqual(settings[.TARGET_NAME], "bar") XCTAssertEqual( @@ -755,8 +743,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_NAME], "cbar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cbar") XCTAssertEqual( settings[.LIBRARY_SEARCH_PATHS], @@ -784,8 +770,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_NAME], "cbar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cbar") XCTAssertEqual( settings[.LIBRARY_SEARCH_PATHS], @@ -922,8 +906,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "FooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooTests") XCTAssertEqual( @@ -972,8 +954,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "FooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooTests") XCTAssertEqual( @@ -1037,8 +1017,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "CFooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "CFooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "CFooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "CFooTests") XCTAssertEqual( settings[.WATCHOS_DEPLOYMENT_TARGET], @@ -1090,8 +1068,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "CFooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "CFooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "CFooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "CFooTests") XCTAssertEqual( settings[.WATCHOS_DEPLOYMENT_TARGET],