//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #if !NO_SWIFTPM_DEPENDENCY import BuildServerProtocol @_spi(Testing) import BuildSystemIntegration import LanguageServerProtocol import LanguageServerProtocolExtensions import PackageModel import SKOptions import SKTestSupport import SourceKitLSP @preconcurrency import SPMBuildCore import SwiftExtensions import TSCBasic import TSCExtensions import ToolchainRegistry import XCTest import struct Basics.AbsolutePath import struct Basics.Triple private var hostTriple: Triple { get async throws { let toolchain = try await unwrap( ToolchainRegistry.forTesting.preferredToolchain(containing: [\.clang, \.clangd, \.sourcekitd, \.swift, \.swiftc]) ) let destinationToolchainBinDir = try XCTUnwrap(toolchain.swiftc?.deletingLastPathComponent()) let hostSDK = try SwiftSDK.hostSwiftSDK(Basics.AbsolutePath(validating: destinationToolchainBinDir.filePath)) let hostSwiftPMToolchain = try UserToolchain(swiftSDK: hostSDK) return hostSwiftPMToolchain.targetTriple } } final class SwiftPMBuildSystemTests: XCTestCase { func testNoPackage() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "" ] ) let packageRoot = tempDir.appendingPathComponent("pkg") let buildSystemSpec = SwiftPMBuildSystem.searchForConfig(in: packageRoot, options: try await .testDefault()) XCTAssertNil(buildSystemSpec) } } func testNoToolchain() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") await assertThrowsError( try await SwiftPMBuildSystem( projectRoot: packageRoot, toolchainRegistry: ToolchainRegistry(toolchains: []), options: SourceKitLSPOptions(), connectionToSourceKitLSP: LocalConnection(receiverName: "dummy"), testHooks: SwiftPMTestHooks() ) ) } } func testRelativeScratchPath() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = tempDir.appending(component: "pkg") let options = SourceKitLSPOptions( swiftPM: .init( scratchPath: "non_default_relative_build_path" ), backgroundIndexing: false ) let swiftpmBuildSystem = try await SwiftPMBuildSystem( projectRoot: packageRoot, toolchainRegistry: .forTesting, options: options, connectionToSourceKitLSP: LocalConnection(receiverName: "dummy"), testHooks: SwiftPMTestHooks() ) let dataPath = await swiftpmBuildSystem.destinationBuildParameters.dataPath let expectedScratchPath = packageRoot.appendingPathComponent(try XCTUnwrap(options.swiftPMOrDefault.scratchPath)) XCTAssertTrue(dataPath.asURL.isDescendant(of: expectedScratchPath)) } } func testBasicSwiftArgs() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.swift") let build = try await buildPath(root: packageRoot, platform: hostTriple.platformBuildPathComponent) assertNotNil(await buildSystemManager.initializationData?.indexDatabasePath) assertNotNil(await buildSystemManager.initializationData?.indexStorePath) let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain("-module-name", "lib", arguments: arguments) assertArgumentsContain("-parse-as-library", arguments: arguments) assertArgumentsContain("-target", arguments: arguments) // Only one! #if os(macOS) let versionString = PackageModel.Platform.macOS.oldestSupportedVersion.versionString assertArgumentsContain( "-target", try await hostTriple.tripleString(forPlatformVersion: versionString), arguments: arguments ) assertArgumentsContain("-sdk", arguments: arguments) assertArgumentsContain("-F", arguments: arguments, allowMultiple: true) #else assertArgumentsContain("-target", try await hostTriple.tripleString, arguments: arguments) #endif assertArgumentsContain("-I", try build.appendingPathComponent("Modules").filePath, arguments: arguments) assertArgumentsContain(try aswift.filePath, arguments: arguments) } } func testCompilerArgumentsForFileThatContainsPlusCharacterURLEncoded() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Sources/lib/a+something.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aPlusSomething = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a+something.swift") assertNotNil(await buildSystemManager.initializationData?.indexStorePath) let pathWithPlusEscaped = "\(try aPlusSomething.filePath.replacing("+", with: "%2B"))" #if os(Windows) let urlWithPlusEscaped = try XCTUnwrap(URL(string: "file:///\(pathWithPlusEscaped)")) #else let urlWithPlusEscaped = try XCTUnwrap(URL(string: "file://\(pathWithPlusEscaped)")) #endif let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(urlWithPlusEscaped), language: .swift, fallbackAfterTimeout: false ) ) .compilerArguments // Check that we have both source files in the compiler arguments, which means that we didn't compute the compiler // arguments for a+something.swift using substitute arguments from a.swift. XCTAssert( try arguments.contains(aPlusSomething.filePath), "Compiler arguments do not contain a+something.swift: \(arguments)" ) XCTAssert( try arguments.contains( packageRoot.appendingPathComponent("Sources").appendingPathComponent("lib").appendingPathComponent("a.swift") .filePath ), "Compiler arguments do not contain a.swift: \(arguments)" ) } } func testBuildSetup() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") let options = SourceKitLSPOptions.SwiftPMOptions( configuration: .release, scratchPath: try packageRoot.appendingPathComponent("non_default_build_path").filePath, cCompilerFlags: ["-m32"], swiftCompilerFlags: ["-typecheck"] ) let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(swiftPM: options), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.swift") let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain("-typecheck", arguments: arguments) assertArgumentsContain("-Xcc", "-m32", arguments: arguments) assertArgumentsContain("-O", arguments: arguments) } } func testManifestArgs() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let source = try packageRoot.appendingPathComponent("Package.swift").realpath let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(source), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain("-swift-version", "4.2", arguments: arguments) assertArgumentsContain(try source.filePath, arguments: arguments) } } func testMultiFileSwift() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Sources/lib/b.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package(name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.swift") let bswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("b.swift") let argumentsA = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain(try aswift.filePath, arguments: argumentsA) assertArgumentsContain(try bswift.filePath, arguments: argumentsA) let argumentsB = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain(try aswift.filePath, arguments: argumentsB) assertArgumentsContain(try bswift.filePath, arguments: argumentsB) } } func testMultiTargetSwift() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/libA/a.swift": "", "pkg/Sources/libB/b.swift": "", "pkg/Sources/libC/include/libC.h": "", "pkg/Sources/libC/libC.c": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [ .target(name: "libA", dependencies: ["libB", "libC"]), .target(name: "libB"), .target(name: "libC"), ] ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("libA") .appendingPathComponent("a.swift") let bswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("libB") .appendingPathComponent("b.swift") let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain(try aswift.filePath, arguments: arguments) assertArgumentsDoNotContain(try bswift.filePath, arguments: arguments) assertArgumentsContain( "-Xcc", "-I", "-Xcc", try packageRoot .appendingPathComponent("Sources") .appendingPathComponent("libC") .appendingPathComponent("include") .filePath, arguments: arguments ) let argumentsB = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(bswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain(try bswift.filePath, arguments: argumentsB) assertArgumentsDoNotContain(try aswift.filePath, arguments: argumentsB) assertArgumentsDoNotContain( "-I", try packageRoot .appendingPathComponent("Sources") .appendingPathComponent("libC") .appendingPathComponent("include") .filePath, arguments: argumentsB ) } } func testUnknownFile() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/libA/a.swift": "", "pkg/Sources/libB/b.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "libA")] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("libA") .appendingPathComponent("a.swift") let bswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("libB") .appendingPathComponent("b.swift") assertNotNil( await buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ) assertEqual( await buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(bswift), language: .swift, fallbackAfterTimeout: false )?.isFallback, true ) assertEqual( await buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(URL(string: "https://www.apple.com")!), language: .swift, fallbackAfterTimeout: false )?.isFallback, true ) } } func testBasicCXXArgs() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.cpp": "", "pkg/Sources/lib/b.cpp": "", "pkg/Sources/lib/include/a.h": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")], cxxLanguageStandard: .cxx14 ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let acxx = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.cpp") let bcxx = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("b.cpp") let header = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("include") .appendingPathComponent("a.h") let build = buildPath(root: packageRoot, platform: try await hostTriple.platformBuildPathComponent) assertNotNil(await buildSystemManager.initializationData?.indexStorePath) for file in [acxx, header] { let args = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(file), language: .cpp, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain("-std=c++14", arguments: args) assertArgumentsDoNotContain("-arch", arguments: args) assertArgumentsContain("-target", arguments: args) // Only one! #if os(macOS) let versionString = PackageModel.Platform.macOS.oldestSupportedVersion.versionString assertArgumentsContain( "-target", try await hostTriple.tripleString(forPlatformVersion: versionString), arguments: args ) assertArgumentsContain("-isysroot", arguments: args) assertArgumentsContain("-F", arguments: args, allowMultiple: true) #else assertArgumentsContain("-target", try await hostTriple.tripleString, arguments: args) #endif assertArgumentsContain( "-I", try packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("include") .filePath, arguments: args ) assertArgumentsDoNotContain("-I", try build.filePath, arguments: args) assertArgumentsDoNotContain(try bcxx.filePath, arguments: args) URL(fileURLWithPath: try build.appendingPathComponent("lib.build").appendingPathComponent("a.cpp.o").filePath) .withUnsafeFileSystemRepresentation { assertArgumentsContain("-o", String(cString: $0!), arguments: args) } } } } func testDeploymentTargetSwift() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:5.0 import PackageDescription let package = Package(name: "a", platforms: [.macOS(.v10_13)], targets: [.target(name: "lib")] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.swift") let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments assertArgumentsContain("-target", arguments: arguments) // Only one! #if os(macOS) try await assertArgumentsContain( "-target", hostTriple.tripleString(forPlatformVersion: "10.13"), arguments: arguments ) #else assertArgumentsContain("-target", try await hostTriple.tripleString, arguments: arguments) #endif } } func testSymlinkInWorkspaceSwift() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg_real/Sources/lib/a.swift": "", "pkg_real/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") try FileManager.default.createSymbolicLink( at: URL(fileURLWithPath: packageRoot.filePath), withDestinationURL: URL(fileURLWithPath: tempDir.appendingPathComponent("pkg_real").filePath) ) let buildSystemSpec = try unwrap( SwiftPMBuildSystem.searchForConfig(in: packageRoot, options: await .testDefault()) ) let buildSystemManager = await BuildSystemManager( buildSystemSpec: buildSystemSpec, toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswiftSymlink = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.swift") let aswiftReal = try aswiftSymlink.realpath let manifest = packageRoot.appendingPathComponent("Package.swift") let argumentsFromSymlink = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswiftSymlink), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments // We opened the project from a symlink. The realpath isn't part of the project and we should thus not receive // build settings for it. assertTrue( try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswiftReal), language: .swift, fallbackAfterTimeout: false ) ).isFallback ) assertArgumentsContain(try aswiftSymlink.filePath, arguments: argumentsFromSymlink) assertArgumentsDoNotContain(try aswiftReal.filePath, arguments: argumentsFromSymlink) let argsManifest = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(manifest), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments XCTAssertNotNil(argsManifest) assertArgumentsContain(try manifest.filePath, arguments: argsManifest) assertArgumentsDoNotContain(try manifest.realpath.filePath, arguments: argsManifest) } } func testSymlinkInWorkspaceCXX() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg_real/Sources/lib/a.cpp": "", "pkg_real/Sources/lib/b.cpp": "", "pkg_real/Sources/lib/include/a.h": "", "pkg_real/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")], cxxLanguageStandard: .cxx14 ) """, ] ) let acpp = ["Sources", "lib", "a.cpp"] let ah = ["Sources", "lib", "include", "a.h"] let realRoot = tempDir.appendingPathComponent("pkg_real") let symlinkRoot = tempDir.appendingPathComponent("pkg") try FileManager.default.createSymbolicLink( at: URL(fileURLWithPath: symlinkRoot.filePath), withDestinationURL: URL(fileURLWithPath: tempDir.appendingPathComponent("pkg_real").filePath) ) let buildSystemSpec = try unwrap( SwiftPMBuildSystem.searchForConfig(in: symlinkRoot, options: await .testDefault()) ) let buildSystemManager = await BuildSystemManager( buildSystemSpec: buildSystemSpec, toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() for file in [acpp, ah] { let args = try unwrap( await buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(symlinkRoot.appending(components: file)), language: .cpp, fallbackAfterTimeout: false )? .compilerArguments ) assertArgumentsDoNotContain(try realRoot.appending(components: file).filePath, arguments: args) assertArgumentsContain(try symlinkRoot.appending(components: file).filePath, arguments: args) } } } func testSwiftDerivedSources() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Sources/lib/a.txt": "", "pkg/Package.swift": """ // swift-tools-version:5.3 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib", resources: [.copy("a.txt")])] ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Sources") .appendingPathComponent("lib") .appendingPathComponent("a.swift") let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ) .compilerArguments assertArgumentsContain(try aswift.filePath, arguments: arguments) XCTAssertNotNil( arguments.firstIndex(where: { $0.hasSuffix(".swift") && $0.contains("DerivedSources") }), "missing resource_bundle_accessor.swift from \(arguments)" ) } } func testNestedInvalidPackageSwift() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/Package.swift": "// not a valid package", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let workspaceRoot = tempDir .appendingPathComponent("pkg") .appendingPathComponent("Sources") .appendingPathComponent("lib") let buildSystemSpec = SwiftPMBuildSystem.searchForConfig(in: workspaceRoot, options: try await .testDefault()) XCTAssertNil(buildSystemSpec) } } func testPluginArgs() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Plugins/MyPlugin/a.swift": "", "pkg/Sources/lib/lib.swift": "", "pkg/Package.swift": """ // swift-tools-version:5.7 import PackageDescription let package = Package( name: "a", targets: [ .target(name: "lib"), .plugin(name: "MyPlugin", capability: .buildTool) ] ) """, ] ) let packageRoot = tempDir.appendingPathComponent("pkg") let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let aswift = packageRoot .appendingPathComponent("Plugins") .appendingPathComponent("MyPlugin") .appendingPathComponent("a.swift") assertNotNil(await buildSystemManager.initializationData?.indexStorePath) let arguments = try await unwrap( buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(aswift), language: .swift, fallbackAfterTimeout: false ) ).compilerArguments // Plugins get compiled with the same compiler arguments as the package manifest assertArgumentsContain("-package-description-version", "5.7.0", arguments: arguments) assertArgumentsContain(try aswift.filePath, arguments: arguments) } } func testPackageWithDependencyWithoutResolving() async throws { // This package has a dependency but we haven't run `swift package resolve`. We don't want to resolve packages from // SourceKit-LSP because it has side-effects to the build directory. // But even without the dependency checked out, we should be able to create a SwiftPMBuildSystem and retrieve the // existing source files. let project = try await SwiftPMTestProject( files: [ "Tests/PackageTests/PackageTests.swift": """ import Testing 1️⃣@Test func topLevelTestPassing() {}2️⃣ """ ], manifest: """ let package = Package( name: "MyLibrary", dependencies: [.package(url: "https://github.com/swiftlang/swift-testing.git", branch: "main")], targets: [ .testTarget(name: "PackageTests", dependencies: [.product(name: "Testing", package: "swift-testing")]), ] ) """ ) let tests = try await project.testClient.send(WorkspaceTestsRequest()) XCTAssertEqual( tests, [ TestItem( id: "PackageTests.topLevelTestPassing()", label: "topLevelTestPassing()", disabled: false, style: "swift-testing", location: try project.location(from: "1️⃣", to: "2️⃣", in: "PackageTests.swift"), children: [], tags: [] ) ] ) } func testPackageLoadingWorkDoneProgress() async throws { let didReceiveWorkDoneProgressNotification = WrappedSemaphore(name: "work done progress received") let project = try await SwiftPMTestProject( files: [ "MyLibrary/Test.swift": "" ], capabilities: ClientCapabilities(window: WindowClientCapabilities(workDoneProgress: true)), hooks: Hooks( buildSystemHooks: BuildSystemHooks( swiftPMTestHooks: SwiftPMTestHooks(reloadPackageDidStart: { didReceiveWorkDoneProgressNotification.waitOrXCTFail() }) ) ), pollIndex: false, preInitialization: { testClient in testClient.handleMultipleRequests { (request: CreateWorkDoneProgressRequest) in return VoidResponse() } } ) let begin = try await project.testClient.nextNotification(ofType: WorkDoneProgress.self) XCTAssertEqual(begin.value, .begin(WorkDoneProgressBegin(title: "SourceKit-LSP: Reloading Package"))) didReceiveWorkDoneProgressNotification.signal() let end = try await project.testClient.nextNotification(ofType: WorkDoneProgress.self) XCTAssertEqual(end.token, begin.token) XCTAssertEqual(end.value, .end(WorkDoneProgressEnd())) } func testBuildSettingsForVersionSpecificPackageManifest() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, "pkg/Package@swift-5.8.swift": """ // swift-tools-version:4.2 import PackageDescription let package = Package( name: "a", targets: [.target(name: "lib")] ) """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let versionSpecificManifestURL = packageRoot.appendingPathComponent("Package@swift-5.8.swift") let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let settings = await buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(versionSpecificManifestURL), language: .swift, fallbackAfterTimeout: false ) let compilerArgs = try XCTUnwrap(settings?.compilerArguments) assertContains(compilerArgs, "-package-description-version") assertContains(compilerArgs, try versionSpecificManifestURL.filePath) } } func testBuildSettingsForInvalidManifest() async throws { try await withTestScratchDir { tempDir in try FileManager.default.createFiles( root: tempDir, files: [ "pkg/Sources/lib/a.swift": "", "pkg/Package.swift": """ // swift-tools-version:5.1 import PackageDescription """, ] ) let packageRoot = try tempDir.appendingPathComponent("pkg").realpath let manifestURL = packageRoot.appendingPathComponent("Package.swift") let buildSystemManager = await BuildSystemManager( buildSystemSpec: .swiftpmSpec(for: packageRoot), toolchainRegistry: .forTesting, options: SourceKitLSPOptions(), connectionToClient: DummyBuildSystemManagerConnectionToClient(), buildSystemHooks: BuildSystemHooks() ) await buildSystemManager.waitForUpToDateBuildGraph() let settings = await buildSystemManager.buildSettingsInferredFromMainFile( for: DocumentURI(manifestURL), language: .swift, fallbackAfterTimeout: false ) let compilerArgs = try XCTUnwrap(settings?.compilerArguments) assertArgumentsContain("-package-description-version", "5.1.0", arguments: compilerArgs) assertContains(compilerArgs, try manifestURL.filePath) } } } private func assertArgumentsDoNotContain( _ pattern: String..., arguments: [String], file: StaticString = #filePath, line: UInt = #line ) { if let index = arguments.firstRange(of: pattern)?.startIndex { XCTFail( "not-pattern \(pattern) unexpectedly found at \(index) in arguments \(arguments)", file: file, line: line ) return } } private func assertArgumentsContain( _ pattern: String..., arguments: [String], allowMultiple: Bool = false, file: StaticString = #filePath, line: UInt = #line ) { guard let index = arguments.firstRange(of: pattern)?.startIndex else { XCTFail("pattern \(pattern) not found in arguments \(arguments)", file: file, line: line) return } if !allowMultiple, let index2 = arguments[(index + 1)...].firstRange(of: pattern)?.startIndex { XCTFail( "pattern \(pattern) found twice (\(index), \(index2)) in \(arguments)", file: file, line: line ) } } private func buildPath( root: URL, options: SourceKitLSPOptions.SwiftPMOptions = SourceKitLSPOptions.SwiftPMOptions(), platform: String ) -> URL { let buildPath = if let scratchPath = options.scratchPath { URL(fileURLWithPath: scratchPath) } else { root.appendingPathComponent(".build").appendingPathComponent("index-build") } return buildPath.appendingPathComponent(platform).appendingPathComponent("\(options.configuration ?? .debug)") } fileprivate extension URL { func appending(components: [String]) -> URL { var result = self for component in components { result.appendPathComponent(component) } return result } } fileprivate extension BuildSystemSpec { static func swiftpmSpec(for packageRoot: URL) -> BuildSystemSpec { return BuildSystemSpec( kind: .swiftPM, projectRoot: packageRoot, configPath: packageRoot.appendingPathComponent("Package.swift") ) } } #endif