Skip to content

Commit 35517d0

Browse files
committed
refine location comparison when pre-loading package state from local state
motivation: when changing between canonical dependency URLs SwiftPM over-caches and ignores changes in manifest until local cache is deleted. this can lead to auth issues when switching between https and ssh for example changes: * take into account URL scheme when comparing dependency locations * always prefer root dependencies when computing preferred URLs * clean up call sites that compare dependecy location * add and expand tests rdar://105732543
1 parent 3552b58 commit 35517d0

File tree

6 files changed

+498
-27
lines changed

6 files changed

+498
-27
lines changed

Sources/Basics/SourceControlURL.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@
1313
import Foundation
1414

1515
public struct SourceControlURL: Codable, Equatable, Hashable, Sendable {
16+
private let underlying: URL?
1617
private let urlString: String
1718

1819
public init(stringLiteral: String) {
20+
self.underlying = URL(string: stringLiteral)
1921
self.urlString = stringLiteral
2022
}
2123

2224
public init(_ urlString: String) {
25+
self.underlying = URL(string: urlString)
2326
self.urlString = urlString
2427
}
2528

2629
public init(_ url: URL) {
30+
self.underlying = url
2731
self.urlString = url.absoluteString
2832
}
2933

@@ -36,7 +40,11 @@ public struct SourceControlURL: Codable, Equatable, Hashable, Sendable {
3640
}
3741

3842
public var url: URL? {
39-
return URL(string: self.urlString)
43+
return self.underlying
44+
}
45+
46+
public var scheme: String? {
47+
return self.underlying?.scheme
4048
}
4149
}
4250

Sources/PackageGraph/PubGrub/ContainerProvider.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ final class ContainerProvider {
6262
completion: @escaping (Result<PubGrubPackageContainer, Error>) -> Void
6363
) {
6464
// Return the cached container, if available.
65-
if let container = self.containersCache[package], package.equalsIncludingLocation(container.package) {
65+
if let container = self.containersCache[comparingLocation: package] {
6666
return completion(.success(container))
6767
}
6868

6969
if let prefetchSync = self.prefetches[package] {
7070
// If this container is already being prefetched, wait for that to complete
7171
prefetchSync.notify(queue: .sharedConcurrent) {
72-
if let container = self.containersCache[package], package.equalsIncludingLocation(container.package) {
72+
if let container = self.containersCache[comparingLocation: package] {
7373
// should be in the cache once prefetch completed
7474
return completion(.success(container))
7575
} else {
@@ -125,3 +125,12 @@ final class ContainerProvider {
125125
}
126126
}
127127
}
128+
129+
extension ThreadSafeKeyValueStore where Key == PackageReference, Value == PubGrubPackageContainer {
130+
subscript(comparingLocation package: PackageReference) -> PubGrubPackageContainer? {
131+
if let container = self[package], container.package.equalsIncludingLocation(package) {
132+
return container
133+
}
134+
return .none
135+
}
136+
}

Sources/PackageModel/PackageReference.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,19 @@ extension PackageReference: Equatable {
161161

162162
// TODO: consider rolling into Equatable
163163
public func equalsIncludingLocation(_ other: PackageReference) -> Bool {
164-
return self.identity == other.identity && self.canonicalLocation == other.canonicalLocation
164+
if self.identity != other.identity {
165+
return false
166+
}
167+
if self.canonicalLocation != other.canonicalLocation {
168+
return false
169+
}
170+
switch (self.kind, other.kind) {
171+
case (.remoteSourceControl(let lurl), .remoteSourceControl(let rurl)):
172+
return lurl.scheme == rurl.scheme
173+
default:
174+
break
175+
}
176+
return true
165177
}
166178
}
167179

Sources/Workspace/ManagedDependency.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ extension Workspace {
196196

197197
// When loading manifests in Workspace, there are cases where we must also compare the location
198198
// as it may attempt to load manifests for dependencies that have the same identity but from a different location
199-
// (e.g. dependency is changed to a fork with the same identity)
199+
// (e.g. dependency is changed to a fork with the same identity)
200200
public subscript(comparingLocation package: PackageReference) -> ManagedDependency? {
201201
if let dependency = self.dependencies[package.identity], dependency.packageRef.equalsIncludingLocation(package) {
202202
return dependency

Sources/Workspace/Workspace.swift

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,8 +1716,8 @@ extension Workspace {
17161716
needsUpdate = true
17171717
} else {
17181718
for dependency in dependenciesToPin {
1719-
if let pin = storedPinStore.pins.first(where: { $0.value.packageRef.equalsIncludingLocation(dependency.packageRef) }) {
1720-
if pin.value.state != PinsStore.Pin(dependency)?.state {
1719+
if let pin = storedPinStore.pins[comparingLocation: dependency.packageRef] {
1720+
if pin.state != PinsStore.Pin(dependency)?.state {
17211721
needsUpdate = true
17221722
break
17231723
}
@@ -1913,6 +1913,8 @@ extension Workspace {
19131913
}
19141914
}
19151915

1916+
let topLevelDependencies = root.packages.flatMap { $1.manifest.dependencies.map { $0.packageRef } }
1917+
19161918
var requiredIdentities: OrderedCollections.OrderedSet<PackageReference> = []
19171919
_ = transitiveClosure(inputNodes) { node in
19181920
return node.manifest.dependenciesRequired(for: node.productFilter).compactMap{ dependency in
@@ -1924,17 +1926,26 @@ extension Workspace {
19241926
if existing.canonicalLocation == package.canonicalLocation {
19251927
// same literal location is fine
19261928
if existing.locationString != package.locationString {
1927-
let preferred = [existing, package].sorted(by: {
1928-
$0.locationString > $1.locationString
1929-
}).first! // safe
1930-
observabilityScope.emit(debug: """
1931-
similar variants of package '\(package.identity)' \
1932-
found at '\(package.locationString)' and '\(existing.locationString)'. \
1933-
using preferred variant '\(preferred.locationString)'
1934-
""")
1935-
if preferred.locationString != existing.locationString {
1936-
requiredIdentities.remove(existing)
1937-
requiredIdentities.insert(preferred, at: index)
1929+
// we prefer the top level dependencies
1930+
if topLevelDependencies.contains(where: { $0.locationString == existing.locationString }) {
1931+
observabilityScope.emit(debug: """
1932+
similar variants of package '\(package.identity)' \
1933+
found at '\(package.locationString)' and '\(existing.locationString)'. \
1934+
using preferred root variant '\(existing.locationString)'
1935+
""")
1936+
} else {
1937+
let preferred = [existing, package].sorted(by: {
1938+
$0.locationString > $1.locationString
1939+
}).first! // safe
1940+
observabilityScope.emit(debug: """
1941+
similar variants of package '\(package.identity)' \
1942+
found at '\(package.locationString)' and '\(existing.locationString)'. \
1943+
using preferred variant '\(preferred.locationString)'
1944+
""")
1945+
if preferred.locationString != existing.locationString {
1946+
requiredIdentities.remove(existing)
1947+
requiredIdentities.insert(preferred, at: index)
1948+
}
19381949
}
19391950
}
19401951
} else {
@@ -3179,9 +3190,8 @@ extension Workspace {
31793190
// a required dependency that is already loaded (managed) should be represented in the pins store.
31803191
// also comparing location as it may have changed at this point
31813192
if requiredDependencies.contains(where: { $0.equalsIncludingLocation(dependency.packageRef) }) {
3182-
let pin = pinsStore.pins[dependency.packageRef.identity]
31833193
// if pin not found, or location is different (it may have changed at this point) pin it
3184-
if !(pin?.packageRef.equalsIncludingLocation(dependency.packageRef) ?? false) {
3194+
if pinsStore.pins[comparingLocation: dependency.packageRef] == .none {
31853195
pinsStore.pin(dependency)
31863196
}
31873197
} else if let pin = pinsStore.pins[dependency.packageRef.identity] {
@@ -3747,7 +3757,7 @@ extension Workspace {
37473757

37483758
// only update if necessary
37493759
if !workingCopy.exists(revision: revision) {
3750-
// The fetch operation may update contents of the checkout,
3760+
// The fetch operation may update contents of the checkout,
37513761
// so we need to do mutable-immutable dance.
37523762
try self.fileSystem.chmod(.userWritable, path: checkoutPath, options: [.recursive, .onlyFiles])
37533763
try workingCopy.fetch()
@@ -4632,3 +4642,12 @@ extension Workspace.ManagedDependencies {
46324642
})
46334643
}
46344644
}
4645+
4646+
extension PinsStore.Pins {
4647+
subscript(comparingLocation package: PackageReference) -> PinsStore.Pin? {
4648+
if let pin = self[package.identity], pin.packageRef.equalsIncludingLocation(package) {
4649+
return pin
4650+
}
4651+
return .none
4652+
}
4653+
}

0 commit comments

Comments
 (0)