Skip to content

Commit 3c692dc

Browse files
authored
Remove slightly dangerous optimization (#4317)
If there happens to be a file at the location passed into the `ManifestLoader, we were using that to compile the manifest, instead of the actual contents. This is dangerous, because the public API of `ManifestLoader` accepts a custom filesystem parameter that is not taken into account at all here, so the file being used might not be the right one, it just has the same path on the local filesystem. We could have done some more work to preserve the optimization in the case where the used filesystem is the local one and the correct file is being used, but that doesn't seem worth it since the vast majority of loads are being done for remote dependencies (this we may load multiple times as we are resolving) which are never using the local filesystem.
1 parent f6bde10 commit 3c692dc

File tree

5 files changed

+82
-26
lines changed

5 files changed

+82
-26
lines changed

Sources/Basics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ add_library(Basics
3232
SwiftVersion.swift
3333
SQLiteBackedCache.swift
3434
Version+Extensions.swift
35+
VFSOverlay.swift
3536
WritableByteStream+Extensions.swift)
3637
target_link_libraries(Basics PUBLIC
3738
SwiftCollections::OrderedCollections

Sources/Basics/VFSOverlay.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) 2022 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 class Foundation.JSONEncoder
14+
import struct TSCBasic.AbsolutePath
15+
import protocol TSCBasic.FileSystem
16+
17+
public struct VFSOverlay: Encodable {
18+
public struct File: Encodable {
19+
enum CodingKeys: String, CodingKey {
20+
case externalContents = "external-contents"
21+
case name
22+
case type
23+
}
24+
25+
private let externalContents: String
26+
private let name: String
27+
private let type = "file"
28+
29+
public init(name: String, externalContents: String) {
30+
self.name = name
31+
self.externalContents = externalContents
32+
}
33+
}
34+
35+
enum CodingKeys: String, CodingKey {
36+
case roots
37+
case useExternalNames = "use-external-names"
38+
case version
39+
}
40+
41+
private let roots: [File]
42+
private let useExternalNames = false
43+
private let version = 0
44+
45+
public init(roots: [File]) {
46+
self.roots = roots
47+
}
48+
49+
public func write(to path: AbsolutePath, fileSystem: FileSystem) throws {
50+
// VFS overlay files are YAML, but ours is simple enough that it works when being written using `JSONEncoder`.
51+
try JSONEncoder.makeWithDefaults(prettified: false).encode(path: path, fileSystem: fileSystem, self)
52+
}
53+
}

Sources/PackageLoading/ManifestLoader.swift

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -442,29 +442,26 @@ public final class ManifestLoader: ManifestLoaderProtocol {
442442
completion: @escaping (Result<EvaluationResult, Error>) -> Void
443443
) throws {
444444
do {
445-
if localFileSystem.isFile(manifestPath) {
445+
try withTemporaryDirectory { tempDir, cleanupTempDir in
446+
let manifestTempFilePath = tempDir.appending(component: "manifest.swift")
447+
try localFileSystem.writeFileContents(manifestTempFilePath, bytes: ByteString(manifestContents))
448+
449+
let vfsOverlayTempFilePath = tempDir.appending(component: "vfs.yaml")
450+
try VFSOverlay(roots: [
451+
VFSOverlay.File(name: manifestPath.pathString, externalContents: manifestTempFilePath.pathString)
452+
]).write(to: vfsOverlayTempFilePath, fileSystem: localFileSystem)
453+
446454
try self.evaluateManifest(
447455
at: manifestPath,
456+
vfsOverlayPath: vfsOverlayTempFilePath,
448457
packageIdentity: packageIdentity,
449458
toolsVersion: toolsVersion,
450-
delegateQueue: delegateQueue,
451-
callbackQueue: callbackQueue,
452-
completion: completion
453-
)
454-
} else {
455-
try withTemporaryFile(suffix: ".swift") { tempFile, cleanupTempFile in
456-
try localFileSystem.writeFileContents(tempFile.path, bytes: ByteString(manifestContents))
457-
try self.evaluateManifest(
458-
at: tempFile.path,
459-
packageIdentity: packageIdentity,
460-
toolsVersion: toolsVersion,
461-
delegateQueue: delegateQueue,
462-
callbackQueue: callbackQueue
463-
) { result in
464-
dispatchPrecondition(condition: .onQueue(callbackQueue))
465-
cleanupTempFile(tempFile)
466-
completion(result)
467-
}
459+
delegateQueue: delegateQueue,
460+
callbackQueue: callbackQueue
461+
) { result in
462+
dispatchPrecondition(condition: .onQueue(callbackQueue))
463+
cleanupTempDir(tempDir)
464+
completion(result)
468465
}
469466
}
470467
} catch {
@@ -477,6 +474,7 @@ public final class ManifestLoader: ManifestLoaderProtocol {
477474
/// Helper method for evaluating the manifest.
478475
func evaluateManifest(
479476
at manifestPath: AbsolutePath,
477+
vfsOverlayPath: AbsolutePath? = nil,
480478
packageIdentity: PackageIdentity,
481479
toolsVersion: ToolsVersion,
482480
delegateQueue: DispatchQueue,
@@ -512,6 +510,10 @@ public final class ManifestLoader: ManifestLoaderProtocol {
512510
var cmd: [String] = []
513511
cmd += [self.toolchain.swiftCompilerPathForManifests.pathString]
514512

513+
if let vfsOverlayPath = vfsOverlayPath {
514+
cmd += ["-vfsoverlay", vfsOverlayPath.pathString]
515+
}
516+
515517
// if runtimePath is set to "PackageFrameworks" that means we could be developing SwiftPM in Xcode
516518
// which produces a framework for dynamic package products.
517519
if runtimePath.extension == "framework" {

Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests {
609609

610610
XCTAssertNoDiagnostics(observability.diagnostics)
611611
XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath])
612-
XCTAssertEqual(try delegate.parsed(timeout: .now() + 1), expectCached ? [] : [manifestPath])
612+
XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1)
613613
XCTAssertEqual(manifest.displayName, "Trivial")
614614
XCTAssertEqual(manifest.targets[0].name, "foo")
615615
}
@@ -669,7 +669,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests {
669669

670670
XCTAssertNoDiagnostics(observability.diagnostics)
671671
XCTAssertEqual(try delegate.loaded(timeout: .now() + 1), [manifestPath])
672-
XCTAssertEqual(try delegate.parsed(timeout: .now() + 1), expectCached ? [] : [manifestPath])
672+
XCTAssertEqual(try delegate.parsed(timeout: .now() + 1).count, expectCached ? 0 : 1)
673673
XCTAssertEqual(manifest.displayName, "Trivial")
674674
XCTAssertEqual(manifest.targets[0].name, "foo")
675675
}

Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,19 +221,19 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests {
221221
222222
let fileManager = FileManager.default
223223
let contents = (try? fileManager.contentsOfDirectory(atPath: Context.packageDirectory)) ?? []
224-
let swiftFiles = contents.filter { $0.hasPrefix("TemporaryFile") && $0.hasSuffix(".swift") }
225224
226-
let package = Package(name: swiftFiles.joined(separator: ","))
225+
let package = Package(name: contents.joined(separator: ","))
227226
"""
228227

229228
let observability = ObservabilitySystem.makeForTesting()
230229
let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope)
231230
XCTAssertNoDiagnostics(observability.diagnostics)
232231
XCTAssertNoDiagnostics(validationDiagnostics)
233232

234-
let name = parsedManifest.components?.last ?? ""
235-
let swiftFiles = manifest.displayName.split(separator: ",").map(String.init)
236-
XCTAssertNotNil(swiftFiles.firstIndex(of: name))
233+
let files = manifest.displayName.split(separator: ",").map(String.init)
234+
// Since we're loading `/Package.swift` in these tests, the context's package directory is supposed to be /.
235+
let expectedFiles = try FileManager.default.contentsOfDirectory(atPath: "/")
236+
XCTAssertEqual(files, expectedFiles)
237237
}
238238

239239
func testCommandPluginTarget() throws {

0 commit comments

Comments
 (0)