Skip to content

Commit 5929cf9

Browse files
committed
Watch for changes to Package.resolved
We need to watch for changes to `Package.resolved` so that we can update the dependency checkouts in `.index-build` when the user runs `swift package update`. rdar://130103181
1 parent c557f16 commit 5929cf9

File tree

7 files changed

+179
-20
lines changed

7 files changed

+179
-20
lines changed

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
692692
packageGraph: self.modulesGraph
693693
)
694694
case .changed:
695-
return fileURL.lastPathComponent == "Package.swift"
695+
return fileURL.lastPathComponent == "Package.swift" || fileURL.lastPathComponent == "Package.resolved"
696696
default: // Unknown file change type
697697
return false
698698
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import LSPTestSupport
14+
import XCTest
15+
16+
/// Runs the body repeatedly once per second until it returns `true`, giving up after `timeout`.
17+
///
18+
/// This is useful to test some request that requires global state to be updated but will eventually converge on the
19+
/// correct result.
20+
///
21+
/// If `bodyHasOneSecondDelay` is true, it is assume that the body already has a one-second delay between iterations.
22+
public func repeatUntilExpectedResult(
23+
_ body: () async throws -> Bool,
24+
bodyHasOneSecondDelay: Bool = false,
25+
timeout: TimeInterval = defaultTimeout,
26+
file: StaticString = #filePath,
27+
line: UInt = #line
28+
) async throws {
29+
for _ in 0..<Int(timeout) {
30+
if try await body() {
31+
return
32+
}
33+
if !bodyHasOneSecondDelay {
34+
try await Task.sleep(for: .seconds(1))
35+
}
36+
}
37+
XCTFail("Failed to get expected result", file: file, line: line)
38+
}

Sources/SKTestSupport/SwiftPMDependencyProject.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,20 @@ public class SwiftPMDependencyProject {
8686
}
8787

8888
try await runGitCommand(["init"], workingDirectory: packageDirectory)
89+
try await tag(changedFiles: files.keys.map { $0.url(relativeTo: packageDirectory) }, version: "1.0.0")
90+
}
91+
92+
public func tag(changedFiles: [URL], version: String) async throws {
8993
try await runGitCommand(
90-
["add"] + files.keys.map { $0.url(relativeTo: packageDirectory).path },
94+
["add"] + changedFiles.map(\.path),
9195
workingDirectory: packageDirectory
9296
)
9397
try await runGitCommand(
94-
["-c", "user.name=Dummy", "-c", "[email protected]", "commit", "-m", "Initial commit"],
98+
["-c", "user.name=Dummy", "-c", "[email protected]", "commit", "-m", "Version \(version)"],
9599
workingDirectory: packageDirectory
96100
)
97-
try await runGitCommand(["tag", "1.0.0"], workingDirectory: packageDirectory)
101+
102+
try await runGitCommand(["tag", version], workingDirectory: self.packageDirectory)
98103
}
99104

100105
deinit {

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,7 @@ extension SourceKitLSPServer {
11891189
return FileSystemWatcher(globPattern: "**/*.\(fileExtension)", kind: [.create, .change, .delete])
11901190
}
11911191
watchers.append(FileSystemWatcher(globPattern: "**/Package.swift", kind: [.change]))
1192+
watchers.append(FileSystemWatcher(globPattern: "**/Package.resolved", kind: [.change]))
11921193
watchers.append(FileSystemWatcher(globPattern: "**/compile_commands.json", kind: [.create, .change, .delete]))
11931194
watchers.append(FileSystemWatcher(globPattern: "**/compile_flags.txt", kind: [.create, .change, .delete]))
11941195
// Watch for changes to `.swiftmodule` files to detect updated modules during a build.

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212

1313
import LSPTestSupport
1414
import LanguageServerProtocol
15-
import SKCore
15+
@_spi(Testing) import SKCore
1616
import SKTestSupport
1717
import SemanticIndex
1818
import SourceKitLSP
1919
import XCTest
2020

21+
import class TSCBasic.Process
22+
2123
final class BackgroundIndexingTests: XCTestCase {
2224
func testBackgroundIndexingOfSingleFile() async throws {
2325
let project = try await SwiftPMTestProject(
@@ -1094,4 +1096,126 @@ final class BackgroundIndexingTests: XCTestCase {
10941096
)
10951097
XCTAssertEqual(response, .locations([try project.location(from: "1️⃣", to: "1️⃣", in: "LibB.swift")]))
10961098
}
1099+
1100+
func testUpdatePackageDependency() async throws {
1101+
try SkipUnless.longTestsEnabled()
1102+
1103+
let dependencyProject = try await SwiftPMDependencyProject(files: [
1104+
"Sources/MyDependency/Dependency.swift": """
1105+
/// Do something v1.0.0
1106+
public func doSomething() {}
1107+
"""
1108+
])
1109+
let dependencySwiftURL = dependencyProject.packageDirectory
1110+
.appendingPathComponent("Sources")
1111+
.appendingPathComponent("MyDependency")
1112+
.appendingPathComponent("Dependency.swift")
1113+
defer { dependencyProject.keepAlive() }
1114+
1115+
let project = try await SwiftPMTestProject(
1116+
files: [
1117+
"Test.swift": """
1118+
import MyDependency
1119+
1120+
func test() {
1121+
1️⃣doSomething()
1122+
}
1123+
"""
1124+
],
1125+
manifest: """
1126+
let package = Package(
1127+
name: "MyLibrary",
1128+
dependencies: [.package(url: "\(dependencyProject.packageDirectory)", from: "1.0.0")],
1129+
targets: [
1130+
.target(
1131+
name: "MyLibrary",
1132+
dependencies: [.product(name: "MyDependency", package: "MyDependency")]
1133+
)
1134+
]
1135+
)
1136+
""",
1137+
enableBackgroundIndexing: true
1138+
)
1139+
let packageResolvedURL = project.scratchDirectory.appendingPathComponent("Package.resolved")
1140+
1141+
let originalPackageResolvedContents = try String(contentsOf: packageResolvedURL)
1142+
1143+
// First check our setup to see that we get the expected hover response before changing the dependency project.
1144+
let (uri, positions) = try project.openDocument("Test.swift")
1145+
let hoverBeforeUpdate = try await project.testClient.send(
1146+
HoverRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1147+
)
1148+
XCTAssert(
1149+
hoverBeforeUpdate?.contents.markupContent?.value.contains("Do something v1.0.0") ?? false,
1150+
"Did not contain expected string: \(String(describing: hoverBeforeUpdate))"
1151+
)
1152+
1153+
// Just committing a new version of the dependency shouldn't change anything because we didn't update the package
1154+
// dependencies.
1155+
try """
1156+
/// Do something v1.1.0
1157+
public func doSomething() {}
1158+
""".write(to: dependencySwiftURL, atomically: true, encoding: .utf8)
1159+
try await dependencyProject.tag(changedFiles: [dependencySwiftURL], version: "1.1.0")
1160+
1161+
let hoverAfterNewVersionCommit = try await project.testClient.send(
1162+
HoverRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1163+
)
1164+
XCTAssert(
1165+
hoverAfterNewVersionCommit?.contents.markupContent?.value.contains("Do something v1.0.0") ?? false,
1166+
"Did not contain expected string: \(String(describing: hoverBeforeUpdate))"
1167+
)
1168+
1169+
// Updating Package.swift causes a package reload but should not cause dependencies to be updated.
1170+
project.testClient.send(
1171+
DidChangeWatchedFilesNotification(changes: [
1172+
FileEvent(uri: DocumentURI(project.scratchDirectory.appendingPathComponent("Package.resolved")), type: .changed)
1173+
])
1174+
)
1175+
_ = try await project.testClient.send(PollIndexRequest())
1176+
XCTAssertEqual(try String(contentsOf: packageResolvedURL), originalPackageResolvedContents)
1177+
1178+
// Simulate a package update which goes as follows:
1179+
// - The user runs `swift package update`
1180+
// - This updates `Package.resolved`, which we watch
1181+
// - We reload the package, which updates `Dependency.swift` in `.index-build/checkouts`, which we also watch.
1182+
try await Process.run(
1183+
arguments: [
1184+
unwrap(ToolchainRegistry.forTesting.default?.swift?.pathString),
1185+
"package", "update",
1186+
"--package-path", project.scratchDirectory.path,
1187+
],
1188+
workingDirectory: nil
1189+
)
1190+
XCTAssertNotEqual(try String(contentsOf: packageResolvedURL), originalPackageResolvedContents)
1191+
project.testClient.send(
1192+
DidChangeWatchedFilesNotification(changes: [
1193+
FileEvent(uri: DocumentURI(project.scratchDirectory.appendingPathComponent("Package.resolved")), type: .changed)
1194+
])
1195+
)
1196+
_ = try await project.testClient.send(PollIndexRequest())
1197+
project.testClient.send(
1198+
DidChangeWatchedFilesNotification(
1199+
changes: FileManager.default.findFiles(named: "Dependency.swift", in: project.scratchDirectory).map {
1200+
FileEvent(uri: DocumentURI($0), type: .changed)
1201+
}
1202+
)
1203+
)
1204+
1205+
try await repeatUntilExpectedResult {
1206+
let hoverAfterPackageUpdate = try await project.testClient.send(
1207+
HoverRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1208+
)
1209+
return hoverAfterPackageUpdate?.contents.markupContent?.value.contains("Do something v1.1.0") ?? false
1210+
}
1211+
}
1212+
}
1213+
1214+
extension HoverResponseContents {
1215+
var markupContent: MarkupContent? {
1216+
switch self {
1217+
case .markupContent(let markupContent): return markupContent
1218+
default: return nil
1219+
}
1220+
}
10971221
}

Tests/SourceKitLSPTests/BuildSystemTests.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,10 @@ final class BuildSystemTests: XCTestCase {
187187

188188
await buildSystem.delegate?.fileBuildSettingsChanged([doc])
189189

190-
var receivedCorrectDiagnostic = false
191-
for _ in 0..<Int(defaultTimeout) {
190+
try await repeatUntilExpectedResult {
192191
let refreshedDiags = try await testClient.nextDiagnosticsNotification(timeout: .seconds(1))
193-
if refreshedDiags.diagnostics.count == 0, try text == documentManager.latestSnapshot(doc).text {
194-
receivedCorrectDiagnostic = true
195-
break
196-
}
192+
return try text == documentManager.latestSnapshot(doc).text && refreshedDiags.diagnostics.count == 0
197193
}
198-
XCTAssert(receivedCorrectDiagnostic)
199194
}
200195

201196
func testSwiftDocumentUpdatedBuildSettings() async throws {

Tests/SourceKitLSPTests/MainFilesProviderTests.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,12 @@ final class MainFilesProviderTests: XCTestCase {
201201
// `clangd` may return diagnostics from the old build settings sometimes (I believe when it's still building the
202202
// preamble for shared.h when the new build settings come in). Check that it eventually returns the correct
203203
// diagnostics.
204-
var receivedCorrectDiagnostic = false
205-
for _ in 0..<Int(defaultTimeout) {
204+
try await repeatUntilExpectedResult {
206205
let refreshedDiags = try await project.testClient.nextDiagnosticsNotification(timeout: .seconds(1))
207-
if let diagnostic = refreshedDiags.diagnostics.only,
208-
diagnostic.message == "Unused variable 'fromMyFancyLibrary'"
209-
{
210-
receivedCorrectDiagnostic = true
211-
break
206+
guard let diagnostic = refreshedDiags.diagnostics.only else {
207+
return false
212208
}
209+
return diagnostic.message == "Unused variable 'fromMyFancyLibrary'"
213210
}
214-
XCTAssert(receivedCorrectDiagnostic)
215211
}
216212
}

0 commit comments

Comments
 (0)