Skip to content

Commit 068fa49

Browse files
authored
Transition away from Foundation.URL (#6706)
The parser of `NSURL` is changing in macOS Sonoma and will no longer be compatible with the GitHub-style SSH URLs which means we have to transition back to using our own URL type (which is a wrapper of `String` for now) in order to continue to support SSH URLs. rdar://112482783
1 parent 9074699 commit 068fa49

39 files changed

+218
-170
lines changed

Sources/Basics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_library(Basics
3131
FileSystem/TemporaryFile.swift
3232
FileSystem/TSCAdapters.swift
3333
FileSystem/VFSOverlay.swift
34+
SourceControlURL.swift
3435
HTTPClient/HTTPClient.swift
3536
HTTPClient/HTTPClientConfiguration.swift
3637
HTTPClient/HTTPClientError.swift

Sources/Basics/SourceControlURL.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
public struct SourceControlURL: Codable, Equatable, Hashable, Sendable {
16+
private let urlString: String
17+
18+
public init(stringLiteral: String) {
19+
self.urlString = stringLiteral
20+
}
21+
22+
public init(_ urlString: String) {
23+
self.urlString = urlString
24+
}
25+
26+
public init(_ url: URL) {
27+
self.urlString = url.absoluteString
28+
}
29+
30+
public var absoluteString: String {
31+
return self.urlString
32+
}
33+
34+
public var lastPathComponent: String {
35+
return (self.urlString as NSString).lastPathComponent
36+
}
37+
38+
public var url: URL? {
39+
return URL(string: self.urlString)
40+
}
41+
}
42+
43+
extension SourceControlURL: CustomStringConvertible {
44+
public var description: String {
45+
return self.urlString
46+
}
47+
}
48+
49+
extension SourceControlURL: ExpressibleByStringInterpolation {
50+
}
51+
52+
extension SourceControlURL: ExpressibleByStringLiteral {
53+
}

Sources/PackageCollections/PackageCollections+Validation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ extension PackageCollectionModel.V1 {
8282

8383
// TODO: validate package url?
8484
private func validate(package: Collection.Package, messages: inout [ValidationMessage]) {
85-
let packageID = "\(PackageIdentity(url: package.url).description) (\(package.url.absoluteString))"
85+
let packageID = "\(PackageIdentity(url: SourceControlURL(package.url)).description) (\(package.url.absoluteString))"
8686

8787
guard !package.versions.isEmpty else {
8888
messages.append(.error("Package \(packageID) does not have any versions.", property: "package.versions"))

Sources/PackageCollections/Providers/GitHubPackageMetadataProvider.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ struct GitHubPackageMetadataProvider: PackageMetadataProvider, Closable {
6262
callback: @escaping (Result<Model.PackageBasicMetadata, Error>, PackageMetadataProviderContext?) -> Void
6363
) {
6464
guard let baseURL = Self.apiURL(location) else {
65-
return self.errorCallback(GitHubPackageMetadataProviderError.invalidGitURL(location), apiHost: nil, callback: callback)
65+
return self.errorCallback(GitHubPackageMetadataProviderError.invalidSourceControlURL(location), apiHost: nil, callback: callback)
6666
}
6767

6868
if let cached = try? self.cache?.get(key: identity.description) {
@@ -334,7 +334,7 @@ struct GitHubPackageMetadataProvider: PackageMetadataProvider, Closable {
334334
}
335335

336336
enum GitHubPackageMetadataProviderError: Error, Equatable {
337-
case invalidGitURL(String)
337+
case invalidSourceControlURL(String)
338338
case invalidResponse(URL, String)
339339
case permissionDenied(URL)
340340
case invalidAuthToken(URL)

Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
371371

372372
// If package identity is set, use that. Otherwise create one from URL.
373373
return .init(
374-
identity: package.identity.map { PackageIdentity.plain($0) } ?? PackageIdentity(url: package.url),
374+
identity: package.identity.map { PackageIdentity.plain($0) } ?? PackageIdentity(url: SourceControlURL(package.url)),
375375
location: package.url.absoluteString,
376376
summary: package.summary,
377377
keywords: package.keywords,

Sources/PackageFingerprint/FilePackageFingerprintStorage.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,14 @@ private enum StorageModel {
265265

266266
let fingerprintsByContentType = try Dictionary(
267267
throwingUniqueKeysWithValues: fingerprintsForKind.map { _, storedFingerprint in
268-
guard let originURL = URL(string: storedFingerprint.origin) else {
269-
throw SerializationError.invalidURL(storedFingerprint.origin)
270-
}
271-
272268
let origin: Fingerprint.Origin
273269
switch kind {
274270
case .sourceControl:
275-
origin = .sourceControl(originURL)
271+
origin = .sourceControl(SourceControlURL(storedFingerprint.origin))
276272
case .registry:
273+
guard let originURL = URL(string: storedFingerprint.origin) else {
274+
throw SerializationError.invalidURL(storedFingerprint.origin)
275+
}
277276
origin = .registry(originURL)
278277
}
279278

Sources/PackageFingerprint/Model.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import struct Foundation.URL
1414

15+
import Basics
1516
import PackageModel
1617
import struct TSCUtility.Version
1718

@@ -34,7 +35,7 @@ extension Fingerprint {
3435
}
3536

3637
public enum Origin: Equatable, CustomStringConvertible {
37-
case sourceControl(URL)
38+
case sourceControl(SourceControlURL)
3839
case registry(URL)
3940

4041
public var kind: Fingerprint.Kind {
@@ -46,12 +47,12 @@ extension Fingerprint {
4647
}
4748
}
4849

49-
public var url: URL? {
50+
public var url: SourceControlURL? {
5051
switch self {
5152
case .sourceControl(let url):
5253
return url
5354
case .registry(let url):
54-
return url
55+
return SourceControlURL(url.absoluteString)
5556
}
5657
}
5758

Sources/PackageGraph/DependencyMirrors.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,8 @@ public final class DependencyMirrors: Equatable {
166166
return PackageIdentity.plain(location)
167167
} else if let path = try? AbsolutePath(validating: location) {
168168
return PackageIdentity(path: path)
169-
} else if let url = URL(string: location) {
170-
return PackageIdentity(url: url)
171169
} else {
172-
throw StringError("invalid location \(location), cannot extract identity")
170+
return PackageIdentity(url: SourceControlURL(location))
173171
}
174172
}
175173
}

Sources/PackageGraph/PinsStore.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,10 +428,8 @@ extension PinsStore.Pin {
428428
var packageRef: PackageReference
429429
if let path = try? AbsolutePath(validating: location) {
430430
packageRef = .localSourceControl(identity: identity, path: path)
431-
} else if let url = URL(string: location) {
432-
packageRef = .remoteSourceControl(identity: identity, url: url)
433431
} else {
434-
throw StringError("invalid package location \(location)")
432+
packageRef = .remoteSourceControl(identity: identity, url: SourceControlURL(location))
435433
}
436434
if let newName = pin.package {
437435
packageRef = packageRef.withName(newName)
@@ -466,10 +464,7 @@ extension PinsStore.Pin {
466464
case .localSourceControl:
467465
packageRef = try .localSourceControl(identity: identity, path: AbsolutePath(validating: location))
468466
case .remoteSourceControl:
469-
guard let url = URL(string: location) else {
470-
throw StringError("invalid url location: \(location)")
471-
}
472-
packageRef = .remoteSourceControl(identity: identity, url: url)
467+
packageRef = .remoteSourceControl(identity: identity, url: SourceControlURL(location))
473468
case .registry:
474469
packageRef = .registry(identity: identity)
475470
}

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import PackageModel
1515

1616
import struct Basics.AbsolutePath
1717
import protocol Basics.FileSystem
18+
import struct Basics.SourceControlURL
1819
import struct Basics.InternalError
1920
import struct Basics.RelativePath
2021

@@ -237,7 +238,8 @@ enum ManifestJSONParser {
237238
requirement: requirement,
238239
productFilter: .everything
239240
)
240-
} else if let url = URL(string: location){
241+
} else {
242+
let url = SourceControlURL(location)
241243
// in the future this will check with the registries for the identity of the URL
242244
let identity = try identityResolver.resolveIdentity(for: url)
243245
return .remoteSourceControl(
@@ -247,8 +249,6 @@ enum ManifestJSONParser {
247249
requirement: requirement,
248250
productFilter: .everything
249251
)
250-
} else {
251-
throw StringError("invalid location: \(location)")
252252
}
253253
}
254254

@@ -268,8 +268,9 @@ enum ManifestJSONParser {
268268
productFilter: .everything
269269
)
270270
} else if let url = URL(string: location){
271+
let SourceControlURL = SourceControlURL(url)
271272
// in the future this will check with the registries for the identity of the URL
272-
let identity = try identityResolver.resolveIdentity(for: url)
273+
let identity = try identityResolver.resolveIdentity(for: SourceControlURL)
273274
let sourceControlRequirement: PackageDependency.SourceControl.Requirement
274275
switch requirement {
275276
case .exact(let value):
@@ -280,7 +281,7 @@ enum ManifestJSONParser {
280281
return .remoteSourceControl(
281282
identity: identity,
282283
nameForTargetDependencyResolutionOnly: identity.description,
283-
url: url,
284+
url: SourceControlURL,
284285
requirement: sourceControlRequirement,
285286
productFilter: .everything
286287
)

Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private struct CodableRegistryReleaseMetadata: Codable {
4343
public let description: String?
4444
public let licenseURL: URL?
4545
public let readmeURL: URL?
46-
public let scmRepositoryURLs: [URL]?
46+
public let scmRepositoryURLs: [SourceControlURL]?
4747

4848
init(_ seed: RegistryReleaseMetadata) {
4949
switch seed.source {

Sources/PackageMetadata/PackageMetadata.swift

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public struct Package {
2727
public enum Source {
2828
case indexAndCollections(collections: [PackageCollectionsModel.CollectionIdentifier], indexes: [URL])
2929
case registry(url: URL)
30-
case sourceControl(url: URL)
30+
case sourceControl(url: SourceControlURL)
3131
}
3232

3333
public struct Resource: Sendable {
@@ -67,7 +67,7 @@ public struct Package {
6767
// Per version metadata based on the latest version that we include here for convenience.
6868
public let licenseURL: URL?
6969
public let readmeURL: URL?
70-
public let repositoryURLs: [URL]?
70+
public let repositoryURLs: [SourceControlURL]?
7171
public let resources: [Resource]
7272
public let author: Author?
7373
public let description: String?
@@ -82,7 +82,7 @@ public struct Package {
8282
versions: [Version],
8383
licenseURL: URL? = nil,
8484
readmeURL: URL? = nil,
85-
repositoryURLs: [URL]?,
85+
repositoryURLs: [SourceControlURL]?,
8686
resources: [Resource],
8787
author: Author?,
8888
description: String?,
@@ -133,11 +133,21 @@ public struct PackageSearchClient {
133133
}
134134

135135
// FIXME: This matches the current implementation, but we may want be smarter about it?
136+
private func guessReadMeURL(baseURL: SourceControlURL, defaultBranch: String) -> URL? {
137+
if let baseURL = baseURL.url {
138+
return guessReadMeURL(baseURL: baseURL, defaultBranch: defaultBranch)
139+
} else {
140+
return nil
141+
}
142+
}
143+
136144
private func guessReadMeURL(baseURL: URL, defaultBranch: String) -> URL {
137145
baseURL.appendingPathComponent("raw").appendingPathComponent(defaultBranch).appendingPathComponent("README.md")
138146
}
139147

140-
private func guessReadMeURL(alternateLocations: [URL]?) -> URL? {
148+
149+
150+
private func guessReadMeURL(alternateLocations: [SourceControlURL]?) -> URL? {
141151
if let alternateURL = alternateLocations?.first {
142152
// FIXME: This is pretty crude, we should let the registry metadata provide the value instead.
143153
return guessReadMeURL(baseURL: alternateURL, defaultBranch: "main")
@@ -148,7 +158,7 @@ public struct PackageSearchClient {
148158
private struct Metadata {
149159
public let licenseURL: URL?
150160
public let readmeURL: URL?
151-
public let repositoryURLs: [URL]?
161+
public let repositoryURLs: [SourceControlURL]?
152162
public let resources: [Package.Resource]
153163
public let author: Package.Author?
154164
public let description: String?
@@ -231,9 +241,7 @@ public struct PackageSearchClient {
231241
// as a URL or there are any errors during the process, we fall back to searching the configured
232242
// index or package collections.
233243
let fetchStandalonePackageByURL = { (error: Error?) in
234-
guard let url = URL(string: query) else {
235-
return search(error)
236-
}
244+
let url = SourceControlURL(query)
237245

238246
do {
239247
try withTemporaryDirectory(removeTreeOnDeinit: true) { (tempDir: AbsolutePath) in
@@ -303,7 +311,7 @@ public struct PackageSearchClient {
303311
self.getVersionMetadata(package: identity, version: version) { result in
304312
let licenseURL: URL?
305313
let readmeURL: URL?
306-
let repositoryURLs: [URL]?
314+
let repositoryURLs: [SourceControlURL]?
307315
let resources: [Package.Resource]
308316
let author: Package.Author?
309317
let description: String?
@@ -372,7 +380,7 @@ public struct PackageSearchClient {
372380
}
373381

374382
public func lookupIdentities(
375-
scmURL: URL,
383+
scmURL: SourceControlURL,
376384
timeout: DispatchTimeInterval? = .none,
377385
observabilityScope: ObservabilityScope,
378386
callbackQueue: DispatchQueue,
@@ -392,7 +400,7 @@ public struct PackageSearchClient {
392400
timeout: DispatchTimeInterval? = .none,
393401
observabilityScope: ObservabilityScope,
394402
callbackQueue: DispatchQueue,
395-
completion: @escaping (Result<Set<URL>, Error>) -> Void
403+
completion: @escaping (Result<Set<SourceControlURL>, Error>) -> Void
396404
) {
397405
registryClient.getPackageMetadata(
398406
package: package,

Sources/PackageModel/IdentityResolver.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Foundation
1616
// TODO: refactor this when adding registry support
1717
public protocol IdentityResolver {
1818
func resolveIdentity(for packageKind: PackageReference.Kind) throws -> PackageIdentity
19-
func resolveIdentity(for url: URL) throws -> PackageIdentity
19+
func resolveIdentity(for url: SourceControlURL) throws -> PackageIdentity
2020
func resolveIdentity(for path: AbsolutePath) throws -> PackageIdentity
2121
func mappedLocation(for location: String) -> String
2222
func mappedIdentity(for identity: PackageIdentity) throws -> PackageIdentity
@@ -49,25 +49,21 @@ public struct DefaultIdentityResolver: IdentityResolver {
4949
}
5050
}
5151

52-
public func resolveIdentity(for url: URL) throws -> PackageIdentity {
52+
public func resolveIdentity(for url: SourceControlURL) throws -> PackageIdentity {
5353
let location = self.mappedLocation(for: url.absoluteString)
5454
if let path = try? AbsolutePath(validating: location) {
5555
return PackageIdentity(path: path)
56-
} else if let url = URL(string: location) {
57-
return PackageIdentity(url: url)
5856
} else {
59-
throw StringError("invalid mapped location: \(location) for \(url)")
57+
return PackageIdentity(url: SourceControlURL(location))
6058
}
6159
}
6260

6361
public func resolveIdentity(for path: AbsolutePath) throws -> PackageIdentity {
6462
let location = self.mappedLocation(for: path.pathString)
6563
if let path = try? AbsolutePath(validating: location) {
6664
return PackageIdentity(path: path)
67-
} else if let url = URL(string: location) {
68-
return PackageIdentity(url: url)
6965
} else {
70-
throw StringError("invalid mapped location: \(location) for \(path)")
66+
return PackageIdentity(url: SourceControlURL(location))
7167
}
7268
}
7369

Sources/PackageModel/Manifest/PackageDependencyDescription.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public enum PackageDependency: Equatable, Hashable, Sendable {
4646

4747
public enum Location: Equatable, Hashable, Sendable {
4848
case local(AbsolutePath)
49-
case remote(URL)
49+
case remote(SourceControlURL)
5050
}
5151
}
5252

@@ -173,7 +173,7 @@ public enum PackageDependency: Equatable, Hashable, Sendable {
173173

174174
public static func remoteSourceControl(identity: PackageIdentity,
175175
nameForTargetDependencyResolutionOnly: String?,
176-
url: URL,
176+
url: SourceControlURL,
177177
requirement: SourceControl.Requirement,
178178
productFilter: ProductFilter
179179
) -> Self {

0 commit comments

Comments
 (0)