Skip to content

Commit

Permalink
Transition away from Foundation.URL (#6706)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
neonichu authored Jul 18, 2023
1 parent 9074699 commit 068fa49
Show file tree
Hide file tree
Showing 39 changed files with 218 additions and 170 deletions.
1 change: 1 addition & 0 deletions Sources/Basics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_library(Basics
FileSystem/TemporaryFile.swift
FileSystem/TSCAdapters.swift
FileSystem/VFSOverlay.swift
SourceControlURL.swift
HTTPClient/HTTPClient.swift
HTTPClient/HTTPClientConfiguration.swift
HTTPClient/HTTPClientError.swift
Expand Down
53 changes: 53 additions & 0 deletions Sources/Basics/SourceControlURL.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2023 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 Foundation

public struct SourceControlURL: Codable, Equatable, Hashable, Sendable {
private let urlString: String

public init(stringLiteral: String) {
self.urlString = stringLiteral
}

public init(_ urlString: String) {
self.urlString = urlString
}

public init(_ url: URL) {
self.urlString = url.absoluteString
}

public var absoluteString: String {
return self.urlString
}

public var lastPathComponent: String {
return (self.urlString as NSString).lastPathComponent
}

public var url: URL? {
return URL(string: self.urlString)
}
}

extension SourceControlURL: CustomStringConvertible {
public var description: String {
return self.urlString
}
}

extension SourceControlURL: ExpressibleByStringInterpolation {
}

extension SourceControlURL: ExpressibleByStringLiteral {
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ extension PackageCollectionModel.V1 {

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

guard !package.versions.isEmpty else {
messages.append(.error("Package \(packageID) does not have any versions.", property: "package.versions"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ struct GitHubPackageMetadataProvider: PackageMetadataProvider, Closable {
callback: @escaping (Result<Model.PackageBasicMetadata, Error>, PackageMetadataProviderContext?) -> Void
) {
guard let baseURL = Self.apiURL(location) else {
return self.errorCallback(GitHubPackageMetadataProviderError.invalidGitURL(location), apiHost: nil, callback: callback)
return self.errorCallback(GitHubPackageMetadataProviderError.invalidSourceControlURL(location), apiHost: nil, callback: callback)
}

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

enum GitHubPackageMetadataProviderError: Error, Equatable {
case invalidGitURL(String)
case invalidSourceControlURL(String)
case invalidResponse(URL, String)
case permissionDenied(URL)
case invalidAuthToken(URL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ struct JSONPackageCollectionProvider: PackageCollectionProvider {

// If package identity is set, use that. Otherwise create one from URL.
return .init(
identity: package.identity.map { PackageIdentity.plain($0) } ?? PackageIdentity(url: package.url),
identity: package.identity.map { PackageIdentity.plain($0) } ?? PackageIdentity(url: SourceControlURL(package.url)),
location: package.url.absoluteString,
summary: package.summary,
keywords: package.keywords,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,14 @@ private enum StorageModel {

let fingerprintsByContentType = try Dictionary(
throwingUniqueKeysWithValues: fingerprintsForKind.map { _, storedFingerprint in
guard let originURL = URL(string: storedFingerprint.origin) else {
throw SerializationError.invalidURL(storedFingerprint.origin)
}

let origin: Fingerprint.Origin
switch kind {
case .sourceControl:
origin = .sourceControl(originURL)
origin = .sourceControl(SourceControlURL(storedFingerprint.origin))
case .registry:
guard let originURL = URL(string: storedFingerprint.origin) else {
throw SerializationError.invalidURL(storedFingerprint.origin)
}
origin = .registry(originURL)
}

Expand Down
7 changes: 4 additions & 3 deletions Sources/PackageFingerprint/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import struct Foundation.URL

import Basics
import PackageModel
import struct TSCUtility.Version

Expand All @@ -34,7 +35,7 @@ extension Fingerprint {
}

public enum Origin: Equatable, CustomStringConvertible {
case sourceControl(URL)
case sourceControl(SourceControlURL)
case registry(URL)

public var kind: Fingerprint.Kind {
Expand All @@ -46,12 +47,12 @@ extension Fingerprint {
}
}

public var url: URL? {
public var url: SourceControlURL? {
switch self {
case .sourceControl(let url):
return url
case .registry(let url):
return url
return SourceControlURL(url.absoluteString)
}
}

Expand Down
4 changes: 1 addition & 3 deletions Sources/PackageGraph/DependencyMirrors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,8 @@ public final class DependencyMirrors: Equatable {
return PackageIdentity.plain(location)
} else if let path = try? AbsolutePath(validating: location) {
return PackageIdentity(path: path)
} else if let url = URL(string: location) {
return PackageIdentity(url: url)
} else {
throw StringError("invalid location \(location), cannot extract identity")
return PackageIdentity(url: SourceControlURL(location))
}
}
}
Expand Down
9 changes: 2 additions & 7 deletions Sources/PackageGraph/PinsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -428,10 +428,8 @@ extension PinsStore.Pin {
var packageRef: PackageReference
if let path = try? AbsolutePath(validating: location) {
packageRef = .localSourceControl(identity: identity, path: path)
} else if let url = URL(string: location) {
packageRef = .remoteSourceControl(identity: identity, url: url)
} else {
throw StringError("invalid package location \(location)")
packageRef = .remoteSourceControl(identity: identity, url: SourceControlURL(location))
}
if let newName = pin.package {
packageRef = packageRef.withName(newName)
Expand Down Expand Up @@ -466,10 +464,7 @@ extension PinsStore.Pin {
case .localSourceControl:
packageRef = try .localSourceControl(identity: identity, path: AbsolutePath(validating: location))
case .remoteSourceControl:
guard let url = URL(string: location) else {
throw StringError("invalid url location: \(location)")
}
packageRef = .remoteSourceControl(identity: identity, url: url)
packageRef = .remoteSourceControl(identity: identity, url: SourceControlURL(location))
case .registry:
packageRef = .registry(identity: identity)
}
Expand Down
11 changes: 6 additions & 5 deletions Sources/PackageLoading/ManifestJSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PackageModel

import struct Basics.AbsolutePath
import protocol Basics.FileSystem
import struct Basics.SourceControlURL
import struct Basics.InternalError
import struct Basics.RelativePath

Expand Down Expand Up @@ -237,7 +238,8 @@ enum ManifestJSONParser {
requirement: requirement,
productFilter: .everything
)
} else if let url = URL(string: location){
} else {
let url = SourceControlURL(location)
// in the future this will check with the registries for the identity of the URL
let identity = try identityResolver.resolveIdentity(for: url)
return .remoteSourceControl(
Expand All @@ -247,8 +249,6 @@ enum ManifestJSONParser {
requirement: requirement,
productFilter: .everything
)
} else {
throw StringError("invalid location: \(location)")
}
}

Expand All @@ -268,8 +268,9 @@ enum ManifestJSONParser {
productFilter: .everything
)
} else if let url = URL(string: location){
let SourceControlURL = SourceControlURL(url)
// in the future this will check with the registries for the identity of the URL
let identity = try identityResolver.resolveIdentity(for: url)
let identity = try identityResolver.resolveIdentity(for: SourceControlURL)
let sourceControlRequirement: PackageDependency.SourceControl.Requirement
switch requirement {
case .exact(let value):
Expand All @@ -280,7 +281,7 @@ enum ManifestJSONParser {
return .remoteSourceControl(
identity: identity,
nameForTargetDependencyResolutionOnly: identity.description,
url: url,
url: SourceControlURL,
requirement: sourceControlRequirement,
productFilter: .everything
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private struct CodableRegistryReleaseMetadata: Codable {
public let description: String?
public let licenseURL: URL?
public let readmeURL: URL?
public let scmRepositoryURLs: [URL]?
public let scmRepositoryURLs: [SourceControlURL]?

init(_ seed: RegistryReleaseMetadata) {
switch seed.source {
Expand Down
30 changes: 19 additions & 11 deletions Sources/PackageMetadata/PackageMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct Package {
public enum Source {
case indexAndCollections(collections: [PackageCollectionsModel.CollectionIdentifier], indexes: [URL])
case registry(url: URL)
case sourceControl(url: URL)
case sourceControl(url: SourceControlURL)
}

public struct Resource: Sendable {
Expand Down Expand Up @@ -67,7 +67,7 @@ public struct Package {
// Per version metadata based on the latest version that we include here for convenience.
public let licenseURL: URL?
public let readmeURL: URL?
public let repositoryURLs: [URL]?
public let repositoryURLs: [SourceControlURL]?
public let resources: [Resource]
public let author: Author?
public let description: String?
Expand All @@ -82,7 +82,7 @@ public struct Package {
versions: [Version],
licenseURL: URL? = nil,
readmeURL: URL? = nil,
repositoryURLs: [URL]?,
repositoryURLs: [SourceControlURL]?,
resources: [Resource],
author: Author?,
description: String?,
Expand Down Expand Up @@ -133,11 +133,21 @@ public struct PackageSearchClient {
}

// FIXME: This matches the current implementation, but we may want be smarter about it?
private func guessReadMeURL(baseURL: SourceControlURL, defaultBranch: String) -> URL? {
if let baseURL = baseURL.url {
return guessReadMeURL(baseURL: baseURL, defaultBranch: defaultBranch)
} else {
return nil
}
}

private func guessReadMeURL(baseURL: URL, defaultBranch: String) -> URL {
baseURL.appendingPathComponent("raw").appendingPathComponent(defaultBranch).appendingPathComponent("README.md")
}

private func guessReadMeURL(alternateLocations: [URL]?) -> URL? {


private func guessReadMeURL(alternateLocations: [SourceControlURL]?) -> URL? {
if let alternateURL = alternateLocations?.first {
// FIXME: This is pretty crude, we should let the registry metadata provide the value instead.
return guessReadMeURL(baseURL: alternateURL, defaultBranch: "main")
Expand All @@ -148,7 +158,7 @@ public struct PackageSearchClient {
private struct Metadata {
public let licenseURL: URL?
public let readmeURL: URL?
public let repositoryURLs: [URL]?
public let repositoryURLs: [SourceControlURL]?
public let resources: [Package.Resource]
public let author: Package.Author?
public let description: String?
Expand Down Expand Up @@ -231,9 +241,7 @@ public struct PackageSearchClient {
// as a URL or there are any errors during the process, we fall back to searching the configured
// index or package collections.
let fetchStandalonePackageByURL = { (error: Error?) in
guard let url = URL(string: query) else {
return search(error)
}
let url = SourceControlURL(query)

do {
try withTemporaryDirectory(removeTreeOnDeinit: true) { (tempDir: AbsolutePath) in
Expand Down Expand Up @@ -303,7 +311,7 @@ public struct PackageSearchClient {
self.getVersionMetadata(package: identity, version: version) { result in
let licenseURL: URL?
let readmeURL: URL?
let repositoryURLs: [URL]?
let repositoryURLs: [SourceControlURL]?
let resources: [Package.Resource]
let author: Package.Author?
let description: String?
Expand Down Expand Up @@ -372,7 +380,7 @@ public struct PackageSearchClient {
}

public func lookupIdentities(
scmURL: URL,
scmURL: SourceControlURL,
timeout: DispatchTimeInterval? = .none,
observabilityScope: ObservabilityScope,
callbackQueue: DispatchQueue,
Expand All @@ -392,7 +400,7 @@ public struct PackageSearchClient {
timeout: DispatchTimeInterval? = .none,
observabilityScope: ObservabilityScope,
callbackQueue: DispatchQueue,
completion: @escaping (Result<Set<URL>, Error>) -> Void
completion: @escaping (Result<Set<SourceControlURL>, Error>) -> Void
) {
registryClient.getPackageMetadata(
package: package,
Expand Down
12 changes: 4 additions & 8 deletions Sources/PackageModel/IdentityResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Foundation
// TODO: refactor this when adding registry support
public protocol IdentityResolver {
func resolveIdentity(for packageKind: PackageReference.Kind) throws -> PackageIdentity
func resolveIdentity(for url: URL) throws -> PackageIdentity
func resolveIdentity(for url: SourceControlURL) throws -> PackageIdentity
func resolveIdentity(for path: AbsolutePath) throws -> PackageIdentity
func mappedLocation(for location: String) -> String
func mappedIdentity(for identity: PackageIdentity) throws -> PackageIdentity
Expand Down Expand Up @@ -49,25 +49,21 @@ public struct DefaultIdentityResolver: IdentityResolver {
}
}

public func resolveIdentity(for url: URL) throws -> PackageIdentity {
public func resolveIdentity(for url: SourceControlURL) throws -> PackageIdentity {
let location = self.mappedLocation(for: url.absoluteString)
if let path = try? AbsolutePath(validating: location) {
return PackageIdentity(path: path)
} else if let url = URL(string: location) {
return PackageIdentity(url: url)
} else {
throw StringError("invalid mapped location: \(location) for \(url)")
return PackageIdentity(url: SourceControlURL(location))
}
}

public func resolveIdentity(for path: AbsolutePath) throws -> PackageIdentity {
let location = self.mappedLocation(for: path.pathString)
if let path = try? AbsolutePath(validating: location) {
return PackageIdentity(path: path)
} else if let url = URL(string: location) {
return PackageIdentity(url: url)
} else {
throw StringError("invalid mapped location: \(location) for \(path)")
return PackageIdentity(url: SourceControlURL(location))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public enum PackageDependency: Equatable, Hashable, Sendable {

public enum Location: Equatable, Hashable, Sendable {
case local(AbsolutePath)
case remote(URL)
case remote(SourceControlURL)
}
}

Expand Down Expand Up @@ -173,7 +173,7 @@ public enum PackageDependency: Equatable, Hashable, Sendable {

public static func remoteSourceControl(identity: PackageIdentity,
nameForTargetDependencyResolutionOnly: String?,
url: URL,
url: SourceControlURL,
requirement: SourceControl.Requirement,
productFilter: ProductFilter
) -> Self {
Expand Down
Loading

0 comments on commit 068fa49

Please sign in to comment.