Skip to content

Commit 316a3d2

Browse files
authored
Transition away from Foundation.URL (#6724)
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 (cherry picked from commit 068fa49)
1 parent 9a36fe9 commit 316a3d2

39 files changed

+226
-170
lines changed

Sources/Basics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ add_library(Basics
3030
FileSystem/Path+Extensions.swift
3131
FileSystem/TemporaryFile.swift
3232
FileSystem/VFSOverlay.swift
33+
SourceControlURL.swift
3334
HTTPClient/HTTPClient.swift
3435
HTTPClient/HTTPClientConfiguration.swift
3536
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
@@ -84,7 +84,7 @@ extension PackageCollectionModel.V1 {
8484

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

8989
guard !package.versions.isEmpty else {
9090
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
@@ -61,7 +61,7 @@ struct GitHubPackageMetadataProvider: PackageMetadataProvider, Closable {
6161
callback: @escaping (Result<Model.PackageBasicMetadata, Error>, PackageMetadataProviderContext?) -> Void
6262
) {
6363
guard let baseURL = Self.apiURL(location) else {
64-
return self.errorCallback(GitHubPackageMetadataProviderError.invalidGitURL(location), apiHost: nil, callback: callback)
64+
return self.errorCallback(GitHubPackageMetadataProviderError.invalidSourceControlURL(location), apiHost: nil, callback: callback)
6565
}
6666

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

335335
enum GitHubPackageMetadataProviderError: Error, Equatable {
336-
case invalidGitURL(String)
336+
case invalidSourceControlURL(String)
337337
case invalidResponse(URL, String)
338338
case permissionDenied(URL)
339339
case invalidAuthToken(URL)

Sources/PackageCollections/Providers/JSONPackageCollectionProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {
282282
}
283283

284284
// If package identity is set, use that. Otherwise create one from URL.
285-
return .init(identity: package.identity.map { PackageIdentity.plain($0) } ?? PackageIdentity(url: package.url),
285+
return .init(identity: package.identity.map { PackageIdentity.plain($0) } ?? PackageIdentity(url: SourceControlURL(package.url)),
286286
location: package.url.absoluteString,
287287
summary: package.summary,
288288
keywords: package.keywords,

Sources/PackageFingerprint/FilePackageFingerprintStorage.swift

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

267267
let fingerprintsByContentType = try Dictionary(
268268
throwingUniqueKeysWithValues: fingerprintsForKind.map { _, storedFingerprint in
269-
guard let originURL = URL(string: storedFingerprint.origin) else {
270-
throw SerializationError.invalidURL(storedFingerprint.origin)
271-
}
272-
273269
let origin: Fingerprint.Origin
274270
switch kind {
275271
case .sourceControl:
276-
origin = .sourceControl(originURL)
272+
origin = .sourceControl(SourceControlURL(storedFingerprint.origin))
277273
case .registry:
274+
guard let originURL = URL(string: storedFingerprint.origin) else {
275+
throw SerializationError.invalidURL(storedFingerprint.origin)
276+
}
278277
origin = .registry(originURL)
279278
}
280279

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: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import struct Basics.SourceControlURL
1314
import Foundation
1415
import OrderedCollections
1516
import PackageModel
@@ -164,10 +165,8 @@ public final class DependencyMirrors: Equatable {
164165
return PackageIdentity.plain(location)
165166
} else if let path = try? AbsolutePath(validating: location) {
166167
return PackageIdentity(path: path)
167-
} else if let url = URL(string: location) {
168-
return PackageIdentity(url: url)
169168
} else {
170-
throw StringError("invalid location \(location), cannot extract identity")
169+
return PackageIdentity(url: SourceControlURL(location))
171170
}
172171
}
173172
}

Sources/PackageGraph/PinsStore.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,8 @@ extension PinsStore.Pin {
432432
var packageRef: PackageReference
433433
if let path = try? AbsolutePath(validating: location) {
434434
packageRef = .localSourceControl(identity: identity, path: path)
435-
} else if let url = URL(string: location) {
436-
packageRef = .remoteSourceControl(identity: identity, url: url)
437435
} else {
438-
throw StringError("invalid package location \(location)")
436+
packageRef = .remoteSourceControl(identity: identity, url: SourceControlURL(location))
439437
}
440438
if let newName = pin.package {
441439
packageRef = packageRef.withName(newName)
@@ -470,10 +468,7 @@ extension PinsStore.Pin {
470468
case .localSourceControl:
471469
packageRef = try .localSourceControl(identity: identity, path: AbsolutePath(validating: location))
472470
case .remoteSourceControl:
473-
guard let url = URL(string: location) else {
474-
throw StringError("invalid url location: \(location)")
475-
}
476-
packageRef = .remoteSourceControl(identity: identity, url: url)
471+
packageRef = .remoteSourceControl(identity: identity, url: SourceControlURL(location))
477472
case .registry:
478473
packageRef = .registry(identity: identity)
479474
}

Sources/PackageLoading/ManifestJSONParser.swift

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

1616
import struct Basics.InternalError
17+
import struct Basics.SourceControlURL
1718
import struct TSCBasic.AbsolutePath
1819
import protocol TSCBasic.FileSystem
1920
import enum TSCBasic.PathValidationError
@@ -235,7 +236,8 @@ enum ManifestJSONParser {
235236
requirement: requirement,
236237
productFilter: .everything
237238
)
238-
} else if let url = URL(string: location){
239+
} else {
240+
let url = SourceControlURL(location)
239241
// in the future this will check with the registries for the identity of the URL
240242
let identity = try identityResolver.resolveIdentity(for: url)
241243
return .remoteSourceControl(
@@ -245,8 +247,6 @@ enum ManifestJSONParser {
245247
requirement: requirement,
246248
productFilter: .everything
247249
)
248-
} else {
249-
throw StringError("invalid location: \(location)")
250250
}
251251
}
252252

@@ -266,8 +266,9 @@ enum ManifestJSONParser {
266266
productFilter: .everything
267267
)
268268
} else if let url = URL(string: location){
269+
let SourceControlURL = SourceControlURL(url)
269270
// in the future this will check with the registries for the identity of the URL
270-
let identity = try identityResolver.resolveIdentity(for: url)
271+
let identity = try identityResolver.resolveIdentity(for: SourceControlURL)
271272
let sourceControlRequirement: PackageDependency.SourceControl.Requirement
272273
switch requirement {
273274
case .exact(let value):
@@ -278,7 +279,7 @@ enum ManifestJSONParser {
278279
return .remoteSourceControl(
279280
identity: identity,
280281
nameForTargetDependencyResolutionOnly: identity.description,
281-
url: url,
282+
url: SourceControlURL,
282283
requirement: sourceControlRequirement,
283284
productFilter: .everything
284285
)

Sources/PackageLoading/RegistryReleaseMetadataSerialization.swift

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

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

Sources/PackageMetadata/PackageMetadata.swift

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

3535
public struct Resource: Sendable {
@@ -69,7 +69,7 @@ public struct Package {
6969
// Per version metadata based on the latest version that we include here for convenience.
7070
public let licenseURL: URL?
7171
public let readmeURL: URL?
72-
public let repositoryURLs: [URL]?
72+
public let repositoryURLs: [SourceControlURL]?
7373
public let resources: [Resource]
7474
public let author: Author?
7575
public let description: String?
@@ -84,7 +84,7 @@ public struct Package {
8484
versions: [Version],
8585
licenseURL: URL? = nil,
8686
readmeURL: URL? = nil,
87-
repositoryURLs: [URL]?,
87+
repositoryURLs: [SourceControlURL]?,
8888
resources: [Resource],
8989
author: Author?,
9090
description: String?,
@@ -135,11 +135,21 @@ public struct PackageSearchClient {
135135
}
136136

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

142-
private func guessReadMeURL(alternateLocations: [URL]?) -> URL? {
150+
151+
152+
private func guessReadMeURL(alternateLocations: [SourceControlURL]?) -> URL? {
143153
if let alternateURL = alternateLocations?.first {
144154
// FIXME: This is pretty crude, we should let the registry metadata provide the value instead.
145155
return guessReadMeURL(baseURL: alternateURL, defaultBranch: "main")
@@ -150,7 +160,7 @@ public struct PackageSearchClient {
150160
private struct Metadata {
151161
public let licenseURL: URL?
152162
public let readmeURL: URL?
153-
public let repositoryURLs: [URL]?
163+
public let repositoryURLs: [SourceControlURL]?
154164
public let resources: [Package.Resource]
155165
public let author: Package.Author?
156166
public let description: String?
@@ -233,9 +243,7 @@ public struct PackageSearchClient {
233243
// as a URL or there are any errors during the process, we fall back to searching the configured
234244
// index or package collections.
235245
let fetchStandalonePackageByURL = { (error: Error?) in
236-
guard let url = URL(string: query) else {
237-
return search(error)
238-
}
246+
let url = SourceControlURL(query)
239247

240248
do {
241249
try withTemporaryDirectory(removeTreeOnDeinit: true) { (tempDir: AbsolutePath) in
@@ -305,7 +313,7 @@ public struct PackageSearchClient {
305313
self.getVersionMetadata(package: identity, version: version) { result in
306314
let licenseURL: URL?
307315
let readmeURL: URL?
308-
let repositoryURLs: [URL]?
316+
let repositoryURLs: [SourceControlURL]?
309317
let resources: [Package.Resource]
310318
let author: Package.Author?
311319
let description: String?
@@ -374,7 +382,7 @@ public struct PackageSearchClient {
374382
}
375383

376384
public func lookupIdentities(
377-
scmURL: URL,
385+
scmURL: SourceControlURL,
378386
timeout: DispatchTimeInterval? = .none,
379387
observabilityScope: ObservabilityScope,
380388
callbackQueue: DispatchQueue,
@@ -394,7 +402,7 @@ public struct PackageSearchClient {
394402
timeout: DispatchTimeInterval? = .none,
395403
observabilityScope: ObservabilityScope,
396404
callbackQueue: DispatchQueue,
397-
completion: @escaping (Result<Set<URL>, Error>) -> Void
405+
completion: @escaping (Result<Set<SourceControlURL>, Error>) -> Void
398406
) {
399407
registryClient.getPackageMetadata(
400408
package: package,

Sources/PackageModel/IdentityResolver.swift

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import struct Basics.SourceControlURL
1314
import Foundation
1415
import TSCBasic
1516

1617
// TODO: refactor this when adding registry support
1718
public protocol IdentityResolver {
1819
func resolveIdentity(for packageKind: PackageReference.Kind) throws -> PackageIdentity
19-
func resolveIdentity(for url: URL) throws -> PackageIdentity
20+
func resolveIdentity(for url: SourceControlURL) throws -> PackageIdentity
2021
func resolveIdentity(for path: AbsolutePath) throws -> PackageIdentity
2122
func mappedLocation(for location: String) -> String
2223
func mappedIdentity(for identity: PackageIdentity) throws -> PackageIdentity
@@ -49,25 +50,21 @@ public struct DefaultIdentityResolver: IdentityResolver {
4950
}
5051
}
5152

52-
public func resolveIdentity(for url: URL) throws -> PackageIdentity {
53+
public func resolveIdentity(for url: SourceControlURL) throws -> PackageIdentity {
5354
let location = self.mappedLocation(for: url.absoluteString)
5455
if let path = try? AbsolutePath(validating: location) {
5556
return PackageIdentity(path: path)
56-
} else if let url = URL(string: location) {
57-
return PackageIdentity(url: url)
5857
} else {
59-
throw StringError("invalid mapped location: \(location) for \(url)")
58+
return PackageIdentity(url: SourceControlURL(location))
6059
}
6160
}
6261

6362
public func resolveIdentity(for path: AbsolutePath) throws -> PackageIdentity {
6463
let location = self.mappedLocation(for: path.pathString)
6564
if let path = try? AbsolutePath(validating: location) {
6665
return PackageIdentity(path: path)
67-
} else if let url = URL(string: location) {
68-
return PackageIdentity(url: url)
6966
} else {
70-
throw StringError("invalid mapped location: \(location) for \(path)")
67+
return PackageIdentity(url: SourceControlURL(location))
7168
}
7269
}
7370

0 commit comments

Comments
 (0)