Skip to content

Commit 73f5e42

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 0cc531b commit 73f5e42

File tree

7 files changed

+499
-28
lines changed

7 files changed

+499
-28
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+Manifests.swift

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,11 @@ extension Workspace {
198198
}
199199
}
200200

201+
let topLevelDependencies = root.packages.flatMap { $1.manifest.dependencies.map { $0.packageRef } }
202+
201203
var requiredIdentities: OrderedCollections.OrderedSet<PackageReference> = []
202204
_ = transitiveClosure(inputNodes) { node in
203-
node.manifest.dependenciesRequired(for: node.productFilter).compactMap { dependency in
205+
return node.manifest.dependenciesRequired(for: node.productFilter).compactMap{ dependency in
204206
let package = dependency.packageRef
205207
let (inserted, index) = requiredIdentities.append(package)
206208
if !inserted {
@@ -209,17 +211,26 @@ extension Workspace {
209211
if existing.canonicalLocation == package.canonicalLocation {
210212
// same literal location is fine
211213
if existing.locationString != package.locationString {
212-
let preferred = [existing, package].sorted(by: {
213-
$0.locationString > $1.locationString
214-
}).first! // safe
215-
observabilityScope.emit(debug: """
216-
similar variants of package '\(package.identity)' \
217-
found at '\(package.locationString)' and '\(existing.locationString)'. \
218-
using preferred variant '\(preferred.locationString)'
219-
""")
220-
if preferred.locationString != existing.locationString {
221-
requiredIdentities.remove(existing)
222-
requiredIdentities.insert(preferred, at: index)
214+
// we prefer the top level dependencies
215+
if topLevelDependencies.contains(where: { $0.locationString == existing.locationString }) {
216+
observabilityScope.emit(debug: """
217+
similar variants of package '\(package.identity)' \
218+
found at '\(package.locationString)' and '\(existing.locationString)'. \
219+
using preferred root variant '\(existing.locationString)'
220+
""")
221+
} else {
222+
let preferred = [existing, package].sorted(by: {
223+
$0.locationString > $1.locationString
224+
}).first! // safe
225+
observabilityScope.emit(debug: """
226+
similar variants of package '\(package.identity)' \
227+
found at '\(package.locationString)' and '\(existing.locationString)'. \
228+
using preferred variant '\(preferred.locationString)'
229+
""")
230+
if preferred.locationString != existing.locationString {
231+
requiredIdentities.remove(existing)
232+
requiredIdentities.insert(preferred, at: index)
233+
}
223234
}
224235
}
225236
} else {

Sources/Workspace/Workspace.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,8 +1723,8 @@ extension Workspace {
17231723
needsUpdate = true
17241724
} else {
17251725
for dependency in dependenciesToPin {
1726-
if let pin = storedPinStore.pins.first(where: { $0.value.packageRef.equalsIncludingLocation(dependency.packageRef) }) {
1727-
if pin.value.state != PinsStore.Pin(dependency)?.state {
1726+
if let pin = storedPinStore.pins[comparingLocation: dependency.packageRef] {
1727+
if pin.state != PinsStore.Pin(dependency)?.state {
17281728
needsUpdate = true
17291729
break
17301730
}
@@ -2531,9 +2531,8 @@ extension Workspace {
25312531
// a required dependency that is already loaded (managed) should be represented in the pins store.
25322532
// also comparing location as it may have changed at this point
25332533
if requiredDependencies.contains(where: { $0.equalsIncludingLocation(dependency.packageRef) }) {
2534-
let pin = pinsStore.pins[dependency.packageRef.identity]
25352534
// if pin not found, or location is different (it may have changed at this point) pin it
2536-
if !(pin?.packageRef.equalsIncludingLocation(dependency.packageRef) ?? false) {
2535+
if pinsStore.pins[comparingLocation: dependency.packageRef] == .none {
25372536
pinsStore.pin(dependency)
25382537
}
25392538
} else if let pin = pinsStore.pins[dependency.packageRef.identity] {
@@ -3099,7 +3098,7 @@ extension Workspace {
30993098

31003099
// only update if necessary
31013100
if !workingCopy.exists(revision: revision) {
3102-
// The fetch operation may update contents of the checkout,
3101+
// The fetch operation may update contents of the checkout,
31033102
// so we need to do mutable-immutable dance.
31043103
try self.fileSystem.chmod(.userWritable, path: checkoutPath, options: [.recursive, .onlyFiles])
31053104
try workingCopy.fetch()
@@ -3951,3 +3950,12 @@ extension Workspace.ManagedDependencies {
39513950
})
39523951
}
39533952
}
3953+
3954+
extension PinsStore.Pins {
3955+
subscript(comparingLocation package: PackageReference) -> PinsStore.Pin? {
3956+
if let pin = self[package.identity], pin.packageRef.equalsIncludingLocation(package) {
3957+
return pin
3958+
}
3959+
return .none
3960+
}
3961+
}

0 commit comments

Comments
 (0)