diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml index 0c1f448e454..290bf8768f4 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -2,6 +2,14 @@ name: Bug Report description: Something isn't working as expected labels: [bug] body: +- type: checkboxes + id: cat-preferences + attributes: + label: "Is it reproducible with SwiftPM command-line tools: `swift build`, `swift test`, `swift package` etc?" + description: "Issues related to closed-source software are not tracked by this repository and will be closed. For Xcode, please file a feedback at https://feedbackassistant.apple.com instead." + options: + - label: Confirmed reproduction steps with SwiftPM CLI. + required: true - type: textarea attributes: label: Description diff --git a/.swiftformat b/.swiftformat index 8ae09674179..4ee24410ab8 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,6 +1,6 @@ ## File options ---swiftversion 5.7 +--swiftversion 5.9 --exclude .build ## Formatting options diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme deleted file mode 100644 index 447e5415e3b..00000000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/SwiftPM-Package.xcscheme +++ /dev/null @@ -1,911 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Benchmarks/Benchmarks/PackageGraphBenchmarks/PackageGraphBenchmarks.swift b/Benchmarks/Benchmarks/PackageGraphBenchmarks/PackageGraphBenchmarks.swift new file mode 100644 index 00000000000..bab1f4af582 --- /dev/null +++ b/Benchmarks/Benchmarks/PackageGraphBenchmarks/PackageGraphBenchmarks.swift @@ -0,0 +1,184 @@ +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +import Basics +import Benchmark +import Foundation +import PackageModel + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +import func PackageGraph.loadModulesGraph + +import class TSCBasic.InMemoryFileSystem +import Workspace + +let benchmarks = { + let defaultMetrics: [BenchmarkMetric] + if let envVar = ProcessInfo.processInfo.environment["SWIFTPM_BENCHMARK_ALL_METRICS"], + envVar.lowercased() == "true" || envVar == "1" { + defaultMetrics = .all + } else { + defaultMetrics = [ + .mallocCountTotal, + .syscalls, + ] + } + + let modulesGraphDepth: Int + if let envVar = ProcessInfo.processInfo.environment["SWIFTPM_BENCHMARK_MODULES_GRAPH_DEPTH"], + let parsedValue = Int(envVar) { + modulesGraphDepth = parsedValue + } else { + modulesGraphDepth = 150 + } + + let modulesGraphWidth: Int + if let envVar = ProcessInfo.processInfo.environment["SWIFTPM_BENCHMARK_MODULES_GRAPH_WIDTH"], + let parsedValue = Int(envVar) { + modulesGraphWidth = parsedValue + } else { + modulesGraphWidth = 150 + } + + let packagesGraphDepth: Int + if let envVar = ProcessInfo.processInfo.environment["SWIFTPM_BENCHMARK_PACKAGES_GRAPH_DEPTH"], + let parsedValue = Int(envVar) { + packagesGraphDepth = parsedValue + } else { + packagesGraphDepth = 10 + } + + // Benchmarks computation of a resolved graph of modules for a package using `Workspace` as an entry point. It runs PubGrub to get + // resolved concrete versions of dependencies, assigning all modules and products to each other as corresponding dependencies + // with their build triples, but with the build plan not yet constructed. In this benchmark specifically we're loading `Package.swift` + // for SwiftPM itself. + Benchmark( + "SwiftPMWorkspaceModulesGraph", + configuration: .init( + metrics: defaultMetrics, + maxDuration: .seconds(10), + thresholds: [ + .mallocCountTotal: .init(absolute: [.p90: 12000]), + .syscalls: .init(absolute: [.p90: 1600]), + ] + ) + ) { benchmark in + let path = try AbsolutePath(validating: #file).parentDirectory.parentDirectory.parentDirectory + let workspace = try Workspace(fileSystem: localFileSystem, location: .init(forRootPackage: path, fileSystem: localFileSystem)) + + for _ in benchmark.scaledIterations { + try workspace.loadPackageGraph(rootPath: path, observabilityScope: ObservabilitySystem.NOOP) + } + } + + // Benchmarks computation of a resolved graph of modules for a trivial synthesized package using `loadModulesGraph` + // as an entry point, which almost immediately delegates to `ModulesGraph.load` under the hood. + Benchmark( + "SyntheticModulesGraph", + configuration: .init( + metrics: defaultMetrics, + maxDuration: .seconds(10), + thresholds: [ + .mallocCountTotal: .init(absolute: [.p90: 17000]), + .syscalls: .init(absolute: [.p90: 5]), + ] + ) + ) { benchmark in + try syntheticModulesGraph( + benchmark, + modulesGraphDepth: modulesGraphDepth, + modulesGraphWidth: modulesGraphWidth + ) + } + + // Benchmarks computation of a resolved graph of modules for a synthesized package that includes macros, + // using `loadModulesGraph` as an entry point, which almost immediately delegates to `ModulesGraph.load` under + // the hood. + Benchmark( + "SyntheticModulesGraphWithMacros", + configuration: .init( + metrics: defaultMetrics, + maxDuration: .seconds(10), + thresholds: [ + .mallocCountTotal: .init(absolute: [.p90: 8000]), + .syscalls: .init(absolute: [.p90: 5]), + ] + ) + ) { benchmark in + try syntheticModulesGraph( + benchmark, + modulesGraphDepth: modulesGraphDepth, + modulesGraphWidth: modulesGraphWidth, + includeMacros: true + ) + } +} + +func syntheticModulesGraph( + _ benchmark: Benchmark, + modulesGraphDepth: Int, + modulesGraphWidth: Int, + includeMacros: Bool = false +) throws { + // If macros are included, modules are split in three parts: + // 1. top-level modules + // 2. macros + // 3. dependencies of macros + let macrosDenominator = includeMacros ? 3 : 1 + let libraryModules: [TargetDescription] = try (0..<(modulesGraphWidth / macrosDenominator)).map { i -> TargetDescription in + let dependencies = (0.. [TargetDescription.Dependency] in + if includeMacros { + [.target(name: "Module\(i)"), .target(name: "Macros\(i)")] + } else { + [.target(name: "Module\(i)")] + } + } + return try TargetDescription(name: "Module\(i)", dependencies: dependencies) + } + + let macrosModules: [TargetDescription] + let macrosDependenciesModules: [TargetDescription] + if includeMacros { + macrosModules = try (0..:-package-name;SwiftPM>") +add_subdirectory(BuildSupport/SwiftSyntax) add_subdirectory(Sources) add_subdirectory(cmake/modules) diff --git a/CODEOWNERS b/CODEOWNERS index 4ddc2dd2776..d08f417f13b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,10 +8,6 @@ # (W), PGP key ID and fingerprint (P), description (D), and snail-mail address # (S). -# N: Boris Buegling -# E: bbuegling@apple.com -# D: Package Manager - # N: Tomer Doron # E: tomer@apple.com # D: Package Manager @@ -28,4 +24,6 @@ # The following lines are used by GitHub to automatically recommend reviewers. -* @bnbarham @MaxDesiatov @neonichu @tomerd +Sources/XCBuildSupport/* @jakepetroules + +* @bnbarham @MaxDesiatov @jakepetroules @francescomikulis diff --git a/Documentation/PackageDescription.md b/Documentation/PackageDescription.md index 7d2b6aa1260..9595c9b41e1 100644 --- a/Documentation/PackageDescription.md +++ b/Documentation/PackageDescription.md @@ -235,7 +235,7 @@ let package = Package( /// - Parameters: /// - name: The name of the library product. /// - type: The optional type of the library that is used to determine how to link to the library. -/// Leave this parameter unspecified to let to let the Swift Package Manager choose between static or dynamic linking (recommended). +/// Leave this parameter unspecified to let the Swift Package Manager choose between static or dynamic linking (recommended). /// If you do not support both linkage types, use `.static` or `.dynamic` for this parameter. /// - targets: The targets that are bundled into a library product. static func library(name: String, type: Product.Library.LibraryType? = nil, targets: [String]) -> Product diff --git a/Examples/package-info/Package.swift b/Examples/package-info/Package.swift deleted file mode 100644 index 94e5d6d6ffa..00000000000 --- a/Examples/package-info/Package.swift +++ /dev/null @@ -1,25 +0,0 @@ -// swift-tools-version:5.5 - -import PackageDescription - -let package = Package( - name: "package-info", - platforms: [ - .macOS(.v12), - .iOS(.v13) - ], - dependencies: [ - // This just points to the SwiftPM at the root of this repository. - .package(name: "swift-package-manager", path: "../../"), - // You will want to depend on a stable semantic version instead: - // .package(url: "https://github.com/apple/swift-package-manager", .exact("0.4.0")) - ], - targets: [ - .executableTarget( - name: "package-info", - dependencies: [ - .product(name: "SwiftPM", package: "swift-package-manager") - ] - ), - ] -) diff --git a/Examples/package-info/README.md b/Examples/package-info/README.md deleted file mode 100644 index 8d316f30a44..00000000000 --- a/Examples/package-info/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# package-info - -Sample package built on top of libSwiftPM. diff --git a/Examples/package-info/Sources/package-info/example.swift b/Examples/package-info/Sources/package-info/example.swift deleted file mode 100644 index 0ec45d4ecf5..00000000000 --- a/Examples/package-info/Sources/package-info/example.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Basics -import Workspace - -@main -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -struct Example { - static func main() async throws { - // PREREQUISITES - // ============ - - // We need a package to work with. - // This computes the path of this package root based on the file location - let packagePath = try AbsolutePath(validating: #file).parentDirectory.parentDirectory.parentDirectory - - // LOADING - // ======= - - // There are several levels of information available. - // Each takes longer to load than the level above it, but provides more detail. - - let observability = ObservabilitySystem({ print("\($0): \($1)") }) - - let workspace = try Workspace(forRootPackage: packagePath) - - let manifest = try await workspace.loadRootManifest(at: packagePath, observabilityScope: observability.topScope) - - let package = try await workspace.loadRootPackage(at: packagePath, observabilityScope: observability.topScope) - - let graph = try workspace.loadPackageGraph(rootPath: packagePath, observabilityScope: observability.topScope) - - // EXAMPLES - // ======== - - // Manifest - let products = manifest.products.map({ $0.name }).joined(separator: ", ") - print("Products:", products) - - let targets = manifest.targets.map({ $0.name }).joined(separator: ", ") - print("Targets:", targets) - - // Package - let executables = package.targets.filter({ $0.type == .executable }).map({ $0.name }) - print("Executable targets:", executables) - - // PackageGraph - let numberOfFiles = graph.reachableTargets.reduce(0, { $0 + $1.sources.paths.count }) - print("Total number of source files (including dependencies):", numberOfFiles) - } -} diff --git a/Fixtures/Miscellaneous/DoNotFilterLinkerDiagnostics/Package.swift b/Fixtures/Miscellaneous/DoNotFilterLinkerDiagnostics/Package.swift index 326e3041c52..636e93fcc67 100644 --- a/Fixtures/Miscellaneous/DoNotFilterLinkerDiagnostics/Package.swift +++ b/Fixtures/Miscellaneous/DoNotFilterLinkerDiagnostics/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.11 +// swift-tools-version: 6.0 import PackageDescription @@ -8,9 +8,7 @@ let package = Package( .executableTarget( name: "DoNotFilterLinkerDiagnostics", linkerSettings: [ - .linkedLibrary("z"), - .unsafeFlags(["-lz"]), - // should produce: ld: warning: ignoring duplicate libraries: '-lz' + .unsafeFlags(["-Lfoobar"]), ] ), ] diff --git a/Fixtures/Miscellaneous/Plugins/DependentPlugins/Package.swift b/Fixtures/Miscellaneous/Plugins/DependentPlugins/Package.swift index ca0411b5e2e..19db113db4f 100644 --- a/Fixtures/Miscellaneous/Plugins/DependentPlugins/Package.swift +++ b/Fixtures/Miscellaneous/Plugins/DependentPlugins/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.11 +// swift-tools-version: 6.0 import PackageDescription @@ -10,7 +10,7 @@ let package = Package( targets: [ .executableTarget(name: "MyExecutable"), .executableTarget(name: "MyExecutable2"), - + .plugin( name: "MyPlugin", capability: .buildTool(), @@ -18,7 +18,7 @@ let package = Package( "MyExecutable" ] ), - + .plugin( name: "MyPlugin2", capability: .buildTool(), @@ -34,5 +34,6 @@ let package = Package( "MyPlugin2", ] ), - ] + ], + swiftLanguageVersions: [.v5] ) diff --git a/Fixtures/Miscellaneous/Plugins/MyBuildToolPluginDependencies/Sources/MySourceGenBuildTool/main.swift b/Fixtures/Miscellaneous/Plugins/MyBuildToolPluginDependencies/Sources/MySourceGenBuildTool/main.swift index 94f7766628c..bedca50b459 100644 --- a/Fixtures/Miscellaneous/Plugins/MyBuildToolPluginDependencies/Sources/MySourceGenBuildTool/main.swift +++ b/Fixtures/Miscellaneous/Plugins/MyBuildToolPluginDependencies/Sources/MySourceGenBuildTool/main.swift @@ -13,6 +13,6 @@ let variableName = URL(fileURLWithPath: inputFile).deletingPathExtension().lastP let inputData = FileManager.default.contents(atPath: inputFile) ?? Data() let dataAsHex = inputData.map { String(format: "%02hhx", $0) }.joined() -let outputString = "public var \(variableName) = \(dataAsHex.quotedForSourceCode)\n" +let outputString = "public let \(variableName) = \(dataAsHex.quotedForSourceCode)\n" let outputData = outputString.data(using: .utf8) FileManager.default.createFile(atPath: outputFile, contents: outputData) diff --git a/Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Sources/MySourceGenBuildTool/main.swift b/Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Sources/MySourceGenBuildTool/main.swift index 94f7766628c..bedca50b459 100644 --- a/Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Sources/MySourceGenBuildTool/main.swift +++ b/Fixtures/Miscellaneous/Plugins/MySourceGenPlugin/Sources/MySourceGenBuildTool/main.swift @@ -13,6 +13,6 @@ let variableName = URL(fileURLWithPath: inputFile).deletingPathExtension().lastP let inputData = FileManager.default.contents(atPath: inputFile) ?? Data() let dataAsHex = inputData.map { String(format: "%02hhx", $0) }.joined() -let outputString = "public var \(variableName) = \(dataAsHex.quotedForSourceCode)\n" +let outputString = "public let \(variableName) = \(dataAsHex.quotedForSourceCode)\n" let outputData = outputString.data(using: .utf8) FileManager.default.createFile(atPath: outputFile, contents: outputData) diff --git a/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Package.swift b/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Package.swift index a16a412ff15..35a06c404bc 100644 --- a/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Package.swift +++ b/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.11 +// swift-tools-version: 6.0 import PackageDescription let package = Package( diff --git a/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Sources/MySourceGenBuildTool/main.swift b/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Sources/MySourceGenBuildTool/main.swift index 94f7766628c..bedca50b459 100644 --- a/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Sources/MySourceGenBuildTool/main.swift +++ b/Fixtures/Miscellaneous/Plugins/MySourceGenPluginUsingURLBasedAPI/Sources/MySourceGenBuildTool/main.swift @@ -13,6 +13,6 @@ let variableName = URL(fileURLWithPath: inputFile).deletingPathExtension().lastP let inputData = FileManager.default.contents(atPath: inputFile) ?? Data() let dataAsHex = inputData.map { String(format: "%02hhx", $0) }.joined() -let outputString = "public var \(variableName) = \(dataAsHex.quotedForSourceCode)\n" +let outputString = "public let \(variableName) = \(dataAsHex.quotedForSourceCode)\n" let outputData = outputString.data(using: .utf8) FileManager.default.createFile(atPath: outputFile, contents: outputData) diff --git a/Fixtures/Miscellaneous/Plugins/PluginWithInternalExecutable/Sources/PluginExecutable/main.swift b/Fixtures/Miscellaneous/Plugins/PluginWithInternalExecutable/Sources/PluginExecutable/main.swift index 034d9732cd0..6a8b8f23020 100644 --- a/Fixtures/Miscellaneous/Plugins/PluginWithInternalExecutable/Sources/PluginExecutable/main.swift +++ b/Fixtures/Miscellaneous/Plugins/PluginWithInternalExecutable/Sources/PluginExecutable/main.swift @@ -12,6 +12,6 @@ let variableName = URL(fileURLWithPath: inputFile).deletingPathExtension().lastP let inputData = FileManager.default.contents(atPath: inputFile) ?? Data() let dataAsHex = inputData.map { String(format: "%02hhx", $0) }.joined() -let outputString = "public var \(variableName) = \(dataAsHex.quotedForSourceCode)\n" +let outputString = "public let \(variableName) = \(dataAsHex.quotedForSourceCode)\n" let outputData = outputString.data(using: .utf8) FileManager.default.createFile(atPath: outputFile, contents: outputData) diff --git a/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Package.swift b/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Package.swift new file mode 100644 index 00000000000..d7749959468 --- /dev/null +++ b/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Package.swift @@ -0,0 +1,9 @@ +// swift-tools-version:5.10 +import PackageDescription + +let package = Package( + name: "IgnoresLinuxMain", + targets: [ + .testTarget(name: "IgnoresLinuxMainTests"), + ] +) diff --git a/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Tests/IgnoresLinuxMainTests/SomeTest.swift b/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Tests/IgnoresLinuxMainTests/SomeTest.swift new file mode 100644 index 00000000000..dec36ed294c --- /dev/null +++ b/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Tests/IgnoresLinuxMainTests/SomeTest.swift @@ -0,0 +1,5 @@ +import XCTest + +final class SomeTests: XCTestCase { + func testSomething() {} +} diff --git a/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Tests/LinuxMain.swift b/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Tests/LinuxMain.swift new file mode 100644 index 00000000000..f7b651fc7aa --- /dev/null +++ b/Fixtures/Miscellaneous/TestDiscovery/IgnoresLinuxMain/Tests/LinuxMain.swift @@ -0,0 +1 @@ +fatalError("Should not use the contents of LinuxMain.swift") diff --git a/Fixtures/SwiftSDKs/Package.swift b/Fixtures/SwiftSDKs/Package.swift new file mode 100644 index 00000000000..fc9bc8d91c8 --- /dev/null +++ b/Fixtures/SwiftSDKs/Package.swift @@ -0,0 +1 @@ +// This empty file tells test fixture logic to copy this directory's content to the test case temp directory. diff --git a/Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz b/Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz index 04f705d0b65..d02b411724d 100644 Binary files a/Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz and b/Fixtures/SwiftSDKs/test-sdk.artifactbundle.tar.gz differ diff --git a/Fixtures/SwiftSDKs/test-sdk.artifactbundle.zip b/Fixtures/SwiftSDKs/test-sdk.artifactbundle.zip index e6c3a41a9de..6d38f278a20 100644 Binary files a/Fixtures/SwiftSDKs/test-sdk.artifactbundle.zip and b/Fixtures/SwiftSDKs/test-sdk.artifactbundle.zip differ diff --git a/IntegrationTests/Tests/IntegrationTests/BasicTests.swift b/IntegrationTests/Tests/IntegrationTests/BasicTests.swift index 8cd4ddab56f..032d14fc0b3 100644 --- a/IntegrationTests/Tests/IntegrationTests/BasicTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/BasicTests.swift @@ -19,6 +19,7 @@ final class BasicTests: XCTestCase { func testExamplePackageDealer() throws { try XCTSkipIf(isSelfHosted, "These packages don't use the latest runtime library, which doesn't work with self-hosted builds.") + try skipUnlessAtLeastSwift6() try withTemporaryDirectory { tempDir in let packagePath = tempDir.appending(component: "dealer") @@ -93,9 +94,7 @@ final class BasicTests: XCTestCase { } func testSwiftPackageInitExec() throws { - #if swift(<5.5) - try XCTSkipIf(true, "skipping because host compiler doesn't support '-entry-point-function-name'") - #endif + try skipUnlessAtLeastSwift6() try withTemporaryDirectory { tempDir in // Create a new package with an executable target. @@ -122,9 +121,7 @@ final class BasicTests: XCTestCase { } func testSwiftPackageInitExecTests() throws { - #if swift(<5.5) - try XCTSkipIf(true, "skipping because host compiler doesn't support '-entry-point-function-name'") - #endif + try skipUnlessAtLeastSwift6() try XCTSkip("FIXME: swift-test invocations are timing out in Xcode and self-hosted CI") @@ -149,6 +146,8 @@ final class BasicTests: XCTestCase { } func testSwiftPackageInitLib() throws { + try skipUnlessAtLeastSwift6() + try withTemporaryDirectory { tempDir in // Create a new package with an executable target. let packagePath = tempDir.appending(component: "Project") @@ -167,6 +166,8 @@ final class BasicTests: XCTestCase { } func testSwiftPackageLibsTests() throws { + try skipUnlessAtLeastSwift6() + try XCTSkip("FIXME: swift-test invocations are timing out in Xcode and self-hosted CI") try withTemporaryDirectory { tempDir in @@ -225,9 +226,7 @@ final class BasicTests: XCTestCase { } func testSwiftRun() throws { - #if swift(<5.5) - try XCTSkipIf(true, "skipping because host compiler doesn't support '-entry-point-function-name'") - #endif + try skipUnlessAtLeastSwift6() try withTemporaryDirectory { tempDir in let packagePath = tempDir.appending(component: "secho") @@ -256,6 +255,8 @@ final class BasicTests: XCTestCase { } func testSwiftTest() throws { + try skipUnlessAtLeastSwift6() + try XCTSkip("FIXME: swift-test invocations are timing out in Xcode and self-hosted CI") try withTemporaryDirectory { tempDir in @@ -377,3 +378,9 @@ private extension Character { } } } + +private func skipUnlessAtLeastSwift6() throws { + #if compiler(<6.0) + try XCTSkipIf(true, "Skipping because test requires at least Swift 6.0") + #endif +} diff --git a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift index d9fcdccc1bf..fc394df0421 100644 --- a/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift +++ b/IntegrationTests/Tests/IntegrationTests/SwiftPMTests.swift @@ -53,6 +53,9 @@ final class SwiftPMTests: XCTestCase { #if !os(macOS) try XCTSkip("Test requires macOS") #endif + #if swift(<6.0) + try XCTSkipIf(true, "Skipping because test requires at least Swift 6.0") + #endif try withTemporaryDirectory { tmpDir in let packagePath = tmpDir.appending(component: "foo") diff --git a/Package.swift b/Package.swift index 4cb31ec61bf..f65164ea0b2 100644 --- a/Package.swift +++ b/Package.swift @@ -1,10 +1,10 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.9 //===----------------------------------------------------------------------===// // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -12,26 +12,34 @@ // //===----------------------------------------------------------------------===// -import PackageDescription import class Foundation.ProcessInfo +import PackageDescription // When building the toolchain on the CI for ELF platforms, remove the CI's // stdlib absolute runpath and add ELF's $ORIGIN relative paths before installing. -let swiftpmLinkSettings : [LinkerSetting] -let packageLibraryLinkSettings : [LinkerSetting] +let swiftpmLinkSettings: [LinkerSetting] +let packageLibraryLinkSettings: [LinkerSetting] if let resourceDirPath = ProcessInfo.processInfo.environment["SWIFTCI_INSTALL_RPATH_OS"] { - swiftpmLinkSettings = [ .unsafeFlags(["-no-toolchain-stdlib-rpath", "-Xlinker", "-rpath", "-Xlinker", "$ORIGIN/../lib/swift/\(resourceDirPath)"]) ] - packageLibraryLinkSettings = [ .unsafeFlags(["-no-toolchain-stdlib-rpath", "-Xlinker", "-rpath", "-Xlinker", "$ORIGIN/../../\(resourceDirPath)"]) ] + swiftpmLinkSettings = [.unsafeFlags([ + "-no-toolchain-stdlib-rpath", + "-Xlinker", "-rpath", + "-Xlinker", "$ORIGIN/../lib/swift/\(resourceDirPath)", + ])] + packageLibraryLinkSettings = [.unsafeFlags([ + "-no-toolchain-stdlib-rpath", + "-Xlinker", "-rpath", + "-Xlinker", "$ORIGIN/../../\(resourceDirPath)", + ])] } else { - swiftpmLinkSettings = [] - packageLibraryLinkSettings = [] + swiftpmLinkSettings = [] + packageLibraryLinkSettings = [] } /** SwiftPMDataModel is the subset of SwiftPM product that includes just its data model. -This allows some clients (such as IDEs) that use SwiftPM's data model but not its build system -to not have to depend on SwiftDriver, SwiftLLBuild, etc. We should probably have better names here, -though that could break some clients. -*/ + This allows some clients (such as IDEs) that use SwiftPM's data model but not its build system + to not have to depend on SwiftDriver, SwiftLLBuild, etc. We should probably have better names here, + though that could break some clients. + */ let swiftPMDataModelProduct = ( name: "SwiftPMDataModel", targets: [ @@ -41,6 +49,7 @@ let swiftPMDataModelProduct = ( "PackageLoading", "PackageMetadata", "PackageModel", + "PackageModelSyntax", "SourceControl", "Workspace", ] @@ -51,7 +60,7 @@ let swiftPMDataModelProduct = ( command line tools, while `libSwiftPMDataModel` includes only the data model. NOTE: This API is *unstable* and may change at any time. -*/ + */ let swiftPMProduct = ( name: "SwiftPM", targets: swiftPMDataModelProduct.targets + [ @@ -69,19 +78,31 @@ let systemSQLitePkgConfig: String? = "sqlite3" #endif /** An array of products which have two versions listed: one dynamically linked, the other with the -automatic linking type with `-auto` suffix appended to product's name. -*/ + automatic linking type with `-auto` suffix appended to product's name. + */ let autoProducts = [swiftPMProduct, swiftPMDataModelProduct] +let packageModelResourcesSettings: [SwiftSetting] +let packageModelResources: [Resource] +if ProcessInfo.processInfo.environment["SWIFTPM_USE_LIBRARIES_METADATA"] == nil { + packageModelResources = [] + packageModelResourcesSettings = [.define("SKIP_RESOURCE_SUPPORT")] +} else { + packageModelResources = [ + .copy("InstalledLibrariesSupport/provided-libraries.json"), + ] + packageModelResourcesSettings = [] +} + let package = Package( name: "SwiftPM", platforms: [ .macOS(.v13), - .iOS(.v16) + .iOS(.v16), ], products: - autoProducts.flatMap { - [ + autoProducts.flatMap { + [ .library( name: $0.name, type: .dynamic, @@ -90,9 +111,9 @@ let package = Package( .library( name: "\($0.name)-auto", targets: $0.targets - ) - ] - } + [ + ), + ] + } + [ .library( name: "XCBuildSupport", targets: ["XCBuildSupport"] @@ -153,9 +174,10 @@ let package = Package( name: "SourceKitLSPAPI", dependencies: [ "Build", - "SPMBuildCore" + "SPMBuildCore", ], - exclude: ["CMakeLists.txt"] + exclude: ["CMakeLists.txt"], + swiftSettings: [.enableExperimentalFeature("AccessLevelOnImport")] ), // MARK: SwiftPM specific support libraries @@ -171,7 +193,10 @@ let package = Package( .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"), .product(name: "SystemPackage", package: "swift-system"), ], - exclude: ["CMakeLists.txt", "Vendor/README.md"] + exclude: ["CMakeLists.txt", "Vendor/README.md"], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + ] ), .target( @@ -199,7 +224,7 @@ let package = Package( name: "SourceControl", dependencies: [ "Basics", - "PackageModel" + "PackageModel", ], exclude: ["CMakeLists.txt"] ), @@ -217,7 +242,26 @@ let package = Package( /** Primitive Package model objects */ name: "PackageModel", dependencies: ["Basics"], - exclude: ["CMakeLists.txt", "README.md"] + exclude: ["CMakeLists.txt", "README.md"], + resources: packageModelResources, + swiftSettings: packageModelResourcesSettings + ), + + .target( + /** Primary Package model objects relationship to SwiftSyntax */ + name: "PackageModelSyntax", + dependencies: [ + "Basics", + "PackageLoading", + "PackageModel", + .product(name: "SwiftBasicFormat", package: "swift-syntax"), + .product(name: "SwiftDiagnostics", package: "swift-syntax"), + .product(name: "SwiftIDEUtils", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + ], + exclude: ["CMakeLists.txt"] ), .target( @@ -239,7 +283,7 @@ let package = Package( dependencies: [ "Basics", "PackageLoading", - "PackageModel" + "PackageModel", ], exclude: ["CMakeLists.txt", "README.md"] ), @@ -251,7 +295,7 @@ let package = Package( name: "PackageCollectionsModel", dependencies: [], exclude: [ - "Formats/v1.md" + "Formats/v1.md", ] ), @@ -285,7 +329,7 @@ let package = Package( ], exclude: ["CMakeLists.txt"] ), - + .target( name: "PackageSigning", dependencies: [ @@ -304,7 +348,7 @@ let package = Package( name: "SPMBuildCore", dependencies: [ "Basics", - "PackageGraph" + "PackageGraph", ], exclude: ["CMakeLists.txt"] ), @@ -334,8 +378,8 @@ let package = Package( .target( /** Support for building using Xcode's build system */ name: "XCBuildSupport", - dependencies: ["SPMBuildCore", "PackageGraph"], - exclude: ["CMakeLists.txt", "CODEOWNERS"] + dependencies: ["DriverSupport", "SPMBuildCore", "PackageGraph"], + exclude: ["CMakeLists.txt"] ), .target( /** High level functionality */ @@ -388,10 +432,12 @@ let package = Package( dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "OrderedCollections", package: "swift-collections"), + .product(name: "SwiftIDEUtils", package: "swift-syntax"), "Basics", "Build", "CoreCommands", "PackageGraph", + "PackageModelSyntax", "SourceControl", "Workspace", "XCBuildSupport", @@ -401,7 +447,7 @@ let package = Package( .target( /** Interacts with Swift SDKs used for cross-compilation */ - name: "SwiftSDKTool", + name: "SwiftSDKCommand", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), "Basics", @@ -414,7 +460,7 @@ let package = Package( .target( /** Interacts with package collections */ - name: "PackageCollectionsTool", + name: "PackageCollectionsCommand", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), "Basics", @@ -427,7 +473,7 @@ let package = Package( .target( /** Interact with package registry */ - name: "PackageRegistryTool", + name: "PackageRegistryCommand", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), "Basics", @@ -444,6 +490,17 @@ let package = Package( ] ), + .target( + name: "QueryEngine", + dependencies: [ + "Basics", + .product(name: "Crypto", package: "swift-crypto"), + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency=complete"), + ] + ), + .executableTarget( /** The main executable provided by SwiftPM */ name: "swift-package", @@ -472,8 +529,14 @@ let package = Package( ), .executableTarget( /** Interacts with Swift SDKs used for cross-compilation */ + name: "swift-sdk", + dependencies: ["Commands", "SwiftSDKCommand"], + exclude: ["CMakeLists.txt"] + ), + .executableTarget( + /** Deprecated command superseded by `swift-sdk` */ name: "swift-experimental-sdk", - dependencies: ["Commands", "SwiftSDKTool"], + dependencies: ["Commands", "SwiftSDKCommand"], exclude: ["CMakeLists.txt"] ), .executableTarget( @@ -491,24 +554,24 @@ let package = Package( .executableTarget( /** Interacts with package collections */ name: "swift-package-collection", - dependencies: ["Commands", "PackageCollectionsTool"] + dependencies: ["Commands", "PackageCollectionsCommand"] ), .executableTarget( - /** Multi-tool entry point for SwiftPM. */ + /** Multi-command entry point for SwiftPM. */ name: "swift-package-manager", dependencies: [ "Basics", "Commands", - "SwiftSDKTool", - "PackageCollectionsTool", - "PackageRegistryTool" + "SwiftSDKCommand", + "PackageCollectionsCommand", + "PackageRegistryCommand", ], linkerSettings: swiftpmLinkSettings ), .executableTarget( /** Interact with package registry */ name: "swift-package-registry", - dependencies: ["Commands", "PackageRegistryTool"] + dependencies: ["Commands", "PackageRegistryCommand"] ), // MARK: Support for Swift macros, should eventually move to a plugin-based solution @@ -546,7 +609,8 @@ let package = Package( .target( /** Test for thread-santizer. */ name: "tsan_utils", - dependencies: []), + dependencies: [] + ), // MARK: SwiftPM tests @@ -593,14 +657,18 @@ let package = Package( dependencies: ["PackageLoading", "SPMTestSupport"], exclude: ["Inputs", "pkgconfigInputs"] ), - .testTarget( - name: "PackageLoadingPerformanceTests", - dependencies: ["PackageLoading", "SPMTestSupport"] - ), .testTarget( name: "PackageModelTests", dependencies: ["PackageModel", "SPMTestSupport"] ), + .testTarget( + name: "PackageModelSyntaxTests", + dependencies: [ + "PackageModelSyntax", + "SPMTestSupport", + .product(name: "SwiftIDEUtils", package: "swift-syntax"), + ] + ), .testTarget( name: "PackageGraphTests", dependencies: ["PackageGraph", "SPMTestSupport"] @@ -643,6 +711,10 @@ let package = Package( name: "PackageSigningTests", dependencies: ["SPMTestSupport", "PackageSigning"] ), + .testTarget( + name: "QueryEngineTests", + dependencies: ["QueryEngine", "SPMTestSupport"] + ), .testTarget( name: "SourceControlTests", dependencies: ["SourceControl", "SPMTestSupport"], @@ -653,12 +725,6 @@ let package = Package( dependencies: ["XCBuildSupport", "SPMTestSupport"], exclude: ["Inputs/Foo.pc"] ), - // Examples (These are built to ensure they stay up to date with the API.) - .executableTarget( - name: "package-info", - dependencies: ["Workspace"], - path: "Examples/package-info/Sources/package-info" - ) ], swiftLanguageVersions: [.v5] ) @@ -670,25 +736,22 @@ package.targets.append(contentsOf: [ .testTarget( name: "FunctionalPerformanceTests", dependencies: [ - "swift-build", - "swift-package", - "swift-test", - "SPMTestSupport" + "swift-package-manager", + "SPMTestSupport", ] ), ]) -// rdar://101868275 "error: cannot find 'XCTAssertEqual' in scope" can affect almost any functional test, so we flat out disable them all until we know what is going on +// rdar://101868275 "error: cannot find 'XCTAssertEqual' in scope" can affect almost any functional test, so we flat out +// disable them all until we know what is going on if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] == nil { package.targets.append(contentsOf: [ .testTarget( name: "FunctionalTests", dependencies: [ - "swift-build", - "swift-package", - "swift-test", + "swift-package-manager", "PackageModel", - "SPMTestSupport" + "SPMTestSupport", ] ), @@ -702,15 +765,12 @@ if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] == .testTarget( name: "CommandsTests", dependencies: [ - "swift-build", - "swift-package", - "swift-test", - "swift-run", + "swift-package-manager", "Basics", "Build", "Commands", "PackageModel", - "PackageRegistryTool", + "PackageRegistryCommand", "SourceControl", "SPMTestSupport", "Workspace", @@ -759,6 +819,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.2")), .package(url: "https://github.com/apple/swift-driver.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/apple/swift-crypto.git", .upToNextMinor(from: "3.0.0")), + .package(url: "https://github.com/apple/swift-syntax.git", branch: relatedDependenciesBranch), .package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "1.1.1")), .package(url: "https://github.com/apple/swift-collections.git", .upToNextMinor(from: "1.0.1")), .package(url: "https://github.com/apple/swift-certificates.git", .upToNextMinor(from: "1.0.1")), @@ -769,6 +830,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(path: "../swift-argument-parser"), .package(path: "../swift-driver"), .package(path: "../swift-crypto"), + .package(path: "../swift-syntax"), .package(path: "../swift-system"), .package(path: "../swift-collections"), .package(path: "../swift-certificates"), diff --git a/Sources/Basics/Archiver/Archiver.swift b/Sources/Basics/Archiver/Archiver.swift index b76130b1500..e9d416ef21d 100644 --- a/Sources/Basics/Archiver/Archiver.swift +++ b/Sources/Basics/Archiver/Archiver.swift @@ -13,7 +13,7 @@ import _Concurrency /// The `Archiver` protocol abstracts away the different operations surrounding archives. -public protocol Archiver { +public protocol Archiver: Sendable { /// A set of extensions the current archiver supports. var supportedExtensions: Set { get } @@ -27,7 +27,7 @@ public protocol Archiver { func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) /// Asynchronously compress the contents of a directory to a destination archive. @@ -40,7 +40,7 @@ public protocol Archiver { func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) /// Asynchronously validates if a file is an archive. @@ -51,7 +51,7 @@ public protocol Archiver { @available(*, noasync, message: "Use the async alternative") func validate( path: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) } @@ -65,8 +65,8 @@ extension Archiver { from archivePath: AbsolutePath, to destinationPath: AbsolutePath ) async throws { - try await withCheckedThrowingContinuation { - self.extract(from: archivePath, to: destinationPath, completion: $0.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + self.extract(from: archivePath, to: destinationPath, completion: { continuation.resume(with: $0) }) } } @@ -79,8 +79,8 @@ extension Archiver { directory: AbsolutePath, to destinationPath: AbsolutePath ) async throws { - try await withCheckedThrowingContinuation { - self.compress(directory: directory, to: destinationPath, completion: $0.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + self.compress(directory: directory, to: destinationPath, completion: { continuation.resume(with: $0) }) } } @@ -91,8 +91,8 @@ extension Archiver { public func validate( path: AbsolutePath ) async throws -> Bool { - try await withCheckedThrowingContinuation { - self.validate(path: path, completion: $0.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + self.validate(path: path, completion: { continuation.resume(with: $0) }) } } } diff --git a/Sources/Basics/Archiver/TarArchiver.swift b/Sources/Basics/Archiver/TarArchiver.swift index d1da7ed0c15..99948bf82a9 100644 --- a/Sources/Basics/Archiver/TarArchiver.swift +++ b/Sources/Basics/Archiver/TarArchiver.swift @@ -47,7 +47,7 @@ public struct TarArchiver: Archiver { public func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.exists(archivePath) else { @@ -84,7 +84,7 @@ public struct TarArchiver: Archiver { public func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.isDirectory(directory) else { @@ -115,7 +115,7 @@ public struct TarArchiver: Archiver { } } - public func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { + public func validate(path: AbsolutePath, completion: @escaping @Sendable (Result) -> Void) { do { guard self.fileSystem.exists(path) else { throw FileSystemError(.noEntry, path.underlying) diff --git a/Sources/Basics/Archiver/UniversalArchiver.swift b/Sources/Basics/Archiver/UniversalArchiver.swift index cbd5d5d742f..d6ed496df97 100644 --- a/Sources/Basics/Archiver/UniversalArchiver.swift +++ b/Sources/Basics/Archiver/UniversalArchiver.swift @@ -73,7 +73,7 @@ public struct UniversalArchiver: Archiver { public func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let archiver = try archiver(for: archivePath) @@ -86,7 +86,7 @@ public struct UniversalArchiver: Archiver { public func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let archiver = try archiver(for: destinationPath) @@ -98,7 +98,7 @@ public struct UniversalArchiver: Archiver { public func validate( path: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let archiver = try archiver(for: path) diff --git a/Sources/Basics/Archiver/ZipArchiver.swift b/Sources/Basics/Archiver/ZipArchiver.swift index 9aab24e13ce..092752d8382 100644 --- a/Sources/Basics/Archiver/ZipArchiver.swift +++ b/Sources/Basics/Archiver/ZipArchiver.swift @@ -37,7 +37,7 @@ public struct ZipArchiver: Archiver, Cancellable { public func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.exists(archivePath) else { @@ -77,7 +77,7 @@ public struct ZipArchiver: Archiver, Cancellable { public func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { guard self.fileSystem.isDirectory(directory) else { @@ -125,7 +125,7 @@ public struct ZipArchiver: Archiver, Cancellable { } } - public func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { + public func validate(path: AbsolutePath, completion: @escaping @Sendable (Result) -> Void) { do { guard self.fileSystem.exists(path) else { throw FileSystemError(.noEntry, path.underlying) diff --git a/Sources/Basics/AuthorizationProvider.swift b/Sources/Basics/AuthorizationProvider.swift index 11e80cb79ed..89326664a3c 100644 --- a/Sources/Basics/AuthorizationProvider.swift +++ b/Sources/Basics/AuthorizationProvider.swift @@ -17,8 +17,7 @@ import struct Foundation.URL import Security #endif -public protocol AuthorizationProvider { - @Sendable +public protocol AuthorizationProvider: Sendable { func authentication(for url: URL) -> (user: String, password: String)? } @@ -80,7 +79,7 @@ extension AuthorizationProvider { // MARK: - netrc -public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { +public final class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { // marked internal for testing internal let path: AbsolutePath private let fileSystem: FileSystem @@ -202,7 +201,7 @@ public class NetrcAuthorizationProvider: AuthorizationProvider, AuthorizationWri // MARK: - Keychain #if canImport(Security) -public class KeychainAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { +public final class KeychainAuthorizationProvider: AuthorizationProvider, AuthorizationWriter { private let observabilityScope: ObservabilityScope private let cache = ThreadSafeKeyValueStore() diff --git a/Sources/Basics/CMakeLists.txt b/Sources/Basics/CMakeLists.txt index 777de1a98ed..343bbedbc56 100644 --- a/Sources/Basics/CMakeLists.txt +++ b/Sources/Basics/CMakeLists.txt @@ -34,6 +34,10 @@ add_library(Basics FileSystem/TemporaryFile.swift FileSystem/TSCAdapters.swift FileSystem/VFSOverlay.swift + Graph/AdjacencyMatrix.swift + Graph/DirectedGraph.swift + Graph/GraphAlgorithms.swift + Graph/UndirectedGraph.swift SourceControlURL.swift HTTPClient/HTTPClient.swift HTTPClient/HTTPClientConfiguration.swift @@ -51,7 +55,11 @@ add_library(Basics Netrc.swift Observability.swift OSSignpost.swift - ProgressAnimation.swift + ProgressAnimation/NinjaProgressAnimation.swift + ProgressAnimation/PercentProgressAnimation.swift + ProgressAnimation/ProgressAnimationProtocol.swift + ProgressAnimation/SingleLinePercentProgressAnimation.swift + ProgressAnimation/ThrottledProgressAnimation.swift SQLite.swift Sandbox.swift SendableTimeInterval.swift @@ -72,6 +80,7 @@ target_link_libraries(Basics PUBLIC target_link_libraries(Basics PRIVATE SPMSQLite3 TSCclibc) + # NOTE(compnerd) workaround for CMake not setting up include flags yet set_target_properties(Basics PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) diff --git a/Sources/Basics/Cancellator.swift b/Sources/Basics/Cancellator.swift index d2c808e88ef..e1e8f8d0434 100644 --- a/Sources/Basics/Cancellator.swift +++ b/Sources/Basics/Cancellator.swift @@ -18,9 +18,9 @@ import class TSCBasic.Thread import WinSDK #endif -public typealias CancellationHandler = (DispatchTime) throws -> Void +public typealias CancellationHandler = @Sendable (DispatchTime) throws -> Void -public final class Cancellator: Cancellable { +public final class Cancellator: Cancellable, Sendable { public typealias RegistrationKey = String private let observabilityScope: ObservabilityScope? @@ -119,7 +119,7 @@ public final class Cancellator: Cancellable { } @discardableResult - public func register(name: String, handler: @escaping () throws -> Void) -> RegistrationKey? { + public func register(name: String, handler: @escaping @Sendable () throws -> Void) -> RegistrationKey? { self.register(name: name, handler: { _ in try handler() }) } diff --git a/Sources/Basics/Collections/IdentifiableSet.swift b/Sources/Basics/Collections/IdentifiableSet.swift index b3bfec3071f..59bda6bdd85 100644 --- a/Sources/Basics/Collections/IdentifiableSet.swift +++ b/Sources/Basics/Collections/IdentifiableSet.swift @@ -45,13 +45,22 @@ public struct IdentifiableSet: Collection { } public subscript(id: Element.ID) -> Element? { - self.storage[id] + get { + self.storage[id] + } + set { + self.storage[id] = newValue + } } public func index(after i: Index) -> Index { Index(storageIndex: self.storage.index(after: i.storageIndex)) } + public mutating func insert(_ element: Element) { + self.storage[element.id] = element + } + public func union(_ otherSequence: some Sequence) -> Self { var result = self for element in otherSequence { diff --git a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift index cc60514fd9a..7927d2e2dec 100644 --- a/Sources/Basics/Concurrency/ConcurrencyHelpers.swift +++ b/Sources/Basics/Concurrency/ConcurrencyHelpers.swift @@ -20,7 +20,7 @@ import func TSCBasic.tsc_await public enum Concurrency { public static var maxOperations: Int { - ProcessEnv.vars["SWIFTPM_MAX_CONCURRENT_OPERATIONS"].flatMap(Int.init) ?? ProcessInfo.processInfo + ProcessEnv.block["SWIFTPM_MAX_CONCURRENT_OPERATIONS"].flatMap(Int.init) ?? ProcessInfo.processInfo .activeProcessorCount } } @@ -49,7 +49,7 @@ extension DispatchQueue { /// Bridges between potentially blocking methods that take a result completion closure and async/await public func safe_async( - _ body: @Sendable @escaping (@Sendable @escaping (Result) -> Void) -> Void + _ body: @escaping @Sendable (@escaping @Sendable (Result) -> Void) -> Void ) async throws -> T { try await withCheckedThrowingContinuation { continuation in // It is possible that body make block indefinitely on a lock, semaphore, @@ -64,7 +64,7 @@ public func safe_async( } /// Bridges between potentially blocking methods that take a result completion closure and async/await -public func safe_async(_ body: @escaping (@escaping (Result) -> Void) -> Void) async -> T { +public func safe_async(_ body: @escaping @Sendable (@escaping (Result) -> Void) -> Void) async -> T { await withCheckedContinuation { continuation in // It is possible that body make block indefinitely on a lock, semaphore, // or similar then synchronously call the completion handler. For full safety diff --git a/Sources/Basics/Concurrency/ThrowingDefer.swift b/Sources/Basics/Concurrency/ThrowingDefer.swift new file mode 100644 index 00000000000..fdf821f7c96 --- /dev/null +++ b/Sources/Basics/Concurrency/ThrowingDefer.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +/// Runs a cleanup closure (`deferred`) after a given `work` closure, +/// making sure `deferred` is run also when `work` throws an error. +/// - Parameters: +/// - work: The work that should be performed. Will always be executed. +/// - deferred: The cleanup that needs to be done in any case. +/// - Throws: Any error thrown by `deferred` or `work` (in that order). +/// - Returns: The result of `work`. +/// - Note: If `work` **and** `deferred` throw an error, +/// the one thrown by `deferred` is thrown from this function. +/// - SeeAlso: ``withAsyncThrowing(do:defer:)`` +public func withThrowing( + do work: () throws -> T, + defer deferred: () throws -> Void +) throws -> T { + do { + let result = try work() + try deferred() + return result + } catch { + try deferred() + throw error + } +} + +/// Runs an async cleanup closure (`deferred`) after a given async `work` closure, +/// making sure `deferred` is run also when `work` throws an error. +/// - Parameters: +/// - work: The work that should be performed. Will always be executed. +/// - deferred: The cleanup that needs to be done in any case. +/// - Throws: Any error thrown by `deferred` or `work` (in that order). +/// - Returns: The result of `work`. +/// - Note: If `work` **and** `deferred` throw an error, +/// the one thrown by `deferred` is thrown from this function. +/// - SeeAlso: ``withThrowing(do:defer:)`` +public func withAsyncThrowing( + do work: @Sendable () async throws -> T, + defer deferred: @Sendable () async throws -> Void +) async throws -> T { + do { + let result = try await work() + try await deferred() + return result + } catch { + try await deferred() + throw error + } +} diff --git a/Sources/Basics/Concurrency/TokenBucket.swift b/Sources/Basics/Concurrency/TokenBucket.swift index e9cf6ff4251..010da630a35 100644 --- a/Sources/Basics/Concurrency/TokenBucket.swift +++ b/Sources/Basics/Concurrency/TokenBucket.swift @@ -30,7 +30,7 @@ public actor TokenBucket { /// invocations of `withToken` will suspend until a "free" token is available. /// - Parameter body: The closure to invoke when a token is available. /// - Returns: Resulting value returned by `body`. - public func withToken( + public func withToken( _ body: @Sendable () async throws -> ReturnType ) async rethrows -> ReturnType { await self.getToken() diff --git a/Sources/Basics/Errors.swift b/Sources/Basics/Errors.swift index c0900f565d7..f075978da86 100644 --- a/Sources/Basics/Errors.swift +++ b/Sources/Basics/Errors.swift @@ -21,8 +21,7 @@ public struct InternalError: Error { private let description: String public init(_ description: String) { assertionFailure(description) - self - .description = + self.description = "Internal error. Please file a bug at https://github.com/apple/swift-package-manager/issues with this info. \(description)" } } diff --git a/Sources/Basics/FileSystem/FileSystem+Extensions.swift b/Sources/Basics/FileSystem/FileSystem+Extensions.swift index 9b62e94ae79..d16637d5872 100644 --- a/Sources/Basics/FileSystem/FileSystem+Extensions.swift +++ b/Sources/Basics/FileSystem/FileSystem+Extensions.swift @@ -21,11 +21,12 @@ import class TSCBasic.FileLock import enum TSCBasic.FileMode import protocol TSCBasic.FileSystem import enum TSCBasic.FileSystemAttribute +import class TSCBasic.InMemoryFileSystem import var TSCBasic.localFileSystem import protocol TSCBasic.WritableByteStream public typealias FileSystem = TSCBasic.FileSystem -public var localFileSystem = TSCBasic.localFileSystem +public let localFileSystem = TSCBasic.localFileSystem // MARK: - Custom path @@ -211,9 +212,14 @@ extension FileSystem { extension FileSystem { /// SwiftPM directory under user's home directory (~/.swiftpm) + /// or under $XDG_CONFIG_HOME/swiftpm if the environmental variable is defined public var dotSwiftPM: AbsolutePath { get throws { - try self.homeDirectory.appending(".swiftpm") + if let configurationDirectory = EnvironmentVariables.process()["XDG_CONFIG_HOME"] { + return try AbsolutePath(validating: configurationDirectory).appending("swiftpm") + } else { + return try self.homeDirectory.appending(".swiftpm") + } } } @@ -627,3 +633,48 @@ extension FileLock { return try Self.prepareLock(fileToLock: fileToLock.underlying, at: lockFilesDirectory?.underlying) } } + +/// Convenience initializers for testing purposes. +extension InMemoryFileSystem { + /// Create a new file system with the given files, provided as a map from + /// file path to contents. + public convenience init(files: [String: ByteString]) { + self.init() + + for (path, contents) in files { + let path = try! AbsolutePath(validating: path) + try! createDirectory(path.parentDirectory, recursive: true) + try! writeFileContents(path, bytes: contents) + } + } + + /// Create a new file system with an empty file at each provided path. + public convenience init(emptyFiles files: String...) { + self.init(emptyFiles: files) + } + + /// Create a new file system with an empty file at each provided path. + public convenience init(emptyFiles files: [String]) { + self.init() + self.createEmptyFiles(at: .root, files: files) + } +} + +extension FileSystem { + public func createEmptyFiles(at root: AbsolutePath, files: String...) { + self.createEmptyFiles(at: root, files: files) + } + + public func createEmptyFiles(at root: AbsolutePath, files: [String]) { + do { + try createDirectory(root, recursive: true) + for path in files { + let path = try AbsolutePath(validating: String(path.dropFirst()), relativeTo: root) + try createDirectory(path.parentDirectory, recursive: true) + try writeFileContents(path, bytes: "") + } + } catch { + fatalError("Failed to create empty files: \(error)") + } + } +} diff --git a/Sources/Basics/FileSystem/TemporaryFile.swift b/Sources/Basics/FileSystem/TemporaryFile.swift index a8e31126448..a9f253b6f08 100644 --- a/Sources/Basics/FileSystem/TemporaryFile.swift +++ b/Sources/Basics/FileSystem/TemporaryFile.swift @@ -34,7 +34,7 @@ public func withTemporaryDirectory( fileSystem: FileSystem = localFileSystem, dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", - _ body: @Sendable @escaping (AbsolutePath, @escaping (AbsolutePath) -> Void) async throws -> Result + _ body: @escaping @Sendable (AbsolutePath, @escaping (AbsolutePath) -> Void) async throws -> Result ) throws -> Task { let temporaryDirectory = try createTemporaryDirectory(fileSystem: fileSystem, dir: dir, prefix: prefix) @@ -72,7 +72,7 @@ public func withTemporaryDirectory( dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", removeTreeOnDeinit: Bool = false, - _ body: @escaping (AbsolutePath) async throws -> Result + _ body: @escaping @Sendable (AbsolutePath) async throws -> Result ) throws -> Task { try withTemporaryDirectory(fileSystem: fileSystem, dir: dir, prefix: prefix) { path, cleanup in defer { if removeTreeOnDeinit { cleanup(path) } } diff --git a/Sources/Basics/Graph/AdjacencyMatrix.swift b/Sources/Basics/Graph/AdjacencyMatrix.swift new file mode 100644 index 00000000000..9326d499a7c --- /dev/null +++ b/Sources/Basics/Graph/AdjacencyMatrix.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// A matrix storing bits of `true`/`false` state for a given combination of row and column indices. Used as +/// a square matrix indicating edges in graphs, where rows and columns are indices in a storage of graph's nodes. +/// +/// For example, in a graph that contains 3 nodes `matrix[row: 1, column: 2]` evaluating to `true` means an edge +/// between nodes with indices `1` and `2` exists. `matrix[row: 1, column: 2]` evaluating to `false` means that no +/// edge exists. +/// +/// See https://en.wikipedia.org/wiki/Adjacency_matrix for more details. +struct AdjacencyMatrix { + let columns: Int + let rows: Int + private var bytes: [UInt8] + + /// Allocates a new bit matrix with a given size. + /// - Parameters: + /// - rows: Number of rows in the matrix. + /// - columns: Number of columns in the matrix. + init(rows: Int, columns: Int) { + self.columns = columns + self.rows = rows + + let (quotient, remainder) = (rows * columns).quotientAndRemainder(dividingBy: 8) + self.bytes = .init(repeating: 0, count: quotient + (remainder > 0 ? 1 : 0)) + } + + var bitCount: Int { + bytes.count * 8 + } + + private func calculateOffsets(row: Int, column: Int) -> (byteOffset: Int, bitOffsetInByte: Int) { + let totalBitOffset = row * columns + column + return (byteOffset: totalBitOffset / 8, bitOffsetInByte: totalBitOffset % 8) + } + + subscript(row: Int, column: Int) -> Bool { + get { + let (byteOffset, bitOffsetInByte) = calculateOffsets(row: row, column: column) + + let result = (self.bytes[byteOffset] >> bitOffsetInByte) & 1 + return result == 1 + } + + set { + let (byteOffset, bitOffsetInByte) = calculateOffsets(row: row, column: column) + + self.bytes[byteOffset] |= 1 << bitOffsetInByte + } + } +} diff --git a/Sources/Basics/Graph/DirectedGraph.swift b/Sources/Basics/Graph/DirectedGraph.swift new file mode 100644 index 00000000000..1d5bb2156bc --- /dev/null +++ b/Sources/Basics/Graph/DirectedGraph.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct DequeModule.Deque + +/// Directed graph that stores edges in [adjacency lists](https://en.wikipedia.org/wiki/Adjacency_list). +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +public struct DirectedGraph { + public init(nodes: [Node]) { + self.nodes = nodes + self.edges = .init(repeating: [], count: nodes.count) + } + + public private(set) var nodes: [Node] + private var edges: [[Int]] + + public mutating func addEdge(source: Int, destination: Int) { + self.edges[source].append(destination) + } + + /// Checks whether a path via previously created edges between two given nodes exists. + /// - Parameters: + /// - source: `Index` of a node to start traversing edges from. + /// - destination: `Index` of a node to which a path could exist via edges from `source`. + /// - Returns: `true` if a path from `source` to `destination` exists, `false` otherwise. + @_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) + public func areNodesConnected(source: Int, destination: Int) -> Bool { + var todo = Deque([source]) + var done = Set() + + while !todo.isEmpty { + let nodeIndex = todo.removeFirst() + + for reachableIndex in self.edges[nodeIndex] { + if reachableIndex == destination { + return true + } else if !done.contains(reachableIndex) { + todo.append(reachableIndex) + } + } + + done.insert(nodeIndex) + } + + return false + } +} diff --git a/Sources/Basics/Graph/GraphAlgorithms.swift b/Sources/Basics/Graph/GraphAlgorithms.swift new file mode 100644 index 00000000000..6f7d98970a8 --- /dev/null +++ b/Sources/Basics/Graph/GraphAlgorithms.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct OrderedCollections.OrderedSet + +/// Implements a pre-order depth-first search. +/// +/// The cycles are handled by skipping cycle points but it should be possible to +/// to extend this in the future to provide a callback for every cycle. +/// +/// - Parameters: +/// - nodes: The list of input nodes to sort. +/// - successors: A closure for fetching the successors of a particular node. +/// - onUnique: A callback to indicate the the given node is being processed for the first time. +/// - onDuplicate: A callback to indicate that the node was already processed at least once. +/// +/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges +/// reachable from the input nodes via the relation. +public func depthFirstSearch( + _ nodes: [T], + successors: (T) throws -> [T], + onUnique: (T) -> Void, + onDuplicate: (T, T) -> Void +) rethrows { + var stack = OrderedSet() + var visited = Set() + + for node in nodes { + precondition(stack.isEmpty) + stack.append(node) + + while !stack.isEmpty { + let curr = stack.removeLast() + + let visitResult = visited.insert(curr) + if visitResult.inserted { + onUnique(curr) + } else { + onDuplicate(visitResult.memberAfterInsert, curr) + continue + } + + for succ in try successors(curr) { + stack.append(succ) + } + } + } +} diff --git a/Sources/Basics/Graph/UndirectedGraph.swift b/Sources/Basics/Graph/UndirectedGraph.swift new file mode 100644 index 00000000000..87fda92d854 --- /dev/null +++ b/Sources/Basics/Graph/UndirectedGraph.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct DequeModule.Deque + +/// Undirected graph that stores edges in an [adjacency matrix](https://en.wikipedia.org/wiki/Adjacency_list). +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +public struct UndirectedGraph { + public init(nodes: [Node]) { + self.nodes = nodes + self.edges = .init(rows: nodes.count, columns: nodes.count) + } + + private var nodes: [Node] + private var edges: AdjacencyMatrix + + public mutating func addEdge(source: Int, destination: Int) { + // Adjacency matrix is symmetrical for undirected graphs. + self.edges[source, destination] = true + self.edges[destination, source] = true + } + + /// Checks whether a connection via previously created edges between two given nodes exists. + /// - Parameters: + /// - source: `Index` of a node to start traversing edges from. + /// - destination: `Index` of a node to which a connection could exist via edges from `source`. + /// - Returns: `true` if a path from `source` to `destination` exists, `false` otherwise. + public func areNodesConnected(source: Int, destination: Int) -> Bool { + var todo = Deque([source]) + var done = Set() + + while !todo.isEmpty { + let nodeIndex = todo.removeFirst() + + for reachableIndex in self.edges.nodesAdjacentTo(nodeIndex) { + if reachableIndex == destination { + return true + } else if !done.contains(reachableIndex) { + todo.append(reachableIndex) + } + } + + done.insert(nodeIndex) + } + + return false + } +} + +private extension AdjacencyMatrix { + func nodesAdjacentTo(_ nodeIndex: Int) -> [Int] { + var result = [Int]() + + for i in 0..) -> Void) -> Void - public typealias ProgressHandler = (_ bytesReceived: Int64, _ totalBytes: Int64?) throws -> Void - public typealias CompletionHandler = (Result) -> Void + public typealias Handler = (Request, ProgressHandler?, @escaping @Sendable (Result) -> Void) -> Void + public typealias ProgressHandler = @Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) throws -> Void + public typealias CompletionHandler = @Sendable (Result) -> Void public var configuration: LegacyHTTPClientConfiguration private let underlying: Handler @@ -121,7 +121,7 @@ public final class LegacyHTTPClient: Cancellable { requestNumber: 0, observabilityScope: observabilityScope, progress: progress.map { handler in - { received, expected in + { @Sendable received, expected in // call back on the requested queue callbackQueue.async { do { @@ -312,7 +312,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .head, url: url, headers: headers, body: nil, options: options), @@ -326,7 +326,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .get, url: url, headers: headers, body: nil, options: options), @@ -341,7 +341,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .put, url: url, headers: headers, body: body, options: options), @@ -356,7 +356,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .post, url: url, headers: headers, body: body, options: options), @@ -370,7 +370,7 @@ extension LegacyHTTPClient { headers: HTTPClientHeaders = .init(), options: Request.Options = .init(), observabilityScope: ObservabilityScope? = .none, - completion: @escaping (Result) -> Void + completion: @Sendable @escaping (Result) -> Void ) { self.execute( Request(method: .delete, url: url, headers: headers, body: nil, options: options), @@ -383,7 +383,7 @@ extension LegacyHTTPClient { // MARK: - LegacyHTTPClientConfiguration public struct LegacyHTTPClientConfiguration { - public typealias AuthorizationProvider = (URL) -> String? + public typealias AuthorizationProvider = @Sendable (URL) -> String? public var requestHeaders: HTTPClientHeaders? public var requestTimeout: DispatchTimeInterval? diff --git a/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift b/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift index d6302003d2a..7c27608749d 100644 --- a/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift +++ b/Sources/Basics/HTTPClient/URLSessionHTTPClient.swift @@ -14,18 +14,41 @@ import _Concurrency import Foundation import struct TSCUtility.Versioning #if canImport(FoundationNetworking) -// FIXME: this brings OpenSSL dependency on Linux -// need to decide how to best deal with that +// FIXME: this brings OpenSSL dependency on Linux and needs to be replaced with `swift-server/async-http-client` package import FoundationNetworking #endif -final class URLSessionHTTPClient { +final class URLSessionHTTPClient: Sendable { + private let dataSession: URLSession + private let downloadSession: URLSession private let dataTaskManager: DataTaskManager private let downloadTaskManager: DownloadTaskManager init(configuration: URLSessionConfiguration = .default) { - self.dataTaskManager = DataTaskManager(configuration: configuration) - self.downloadTaskManager = DownloadTaskManager(configuration: configuration) + let dataDelegateQueue = OperationQueue() + dataDelegateQueue.name = "org.swift.swiftpm.urlsession-http-client-data-delegate" + dataDelegateQueue.maxConcurrentOperationCount = 1 + self.dataTaskManager = DataTaskManager() + self.dataSession = URLSession( + configuration: configuration, + delegate: self.dataTaskManager, + delegateQueue: dataDelegateQueue + ) + + let downloadDelegateQueue = OperationQueue() + downloadDelegateQueue.name = "org.swift.swiftpm.urlsession-http-client-download-delegate" + downloadDelegateQueue.maxConcurrentOperationCount = 1 + self.downloadTaskManager = DownloadTaskManager() + self.downloadSession = URLSession( + configuration: configuration, + delegate: self.downloadTaskManager, + delegateQueue: downloadDelegateQueue + ) + } + + deinit { + dataSession.finishTasksAndInvalidate() + downloadSession.finishTasksAndInvalidate() } @Sendable @@ -38,27 +61,34 @@ final class URLSessionHTTPClient { let task: URLSessionTask switch request.kind { case .generic: - task = self.dataTaskManager.makeTask( + let dataTask = self.dataSession.dataTask(with: urlRequest) + self.dataTaskManager.register( + task: dataTask, urlRequest: urlRequest, authorizationProvider: request.options.authorizationProvider, progress: progress, - completion: continuation.resume(with:) + completion: { continuation.resume(with: $0) } ) + task = dataTask case .download(_, let destination): - task = self.downloadTaskManager.makeTask( + let downloadTask = self.downloadSession.downloadTask(with: urlRequest) + self.downloadTaskManager.register( + task: downloadTask, urlRequest: urlRequest, - // FIXME: always using a synchronous filesystem, because `URLSessionDownloadDelegate` + // FIXME: always using synchronous filesystem, because `URLSessionDownloadDelegate` // needs temporary files to moved out of temporary locations synchronously in delegate callbacks. fileSystem: localFileSystem, destination: destination, progress: progress, - completion: continuation.resume(with:) + completion: { continuation.resume(with: $0) } ) + task = downloadTask } task.resume() } } + @Sendable public func execute( _ request: LegacyHTTPClient.Request, progress: LegacyHTTPClient.ProgressHandler?, @@ -68,52 +98,48 @@ final class URLSessionHTTPClient { let task: URLSessionTask switch request.kind { case .generic: - task = self.dataTaskManager.makeTask( + let dataTask = self.dataSession.dataTask(with: urlRequest) + self.dataTaskManager.register( + task: dataTask, urlRequest: urlRequest, authorizationProvider: request.options.authorizationProvider, progress: progress, completion: completion ) + task = dataTask case .download(let fileSystem, let destination): - task = self.downloadTaskManager.makeTask( + let downloadTask = self.downloadSession.downloadTask(with: urlRequest) + self.downloadTaskManager.register( + task: downloadTask, urlRequest: urlRequest, fileSystem: fileSystem, destination: destination, progress: progress, completion: completion ) + task = downloadTask } task.resume() } } -private class DataTaskManager: NSObject, URLSessionDataDelegate { - private var tasks = ThreadSafeKeyValueStore() - private let delegateQueue: OperationQueue - private var session: URLSession! - - public init(configuration: URLSessionConfiguration) { - self.delegateQueue = OperationQueue() - self.delegateQueue.name = "org.swift.swiftpm.urlsession-http-client-data-delegate" - self.delegateQueue.maxConcurrentOperationCount = 1 - super.init() - self.session = URLSession(configuration: configuration, delegate: self, delegateQueue: self.delegateQueue) - } +private final class DataTaskManager: NSObject, URLSessionDataDelegate { + private let tasks = ThreadSafeKeyValueStore() - func makeTask( + func register( + task: URLSessionDataTask, urlRequest: URLRequest, authorizationProvider: LegacyHTTPClientConfiguration.AuthorizationProvider?, progress: LegacyHTTPClient.ProgressHandler?, completion: @escaping LegacyHTTPClient.CompletionHandler - ) -> URLSessionDataTask { - let task = self.session.dataTask(with: urlRequest) + ) { self.tasks[task.taskIdentifier] = DataTask( task: task, progressHandler: progress, + dataTaskManager: self, completionHandler: completion, authorizationProvider: authorizationProvider ) - return task } public func urlSession( @@ -122,11 +148,13 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate { didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void ) { - guard let task = self.tasks[dataTask.taskIdentifier] else { + guard var task = self.tasks[dataTask.taskIdentifier] else { return completionHandler(.cancel) } task.response = response as? HTTPURLResponse task.expectedContentLength = response.expectedContentLength + self.tasks[dataTask.taskIdentifier] = task + do { try task.progressHandler?(0, response.expectedContentLength) completionHandler(.allow) @@ -136,7 +164,7 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate { } public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { - guard let task = self.tasks[dataTask.taskIdentifier] else { + guard var task = self.tasks[dataTask.taskIdentifier] else { return } if task.buffer != nil { @@ -144,6 +172,7 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate { } else { task.buffer = data } + self.tasks[dataTask.taskIdentifier] = task do { // safe since created in the line above @@ -189,9 +218,14 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate { completionHandler(request) } - class DataTask { + struct DataTask: Sendable { let task: URLSessionDataTask let completionHandler: LegacyHTTPClient.CompletionHandler + /// A strong reference to keep the `DataTaskManager` alive so it can handle the callbacks from the + /// `URLSession`. + /// + /// See comment on `WeakDataTaskManager`. + let dataTaskManager: DataTaskManager let progressHandler: LegacyHTTPClient.ProgressHandler? let authorizationProvider: LegacyHTTPClientConfiguration.AuthorizationProvider? @@ -202,46 +236,38 @@ private class DataTaskManager: NSObject, URLSessionDataDelegate { init( task: URLSessionDataTask, progressHandler: LegacyHTTPClient.ProgressHandler?, + dataTaskManager: DataTaskManager, completionHandler: @escaping LegacyHTTPClient.CompletionHandler, authorizationProvider: LegacyHTTPClientConfiguration.AuthorizationProvider? ) { self.task = task self.progressHandler = progressHandler + self.dataTaskManager = dataTaskManager self.completionHandler = completionHandler self.authorizationProvider = authorizationProvider } } } -private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { - private var tasks = ThreadSafeKeyValueStore() - private let delegateQueue: OperationQueue - private var session: URLSession! - - init(configuration: URLSessionConfiguration) { - self.delegateQueue = OperationQueue() - self.delegateQueue.name = "org.swift.swiftpm.urlsession-http-client-download-delegate" - self.delegateQueue.maxConcurrentOperationCount = 1 - super.init() - self.session = URLSession(configuration: configuration, delegate: self, delegateQueue: self.delegateQueue) - } +private final class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { + private let tasks = ThreadSafeKeyValueStore() - func makeTask( + func register( + task: URLSessionDownloadTask, urlRequest: URLRequest, fileSystem: FileSystem, destination: AbsolutePath, progress: LegacyHTTPClient.ProgressHandler?, completion: @escaping LegacyHTTPClient.CompletionHandler - ) -> URLSessionDownloadTask { - let task = self.session.downloadTask(with: urlRequest) + ) { self.tasks[task.taskIdentifier] = DownloadTask( task: task, fileSystem: fileSystem, destination: destination, + downloadTaskManager: self, progressHandler: progress, completionHandler: completion ) - return task } func urlSession( @@ -270,7 +296,7 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL ) { - guard let task = self.tasks[downloadTask.taskIdentifier] else { + guard var task = self.tasks[downloadTask.taskIdentifier] else { return } @@ -283,6 +309,7 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { try task.fileSystem.move(from: path, to: task.destination) } catch { task.moveFileError = error + self.tasks[downloadTask.taskIdentifier] = task } } @@ -310,12 +337,12 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { } } - class DownloadTask { + struct DownloadTask: Sendable { let task: URLSessionDownloadTask let fileSystem: FileSystem let destination: AbsolutePath - let completionHandler: LegacyHTTPClient.CompletionHandler let progressHandler: LegacyHTTPClient.ProgressHandler? + let completionHandler: LegacyHTTPClient.CompletionHandler var moveFileError: Error? @@ -323,6 +350,7 @@ private class DownloadTaskManager: NSObject, URLSessionDownloadDelegate { task: URLSessionDownloadTask, fileSystem: FileSystem, destination: AbsolutePath, + downloadTaskManager: DownloadTaskManager, progressHandler: LegacyHTTPClient.ProgressHandler?, completionHandler: @escaping LegacyHTTPClient.CompletionHandler ) { diff --git a/Sources/Basics/OSSignpost.swift b/Sources/Basics/OSSignpost.swift index 34eb9d49c04..96f524f2a60 100644 --- a/Sources/Basics/OSSignpost.swift +++ b/Sources/Basics/OSSignpost.swift @@ -22,7 +22,7 @@ extension os.OSLog { #endif /// Emits a signpost. -@inlinable public func os_signpost( +@inlinable package func os_signpost( _ type: SignpostType, name: StaticString, signpostID: SignpostID = .exclusive @@ -39,8 +39,8 @@ extension os.OSLog { #endif } - -public enum SignpostType { +@usableFromInline +package enum SignpostType { case begin case end case event @@ -61,7 +61,8 @@ public enum SignpostType { #endif } -public enum SignpostID { +@usableFromInline +package enum SignpostID { case exclusive #if canImport(os) @@ -77,7 +78,7 @@ public enum SignpostID { } -public enum SignpostName { +package enum SignpostName { public static let updatingDependencies: StaticString = "updating" public static let resolvingDependencies: StaticString = "resolving" public static let pubgrub: StaticString = "pubgrub" diff --git a/Sources/Basics/Observability.swift b/Sources/Basics/Observability.swift index 4d6e01f959b..8a5afc23a70 100644 --- a/Sources/Basics/Observability.swift +++ b/Sources/Basics/Observability.swift @@ -46,8 +46,7 @@ public class ObservabilitySystem { private struct SingleDiagnosticsHandler: ObservabilityHandlerProvider, DiagnosticsHandler { var diagnosticsHandler: DiagnosticsHandler { self } - let underlying: @Sendable (ObservabilityScope, Diagnostic) - -> Void + let underlying: @Sendable (ObservabilityScope, Diagnostic) -> Void init(_ underlying: @escaping @Sendable (ObservabilityScope, Diagnostic) -> Void) { self.underlying = underlying @@ -57,6 +56,10 @@ public class ObservabilitySystem { self.underlying(scope, diagnostic) } } + + public static var NOOP: ObservabilityScope { + ObservabilitySystem { _, _ in }.topScope + } } public protocol ObservabilityHandlerProvider { diff --git a/Sources/Basics/ProgressAnimation/NinjaProgressAnimation.swift b/Sources/Basics/ProgressAnimation/NinjaProgressAnimation.swift new file mode 100644 index 00000000000..33d2e121957 --- /dev/null +++ b/Sources/Basics/ProgressAnimation/NinjaProgressAnimation.swift @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import class TSCBasic.TerminalController +import protocol TSCBasic.WritableByteStream + +extension ProgressAnimation { + /// A ninja-like progress animation that adapts to the provided output stream. + package static func ninja( + stream: WritableByteStream, + verbose: Bool + ) -> any ProgressAnimationProtocol { + Self.dynamic( + stream: stream, + verbose: verbose, + ttyTerminalAnimationFactory: { RedrawingNinjaProgressAnimation(terminal: $0) }, + dumbTerminalAnimationFactory: { SingleLinePercentProgressAnimation(stream: stream, header: nil) }, + defaultAnimationFactory: { MultiLineNinjaProgressAnimation(stream: stream) } + ) + } +} + +/// A redrawing ninja-like progress animation. +final class RedrawingNinjaProgressAnimation: ProgressAnimationProtocol { + private let terminal: TerminalController + private var hasDisplayedProgress = false + + init(terminal: TerminalController) { + self.terminal = terminal + } + + func update(step: Int, total: Int, text: String) { + assert(step <= total) + + terminal.clearLine() + + let progressText = "[\(step)/\(total)] \(text)" + let width = terminal.width + if progressText.utf8.count > width { + let suffix = "…" + terminal.write(String(progressText.prefix(width - suffix.utf8.count))) + terminal.write(suffix) + } else { + terminal.write(progressText) + } + + hasDisplayedProgress = true + } + + func complete(success: Bool) { + if hasDisplayedProgress { + terminal.endLine() + } + } + + func clear() { + terminal.clearLine() + } +} + +/// A multi-line ninja-like progress animation. +final class MultiLineNinjaProgressAnimation: ProgressAnimationProtocol { + private struct Info: Equatable { + let step: Int + let total: Int + let text: String + } + + private let stream: WritableByteStream + private var lastDisplayedText: String? = nil + + init(stream: WritableByteStream) { + self.stream = stream + } + + func update(step: Int, total: Int, text: String) { + assert(step <= total) + + guard text != lastDisplayedText else { return } + + stream.send("[\(step)/\(total)] ").send(text) + stream.send("\n") + stream.flush() + lastDisplayedText = text + } + + func complete(success: Bool) { + } + + func clear() { + } +} diff --git a/Sources/Basics/ProgressAnimation/PercentProgressAnimation.swift b/Sources/Basics/ProgressAnimation/PercentProgressAnimation.swift new file mode 100644 index 00000000000..a0929634288 --- /dev/null +++ b/Sources/Basics/ProgressAnimation/PercentProgressAnimation.swift @@ -0,0 +1,138 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import class TSCBasic.TerminalController +import protocol TSCBasic.WritableByteStream + +extension ProgressAnimation { + /// A percent-based progress animation that adapts to the provided output stream. + package static func percent( + stream: WritableByteStream, + verbose: Bool, + header: String + ) -> any ProgressAnimationProtocol { + Self.dynamic( + stream: stream, + verbose: verbose, + ttyTerminalAnimationFactory: { RedrawingPercentProgressAnimation(terminal: $0, header: header) }, + dumbTerminalAnimationFactory: { SingleLinePercentProgressAnimation(stream: stream, header: header) }, + defaultAnimationFactory: { MultiLinePercentProgressAnimation(stream: stream, header: header) } + ) + } +} + +/// A redrawing lit-like progress animation. +final class RedrawingPercentProgressAnimation: ProgressAnimationProtocol { + private let terminal: TerminalController + private let header: String + private var hasDisplayedHeader = false + + init(terminal: TerminalController, header: String) { + self.terminal = terminal + self.header = header + } + + /// Creates repeating string for count times. + /// If count is negative, returns empty string. + private func repeating(string: String, count: Int) -> String { + return String(repeating: string, count: max(count, 0)) + } + + func update(step: Int, total: Int, text: String) { + assert(step <= total) + + let width = terminal.width + if !hasDisplayedHeader { + let spaceCount = width / 2 - header.utf8.count / 2 + terminal.write(repeating(string: " ", count: spaceCount)) + terminal.write(header, inColor: .cyan, bold: true) + terminal.endLine() + hasDisplayedHeader = true + } else { + terminal.moveCursor(up: 1) + } + + terminal.clearLine() + let percentage = step * 100 / total + let paddedPercentage = percentage < 10 ? " \(percentage)" : "\(percentage)" + let prefix = "\(paddedPercentage)% " + terminal.wrap("[", inColor: .green, bold: true) + terminal.write(prefix) + + let barWidth = width - prefix.utf8.count + let n = Int(Double(barWidth) * Double(percentage) / 100.0) + + terminal.write(repeating(string: "=", count: n) + repeating(string: "-", count: barWidth - n), inColor: .green) + terminal.write("]", inColor: .green, bold: true) + terminal.endLine() + + terminal.clearLine() + if text.utf8.count > width { + let prefix = "…" + terminal.write(prefix) + terminal.write(String(text.suffix(width - prefix.utf8.count))) + } else { + terminal.write(text) + } + } + + func complete(success: Bool) { + terminal.endLine() + terminal.endLine() + } + + func clear() { + terminal.clearLine() + terminal.moveCursor(up: 1) + terminal.clearLine() + } +} + +/// A multi-line percent-based progress animation. +final class MultiLinePercentProgressAnimation: ProgressAnimationProtocol { + private struct Info: Equatable { + let percentage: Int + let text: String + } + + private let stream: WritableByteStream + private let header: String + private var hasDisplayedHeader = false + private var lastDisplayedText: String? = nil + + init(stream: WritableByteStream, header: String) { + self.stream = stream + self.header = header + } + + func update(step: Int, total: Int, text: String) { + assert(step <= total) + + if !hasDisplayedHeader, !header.isEmpty { + stream.send(header) + stream.send("\n") + stream.flush() + hasDisplayedHeader = true + } + + let percentage = step * 100 / total + stream.send("\(percentage)%: ").send(text) + stream.send("\n") + stream.flush() + lastDisplayedText = text + } + + func complete(success: Bool) { + } + + func clear() { + } +} diff --git a/Sources/Basics/ProgressAnimation/ProgressAnimationProtocol.swift b/Sources/Basics/ProgressAnimation/ProgressAnimationProtocol.swift new file mode 100644 index 00000000000..43d279cbb46 --- /dev/null +++ b/Sources/Basics/ProgressAnimation/ProgressAnimationProtocol.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import class TSCBasic.TerminalController +import class TSCBasic.LocalFileOutputByteStream +import protocol TSCBasic.WritableByteStream +import protocol TSCUtility.ProgressAnimationProtocol + +package typealias ProgressAnimationProtocol = TSCUtility.ProgressAnimationProtocol + +/// Namespace to nest public progress animations under. +package enum ProgressAnimation { + /// Dynamically create a progress animation based on the current stream + /// capabilities and desired verbosity. + /// + /// - Parameters: + /// - stream: A stream to write animations into. + /// - verbose: The verbosity level of other output in the system. + /// - ttyTerminalAnimationFactory: A progress animation to use when the + /// output stream is connected to a terminal with support for special + /// escape sequences. + /// - dumbTerminalAnimationFactory: A progress animation to use when the + /// output stream is connected to a terminal without support for special + /// escape sequences for clearing lines or controlling cursor positions. + /// - defaultAnimationFactory: A progress animation to use when the + /// desired output is verbose or the output stream verbose or is not + /// connected to a terminal, e.g. a pipe or file. + /// - Returns: A progress animation instance matching the stream + /// capabilities and desired verbosity. + static func dynamic( + stream: WritableByteStream, + verbose: Bool, + ttyTerminalAnimationFactory: (TerminalController) -> any ProgressAnimationProtocol, + dumbTerminalAnimationFactory: () -> any ProgressAnimationProtocol, + defaultAnimationFactory: () -> any ProgressAnimationProtocol + ) -> any ProgressAnimationProtocol { + if let terminal = TerminalController(stream: stream), !verbose { + return ttyTerminalAnimationFactory(terminal) + } else if let fileStream = stream as? LocalFileOutputByteStream, + TerminalController.terminalType(fileStream) == .dumb + { + return dumbTerminalAnimationFactory() + } else { + return defaultAnimationFactory() + } + } +} + diff --git a/Sources/Basics/ProgressAnimation/SingleLinePercentProgressAnimation.swift b/Sources/Basics/ProgressAnimation/SingleLinePercentProgressAnimation.swift new file mode 100644 index 00000000000..c11b25e4b9b --- /dev/null +++ b/Sources/Basics/ProgressAnimation/SingleLinePercentProgressAnimation.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import class TSCBasic.TerminalController +import protocol TSCBasic.WritableByteStream + +/// A single line percent-based progress animation. +final class SingleLinePercentProgressAnimation: ProgressAnimationProtocol { + private let stream: WritableByteStream + private let header: String? + private var displayedPercentages: Set = [] + private var hasDisplayedHeader = false + + init(stream: WritableByteStream, header: String?) { + self.stream = stream + self.header = header + } + + func update(step: Int, total: Int, text: String) { + if let header = header, !hasDisplayedHeader { + stream.send(header) + stream.send("\n") + stream.flush() + hasDisplayedHeader = true + } + + let percentage = step * 100 / total + let roundedPercentage = Int(Double(percentage / 10).rounded(.down)) * 10 + if percentage != 100, !displayedPercentages.contains(roundedPercentage) { + stream.send(String(roundedPercentage)).send(".. ") + displayedPercentages.insert(roundedPercentage) + } + + stream.flush() + } + + func complete(success: Bool) { + if success { + stream.send("OK") + stream.flush() + } + } + + func clear() { + } +} diff --git a/Sources/Basics/ProgressAnimation.swift b/Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift similarity index 64% rename from Sources/Basics/ProgressAnimation.swift rename to Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift index 9be0dc93c5e..4e4747be2dc 100644 --- a/Sources/Basics/ProgressAnimation.swift +++ b/Sources/Basics/ProgressAnimation/ThrottledProgressAnimation.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,23 +11,13 @@ //===----------------------------------------------------------------------===// import _Concurrency -import protocol TSCUtility.ProgressAnimationProtocol /// A progress animation wrapper that throttles updates to a given interval. -@_spi(SwiftPMInternal) -public class ThrottledProgressAnimation: ProgressAnimationProtocol { +final class ThrottledProgressAnimation: ProgressAnimationProtocol { private let animation: ProgressAnimationProtocol private let shouldUpdate: () -> Bool private var pendingUpdate: (Int, Int, String)? - public convenience init(_ animation: ProgressAnimationProtocol, interval: ContinuousClock.Duration) { - self.init(animation, clock: ContinuousClock(), interval: interval) - } - - public convenience init(_ animation: ProgressAnimationProtocol, clock: C, interval: C.Duration) { - self.init(animation, now: { clock.now }, interval: interval, clock: C.self) - } - init( _ animation: ProgressAnimationProtocol, now: @escaping () -> C.Instant, interval: C.Duration, clock: C.Type = C.self @@ -45,7 +35,7 @@ public class ThrottledProgressAnimation: ProgressAnimationProtocol { } } - public func update(step: Int, total: Int, text: String) { + func update(step: Int, total: Int, text: String) { guard shouldUpdate() else { pendingUpdate = (step, total, text) return @@ -54,14 +44,37 @@ public class ThrottledProgressAnimation: ProgressAnimationProtocol { animation.update(step: step, total: total, text: text) } - public func complete(success: Bool) { + func complete(success: Bool) { if let (step, total, text) = pendingUpdate { animation.update(step: step, total: total, text: text) } animation.complete(success: success) } - public func clear() { + func clear() { animation.clear() } } + +extension ProgressAnimationProtocol { + package func throttled( + now: @escaping () -> C.Instant, + interval: C.Duration, + clock: C.Type = C.self + ) -> some ProgressAnimationProtocol { + ThrottledProgressAnimation(self, now: now, interval: interval, clock: clock) + } + + package func throttled( + clock: C, + interval: C.Duration + ) -> some ProgressAnimationProtocol { + self.throttled(now: { clock.now }, interval: interval, clock: C.self) + } + + package func throttled( + interval: ContinuousClock.Duration + ) -> some ProgressAnimationProtocol { + self.throttled(clock: ContinuousClock(), interval: interval) + } +} diff --git a/Sources/Basics/SQLite.swift b/Sources/Basics/SQLite.swift index ab4e8ae34a5..d6cc6a108d2 100644 --- a/Sources/Basics/SQLite.swift +++ b/Sources/Basics/SQLite.swift @@ -19,12 +19,12 @@ import SPMSQLite3 #endif /// A minimal SQLite wrapper. -public final class SQLite { +package final class SQLite { /// The location of the database. - public let location: Location + package let location: Location /// The configuration for the database. - public let configuration: Configuration + package let configuration: Configuration /// Pointer to the database. let db: OpaquePointer @@ -32,7 +32,7 @@ public final class SQLite { /// Create or open the database at the given path. /// /// The database is opened in serialized mode. - public init(location: Location, configuration: Configuration = Configuration()) throws { + package init(location: Location, configuration: Configuration = Configuration()) throws { self.location = location self.configuration = configuration @@ -64,19 +64,19 @@ public final class SQLite { } @available(*, deprecated, message: "use init(location:configuration) instead") - public convenience init(dbPath: AbsolutePath) throws { + package convenience init(dbPath: AbsolutePath) throws { try self.init(location: .path(dbPath)) } /// Prepare the given query. - public func prepare(query: String) throws -> PreparedStatement { + package func prepare(query: String) throws -> PreparedStatement { try PreparedStatement(db: self.db, query: query) } /// Directly execute the given query. /// /// Note: Use withCString for string arguments. - public func exec(query queryString: String, args: [CVarArg] = [], _ callback: SQLiteExecCallback? = nil) throws { + package func exec(query queryString: String, args: [CVarArg] = [], _ callback: SQLiteExecCallback? = nil) throws { let query = withVaList(args) { ptr in sqlite3_vmprintf(queryString, ptr) } @@ -96,27 +96,27 @@ public final class SQLite { } } - public func close() throws { + package func close() throws { try Self.checkError { sqlite3_close(db) } } - public typealias SQLiteExecCallback = ([Column]) -> Void + package typealias SQLiteExecCallback = ([Column]) -> Void - public struct Configuration { - public var busyTimeoutMilliseconds: Int32 - public var maxSizeInBytes: Int? + package struct Configuration { + package var busyTimeoutMilliseconds: Int32 + package var maxSizeInBytes: Int? // https://www.sqlite.org/pgszchng2016.html private let defaultPageSizeInBytes = 1024 - public init() { + package init() { self.busyTimeoutMilliseconds = 5000 self.maxSizeInBytes = .none } // FIXME: deprecated 12/2020, remove once clients migrated over @available(*, deprecated, message: "use busyTimeout instead") - public var busyTimeoutSeconds: Int32 { + package var busyTimeoutSeconds: Int32 { get { self._busyTimeoutSeconds } set { @@ -133,7 +133,7 @@ public final class SQLite { } } - public var maxSizeInMegabytes: Int? { + package var maxSizeInMegabytes: Int? { get { self.maxSizeInBytes.map { $0 / (1024 * 1024) } } @@ -142,12 +142,12 @@ public final class SQLite { } } - public var maxPageCount: Int? { + package var maxPageCount: Int? { self.maxSizeInBytes.map { $0 / self.defaultPageSizeInBytes } } } - public enum Location { + package enum Location: Sendable { case path(AbsolutePath) case memory case temporary @@ -165,7 +165,7 @@ public final class SQLite { } /// Represents an sqlite value. - public enum SQLiteValue { + package enum SQLiteValue { case null case string(String) case int(Int) @@ -173,35 +173,35 @@ public final class SQLite { } /// Represents a row returned by called step() on a prepared statement. - public struct Row { + package struct Row { /// The pointer to the prepared statement. let stmt: OpaquePointer /// Get integer at the given column index. - public func int(at index: Int32) -> Int { + package func int(at index: Int32) -> Int { Int(sqlite3_column_int64(self.stmt, index)) } /// Get blob data at the given column index. - public func blob(at index: Int32) -> Data { + package func blob(at index: Int32) -> Data { let bytes = sqlite3_column_blob(stmt, index)! let count = sqlite3_column_bytes(stmt, index) return Data(bytes: bytes, count: Int(count)) } /// Get string at the given column index. - public func string(at index: Int32) -> String { + package func string(at index: Int32) -> String { String(cString: sqlite3_column_text(self.stmt, index)) } } - public struct Column { - public var name: String - public var value: String + package struct Column { + package var name: String + package var value: String } /// Represents a prepared statement. - public struct PreparedStatement { + package struct PreparedStatement { typealias sqlite3_destructor_type = @convention(c) (UnsafeMutableRawPointer?) -> Void static let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self) static let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) @@ -209,7 +209,7 @@ public final class SQLite { /// The pointer to the prepared statement. let stmt: OpaquePointer - public init(db: OpaquePointer, query: String) throws { + package init(db: OpaquePointer, query: String) throws { var stmt: OpaquePointer? try SQLite.checkError { sqlite3_prepare_v2(db, query, -1, &stmt, nil) } self.stmt = stmt! @@ -217,7 +217,7 @@ public final class SQLite { /// Evaluate the prepared statement. @discardableResult - public func step() throws -> Row? { + package func step() throws -> Row? { let result = sqlite3_step(stmt) switch result { @@ -231,7 +231,7 @@ public final class SQLite { } /// Bind the given arguments to the statement. - public func bind(_ arguments: [SQLiteValue]) throws { + package func bind(_ arguments: [SQLiteValue]) throws { for (idx, argument) in arguments.enumerated() { let idx = Int32(idx) + 1 switch argument { @@ -258,17 +258,17 @@ public final class SQLite { } /// Reset the prepared statement. - public func reset() throws { + package func reset() throws { try SQLite.checkError { sqlite3_reset(stmt) } } /// Clear bindings from the prepared statement. - public func clearBindings() throws { + package func clearBindings() throws { try SQLite.checkError { sqlite3_clear_bindings(stmt) } } /// Finalize the statement and free up resources. - public func finalize() throws { + package func finalize() throws { try SQLite.checkError { sqlite3_finalize(stmt) } } } @@ -296,7 +296,7 @@ public final class SQLite { } } - public enum Errors: Error { + package enum Errors: Error { case databaseFull } } diff --git a/Sources/Basics/SQLiteBackedCache.swift b/Sources/Basics/SQLiteBackedCache.swift index 73554819747..373755bd39b 100644 --- a/Sources/Basics/SQLiteBackedCache.swift +++ b/Sources/Basics/SQLiteBackedCache.swift @@ -17,13 +17,13 @@ import class TSCBasic.InMemoryFileSystem import var TSCBasic.localFileSystem /// SQLite backed persistent cache. -public final class SQLiteBackedCache: Closable { - public typealias Key = String +package final class SQLiteBackedCache: Closable { + package typealias Key = String - public let tableName: String - public let fileSystem: FileSystem - public let location: SQLite.Location - public let configuration: SQLiteBackedCacheConfiguration + package let tableName: String + package let fileSystem: FileSystem + package let location: SQLite.Location + package let configuration: SQLiteBackedCacheConfiguration private var state = State.idle private let stateLock = NSLock() @@ -37,7 +37,7 @@ public final class SQLiteBackedCache: Closable { /// - tableName: The SQLite table name. Must follow SQLite naming rules (e.g., no spaces). /// - location: SQLite.Location /// - configuration: Optional. Configuration for the cache. - public init(tableName: String, location: SQLite.Location, configuration: SQLiteBackedCacheConfiguration = .init()) { + package init(tableName: String, location: SQLite.Location, configuration: SQLiteBackedCacheConfiguration = .init()) { self.tableName = tableName self.location = location switch self.location { @@ -57,7 +57,7 @@ public final class SQLiteBackedCache: Closable { /// - tableName: The SQLite table name. Must follow SQLite naming rules (e.g., no spaces). /// - path: The path of the SQLite database. /// - configuration: Optional. Configuration for the cache. - public convenience init( + package convenience init( tableName: String, path: AbsolutePath, configuration: SQLiteBackedCacheConfiguration = .init() @@ -75,7 +75,7 @@ public final class SQLiteBackedCache: Closable { } } - public func close() throws { + package func close() throws { try self.withStateLock { if case .connected(let db) = self.state { try db.close() @@ -84,8 +84,8 @@ public final class SQLiteBackedCache: Closable { } } - public func put( - key: Key, + private func put( + rawKey key: SQLite.SQLiteValue, value: Value, replace: Bool = false, observabilityScope: ObservabilityScope? = nil @@ -95,7 +95,7 @@ public final class SQLiteBackedCache: Closable { try self.executeStatement(query) { statement in let data = try self.jsonEncoder.encode(value) let bindings: [SQLite.SQLiteValue] = [ - .string(key), + key, .blob(data), ] try statement.bind(bindings) @@ -107,18 +107,40 @@ public final class SQLiteBackedCache: Closable { } observabilityScope? .emit( - warning: "truncating \(self.tableName) cache database since it reached max size of \(self.configuration.maxSizeInBytes ?? 0) bytes" + warning: """ + truncating \(self.tableName) cache database since it reached max size of \( + self.configuration.maxSizeInBytes ?? 0 + ) bytes + """ ) try self.executeStatement("DELETE FROM \(self.tableName);") { statement in try statement.step() } - try self.put(key: key, value: value, replace: replace, observabilityScope: observabilityScope) + try self.put(rawKey: key, value: value, replace: replace, observabilityScope: observabilityScope) } catch { throw error } } - public func get(key: Key) throws -> Value? { + package func put( + blobKey key: some Sequence, + value: Value, + replace: Bool = false, + observabilityScope: ObservabilityScope? = nil + ) throws { + try self.put(rawKey: .blob(Data(key)), value: value, observabilityScope: observabilityScope) + } + + package func put( + key: Key, + value: Value, + replace: Bool = false, + observabilityScope: ObservabilityScope? = nil + ) throws { + try self.put(rawKey: .string(key), value: value, replace: replace, observabilityScope: observabilityScope) + } + + package func get(key: Key) throws -> Value? { let query = "SELECT value FROM \(self.tableName) WHERE key = ? LIMIT 1;" return try self.executeStatement(query) { statement -> Value? in try statement.bind([.string(key)]) @@ -129,7 +151,18 @@ public final class SQLiteBackedCache: Closable { } } - public func remove(key: Key) throws { + package func get(blobKey key: some Sequence) throws -> Value? { + let query = "SELECT value FROM \(self.tableName) WHERE key = ? LIMIT 1;" + return try self.executeStatement(query) { statement -> Value? in + try statement.bind([.blob(Data(key))]) + let data = try statement.step()?.blob(at: 0) + return try data.flatMap { + try self.jsonDecoder.decode(Value.self, from: $0) + } + } + } + + package func remove(key: Key) throws { let query = "DELETE FROM \(self.tableName) WHERE key = ?;" try self.executeStatement(query) { statement in try statement.bind([.string(key)]) @@ -143,7 +176,7 @@ public final class SQLiteBackedCache: Closable { let result: Result let statement = try db.prepare(query: query) do { - result = .success(try body(statement)) + result = try .success(body(statement)) } catch { result = .failure(error) } @@ -221,12 +254,12 @@ public final class SQLiteBackedCache: Closable { } } -public struct SQLiteBackedCacheConfiguration { - public var truncateWhenFull: Bool +package struct SQLiteBackedCacheConfiguration { + package var truncateWhenFull: Bool fileprivate var underlying: SQLite.Configuration - public init() { + package init() { self.underlying = .init() self.truncateWhenFull = true self.maxSizeInMegabytes = 100 @@ -234,7 +267,7 @@ public struct SQLiteBackedCacheConfiguration { self.busyTimeoutMilliseconds = 1000 } - public var maxSizeInMegabytes: Int? { + package var maxSizeInMegabytes: Int? { get { self.underlying.maxSizeInMegabytes } @@ -243,7 +276,7 @@ public struct SQLiteBackedCacheConfiguration { } } - public var maxSizeInBytes: Int? { + package var maxSizeInBytes: Int? { get { self.underlying.maxSizeInBytes } @@ -252,7 +285,7 @@ public struct SQLiteBackedCacheConfiguration { } } - public var busyTimeoutMilliseconds: Int32 { + package var busyTimeoutMilliseconds: Int32 { get { self.underlying.busyTimeoutMilliseconds } diff --git a/Sources/Basics/Sandbox.swift b/Sources/Basics/Sandbox.swift index f8a32d46874..16e981e540c 100644 --- a/Sources/Basics/Sandbox.swift +++ b/Sources/Basics/Sandbox.swift @@ -45,7 +45,7 @@ public enum Sandbox { /// - Parameters: /// - command: The command line to sandbox (including executable as first argument) /// - fileSystem: The file system instance to use. - /// - strictness: The basic strictness level of the standbox. + /// - strictness: The basic strictness level of the sandbox. /// - writableDirectories: Paths under which writing should be allowed, even if they would otherwise be read-only based on the strictness or paths in `readOnlyDirectories`. /// - readOnlyDirectories: Paths under which writing should be denied, even if they would have otherwise been allowed by the rules implied by the strictness level. public static func apply( diff --git a/Sources/Basics/SwiftVersion.swift b/Sources/Basics/SwiftVersion.swift index 0d479c4cdb5..7d95a327ca4 100644 --- a/Sources/Basics/SwiftVersion.swift +++ b/Sources/Basics/SwiftVersion.swift @@ -16,7 +16,7 @@ import TSCclibc #endif -public struct SwiftVersion { +public struct SwiftVersion: Sendable { /// The version number. public var version: (major: Int, minor: Int, patch: Int) @@ -58,7 +58,7 @@ public struct SwiftVersion { extension SwiftVersion { /// The current version of the package manager. public static let current = SwiftVersion( - version: (5, 11, 0), + version: (6, 0, 0), isDevelopment: true, buildIdentifier: getBuildIdentifier() ) diff --git a/Sources/Basics/Triple+Basics.swift b/Sources/Basics/Triple+Basics.swift index a7664bd39b9..12349f13009 100644 --- a/Sources/Basics/Triple+Basics.swift +++ b/Sources/Basics/Triple+Basics.swift @@ -24,6 +24,10 @@ extension Triple { } extension Triple { + public var isWasm: Bool { + [.wasm32, .wasm64].contains(self.arch) + } + public func isApple() -> Bool { vendor == .apple } @@ -148,6 +152,10 @@ extension Triple { } public var executableExtension: String { + guard !self.isWasm else { + return ".wasm" + } + guard let os = self.os else { return "" } @@ -157,8 +165,6 @@ extension Triple { return "" case .linux, .openbsd: return "" - case .wasi: - return ".wasm" case .win32: return ".exe" case .noneOS: diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index 69412b64fb3..a1e22c52d6e 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -11,10 +11,11 @@ //===----------------------------------------------------------------------===// import Basics +import PackageGraph import PackageLoading import PackageModel -import struct PackageGraph.PackageGraph -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ModulesGraph +import struct PackageGraph.ResolvedModule import struct SPMBuildCore.BuildParameters import struct SPMBuildCore.BuildToolPluginInvocationResult import struct SPMBuildCore.PrebuildCommandResult @@ -22,17 +23,20 @@ import struct SPMBuildCore.PrebuildCommandResult import enum TSCBasic.ProcessEnv /// Target description for a Clang target i.e. C language family target. -public final class ClangTargetBuildDescription { +package final class ClangTargetBuildDescription { + /// The package this target belongs to. + package let package: ResolvedPackage + /// The target described by this target. - public let target: ResolvedTarget + package let target: ResolvedModule /// The underlying clang target. - public let clangTarget: ClangTarget + package let clangTarget: ClangTarget /// The tools version of the package that declared the target. This can /// can be used to conditionalize semantically significant changes in how /// a target is built. - public let toolsVersion: ToolsVersion + package let toolsVersion: ToolsVersion /// The build parameters. let buildParameters: BuildParameters @@ -43,13 +47,13 @@ public final class ClangTargetBuildDescription { } /// The list of all resource files in the target, including the derived ones. - public var resources: [Resource] { + package var resources: [Resource] { self.target.underlying.resources + self.pluginDerivedResources } /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { - guard !resources.isEmpty else { + guard !self.resources.isEmpty else { return .none } @@ -61,7 +65,7 @@ public final class ClangTargetBuildDescription { } /// The modulemap file for this target, if any. - public private(set) var moduleMap: AbsolutePath? + package private(set) var moduleMap: AbsolutePath? /// Path to the temporary directory for this target. var tempsPath: AbsolutePath @@ -78,13 +82,13 @@ public final class ClangTargetBuildDescription { private var pluginDerivedResources: [Resource] /// Path to the resource accessor header file, if generated. - public private(set) var resourceAccessorHeaderFile: AbsolutePath? + package private(set) var resourceAccessorHeaderFile: AbsolutePath? /// Path to the resource Info.plist file, if generated. - public private(set) var resourceBundleInfoPlistPath: AbsolutePath? + package private(set) var resourceBundleInfoPlistPath: AbsolutePath? /// The objects in this target. - public var objects: [AbsolutePath] { + package var objects: [AbsolutePath] { get throws { try compilePaths().map(\.object) } @@ -100,16 +104,17 @@ public final class ClangTargetBuildDescription { private let fileSystem: FileSystem /// If this target is a test target. - public var isTestTarget: Bool { + package var isTestTarget: Bool { target.type == .test } /// The results of applying any build tool plugins to this target. - public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] + package let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] /// Create a new target description with target and build parameters. init( - target: ResolvedTarget, + package: ResolvedPackage, + target: ResolvedModule, toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription] = [], buildParameters: BuildParameters, @@ -122,19 +127,20 @@ public final class ClangTargetBuildDescription { throw InternalError("underlying target type mismatch \(target)") } + self.package = package self.clangTarget = clangTarget self.fileSystem = fileSystem self.target = target self.toolsVersion = toolsVersion self.buildParameters = buildParameters - self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") + self.tempsPath = target.tempsPath(buildParameters) self.derivedSources = Sources(paths: [], root: tempsPath.appending("DerivedSources")) // We did not use to apply package plugins to C-family targets in prior tools-versions, this preserves the behavior. if toolsVersion >= .v5_9 { self.buildToolPluginInvocationResults = buildToolPluginInvocationResults - (self.pluginDerivedSources, self.pluginDerivedResources) = PackageGraph.computePluginGeneratedFiles( + (self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -181,8 +187,8 @@ public final class ClangTargetBuildDescription { } } - /// An array of tuple containing filename, source, object and dependency path for each of the source in this target. - public func compilePaths() + /// An array of tuples containing filename, source, object and dependency path for each of the source in this target. + package func compilePaths() throws -> [(filename: RelativePath, source: AbsolutePath, object: AbsolutePath, deps: AbsolutePath)] { let sources = [ @@ -206,7 +212,7 @@ public final class ClangTargetBuildDescription { /// NOTE: The parameter to specify whether to get C++ semantics is currently optional, but this is only for revlock /// avoidance with clients. Callers should always specify what they want based either the user's indication or on a /// default value (possibly based on the filename suffix). - public func basicArguments( + package func basicArguments( isCXX isCXXOverride: Bool? = .none, isC: Bool = false ) throws -> [String] { @@ -219,7 +225,7 @@ public final class ClangTargetBuildDescription { if self.buildParameters.triple.isDarwin() { args += ["-fobjc-arc"] } - args += try buildParameters.targetTripleArgs(for: target) + args += try self.buildParameters.tripleArgs(for: target) args += optimizationArguments args += activeCompilationConditions @@ -305,10 +311,31 @@ public final class ClangTargetBuildDescription { args += ["-I", includeSearchPath.pathString] } + // FIXME: Remove this once it becomes possible to express this dependency in a package manifest. + // + // On Linux/Android swift-corelibs-foundation depends on dispatch library which is + // currently shipped with the Swift toolchain. + if (triple.isLinux() || triple.isAndroid()) && self.package.id == .plain("swift-corelibs-foundation") { + let swiftCompilerPath = self.buildParameters.toolchain.swiftCompilerPath + let toolchainResourcesPath = swiftCompilerPath.parentDirectory + .parentDirectory + .appending(components: ["lib", "swift"]) + args += ["-I", toolchainResourcesPath.pathString] + } + + // suppress warnings if the package is remote + if self.package.isRemote { + args += ["-w"] + // `-w` (suppress warnings) and `-Werror` (warnings as errors) flags are mutually exclusive + if let index = args.firstIndex(of: "-Werror") { + args.remove(at: index) + } + } + return args } - public func emitCommandLine(for filePath: AbsolutePath) throws -> [String] { + package func emitCommandLine(for filePath: AbsolutePath) throws -> [String] { let standards = [ (clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions), (clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions), diff --git a/Sources/Build/BuildDescription/PluginDescription.swift b/Sources/Build/BuildDescription/PluginDescription.swift index 03a8d62dc0b..8049d272274 100644 --- a/Sources/Build/BuildDescription/PluginDescription.swift +++ b/Sources/Build/BuildDescription/PluginDescription.swift @@ -21,29 +21,29 @@ import protocol Basics.FileSystem /// But because the package graph and build plan are not loaded for incremental /// builds, this information is included in the BuildDescription, and the plugin /// targets are compiled directly. -public final class PluginDescription: Codable { +package final class PluginDescription: Codable { /// The identity of the package in which the plugin is defined. - public let package: PackageIdentity + package let package: PackageIdentity /// The name of the plugin target in that package (this is also the name of /// the plugin). - public let targetName: String + package let targetName: String /// The names of any plugin products in that package that vend the plugin /// to other packages. - public let productNames: [String] + package let productNames: [String] /// The tools version of the package that declared the target. This affects /// the API that is available in the PackagePlugin module. - public let toolsVersion: ToolsVersion + package let toolsVersion: ToolsVersion /// Swift source files that comprise the plugin. - public let sources: Sources + package let sources: Sources /// Initialize a new plugin target description. The target is expected to be /// a `PluginTarget`. init( - target: ResolvedTarget, + target: ResolvedModule, products: [ResolvedProduct], package: ResolvedPackage, toolsVersion: ToolsVersion, diff --git a/Sources/Build/BuildDescription/ProductBuildDescription.swift b/Sources/Build/BuildDescription/ProductBuildDescription.swift index bb96cd383a2..1cb1018bc95 100644 --- a/Sources/Build/BuildDescription/ProductBuildDescription.swift +++ b/Sources/Build/BuildDescription/ProductBuildDescription.swift @@ -12,42 +12,48 @@ import Basics import PackageGraph + +@_spi(SwiftPMInternal) import PackageModel + import OrderedCollections import SPMBuildCore import struct TSCBasic.SortedArray /// The build description for a product. -public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription { +package final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription { /// The reference to the product. - public let package: ResolvedPackage + package let package: ResolvedPackage /// The reference to the product. - public let product: ResolvedProduct + package let product: ResolvedProduct /// The tools version of the package that declared the product. This can /// can be used to conditionalize semantically significant changes in how /// a target is built. - public let toolsVersion: ToolsVersion + package let toolsVersion: ToolsVersion /// The build parameters. - public let buildParameters: BuildParameters + package let buildParameters: BuildParameters /// All object files to link into this product. /// // Computed during build planning. - public internal(set) var objects = SortedArray() + package internal(set) var objects = SortedArray() /// The dynamic libraries this product needs to link with. // Computed during build planning. var dylibs: [ProductBuildDescription] = [] + /// The list of provided libraries that are going to be used by this product. + var providedLibraries: [String: AbsolutePath] = [:] + /// Any additional flags to be added. These flags are expected to be computed during build planning. var additionalFlags: [String] = [] /// The list of targets that are going to be linked statically in this product. - var staticTargets: [ResolvedTarget] = [] + var staticTargets: [ResolvedModule] = [] /// The list of Swift modules that should be passed to the linker. This is required for debugging to work. var swiftASTs: SortedArray = .init() @@ -119,15 +125,6 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription return ["-Xlinker", "-dead_strip"] } else if triple.isWindows() { return ["-Xlinker", "/OPT:REF"] - } else if triple.arch == .wasm32 { - // FIXME: wasm-ld strips data segments referenced through __start/__stop symbols - // during GC, and it removes Swift metadata sections like swift5_protocols - // We should add support of SHF_GNU_RETAIN-like flag for __attribute__((retain)) - // to LLVM and wasm-ld - // This workaround is required for not only WASI but also all WebAssembly triples - // using wasm-ld (e.g. wasm32-unknown-unknown). So this branch is conditioned by - // arch == .wasm32 - return [] } else { return ["-Xlinker", "--gc-sections"] } @@ -135,7 +132,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription } /// The arguments to the librarian to create a static library. - public func archiveArguments() throws -> [String] { + package func archiveArguments() throws -> [String] { let librarian = self.buildParameters.toolchain.librarianPath.pathString let triple = self.buildParameters.triple if triple.isWindows(), librarian.hasSuffix("link") || librarian.hasSuffix("link.exe") { @@ -148,7 +145,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription } /// The arguments to link and create this product. - public func linkArguments() throws -> [String] { + package func linkArguments() throws -> [String] { var args = [buildParameters.toolchain.swiftCompilerPath.pathString] args += self.buildParameters.sanitizers.linkSwiftFlags() args += self.additionalFlags @@ -163,6 +160,8 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += ["-F", self.buildParameters.buildPath.pathString] } + self.providedLibraries.forEach { args += ["-L", $1.pathString, "-l", $0] } + args += ["-L", self.buildParameters.buildPath.pathString] args += try ["-o", binaryPath.pathString] args += ["-module-name", self.product.name.spm_mangledToC99ExtendedIdentifier()] @@ -189,6 +188,12 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription var isLinkingStaticStdlib = false let triple = self.buildParameters.triple + + // radar://112671586 supress unnecessary warnings + if triple.isMacOSX { + args += ["-Xlinker", "-no_warn_duplicate_libraries"] + } + switch derivedProductType { case .macro: throw InternalError("macro not supported") // should never be reached @@ -198,7 +203,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // No arguments for static libraries. return [] case .test: - // Test products are bundle when using objectiveC, executable when using test entry point. + // Test products are bundle when using Objective-C, executable when using test entry point. switch self.buildParameters.testingParameters.testProductStyle { case .loadableBundle: args += ["-Xlinker", "-bundle"] @@ -271,8 +276,21 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription } args += ["@\(self.linkFileListPath.pathString)"] - // Embed the swift stdlib library path inside tests and executables on Darwin. if containsSwiftTargets { + // Pass experimental features to link jobs in addition to compile jobs. Preserve ordering while eliminating + // duplicates with `OrderedSet`. + var experimentalFeatures = OrderedSet() + for target in self.product.targets { + let swiftSettings = target.underlying.buildSettingsDescription.filter { $0.tool == .swift } + for case let .enableExperimentalFeature(feature) in swiftSettings.map(\.kind) { + experimentalFeatures.append(feature) + } + } + for feature in experimentalFeatures { + args += ["-enable-experimental-feature", feature] + } + + // Embed the swift stdlib library path inside tests and executables on Darwin. let useStdlibRpath: Bool switch self.product.type { case .library(let type): @@ -297,11 +315,9 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription args += ["-Xlinker", "-rpath", "-Xlinker", backDeployedStdlib.pathString] } } - } - - // Don't link runtime compatibility patch libraries if there are no - // Swift sources in the target. - if !containsSwiftTargets { + } else { + // Don't link runtime compatibility patch libraries if there are no + // Swift sources in the target. args += ["-runtime-compatibility-version", "none"] } @@ -311,7 +327,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // setting is the package-level right now. We might need to figure out a better // answer for libraries if/when we support specifying deployment target at the // target-level. - args += try self.buildParameters.targetTripleArgs(for: self.product.targets[self.product.targets.startIndex]) + args += try self.buildParameters.tripleArgs(for: self.product.targets[self.product.targets.startIndex]) // Add arguments from declared build settings. args += self.buildSettingsFlags @@ -346,7 +362,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription // Library search path for the toolchain's copy of SwiftSyntax. #if BUILD_MACROS_AS_DYLIBS if product.type == .macro { - args += try ["-L", buildParameters.toolchain.hostLibDir.pathString] + args += try ["-L", defaultBuildParameters.toolchain.hostLibDir.pathString] } #endif @@ -386,7 +402,7 @@ public final class ProductBuildDescription: SPMBuildCore.ProductBuildDescription } extension SortedArray where Element == AbsolutePath { - public static func +=(lhs: inout SortedArray, rhs: S) where S.Iterator.Element == AbsolutePath { + package static func +=(lhs: inout SortedArray, rhs: S) where S.Iterator.Element == AbsolutePath { lhs.insert(contentsOf: rhs) } } diff --git a/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift b/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift new file mode 100644 index 00000000000..59e5e2abacd --- /dev/null +++ b/Sources/Build/BuildDescription/ResolvedModule+BuildDescription.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2015-2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath +import struct PackageGraph.ResolvedModule + +import SPMBuildCore + +extension ResolvedModule { + func tempsPath(_ buildParameters: BuildParameters) -> AbsolutePath { + let suffix = buildParameters.suffix(triple: self.buildTriple) + return buildParameters.buildPath.appending(component: "\(self.c99name)\(suffix).build") + } +} diff --git a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift index a7216c99aa0..62b9a2c2c51 100644 --- a/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift @@ -11,10 +11,12 @@ //===----------------------------------------------------------------------===// import Basics + import Foundation import PackageGraph import PackageLoading import PackageModel + import SPMBuildCore #if USE_IMPL_ONLY_IMPORTS @@ -26,22 +28,25 @@ import DriverSupport import struct TSCBasic.ByteString /// Target description for a Swift target. -public final class SwiftTargetBuildDescription { +package final class SwiftTargetBuildDescription { /// The package this target belongs to. - public let package: ResolvedPackage + package let package: ResolvedPackage /// The target described by this target. - public let target: ResolvedTarget + package let target: ResolvedModule private let swiftTarget: SwiftTarget /// The tools version of the package that declared the target. This can /// can be used to conditionalize semantically significant changes in how /// a target is built. - public let toolsVersion: ToolsVersion + package let toolsVersion: ToolsVersion - /// The build parameters. - let buildParameters: BuildParameters + /// The build parameters for this target. + let defaultBuildParameters: BuildParameters + + /// The build parameters for build tools. + let toolsBuildParameters: BuildParameters /// Path to the temporary directory for this target. let tempsPath: AbsolutePath @@ -60,9 +65,10 @@ public final class SwiftTargetBuildDescription { /// Path to the bundle generated for this module (if any). var bundlePath: AbsolutePath? { if let bundleName = target.underlying.potentialBundleName, needsResourceBundle { - return self.buildParameters.bundlePath(named: bundleName) + let suffix = self.defaultBuildParameters.suffix(triple: self.target.buildTriple) + return self.defaultBuildParameters.bundlePath(named: bundleName + suffix) } else { - return .none + return nil } } @@ -75,27 +81,27 @@ public final class SwiftTargetBuildDescription { } /// The list of all source files in the target, including the derived ones. - public var sources: [AbsolutePath] { + package var sources: [AbsolutePath] { self.target.sources.paths + self.derivedSources.paths + self.pluginDerivedSources.paths } - public var sourcesFileListPath: AbsolutePath { + package var sourcesFileListPath: AbsolutePath { self.tempsPath.appending(component: "sources") } /// The list of all resource files in the target, including the derived ones. - public var resources: [Resource] { + package var resources: [Resource] { self.target.underlying.resources + self.pluginDerivedResources } /// The objects in this target, containing either machine code or bitcode /// depending on the build parameters used. - public var objects: [AbsolutePath] { + package var objects: [AbsolutePath] { get throws { let relativeSources = self.target.sources.relativePaths + self.derivedSources.relativePaths + self.pluginDerivedSources.relativePaths - let ltoEnabled = self.buildParameters.linkingParameters.linkTimeOptimizationMode != nil + let ltoEnabled = self.defaultBuildParameters.linkingParameters.linkTimeOptimizationMode != nil let objectFileExtension = ltoEnabled ? "bc" : "o" return try relativeSources.map { try AbsolutePath( @@ -106,16 +112,17 @@ public final class SwiftTargetBuildDescription { } var modulesPath: AbsolutePath { - return self.buildParameters.buildPath.appending(component: "Modules") + let suffix = self.defaultBuildParameters.suffix(triple: self.target.buildTriple) + return self.defaultBuildParameters.buildPath.appending(component: "Modules\(suffix)") } /// The path to the swiftmodule file after compilation. - public var moduleOutputPath: AbsolutePath { // note: needs to be public because of sourcekit-lsp + public var moduleOutputPath: AbsolutePath { // note: needs to be `public` because of sourcekit-lsp // If we're an executable and we're not allowing test targets to link against us, we hide the module. - let triple = buildParameters.triple + let triple = defaultBuildParameters.triple let allowLinkingAgainstExecutables = (triple.isDarwin() || triple.isLinux() || triple.isWindows()) && self.toolsVersion >= .v5_5 let dirPath = (target.type == .executable && !allowLinkingAgainstExecutables) ? self.tempsPath : self.modulesPath - return dirPath.appending(component: self.target.c99name + ".swiftmodule") + return dirPath.appending(component: "\(self.target.c99name).swiftmodule") } /// The path to the wrapped swift module which is created using the modulewrap tool. This is required @@ -125,13 +132,13 @@ public final class SwiftTargetBuildDescription { self.tempsPath.appending(component: self.target.c99name + ".swiftmodule.o") } - /// The path to the swifinterface file after compilation. + /// The path to the swiftinterface file after compilation. var parseableModuleInterfaceOutputPath: AbsolutePath { self.modulesPath.appending(component: self.target.c99name + ".swiftinterface") } /// Path to the resource Info.plist file, if generated. - public private(set) var resourceBundleInfoPlistPath: AbsolutePath? + package private(set) var resourceBundleInfoPlistPath: AbsolutePath? /// Paths to the binary libraries the target depends on. var libraryBinaryPaths: Set = [] @@ -139,14 +146,9 @@ public final class SwiftTargetBuildDescription { /// Any addition flags to be added. These flags are expected to be computed during build planning. var additionalFlags: [String] = [] - /// The swift version for this target. - var swiftVersion: SwiftLanguageVersion { - self.swiftTarget.swiftVersion - } - /// Describes the purpose of a test target, including any special roles such as containing a list of discovered /// tests or serving as the manifest target which contains the main entry point. - public enum TestTargetRole { + package enum TestTargetRole { /// An ordinary test target, defined explicitly in a package, containing test code. case `default` @@ -161,10 +163,10 @@ public final class SwiftTargetBuildDescription { case entryPoint(isSynthesized: Bool) } - public let testTargetRole: TestTargetRole? + package let testTargetRole: TestTargetRole? /// If this target is a test target. - public var isTestTarget: Bool { + package var isTestTarget: Bool { self.testTargetRole != nil } @@ -226,13 +228,13 @@ public final class SwiftTargetBuildDescription { private(set) var moduleMap: AbsolutePath? /// The results of applying any build tool plugins to this target. - public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] + package let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] /// The results of running any prebuild commands for this target. - public let prebuildCommandResults: [PrebuildCommandResult] + package let prebuildCommandResults: [PrebuildCommandResult] /// Any macro products that this target requires to build. - public let requiredMacroProducts: [ResolvedProduct] + package let requiredMacroProducts: [ResolvedProduct] /// ObservabilityScope with which to emit diagnostics private let observabilityScope: ObservabilityScope @@ -241,21 +243,22 @@ public final class SwiftTargetBuildDescription { private let shouldGenerateTestObservation: Bool /// Whether to disable sandboxing (e.g. for macros). - private let disableSandbox: Bool + private let shouldDisableSandbox: Bool /// Create a new target description with target and build parameters. init( package: ResolvedPackage, - target: ResolvedTarget, + target: ResolvedModule, toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription] = [], - buildParameters: BuildParameters, + destinationBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [], prebuildCommandResults: [PrebuildCommandResult] = [], requiredMacroProducts: [ResolvedProduct] = [], testTargetRole: TestTargetRole? = nil, shouldGenerateTestObservation: Bool = false, - disableSandbox: Bool, + shouldDisableSandbox: Bool, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { @@ -267,7 +270,9 @@ public final class SwiftTargetBuildDescription { self.package = package self.target = target self.toolsVersion = toolsVersion - self.buildParameters = buildParameters + self.defaultBuildParameters = destinationBuildParameters + self.toolsBuildParameters = toolsBuildParameters + // Unless mentioned explicitly, use the target type to determine if this is a test target. if let testTargetRole { self.testTargetRole = testTargetRole @@ -277,21 +282,21 @@ public final class SwiftTargetBuildDescription { self.testTargetRole = nil } - self.tempsPath = buildParameters.buildPath.appending(component: target.c99name + ".build") + self.tempsPath = target.tempsPath(destinationBuildParameters) self.derivedSources = Sources(paths: [], root: self.tempsPath.appending("DerivedSources")) self.buildToolPluginInvocationResults = buildToolPluginInvocationResults self.prebuildCommandResults = prebuildCommandResults self.requiredMacroProducts = requiredMacroProducts self.shouldGenerateTestObservation = shouldGenerateTestObservation - self.disableSandbox = disableSandbox + self.shouldDisableSandbox = shouldDisableSandbox self.fileSystem = fileSystem self.observabilityScope = observabilityScope - (self.pluginDerivedSources, self.pluginDerivedResources) = PackageGraph.computePluginGeneratedFiles( + (self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles( target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, - buildParameters: buildParameters, + buildParameters: destinationBuildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults, prebuildCommandResults: prebuildCommandResults, observabilityScope: observabilityScope @@ -328,18 +333,22 @@ public final class SwiftTargetBuildDescription { return } - guard self.buildParameters.triple.isDarwin(), self.buildParameters.testingParameters.experimentalTestOutput else { + guard + self.defaultBuildParameters.triple.isDarwin() && + self.defaultBuildParameters.testingParameters.experimentalTestOutput + else { return } - let content = generateTestObservationCode(buildParameters: self.buildParameters) + let content = generateTestObservationCode(buildParameters: self.defaultBuildParameters) // FIXME: We should generate this file during the actual build. self.derivedSources.relativePaths.append(subpath) try self.fileSystem.writeIfChanged(path: path, string: content) } - // FIXME: This will not work well for large files, as we will store the entire contents, plus its byte array representation in memory and also `writeIfChanged()` will read the entire generated file again. + // FIXME: This will not work well for large files, as we will store the entire contents, plus its byte array + // representation in memory and also `writeIfChanged()` will read the entire generated file again. private func generateResourceEmbeddingCode() throws { guard needsResourceEmbedding else { return } @@ -372,7 +381,7 @@ public final class SwiftTargetBuildDescription { guard let bundlePath else { return } let mainPathSubstitution: String - if self.buildParameters.triple.isWASI() { + if self.defaultBuildParameters.triple.isWASI() { // We prefer compile-time evaluation of the bundle path here for WASI. There's no benefit in evaluating this // at runtime, especially as `Bundle` support in WASI Foundation is partial. We expect all resource paths to // evaluate to `/\(resourceBundleName)/\(resourcePath)`, which allows us to pass this path to JS APIs like @@ -391,6 +400,11 @@ public final class SwiftTargetBuildDescription { """ import Foundation + #if compiler(>=6.0) + extension Foundation.Bundle: @unchecked @retroactive Sendable {} + #else + extension Foundation.Bundle: @unchecked Sendable {} + #endif extension Foundation.Bundle { static let module: Bundle = { let mainPath = \(mainPathSubstitution) @@ -419,29 +433,17 @@ public final class SwiftTargetBuildDescription { try self.fileSystem.writeIfChanged(path: path, string: content) } - private func packageNameArgumentIfSupported(with pkg: ResolvedPackage, packageAccess: Bool) -> [String] { - let flag = "-package-name" - if pkg.manifest.usePackageNameFlag, - DriverSupport.checkToolchainDriverFlags(flags: [flag], toolchain: self.buildParameters.toolchain, fileSystem: self.fileSystem) { - if packageAccess { - let pkgID = pkg.identity.description.spm_mangledToC99ExtendedIdentifier() - return [flag, pkgID] - } - } - return [] - } - private func macroArguments() throws -> [String] { var args = [String]() #if BUILD_MACROS_AS_DYLIBS self.requiredMacroProducts.forEach { macro in - args += ["-Xfrontend", "-load-plugin-library", "-Xfrontend", self.buildParameters.binaryPath(for: macro).pathString] + args += ["-Xfrontend", "-load-plugin-library", "-Xfrontend", self.toolsBuildParameters.binaryPath(for: macro).pathString] } #else try self.requiredMacroProducts.forEach { macro in if let macroTarget = macro.targets.first { - let executablePath = try self.buildParameters.binaryPath(for: macro).pathString + let executablePath = try self.toolsBuildParameters.binaryPath(for: macro).pathString args += ["-Xfrontend", "-load-plugin-executable", "-Xfrontend", "\(executablePath)#\(macroTarget.c99name)"] } else { throw InternalError("macro product \(macro.name) has no targets") // earlier validation should normally catch this @@ -449,20 +451,12 @@ public final class SwiftTargetBuildDescription { } #endif - // If we're using an OSS toolchain, add the required arguments bringing in the plugin server from the default toolchain if available. - if self.buildParameters.toolchain.isSwiftDevelopmentToolchain, DriverSupport.checkSupportedFrontendFlags(flags: ["-external-plugin-path"], toolchain: self.buildParameters.toolchain, fileSystem: self.fileSystem), let pluginServer = try self.buildParameters.toolchain.swiftPluginServerPath { - let toolchainUsrPath = pluginServer.parentDirectory.parentDirectory - let pluginPathComponents = ["lib", "swift", "host", "plugins"] - - let pluginPath = toolchainUsrPath.appending(components: pluginPathComponents) - args += ["-Xfrontend", "-external-plugin-path", "-Xfrontend", "\(pluginPath)#\(pluginServer.pathString)"] - - let localPluginPath = toolchainUsrPath.appending(components: ["local"] + pluginPathComponents) - args += ["-Xfrontend", "-external-plugin-path", "-Xfrontend", "\(localPluginPath)#\(pluginServer.pathString)"] - } - - if self.disableSandbox { - let toolchainSupportsDisablingSandbox = DriverSupport.checkSupportedFrontendFlags(flags: ["-disable-sandbox"], toolchain: self.buildParameters.toolchain, fileSystem: fileSystem) + if self.shouldDisableSandbox { + let toolchainSupportsDisablingSandbox = DriverSupport.checkSupportedFrontendFlags( + flags: ["-disable-sandbox"], + toolchain: self.defaultBuildParameters.toolchain, + fileSystem: fileSystem + ) if toolchainSupportsDisablingSandbox { args += ["-disable-sandbox"] } else { @@ -477,13 +471,12 @@ public final class SwiftTargetBuildDescription { } /// The arguments needed to compile this target. - public func compileArguments() throws -> [String] { + package func compileArguments() throws -> [String] { var args = [String]() - args += try self.buildParameters.targetTripleArgs(for: self.target) - args += ["-swift-version", self.swiftVersion.rawValue] + args += try self.defaultBuildParameters.tripleArgs(for: self.target) // pass `-v` during verbose builds. - if self.buildParameters.outputParameters.isVerbose { + if self.defaultBuildParameters.outputParameters.isVerbose { args += ["-v"] } @@ -491,22 +484,22 @@ public final class SwiftTargetBuildDescription { // // Technically, it should be enabled whenever WMO is off but we // don't currently make that distinction in SwiftPM - switch self.buildParameters.configuration { + switch self.defaultBuildParameters.configuration { case .debug: args += ["-enable-batch-mode"] case .release: break } - args += self.buildParameters.indexStoreArguments(for: self.target) + args += self.defaultBuildParameters.indexStoreArguments(for: self.target) args += self.optimizationArguments args += self.testingArguments - args += ["-j\(self.buildParameters.workers)"] + args += ["-j\(self.defaultBuildParameters.workers)"] args += self.activeCompilationConditions args += self.additionalFlags args += try self.moduleCacheArgs args += self.stdlibArguments - args += self.buildParameters.sanitizers.compileSwiftFlags() + args += self.defaultBuildParameters.sanitizers.compileSwiftFlags() args += ["-parseable-output"] // If we're compiling the main module of an executable other than the one that @@ -526,8 +519,8 @@ public final class SwiftTargetBuildDescription { // we can rename the symbol unconditionally. // No `-` for these flags because the set of Strings in driver.supportedFrontendFlags do // not have a leading `-` - if self.buildParameters.driverParameters.canRenameEntrypointFunctionName, - self.buildParameters.linkerFlagsForRenamingMainFunction(of: self.target) != nil + if self.defaultBuildParameters.driverParameters.canRenameEntrypointFunctionName, + self.defaultBuildParameters.linkerFlagsForRenamingMainFunction(of: self.target) != nil { args += ["-Xfrontend", "-entry-point-function-name", "-Xfrontend", "\(self.target.c99name)_main"] } @@ -540,7 +533,7 @@ public final class SwiftTargetBuildDescription { // Only add the build path to the framework search path if there are binary frameworks to link against. if !self.libraryBinaryPaths.isEmpty { - args += ["-F", self.buildParameters.buildPath.pathString] + args += ["-F", self.defaultBuildParameters.buildPath.pathString] } // Emit the ObjC compatibility header if enabled. @@ -549,22 +542,22 @@ public final class SwiftTargetBuildDescription { } // Add arguments needed for code coverage if it is enabled. - if self.buildParameters.testingParameters.enableCodeCoverage { + if self.defaultBuildParameters.testingParameters.enableCodeCoverage { args += ["-profile-coverage-mapping", "-profile-generate"] } // Add arguments to colorize output if stdout is tty - if self.buildParameters.outputParameters.isColorized { + if self.defaultBuildParameters.outputParameters.isColorized { args += ["-color-diagnostics"] } - // If this is a generated test discovery target, it might import a test + // If this is a generated test discovery target or a test entry point, it might import a test // target that is built with C++ interop enabled. In that case, the test // discovery target must enable C++ interop as well switch testTargetRole { - case .discovery: + case .discovery, .entryPoint: for dependency in try self.target.recursiveTargetDependencies() { - let dependencyScope = self.buildParameters.createScope(for: dependency) + let dependencyScope = self.defaultBuildParameters.createScope(for: dependency) let dependencySwiftFlags = dependencyScope.evaluate(.OTHER_SWIFT_FLAGS) if let interopModeFlag = dependencySwiftFlags.first(where: { $0.hasPrefix("-cxx-interoperability-mode=") }) { args += [interopModeFlag] @@ -584,17 +577,17 @@ public final class SwiftTargetBuildDescription { // Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other // way. - if self.buildParameters.driverParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { + if self.defaultBuildParameters.driverParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") { args += ["-emit-module-interface-path", self.parseableModuleInterfaceOutputPath.pathString] } - args += self.buildParameters.toolchain.extraFlags.swiftCompilerFlags + args += self.defaultBuildParameters.toolchain.extraFlags.swiftCompilerFlags // User arguments (from -Xswiftc) should follow generated arguments to allow user overrides - args += self.buildParameters.flags.swiftCompilerFlags + args += self.defaultBuildParameters.flags.swiftCompilerFlags - args += self.buildParameters.toolchain.extraFlags.cCompilerFlags.asSwiftcCCompilerFlags() + args += self.defaultBuildParameters.toolchain.extraFlags.cCompilerFlags.asSwiftcCCompilerFlags() // User arguments (from -Xcc) should follow generated arguments to allow user overrides - args += self.buildParameters.flags.cCompilerFlags.asSwiftcCCompilerFlags() + args += self.defaultBuildParameters.flags.cCompilerFlags.asSwiftcCCompilerFlags() // TODO: Pass -Xcxx flags to swiftc (#6491) // Uncomment when downstream support arrives. @@ -603,7 +596,7 @@ public final class SwiftTargetBuildDescription { // args += self.buildParameters.flags.cxxCompilerFlags.asSwiftcCXXCompilerFlags() // Enable the correct LTO mode if requested. - switch self.buildParameters.linkingParameters.linkTimeOptimizationMode { + switch self.defaultBuildParameters.linkingParameters.linkTimeOptimizationMode { case nil: break case .full: @@ -613,7 +606,7 @@ public final class SwiftTargetBuildDescription { } // Pass default include paths from the toolchain. - for includeSearchPath in self.buildParameters.toolchain.includeSearchPaths { + for includeSearchPath in self.defaultBuildParameters.toolchain.includeSearchPaths { args += ["-I", includeSearchPath.pathString] } @@ -631,13 +624,16 @@ public final class SwiftTargetBuildDescription { args += ["-user-module-version", version.description] } - args += self.packageNameArgumentIfSupported(with: self.package, packageAccess: self.target.packageAccess) + args += self.package.packageNameArgument( + target: self.target, + isPackageNameSupported: self.defaultBuildParameters.driverParameters.isPackageAccessModifierSupported + ) args += try self.macroArguments() // rdar://117578677 // Pass -fno-omit-frame-pointer to support backtraces // this can be removed once the backtracer uses DWARF instead of frame pointers - if let omitFramePointers = self.buildParameters.debuggingParameters.omitFramePointers { + if let omitFramePointers = self.defaultBuildParameters.debuggingParameters.omitFramePointers { if omitFramePointers { args += ["-Xcc", "-fomit-frame-pointer"] } else { @@ -650,13 +646,18 @@ public final class SwiftTargetBuildDescription { /// When `scanInvocation` argument is set to `true`, omit the side-effect producing arguments /// such as emitting a module or supplementary outputs. - public func emitCommandLine(scanInvocation: Bool = false) throws -> [String] { + package func emitCommandLine(scanInvocation: Bool = false) throws -> [String] { var result: [String] = [] - result.append(self.buildParameters.toolchain.swiftCompilerPath.pathString) + result.append(self.defaultBuildParameters.toolchain.swiftCompilerPath.pathString) result.append("-module-name") result.append(self.target.c99name) - result.append(contentsOf: packageNameArgumentIfSupported(with: self.package, packageAccess: self.target.packageAccess)) + result.append( + contentsOf: self.package.packageNameArgument( + target: self.target, + isPackageNameSupported: self.defaultBuildParameters.driverParameters.isPackageAccessModifierSupported + ) + ) if !scanInvocation { result.append("-emit-dependencies") @@ -670,7 +671,7 @@ public final class SwiftTargetBuildDescription { result.append(try self.writeOutputFileMap().pathString) } - if self.buildParameters.useWholeModuleOptimization { + if self.defaultBuildParameters.useWholeModuleOptimization { result.append("-whole-module-optimization") result.append("-num-threads") result.append(String(ProcessInfo.processInfo.activeProcessorCount)) @@ -690,7 +691,7 @@ public final class SwiftTargetBuildDescription { /// Returns true if ObjC compatibility header should be emitted. private var shouldEmitObjCCompatibilityHeader: Bool { - self.buildParameters.triple.isDarwin() && self.target.type == .library + self.defaultBuildParameters.triple.isDarwin() && self.target.type == .library } func writeOutputFileMap() throws -> AbsolutePath { @@ -704,7 +705,7 @@ public final class SwiftTargetBuildDescription { """# - if self.buildParameters.useWholeModuleOptimization { + if self.defaultBuildParameters.useWholeModuleOptimization { let moduleName = self.target.c99name content += #""" @@ -735,7 +736,7 @@ public final class SwiftTargetBuildDescription { // Write out the entries for each source file. let sources = self.sources let objects = try self.objects - let ltoEnabled = self.buildParameters.linkingParameters.linkTimeOptimizationMode != nil + let ltoEnabled = self.defaultBuildParameters.linkingParameters.linkTimeOptimizationMode != nil let objectKey = ltoEnabled ? "llvm-bc" : "object" for idx in 0.. [String] { - let scope = self.buildParameters.createScope(for: self.target) + let scope = self.defaultBuildParameters.createScope(for: self.target) var flags: [String] = [] + // A custom swift version. + flags += scope.evaluate(.SWIFT_VERSION).flatMap { ["-swift-version", $0] } + // Swift defines. let swiftDefines = scope.evaluate(.SWIFT_ACTIVE_COMPILATION_CONDITIONS) flags += swiftDefines.map { "-D" + $0 } @@ -838,7 +843,7 @@ public final class SwiftTargetBuildDescription { // Include path for the toolchain's copy of SwiftSyntax. #if BUILD_MACROS_AS_DYLIBS if target.type == .macro { - flags += try ["-I", self.buildParameters.toolchain.hostLibDir.pathString] + flags += try ["-I", self.defaultBuildParameters.toolchain.hostLibDir.pathString] } #endif @@ -849,7 +854,7 @@ public final class SwiftTargetBuildDescription { private var activeCompilationConditions: [String] { var compilationConditions = ["-DSWIFT_PACKAGE"] - switch self.buildParameters.configuration { + switch self.defaultBuildParameters.configuration { case .debug: compilationConditions += ["-DDEBUG"] case .release: @@ -861,7 +866,7 @@ public final class SwiftTargetBuildDescription { /// Optimization arguments according to the build configuration. private var optimizationArguments: [String] { - switch self.buildParameters.configuration { + switch self.defaultBuildParameters.configuration { case .debug: return ["-Onone"] case .release: @@ -875,7 +880,7 @@ public final class SwiftTargetBuildDescription { // test targets must be built with -enable-testing // since its required for test discovery (the non objective-c reflection kind) return ["-enable-testing"] - } else if self.buildParameters.testingParameters.enableTestability { + } else if self.defaultBuildParameters.testingParameters.enableTestability { return ["-enable-testing"] } else { return [] @@ -885,20 +890,20 @@ public final class SwiftTargetBuildDescription { /// Module cache arguments. private var moduleCacheArgs: [String] { get throws { - ["-module-cache-path", try self.buildParameters.moduleCache.pathString] + ["-module-cache-path", try self.defaultBuildParameters.moduleCache.pathString] } } private var stdlibArguments: [String] { var arguments: [String] = [] - let isLinkingStaticStdlib = self.buildParameters.linkingParameters.shouldLinkStaticSwiftStdlib - && self.buildParameters.triple.isSupportingStaticStdlib + let isLinkingStaticStdlib = self.defaultBuildParameters.linkingParameters.shouldLinkStaticSwiftStdlib + && self.defaultBuildParameters.triple.isSupportingStaticStdlib if isLinkingStaticStdlib { arguments += ["-static-stdlib"] } - if let resourcesPath = self.buildParameters.toolchain.swiftResourcesPath(isStatic: isLinkingStaticStdlib) { + if let resourcesPath = self.defaultBuildParameters.toolchain.swiftResourcesPath(isStatic: isLinkingStaticStdlib) { arguments += ["-resource-dir", "\(resourcesPath)"] } diff --git a/Sources/Build/BuildDescription/TargetBuildDescription.swift b/Sources/Build/BuildDescription/TargetBuildDescription.swift index 4fae9198680..b4d578947f2 100644 --- a/Sources/Build/BuildDescription/TargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/TargetBuildDescription.swift @@ -11,18 +11,18 @@ //===----------------------------------------------------------------------===// import Basics -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedModule import struct PackageModel.Resource import struct PackageModel.ToolsVersion import struct SPMBuildCore.BuildToolPluginInvocationResult import struct SPMBuildCore.BuildParameters -public enum BuildDescriptionError: Swift.Error { +package enum BuildDescriptionError: Swift.Error { case requestedFileNotPartOfTarget(targetName: String, requestedFilePath: AbsolutePath) } /// A target description which can either be for a Swift or Clang target. -public enum TargetBuildDescription { +package enum TargetBuildDescription { /// Swift target description. case swift(SwiftTargetBuildDescription) @@ -61,7 +61,7 @@ public enum TargetBuildDescription { } } - var target: ResolvedTarget { + var target: ResolvedModule { switch self { case .swift(let target): return target.target @@ -101,7 +101,7 @@ public enum TargetBuildDescription { var buildParameters: BuildParameters { switch self { case .swift(let swiftTargetBuildDescription): - return swiftTargetBuildDescription.buildParameters + return swiftTargetBuildDescription.defaultBuildParameters case .clang(let clangTargetBuildDescription): return clangTargetBuildDescription.buildParameters } diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift index 16f79dc0f35..e6ddfc1a4fb 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift @@ -14,7 +14,7 @@ import struct LLBuildManifest.Node import struct Basics.AbsolutePath import struct Basics.InternalError import class Basics.ObservabilityScope -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedModule import PackageModel extension LLBuildManifestBuilder { @@ -32,7 +32,7 @@ extension LLBuildManifestBuilder { inputs.append(resourcesNode) } - func addStaticTargetInputs(_ target: ResolvedTarget) { + func addStaticTargetInputs(_ target: ResolvedModule) { if case .swift(let desc)? = self.plan.targetMap[target.id], target.type == .library { inputs.append(file: desc.moduleOutputPath) } @@ -93,7 +93,7 @@ extension LLBuildManifestBuilder { let additionalInputs = try addBuildToolPlugins(.clang(target)) // Create a phony node to represent the entire target. - let targetName = target.target.getLLBuildTargetName(config: target.buildParameters.buildConfig) + let targetName = target.target.getLLBuildTargetName(buildParameters: target.buildParameters) let output: Node = .virtual(targetName) self.manifest.addNode(output, toTarget: targetName) diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift index 3061b25ee7b..cf8a797ed9c 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift @@ -15,7 +15,7 @@ import struct LLBuildManifest.Node extension LLBuildManifestBuilder { func createProductCommand(_ buildProduct: ProductBuildDescription) throws { - let cmdName = try buildProduct.product.getCommandName(config: buildProduct.buildParameters.buildConfig) + let cmdName = try buildProduct.product.getCommandName(buildParameters: buildProduct.buildParameters) // Add dependency on Info.plist generation on Darwin platforms. let testInputs: [AbsolutePath] @@ -34,7 +34,7 @@ extension LLBuildManifestBuilder { } // Create a phony node to represent the entire target. - let targetName = try buildProduct.product.getLLBuildTargetName(config: buildProduct.buildParameters.buildConfig) + let targetName = try buildProduct.product.getLLBuildTargetName(buildParameters: buildProduct.buildParameters) let output: Node = .virtual(targetName) let finalProductNode: Node @@ -85,7 +85,7 @@ extension LLBuildManifestBuilder { outputPath: plistPath ) - let cmdName = try buildProduct.product.getCommandName(config: buildProduct.buildParameters.buildConfig) + let cmdName = try buildProduct.product.getCommandName(buildParameters: buildProduct.buildParameters) let codeSigningOutput = Node.virtual(targetName + "-CodeSigning") try self.manifest.addShellCmd( name: "\(cmdName)-entitlements", diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift index 599c7c435d5..df561d46b8b 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift @@ -45,7 +45,7 @@ extension LLBuildManifestBuilder { outputs.append(output) } - let cmdName = target.target.getLLBuildResourcesCmdName(config: target.buildParameters.buildConfig) + let cmdName = target.target.getLLBuildResourcesCmdName(buildParameters: target.buildParameters) self.manifest.addPhonyCmd(name: cmdName, inputs: outputs, outputs: [.virtual(cmdName)]) return .virtual(cmdName) diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift index a51bf24d954..e2ea4abc021 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift @@ -17,7 +17,7 @@ import struct Basics.TSCAbsolutePath import struct LLBuildManifest.Node import struct LLBuildManifest.LLBuildManifest import struct SPMBuildCore.BuildParameters -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedModule import protocol TSCBasic.FileSystem import enum TSCBasic.ProcessEnv import func TSCBasic.topologicalSort @@ -45,7 +45,7 @@ extension LLBuildManifestBuilder { let moduleNode = Node.file(target.moduleOutputPath) let cmdOutputs = objectNodes + [moduleNode] - if target.buildParameters.driverParameters.useIntegratedSwiftDriver { + if target.defaultBuildParameters.driverParameters.useIntegratedSwiftDriver { try self.addSwiftCmdsViaIntegratedDriver( target, inputs: inputs, @@ -68,7 +68,7 @@ extension LLBuildManifestBuilder { // jobs needed to build this Swift target. var commandLine = try target.emitCommandLine() commandLine.append("-driver-use-frontend-path") - commandLine.append(target.buildParameters.toolchain.swiftCompilerPath.pathString) + commandLine.append(target.defaultBuildParameters.toolchain.swiftCompilerPath.pathString) // FIXME: At some point SwiftPM should provide its own executor for // running jobs/launching processes during planning let resolver = try ArgsResolver(fileSystem: target.fileSystem) @@ -132,7 +132,7 @@ extension LLBuildManifestBuilder { // common intermediate dependency modules, such dependencies can lead // to cycles in the resulting manifest. var manifestNodeInputs: [Node] = [] - if targetDescription.buildParameters.driverParameters.useExplicitModuleBuild && !isMainModule(job) { + if targetDescription.defaultBuildParameters.driverParameters.useExplicitModuleBuild && !isMainModule(job) { manifestNodeInputs = jobInputs } else { manifestNodeInputs = (inputs + jobInputs).uniqued() @@ -188,12 +188,14 @@ extension LLBuildManifestBuilder { // dependency graph of B. The driver is then responsible for the necessary post-processing // to merge the dependency graphs and plan the build for A, using artifacts of B as explicit // inputs. - public func addTargetsToExplicitBuildManifest() throws { + package func addTargetsToExplicitBuildManifest() throws { // Sort the product targets in topological order in order to collect and "bubble up" // their respective dependency graphs to the depending targets. - let nodes: [ResolvedTarget.Dependency] = try self.plan.targetMap.keys.compactMap { - guard let target = self.plan.graph.allTargets[$0] else { throw InternalError("unknown target \($0)") } - return ResolvedTarget.Dependency.target(target, conditions: []) + let nodes: [ResolvedModule.Dependency] = try self.plan.targetMap.keys.compactMap { + guard let target = self.plan.graph.allTargets[$0] else { + throw InternalError("unknown target \($0)") + } + return ResolvedModule.Dependency.target(target, conditions: []) } let allPackageDependencies = try topologicalSort(nodes, successors: { $0.dependencies }) // Instantiate the inter-module dependency oracle which will cache commonly-scanned @@ -285,7 +287,7 @@ extension LLBuildManifestBuilder { // jobs needed to build this Swift target. var commandLine = try targetDescription.emitCommandLine() commandLine.append("-driver-use-frontend-path") - commandLine.append(targetDescription.buildParameters.toolchain.swiftCompilerPath.pathString) + commandLine.append(targetDescription.defaultBuildParameters.toolchain.swiftCompilerPath.pathString) commandLine.append("-experimental-explicit-module-build") let resolver = try ArgsResolver(fileSystem: self.fileSystem) let executor = SPMSwiftDriverExecutor( @@ -376,14 +378,14 @@ extension LLBuildManifestBuilder { cmdOutputs: [Node] ) throws { let isLibrary = target.target.type == .library || target.target.type == .test - let cmdName = target.target.getCommandName(config: target.buildParameters.buildConfig) + let cmdName = target.target.getCommandName(buildParameters: target.defaultBuildParameters) self.manifest.addWriteSourcesFileListCommand(sources: target.sources, sourcesFileListPath: target.sourcesFileListPath) self.manifest.addSwiftCmd( name: cmdName, inputs: inputs + [Node.file(target.sourcesFileListPath)], outputs: cmdOutputs, - executable: target.buildParameters.toolchain.swiftCompilerPath, + executable: target.defaultBuildParameters.toolchain.swiftCompilerPath, moduleName: target.target.c99name, moduleAliases: target.target.moduleAliases, moduleOutputPath: target.moduleOutputPath, @@ -394,7 +396,7 @@ extension LLBuildManifestBuilder { sources: target.sources, fileList: target.sourcesFileListPath, isLibrary: isLibrary, - wholeModuleOptimization: target.buildParameters.configuration == .release, + wholeModuleOptimization: target.defaultBuildParameters.configuration == .release, outputFileMapPath: try target.writeOutputFileMap() // FIXME: Eliminate side effect. ) } @@ -404,7 +406,7 @@ extension LLBuildManifestBuilder { ) throws -> [Node] { var inputs = target.sources.map(Node.file) - let swiftVersionFilePath = addSwiftGetVersionCommand(buildParameters: target.buildParameters) + let swiftVersionFilePath = addSwiftGetVersionCommand(buildParameters: target.defaultBuildParameters) inputs.append(.file(swiftVersionFilePath)) // Add resources node as the input to the target. This isn't great because we @@ -415,13 +417,15 @@ extension LLBuildManifestBuilder { inputs.append(resourcesNode) } - func addStaticTargetInputs(_ target: ResolvedTarget) throws { + func addStaticTargetInputs(_ target: ResolvedModule) throws { // Ignore C Modules. if target.underlying is SystemLibraryTarget { return } // Ignore Binary Modules. if target.underlying is BinaryTarget { return } // Ignore Plugin Targets. if target.underlying is PluginTarget { return } + // Ignore Provided Libraries. + if target.underlying is ProvidedLibraryTarget { return } // Depend on the binary for executable targets. if target.type == .executable { @@ -450,7 +454,7 @@ extension LLBuildManifestBuilder { } } - for dependency in target.target.dependencies(satisfying: target.buildParameters.buildEnvironment) { + for dependency in target.target.dependencies(satisfying: target.defaultBuildParameters.buildEnvironment) { switch dependency { case .target(let target, _): try addStaticTargetInputs(target) @@ -477,7 +481,7 @@ extension LLBuildManifestBuilder { } for binaryPath in target.libraryBinaryPaths { - let path = target.buildParameters.destinationPath(forBinaryAt: binaryPath) + let path = target.defaultBuildParameters.destinationPath(forBinaryAt: binaryPath) if self.fileSystem.isDirectory(binaryPath) { inputs.append(directory: path) } else { @@ -489,7 +493,7 @@ extension LLBuildManifestBuilder { // Depend on any required macro product's output. try target.requiredMacroProducts.forEach { macro in - try inputs.append(.virtual(macro.getLLBuildTargetName(config: target.buildParameters.buildConfig))) + try inputs.append(.virtual(macro.getLLBuildTargetName(buildParameters: target.defaultBuildParameters))) } return inputs + additionalInputs @@ -498,7 +502,7 @@ extension LLBuildManifestBuilder { /// Adds a top-level phony command that builds the entire target. private func addTargetCmd(_ target: SwiftTargetBuildDescription, cmdOutputs: [Node]) { // Create a phony node to represent the entire target. - let targetName = target.target.getLLBuildTargetName(config: target.buildParameters.buildConfig) + let targetName = target.target.getLLBuildTargetName(buildParameters: target.defaultBuildParameters) let targetOutput: Node = .virtual(targetName) self.manifest.addNode(targetOutput, toTarget: targetName) @@ -507,7 +511,7 @@ extension LLBuildManifestBuilder { inputs: cmdOutputs, outputs: [targetOutput] ) - if self.plan.graph.isInRootPackages(target.target, satisfying: target.buildParameters.buildEnvironment) { + if self.plan.graph.isInRootPackages(target.target, satisfying: target.defaultBuildParameters.buildEnvironment) { if !target.isTestTarget { self.addNode(targetOutput, toTarget: .main) } @@ -517,13 +521,13 @@ extension LLBuildManifestBuilder { private func addModuleWrapCmd(_ target: SwiftTargetBuildDescription) throws { // Add commands to perform the module wrapping Swift modules when debugging strategy is `modulewrap`. - guard target.buildParameters.debuggingStrategy == .modulewrap else { return } + guard target.defaultBuildParameters.debuggingStrategy == .modulewrap else { return } var moduleWrapArgs = [ - target.buildParameters.toolchain.swiftCompilerPath.pathString, + target.defaultBuildParameters.toolchain.swiftCompilerPath.pathString, "-modulewrap", target.moduleOutputPath.pathString, "-o", target.wrappedModuleOutputPath.pathString, ] - moduleWrapArgs += try target.buildParameters.targetTripleArgs(for: target.target) + moduleWrapArgs += try target.defaultBuildParameters.tripleArgs(for: target.target) self.manifest.addShellCmd( name: target.wrappedModuleOutputPath.pathString, description: "Wrapping AST for \(target.target.name) for debugging", diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift index 4d9b6d62787..edf8415c123 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder.swift @@ -27,7 +27,7 @@ import enum TSCBasic.ProcessEnv import func TSCBasic.topologicalSort /// High-level interface to ``LLBuildManifest`` and ``LLBuildManifestWriter``. -public class LLBuildManifestBuilder { +package class LLBuildManifestBuilder { enum Error: Swift.Error { case ldPathDriverOptionUnavailable(option: String) @@ -39,11 +39,11 @@ public class LLBuildManifestBuilder { } } - public enum TargetKind { + package enum TargetKind { case main case test - public var targetName: String { + package var targetName: String { switch self { case .main: return "main" case .test: return "test" @@ -52,24 +52,24 @@ public class LLBuildManifestBuilder { } /// The build plan to work on. - public let plan: BuildPlan + package let plan: BuildPlan /// Whether to sandbox commands from build tool plugins. - public let disableSandboxForPluginCommands: Bool + package let disableSandboxForPluginCommands: Bool /// File system reference. let fileSystem: any FileSystem /// ObservabilityScope with which to emit diagnostics - public let observabilityScope: ObservabilityScope + package let observabilityScope: ObservabilityScope - public internal(set) var manifest: LLBuildManifest = .init() + package internal(set) var manifest: LLBuildManifest = .init() /// Mapping from Swift compiler path to Swift get version files. var swiftGetVersionFiles = [AbsolutePath: AbsolutePath]() /// Create a new builder with a build plan. - public init( + package init( _ plan: BuildPlan, disableSandboxForPluginCommands: Bool = false, fileSystem: any FileSystem, @@ -85,7 +85,7 @@ public class LLBuildManifestBuilder { /// Generate build manifest at the given path. @discardableResult - public func generateManifest(at path: AbsolutePath) throws -> LLBuildManifest { + package func generateManifest(at path: AbsolutePath) throws -> LLBuildManifest { self.swiftGetVersionFiles.removeAll() self.manifest.createTarget(TargetKind.main.targetName) @@ -220,12 +220,12 @@ extension LLBuildManifestBuilder { } let additionalOutputs: [Node] if command.outputFiles.isEmpty { - if target.toolsVersion >= .v5_11 { + if target.toolsVersion >= .v6_0 { additionalOutputs = [.virtual("\(target.target.c99name)-\(command.configuration.displayName ?? "\(pluginNumber)")")] phonyOutputs += additionalOutputs } else { additionalOutputs = [] - observabilityScope.emit(warning: "Build tool command '\(displayName)' (applied to target '\(target.target.name)') does not declare any output files and therefore will not run. You may want to consider updating the given package to tools-version 5.11 (or higher) which would run such a build tool command even without declared outputs.") + observabilityScope.emit(warning: "Build tool command '\(displayName)' (applied to target '\(target.target.name)') does not declare any output files and therefore will not run. You may want to consider updating the given package to tools-version 6.0 (or higher) which would run such a build tool command even without declared outputs.") } pluginNumber += 1 } else { @@ -316,32 +316,34 @@ extension TargetBuildDescription { } } -extension ResolvedTarget { - public func getCommandName(config: String) -> String { - "C." + self.getLLBuildTargetName(config: config) +extension ResolvedModule { + package func getCommandName(buildParameters: BuildParameters) -> String { + "C." + self.getLLBuildTargetName(buildParameters: buildParameters) } - public func getLLBuildTargetName(config: String) -> String { - "\(name)-\(config).module" + package func getLLBuildTargetName(buildParameters: BuildParameters) -> String { + "\(self.name)-\(buildParameters.buildConfig)\(buildParameters.suffix(triple: self.buildTriple)).module" } - public func getLLBuildResourcesCmdName(config: String) -> String { - "\(name)-\(config).module-resources" + package func getLLBuildResourcesCmdName(buildParameters: BuildParameters) -> String { + "\(self.name)-\(buildParameters.buildConfig)\(buildParameters.suffix(triple: self.buildTriple)).module-resources" } } extension ResolvedProduct { - public func getLLBuildTargetName(config: String) throws -> String { - let potentialExecutableTargetName = "\(name)-\(config).exe" - let potentialLibraryTargetName = "\(name)-\(config).dylib" + package func getLLBuildTargetName(buildParameters: BuildParameters) throws -> String { + let config = buildParameters.buildConfig + let suffix = buildParameters.suffix(triple: self.buildTriple) + let potentialExecutableTargetName = "\(name)-\(config)\(suffix).exe" + let potentialLibraryTargetName = "\(name)-\(config)\(suffix).dylib" switch type { case .library(.dynamic): return potentialLibraryTargetName case .test: - return "\(name)-\(config).test" + return "\(name)-\(config)\(suffix).test" case .library(.static): - return "\(name)-\(config).a" + return "\(name)-\(config)\(suffix).a" case .library(.automatic): throw InternalError("automatic library not supported") case .executable, .snippet: @@ -357,8 +359,8 @@ extension ResolvedProduct { } } - public func getCommandName(config: String) throws -> String { - try "C." + self.getLLBuildTargetName(config: config) + public func getCommandName(buildParameters: BuildParameters) throws -> String { + try "C.\(self.getLLBuildTargetName(buildParameters: buildParameters))\(buildParameters.suffix(triple: self.buildTriple))" } } diff --git a/Sources/Build/BuildOperation.swift b/Sources/Build/BuildOperation.swift index 65f7d9e5c8f..6055c2c708d 100644 --- a/Sources/Build/BuildOperation.swift +++ b/Sources/Build/BuildOperation.swift @@ -26,9 +26,6 @@ import enum TSCBasic.ProcessEnv import struct TSCBasic.RegEx import enum TSCUtility.Diagnostics -import class TSCUtility.MultiLineNinjaProgressAnimation -import class TSCUtility.NinjaProgressAnimation -import protocol TSCUtility.ProgressAnimationProtocol #if USE_IMPL_ONLY_IMPORTS @_implementationOnly import DriverSupport @@ -38,9 +35,9 @@ import DriverSupport import SwiftDriver #endif -public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildSystem, BuildErrorAdviceProvider { +package final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildSystem, BuildErrorAdviceProvider { /// The delegate used by the build system. - public weak var delegate: SPMBuildCore.BuildSystemDelegate? + package weak var delegate: SPMBuildCore.BuildSystemDelegate? /// Build parameters for products. let productsBuildParameters: BuildParameters @@ -49,7 +46,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS let toolsBuildParameters: BuildParameters /// The closure for loading the package graph. - let packageGraphLoader: () throws -> PackageGraph + let packageGraphLoader: () throws -> ModulesGraph /// the plugin configuration for build plugins let pluginConfiguration: PluginConfiguration? @@ -61,12 +58,12 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS private var buildSystem: SPMLLBuild.BuildSystem? /// If build manifest caching should be enabled. - public let cacheBuildManifest: Bool + package let cacheBuildManifest: Bool /// The build plan that was computed, if any. - public private(set) var _buildPlan: BuildPlan? + package private(set) var _buildPlan: BuildPlan? - public var buildPlan: SPMBuildCore.BuildPlan { + package var buildPlan: SPMBuildCore.BuildPlan { get throws { if let buildPlan = _buildPlan { return buildPlan @@ -80,7 +77,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS private let buildDescription = ThreadSafeBox() /// The loaded package graph. - private let packageGraph = ThreadSafeBox() + private let packageGraph = ThreadSafeBox() /// The output stream for the build delegate. private let outputStream: OutputByteStream @@ -94,7 +91,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS /// ObservabilityScope with which to emit diagnostics. private let observabilityScope: ObservabilityScope - public var builtTestProducts: [BuiltTestProduct] { + package var builtTestProducts: [BuiltTestProduct] { (try? getBuildDescription())?.builtTestProducts ?? [] } @@ -104,14 +101,22 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS /// Alternative path to search for pkg-config `.pc` files. private let pkgConfigDirectories: [AbsolutePath] - public init( + /// Map of dependency package identities by root packages that depend on them. + private let dependenciesByRootPackageIdentity: [PackageIdentity: [PackageIdentity]] + + /// Map of root package identities by target names which are declared in them. + private let rootPackageIdentityByTargetName: [String: PackageIdentity] + + package init( productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters, cacheBuildManifest: Bool, - packageGraphLoader: @escaping () throws -> PackageGraph, + packageGraphLoader: @escaping () throws -> ModulesGraph, pluginConfiguration: PluginConfiguration? = .none, additionalFileRules: [FileRuleDescription], pkgConfigDirectories: [AbsolutePath], + dependenciesByRootPackageIdentity: [PackageIdentity: [PackageIdentity]], + targetsByRootPackageIdentity: [PackageIdentity: [String]], outputStream: OutputByteStream, logLevel: Basics.Diagnostic.Severity, fileSystem: Basics.FileSystem, @@ -131,13 +136,15 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS self.additionalFileRules = additionalFileRules self.pluginConfiguration = pluginConfiguration self.pkgConfigDirectories = pkgConfigDirectories + self.dependenciesByRootPackageIdentity = dependenciesByRootPackageIdentity + self.rootPackageIdentityByTargetName = (try? Dictionary(throwingUniqueKeysWithValues: targetsByRootPackageIdentity.lazy.flatMap { e in e.value.map { ($0, e.key) } })) ?? [:] self.outputStream = outputStream self.logLevel = logLevel self.fileSystem = fileSystem self.observabilityScope = observabilityScope.makeChildScope(description: "Build Operation") } - public func getPackageGraph() throws -> PackageGraph { + package func getPackageGraph() throws -> ModulesGraph { try self.packageGraph.memoize { try self.packageGraphLoader() } @@ -147,7 +154,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS /// /// This will try skip build planning if build manifest caching is enabled /// and the package structure hasn't changed. - public func getBuildDescription(subset: BuildSubset? = nil) throws -> BuildDescription { + package func getBuildDescription(subset: BuildSubset? = nil) throws -> BuildDescription { return try self.buildDescription.memoize { if self.cacheBuildManifest { do { @@ -175,12 +182,12 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } } - public func getBuildManifest() throws -> LLBuildManifest { + package func getBuildManifest() throws -> LLBuildManifest { return try self.plan().manifest } /// Cancel the active build operation. - public func cancel(deadline: DispatchTime) throws { + package func cancel(deadline: DispatchTime) throws { buildSystem?.cancel() } @@ -250,8 +257,83 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } } + private static var didEmitUnexpressedDependencies = false + + private func detectUnexpressedDependencies() { + return self.detectUnexpressedDependencies( + // Note: once we switch from the toolchain global metadata, we will have to ensure we can match the right metadata used during the build. + availableLibraries: self.productsBuildParameters.toolchain.providedLibraries, + targetDependencyMap: self.buildDescription.targetDependencyMap + ) + } + + // TODO: Currently this function will only match frameworks. + func detectUnexpressedDependencies( + availableLibraries: [ProvidedLibrary], + targetDependencyMap: [String: [String]]? + ) { + // Ensure we only emit these once, regardless of how many builds are being done. + guard !Self.didEmitUnexpressedDependencies else { + return + } + Self.didEmitUnexpressedDependencies = true + + let availableFrameworks = Dictionary(uniqueKeysWithValues: availableLibraries.compactMap { + if let identity = Set($0.metadata.identities.map(\.identity)).spm_only { + return ("\($0.metadata.productName).framework", identity) + } else { + return nil + } + }) + + targetDependencyMap?.keys.forEach { targetName in + let c99name = targetName.spm_mangledToC99ExtendedIdentifier() + // Since we're analysing post-facto, we don't know which parameters are the correct ones. + let possibleTempsPaths = [productsBuildParameters, toolsBuildParameters].map { + $0.buildPath.appending(component: "\(c99name).build") + } + + let usedSDKDependencies: [String] = Set(possibleTempsPaths).flatMap { possibleTempsPath in + guard let contents = try? self.fileSystem.readFileContents( + possibleTempsPath.appending(component: "\(c99name).d") + ) else { + return [String]() + } + + // FIXME: We need a real makefile deps parser here... + let deps = contents.description.split(whereSeparator: { $0.isWhitespace }) + return deps.filter { + !$0.hasPrefix(possibleTempsPath.parentDirectory.pathString) + }.compactMap { + try? AbsolutePath(validating: String($0)) + }.compactMap { + return $0.components.first(where: { $0.hasSuffix(".framework") }) + } + } + + let dependencies: [PackageIdentity] + if let rootPackageIdentity = self.rootPackageIdentityByTargetName[targetName] { + dependencies = self.dependenciesByRootPackageIdentity[rootPackageIdentity] ?? [] + } else { + dependencies = [] + } + + Set(usedSDKDependencies).forEach { + if availableFrameworks.keys.contains($0) { + if let availableFrameworkPackageIdentity = availableFrameworks[$0], !dependencies.contains( + availableFrameworkPackageIdentity + ) { + observabilityScope.emit( + warning: "target '\(targetName)' has an unexpressed depedency on '\(availableFrameworkPackageIdentity)'" + ) + } + } + } + } + } + /// Perform a build using the given build description and subset. - public func build(subset: BuildSubset) throws { + package func build(subset: BuildSubset) throws { guard !self.productsBuildParameters.shouldSkipBuilding else { return } @@ -286,6 +368,8 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS let duration = buildStartTime.distance(to: .now()) + self.detectUnexpressedDependencies() + let subsetDescriptor: String? switch subset { case .product(let productName): @@ -437,7 +521,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS let graph = try getPackageGraph() if let result = subset.llbuildTargetName( for: graph, - config: self.productsBuildParameters.configuration.dirname, + buildParameters: self.productsBuildParameters, observabilityScope: self.observabilityScope ) { return result @@ -450,30 +534,35 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS private func plan(subset: BuildSubset? = nil) throws -> (description: BuildDescription, manifest: LLBuildManifest) { // Load the package graph. let graph = try getPackageGraph() - let buildToolPluginInvocationResults: [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] - let prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] + let buildToolPluginInvocationResults: [ResolvedModule.ID: (target: ResolvedModule, results: [BuildToolPluginInvocationResult])] + let prebuildCommandResults: [ResolvedModule.ID: [PrebuildCommandResult]] // Invoke any build tool plugins in the graph to generate prebuild commands and build commands. if let pluginConfiguration, !self.productsBuildParameters.shouldSkipBuilding { // Hacky workaround for rdar://120560817, but it replicates precisely enough the original behavior before - // products/tools build parameters were split. Ideally we want to have specify the correct path at the time + // products/tools build parameters were split. Ideally we want to specify the correct path at the time // when `toolsBuildParameters` is initialized, but we have too many places in the codebase where that's // done, which makes it hard to realign them all at once. var pluginsBuildParameters = self.toolsBuildParameters pluginsBuildParameters.dataPath = pluginsBuildParameters.dataPath.parentDirectory.appending(components: ["plugins", "tools"]) + var buildToolsGraph = graph + try buildToolsGraph.updateBuildTripleRecursively(.tools) + let buildOperationForPluginDependencies = BuildOperation( // FIXME: this doesn't maintain the products/tools split cleanly productsBuildParameters: pluginsBuildParameters, toolsBuildParameters: pluginsBuildParameters, cacheBuildManifest: false, - packageGraphLoader: { return graph }, + packageGraphLoader: { buildToolsGraph }, additionalFileRules: self.additionalFileRules, pkgConfigDirectories: self.pkgConfigDirectories, + dependenciesByRootPackageIdentity: [:], + targetsByRootPackageIdentity: [:], outputStream: self.outputStream, logLevel: self.logLevel, fileSystem: self.fileSystem, observabilityScope: self.observabilityScope ) - buildToolPluginInvocationResults = try graph.invokeBuildToolPlugins( + buildToolPluginInvocationResults = try buildToolsGraph.invokeBuildToolPlugins( outputDir: pluginConfiguration.workDirectory.appending("outputs"), buildParameters: pluginsBuildParameters, additionalFileRules: self.additionalFileRules, @@ -491,7 +580,6 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } } - // Surface any diagnostics from build tool plugins. var succeeded = true for (_, (target, results)) in buildToolPluginInvocationResults { @@ -530,7 +618,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // Emit warnings about any unhandled files in authored packages. We do this after applying build tool plugins, once we know what files they handled. // rdar://113256834 This fix works for the plugins that do not have PreBuildCommands. - let targetsToConsider: [ResolvedTarget] + let targetsToConsider: [ResolvedModule] if let subset = subset, let recursiveDependencies = try subset.recursiveDependencies(for: graph, observabilityScope: observabilityScope) { targetsToConsider = recursiveDependencies @@ -571,7 +659,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS // Create the build plan based, on the graph and any information from plugins. let plan = try BuildPlan( - productsBuildParameters: self.productsBuildParameters, + destinationBuildParameters: self.productsBuildParameters, toolsBuildParameters: self.toolsBuildParameters, graph: graph, additionalFileRules: additionalFileRules, @@ -609,10 +697,10 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS /// building the package structure target. private func createBuildSystem(buildDescription: BuildDescription?) throws -> SPMLLBuild.BuildSystem { // Figure out which progress bar we have to use during the build. - let progressAnimation: ProgressAnimationProtocol = self.logLevel.isVerbose - ? MultiLineNinjaProgressAnimation(stream: self.outputStream) - : NinjaProgressAnimation(stream: self.outputStream) - + let progressAnimation = ProgressAnimation.ninja( + stream: self.outputStream, + verbose: self.logLevel.isVerbose + ) let buildExecutionContext = BuildExecutionContext( productsBuildParameters: self.productsBuildParameters, toolsBuildParameters: self.toolsBuildParameters, @@ -695,7 +783,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } } - public func provideBuildErrorAdvice(for target: String, command: String, message: String) -> String? { + package func provideBuildErrorAdvice(for target: String, command: String, message: String) -> String? { // Find the target for which the error was emitted. If we don't find it, we can't give any advice. guard let _ = self._buildPlan?.targets.first(where: { $0.target.name == target }) else { return nil } @@ -719,7 +807,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS return nil } - public func packageStructureChanged() -> Bool { + package func packageStructureChanged() -> Bool { do { _ = try self.plan() } @@ -735,7 +823,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS } extension BuildOperation { - public struct PluginConfiguration { + package struct PluginConfiguration { /// Entity responsible for compiling and running plugin scripts. let scriptRunner: PluginScriptRunner @@ -745,7 +833,7 @@ extension BuildOperation { /// Whether to sandbox commands from build tool plugins. let disableSandbox: Bool - public init(scriptRunner: PluginScriptRunner, workDirectory: AbsolutePath, disableSandbox: Bool) { + package init(scriptRunner: PluginScriptRunner, workDirectory: AbsolutePath, disableSandbox: Bool) { self.scriptRunner = scriptRunner self.workDirectory = workDirectory self.disableSandbox = disableSandbox @@ -792,7 +880,7 @@ extension BuildDescription { } extension BuildSubset { - func recursiveDependencies(for graph: PackageGraph, observabilityScope: ObservabilityScope) throws -> [ResolvedTarget]? { + func recursiveDependencies(for graph: ModulesGraph, observabilityScope: ObservabilityScope) throws -> [ResolvedModule]? { switch self { case .allIncludingTests: return Array(graph.reachableTargets) @@ -814,9 +902,11 @@ extension BuildSubset { } /// Returns the name of the llbuild target that corresponds to the build subset. - func llbuildTargetName(for graph: PackageGraph, config: String, observabilityScope: ObservabilityScope) - -> String? - { + func llbuildTargetName( + for graph: ModulesGraph, + buildParameters: BuildParameters, + observabilityScope: ObservabilityScope + ) -> String? { switch self { case .allExcludingTests: return LLBuildManifestBuilder.TargetKind.main.targetName @@ -837,14 +927,14 @@ extension BuildSubset { return LLBuildManifestBuilder.TargetKind.main.targetName } return observabilityScope.trap { - try product.getLLBuildTargetName(config: config) + try product.getLLBuildTargetName(buildParameters: buildParameters) } case .target(let targetName): guard let target = graph.allTargets.first(where: { $0.name == targetName }) else { observabilityScope.emit(error: "no target named '\(targetName)'") return nil } - return target.getLLBuildTargetName(config: config) + return target.getLLBuildTargetName(buildParameters: buildParameters) } } } diff --git a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift index 482235fe985..0a6559c9cab 100644 --- a/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift +++ b/Sources/Build/BuildOperationBuildSystemDelegateHandler.swift @@ -15,7 +15,9 @@ import Dispatch import Foundation import LLBuildManifest import PackageModel + import SPMBuildCore + import SPMLLBuild import struct TSCBasic.ByteString @@ -28,7 +30,6 @@ import class TSCBasic.ThreadSafeOutputByteStream import class TSCUtility.IndexStore import class TSCUtility.IndexStoreAPI -import protocol TSCUtility.ProgressAnimationProtocol #if canImport(llbuildSwift) typealias LLBuildBuildSystemDelegate = llbuildSwift.BuildSystemDelegate @@ -201,7 +202,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand { } extension TestEntryPointTool { - public static func mainFileName(for library: BuildParameters.Testing.Library) -> String { + package static func mainFileName(for library: BuildParameters.Testing.Library) -> String { "runner-\(library).swift" } } @@ -263,10 +264,18 @@ final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand { @main @available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings") struct Runner { + #if os(WASI) + /// On WASI, we can't block the main thread, so XCTestMain is defined as async. + static func main() async { + \#(testObservabilitySetup) + await XCTMain(__allDiscoveredTests()) as Never + } + #else static func main() { \#(testObservabilitySetup) XCTMain(__allDiscoveredTests()) as Never } + #endif } """# ) @@ -313,10 +322,10 @@ private final class InProcessTool: Tool { } /// Contains the description of the build that is needed during the execution. -public struct BuildDescription: Codable { - public typealias CommandName = String - public typealias TargetName = String - public typealias CommandLineFlag = String +package struct BuildDescription: Codable { + package typealias CommandName = String + package typealias TargetName = String + package typealias CommandLineFlag = String /// The Swift compiler invocation targets. let swiftCommands: [LLBuildManifest.CmdName: SwiftCompilerTool] @@ -350,12 +359,12 @@ public struct BuildDescription: Codable { let generatedSourceTargetSet: Set /// The built test products. - public let builtTestProducts: [BuiltTestProduct] + package let builtTestProducts: [BuiltTestProduct] /// Distilled information about any plugins defined in the package. let pluginDescriptions: [PluginDescription] - public init( + package init( plan: BuildPlan, swiftCommands: [LLBuildManifest.CmdName: SwiftCompilerTool], swiftFrontendCommands: [LLBuildManifest.CmdName: SwiftFrontendTool], @@ -412,13 +421,13 @@ public struct BuildDescription: Codable { self.pluginDescriptions = pluginDescriptions } - public func write(fileSystem: Basics.FileSystem, path: AbsolutePath) throws { + package func write(fileSystem: Basics.FileSystem, path: AbsolutePath) throws { let encoder = JSONEncoder.makeWithDefaults() let data = try encoder.encode(self) try fileSystem.writeFileContents(path, bytes: ByteString(data)) } - public static func load(fileSystem: Basics.FileSystem, path: AbsolutePath) throws -> BuildDescription { + package static func load(fileSystem: Basics.FileSystem, path: AbsolutePath) throws -> BuildDescription { let contents: Data = try fileSystem.readFileContents(path) let decoder = JSONDecoder.makeWithDefaults() return try decoder.decode(BuildDescription.self, from: contents) @@ -426,14 +435,14 @@ public struct BuildDescription: Codable { } /// A provider of advice about build errors. -public protocol BuildErrorAdviceProvider { +package protocol BuildErrorAdviceProvider { /// Invoked after a command fails and an error message is detected in the output. Should return a string containing /// advice or additional information, if any, based on the build plan. func provideBuildErrorAdvice(for target: String, command: String, message: String) -> String? } /// The context available during build execution. -public final class BuildExecutionContext { +package final class BuildExecutionContext { /// Build parameters for products. let productsBuildParameters: BuildParameters @@ -456,7 +465,7 @@ public final class BuildExecutionContext { let observabilityScope: ObservabilityScope - public init( + package init( productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters, buildDescription: BuildDescription? = nil, @@ -582,7 +591,7 @@ final class WriteAuxiliaryFileCommand: CustomLLBuildCommand { } } -public protocol PackageStructureDelegate { +package protocol PackageStructureDelegate { func packageStructureChanged() -> Bool } diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift index d14ec2418ad..f671a6fb367 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Product.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift @@ -14,10 +14,12 @@ import struct Basics.AbsolutePath import struct Basics.Triple import struct Basics.InternalError import struct PackageGraph.ResolvedProduct -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedModule import class PackageModel.BinaryTarget import class PackageModel.ClangTarget + import class PackageModel.Target + import class PackageModel.SwiftTarget import class PackageModel.SystemLibraryTarget import struct SPMBuildCore.BuildParameters @@ -28,7 +30,10 @@ extension BuildPlan { /// Plan a product. func plan(buildProduct: ProductBuildDescription) throws { // Compute the product's dependency. - let dependencies = try computeDependencies(of: buildProduct.product, buildParameters: buildProduct.buildParameters) + let dependencies = try computeDependencies( + of: buildProduct.product, + buildParameters: buildProduct.buildParameters + ) // Add flags for system targets. for systemModule in dependencies.systemModules { @@ -50,19 +55,24 @@ extension BuildPlan { } } - // Link C++ if needed. - // Note: This will come from build settings in future. - for target in dependencies.staticTargets { - if case let target as ClangTarget = target.underlying, target.isCXX { - let triple = buildProduct.buildParameters.triple - if triple.isDarwin() { - buildProduct.additionalFlags += ["-lc++"] - } else if triple.isWindows() { - // Don't link any C++ library. - } else { - buildProduct.additionalFlags += ["-lstdc++"] + // Don't link libc++ or libstd++ when building for Embedded Swift. + // Users can still link it manually for embedded platforms when needed, + // by providing `-Xlinker -lc++` options via CLI or `Package.swift`. + if !buildProduct.product.targets.contains(where: \.underlying.isEmbeddedSwiftTarget) { + // Link C++ if needed. + // Note: This will come from build settings in future. + for target in dependencies.staticTargets { + if case let target as ClangTarget = target.underlying, target.isCXX { + let triple = buildProduct.buildParameters.triple + if triple.isDarwin() { + buildProduct.additionalFlags += ["-lc++"] + } else if triple.isWindows() { + // Don't link any C++ library. + } else { + buildProduct.additionalFlags += ["-lstdc++"] + } + break } - break } } @@ -70,7 +80,7 @@ extension BuildPlan { switch target.underlying { case is SwiftTarget: // Swift targets are guaranteed to have a corresponding Swift description. - guard case .swift(let description) = targetMap[target.id] else { + guard case .swift(let description) = self.targetMap[target.id] else { throw InternalError("unknown target \(target)") } @@ -92,19 +102,21 @@ extension BuildPlan { buildProduct.staticTargets = dependencies.staticTargets buildProduct.dylibs = try dependencies.dylibs.map { - guard let product = productMap[$0.id] else { + guard let product = self.productMap[$0.id] else { throw InternalError("unknown product \($0)") } return product } buildProduct.objects += try dependencies.staticTargets.flatMap { targetName -> [AbsolutePath] in - guard let target = targetMap[targetName.id] else { + guard let target = self.targetMap[targetName.id] else { throw InternalError("unknown target \(targetName)") } return try target.objects } buildProduct.libraryBinaryPaths = dependencies.libraryBinaryPaths + buildProduct.providedLibraries = dependencies.providedLibraries + buildProduct.availableTools = dependencies.availableTools } @@ -114,9 +126,10 @@ extension BuildPlan { buildParameters: BuildParameters ) throws -> ( dylibs: [ResolvedProduct], - staticTargets: [ResolvedTarget], - systemModules: [ResolvedTarget], + staticTargets: [ResolvedModule], + systemModules: [ResolvedModule], libraryBinaryPaths: Set, + providedLibraries: [String: AbsolutePath], availableTools: [String: AbsolutePath] ) { /* Prior to tools-version 5.9, we used to erroneously recursively traverse executable/plugin dependencies and statically include their @@ -135,6 +148,9 @@ extension BuildPlan { switch $0 { case .product: return nil + case .innerProduct: + // TODO: Does this just mean that we can't @testable inner products? (seems fair enough?) + return nil case .target(let target, _): return target } @@ -150,7 +166,7 @@ extension BuildPlan { } // Sort the product targets in topological order. - let nodes: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) } + let nodes: [ResolvedModule.Dependency] = product.targets.map { .target($0, conditions: []) } let allTargets = try topologicalSort(nodes, successors: { dependency in switch dependency { // Include all the dependencies of a target. @@ -175,7 +191,7 @@ extension BuildPlan { return [] } - let productDependencies: [ResolvedTarget.Dependency] = product.targets.map { .target($0, conditions: []) } + let productDependencies: [ResolvedModule.Dependency] = product.targets.map { .target($0, conditions: []) } switch product.type { case .library(.automatic), .library(.static): return productDependencies @@ -191,9 +207,10 @@ extension BuildPlan { // Create empty arrays to collect our results. var linkLibraries = [ResolvedProduct]() - var staticTargets = [ResolvedTarget]() - var systemModules = [ResolvedTarget]() + var staticTargets = [ResolvedModule]() + var systemModules = [ResolvedModule]() var libraryBinaryPaths: Set = [] + var providedLibraries = [String: AbsolutePath]() var availableTools = [String: AbsolutePath]() for dependency in allTargets { @@ -220,9 +237,11 @@ extension BuildPlan { if product.targets.contains(id: target.id) { staticTargets.append(target) } - // Library targets should always be included. + // Library targets should always be included for the same build triple. case .library: - staticTargets.append(target) + if target.buildTriple == product.buildTriple { + staticTargets.append(target) + } // Add system target to system targets array. case .systemModule: systemModules.append(target) @@ -245,6 +264,8 @@ extension BuildPlan { } case .plugin: continue + case .providedLibrary: + providedLibraries[target.name] = target.underlying.path } case .product(let product, _): @@ -262,7 +283,7 @@ extension BuildPlan { } } - return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, availableTools) + return (linkLibraries, staticTargets, systemModules, libraryBinaryPaths, providedLibraries, availableTools) } /// Extracts the artifacts from an artifactsArchive diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift index 36b1cacde0c..dadb3c776e8 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift @@ -14,12 +14,13 @@ import struct Basics.InternalError import class PackageModel.BinaryTarget import class PackageModel.ClangTarget import class PackageModel.SystemLibraryTarget +import class PackageModel.ProvidedLibraryTarget extension BuildPlan { func plan(swiftTarget: SwiftTargetBuildDescription) throws { // We need to iterate recursive dependencies because Swift compiler needs to see all the targets a target // depends on. - let environment = swiftTarget.buildParameters.buildEnvironment + let environment = swiftTarget.defaultBuildParameters.buildEnvironment for case .target(let dependency, _) in try swiftTarget.target.recursiveDependencies(satisfying: environment) { switch dependency.underlying { case let underlyingTarget as ClangTarget where underlyingTarget.type == .library: @@ -40,7 +41,7 @@ extension BuildPlan { swiftTarget.additionalFlags += try pkgConfig(for: target).cFlags case let target as BinaryTarget: if case .xcframework = target.kind { - let libraries = try self.parseXCFramework(for: target, triple: swiftTarget.buildParameters.triple) + let libraries = try self.parseXCFramework(for: target, triple: swiftTarget.defaultBuildParameters.triple) for library in libraries { library.headersPaths.forEach { swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString] @@ -48,6 +49,10 @@ extension BuildPlan { swiftTarget.libraryBinaryPaths.insert(library.libraryPath) } } + case let target as ProvidedLibraryTarget: + swiftTarget.additionalFlags += [ + "-I", target.path.pathString + ] default: break } diff --git a/Sources/Build/BuildPlan/BuildPlan+Test.swift b/Sources/Build/BuildPlan/BuildPlan+Test.swift index 93fd9da29c7..b82828608f1 100644 --- a/Sources/Build/BuildPlan/BuildPlan+Test.swift +++ b/Sources/Build/BuildPlan/BuildPlan+Test.swift @@ -15,9 +15,9 @@ import struct Basics.InternalError import struct Basics.AbsolutePath import struct LLBuildManifest.TestDiscoveryTool import struct LLBuildManifest.TestEntryPointTool -import struct PackageGraph.PackageGraph +import struct PackageGraph.ModulesGraph import struct PackageGraph.ResolvedProduct -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedModule import struct PackageModel.Sources import class PackageModel.SwiftTarget import class PackageModel.Target @@ -26,15 +26,16 @@ import protocol TSCBasic.FileSystem extension BuildPlan { static func makeDerivedTestTargets( - _ buildParameters: BuildParameters, - _ graph: PackageGraph, - _ disableSandbox: Bool, + destinationBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, + _ graph: ModulesGraph, + shouldDisableSandbox: Bool, _ fileSystem: FileSystem, _ observabilityScope: ObservabilityScope ) throws -> [(product: ResolvedProduct, discoveryTargetBuildDescription: SwiftTargetBuildDescription?, entryPointTargetBuildDescription: SwiftTargetBuildDescription)] { - guard buildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets, - case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath) = - buildParameters.testingParameters.testProductStyle + guard destinationBuildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets, + case .entryPointExecutable(let explicitlyEnabledDiscovery, let explicitlySpecifiedPath) = + destinationBuildParameters.testingParameters.testProductStyle else { throw InternalError("makeTestManifestTargets should not be used for build plan which does not require additional derived test targets") } @@ -66,9 +67,9 @@ extension BuildPlan { } /// Generates test discovery targets, which contain derived sources listing the discovered tests. - func generateDiscoveryTargets() throws -> (target: SwiftTarget, resolved: ResolvedTarget, buildDescription: SwiftTargetBuildDescription) { + func generateDiscoveryTargets() throws -> (target: SwiftTarget, resolved: ResolvedModule, buildDescription: SwiftTargetBuildDescription) { let discoveryTargetName = "\(package.manifest.displayName)PackageDiscoveredTests" - let discoveryDerivedDir = buildParameters.buildPath.appending(components: "\(discoveryTargetName).derived") + let discoveryDerivedDir = destinationBuildParameters.buildPath.appending(components: "\(discoveryTargetName).derived") let discoveryMainFile = discoveryDerivedDir.appending(component: TestDiscoveryTool.mainFileName) var discoveryPaths: [AbsolutePath] = [] @@ -84,7 +85,7 @@ extension BuildPlan { packageAccess: true, // test target is allowed access to package decls by default testDiscoverySrc: Sources(paths: discoveryPaths, root: discoveryDerivedDir) ) - let discoveryResolvedTarget = ResolvedTarget( + var discoveryResolvedTarget = ResolvedModule( packageIdentity: testProduct.packageIdentity, underlying: discoveryTarget, dependencies: testProduct.targets.map { .target($0, conditions: []) }, @@ -92,13 +93,23 @@ extension BuildPlan { supportedPlatforms: testProduct.supportedPlatforms, platformVersionProvider: testProduct.platformVersionProvider ) + + discoveryResolvedTarget.buildTriple = testProduct.buildTriple + let discoveryTargetBuildParameters: BuildParameters + switch discoveryResolvedTarget.buildTriple { + case .tools: + discoveryTargetBuildParameters = toolsBuildParameters + case .destination: + discoveryTargetBuildParameters = destinationBuildParameters + } let discoveryTargetBuildDescription = try SwiftTargetBuildDescription( package: package, target: discoveryResolvedTarget, toolsVersion: toolsVersion, - buildParameters: buildParameters, + destinationBuildParameters: discoveryTargetBuildParameters, + toolsBuildParameters: toolsBuildParameters, testTargetRole: .discovery, - disableSandbox: disableSandbox, + shouldDisableSandbox: shouldDisableSandbox, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -110,10 +121,10 @@ extension BuildPlan { /// point API and leverages the test discovery target to reference which tests to run. func generateSynthesizedEntryPointTarget( swiftTargetDependencies: [Target.Dependency], - resolvedTargetDependencies: [ResolvedTarget.Dependency] + resolvedTargetDependencies: [ResolvedModule.Dependency] ) throws -> SwiftTargetBuildDescription { - let entryPointDerivedDir = buildParameters.buildPath.appending(components: "\(testProduct.name).derived") - let entryPointMainFileName = TestEntryPointTool.mainFileName(for: buildParameters.testingParameters.library) + let entryPointDerivedDir = destinationBuildParameters.buildPath.appending(components: "\(testProduct.name).derived") + let entryPointMainFileName = TestEntryPointTool.mainFileName(for: destinationBuildParameters.testingParameters.library) let entryPointMainFile = entryPointDerivedDir.appending(component: entryPointMainFileName) let entryPointSources = Sources(paths: [entryPointMainFile], root: entryPointDerivedDir) @@ -124,7 +135,7 @@ extension BuildPlan { packageAccess: true, // test target is allowed access to package decls testEntryPointSources: entryPointSources ) - let entryPointResolvedTarget = ResolvedTarget( + var entryPointResolvedTarget = ResolvedModule( packageIdentity: testProduct.packageIdentity, underlying: entryPointTarget, dependencies: testProduct.targets.map { .target($0, conditions: []) } + resolvedTargetDependencies, @@ -132,23 +143,33 @@ extension BuildPlan { supportedPlatforms: testProduct.supportedPlatforms, platformVersionProvider: testProduct.platformVersionProvider ) + entryPointResolvedTarget.buildTriple = testProduct.buildTriple + let entryPointBuildParameters: BuildParameters + switch entryPointResolvedTarget.buildTriple { + case .tools: + entryPointBuildParameters = toolsBuildParameters + case .destination: + entryPointBuildParameters = destinationBuildParameters + } + return try SwiftTargetBuildDescription( package: package, target: entryPointResolvedTarget, toolsVersion: toolsVersion, - buildParameters: buildParameters, + destinationBuildParameters: entryPointBuildParameters, + toolsBuildParameters: toolsBuildParameters, testTargetRole: .entryPoint(isSynthesized: true), - disableSandbox: disableSandbox, + shouldDisableSandbox: shouldDisableSandbox, fileSystem: fileSystem, observabilityScope: observabilityScope ) } - let discoveryTargets: (target: SwiftTarget, resolved: ResolvedTarget, buildDescription: SwiftTargetBuildDescription)? + let discoveryTargets: (target: SwiftTarget, resolved: ResolvedModule, buildDescription: SwiftTargetBuildDescription)? let swiftTargetDependencies: [Target.Dependency] - let resolvedTargetDependencies: [ResolvedTarget.Dependency] + let resolvedTargetDependencies: [ResolvedModule.Dependency] - switch buildParameters.testingParameters.library { + switch destinationBuildParameters.testingParameters.library { case .xctest: discoveryTargets = try generateDiscoveryTargets() swiftTargetDependencies = [.target(discoveryTargets!.target, conditions: [])] @@ -169,7 +190,7 @@ extension BuildPlan { packageAccess: entryPointResolvedTarget.packageAccess, testEntryPointSources: entryPointResolvedTarget.underlying.sources ) - let entryPointResolvedTarget = ResolvedTarget( + let entryPointResolvedTarget = ResolvedModule( packageIdentity: testProduct.packageIdentity, underlying: entryPointTarget, dependencies: entryPointResolvedTarget.dependencies + resolvedTargetDependencies, @@ -181,9 +202,10 @@ extension BuildPlan { package: package, target: entryPointResolvedTarget, toolsVersion: toolsVersion, - buildParameters: buildParameters, + destinationBuildParameters: destinationBuildParameters, + toolsBuildParameters: toolsBuildParameters, testTargetRole: .entryPoint(isSynthesized: false), - disableSandbox: disableSandbox, + shouldDisableSandbox: shouldDisableSandbox, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -203,9 +225,10 @@ extension BuildPlan { package: package, target: entryPointResolvedTarget, toolsVersion: toolsVersion, - buildParameters: buildParameters, + destinationBuildParameters: destinationBuildParameters, + toolsBuildParameters: toolsBuildParameters, testTargetRole: .entryPoint(isSynthesized: false), - disableSandbox: disableSandbox, + shouldDisableSandbox: shouldDisableSandbox, fileSystem: fileSystem, observabilityScope: observabilityScope ) @@ -245,7 +268,6 @@ private extension PackageModel.SwiftTarget { sources: sources, dependencies: dependencies, packageAccess: packageAccess, - swiftVersion: .v5, usesUnsafeFlags: false ) } diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift index 1f4eda79e5f..3fd34b1f65c 100644 --- a/Sources/Build/BuildPlan/BuildPlan.swift +++ b/Sources/Build/BuildPlan/BuildPlan.swift @@ -91,11 +91,11 @@ extension [String] { extension BuildParameters { /// Returns the directory to be used for module cache. - public var moduleCache: AbsolutePath { + package var moduleCache: AbsolutePath { get throws { // FIXME: We use this hack to let swiftpm's functional test use shared // cache so it doesn't become painfully slow. - if let path = ProcessEnv.vars["SWIFTPM_TESTS_MODULECACHE"] { + if let path = ProcessEnv.block["SWIFTPM_TESTS_MODULECACHE"] { return try AbsolutePath(validating: path) } return buildPath.appending("ModuleCache") @@ -103,7 +103,7 @@ extension BuildParameters { } /// Returns the compiler arguments for the index store, if enabled. - func indexStoreArguments(for target: ResolvedTarget) -> [String] { + func indexStoreArguments(for target: ResolvedModule) -> [String] { let addIndexStoreArguments: Bool switch indexStoreMode { case .on: @@ -128,12 +128,13 @@ extension BuildParameters { } /// Computes the target triple arguments for a given resolved target. - public func targetTripleArgs(for target: ResolvedTarget) throws -> [String] { + package func tripleArgs(for target: ResolvedModule) throws -> [String] { + // confusingly enough this is the triple argument, not the target argument var args = ["-target"] // Compute the triple string for Darwin platform using the platform version. if self.triple.isDarwin() { - let platform = buildEnvironment.platform + let platform = self.buildEnvironment.platform let supportedPlatform = target.getSupportedPlatform(for: platform, usingXCTest: target.type == .test) args += [self.triple.tripleString(forPlatformVersion: supportedPlatform.version.versionString)] } else { @@ -144,7 +145,7 @@ extension BuildParameters { /// Computes the linker flags to use in order to rename a module-named main function to 'main' for the target /// platform, or nil if the linker doesn't support it for the platform. - func linkerFlagsForRenamingMainFunction(of target: ResolvedTarget) -> [String]? { + func linkerFlagsForRenamingMainFunction(of target: ResolvedModule) -> [String]? { let args: [String] if self.triple.isApple() { args = ["-alias", "_\(target.c99name)_main", "_main"] @@ -157,18 +158,18 @@ extension BuildParameters { } /// Returns the scoped view of build settings for a given target. - func createScope(for target: ResolvedTarget) -> BuildSettings.Scope { - return BuildSettings.Scope(target.underlying.buildSettings, environment: buildEnvironment) + func createScope(for target: ResolvedModule) -> BuildSettings.Scope { + BuildSettings.Scope(target.underlying.buildSettings, environment: buildEnvironment) } } /// A build plan for a package graph. public class BuildPlan: SPMBuildCore.BuildPlan { - public enum Error: Swift.Error, CustomStringConvertible, Equatable { + package enum Error: Swift.Error, CustomStringConvertible, Equatable { /// There is no buildable target in the graph. case noBuildableTarget - public var description: String { + package var description: String { switch self { case .noBuildableTarget: return """ @@ -185,31 +186,21 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// Build parameters used for tools. public let toolsBuildParameters: BuildParameters - /// Triple for which this target is compiled. - private func buildTriple(for target: ResolvedTarget) -> Basics.Triple { - self.buildParameters(for: target).triple - } - - /// Triple for which this product is compiled. - private func buildTriple(for product: ResolvedProduct) -> Basics.Triple { - self.buildParameters(for: product).triple - } - /// The package graph. - public let graph: PackageGraph + package let graph: ModulesGraph /// The target build description map. - public let targetMap: [ResolvedTarget.ID: TargetBuildDescription] + package let targetMap: [ResolvedModule.ID: TargetBuildDescription] /// The product build description map. - public let productMap: [ResolvedProduct.ID: ProductBuildDescription] + package let productMap: [ResolvedProduct.ID: ProductBuildDescription] /// The plugin descriptions. Plugins are represented in the package graph /// as targets, but they are not directly included in the build graph. - public let pluginDescriptions: [PluginDescription] + package let pluginDescriptions: [PluginDescription] /// The build targets. - public var targets: AnySequence { + package var targets: AnySequence { AnySequence(self.targetMap.values) } @@ -219,26 +210,25 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } /// The results of invoking any build tool plugins used by targets in this build. - public let buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] + package let buildToolPluginInvocationResults: [ResolvedModule.ID: [BuildToolPluginInvocationResult]] /// The results of running any prebuild commands for the targets in this build. This includes any derived /// source files as well as directories to which any changes should cause us to reevaluate the build plan. - public let prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] + package let prebuildCommandResults: [ResolvedModule.ID: [PrebuildCommandResult]] - @_spi(SwiftPMInternal) - public private(set) var derivedTestTargetsMap: [ResolvedProduct.ID: [ResolvedTarget]] = [:] + package private(set) var derivedTestTargetsMap: [ResolvedProduct.ID: [ResolvedModule]] = [:] /// Cache for pkgConfig flags. private var pkgConfigCache = [SystemLibraryTarget: (cFlags: [String], libs: [String])]() - /// Cache for library information. + /// Cache for library information. private var externalLibrariesCache = [BinaryTarget: [LibraryInfo]]() - /// Cache for tools information. + /// Cache for tools information. var externalExecutablesCache = [BinaryTarget: [ExecutableInfo]]() /// Whether to disable sandboxing (e.g. for macros). - private let disableSandbox: Bool + private let shouldDisableSandbox: Bool /// The filesystem to operate on. let fileSystem: any FileSystem @@ -246,18 +236,18 @@ public class BuildPlan: SPMBuildCore.BuildPlan { /// ObservabilityScope with which to emit diagnostics let observabilityScope: ObservabilityScope - @available(*, deprecated, renamed: "init(productsBuildParameters:toolsBuildParameters:graph:)") + @available(*, deprecated, renamed: "init(destinationBuildParameters:toolsBuildParameters:graph:)") public convenience init( buildParameters: BuildParameters, - graph: PackageGraph, + graph: ModulesGraph, additionalFileRules: [FileRuleDescription] = [], - buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] = [:], - prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] = [:], + buildToolPluginInvocationResults: [ResolvedModule.ID: [BuildToolPluginInvocationResult]] = [:], + prebuildCommandResults: [ResolvedModule.ID: [PrebuildCommandResult]] = [:], fileSystem: any FileSystem, observabilityScope: ObservabilityScope ) throws { try self.init( - productsBuildParameters: buildParameters, + destinationBuildParameters: buildParameters, toolsBuildParameters: buildParameters, graph: graph, additionalFileRules: additionalFileRules, @@ -268,28 +258,47 @@ public class BuildPlan: SPMBuildCore.BuildPlan { ) } - /// Create a build plan with a package graph and explicitly distinct build parameters for products and tools. - public init( + @available(*, deprecated, renamed: "init(destinationBuildParameters:toolsBuildParameters:graph:fileSystem:observabilityScope:)") + public convenience init( productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters, - graph: PackageGraph, + graph: ModulesGraph, + fileSystem: any FileSystem, + observabilityScope: ObservabilityScope + ) throws { + try self.init( + destinationBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, + graph: graph, + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) + } + + /// Create a build plan with a package graph and explicitly distinct build parameters for destination platform and + /// tools platform. + public init( + destinationBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, + graph: ModulesGraph, additionalFileRules: [FileRuleDescription] = [], - buildToolPluginInvocationResults: [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] = [:], - prebuildCommandResults: [ResolvedTarget.ID: [PrebuildCommandResult]] = [:], + buildToolPluginInvocationResults: [ResolvedModule.ID: [BuildToolPluginInvocationResult]] = [:], + prebuildCommandResults: [ResolvedModule.ID: [PrebuildCommandResult]] = [:], disableSandbox: Bool = false, fileSystem: any FileSystem, observabilityScope: ObservabilityScope ) throws { - self.destinationBuildParameters = productsBuildParameters + self.destinationBuildParameters = destinationBuildParameters self.toolsBuildParameters = toolsBuildParameters self.graph = graph self.buildToolPluginInvocationResults = buildToolPluginInvocationResults self.prebuildCommandResults = prebuildCommandResults - self.disableSandbox = disableSandbox + self.shouldDisableSandbox = disableSandbox self.fileSystem = fileSystem self.observabilityScope = observabilityScope.makeChildScope(description: "Build Plan") - var productMap: [ResolvedProduct.ID: (product: ResolvedProduct, buildDescription: ProductBuildDescription)] = [:] + var productMap: [ResolvedProduct.ID: (product: ResolvedProduct, buildDescription: ProductBuildDescription)] = + [:] // Create product description for each product we have in the package graph that is eligible. for product in graph.allProducts where product.shouldCreateProductDescription { let buildParameters: BuildParameters @@ -297,7 +306,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { case .tools: buildParameters = toolsBuildParameters case .destination: - buildParameters = productsBuildParameters + buildParameters = destinationBuildParameters } guard let package = graph.package(for: product) else { @@ -316,7 +325,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { )) } let macroProductsByTarget = productMap.values.filter { $0.product.type == .macro } - .reduce(into: [ResolvedTarget.ID: ResolvedProduct]()) { + .reduce(into: [ResolvedModule.ID: ResolvedProduct]()) { if let target = $1.product.targets.first { $0[target.id] = $1.product } @@ -326,7 +335,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { // Plugin targets are noted, since they need to be compiled, but they do // not get directly incorporated into the build description that will be // given to LLBuild. - var targetMap = [ResolvedTarget.ID: TargetBuildDescription]() + var targetMap = [ResolvedModule.ID: TargetBuildDescription]() var pluginDescriptions = [PluginDescription]() var shouldGenerateTestObservation = true for target in graph.allTargets.sorted(by: { $0.name < $1.name }) { @@ -335,7 +344,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { case .tools: buildParameters = toolsBuildParameters case .destination: - buildParameters = productsBuildParameters + buildParameters = destinationBuildParameters } // Validate the product dependencies of this target. @@ -384,19 +393,25 @@ public class BuildPlan: SPMBuildCore.BuildPlan { target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, - buildParameters: buildParameters, + destinationBuildParameters: buildParameters, + toolsBuildParameters: toolsBuildParameters, buildToolPluginInvocationResults: buildToolPluginInvocationResults[target.id] ?? [], prebuildCommandResults: prebuildCommandResults[target.id] ?? [], requiredMacroProducts: requiredMacroProducts, shouldGenerateTestObservation: generateTestObservation, - disableSandbox: self.disableSandbox, + shouldDisableSandbox: self.shouldDisableSandbox, fileSystem: fileSystem, observabilityScope: observabilityScope ) ) case is ClangTarget: + guard let package = graph.package(for: target) else { + throw InternalError("package not found for \(target)") + } + targetMap[target.id] = try .clang( ClangTargetBuildDescription( + package: package, target: target, toolsVersion: toolsVersion, additionalFileRules: additionalFileRules, @@ -418,7 +433,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { toolsVersion: toolsVersion, fileSystem: fileSystem )) - case is SystemLibraryTarget, is BinaryTarget: + case is SystemLibraryTarget, is BinaryTarget, is ProvidedLibraryTarget: break default: throw InternalError("unhandled \(target.underlying)") @@ -436,18 +451,21 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } // Plan the derived test targets, if necessary. - if productsBuildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets { + if destinationBuildParameters.testingParameters.testProductStyle.requiresAdditionalDerivedTestTargets { let derivedTestTargets = try Self.makeDerivedTestTargets( - productsBuildParameters, + destinationBuildParameters: destinationBuildParameters, + toolsBuildParameters: toolsBuildParameters, graph, - self.disableSandbox, + shouldDisableSandbox: self.shouldDisableSandbox, self.fileSystem, self.observabilityScope ) for item in derivedTestTargets { var derivedTestTargets = [item.entryPointTargetBuildDescription.target] - targetMap[item.entryPointTargetBuildDescription.target.id] = .swift(item.entryPointTargetBuildDescription) + targetMap[item.entryPointTargetBuildDescription.target.id] = .swift( + item.entryPointTargetBuildDescription + ) if let discoveryTargetBuildDescription = item.discoveryTargetBuildDescription { targetMap[discoveryTargetBuildDescription.target.id] = .swift(discoveryTargetBuildDescription) @@ -468,7 +486,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { static func validateDeploymentVersionOfProductDependency( product: ResolvedProduct, - forTarget target: ResolvedTarget, + forTarget target: ResolvedModule, buildEnvironment: BuildEnvironment, observabilityScope: ObservabilityScope ) throws { @@ -553,9 +571,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } // Add search paths from the system library targets. - for target in graph.reachableTargets { + for target in self.graph.reachableTargets { if let systemLib = target.underlying as? SystemLibraryTarget { - arguments.append(contentsOf: try self.pkgConfig(for: systemLib).cFlags) + try arguments.append(contentsOf: self.pkgConfig(for: systemLib).cFlags) // Add the path to the module map. arguments += ["-I", systemLib.moduleMapPath.parentDirectory.pathString] } @@ -590,7 +608,7 @@ public class BuildPlan: SPMBuildCore.BuildPlan { } // Add search paths from the system library targets. - for target in graph.reachableTargets { + for target in self.graph.reachableTargets { if let systemLib = target.underlying as? SystemLibraryTarget { arguments += try self.pkgConfig(for: systemLib).cFlags } @@ -646,12 +664,17 @@ public class BuildPlan: SPMBuildCore.BuildPlan { extension Basics.Diagnostic { static var swiftBackDeployError: Self { .warning( - "Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an optional Swift library package that can be downloaded from \"More Downloads\" for Apple Developers at https://developer.apple.com/download/more/" + """ + Swift compiler no longer supports statically linking the Swift libraries. They're included in the OS by \ + default starting with macOS Mojave 10.14.4 beta 3. For macOS Mojave 10.14.3 and earlier, there's an \ + optional Swift library package that can be downloaded from \"More Downloads\" for Apple Developers at \ + https://developer.apple.com/download/more/ + """ ) } static func productRequiresHigherPlatformVersion( - target: ResolvedTarget, + target: ResolvedModule, targetPlatform: SupportedPlatform, product: String, productPlatform: SupportedPlatform @@ -676,14 +699,14 @@ extension Basics.Diagnostic { extension BuildParameters { /// Returns a named bundle's path inside the build directory. func bundlePath(named name: String) -> AbsolutePath { - buildPath.appending(component: name + self.triple.nsbundleExtension) + self.buildPath.appending(component: name + self.triple.nsbundleExtension) } } /// Generate the resource bundle Info.plist. func generateResourceInfoPlist( fileSystem: FileSystem, - target: ResolvedTarget, + target: ResolvedModule, path: AbsolutePath ) throws -> Bool { guard let defaultLocalization = target.defaultLocalization else { @@ -729,7 +752,7 @@ extension ResolvedProduct { } private var isBinaryOnly: Bool { - return self.targets.filter({ !($0.underlying is BinaryTarget) }).isEmpty + self.targets.filter { !($0.underlying is BinaryTarget) }.isEmpty } private var isPlugin: Bool { diff --git a/Sources/Build/CMakeLists.txt b/Sources/Build/CMakeLists.txt index fcc51aed76d..9412aa38590 100644 --- a/Sources/Build/CMakeLists.txt +++ b/Sources/Build/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(Build BuildDescription/ClangTargetBuildDescription.swift BuildDescription/PluginDescription.swift BuildDescription/ProductBuildDescription.swift + BuildDescription/ResolvedModule+BuildDescription.swift BuildDescription/SwiftTargetBuildDescription.swift BuildDescription/TargetBuildDescription.swift BuildManifest/LLBuildManifestBuilder.swift diff --git a/Sources/Build/ClangSupport.swift b/Sources/Build/ClangSupport.swift index af5fd8581a5..7055b3119dc 100644 --- a/Sources/Build/ClangSupport.swift +++ b/Sources/Build/ClangSupport.swift @@ -14,7 +14,7 @@ import Basics import Foundation import PackageModel -public enum ClangSupport { +package enum ClangSupport { private struct Feature: Decodable { let name: String let value: [String]? @@ -26,7 +26,7 @@ public enum ClangSupport { private static var cachedFeatures = ThreadSafeBox() - public static func supportsFeature(name: String, toolchain: PackageModel.Toolchain) throws -> Bool { + package static func supportsFeature(name: String, toolchain: PackageModel.Toolchain) throws -> Bool { let features = try cachedFeatures.memoize { let clangPath = try toolchain.getClangCompiler() let featuresPath = clangPath.parentDirectory.parentDirectory.appending(components: ["share", "clang", "features.json"]) diff --git a/Sources/Build/SwiftCompilerOutputParser.swift b/Sources/Build/SwiftCompilerOutputParser.swift index 86b118b2be0..65024db7618 100644 --- a/Sources/Build/SwiftCompilerOutputParser.swift +++ b/Sources/Build/SwiftCompilerOutputParser.swift @@ -16,26 +16,26 @@ import class TSCUtility.JSONMessageStreamingParser import protocol TSCUtility.JSONMessageStreamingParserDelegate /// Represents a message output by the Swift compiler in JSON output mode. -public struct SwiftCompilerMessage { - public enum Kind { - public struct Output { - public let type: String - public let path: String +package struct SwiftCompilerMessage { + package enum Kind { + package struct Output { + package let type: String + package let path: String - public init(type: String, path: String) { + package init(type: String, path: String) { self.type = type self.path = path } } - public struct BeganInfo { - public let pid: Int - public let inputs: [String] - public let outputs: [Output]? - public let commandExecutable: String - public let commandArguments: [String] + package struct BeganInfo { + package let pid: Int + package let inputs: [String] + package let outputs: [Output]? + package let commandExecutable: String + package let commandArguments: [String] - public init( + package init( pid: Int, inputs: [String], outputs: [Output]?, @@ -50,21 +50,21 @@ public struct SwiftCompilerMessage { } } - public struct SkippedInfo { - public let inputs: [String] - public let outputs: [Output]? + package struct SkippedInfo { + package let inputs: [String] + package let outputs: [Output]? - public init(inputs: [String], outputs: [SwiftCompilerMessage.Kind.Output]) { + package init(inputs: [String], outputs: [SwiftCompilerMessage.Kind.Output]) { self.inputs = inputs self.outputs = outputs } } - public struct OutputInfo { - public let pid: Int - public let output: String? + package struct OutputInfo { + package let pid: Int + package let output: String? - public init(pid: Int, output: String?) { + package init(pid: Int, output: String?) { self.pid = pid self.output = output } @@ -77,17 +77,17 @@ public struct SwiftCompilerMessage { case unparsableOutput(String) } - public let name: String - public let kind: Kind + package let name: String + package let kind: Kind - public init(name: String, kind: SwiftCompilerMessage.Kind) { + package init(name: String, kind: SwiftCompilerMessage.Kind) { self.name = name self.kind = kind } } /// Protocol for the parser delegate to get notified of parsing events. -public protocol SwiftCompilerOutputParserDelegate: AnyObject { +package protocol SwiftCompilerOutputParserDelegate: AnyObject { /// Called for each message parsed. func swiftCompilerOutputParser(_ parser: SwiftCompilerOutputParser, didParse message: SwiftCompilerMessage) @@ -97,7 +97,7 @@ public protocol SwiftCompilerOutputParserDelegate: AnyObject { } /// Parser for the Swift compiler JSON output mode. -public final class SwiftCompilerOutputParser { +package final class SwiftCompilerOutputParser { /// The underlying JSON message parser. private var jsonParser: JSONMessageStreamingParser! @@ -106,16 +106,16 @@ public final class SwiftCompilerOutputParser { private var hasFailed: Bool /// Name of the target the compiler is compiling. - public let targetName: String + package let targetName: String /// Delegate to notify of parsing events. - public weak var delegate: SwiftCompilerOutputParserDelegate? + package weak var delegate: SwiftCompilerOutputParserDelegate? /// Initializes the parser with a delegate to notify of parsing events. /// - Parameters: /// - targetName: The name of the target being built. /// - delegate: Delegate to notify of parsing events. - public init(targetName: String, delegate: SwiftCompilerOutputParserDelegate) { + package init(targetName: String, delegate: SwiftCompilerOutputParserDelegate) { self.hasFailed = false self.targetName = targetName self.delegate = delegate @@ -128,7 +128,7 @@ public final class SwiftCompilerOutputParser { /// Parse the next bytes of the Swift compiler JSON output. /// - Note: If a parsing error is encountered, the delegate will be notified and the parser won't accept any further /// input. - public func parse(bytes: C) where C: Collection, C.Element == UInt8 { + package func parse(bytes: C) where C: Collection, C.Element == UInt8 { guard !hasFailed else { return } @@ -138,7 +138,7 @@ public final class SwiftCompilerOutputParser { } extension SwiftCompilerOutputParser: JSONMessageStreamingParserDelegate { - public func jsonMessageStreamingParser( + package func jsonMessageStreamingParser( _ parser: JSONMessageStreamingParser, didParse message: SwiftCompilerMessage ) { @@ -153,7 +153,7 @@ extension SwiftCompilerOutputParser: JSONMessageStreamingParserDelegate { } } - public func jsonMessageStreamingParser( + package func jsonMessageStreamingParser( _ parser: JSONMessageStreamingParser, didParseRawText text: String ) { @@ -165,7 +165,7 @@ extension SwiftCompilerOutputParser: JSONMessageStreamingParserDelegate { delegate?.swiftCompilerOutputParser(self, didParse: message) } - public func jsonMessageStreamingParser( + package func jsonMessageStreamingParser( _ parser: JSONMessageStreamingParser, didFailWith error: Error ) { @@ -179,7 +179,7 @@ extension SwiftCompilerMessage: Decodable, Equatable { case name } - public init(from decoder: Decoder) throws { + package init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decode(String.self, forKey: .name) kind = try Kind(from: decoder) @@ -191,7 +191,7 @@ extension SwiftCompilerMessage.Kind: Decodable, Equatable { case kind } - public init(from decoder: Decoder) throws { + package init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let kind = try container.decode(String.self, forKey: .kind) switch kind { diff --git a/Sources/Build/TestObservation.swift b/Sources/Build/TestObservation.swift index 49e4e7cbe30..5258e39454f 100644 --- a/Sources/Build/TestObservation.swift +++ b/Sources/Build/TestObservation.swift @@ -12,7 +12,7 @@ import SPMBuildCore -public func generateTestObservationCode(buildParameters: BuildParameters) -> String { +package func generateTestObservationCode(buildParameters: BuildParameters) -> String { guard buildParameters.triple.supportsTestSummary else { return "" } @@ -130,6 +130,8 @@ public func generateTestObservationCode(buildParameters: BuildParameters) -> Str #elseif os(Windows) @_exported import CRT @_exported import WinSDK + #elseif os(WASI) + @_exported import WASILibc #else @_exported import Darwin.C #endif @@ -176,6 +178,8 @@ public func generateTestObservationCode(buildParameters: BuildParameters) -> Str UInt32.max, UInt32.max, &overlapped) { throw ProcessLockError.unableToAquireLock(errno: Int32(GetLastError())) } + #elseif os(WASI) + // WASI doesn't support flock #else if fileDescriptor == nil { let fd = open(lockFile.path, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666) @@ -201,6 +205,8 @@ public func generateTestObservationCode(buildParameters: BuildParameters) -> Str overlapped.OffsetHigh = 0 overlapped.hEvent = nil UnlockFileEx(handle, 0, UInt32.max, UInt32.max, &overlapped) + #elseif os(WASI) + // WASI doesn't support flock #else guard let fd = fileDescriptor else { return } flock(fd, LOCK_UN) @@ -211,6 +217,8 @@ public func generateTestObservationCode(buildParameters: BuildParameters) -> Str #if os(Windows) guard let handle = handle else { return } CloseHandle(handle) + #elseif os(WASI) + // WASI doesn't support flock #else guard let fd = fileDescriptor else { return } close(fd) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index da35c85d157..279084bb5c0 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -6,15 +6,14 @@ # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for Swift project authors +add_compile_definitions(SKIP_RESOURCE_SUPPORT) add_compile_definitions(USE_IMPL_ONLY_IMPORTS) -add_subdirectory(SPMSQLite3) add_subdirectory(Basics) add_subdirectory(Build) add_subdirectory(Commands) add_subdirectory(CompilerPluginSupport) add_subdirectory(CoreCommands) -add_subdirectory(SwiftSDKTool) add_subdirectory(DriverSupport) add_subdirectory(LLBuildManifest) add_subdirectory(PackageDescription) @@ -22,18 +21,22 @@ add_subdirectory(PackageFingerprint) add_subdirectory(PackageGraph) add_subdirectory(PackageLoading) add_subdirectory(PackageModel) +add_subdirectory(PackageModelSyntax) add_subdirectory(PackagePlugin) add_subdirectory(PackageRegistry) add_subdirectory(PackageSigning) -add_subdirectory(SPMBuildCore) -add_subdirectory(SPMLLBuild) add_subdirectory(SourceControl) add_subdirectory(SourceKitLSPAPI) +add_subdirectory(SPMBuildCore) +add_subdirectory(SPMLLBuild) +add_subdirectory(SPMSQLite3) add_subdirectory(swift-bootstrap) add_subdirectory(swift-build) add_subdirectory(swift-experimental-sdk) +add_subdirectory(swift-sdk) add_subdirectory(swift-package) add_subdirectory(swift-run) add_subdirectory(swift-test) +add_subdirectory(SwiftSDKCommand) add_subdirectory(Workspace) add_subdirectory(XCBuildSupport) diff --git a/Sources/Commands/CMakeLists.txt b/Sources/Commands/CMakeLists.txt index b20e318afad..75a97da70c6 100644 --- a/Sources/Commands/CMakeLists.txt +++ b/Sources/Commands/CMakeLists.txt @@ -7,25 +7,28 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_library(Commands - PackageTools/APIDiff.swift - PackageTools/ArchiveSource.swift - PackageTools/CompletionTool.swift - PackageTools/ComputeChecksum.swift - PackageTools/Config.swift - PackageTools/Describe.swift - PackageTools/DumpCommands.swift - PackageTools/EditCommands.swift - PackageTools/Format.swift - PackageTools/Init.swift - PackageTools/InstalledPackages.swift - PackageTools/Learn.swift - PackageTools/PluginCommand.swift - PackageTools/ResetCommands.swift - PackageTools/Resolve.swift - PackageTools/ShowDependencies.swift - PackageTools/SwiftPackageTool.swift - PackageTools/ToolsVersionCommand.swift - PackageTools/Update.swift + PackageCommands/AddDependency.swift + PackageCommands/AddProduct.swift + PackageCommands/AddTarget.swift + PackageCommands/APIDiff.swift + PackageCommands/ArchiveSource.swift + PackageCommands/CompletionCommand.swift + PackageCommands/ComputeChecksum.swift + PackageCommands/Config.swift + PackageCommands/Describe.swift + PackageCommands/DumpCommands.swift + PackageCommands/EditCommands.swift + PackageCommands/Format.swift + PackageCommands/Init.swift + PackageCommands/InstalledPackages.swift + PackageCommands/Learn.swift + PackageCommands/PluginCommand.swift + PackageCommands/ResetCommands.swift + PackageCommands/Resolve.swift + PackageCommands/ShowDependencies.swift + PackageCommands/SwiftPackageCommand.swift + PackageCommands/ToolsVersionCommand.swift + PackageCommands/Update.swift Snippets/CardEvent.swift Snippets/Cards/SnippetCard.swift Snippets/Cards/SnippetGroupCard.swift @@ -33,10 +36,10 @@ add_library(Commands Snippets/CardStack.swift Snippets/Card.swift Snippets/Colorful.swift - SwiftBuildTool.swift - SwiftRunTool.swift - SwiftTestTool.swift - ToolWorkspaceDelegate.swift + SwiftBuildCommand.swift + SwiftRunCommand.swift + SwiftTestCommand.swift + CommandWorkspaceDelegate.swift Utilities/APIDigester.swift Utilities/DependenciesSerializer.swift Utilities/DescribedPackage.swift @@ -56,6 +59,7 @@ target_link_libraries(Commands PUBLIC CoreCommands LLBuildManifest PackageGraph + PackageModelSyntax SourceControl TSCBasic TSCUtility diff --git a/Sources/Commands/ToolWorkspaceDelegate.swift b/Sources/Commands/CommandWorkspaceDelegate.swift similarity index 97% rename from Sources/Commands/ToolWorkspaceDelegate.swift rename to Sources/Commands/CommandWorkspaceDelegate.swift index 983b0a75a68..38de7358c39 100644 --- a/Sources/Commands/ToolWorkspaceDelegate.swift +++ b/Sources/Commands/CommandWorkspaceDelegate.swift @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// import Basics + import CoreCommands + import Dispatch import class Foundation.NSLock import struct Foundation.URL @@ -24,7 +26,7 @@ import Workspace import protocol TSCBasic.OutputByteStream import struct TSCUtility.Version -class ToolWorkspaceDelegate: WorkspaceDelegate { +final class CommandWorkspaceDelegate: WorkspaceDelegate { private struct DownloadProgress { let bytesDownloaded: Int64 let totalBytesToDownload: Int64 @@ -212,22 +214,22 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { } } - public func willUpdateDependencies() { + package func willUpdateDependencies() { self.observabilityScope.emit(debug: "Updating dependencies") os_signpost(.begin, name: SignpostName.updatingDependencies) } - public func didUpdateDependencies(duration: DispatchTimeInterval) { + package func didUpdateDependencies(duration: DispatchTimeInterval) { self.observabilityScope.emit(debug: "Dependencies updated in (\(duration.descriptionInSeconds))") os_signpost(.end, name: SignpostName.updatingDependencies) } - public func willResolveDependencies() { + package func willResolveDependencies() { self.observabilityScope.emit(debug: "Resolving dependencies") os_signpost(.begin, name: SignpostName.resolvingDependencies) } - public func didResolveDependencies(duration: DispatchTimeInterval) { + package func didResolveDependencies(duration: DispatchTimeInterval) { self.observabilityScope.emit(debug: "Dependencies resolved in (\(duration.descriptionInSeconds))") os_signpost(.end, name: SignpostName.resolvingDependencies) } @@ -264,10 +266,10 @@ class ToolWorkspaceDelegate: WorkspaceDelegate { func willLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) {} } -public extension _SwiftCommand { +package extension _SwiftCommand { var workspaceDelegateProvider: WorkspaceDelegateProvider { return { - ToolWorkspaceDelegate( + CommandWorkspaceDelegate( observabilityScope: $0, outputHandler: $1, progressHandler: $2, diff --git a/Sources/Commands/PackageTools/APIDiff.swift b/Sources/Commands/PackageCommands/APIDiff.swift similarity index 89% rename from Sources/Commands/PackageTools/APIDiff.swift rename to Sources/Commands/PackageCommands/APIDiff.swift index 3eeb2b813ca..079b813aafd 100644 --- a/Sources/Commands/PackageTools/APIDiff.swift +++ b/Sources/Commands/PackageCommands/APIDiff.swift @@ -12,12 +12,16 @@ import ArgumentParser import Basics + import CoreCommands + import Dispatch import PackageGraph import PackageModel import SourceControl +import SPMBuildCore + struct DeprecatedAPIDiff: ParsableCommand { static let configuration = CommandConfiguration(commandName: "experimental-api-diff", abstract: "Deprecated - use `swift package diagnose-api-breaking-changes` instead", @@ -74,21 +78,21 @@ struct APIDiff: SwiftCommand { @Flag(help: "Regenerate the API baseline, even if an existing one is available.") var regenerateBaseline: Bool = false - func run(_ swiftTool: SwiftTool) throws { - let apiDigesterPath = try swiftTool.getTargetToolchain().getSwiftAPIDigester() - let apiDigesterTool = SwiftAPIDigester(fileSystem: swiftTool.fileSystem, tool: apiDigesterPath) + func run(_ swiftCommandState: SwiftCommandState) throws { + let apiDigesterPath = try swiftCommandState.getTargetToolchain().getSwiftAPIDigester() + let apiDigesterTool = SwiftAPIDigester(fileSystem: swiftCommandState.fileSystem, tool: apiDigesterPath) - let packageRoot = try globalOptions.locations.packageDirectory ?? swiftTool.getPackageRoot() + let packageRoot = try globalOptions.locations.packageDirectory ?? swiftCommandState.getPackageRoot() let repository = GitRepository(path: packageRoot) let baselineRevision = try repository.resolveRevision(identifier: treeish) // We turn build manifest caching off because we need the build plan. - let buildSystem = try swiftTool.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false) + let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false) let packageGraph = try buildSystem.getPackageGraph() let modulesToDiff = try determineModulesToDiff( packageGraph: packageGraph, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) // Build the current package. @@ -97,19 +101,19 @@ struct APIDiff: SwiftCommand { // Dump JSON for the baseline package. let baselineDumper = try APIDigesterBaselineDumper( baselineRevision: baselineRevision, - packageRoot: swiftTool.getPackageRoot(), + packageRoot: swiftCommandState.getPackageRoot(), productsBuildParameters: try buildSystem.buildPlan.destinationBuildParameters, toolsBuildParameters: try buildSystem.buildPlan.toolsBuildParameters, apiDigesterTool: apiDigesterTool, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) let baselineDir = try baselineDumper.emitAPIBaseline( for: modulesToDiff, at: overrideBaselineDir, force: regenerateBaseline, - logLevel: swiftTool.logLevel, - swiftTool: swiftTool + logLevel: swiftCommandState.logLevel, + swiftCommandState: swiftCommandState ) let results = ThreadSafeArrayStore() @@ -119,7 +123,7 @@ struct APIDiff: SwiftCommand { for module in modulesToDiff { let moduleBaselinePath = baselineDir.appending("\(module).json") - guard swiftTool.fileSystem.exists(moduleBaselinePath) else { + guard swiftCommandState.fileSystem.exists(moduleBaselinePath) else { print("\nSkipping \(module) because it does not exist in the baseline") skippedModules.insert(module) continue @@ -136,7 +140,7 @@ struct APIDiff: SwiftCommand { results.append(comparisonResult) } } catch { - swiftTool.observabilityScope.emit(error: "failed to compare API to baseline", underlyingError: error) + swiftCommandState.observabilityScope.emit(error: "failed to compare API to baseline", underlyingError: error) } semaphore.signal() } @@ -148,11 +152,11 @@ struct APIDiff: SwiftCommand { .subtracting(skippedModules) .subtracting(results.map(\.moduleName)) for failedModule in failedModules { - swiftTool.observabilityScope.emit(error: "failed to read API digester output for \(failedModule)") + swiftCommandState.observabilityScope.emit(error: "failed to read API digester output for \(failedModule)") } for result in results.get() { - try self.printComparisonResult(result, observabilityScope: swiftTool.observabilityScope) + try self.printComparisonResult(result, observabilityScope: swiftCommandState.observabilityScope) } guard failedModules.isEmpty && results.get().allSatisfy(\.hasNoAPIBreakingChanges) else { @@ -160,7 +164,7 @@ struct APIDiff: SwiftCommand { } } - private func determineModulesToDiff(packageGraph: PackageGraph, observabilityScope: ObservabilityScope) throws -> Set { + private func determineModulesToDiff(packageGraph: ModulesGraph, observabilityScope: ObservabilityScope) throws -> Set { var modulesToDiff: Set = [] if products.isEmpty && targets.isEmpty { modulesToDiff.formUnion(packageGraph.apiDigesterModules) diff --git a/Sources/Commands/PackageCommands/AddDependency.swift b/Sources/Commands/PackageCommands/AddDependency.swift new file mode 100644 index 00000000000..f0b21c8b5c7 --- /dev/null +++ b/Sources/Commands/PackageCommands/AddDependency.swift @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Basics +import CoreCommands +import PackageModel +import PackageModelSyntax +import SwiftParser +import SwiftSyntax +import TSCBasic +import TSCUtility +import Workspace + +extension SwiftPackageCommand { + struct AddDependency: SwiftCommand { + package static let configuration = CommandConfiguration( + abstract: "Add a package dependency to the manifest") + + @Argument(help: "The URL or directory of the package to add") + var dependency: String + + @OptionGroup(visibility: .hidden) + var globalOptions: GlobalOptions + + @Option(help: "The exact package version to depend on") + var exact: Version? + + @Option(help: "The specific package revision to depend on") + var revision: String? + + @Option(help: "The branch of the package to depend on") + var branch: String? + + @Option(help: "The package version to depend on (up to the next major version)") + var from: Version? + + @Option(help: "The package version to depend on (up to the next minor version)") + var upToNextMinorFrom: Version? + + @Option(help: "Specify upper bound on the package version range (exclusive)") + var to: Version? + + func run(_ swiftCommandState: SwiftCommandState) throws { + let workspace = try swiftCommandState.getActiveWorkspace() + + guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else { + throw StringError("unknown package") + } + + // Load the manifest file + let fileSystem = workspace.fileSystem + let manifestPath = packagePath.appending("Package.swift") + let manifestContents: ByteString + do { + manifestContents = try fileSystem.readFileContents(manifestPath) + } catch { + throw StringError("cannot find package manifest in \(manifestPath)") + } + + // Parse the manifest. + let manifestSyntax = manifestContents.withData { data in + data.withUnsafeBytes { buffer in + buffer.withMemoryRebound(to: UInt8.self) { buffer in + Parser.parse(source: buffer) + } + } + } + + let identity = PackageIdentity(url: .init(dependency)) + + // Collect all of the possible version requirements. + var requirements: [PackageDependency.SourceControl.Requirement] = [] + if let exact { + requirements.append(.exact(exact)) + } + + if let branch { + requirements.append(.branch(branch)) + } + + if let revision { + requirements.append(.revision(revision)) + } + + if let from { + requirements.append(.range(.upToNextMajor(from: from))) + } + + if let upToNextMinorFrom { + requirements.append(.range(.upToNextMinor(from: upToNextMinorFrom))) + } + + if requirements.count > 1 { + throw StringError("must specify at most one of --exact, --branch, --revision, --from, or --up-to-next-minor-from") + } + + guard let firstRequirement = requirements.first else { + throw StringError("must specify one of --exact, --branch, --revision, --from, or --up-to-next-minor-from") + } + + let requirement: PackageDependency.SourceControl.Requirement + if case .range(let range) = firstRequirement { + if let to { + requirement = .range(range.lowerBound.. Workspace.Configuration.Mirrors { - let workspace = try swiftTool.getActiveWorkspace() + static func getMirrorsConfig(_ swiftCommandState: SwiftCommandState) throws -> Workspace.Configuration.Mirrors { + let workspace = try swiftCommandState.getActiveWorkspace() return try .init( - fileSystem: swiftTool.fileSystem, + fileSystem: swiftCommandState.fileSystem, localMirrorsFile: workspace.location.localMirrorsConfigurationFile, sharedMirrorsFile: workspace.location.sharedMirrorsConfigurationFile ) diff --git a/Sources/Commands/PackageTools/Describe.swift b/Sources/Commands/PackageCommands/Describe.swift similarity index 87% rename from Sources/Commands/PackageTools/Describe.swift rename to Sources/Commands/PackageCommands/Describe.swift index 96ec1d5e7aa..7e06f1e4ded 100644 --- a/Sources/Commands/PackageTools/Describe.swift +++ b/Sources/Commands/PackageCommands/Describe.swift @@ -12,13 +12,15 @@ import ArgumentParser import Basics + import CoreCommands + import Foundation import PackageModel import struct TSCBasic.StringError -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Describe: AsyncSwiftCommand { static let configuration = CommandConfiguration( abstract: "Describe the current package") @@ -29,16 +31,16 @@ extension SwiftPackageTool { @Option(help: "json | text | mermaid") var type: DescribeMode = .text - func run(_ swiftTool: SwiftTool) async throws { - let workspace = try swiftTool.getActiveWorkspace() + func run(_ swiftCommandState: SwiftCommandState) async throws { + let workspace = try swiftCommandState.getActiveWorkspace() - guard let packagePath = try swiftTool.getWorkspaceRoot().packages.first else { + guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else { throw StringError("unknown package") } let package = try await workspace.loadRootPackage( at: packagePath, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) try self.describe(package, in: type) diff --git a/Sources/Commands/PackageTools/DumpCommands.swift b/Sources/Commands/PackageCommands/DumpCommands.swift similarity index 77% rename from Sources/Commands/PackageTools/DumpCommands.swift rename to Sources/Commands/PackageCommands/DumpCommands.swift index 35845334ad2..40db905b9a5 100644 --- a/Sources/Commands/PackageTools/DumpCommands.swift +++ b/Sources/Commands/PackageCommands/DumpCommands.swift @@ -12,9 +12,14 @@ import ArgumentParser import Basics + import CoreCommands + import Foundation import PackageModel + +import SPMBuildCore + import XCBuildSupport struct DumpSymbolGraph: SwiftCommand { @@ -43,18 +48,18 @@ struct DumpSymbolGraph: SwiftCommand { @Flag(help: "Emit extension block symbols for extensions to external types or directly associate members and conformances with the extended nominal.") var extensionBlockSymbolBehavior: ExtensionBlockSymbolBehavior = .omitExtensionBlockSymbols - func run(_ swiftTool: SwiftTool) throws { + func run(_ swiftCommandState: SwiftCommandState) throws { // Build the current package. // // We turn build manifest caching off because we need the build plan. - let buildSystem = try swiftTool.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false) + let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false) try buildSystem.build() // Configure the symbol graph extractor. let symbolGraphExtractor = try SymbolGraphExtract( - fileSystem: swiftTool.fileSystem, - tool: swiftTool.getTargetToolchain().getSymbolGraphExtract(), - observabilityScope: swiftTool.observabilityScope, + fileSystem: swiftCommandState.fileSystem, + tool: swiftCommandState.getTargetToolchain().getSymbolGraphExtract(), + observabilityScope: swiftCommandState.observabilityScope, skipSynthesizedMembers: skipSynthesizedMembers, minimumAccessLevel: minimumAccessLevel, skipInheritedDocs: skipInheritedDocs, @@ -70,20 +75,20 @@ struct DumpSymbolGraph: SwiftCommand { for target in targets { print("-- Emitting symbol graph for", target.name) let result = try symbolGraphExtractor.extractSymbolGraph( - target: target, + module: target, buildPlan: buildPlan, outputRedirection: .collect(redirectStderr: true), outputDirectory: symbolGraphDirectory, - verboseOutput: swiftTool.logLevel <= .info + verboseOutput: swiftCommandState.logLevel <= .info ) if result.exitStatus != .terminated(code: 0) { let commandline = "\nUsing commandline: \(result.arguments)" switch result.output { case .success(let value): - swiftTool.observabilityScope.emit(error: "Failed to emit symbol graph for '\(target.c99name)': \(String(decoding: value, as: UTF8.self))\(commandline)") + swiftCommandState.observabilityScope.emit(error: "Failed to emit symbol graph for '\(target.c99name)': \(String(decoding: value, as: UTF8.self))\(commandline)") case .failure(let error): - swiftTool.observabilityScope.emit(error: "Internal error while emitting symbol graph for '\(target.c99name)': \(error)\(commandline)") + swiftCommandState.observabilityScope.emit(error: "Internal error while emitting symbol graph for '\(target.c99name)': \(error)\(commandline)") } } } @@ -104,13 +109,13 @@ struct DumpPackage: AsyncSwiftCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) async throws { - let workspace = try swiftTool.getActiveWorkspace() - let root = try swiftTool.getWorkspaceRoot() + func run(_ swiftCommandState: SwiftCommandState) async throws { + let workspace = try swiftCommandState.getActiveWorkspace() + let root = try swiftCommandState.getWorkspaceRoot() let rootManifests = try await workspace.loadRootManifests( packages: root.packages, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) guard let rootManifest = rootManifests.values.first else { throw StringError("invalid manifests at \(root.packages)") @@ -126,7 +131,7 @@ struct DumpPackage: AsyncSwiftCommand { } struct DumpPIF: SwiftCommand { - // hides this command from CLI --help output + // hides this command from CLI `--help` output static let configuration = CommandConfiguration(shouldDisplay: false) @OptionGroup(visibility: .hidden) @@ -135,13 +140,13 @@ struct DumpPIF: SwiftCommand { @Flag(help: "Preserve the internal structure of PIF") var preserveStructure: Bool = false - func run(_ swiftTool: SwiftTool) throws { - let graph = try swiftTool.loadPackageGraph() + func run(_ swiftCommandState: SwiftCommandState) throws { + let graph = try swiftCommandState.loadPackageGraph() let pif = try PIFBuilder.generatePIF( - buildParameters: swiftTool.productsBuildParameters, + buildParameters: swiftCommandState.productsBuildParameters, packageGraph: graph, - fileSystem: swiftTool.fileSystem, - observabilityScope: swiftTool.observabilityScope, + fileSystem: swiftCommandState.fileSystem, + observabilityScope: swiftCommandState.observabilityScope, preservePIFModelStructure: preserveStructure) print(pif) } diff --git a/Sources/Commands/PackageTools/EditCommands.swift b/Sources/Commands/PackageCommands/EditCommands.swift similarity index 78% rename from Sources/Commands/PackageTools/EditCommands.swift rename to Sources/Commands/PackageCommands/EditCommands.swift index c4249d974eb..f20d5c44a2f 100644 --- a/Sources/Commands/PackageTools/EditCommands.swift +++ b/Sources/Commands/PackageCommands/EditCommands.swift @@ -12,10 +12,12 @@ import ArgumentParser import Basics + import CoreCommands + import SourceControl -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Edit: SwiftCommand { static let configuration = CommandConfiguration( abstract: "Put a package in editable mode") @@ -35,9 +37,9 @@ extension SwiftPackageTool { @Argument(help: "The name of the package to edit") var packageName: String - func run(_ swiftTool: SwiftTool) throws { - try swiftTool.resolve() - let workspace = try swiftTool.getActiveWorkspace() + func run(_ swiftCommandState: SwiftCommandState) throws { + try swiftCommandState.resolve() + let workspace = try swiftCommandState.getActiveWorkspace() // Put the dependency in edit mode. workspace.edit( @@ -45,7 +47,7 @@ extension SwiftPackageTool { path: path, revision: revision, checkoutBranch: checkoutBranch, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) } } @@ -64,15 +66,15 @@ extension SwiftPackageTool { @Argument(help: "The name of the package to unedit") var packageName: String - func run(_ swiftTool: SwiftTool) throws { - try swiftTool.resolve() - let workspace = try swiftTool.getActiveWorkspace() + func run(_ swiftCommandState: SwiftCommandState) throws { + try swiftCommandState.resolve() + let workspace = try swiftCommandState.getActiveWorkspace() try workspace.unedit( packageName: packageName, forceRemove: shouldForceRemove, - root: swiftTool.getWorkspaceRoot(), - observabilityScope: swiftTool.observabilityScope + root: swiftCommandState.getWorkspaceRoot(), + observabilityScope: swiftCommandState.observabilityScope ) } } diff --git a/Sources/Commands/PackageTools/Format.swift b/Sources/Commands/PackageCommands/Format.swift similarity index 84% rename from Sources/Commands/PackageTools/Format.swift rename to Sources/Commands/PackageCommands/Format.swift index 3ba0ccc7dc0..13268b1d9b5 100644 --- a/Sources/Commands/PackageTools/Format.swift +++ b/Sources/Commands/PackageCommands/Format.swift @@ -12,7 +12,9 @@ import ArgumentParser import Basics + import CoreCommands + import PackageModel import class TSCBasic.Process @@ -20,7 +22,7 @@ import enum TSCBasic.ProcessEnv import enum TSCUtility.Diagnostics -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Format: AsyncSwiftCommand { static let configuration = CommandConfiguration( commandName: "_format", shouldDisplay: false) @@ -32,25 +34,25 @@ extension SwiftPackageTool { help: "Pass flag through to the swift-format tool") var swiftFormatFlags: [String] = [] - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { // Look for swift-format binary. // FIXME: This should be moved to user toolchain. - let swiftFormatInEnv = lookupExecutablePath(filename: ProcessEnv.vars["SWIFT_FORMAT"]) + let swiftFormatInEnv = lookupExecutablePath(filename: ProcessEnv.block["SWIFT_FORMAT"]) guard let swiftFormat = swiftFormatInEnv ?? Process.findExecutable("swift-format").flatMap(AbsolutePath.init) else { - swiftTool.observabilityScope.emit(error: "Could not find swift-format in PATH or SWIFT_FORMAT") + swiftCommandState.observabilityScope.emit(error: "Could not find swift-format in PATH or SWIFT_FORMAT") throw TSCUtility.Diagnostics.fatalError } // Get the root package. - let workspace = try swiftTool.getActiveWorkspace() + let workspace = try swiftCommandState.getActiveWorkspace() - guard let packagePath = try swiftTool.getWorkspaceRoot().packages.first else { + guard let packagePath = try swiftCommandState.getWorkspaceRoot().packages.first else { throw StringError("unknown package") } let package = try await workspace.loadRootPackage( at: packagePath, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) diff --git a/Sources/Commands/PackageTools/Init.swift b/Sources/Commands/PackageCommands/Init.swift similarity index 72% rename from Sources/Commands/PackageTools/Init.swift rename to Sources/Commands/PackageCommands/Init.swift index 51ce5afd2c8..3daeab93b7c 100644 --- a/Sources/Commands/PackageTools/Init.swift +++ b/Sources/Commands/PackageCommands/Init.swift @@ -12,13 +12,15 @@ import ArgumentParser import Basics + import CoreCommands + import Workspace import SPMBuildCore -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Init: SwiftCommand { - public static let configuration = CommandConfiguration( + package static let configuration = CommandConfiguration( abstract: "Initialize a new package") @OptionGroup(visibility: .hidden) @@ -39,31 +41,25 @@ extension SwiftPackageTool { """)) var initMode: InitPackage.PackageType = .library - /// Whether to enable support for XCTest. - @Flag(name: .customLong("xctest"), - inversion: .prefixedEnableDisable, - help: "Enable support for XCTest") - var enableXCTestSupport: Bool = true - - /// Whether to enable support for swift-testing. - @Flag(name: .customLong("experimental-swift-testing"), - inversion: .prefixedEnableDisable, - help: "Enable experimental support for swift-testing") - var enableSwiftTestingLibrarySupport: Bool = false + /// Which testing libraries to use (and any related options.) + @OptionGroup() + var testLibraryOptions: TestLibraryOptions @Option(name: .customLong("name"), help: "Provide custom package name") var packageName: String? - func run(_ swiftTool: SwiftTool) throws { - guard let cwd = swiftTool.fileSystem.currentWorkingDirectory else { + func run(_ swiftCommandState: SwiftCommandState) throws { + guard let cwd = swiftCommandState.fileSystem.currentWorkingDirectory else { throw InternalError("Could not find the current working directory") } + // NOTE: Do not use testLibraryOptions.enabledTestingLibraries(swiftCommandState:) here + // because the package doesn't exist yet, so there are no dependencies for it to query. var testingLibraries: Set = [] - if enableXCTestSupport { + if testLibraryOptions.enableXCTestSupport { testingLibraries.insert(.xctest) } - if enableSwiftTestingLibrarySupport { + if testLibraryOptions.explicitlyEnableSwiftTestingLibrarySupport == true { testingLibraries.insert(.swiftTesting) } let packageName = self.packageName ?? cwd.basename @@ -72,8 +68,8 @@ extension SwiftPackageTool { packageType: initMode, supportedTestingLibraries: testingLibraries, destinationPath: cwd, - installedSwiftPMConfiguration: swiftTool.getHostToolchain().installedSwiftPMConfiguration, - fileSystem: swiftTool.fileSystem + installedSwiftPMConfiguration: swiftCommandState.getHostToolchain().installedSwiftPMConfiguration, + fileSystem: swiftCommandState.fileSystem ) initPackage.progressReporter = { message in print(message) @@ -83,7 +79,7 @@ extension SwiftPackageTool { } } -#if swift(<5.11) +#if swift(<6.0) extension InitPackage.PackageType: ExpressibleByArgument {} #else extension InitPackage.PackageType: @retroactive ExpressibleByArgument {} diff --git a/Sources/Commands/PackageTools/InstalledPackages.swift b/Sources/Commands/PackageCommands/InstalledPackages.swift similarity index 97% rename from Sources/Commands/PackageTools/InstalledPackages.swift rename to Sources/Commands/PackageCommands/InstalledPackages.swift index c473701479a..9deaceab9fd 100644 --- a/Sources/Commands/PackageTools/InstalledPackages.swift +++ b/Sources/Commands/PackageCommands/InstalledPackages.swift @@ -11,12 +11,17 @@ //===----------------------------------------------------------------------===// import ArgumentParser + import CoreCommands + import Foundation import PackageModel + +import SPMBuildCore + import TSCBasic -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Install: SwiftCommand { static let configuration = CommandConfiguration( commandName: "experimental-install", @@ -29,7 +34,7 @@ extension SwiftPackageTool { @Option(help: "The name of the executable product to install") var product: String? - func run(_ tool: SwiftTool) throws { + func run(_ tool: SwiftCommandState) throws { let swiftpmBinDir = try tool.fileSystem.getOrCreateSwiftPMInstalledBinariesDirectory() let env = ProcessInfo.processInfo.environment @@ -103,7 +108,7 @@ extension SwiftPackageTool { @Argument(help: "Name of the executable to uninstall.") var name: String - func run(_ tool: SwiftTool) throws { + func run(_ tool: SwiftCommandState) throws { let alreadyInstalled = (try? InstalledPackageProduct.installedProducts(tool.fileSystem)) ?? [] guard let removedExecutable = alreadyInstalled.first(where: { $0.name == name }) else { diff --git a/Sources/Commands/PackageTools/Learn.swift b/Sources/Commands/PackageCommands/Learn.swift similarity index 93% rename from Sources/Commands/PackageTools/Learn.swift rename to Sources/Commands/PackageCommands/Learn.swift index 8e4af0d9410..7b0f21e2ff1 100644 --- a/Sources/Commands/PackageTools/Learn.swift +++ b/Sources/Commands/PackageCommands/Learn.swift @@ -12,11 +12,13 @@ import ArgumentParser import Basics + import CoreCommands + import PackageGraph import PackageModel -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Learn: SwiftCommand { @OptionGroup() @@ -90,14 +92,14 @@ extension SwiftPackageTool { return snippetGroups.filter { !$0.snippets.isEmpty } } - func run(_ swiftTool: SwiftTool) throws { - let graph = try swiftTool.loadPackageGraph() + func run(_ swiftCommandState: SwiftCommandState) throws { + let graph = try swiftCommandState.loadPackageGraph() let package = graph.rootPackages[graph.rootPackages.startIndex] print(package.products.map { $0.description }) - let snippetGroups = try loadSnippetsAndSnippetGroups(fileSystem: swiftTool.fileSystem, from: package) + let snippetGroups = try loadSnippetsAndSnippetGroups(fileSystem: swiftCommandState.fileSystem, from: package) - var cardStack = CardStack(package: package, snippetGroups: snippetGroups, swiftTool: swiftTool) + var cardStack = CardStack(package: package, snippetGroups: snippetGroups, swiftCommandState: swiftCommandState) cardStack.run() } diff --git a/Sources/Commands/PackageTools/PluginCommand.swift b/Sources/Commands/PackageCommands/PluginCommand.swift similarity index 87% rename from Sources/Commands/PackageTools/PluginCommand.swift rename to Sources/Commands/PackageCommands/PluginCommand.swift index 1968dae8183..5c8b6f6a413 100644 --- a/Sources/Commands/PackageTools/PluginCommand.swift +++ b/Sources/Commands/PackageCommands/PluginCommand.swift @@ -12,9 +12,15 @@ import ArgumentParser import Basics + import CoreCommands + +import SPMBuildCore + import Dispatch + import PackageGraph + import PackageModel import enum TSCBasic.ProcessEnv @@ -137,7 +143,7 @@ struct PluginCommand: SwiftCommand { ) var arguments: [String] = [] - func run(_ swiftTool: SwiftTool) throws { + func run(_ swiftCommandState: SwiftCommandState) throws { // Check for a missing plugin command verb. if self.command == "" && !self.listCommands { throw ValidationError("Missing expected plugin command") @@ -145,7 +151,7 @@ struct PluginCommand: SwiftCommand { // List the available plugins, if asked to. if self.listCommands { - let packageGraph = try swiftTool.loadPackageGraph() + let packageGraph = try swiftCommandState.loadPackageGraph() let allPlugins = PluginCommand.availableCommandPlugins(in: packageGraph, limitedTo: self.pluginOptions.packageIdentity) for plugin in allPlugins.sorted(by: { $0.name < $1.name }) { guard case .command(let intent, _) = plugin.capability else { continue } @@ -165,7 +171,7 @@ struct PluginCommand: SwiftCommand { command: self.command, options: self.pluginOptions, arguments: self.arguments, - swiftTool: swiftTool + swiftCommandState: swiftCommandState ) } @@ -173,12 +179,12 @@ struct PluginCommand: SwiftCommand { command: String, options: PluginOptions, arguments: [String], - swiftTool: SwiftTool + swiftCommandState: SwiftCommandState ) throws { // Load the workspace and resolve the package graph. - let packageGraph = try swiftTool.loadPackageGraph() + let packageGraph = try swiftCommandState.loadPackageGraph() - swiftTool.observabilityScope.emit(info: "Finding plugin for command ‘\(command)’") + swiftCommandState.observabilityScope.emit(info: "Finding plugin for command ‘\(command)’") let matchingPlugins = PluginCommand.findPlugins(matching: command, in: packageGraph, limitedTo: options.packageIdentity) // Complain if we didn't find exactly one. @@ -194,7 +200,7 @@ struct PluginCommand: SwiftCommand { // merge the relevant plugin execution options let pluginOptions = options.merged(with: pluginArguments.pluginOptions) // sandbox is special since its generic not a specific plugin option - swiftTool.shouldDisableSandbox = swiftTool.shouldDisableSandbox || pluginArguments.globalOptions.security + swiftCommandState.shouldDisableSandbox = swiftCommandState.shouldDisableSandbox || pluginArguments.globalOptions.security .shouldDisableSandbox // At this point we know we found exactly one command plugin, so we run it. In SwiftPM CLI, we have only one root package. @@ -204,29 +210,29 @@ struct PluginCommand: SwiftCommand { packageGraph: packageGraph, options: pluginOptions, arguments: unparsedArguments, - swiftTool: swiftTool + swiftCommandState: swiftCommandState ) } static func run( plugin: PluginTarget, package: ResolvedPackage, - packageGraph: PackageGraph, + packageGraph: ModulesGraph, options: PluginOptions, arguments: [String], - swiftTool: SwiftTool + swiftCommandState: SwiftCommandState ) throws { - swiftTool.observabilityScope + swiftCommandState.observabilityScope .emit( info: "Running command plugin \(plugin) on package \(package) with options \(options) and arguments \(arguments)" ) // The `plugins` directory is inside the workspace's main data directory, and contains all temporary files related to this plugin in the workspace. - let pluginsDir = try swiftTool.getActiveWorkspace().location.pluginWorkingDirectory + let pluginsDir = try swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory .appending(component: plugin.name) // The `cache` directory is in the plugin’s directory and is where the plugin script runner caches compiled plugin binaries and any other derived information for this plugin. - let pluginScriptRunner = try swiftTool.getPluginScriptRunner( + let pluginScriptRunner = try swiftCommandState.getPluginScriptRunner( customPluginsDir: pluginsDir ) @@ -276,11 +282,11 @@ struct PluginCommand: SwiftCommand { let problem = "Plugin ‘\(plugin.name)’ wants permission to \(permissionString)." let reason = "Stated reason: “\(reasonString)”." - if swiftTool.outputStream.isTTY { + if swiftCommandState.outputStream.isTTY { // We can ask the user directly, so we do so. let query = "Allow this plugin to \(permissionString)?" - swiftTool.outputStream.write("\(problem)\n\(reason)\n\(query) (yes/no) ".utf8) - swiftTool.outputStream.flush() + swiftCommandState.outputStream.write("\(problem)\n\(reason)\n\(query) (yes/no) ".utf8) + swiftCommandState.outputStream.flush() let answer = readLine(strippingNewline: true) // Throw an error if we didn't get permission. if answer?.lowercased() != "yes" { @@ -304,7 +310,7 @@ struct PluginCommand: SwiftCommand { for pathString in options.additionalAllowedWritableDirectories { writableDirectories - .append(try AbsolutePath(validating: pathString, relativeTo: swiftTool.originalWorkingDirectory)) + .append(try AbsolutePath(validating: pathString, relativeTo: swiftCommandState.originalWorkingDirectory)) } // Make sure that the package path is read-only unless it's covered by any of the explicitly writable directories. @@ -312,22 +318,27 @@ struct PluginCommand: SwiftCommand { .contains { package.path.isDescendantOfOrEqual(to: $0) } ? [] : [package.path] // Use the directory containing the compiler as an additional search directory, and add the $PATH. - let toolSearchDirs = [try swiftTool.getTargetToolchain().swiftCompilerPath.parentDirectory] + let toolSearchDirs = [try swiftCommandState.getTargetToolchain().swiftCompilerPath.parentDirectory] + getEnvSearchPaths(pathString: ProcessEnv.path, currentWorkingDirectory: .none) - let buildParameters = try swiftTool.toolsBuildParameters + var buildToolsGraph = packageGraph + try buildToolsGraph.updateBuildTripleRecursively(.tools) + + let buildParameters = try swiftCommandState.toolsBuildParameters // Build or bring up-to-date any executable host-side tools on which this plugin depends. Add them and any binary dependencies to the tool-names-to-path map. - let buildSystem = try swiftTool.createBuildSystem( + let buildSystem = try swiftCommandState.createBuildSystem( explicitBuildSystem: .native, cacheBuildManifest: false, // Force all dependencies to be built for the host, to work around the fact that BuildOperation.plan // knows to compile build tool plugin dependencies for the host but does not do the same for command // plugins. - productsBuildParameters: buildParameters + productsBuildParameters: buildParameters, + packageGraphLoader: { buildToolsGraph } ) + let accessibleTools = try plugin.processAccessibleTools( - packageGraph: packageGraph, - fileSystem: swiftTool.fileSystem, + packageGraph: buildToolsGraph, + fileSystem: swiftCommandState.fileSystem, environment: buildParameters.buildEnvironment, for: try pluginScriptRunner.hostTriple ) { name, _ in @@ -341,7 +352,7 @@ struct PluginCommand: SwiftCommand { } // Set up a delegate to handle callbacks from the command plugin. - let pluginDelegate = PluginDelegate(swiftTool: swiftTool, plugin: plugin) + let pluginDelegate = PluginDelegate(swiftCommandState: swiftCommandState, plugin: plugin) let delegateQueue = DispatchQueue(label: "plugin-invocation") // Run the command plugin. @@ -350,17 +361,18 @@ struct PluginCommand: SwiftCommand { action: .performCommand(package: package, arguments: arguments), buildEnvironment: buildEnvironment, scriptRunner: pluginScriptRunner, - workingDirectory: swiftTool.originalWorkingDirectory, + workingDirectory: swiftCommandState.originalWorkingDirectory, outputDirectory: outputDir, toolSearchDirectories: toolSearchDirs, accessibleTools: accessibleTools, writableDirectories: writableDirectories, readOnlyDirectories: readOnlyDirectories, allowNetworkConnections: allowNetworkConnections, - pkgConfigDirectories: swiftTool.options.locations.pkgConfigDirectories, + pkgConfigDirectories: swiftCommandState.options.locations.pkgConfigDirectories, sdkRootPath: buildParameters.toolchain.sdkRootPath, - fileSystem: swiftTool.fileSystem, - observabilityScope: swiftTool.observabilityScope, + fileSystem: swiftCommandState.fileSystem, + modulesGraph: packageGraph, + observabilityScope: swiftCommandState.observabilityScope, callbackQueue: delegateQueue, delegate: pluginDelegate, completion: $0 @@ -369,12 +381,19 @@ struct PluginCommand: SwiftCommand { // TODO: We should also emit a final line of output regarding the result. } - static func availableCommandPlugins(in graph: PackageGraph, limitedTo packageIdentity: String?) -> [PluginTarget] { + static func availableCommandPlugins(in graph: ModulesGraph, limitedTo packageIdentity: String?) -> [PluginTarget] { // All targets from plugin products of direct dependencies are "available". - let directDependencyPackages = graph.rootPackages.flatMap { $0.dependencies }.filter { $0.matching(identity: packageIdentity) } + let directDependencyPackages = graph.rootPackages.flatMap { + $0.dependencies + }.filter { + $0.matching(identity: packageIdentity) + }.compactMap { + graph.package(for: $0) + } + let directDependencyPluginTargets = directDependencyPackages.flatMap { $0.products.filter { $0.type == .plugin } }.flatMap { $0.targets } // As well as any plugin targets in root packages. - let rootPackageTargets = graph.rootPackages.filter { $0.matching(identity: packageIdentity) }.flatMap { $0.targets } + let rootPackageTargets = graph.rootPackages.filter { $0.identity.matching(identity: packageIdentity) }.flatMap { $0.targets } return (directDependencyPluginTargets + rootPackageTargets).compactMap { $0.underlying as? PluginTarget }.filter { switch $0.capability { case .buildTool: return false @@ -383,7 +402,7 @@ struct PluginCommand: SwiftCommand { } } - static func findPlugins(matching verb: String, in graph: PackageGraph, limitedTo packageIdentity: String?) -> [PluginTarget] { + static func findPlugins(matching verb: String, in graph: ModulesGraph, limitedTo packageIdentity: String?) -> [PluginTarget] { // Find and return the command plugins that match the command. Self.availableCommandPlugins(in: graph, limitedTo: packageIdentity).filter { // Filter out any non-command plugins and any whose verb is different. @@ -458,10 +477,10 @@ extension SandboxNetworkPermission { } } -extension ResolvedPackage { +extension PackageIdentity { fileprivate func matching(identity: String?) -> Bool { if let identity { - return self.identity == .plain(identity) + return self == .plain(identity) } else { return true } diff --git a/Sources/Commands/PackageTools/ResetCommands.swift b/Sources/Commands/PackageCommands/ResetCommands.swift similarity index 68% rename from Sources/Commands/PackageTools/ResetCommands.swift rename to Sources/Commands/PackageCommands/ResetCommands.swift index 26204d1f49a..6711e4840ba 100644 --- a/Sources/Commands/PackageTools/ResetCommands.swift +++ b/Sources/Commands/PackageCommands/ResetCommands.swift @@ -11,9 +11,10 @@ //===----------------------------------------------------------------------===// import ArgumentParser + import CoreCommands -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Clean: SwiftCommand { static let configuration = CommandConfiguration( abstract: "Delete build artifacts") @@ -21,8 +22,8 @@ extension SwiftPackageTool { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) throws { - try swiftTool.getActiveWorkspace().clean(observabilityScope: swiftTool.observabilityScope) + func run(_ swiftCommandState: SwiftCommandState) throws { + try swiftCommandState.getActiveWorkspace().clean(observabilityScope: swiftCommandState.observabilityScope) } } @@ -33,8 +34,8 @@ extension SwiftPackageTool { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) throws { - try swiftTool.getActiveWorkspace().purgeCache(observabilityScope: swiftTool.observabilityScope) + func run(_ swiftCommandState: SwiftCommandState) throws { + try swiftCommandState.getActiveWorkspace().purgeCache(observabilityScope: swiftCommandState.observabilityScope) } } @@ -45,8 +46,8 @@ extension SwiftPackageTool { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) throws { - try swiftTool.getActiveWorkspace().reset(observabilityScope: swiftTool.observabilityScope) + func run(_ swiftCommandState: SwiftCommandState) throws { + try swiftCommandState.getActiveWorkspace().reset(observabilityScope: swiftCommandState.observabilityScope) } } } diff --git a/Sources/Commands/PackageTools/Resolve.swift b/Sources/Commands/PackageCommands/Resolve.swift similarity index 76% rename from Sources/Commands/PackageTools/Resolve.swift rename to Sources/Commands/PackageCommands/Resolve.swift index f89faf0f6bf..a940f778f40 100644 --- a/Sources/Commands/PackageTools/Resolve.swift +++ b/Sources/Commands/PackageCommands/Resolve.swift @@ -11,10 +11,12 @@ //===----------------------------------------------------------------------===// import ArgumentParser + import CoreCommands + import TSCUtility -extension SwiftPackageTool { +extension SwiftPackageCommand { struct ResolveOptions: ParsableArguments { @Option(help: "The version to resolve at", transform: { Version($0) }) var version: Version? @@ -39,24 +41,24 @@ extension SwiftPackageTool { @OptionGroup() var resolveOptions: ResolveOptions - func run(_ swiftTool: SwiftTool) throws { + func run(_ swiftCommandState: SwiftCommandState) throws { // If a package is provided, use that to resolve the dependencies. if let packageName = resolveOptions.packageName { - let workspace = try swiftTool.getActiveWorkspace() + let workspace = try swiftCommandState.getActiveWorkspace() try workspace.resolve( packageName: packageName, - root: swiftTool.getWorkspaceRoot(), + root: swiftCommandState.getWorkspaceRoot(), version: resolveOptions.version, branch: resolveOptions.branch, revision: resolveOptions.revision, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) - if swiftTool.observabilityScope.errorsReported { + if swiftCommandState.observabilityScope.errorsReported { throw ExitCode.failure } } else { // Otherwise, run a normal resolve. - try swiftTool.resolve() + try swiftCommandState.resolve() } } } @@ -70,11 +72,11 @@ extension SwiftPackageTool { @OptionGroup() var resolveOptions: ResolveOptions - func run(_ swiftTool: SwiftTool) throws { - swiftTool.observabilityScope.emit(warning: "'fetch' command is deprecated; use 'resolve' instead") + func run(_ swiftCommandState: SwiftCommandState) throws { + swiftCommandState.observabilityScope.emit(warning: "'fetch' command is deprecated; use 'resolve' instead") let resolveCommand = Resolve(globalOptions: _globalOptions, resolveOptions: _resolveOptions) - try resolveCommand.run(swiftTool) + try resolveCommand.run(swiftCommandState) } } } diff --git a/Sources/Commands/PackageTools/ShowDependencies.swift b/Sources/Commands/PackageCommands/ShowDependencies.swift similarity index 83% rename from Sources/Commands/PackageTools/ShowDependencies.swift rename to Sources/Commands/PackageCommands/ShowDependencies.swift index 9818bed91b0..4c33d937181 100644 --- a/Sources/Commands/PackageTools/ShowDependencies.swift +++ b/Sources/Commands/PackageCommands/ShowDependencies.swift @@ -13,14 +13,16 @@ import ArgumentParser import Basics + import CoreCommands + import PackageGraph import class TSCBasic.LocalFileOutputByteStream import protocol TSCBasic.OutputByteStream import var TSCBasic.stdoutStream -extension SwiftPackageTool { +extension SwiftPackageCommand { struct ShowDependencies: SwiftCommand { static let configuration = CommandConfiguration( abstract: "Print the resolved dependency graph") @@ -35,19 +37,25 @@ extension SwiftPackageTool { help: "The absolute or relative path to output the resolved dependency graph.") var outputPath: AbsolutePath? - func run(_ swiftTool: SwiftTool) throws { - let graph = try swiftTool.loadPackageGraph() + func run(_ swiftCommandState: SwiftCommandState) throws { + let graph = try swiftCommandState.loadPackageGraph() // command's result output goes on stdout // ie "swift package show-dependencies" should output to stdout let stream: OutputByteStream = try outputPath.map { try LocalFileOutputByteStream($0) } ?? TSCBasic.stdoutStream Self.dumpDependenciesOf( + graph: graph, rootPackage: graph.rootPackages[graph.rootPackages.startIndex], mode: format, on: stream ) } - static func dumpDependenciesOf(rootPackage: ResolvedPackage, mode: ShowDependenciesMode, on stream: OutputByteStream) { + static func dumpDependenciesOf( + graph: ModulesGraph, + rootPackage: ResolvedPackage, + mode: ShowDependenciesMode, + on stream: OutputByteStream + ) { let dumper: DependenciesDumper switch mode { case .text: @@ -59,14 +67,14 @@ extension SwiftPackageTool { case .flatlist: dumper = FlatListDumper() } - dumper.dump(dependenciesOf: rootPackage, on: stream) + dumper.dump(graph: graph, dependenciesOf: rootPackage, on: stream) stream.flush() } enum ShowDependenciesMode: String, RawRepresentable, CustomStringConvertible, ExpressibleByArgument { case text, dot, json, flatlist - public init?(rawValue: String) { + package init?(rawValue: String) { switch rawValue.lowercased() { case "text": self = .text @@ -81,7 +89,7 @@ extension SwiftPackageTool { } } - public var description: String { + package var description: String { switch self { case .text: return "text" case .dot: return "dot" diff --git a/Sources/Commands/PackageTools/SwiftPackageTool.swift b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift similarity index 88% rename from Sources/Commands/PackageTools/SwiftPackageTool.swift rename to Sources/Commands/PackageCommands/SwiftPackageCommand.swift index 61495a66a1b..e34da31471c 100644 --- a/Sources/Commands/PackageTools/SwiftPackageTool.swift +++ b/Sources/Commands/PackageCommands/SwiftPackageCommand.swift @@ -12,7 +12,9 @@ import ArgumentParser import Basics + import CoreCommands + import Foundation import PackageGraph import PackageLoading @@ -25,14 +27,17 @@ import XCBuildSupport import enum TSCUtility.Diagnostics /// swift-package tool namespace -public struct SwiftPackageTool: AsyncParsableCommand { - public static var configuration = CommandConfiguration( +package struct SwiftPackageCommand: AsyncParsableCommand { + package static var configuration = CommandConfiguration( commandName: "package", _superCommandName: "swift", abstract: "Perform operations on Swift packages", discussion: "SEE ALSO: swift build, swift run, swift test", version: SwiftVersion.current.completeDisplayString, subcommands: [ + AddDependency.self, + AddProduct.self, + AddTarget.self, Clean.self, PurgeCache.self, Reset.self, @@ -61,7 +66,7 @@ public struct SwiftPackageTool: AsyncParsableCommand { ToolsVersionCommand.self, ComputeChecksum.self, ArchiveSource.self, - CompletionTool.self, + CompletionCommand.self, PluginCommand.self, DefaultCommand.self, @@ -74,12 +79,12 @@ public struct SwiftPackageTool: AsyncParsableCommand { @OptionGroup() var globalOptions: GlobalOptions - public static var _errorLabel: String { "error" } + package static var _errorLabel: String { "error" } - public init() {} + package init() {} } -extension SwiftPackageTool { +extension SwiftPackageCommand { // This command is the default when no other subcommand is passed. It is not shown in the help and is never invoked // directly. struct DefaultCommand: SwiftCommand { @@ -97,10 +102,10 @@ extension SwiftPackageTool { @Argument(parsing: .captureForPassthrough) var remaining: [String] = [] - func run(_ swiftTool: SwiftTool) throws { + func run(_ swiftCommandState: SwiftCommandState) throws { // See if have a possible plugin command. guard let command = remaining.first else { - print(SwiftPackageTool.helpMessage()) + print(SwiftPackageCommand.helpMessage()) return } @@ -116,7 +121,7 @@ extension SwiftPackageTool { command: command, options: self.pluginOptions, arguments: self.remaining, - swiftTool: swiftTool + swiftCommandState: swiftCommandState ) } } diff --git a/Sources/Commands/PackageTools/ToolsVersionCommand.swift b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift similarity index 87% rename from Sources/Commands/PackageTools/ToolsVersionCommand.swift rename to Sources/Commands/PackageCommands/ToolsVersionCommand.swift index 951ac0947fb..046fc83e415 100644 --- a/Sources/Commands/PackageTools/ToolsVersionCommand.swift +++ b/Sources/Commands/PackageCommands/ToolsVersionCommand.swift @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// import ArgumentParser + import CoreCommands + import PackageLoading import PackageModel import Workspace @@ -49,13 +51,13 @@ struct ToolsVersionCommand: SwiftCommand { } } - func run(_ swiftTool: SwiftTool) throws { - let pkg = try swiftTool.getPackageRoot() + func run(_ swiftCommandState: SwiftCommandState) throws { + let pkg = try swiftCommandState.getPackageRoot() switch toolsVersionMode { case .display: - let manifestPath = try ManifestLoader.findManifest(packagePath: pkg, fileSystem: swiftTool.fileSystem, currentToolsVersion: .current) - let version = try ToolsVersionParser.parse(manifestPath: manifestPath, fileSystem: swiftTool.fileSystem) + let manifestPath = try ManifestLoader.findManifest(packagePath: pkg, fileSystem: swiftCommandState.fileSystem, currentToolsVersion: .current) + let version = try ToolsVersionParser.parse(manifestPath: manifestPath, fileSystem: swiftCommandState.fileSystem) print("\(version)") case .set(let value): @@ -66,7 +68,7 @@ struct ToolsVersionCommand: SwiftCommand { try ToolsVersionSpecificationWriter.rewriteSpecification( manifestDirectory: pkg, toolsVersion: toolsVersion, - fileSystem: swiftTool.fileSystem + fileSystem: swiftCommandState.fileSystem ) case .setCurrent: @@ -76,7 +78,7 @@ struct ToolsVersionCommand: SwiftCommand { try ToolsVersionSpecificationWriter.rewriteSpecification( manifestDirectory: pkg, toolsVersion: ToolsVersion.current.zeroedPatch, - fileSystem: swiftTool.fileSystem + fileSystem: swiftCommandState.fileSystem ) } } diff --git a/Sources/Commands/PackageTools/Update.swift b/Sources/Commands/PackageCommands/Update.swift similarity index 84% rename from Sources/Commands/PackageTools/Update.swift rename to Sources/Commands/PackageCommands/Update.swift index 395d34d6ce6..fa2586c2c4f 100644 --- a/Sources/Commands/PackageTools/Update.swift +++ b/Sources/Commands/PackageCommands/Update.swift @@ -11,13 +11,15 @@ //===----------------------------------------------------------------------===// import ArgumentParser + import CoreCommands + import Dispatch import PackageModel import PackageGraph import Workspace -extension SwiftPackageTool { +extension SwiftPackageCommand { struct Update: SwiftCommand { static let configuration = CommandConfiguration( abstract: "Update package dependencies") @@ -32,24 +34,24 @@ extension SwiftPackageTool { @Argument(help: "The packages to update") var packages: [String] = [] - func run(_ swiftTool: SwiftTool) throws { - let workspace = try swiftTool.getActiveWorkspace() + func run(_ swiftCommandState: SwiftCommandState) throws { + let workspace = try swiftCommandState.getActiveWorkspace() let changes = try workspace.updateDependencies( - root: swiftTool.getWorkspaceRoot(), + root: swiftCommandState.getWorkspaceRoot(), packages: packages, dryRun: dryRun, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) - if self.dryRun, let changes = changes, let pinsStore = swiftTool.observabilityScope.trap({ try workspace.pinsStore.load() }){ + if self.dryRun, let changes = changes, let pinsStore = swiftCommandState.observabilityScope.trap({ try workspace.pinsStore.load() }){ self.logPackageChanges(changes: changes, pins: pinsStore) } if !self.dryRun { // Throw if there were errors when loading the graph. // The actual errors will be printed before exiting. - guard !swiftTool.observabilityScope.errorsReported else { + guard !swiftCommandState.observabilityScope.errorsReported else { throw ExitCode.failure } } @@ -71,7 +73,7 @@ extension SwiftPackageTool { case .removed: report += "\n" report += "- \(package.identity) \(currentVersion)" - case .unchanged: + case .unchanged, .usesLibrary: continue } } diff --git a/Sources/Commands/Snippets/CardStack.swift b/Sources/Commands/Snippets/CardStack.swift index 7d2f3cb52d7..e17115cfc90 100644 --- a/Sources/Commands/Snippets/CardStack.swift +++ b/Sources/Commands/Snippets/CardStack.swift @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// import Basics + import CoreCommands + import PackageGraph import PackageModel @@ -34,17 +36,17 @@ struct CardStack { var cards = [Card]() /// The tool used for eventually building and running a chosen snippet. - var swiftTool: SwiftTool + var swiftCommandState: SwiftCommandState /// When true, the escape sequence for clearing the terminal should be /// printed first. private var needsToClearScreen = true - init(package: ResolvedPackage, snippetGroups: [SnippetGroup], swiftTool: SwiftTool) { + init(package: ResolvedPackage, snippetGroups: [SnippetGroup], swiftCommandState: SwiftCommandState) { // this interaction is done on stdout self.terminal = TerminalController(stream: TSCBasic.stdoutStream)! - self.cards = [TopCard(package: package, snippetGroups: snippetGroups, swiftTool: swiftTool)] - self.swiftTool = swiftTool + self.cards = [TopCard(package: package, snippetGroups: snippetGroups, swiftCommandState: swiftCommandState)] + self.swiftCommandState = swiftCommandState } mutating func push(_ card: Card) { @@ -102,7 +104,7 @@ struct CardStack { case let .pop(error): cards.removeLast() if let error { - self.swiftTool.observabilityScope.emit(error) + self.swiftCommandState.observabilityScope.emit(error) needsToClearScreen = false } else { needsToClearScreen = !cards.isEmpty diff --git a/Sources/Commands/Snippets/Cards/SnippetCard.swift b/Sources/Commands/Snippets/Cards/SnippetCard.swift index 8d5492e9416..aabfc2f5025 100644 --- a/Sources/Commands/Snippets/Cards/SnippetCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetCard.swift @@ -11,7 +11,12 @@ //===----------------------------------------------------------------------===// import Basics + import CoreCommands + + +import SPMBuildCore + import PackageModel import enum TSCBasic.ProcessEnv @@ -36,7 +41,7 @@ struct SnippetCard: Card { var number: Int /// The tool used for eventually building and running a chosen snippet. - var swiftTool: SwiftTool + var swiftCommandState: SwiftCommandState func render() -> String { var rendered = colorized { @@ -93,9 +98,9 @@ struct SnippetCard: Card { func runExample() throws { print("Building '\(snippet.path)'\n") - let buildSystem = try swiftTool.createBuildSystem(explicitProduct: snippet.name) + let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: snippet.name) try buildSystem.build(subset: .product(snippet.name)) - let executablePath = try swiftTool.productsBuildParameters.buildPath.appending(component: snippet.name) + let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: snippet.name) if let exampleTarget = try buildSystem.getPackageGraph().allTargets.first(where: { $0.name == snippet.name }) { try ProcessEnv.chdir(exampleTarget.sources.paths[0].parentDirectory) } diff --git a/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift b/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift index 220246ad438..5b47aaf238c 100644 --- a/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift +++ b/Sources/Commands/Snippets/Cards/SnippetGroupCard.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import CoreCommands + import PackageModel /// A card showing the snippets in a ``SnippetGroup``. @@ -19,7 +20,7 @@ struct SnippetGroupCard: Card { var snippetGroup: SnippetGroup /// The tool used for eventually building and running a chosen snippet. - var swiftTool: SwiftTool + var swiftCommandState: SwiftCommandState var inputPrompt: String? { return """ @@ -39,9 +40,9 @@ struct SnippetGroupCard: Card { } if let index = Int(line), snippetGroup.snippets.indices.contains(index) { - return .push(SnippetCard(snippet: snippetGroup.snippets[index], number: index, swiftTool: swiftTool)) + return .push(SnippetCard(snippet: snippetGroup.snippets[index], number: index, swiftCommandState: swiftCommandState)) } else if let foundSnippetIndex = snippetGroup.snippets.firstIndex(where: { $0.name == line }) { - return .push(SnippetCard(snippet: snippetGroup.snippets[foundSnippetIndex], number: foundSnippetIndex, swiftTool: swiftTool)) + return .push(SnippetCard(snippet: snippetGroup.snippets[foundSnippetIndex], number: foundSnippetIndex, swiftCommandState: swiftCommandState)) } else { print(red { "There is not a snippet by that name or index." }) return nil diff --git a/Sources/Commands/Snippets/Cards/TopCard.swift b/Sources/Commands/Snippets/Cards/TopCard.swift index 6a101f9778e..bb0398fda1e 100644 --- a/Sources/Commands/Snippets/Cards/TopCard.swift +++ b/Sources/Commands/Snippets/Cards/TopCard.swift @@ -10,7 +10,9 @@ // //===----------------------------------------------------------------------===// + import CoreCommands + import Foundation import PackageModel import PackageGraph @@ -24,12 +26,12 @@ struct TopCard: Card { let snippetGroups: [SnippetGroup] /// The tool used for eventually building and running a chosen snippet. - let swiftTool: SwiftTool + let swiftCommandState: SwiftCommandState - init(package: ResolvedPackage, snippetGroups: [SnippetGroup], swiftTool: SwiftTool) { + init(package: ResolvedPackage, snippetGroups: [SnippetGroup], swiftCommandState: SwiftCommandState) { self.package = package self.snippetGroups = snippetGroups - self.swiftTool = swiftTool + self.swiftCommandState = swiftCommandState } var inputPrompt: String? { @@ -122,9 +124,9 @@ struct TopCard: Card { } if let index = Int(line), snippetGroups.indices.contains(index) { - return .push(SnippetGroupCard(snippetGroup: snippetGroups[index], swiftTool: swiftTool)) + return .push(SnippetGroupCard(snippetGroup: snippetGroups[index], swiftCommandState: swiftCommandState)) } else if let groupByName = snippetGroups.first(where: { $0.name == line }) { - return .push(SnippetGroupCard(snippetGroup: groupByName, swiftTool: swiftTool)) + return .push(SnippetGroupCard(snippetGroup: groupByName, swiftCommandState: swiftCommandState)) } else { print(red { "There is not a group by that name or index." }) return nil @@ -151,6 +153,8 @@ fileprivate extension Target.Kind { return "snippets" case .macro: return "macros" + case .providedLibrary: + return "provided libraries" } } } diff --git a/Sources/Commands/Snippets/Colorful.swift b/Sources/Commands/Snippets/Colorful.swift index 0bf12d9c3b5..23a264e6881 100644 --- a/Sources/Commands/Snippets/Colorful.swift +++ b/Sources/Commands/Snippets/Colorful.swift @@ -61,20 +61,20 @@ extension Optional: Colorful where Wrapped: Colorful { } @resultBuilder -public struct ColorBuilder { - public static func buildOptional(_ component: [Colorful]?) -> [Colorful] { +package struct ColorBuilder { + package static func buildOptional(_ component: [Colorful]?) -> [Colorful] { return component ?? [] } - public static func buildBlock(_ components: Colorful...) -> [Colorful] { + package static func buildBlock(_ components: Colorful...) -> [Colorful] { return components } - public static func buildEither(first component: [Colorful]) -> [Colorful] { + package static func buildEither(first component: [Colorful]) -> [Colorful] { return component } - public static func buildEither(second component: [Colorful]) -> [Colorful] { + package static func buildEither(second component: [Colorful]) -> [Colorful] { return component } } @@ -130,75 +130,75 @@ enum Color4: String, Color { } } -public func colorized(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func colorized(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.reset, builder) } -public func plain(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func plain(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.reset, builder) } -public func black(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func black(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.black, builder) } -public func brightBlack(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightBlack(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightBlack, builder) } -public func red(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func red(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.red, builder) } -public func brightRed(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightRed(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightRed, builder) } -public func green(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func green(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.green, builder) } -public func brightGreen(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightGreen(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightGreen, builder) } -public func yellow(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func yellow(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.yellow, builder) } -public func brightYellow(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightYellow(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightYellow, builder) } -public func blue(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func blue(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.blue, builder) } -public func brightBlue(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightBlue(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightBlue, builder) } -public func magenta(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func magenta(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.magenta, builder) } -public func brightMagenta(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightMagenta(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightMagenta, builder) } -public func cyan(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func cyan(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.cyan, builder) } -public func brightCyan(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightCyan(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightCyan, builder) } -public func white(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func white(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.white, builder) } -public func brightWhite(@ColorBuilder builder: () -> [Colorful]) -> Colorful { +package func brightWhite(@ColorBuilder builder: () -> [Colorful]) -> Colorful { return Colorized(.brightWhite, builder) } diff --git a/Sources/Commands/SwiftBuildTool.swift b/Sources/Commands/SwiftBuildCommand.swift similarity index 50% rename from Sources/Commands/SwiftBuildTool.swift rename to Sources/Commands/SwiftBuildCommand.swift index 521eb0c9405..b6813895e26 100644 --- a/Sources/Commands/SwiftBuildTool.swift +++ b/Sources/Commands/SwiftBuildCommand.swift @@ -12,9 +12,13 @@ import ArgumentParser import Basics + import Build + import CoreCommands + import PackageGraph + import SPMBuildCore import XCBuildSupport @@ -40,7 +44,7 @@ extension BuildSubset { } } -struct BuildToolOptions: ParsableArguments { +struct BuildCommandOptions: ParsableArguments { /// Returns the build subset specified with the options. func buildSubset(observabilityScope: ObservabilityScope) -> BuildSubset? { var allSubsets: [BuildSubset] = [] @@ -69,6 +73,12 @@ struct BuildToolOptions: ParsableArguments { @Flag(help: "Build both source and test targets") var buildTests: Bool = false + /// Whether to enable code coverage. + @Flag(name: .customLong("code-coverage"), + inversion: .prefixedEnableDisable, + help: "Enable code coverage") + var enableCodeCoverage: Bool = false + /// If the binary output path should be printed. @Flag(name: .customLong("show-bin-path"), help: "Print the binary output path") var shouldPrintBinPath: Bool = false @@ -88,12 +98,27 @@ struct BuildToolOptions: ParsableArguments { /// If should link the Swift stdlib statically. @Flag(name: .customLong("static-swift-stdlib"), inversion: .prefixedNo, help: "Link Swift stdlib statically") - public var shouldLinkStaticSwiftStdlib: Bool = false + package var shouldLinkStaticSwiftStdlib: Bool = false + + /// Which testing libraries to use (and any related options.) + @OptionGroup() + var testLibraryOptions: TestLibraryOptions + + func validate() throws { + // If --build-tests was not specified, it does not make sense to enable + // or disable either testing library. + if !buildTests { + if testLibraryOptions.explicitlyEnableXCTestSupport != nil + || testLibraryOptions.explicitlyEnableSwiftTestingLibrarySupport != nil { + throw StringError("pass --build-tests to build test targets") + } + } + } } -/// swift-build tool namespace -public struct SwiftBuildTool: SwiftCommand { - public static var configuration = CommandConfiguration( +/// swift-build command namespace +package struct SwiftBuildCommand: AsyncSwiftCommand { + package static var configuration = CommandConfiguration( commandName: "build", _superCommandName: "swift", abstract: "Build sources into binary products", @@ -102,19 +127,19 @@ public struct SwiftBuildTool: SwiftCommand { helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) @OptionGroup() - public var globalOptions: GlobalOptions + package var globalOptions: GlobalOptions @OptionGroup() - var options: BuildToolOptions + var options: BuildCommandOptions - public func run(_ swiftTool: SwiftTool) throws { + package func run(_ swiftCommandState: SwiftCommandState) async throws { if options.shouldPrintBinPath { - return try print(swiftTool.productsBuildParameters.buildPath.description) + return try print(swiftCommandState.productsBuildParameters.buildPath.description) } if options.printManifestGraphviz { // FIXME: Doesn't seem ideal that we need an explicit build operation, but this concretely uses the `LLBuildManifest`. - guard let buildOperation = try swiftTool.createBuildSystem(explicitBuildSystem: .native) as? BuildOperation else { + guard let buildOperation = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native) as? BuildOperation else { throw StringError("asked for native build system but did not get it") } let buildManifest = try buildOperation.getBuildManifest() @@ -126,12 +151,55 @@ public struct SwiftBuildTool: SwiftCommand { return } - guard let subset = options.buildSubset(observabilityScope: swiftTool.observabilityScope) else { + guard let subset = options.buildSubset(observabilityScope: swiftCommandState.observabilityScope) else { throw ExitCode.failure } - let buildSystem = try swiftTool.createBuildSystem( + + var productsBuildParameters = try swiftCommandState.productsBuildParameters + var toolsBuildParameters = try swiftCommandState.toolsBuildParameters + + // Clean out the code coverage directory that may contain stale + // profraw files from a previous run of the code coverage tool. + if self.options.enableCodeCoverage { + try swiftCommandState.fileSystem.removeFileTree(swiftCommandState.productsBuildParameters.codeCovPath) + productsBuildParameters.testingParameters.enableCodeCoverage = true + toolsBuildParameters.testingParameters.enableCodeCoverage = true + } + + if case .allIncludingTests = subset { + func updateTestingParameters(of buildParameters: inout BuildParameters, library: BuildParameters.Testing.Library) { + buildParameters.testingParameters = .init( + configuration: buildParameters.configuration, + targetTriple: buildParameters.triple, + enableCodeCoverage: buildParameters.testingParameters.enableCodeCoverage, + enableTestability: buildParameters.testingParameters.enableTestability, + experimentalTestOutput: buildParameters.testingParameters.experimentalTestOutput, + forceTestDiscovery: globalOptions.build.enableTestDiscovery, + testEntryPointPath: globalOptions.build.testEntryPointPath, + library: library + ) + } + for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) { + updateTestingParameters(of: &productsBuildParameters, library: library) + updateTestingParameters(of: &toolsBuildParameters, library: library) + try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) + } + } else { + try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters) + } + } + + private func build( + _ swiftCommandState: SwiftCommandState, + subset: BuildSubset, + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters + ) throws { + let buildSystem = try swiftCommandState.createBuildSystem( explicitProduct: options.product, shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, // command result output goes on stdout // ie "swift build" should output to stdout outputStream: TSCBasic.stdoutStream @@ -143,11 +211,11 @@ public struct SwiftBuildTool: SwiftCommand { } } - public init() {} + package init() {} } -public extension _SwiftCommand { - func buildSystemProvider(_ swiftTool: SwiftTool) throws -> BuildSystemProvider { - swiftTool.defaultBuildSystemProvider +package extension _SwiftCommand { + func buildSystemProvider(_ swiftCommandState: SwiftCommandState) throws -> BuildSystemProvider { + swiftCommandState.defaultBuildSystemProvider } } diff --git a/Sources/Commands/SwiftRunTool.swift b/Sources/Commands/SwiftRunCommand.swift similarity index 74% rename from Sources/Commands/SwiftRunTool.swift rename to Sources/Commands/SwiftRunCommand.swift index edc89214c1a..98838358048 100644 --- a/Sources/Commands/SwiftRunTool.swift +++ b/Sources/Commands/SwiftRunCommand.swift @@ -12,11 +12,15 @@ import ArgumentParser import Basics + import CoreCommands + import Foundation import PackageGraph import PackageModel +import SPMBuildCore + import enum TSCBasic.ProcessEnv import func TSCBasic.exec @@ -48,13 +52,13 @@ extension RunError: CustomStringConvertible { } } -struct RunToolOptions: ParsableArguments { +struct RunCommandOptions: ParsableArguments { enum RunMode: EnumerableFlag { case repl case debugger case run - static func help(for value: RunToolOptions.RunMode) -> ArgumentHelp? { + static func help(for value: RunCommandOptions.RunMode) -> ArgumentHelp? { switch value { case .repl: return "Launch Swift REPL for the package" @@ -89,9 +93,9 @@ struct RunToolOptions: ParsableArguments { var arguments: [String] = [] } -/// swift-run tool namespace -public struct SwiftRunTool: SwiftCommand { - public static var configuration = CommandConfiguration( +/// swift-run command namespace +package struct SwiftRunCommand: AsyncSwiftCommand { + package static var configuration = CommandConfiguration( commandName: "run", _superCommandName: "swift", abstract: "Build and run an executable product", @@ -100,18 +104,18 @@ public struct SwiftRunTool: SwiftCommand { helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) @OptionGroup() - public var globalOptions: GlobalOptions + package var globalOptions: GlobalOptions @OptionGroup() - var options: RunToolOptions + var options: RunCommandOptions - public var toolWorkspaceConfiguration: ToolWorkspaceConfiguration { + package var toolWorkspaceConfiguration: ToolWorkspaceConfiguration { return .init(wantsREPLProduct: options.mode == .repl) } - public func run(_ swiftTool: SwiftTool) throws { + package func run(_ swiftCommandState: SwiftCommandState) async throws { if options.shouldBuildTests && options.shouldSkipBuild { - swiftTool.observabilityScope.emit( + swiftCommandState.observabilityScope.emit( .mutuallyExclusiveArgumentsError(arguments: ["--build-tests", "--skip-build"]) ) throw ExitCode.failure @@ -121,14 +125,14 @@ public struct SwiftRunTool: SwiftCommand { case .repl: // Load a custom package graph which has a special product for REPL. let graphLoader = { - try swiftTool.loadPackageGraph( + try swiftCommandState.loadPackageGraph( explicitProduct: self.options.executable ) } // Construct the build operation. // FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the REPL. rdar://86112934 - let buildSystem = try swiftTool.createBuildSystem( + let buildSystem = try swiftCommandState.createBuildSystem( explicitBuildSystem: .native, cacheBuildManifest: false, packageGraphLoader: graphLoader @@ -141,15 +145,15 @@ public struct SwiftRunTool: SwiftCommand { let arguments = try buildSystem.buildPlan.createREPLArguments() print("Launching Swift REPL with arguments: \(arguments.joined(separator: " "))") try self.run( - fileSystem: swiftTool.fileSystem, - executablePath: swiftTool.getTargetToolchain().swiftInterpreterPath, - originalWorkingDirectory: swiftTool.originalWorkingDirectory, + fileSystem: swiftCommandState.fileSystem, + executablePath: swiftCommandState.getTargetToolchain().swiftInterpreterPath, + originalWorkingDirectory: swiftCommandState.originalWorkingDirectory, arguments: arguments ) case .debugger: do { - let buildSystem = try swiftTool.createBuildSystem(explicitProduct: options.executable) + let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: options.executable) let productName = try findProductName(in: buildSystem.getPackageGraph()) if options.shouldBuildTests { try buildSystem.build(subset: .allIncludingTests) @@ -157,41 +161,41 @@ public struct SwiftRunTool: SwiftCommand { try buildSystem.build(subset: .product(productName)) } - let executablePath = try swiftTool.productsBuildParameters.buildPath.appending(component: productName) + let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: productName) // Make sure we are running from the original working directory. - let cwd: AbsolutePath? = swiftTool.fileSystem.currentWorkingDirectory - if cwd == nil || swiftTool.originalWorkingDirectory != cwd { - try ProcessEnv.chdir(swiftTool.originalWorkingDirectory) + let cwd: AbsolutePath? = swiftCommandState.fileSystem.currentWorkingDirectory + if cwd == nil || swiftCommandState.originalWorkingDirectory != cwd { + try ProcessEnv.chdir(swiftCommandState.originalWorkingDirectory) } - let pathRelativeToWorkingDirectory = executablePath.relative(to: swiftTool.originalWorkingDirectory) - let lldbPath = try swiftTool.getTargetToolchain().getLLDB() + let pathRelativeToWorkingDirectory = executablePath.relative(to: swiftCommandState.originalWorkingDirectory) + let lldbPath = try swiftCommandState.getTargetToolchain().getLLDB() try exec(path: lldbPath.pathString, args: ["--", pathRelativeToWorkingDirectory.pathString] + options.arguments) } catch let error as RunError { - swiftTool.observabilityScope.emit(error) + swiftCommandState.observabilityScope.emit(error) throw ExitCode.failure } case .run: // Detect deprecated uses of swift run to interpret scripts. - if let executable = options.executable, try isValidSwiftFilePath(fileSystem: swiftTool.fileSystem, path: executable) { - swiftTool.observabilityScope.emit(.runFileDeprecation) + if let executable = options.executable, try isValidSwiftFilePath(fileSystem: swiftCommandState.fileSystem, path: executable) { + swiftCommandState.observabilityScope.emit(.runFileDeprecation) // Redirect execution to the toolchain's swift executable. - let swiftInterpreterPath = try swiftTool.getTargetToolchain().swiftInterpreterPath + let swiftInterpreterPath = try swiftCommandState.getTargetToolchain().swiftInterpreterPath // Prepend the script to interpret to the arguments. let arguments = [executable] + options.arguments try self.run( - fileSystem: swiftTool.fileSystem, + fileSystem: swiftCommandState.fileSystem, executablePath: swiftInterpreterPath, - originalWorkingDirectory: swiftTool.originalWorkingDirectory, + originalWorkingDirectory: swiftCommandState.originalWorkingDirectory, arguments: arguments ) return } do { - let buildSystem = try swiftTool.createBuildSystem(explicitProduct: options.executable) + let buildSystem = try swiftCommandState.createBuildSystem(explicitProduct: options.executable) let productName = try findProductName(in: buildSystem.getPackageGraph()) if options.shouldBuildTests { try buildSystem.build(subset: .allIncludingTests) @@ -199,24 +203,24 @@ public struct SwiftRunTool: SwiftCommand { try buildSystem.build(subset: .product(productName)) } - let executablePath = try swiftTool.productsBuildParameters.buildPath.appending(component: productName) + let executablePath = try swiftCommandState.productsBuildParameters.buildPath.appending(component: productName) try self.run( - fileSystem: swiftTool.fileSystem, + fileSystem: swiftCommandState.fileSystem, executablePath: executablePath, - originalWorkingDirectory: swiftTool.originalWorkingDirectory, + originalWorkingDirectory: swiftCommandState.originalWorkingDirectory, arguments: options.arguments ) } catch Diagnostics.fatalError { throw ExitCode.failure } catch let error as RunError { - swiftTool.observabilityScope.emit(error) + swiftCommandState.observabilityScope.emit(error) throw ExitCode.failure } } } /// Returns the path to the correct executable based on options. - private func findProductName(in graph: PackageGraph) throws -> String { + private func findProductName(in graph: ModulesGraph) throws -> String { if let executable = options.executable { let executableExists = graph.allProducts.contains { ($0.type == .executable || $0.type == .snippet) && $0.name == executable } guard executableExists else { @@ -284,15 +288,32 @@ public struct SwiftRunTool: SwiftCommand { /// A safe wrapper of TSCBasic.exec. private func execute(path: String, args: [String]) throws -> Never { #if !os(Windows) - // On platforms other than Windows, signal(SIGINT, SIG_IGN) is used for handling SIGINT by DispatchSourceSignal, - // but this process is about to be replaced by exec, so SIG_IGN must be returned to default. - signal(SIGINT, SIG_DFL) + // Dispatch will disable almost all asynchronous signals on its worker threads, and this is called from `async` + // context. To correctly `exec` a freshly built binary, we will need to: + // 1. reset the signal masks + for i in 1.. Bool { - // Honor the user's explicit command-line selection, if any. - if let callerSuppliedValue = _enableSwiftTestingLibrarySupport { - return callerSuppliedValue - } - - // If the active package has a dependency on swift-testing, automatically enable support for it so that extra steps are not needed. - let workspace = try swiftTool.getActiveWorkspace() - let root = try swiftTool.getWorkspaceRoot() - let rootManifests = try temp_await { - workspace.loadRootManifests( - packages: root.packages, - observabilityScope: swiftTool.observabilityScope, - completion: $0 - ) - } - - // Is swift-testing among the dependencies of the package being built? - // If so, enable support. - let isEnabledByDependency = rootManifests.values.lazy - .flatMap(\.dependencies) - .map(\.identity) - .map(String.init(describing:)) - .contains("swift-testing") - if isEnabledByDependency { - swiftTool.observabilityScope.emit(debug: "Enabling swift-testing support due to its presence as a package dependency.") - return true - } - - // Is swift-testing the package being built itself (unlikely)? If so, - // enable support. - let isEnabledByName = root.packages.lazy - .map(PackageIdentity.init(path:)) - .map(String.init(describing:)) - .contains("swift-testing") - if isEnabledByName { - swiftTool.observabilityScope.emit(debug: "Enabling swift-testing support because it is a root package.") - return true - } - - // Default to disabled since swift-testing is experimental (opt-in.) - return false - } } -struct TestToolOptions: ParsableArguments { +struct TestCommandOptions: ParsableArguments { @OptionGroup() var globalOptions: GlobalOptions @OptionGroup() var sharedOptions: SharedOptions + /// Which testing libraries to use (and any related options.) + @OptionGroup() + var testLibraryOptions: TestLibraryOptions + /// If tests should run in parallel mode. @Flag(name: .customLong("parallel"), inversion: .prefixedNo, @@ -196,15 +149,30 @@ struct TestToolOptions: ParsableArguments { /// Configure test output. @Option(help: ArgumentHelp("", visibility: .hidden)) - public var testOutput: TestOutput = .default + package var testOutput: TestOutput = .default var enableExperimentalTestOutput: Bool { return testOutput == .experimentalSummary } + + /// Path where swift-testing's JSON configuration should be read. + @Option(name: .customLong("experimental-configuration-path"), + help: .hidden) + var configurationPath: AbsolutePath? + + /// Path where swift-testing's JSON output should be written. + @Option(name: .customLong("experimental-event-stream-output"), + help: .hidden) + var eventStreamOutputPath: AbsolutePath? + + /// The schema version of swift-testing's JSON input/output. + @Option(name: .customLong("experimental-event-stream-version"), + help: .hidden) + var eventStreamVersion: Int? } /// Tests filtering specifier, which is used to filter tests to run. -public enum TestCaseSpecifier { +package enum TestCaseSpecifier { /// No filtering case none @@ -219,7 +187,7 @@ public enum TestCaseSpecifier { } /// Different styles of test output. -public enum TestOutput: String, ExpressibleByArgument { +package enum TestOutput: String, ExpressibleByArgument { /// Whatever `xctest` emits to the console. case `default` @@ -231,8 +199,8 @@ public enum TestOutput: String, ExpressibleByArgument { } /// swift-test tool namespace -public struct SwiftTestTool: SwiftCommand { - public static var configuration = CommandConfiguration( +package struct SwiftTestCommand: AsyncSwiftCommand { + package static var configuration = CommandConfiguration( commandName: "test", _superCommandName: "swift", abstract: "Build and run tests", @@ -243,38 +211,49 @@ public struct SwiftTestTool: SwiftCommand { ], helpNames: [.short, .long, .customLong("help", withSingleDash: true)]) - public var globalOptions: GlobalOptions { + package var globalOptions: GlobalOptions { options.globalOptions } @OptionGroup() - var options: TestToolOptions + var options: TestCommandOptions // MARK: - XCTest - private func xctestRun(_ swiftTool: SwiftTool) throws { + private func xctestRun(_ swiftCommandState: SwiftCommandState) async throws { // validate XCTest available on darwin based systems - let toolchain = try swiftTool.getTargetToolchain() - let isHostTestingAvailable = try swiftTool.getHostToolchain().swiftSDK.supportsTesting - if (toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil) || !isHostTestingAvailable { - throw TestError.xctestNotAvailable + let toolchain = try swiftCommandState.getTargetToolchain() + if case let .unsupported(reason) = try swiftCommandState.getHostToolchain().swiftSDK.xctestSupport { + if let reason { + throw TestError.xctestNotAvailable(reason: reason) + } else { + throw TestError.xcodeNotInstalled + } + } else if toolchain.targetTriple.isDarwin() && toolchain.xctestPath == nil { + throw TestError.xcodeNotInstalled } - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: .xctest) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: .xctest) // Remove test output from prior runs and validate priors. - if self.options.enableExperimentalTestOutput && buildParameters.triple.supportsTestSummary { - _ = try? localFileSystem.removeFileTree(buildParameters.testOutputPath) + if self.options.enableExperimentalTestOutput && productsBuildParameters.triple.supportsTestSummary { + _ = try? localFileSystem.removeFileTree(productsBuildParameters.testOutputPath) } - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, library: .xctest) + let testProducts = try buildTestsIfNeeded(swiftCommandState: swiftCommandState, library: .xctest) if !self.options.shouldRunInParallel { - let xctestArgs = try xctestArgs(for: testProducts, swiftTool: swiftTool) - try runTestProducts(testProducts, additionalArguments: xctestArgs, buildParameters: buildParameters, swiftTool: swiftTool, library: .xctest) + let xctestArgs = try xctestArgs(for: testProducts, swiftCommandState: swiftCommandState) + try await runTestProducts( + testProducts, + additionalArguments: xctestArgs, + productsBuildParameters: productsBuildParameters, + swiftCommandState: swiftCommandState, + library: .xctest + ) } else { let testSuites = try TestingSupport.getTestSuites( in: testProducts, - swiftTool: swiftTool, + swiftCommandState: swiftCommandState, enableCodeCoverage: options.enableCodeCoverage, shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, experimentalTestOutput: options.enableExperimentalTestOutput, @@ -282,56 +261,56 @@ public struct SwiftTestTool: SwiftCommand { ) let tests = try testSuites .filteredTests(specifier: options.testCaseSpecifier) - .skippedTests(specifier: options.skippedTests(fileSystem: swiftTool.fileSystem)) + .skippedTests(specifier: options.skippedTests(fileSystem: swiftCommandState.fileSystem)) // If there were no matches, emit a warning and exit. if tests.isEmpty { - swiftTool.observabilityScope.emit(.noMatchingTests) - try generateXUnitOutputIfRequested(for: [], swiftTool: swiftTool) + swiftCommandState.observabilityScope.emit(.noMatchingTests) + try generateXUnitOutputIfRequested(for: [], swiftCommandState: swiftCommandState) return } // Clean out the code coverage directory that may contain stale // profraw files from a previous run of the code coverage tool. if self.options.enableCodeCoverage { - try swiftTool.fileSystem.removeFileTree(buildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(productsBuildParameters.codeCovPath) } // Run the tests using the parallel runner. let runner = ParallelTestRunner( bundlePaths: testProducts.map { $0.bundlePath }, - cancellator: swiftTool.cancellator, + cancellator: swiftCommandState.cancellator, toolchain: toolchain, numJobs: options.numberOfWorkers ?? ProcessInfo.processInfo.activeProcessorCount, buildOptions: globalOptions.build, - buildParameters: buildParameters, - shouldOutputSuccess: swiftTool.logLevel <= .info, - observabilityScope: swiftTool.observabilityScope + productsBuildParameters: productsBuildParameters, + shouldOutputSuccess: swiftCommandState.logLevel <= .info, + observabilityScope: swiftCommandState.observabilityScope ) let testResults = try runner.run(tests) - try generateXUnitOutputIfRequested(for: testResults, swiftTool: swiftTool) + try generateXUnitOutputIfRequested(for: testResults, swiftCommandState: swiftCommandState) // process code Coverage if request if self.options.enableCodeCoverage, runner.ranSuccessfully { - try processCodeCoverage(testProducts, swiftTool: swiftTool, library: .xctest) + try await processCodeCoverage(testProducts, swiftCommandState: swiftCommandState, library: .xctest) } if !runner.ranSuccessfully { - swiftTool.executionStatus = .failure + swiftCommandState.executionStatus = .failure } if self.options.enableExperimentalTestOutput, !runner.ranSuccessfully { - try Self.handleTestOutput(buildParameters: buildParameters, packagePath: testProducts[0].packagePath) + try Self.handleTestOutput(productsBuildParameters: productsBuildParameters, packagePath: testProducts[0].packagePath) } } } - private func xctestArgs(for testProducts: [BuiltTestProduct], swiftTool: SwiftTool) throws -> [String] { + private func xctestArgs(for testProducts: [BuiltTestProduct], swiftCommandState: SwiftCommandState) throws -> [String] { switch options.testCaseSpecifier { case .none: - if case .skip = options.skippedTests(fileSystem: swiftTool.fileSystem) { + if case .skip = options.skippedTests(fileSystem: swiftCommandState.fileSystem) { fallthrough } else { return [] @@ -340,13 +319,13 @@ public struct SwiftTestTool: SwiftCommand { case .regex, .specific, .skip: // If old specifier `-s` option was used, emit deprecation notice. if case .specific = options.testCaseSpecifier { - swiftTool.observabilityScope.emit(warning: "'--specifier' option is deprecated; use '--filter' instead") + swiftCommandState.observabilityScope.emit(warning: "'--specifier' option is deprecated; use '--filter' instead") } // Find the tests we need to run. let testSuites = try TestingSupport.getTestSuites( in: testProducts, - swiftTool: swiftTool, + swiftCommandState: swiftCommandState, enableCodeCoverage: options.enableCodeCoverage, shouldSkipBuilding: options.sharedOptions.shouldSkipBuilding, experimentalTestOutput: options.enableExperimentalTestOutput, @@ -354,11 +333,11 @@ public struct SwiftTestTool: SwiftCommand { ) let tests = try testSuites .filteredTests(specifier: options.testCaseSpecifier) - .skippedTests(specifier: options.skippedTests(fileSystem: swiftTool.fileSystem)) + .skippedTests(specifier: options.skippedTests(fileSystem: swiftCommandState.fileSystem)) // If there were no matches, emit a warning. if tests.isEmpty { - swiftTool.observabilityScope.emit(.noMatchingTests) + swiftCommandState.observabilityScope.emit(.noMatchingTests) } return TestRunner.xctestArguments(forTestSpecifiers: tests.map(\.specifier)) @@ -366,13 +345,16 @@ public struct SwiftTestTool: SwiftCommand { } /// Generate xUnit file if requested. - private func generateXUnitOutputIfRequested(for testResults: [ParallelTestRunner.TestResult], swiftTool: SwiftTool) throws { + private func generateXUnitOutputIfRequested( + for testResults: [ParallelTestRunner.TestResult], + swiftCommandState: SwiftCommandState + ) throws { guard let xUnitOutput = options.xUnitOutput else { return } let generator = XUnitGenerator( - fileSystem: swiftTool.fileSystem, + fileSystem: swiftCommandState.fileSystem, results: testResults ) try generator.generate(at: xUnitOutput) @@ -380,61 +362,73 @@ public struct SwiftTestTool: SwiftCommand { // MARK: - swift-testing - private func swiftTestingRun(_ swiftTool: SwiftTool) throws { - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: .swiftTesting) - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, library: .swiftTesting) + private func swiftTestingRun(_ swiftCommandState: SwiftCommandState) async throws { + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: .swiftTesting) + let testProducts = try buildTestsIfNeeded(swiftCommandState: swiftCommandState, library: .swiftTesting) let additionalArguments = Array(CommandLine.arguments.dropFirst()) - try runTestProducts(testProducts, additionalArguments: additionalArguments, buildParameters: buildParameters, swiftTool: swiftTool, library: .swiftTesting) + try await runTestProducts( + testProducts, + additionalArguments: additionalArguments, + productsBuildParameters: productsBuildParameters, + swiftCommandState: swiftCommandState, + library: .swiftTesting + ) } // MARK: - Common implementation - public func run(_ swiftTool: SwiftTool) throws { + package func run(_ swiftCommandState: SwiftCommandState) async throws { do { // Validate commands arguments - try self.validateArguments(observabilityScope: swiftTool.observabilityScope) + try self.validateArguments(observabilityScope: swiftCommandState.observabilityScope) } catch { - swiftTool.observabilityScope.emit(error) + swiftCommandState.observabilityScope.emit(error) throw ExitCode.failure } if self.options.shouldPrintCodeCovPath { - try printCodeCovPath(swiftTool) + try printCodeCovPath(swiftCommandState) } else if self.options._deprecated_shouldListTests { // backward compatibility 6/2022 for deprecation of flag into a subcommand let command = try List.parse() - try command.run(swiftTool) + try command.run(swiftCommandState) } else { - if try options.sharedOptions.enableSwiftTestingLibrarySupport(swiftTool: swiftTool) { - try swiftTestingRun(swiftTool) + if try options.testLibraryOptions.enableSwiftTestingLibrarySupport(swiftCommandState: swiftCommandState) { + try await swiftTestingRun(swiftCommandState) } - if options.sharedOptions.enableXCTestSupport { - try xctestRun(swiftTool) + if options.testLibraryOptions.enableXCTestSupport { + try await xctestRun(swiftCommandState) } } } - private func runTestProducts(_ testProducts: [BuiltTestProduct], additionalArguments: [String], buildParameters: BuildParameters, swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { + private func runTestProducts( + _ testProducts: [BuiltTestProduct], + additionalArguments: [String], + productsBuildParameters: BuildParameters, + swiftCommandState: SwiftCommandState, + library: BuildParameters.Testing.Library + ) async throws { // Clean out the code coverage directory that may contain stale // profraw files from a previous run of the code coverage tool. if self.options.enableCodeCoverage { - try swiftTool.fileSystem.removeFileTree(buildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(productsBuildParameters.codeCovPath) } - let toolchain = try swiftTool.getTargetToolchain() + let toolchain = try swiftCommandState.getTargetToolchain() let testEnv = try TestingSupport.constructTestEnvironment( toolchain: toolchain, - buildParameters: buildParameters, + destinationBuildParameters: productsBuildParameters, sanitizers: globalOptions.build.sanitizers ) let runner = TestRunner( bundlePaths: testProducts.map { library == .xctest ? $0.bundlePath : $0.binaryPath }, additionalArguments: additionalArguments, - cancellator: swiftTool.cancellator, + cancellator: swiftCommandState.cancellator, toolchain: toolchain, testEnv: testEnv, - observabilityScope: swiftTool.observabilityScope, + observabilityScope: swiftCommandState.observabilityScope, library: library ) @@ -445,25 +439,25 @@ public struct SwiftTestTool: SwiftCommand { print($0, terminator: "") }) if !ranSuccessfully { - swiftTool.executionStatus = .failure + swiftCommandState.executionStatus = .failure } if self.options.enableCodeCoverage, ranSuccessfully { - try processCodeCoverage(testProducts, swiftTool: swiftTool, library: library) + try await processCodeCoverage(testProducts, swiftCommandState: swiftCommandState, library: library) } if self.options.enableExperimentalTestOutput, !ranSuccessfully { - try Self.handleTestOutput(buildParameters: buildParameters, packagePath: testProducts[0].packagePath) + try Self.handleTestOutput(productsBuildParameters: productsBuildParameters, packagePath: testProducts[0].packagePath) } } - private static func handleTestOutput(buildParameters: BuildParameters, packagePath: AbsolutePath) throws { - guard localFileSystem.exists(buildParameters.testOutputPath) else { + private static func handleTestOutput(productsBuildParameters: BuildParameters, packagePath: AbsolutePath) throws { + guard localFileSystem.exists(productsBuildParameters.testOutputPath) else { print("No existing test output found.") return } - let lines = try String(contentsOfFile: buildParameters.testOutputPath.pathString).components(separatedBy: "\n") + let lines = try String(contentsOfFile: productsBuildParameters.testOutputPath.pathString).components(separatedBy: "\n") let events = try lines.map { try JSONDecoder().decode(TestEventRecord.self, from: $0) } let caseEvents = events.compactMap { $0.caseEvent } @@ -489,62 +483,68 @@ public struct SwiftTestTool: SwiftCommand { } /// Processes the code coverage data and emits a json. - private func processCodeCoverage(_ testProducts: [BuiltTestProduct], swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { - let workspace = try swiftTool.getActiveWorkspace() - let root = try swiftTool.getWorkspaceRoot() - let rootManifests = try temp_await { - workspace.loadRootManifests( - packages: root.packages, - observabilityScope: swiftTool.observabilityScope, - completion: $0 - ) - } + private func processCodeCoverage( + _ testProducts: [BuiltTestProduct], + swiftCommandState: SwiftCommandState, + library: BuildParameters.Testing.Library + ) async throws { + let workspace = try swiftCommandState.getActiveWorkspace() + let root = try swiftCommandState.getWorkspaceRoot() + let rootManifests = try await workspace.loadRootManifests( + packages: root.packages, + observabilityScope: swiftCommandState.observabilityScope + ) guard let rootManifest = rootManifests.values.first else { throw StringError("invalid manifests at \(root.packages)") } // Merge all the profraw files to produce a single profdata file. - try mergeCodeCovRawDataFiles(swiftTool: swiftTool, library: library) + try mergeCodeCovRawDataFiles(swiftCommandState: swiftCommandState, library: library) - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) for product in testProducts { // Export the codecov data as JSON. - let jsonPath = buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName) - try exportCodeCovAsJSON(to: jsonPath, testBinary: product.binaryPath, swiftTool: swiftTool, library: library) + let jsonPath = productsBuildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName) + try exportCodeCovAsJSON(to: jsonPath, testBinary: product.binaryPath, swiftCommandState: swiftCommandState, library: library) } } /// Merges all profraw profiles in codecoverage directory into default.profdata file. - private func mergeCodeCovRawDataFiles(swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { + private func mergeCodeCovRawDataFiles(swiftCommandState: SwiftCommandState, library: BuildParameters.Testing.Library) throws { // Get the llvm-prof tool. - let llvmProf = try swiftTool.getTargetToolchain().getLLVMProf() + let llvmProf = try swiftCommandState.getTargetToolchain().getLLVMProf() // Get the profraw files. - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) - let codeCovFiles = try swiftTool.fileSystem.getDirectoryContents(buildParameters.codeCovPath) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) + let codeCovFiles = try swiftCommandState.fileSystem.getDirectoryContents(productsBuildParameters.codeCovPath) // Construct arguments for invoking the llvm-prof tool. var args = [llvmProf.pathString, "merge", "-sparse"] for file in codeCovFiles { - let filePath = buildParameters.codeCovPath.appending(component: file) + let filePath = productsBuildParameters.codeCovPath.appending(component: file) if filePath.extension == "profraw" { args.append(filePath.pathString) } } - args += ["-o", buildParameters.codeCovDataFile.pathString] + args += ["-o", productsBuildParameters.codeCovDataFile.pathString] try TSCBasic.Process.checkNonZeroExit(arguments: args) } /// Exports profdata as a JSON file. - private func exportCodeCovAsJSON(to path: AbsolutePath, testBinary: AbsolutePath, swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws { + private func exportCodeCovAsJSON( + to path: AbsolutePath, + testBinary: AbsolutePath, + swiftCommandState: SwiftCommandState, + library: BuildParameters.Testing.Library + ) throws { // Export using the llvm-cov tool. - let llvmCov = try swiftTool.getTargetToolchain().getLLVMCov() - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) + let llvmCov = try swiftCommandState.getTargetToolchain().getLLVMCov() + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) let args = [ llvmCov.pathString, "export", - "-instr-profile=\(buildParameters.codeCovDataFile)", + "-instr-profile=\(productsBuildParameters.codeCovDataFile)", testBinary.pathString ] let result = try TSCBasic.Process.popen(arguments: args) @@ -553,15 +553,23 @@ public struct SwiftTestTool: SwiftCommand { let output = try result.utf8Output() + result.utf8stderrOutput() throw StringError("Unable to export code coverage:\n \(output)") } - try swiftTool.fileSystem.writeFileContents(path, bytes: ByteString(result.output.get())) + try swiftCommandState.fileSystem.writeFileContents(path, bytes: ByteString(result.output.get())) } /// Builds the "test" target if enabled in options. /// /// - Returns: The paths to the build test products. - private func buildTestsIfNeeded(swiftTool: SwiftTool, library: BuildParameters.Testing.Library) throws -> [BuiltTestProduct] { - let buildParameters = try swiftTool.buildParametersForTest(options: self.options, library: library) - return try Commands.buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters, testProduct: self.options.sharedOptions.testProduct) + private func buildTestsIfNeeded( + swiftCommandState: SwiftCommandState, + library: BuildParameters.Testing.Library + ) throws -> [BuiltTestProduct] { + let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest(options: self.options, library: library) + return try Commands.buildTestsIfNeeded( + swiftCommandState: swiftCommandState, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, + testProduct: self.options.sharedOptions.testProduct + ) } /// Private function that validates the commands arguments @@ -582,7 +590,7 @@ public struct SwiftTestTool: SwiftCommand { throw StringError("'--num-workers' must be greater than zero") } - if !options.sharedOptions.enableXCTestSupport { + if !options.testLibraryOptions.enableXCTestSupport { throw StringError("'--num-workers' is only supported when testing with XCTest") } } @@ -592,36 +600,36 @@ public struct SwiftTestTool: SwiftCommand { } } - public init() {} + package init() {} } -extension SwiftTestTool { - func printCodeCovPath(_ swiftTool: SwiftTool) throws { - let workspace = try swiftTool.getActiveWorkspace() - let root = try swiftTool.getWorkspaceRoot() +extension SwiftTestCommand { + func printCodeCovPath(_ swiftCommandState: SwiftCommandState) throws { + let workspace = try swiftCommandState.getActiveWorkspace() + let root = try swiftCommandState.getWorkspaceRoot() let rootManifests = try temp_await { workspace.loadRootManifests( packages: root.packages, - observabilityScope: swiftTool.observabilityScope, + observabilityScope: swiftCommandState.observabilityScope, completion: $0 ) } guard let rootManifest = rootManifests.values.first else { throw StringError("invalid manifests at \(root.packages)") } - let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: true, library: .xctest) - print(buildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) + let (productsBuildParameters, _) = try swiftCommandState.buildParametersForTest(enableCodeCoverage: true, library: .xctest) + print(productsBuildParameters.codeCovAsJSONPath(packageName: rootManifest.displayName)) } } -extension SwiftTestTool { +extension SwiftTestCommand { struct Last: SwiftCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) throws { - try SwiftTestTool.handleTestOutput( - buildParameters: try swiftTool.productsBuildParameters, + func run(_ swiftCommandState: SwiftCommandState) throws { + try SwiftTestCommand.handleTestOutput( + productsBuildParameters: try swiftCommandState.productsBuildParameters, packagePath: localFileSystem.currentWorkingDirectory ?? .root // by definition runs in the current working directory ) } @@ -638,18 +646,30 @@ extension SwiftTestTool { @OptionGroup() var sharedOptions: SharedOptions + /// Which testing libraries to use (and any related options.) + @OptionGroup() + var testLibraryOptions: TestLibraryOptions + // for deprecated passthrough from SwiftTestTool (parse will fail otherwise) @Flag(name: [.customLong("list-tests"), .customShort("l")], help: .hidden) var _deprecated_passthrough: Bool = false // MARK: - XCTest - private func xctestRun(_ swiftTool: SwiftTool) throws { - let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, library: .xctest) - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters) + private func xctestRun(_ swiftCommandState: SwiftCommandState) throws { + let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest( + enableCodeCoverage: false, + shouldSkipBuilding: sharedOptions.shouldSkipBuilding, + library: .xctest + ) + let testProducts = try buildTestsIfNeeded( + swiftCommandState: swiftCommandState, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters + ) let testSuites = try TestingSupport.getTestSuites( in: testProducts, - swiftTool: swiftTool, + swiftCommandState: swiftCommandState, enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, experimentalTestOutput: false, @@ -664,24 +684,33 @@ extension SwiftTestTool { // MARK: - swift-testing - private func swiftTestingRun(_ swiftTool: SwiftTool) throws { - let buildParameters = try swiftTool.buildParametersForTest(enableCodeCoverage: false, shouldSkipBuilding: sharedOptions.shouldSkipBuilding, library: .swiftTesting) - let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters) + private func swiftTestingRun(_ swiftCommandState: SwiftCommandState) throws { + let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest( + enableCodeCoverage: false, + shouldSkipBuilding: sharedOptions.shouldSkipBuilding, + library: .swiftTesting + ) + let testProducts = try buildTestsIfNeeded( + swiftCommandState: swiftCommandState, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters + ) - let toolchain = try swiftTool.getTargetToolchain() + let toolchain = try swiftCommandState.getTargetToolchain() let testEnv = try TestingSupport.constructTestEnvironment( toolchain: toolchain, - buildParameters: buildParameters, + destinationBuildParameters: productsBuildParameters, sanitizers: globalOptions.build.sanitizers ) + let additionalArguments = ["--list-tests"] + CommandLine.arguments.dropFirst() let runner = TestRunner( bundlePaths: testProducts.map(\.binaryPath), - additionalArguments: ["--list-tests"], - cancellator: swiftTool.cancellator, + additionalArguments: additionalArguments, + cancellator: swiftCommandState.cancellator, toolchain: toolchain, testEnv: testEnv, - observabilityScope: swiftTool.observabilityScope, + observabilityScope: swiftCommandState.observabilityScope, library: .swiftTesting ) @@ -692,23 +721,32 @@ extension SwiftTestTool { print($0, terminator: "") }) if !ranSuccessfully { - swiftTool.executionStatus = .failure + swiftCommandState.executionStatus = .failure } } // MARK: - Common implementation - func run(_ swiftTool: SwiftTool) throws { - if try sharedOptions.enableSwiftTestingLibrarySupport(swiftTool: swiftTool) { - try swiftTestingRun(swiftTool) + func run(_ swiftCommandState: SwiftCommandState) throws { + if try testLibraryOptions.enableSwiftTestingLibrarySupport(swiftCommandState: swiftCommandState) { + try swiftTestingRun(swiftCommandState) } - if sharedOptions.enableXCTestSupport { - try xctestRun(swiftTool) + if testLibraryOptions.enableXCTestSupport { + try xctestRun(swiftCommandState) } } - private func buildTestsIfNeeded(swiftTool: SwiftTool, buildParameters: BuildParameters) throws -> [BuiltTestProduct] { - return try Commands.buildTestsIfNeeded(swiftTool: swiftTool, buildParameters: buildParameters, testProduct: self.sharedOptions.testProduct) + private func buildTestsIfNeeded( + swiftCommandState: SwiftCommandState, + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters + ) throws -> [BuiltTestProduct] { + return try Commands.buildTestsIfNeeded( + swiftCommandState: swiftCommandState, + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters, + testProduct: self.sharedOptions.testProduct + ) } } } @@ -795,7 +833,7 @@ final class TestRunner { /// Executes and returns execution status. Prints test output on standard streams if requested /// - Returns: Boolean indicating if test execution returned code 0, and the output stream result - public func test(outputHandler: @escaping (String) -> Void) -> Bool { + package func test(outputHandler: @escaping (String) -> Void) -> Bool { var success = true for path in self.bundlePaths { let testSuccess = self.test(at: path, outputHandler: outputHandler) @@ -810,7 +848,7 @@ final class TestRunner { #if os(macOS) if library == .xctest { guard let xctestPath = self.toolchain.xctestPath else { - throw TestError.xctestNotAvailable + throw TestError.xcodeNotInstalled } args = [xctestPath.pathString] args += additionalArguments @@ -899,7 +937,7 @@ final class ParallelTestRunner { private let toolchain: UserToolchain private let buildOptions: BuildOptions - private let buildParameters: BuildParameters + private let productsBuildParameters: BuildParameters /// Number of tests to execute in parallel. private let numJobs: Int @@ -916,7 +954,7 @@ final class ParallelTestRunner { toolchain: UserToolchain, numJobs: Int, buildOptions: BuildOptions, - buildParameters: BuildParameters, + productsBuildParameters: BuildParameters, shouldOutputSuccess: Bool, observabilityScope: ObservabilityScope ) { @@ -929,14 +967,21 @@ final class ParallelTestRunner { // command's result output goes on stdout // ie "swift test" should output to stdout - if ProcessEnv.vars["SWIFTPM_TEST_RUNNER_PROGRESS_BAR"] == "lit" { - progressAnimation = PercentProgressAnimation(stream: TSCBasic.stdoutStream, header: "Testing:") + if ProcessEnv.block["SWIFTPM_TEST_RUNNER_PROGRESS_BAR"] == "lit" { + self.progressAnimation = ProgressAnimation.percent( + stream: TSCBasic.stdoutStream, + verbose: false, + header: "Testing:" + ) } else { - progressAnimation = NinjaProgressAnimation(stream: TSCBasic.stdoutStream) + self.progressAnimation = ProgressAnimation.ninja( + stream: TSCBasic.stdoutStream, + verbose: false + ) } self.buildOptions = buildOptions - self.buildParameters = buildParameters + self.productsBuildParameters = productsBuildParameters assert(numJobs > 0, "num jobs should be > 0") } @@ -966,7 +1011,7 @@ final class ParallelTestRunner { let testEnv = try TestingSupport.constructTestEnvironment( toolchain: self.toolchain, - buildParameters: self.buildParameters, + destinationBuildParameters: self.productsBuildParameters, sanitizers: self.buildOptions.sanitizers ) @@ -1033,7 +1078,7 @@ final class ParallelTestRunner { // Print test results. for test in processedTests.get() { - if (!test.success || shouldOutputSuccess) && !buildParameters.testingParameters.experimentalTestOutput { + if (!test.success || shouldOutputSuccess) && !productsBuildParameters.testingParameters.experimentalTestOutput { // command's result output goes on stdout // ie "swift test" should output to stdout print(test.output) @@ -1248,11 +1293,11 @@ final class XUnitGenerator { } } -extension SwiftTool { +extension SwiftCommandState { func buildParametersForTest( - options: TestToolOptions, + options: TestCommandOptions, library: BuildParameters.Testing.Library - ) throws -> BuildParameters { + ) throws -> (productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters) { var result = try self.buildParametersForTest( enableCodeCoverage: options.enableCodeCoverage, enableTestability: options.enableTestableImports, @@ -1260,14 +1305,15 @@ extension SwiftTool { experimentalTestOutput: options.enableExperimentalTestOutput, library: library ) - if try options.sharedOptions.enableSwiftTestingLibrarySupport(swiftTool: self) { - result.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] + if try options.testLibraryOptions.enableSwiftTestingLibrarySupport(swiftCommandState: self) { + result.productsBuildParameters.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] + result.toolsBuildParameters.flags.swiftCompilerFlags += ["-DSWIFT_PM_SUPPORTS_SWIFT_TESTING"] } return result } } -extension TestToolOptions { +extension TestCommandOptions { func skippedTests(fileSystem: FileSystem) -> TestCaseSpecifier { // TODO: Remove this once the environment variable is no longer used. if let override = skippedTestsOverride(fileSystem: fileSystem) { @@ -1281,7 +1327,7 @@ extension TestToolOptions { /// Returns the test case specifier if overridden in the env. private func skippedTestsOverride(fileSystem: FileSystem) -> TestCaseSpecifier? { - guard let override = ProcessEnv.vars["_SWIFTPM_SKIP_TESTS_LIST"] else { + guard let override = ProcessEnv.block["_SWIFTPM_SKIP_TESTS_LIST"] else { return nil } @@ -1319,8 +1365,16 @@ private extension Basics.Diagnostic { /// Builds the "test" target if enabled in options. /// /// - Returns: The paths to the build test products. -private func buildTestsIfNeeded(swiftTool: SwiftTool, buildParameters: BuildParameters, testProduct: String?) throws -> [BuiltTestProduct] { - let buildSystem = try swiftTool.createBuildSystem(productsBuildParameters: buildParameters) +private func buildTestsIfNeeded( + swiftCommandState: SwiftCommandState, + productsBuildParameters: BuildParameters, + toolsBuildParameters: BuildParameters, + testProduct: String? +) throws -> [BuiltTestProduct] { + let buildSystem = try swiftCommandState.createBuildSystem( + productsBuildParameters: productsBuildParameters, + toolsBuildParameters: toolsBuildParameters + ) let subset = testProduct.map(BuildSubset.product) ?? .allIncludingTests try buildSystem.build(subset: subset) diff --git a/Sources/Commands/Utilities/APIDigester.swift b/Sources/Commands/Utilities/APIDigester.swift index 28f47e47406..91aeab7cf65 100644 --- a/Sources/Commands/Utilities/APIDigester.swift +++ b/Sources/Commands/Utilities/APIDigester.swift @@ -14,8 +14,11 @@ import Dispatch import Foundation import SPMBuildCore + import Basics + import CoreCommands + import PackageGraph import PackageModel import SourceControl @@ -73,7 +76,7 @@ struct APIDigesterBaselineDumper { at baselineDir: AbsolutePath?, force: Bool, logLevel: Basics.Diagnostic.Severity, - swiftTool: SwiftTool + swiftCommandState: SwiftCommandState ) throws -> AbsolutePath { var modulesToDiff = modulesToDiff let apiDiffDir = productsBuildParameters.apiDiff @@ -85,7 +88,7 @@ struct APIDigesterBaselineDumper { if !force { // Baselines which already exist don't need to be regenerated. modulesToDiff = modulesToDiff.filter { - !swiftTool.fileSystem.exists(baselinePath($0)) + !swiftCommandState.fileSystem.exists(baselinePath($0)) } } @@ -96,8 +99,8 @@ struct APIDigesterBaselineDumper { // Setup a temporary directory where we can checkout and build the baseline treeish. let baselinePackageRoot = apiDiffDir.appending("\(baselineRevision.identifier)-checkout") - if swiftTool.fileSystem.exists(baselinePackageRoot) { - try swiftTool.fileSystem.removeFileTree(baselinePackageRoot) + if swiftCommandState.fileSystem.exists(baselinePackageRoot) { + try swiftCommandState.fileSystem.removeFileTree(baselinePackageRoot) } // Clone the current package in a sandbox and checkout the baseline revision. @@ -115,7 +118,7 @@ struct APIDigesterBaselineDumper { // Create the workspace for this package. let workspace = try Workspace( forRootPackage: baselinePackageRoot, - cancellator: swiftTool.cancellator + cancellator: swiftCommandState.cancellator ) let graph = try workspace.loadPackageGraph( @@ -137,7 +140,7 @@ struct APIDigesterBaselineDumper { // Build the baseline module. // FIXME: We need to implement the build tool invocation closure here so that build tool plugins work with the APIDigester. rdar://86112934 - let buildSystem = try swiftTool.createBuildSystem( + let buildSystem = try swiftCommandState.createBuildSystem( explicitBuildSystem: .native, cacheBuildManifest: false, productsBuildParameters: productsBuildParameters, @@ -147,7 +150,7 @@ struct APIDigesterBaselineDumper { try buildSystem.build() // Dump the SDK JSON. - try swiftTool.fileSystem.createDirectory(baselineDir, recursive: true) + try swiftCommandState.fileSystem.createDirectory(baselineDir, recursive: true) let group = DispatchGroup() let semaphore = DispatchSemaphore(value: Int(productsBuildParameters.workers)) let errors = ThreadSafeArrayStore() @@ -180,7 +183,7 @@ struct APIDigesterBaselineDumper { } /// A wrapper for the swift-api-digester tool. -public struct SwiftAPIDigester { +package struct SwiftAPIDigester { /// The file system to use let fileSystem: FileSystem @@ -193,7 +196,7 @@ public struct SwiftAPIDigester { } /// Emit an API baseline file for the specified module at the specified location. - public func emitAPIBaseline( + package func emitAPIBaseline( to outputPath: AbsolutePath, for module: String, buildPlan: SPMBuildCore.BuildPlan @@ -223,7 +226,7 @@ public struct SwiftAPIDigester { } /// Compare the current package API to a provided baseline file. - public func compareAPIToBaseline( + package func compareAPIToBaseline( at baselinePath: AbsolutePath, for module: String, buildPlan: SPMBuildCore.BuildPlan, @@ -268,12 +271,12 @@ public struct SwiftAPIDigester { } extension SwiftAPIDigester { - public enum Error: Swift.Error, CustomStringConvertible { + package enum Error: Swift.Error, CustomStringConvertible { case failedToGenerateBaseline(module: String) case failedToValidateBaseline(module: String) case noSymbolsInBaseline(module: String, toolOutput: String) - public var description: String { + package var description: String { switch self { case .failedToGenerateBaseline(let module): return "failed to generate baseline for \(module)" @@ -288,7 +291,7 @@ extension SwiftAPIDigester { extension SwiftAPIDigester { /// The result of comparing a module's API to a provided baseline. - public struct ComparisonResult { + package struct ComparisonResult { /// The name of the module being diffed. var moduleName: String /// Breaking changes made to the API since the baseline was generated. @@ -310,7 +313,7 @@ extension BuildParameters { } } -extension PackageGraph { +extension ModulesGraph { /// The list of modules that should be used as an input to the API digester. var apiDigesterModules: [String] { self.rootPackages @@ -328,7 +331,7 @@ extension SerializedDiagnostics.SourceLocation { } } -#if swift(<5.11) +#if swift(<6.0) extension SerializedDiagnostics.SourceLocation: DiagnosticLocation {} #else extension SerializedDiagnostics.SourceLocation: @retroactive DiagnosticLocation {} diff --git a/Sources/Commands/Utilities/DependenciesSerializer.swift b/Sources/Commands/Utilities/DependenciesSerializer.swift index 3a036bec15f..25190f011c4 100644 --- a/Sources/Commands/Utilities/DependenciesSerializer.swift +++ b/Sources/Commands/Utilities/DependenciesSerializer.swift @@ -17,11 +17,11 @@ import enum TSCBasic.JSON import protocol TSCBasic.OutputByteStream protocol DependenciesDumper { - func dump(dependenciesOf: ResolvedPackage, on: OutputByteStream) + func dump(graph: ModulesGraph, dependenciesOf: ResolvedPackage, on: OutputByteStream) } final class PlainTextDumper: DependenciesDumper { - func dump(dependenciesOf rootpkg: ResolvedPackage, on stream: OutputByteStream) { + func dump(graph: ModulesGraph, dependenciesOf rootpkg: ResolvedPackage, on stream: OutputByteStream) { func recursiveWalk(packages: [ResolvedPackage], prefix: String = "") { var hanger = prefix + "├── " @@ -39,14 +39,14 @@ final class PlainTextDumper: DependenciesDumper { var childPrefix = hanger let startIndex = childPrefix.index(childPrefix.endIndex, offsetBy: -4) childPrefix.replaceSubrange(startIndex.. = [] func printNode(_ package: ResolvedPackage) { let url = package.manifest.packageLocation @@ -87,7 +87,7 @@ final class DotDumper: DependenciesDumper { var dependenciesAlreadyPrinted: Set = [] func recursiveWalk(rootpkg: ResolvedPackage) { printNode(rootpkg) - for dependency in rootpkg.dependencies { + for dependency in graph.directDependencies(for: rootpkg) { let rootURL = rootpkg.manifest.packageLocation let dependencyURL = dependency.manifest.packageLocation let urlPair = DependencyURLs(root: rootURL, dependency: dependencyURL) @@ -120,7 +120,7 @@ final class DotDumper: DependenciesDumper { } final class JSONDumper: DependenciesDumper { - func dump(dependenciesOf rootpkg: ResolvedPackage, on stream: OutputByteStream) { + func dump(graph: ModulesGraph, dependenciesOf rootpkg: ResolvedPackage, on stream: OutputByteStream) { func convert(_ package: ResolvedPackage) -> JSON { return .orderedDictionary([ "identity": .string(package.identity.description), @@ -128,7 +128,7 @@ final class JSONDumper: DependenciesDumper { "url": .string(package.manifest.packageLocation), "version": .string(package.manifest.version?.description ?? "unspecified"), "path": .string(package.path.pathString), - "dependencies": .array(package.dependencies.map(convert)), + "dependencies": .array(package.dependencies.compactMap { graph.packages[$0] }.map(convert)), ]) } diff --git a/Sources/Commands/Utilities/MermaidPackageSerializer.swift b/Sources/Commands/Utilities/MermaidPackageSerializer.swift index 06bad495f72..d2153f97eab 100644 --- a/Sources/Commands/Utilities/MermaidPackageSerializer.swift +++ b/Sources/Commands/Utilities/MermaidPackageSerializer.swift @@ -115,6 +115,14 @@ extension MermaidPackageSerializer.Node { border: .hexagon, subgraph: product.package ) + case let .innerProduct(product, _): + // TODO: Do we need a subgraph here? + self.init( + id: product.name, + title: product.name, + border: .hexagon, + subgraph: nil + ) case let .target(target, _): self.init(target: target) } diff --git a/Sources/Commands/Utilities/MultiRootSupport.swift b/Sources/Commands/Utilities/MultiRootSupport.swift index 7ee468a81f5..7fca57136ca 100644 --- a/Sources/Commands/Utilities/MultiRootSupport.swift +++ b/Sources/Commands/Utilities/MultiRootSupport.swift @@ -21,7 +21,7 @@ import class PackageModel.Manifest /// A bare minimum loader for Xcode workspaces. /// /// Warning: This is only useful for debugging workspaces that contain Swift packages. -public struct XcodeWorkspaceLoader: WorkspaceLoader { +package struct XcodeWorkspaceLoader: WorkspaceLoader { /// The parsed location. private struct Location { @@ -38,13 +38,13 @@ public struct XcodeWorkspaceLoader: WorkspaceLoader { private let fileSystem: FileSystem private let observabilityScope: ObservabilityScope - public init(fileSystem: FileSystem, observabilityScope: ObservabilityScope) { + package init(fileSystem: FileSystem, observabilityScope: ObservabilityScope) { self.fileSystem = fileSystem self.observabilityScope = observabilityScope } /// Load the given workspace and return the file ref paths from it. - public func load(workspace: AbsolutePath) throws -> [AbsolutePath] { + package func load(workspace: AbsolutePath) throws -> [AbsolutePath] { let path = workspace.appending("contents.xcworkspacedata") let contents: Data = try self.fileSystem.readFileContents(path) diff --git a/Sources/Commands/Utilities/PluginDelegate.swift b/Sources/Commands/Utilities/PluginDelegate.swift index d4ad1554444..82960b6e0c8 100644 --- a/Sources/Commands/Utilities/PluginDelegate.swift +++ b/Sources/Commands/Utilities/PluginDelegate.swift @@ -11,9 +11,12 @@ //===----------------------------------------------------------------------===// import Basics + import CoreCommands + import Foundation import PackageModel + import SPMBuildCore import protocol TSCBasic.OutputByteStream @@ -22,12 +25,12 @@ import class TSCBasic.Process import struct TSCBasic.ProcessResult final class PluginDelegate: PluginInvocationDelegate { - let swiftTool: SwiftTool + let swiftCommandState: SwiftCommandState let plugin: PluginTarget var lineBufferedOutput: Data - init(swiftTool: SwiftTool, plugin: PluginTarget) { - self.swiftTool = swiftTool + init(swiftCommandState: SwiftCommandState, plugin: PluginTarget) { + self.swiftCommandState = swiftCommandState self.plugin = plugin self.lineBufferedOutput = Data() } @@ -51,12 +54,12 @@ final class PluginDelegate: PluginInvocationDelegate { } func pluginEmittedDiagnostic(_ diagnostic: Basics.Diagnostic) { - swiftTool.observabilityScope.emit(diagnostic) + swiftCommandState.observabilityScope.emit(diagnostic) } func pluginEmittedProgress(_ message: String) { - swiftTool.outputStream.write("[\(plugin.name)] \(message)\n") - swiftTool.outputStream.flush() + swiftCommandState.outputStream.write("[\(plugin.name)] \(message)\n") + swiftCommandState.outputStream.flush() } func pluginRequestedBuildOperation( @@ -75,7 +78,7 @@ final class PluginDelegate: PluginInvocationDelegate { class TeeOutputByteStream: OutputByteStream { var downstreams: [OutputByteStream] - public init(_ downstreams: [OutputByteStream]) { + package init(_ downstreams: [OutputByteStream]) { self.downstreams = downstreams } @@ -83,7 +86,7 @@ final class PluginDelegate: PluginInvocationDelegate { return 0 // should be related to the downstreams somehow } - public func write(_ byte: UInt8) { + package func write(_ byte: UInt8) { for downstream in downstreams { downstream.write(byte) } @@ -95,13 +98,13 @@ final class PluginDelegate: PluginInvocationDelegate { } } - public func flush() { + package func flush() { for downstream in downstreams { downstream.flush() } } - public func addStream(_ stream: OutputByteStream) { + package func addStream(_ stream: OutputByteStream) { self.downstreams.append(stream) } } @@ -111,7 +114,7 @@ final class PluginDelegate: PluginInvocationDelegate { parameters: PluginInvocationBuildParameters ) throws -> PluginInvocationBuildResult { // Configure the build parameters. - var buildParameters = try self.swiftTool.productsBuildParameters + var buildParameters = try self.swiftCommandState.productsBuildParameters switch parameters.configuration { case .debug: buildParameters.configuration = .debug @@ -155,10 +158,10 @@ final class PluginDelegate: PluginInvocationDelegate { let bufferedOutputStream = BufferedOutputByteStream() let outputStream = TeeOutputByteStream([bufferedOutputStream]) if parameters.echoLogs { - outputStream.addStream(swiftTool.outputStream) + outputStream.addStream(swiftCommandState.outputStream) } - let buildSystem = try swiftTool.createBuildSystem( + let buildSystem = try swiftCommandState.createBuildSystem( explicitBuildSystem: .native, explicitProduct: explicitProduct, cacheBuildManifest: false, @@ -219,24 +222,24 @@ final class PluginDelegate: PluginInvocationDelegate { ) throws -> PluginInvocationTestResult { // Build the tests. Ideally we should only build those that match the subset, but we don't have a way to know // which ones they are until we've built them and can examine the binaries. - let toolchain = try swiftTool.getHostToolchain() - var toolsBuildParameters = try swiftTool.toolsBuildParameters + let toolchain = try swiftCommandState.getHostToolchain() + var toolsBuildParameters = try swiftCommandState.toolsBuildParameters toolsBuildParameters.testingParameters.enableTestability = true toolsBuildParameters.testingParameters.enableCodeCoverage = parameters.enableCodeCoverage - let buildSystem = try swiftTool.createBuildSystem(toolsBuildParameters: toolsBuildParameters) + let buildSystem = try swiftCommandState.createBuildSystem(toolsBuildParameters: toolsBuildParameters) try buildSystem.build(subset: .allIncludingTests) // Clean out the code coverage directory that may contain stale `profraw` files from a previous run of // the code coverage tool. if parameters.enableCodeCoverage { - try swiftTool.fileSystem.removeFileTree(toolsBuildParameters.codeCovPath) + try swiftCommandState.fileSystem.removeFileTree(toolsBuildParameters.codeCovPath) } // Construct the environment we'll pass down to the tests. let testEnvironment = try TestingSupport.constructTestEnvironment( toolchain: toolchain, - buildParameters: toolsBuildParameters, - sanitizers: swiftTool.options.build.sanitizers + destinationBuildParameters: toolsBuildParameters, + sanitizers: swiftCommandState.options.build.sanitizers ) // Iterate over the tests and run those that match the filter. @@ -246,11 +249,11 @@ final class PluginDelegate: PluginInvocationDelegate { // Get the test suites in the bundle. Each is just a container for test cases. let testSuites = try TestingSupport.getTestSuites( fromTestAt: testProduct.bundlePath, - swiftTool: swiftTool, + swiftCommandState: swiftCommandState, enableCodeCoverage: parameters.enableCodeCoverage, shouldSkipBuilding: false, experimentalTestOutput: false, - sanitizers: swiftTool.options.build.sanitizers + sanitizers: swiftCommandState.options.build.sanitizers ) for testSuite in testSuites { // Each test suite is just a container for test cases (confusingly called "tests", @@ -275,10 +278,10 @@ final class PluginDelegate: PluginInvocationDelegate { let testRunner = TestRunner( bundlePaths: [testProduct.bundlePath], additionalArguments: additionalArguments, - cancellator: swiftTool.cancellator, + cancellator: swiftCommandState.cancellator, toolchain: toolchain, testEnv: testEnvironment, - observabilityScope: swiftTool.observabilityScope, + observabilityScope: swiftCommandState.observabilityScope, library: .xctest) // FIXME: support both libraries // Run the test — for now we run the sequentially so we can capture accurate timing results. @@ -321,7 +324,7 @@ final class PluginDelegate: PluginInvocationDelegate { if parameters.enableCodeCoverage { // Use `llvm-prof` to merge all the `.profraw` files into a single `.profdata` file. let mergedCovFile = toolsBuildParameters.codeCovDataFile - let codeCovFileNames = try swiftTool.fileSystem.getDirectoryContents(toolsBuildParameters.codeCovPath) + let codeCovFileNames = try swiftCommandState.fileSystem.getDirectoryContents(toolsBuildParameters.codeCovPath) var llvmProfCommand = [try toolchain.getLLVMProf().pathString] llvmProfCommand += ["merge", "-sparse"] for fileName in codeCovFileNames where fileName.hasSuffix(".profraw") { @@ -343,7 +346,7 @@ final class PluginDelegate: PluginInvocationDelegate { let jsonCovFile = toolsBuildParameters.codeCovDataFile.parentDirectory.appending( component: toolsBuildParameters.codeCovDataFile.basenameWithoutExt + ".json" ) - try swiftTool.fileSystem.writeFileContents(jsonCovFile, string: jsonOutput) + try swiftCommandState.fileSystem.writeFileContents(jsonCovFile, string: jsonOutput) // Return the path of the exported code coverage data file. codeCoverageDataFile = jsonCovFile @@ -380,7 +383,7 @@ final class PluginDelegate: PluginInvocationDelegate { // while building. // Create a build system for building the target., skipping the the cache because we need the build plan. - let buildSystem = try swiftTool.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false) + let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false) // Find the target in the build operation's package graph; it's an error if we don't find it. let packageGraph = try buildSystem.getPackageGraph() @@ -393,9 +396,9 @@ final class PluginDelegate: PluginInvocationDelegate { // Configure the symbol graph extractor. var symbolGraphExtractor = try SymbolGraphExtract( - fileSystem: swiftTool.fileSystem, - tool: swiftTool.getTargetToolchain().getSymbolGraphExtract(), - observabilityScope: swiftTool.observabilityScope + fileSystem: swiftCommandState.fileSystem, + tool: swiftCommandState.getTargetToolchain().getSymbolGraphExtract(), + observabilityScope: swiftCommandState.observabilityScope ) symbolGraphExtractor.skipSynthesizedMembers = !options.includeSynthesized switch options.minimumAccessLevel { @@ -423,15 +426,15 @@ final class PluginDelegate: PluginInvocationDelegate { package.identity.description, target.name ) - try swiftTool.fileSystem.removeFileTree(outputDir) + try swiftCommandState.fileSystem.removeFileTree(outputDir) // Run the symbol graph extractor on the target. let result = try symbolGraphExtractor.extractSymbolGraph( - target: target, + module: target, buildPlan: try buildSystem.buildPlan, outputRedirection: .collect, outputDirectory: outputDir, - verboseOutput: self.swiftTool.logLevel <= .info + verboseOutput: self.swiftCommandState.logLevel <= .info ) guard result.exitStatus == .terminated(code: 0) else { diff --git a/Sources/Commands/Utilities/SymbolGraphExtract.swift b/Sources/Commands/Utilities/SymbolGraphExtract.swift index f9423384c9b..03c3afde7bd 100644 --- a/Sources/Commands/Utilities/SymbolGraphExtract.swift +++ b/Sources/Commands/Utilities/SymbolGraphExtract.swift @@ -26,7 +26,7 @@ import class TSCBasic.Process import struct TSCBasic.ProcessResult /// A wrapper for swift-symbolgraph-extract tool. -public struct SymbolGraphExtract { +package struct SymbolGraphExtract { let fileSystem: FileSystem let tool: AbsolutePath let observabilityScope: ObservabilityScope @@ -39,34 +39,34 @@ public struct SymbolGraphExtract { var outputFormat = OutputFormat.json(pretty: false) /// Access control levels. - public enum AccessLevel: String, RawRepresentable, CaseIterable, ExpressibleByArgument { + package enum AccessLevel: String, RawRepresentable, CaseIterable, ExpressibleByArgument { // The cases reflect those found in `include/swift/AST/AttrKind.h` of the swift compiler (at commit 03f55d7bb4204ca54841218eb7cc175ae798e3bd) case `private`, `fileprivate`, `internal`, `public`, `open` } /// Output format of the generated symbol graph. - public enum OutputFormat { + package enum OutputFormat { /// JSON format, optionally "pretty-printed" be more human-readable. case json(pretty: Bool) } - /// Creates a symbol graph for `target` in `outputDirectory` using the build information from `buildPlan`. + /// Creates a symbol graph for `module` in `outputDirectory` using the build information from `buildPlan`. /// The `outputDirection` determines how the output from the tool subprocess is handled, and `verbosity` specifies /// how much console output to ask the tool to emit. - public func extractSymbolGraph( - target: ResolvedTarget, + package func extractSymbolGraph( + module: ResolvedModule, buildPlan: BuildPlan, outputRedirection: TSCBasic.Process.OutputRedirection = .none, outputDirectory: AbsolutePath, verboseOutput: Bool ) throws -> ProcessResult { - let buildParameters = buildPlan.buildParameters(for: target) + let buildParameters = buildPlan.buildParameters(for: module) try self.fileSystem.createDirectory(outputDirectory, recursive: true) // Construct arguments for extracting symbols for a single target. var commandLine = [self.tool.pathString] - commandLine += ["-module-name", target.c99name] - commandLine += try buildParameters.targetTripleArgs(for: target) + commandLine += ["-module-name", module.c99name] + commandLine += try buildParameters.tripleArgs(for: module) commandLine += try buildPlan.createAPIToolCommonArgs(includeLibrarySearchPaths: true) commandLine += ["-module-cache-path", try buildParameters.moduleCache.pathString] if verboseOutput { diff --git a/Sources/Commands/Utilities/TestingSupport.swift b/Sources/Commands/Utilities/TestingSupport.swift index 236feaa072e..fccd9e1e959 100644 --- a/Sources/Commands/Utilities/TestingSupport.swift +++ b/Sources/Commands/Utilities/TestingSupport.swift @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// import Basics + import CoreCommands + import PackageModel import SPMBuildCore import Workspace @@ -31,13 +33,15 @@ enum TestingSupport { /// Note: It is a fatalError if we are not able to locate the tool. /// /// - Returns: Path to XCTestHelper tool. - static func xctestHelperPath(swiftTool: SwiftTool) throws -> AbsolutePath { + static func xctestHelperPath(swiftCommandState: SwiftCommandState) throws -> AbsolutePath { var triedPaths = [AbsolutePath]() func findXCTestHelper(swiftBuildPath: AbsolutePath) -> AbsolutePath? { // XCTestHelper tool is installed in libexec. - let maybePath = swiftBuildPath.parentDirectory.parentDirectory.appending(components: "libexec", "swift", "pm", "swiftpm-xctest-helper") - if swiftTool.fileSystem.isFile(maybePath) { + let maybePath = swiftBuildPath.parentDirectory.parentDirectory.appending( + components: "libexec", "swift", "pm", "swiftpm-xctest-helper" + ) + if swiftCommandState.fileSystem.isFile(maybePath) { return maybePath } else { triedPaths.append(maybePath) @@ -46,7 +50,7 @@ enum TestingSupport { } if let firstCLIArgument = CommandLine.arguments.first { - let runningSwiftBuildPath = try AbsolutePath(validating: firstCLIArgument, relativeTo: swiftTool.originalWorkingDirectory) + let runningSwiftBuildPath = try AbsolutePath(validating: firstCLIArgument, relativeTo: swiftCommandState.originalWorkingDirectory) if let xctestHelperPath = findXCTestHelper(swiftBuildPath: runningSwiftBuildPath) { return xctestHelperPath } @@ -54,7 +58,10 @@ enum TestingSupport { // This will be true during swiftpm development or when using swift.org toolchains. let xcodePath = try TSCBasic.Process.checkNonZeroExit(args: "/usr/bin/xcode-select", "--print-path").spm_chomp() - let installedSwiftBuildPath = try TSCBasic.Process.checkNonZeroExit(args: "/usr/bin/xcrun", "--find", "swift-build", environment: ["DEVELOPER_DIR": xcodePath]).spm_chomp() + let installedSwiftBuildPath = try TSCBasic.Process.checkNonZeroExit( + args: "/usr/bin/xcrun", "--find", "swift-build", + environment: ["DEVELOPER_DIR": xcodePath] + ).spm_chomp() if let xctestHelperPath = findXCTestHelper(swiftBuildPath: try AbsolutePath(validating: installedSwiftBuildPath)) { return xctestHelperPath } @@ -64,7 +71,7 @@ enum TestingSupport { static func getTestSuites( in testProducts: [BuiltTestProduct], - swiftTool: SwiftTool, + swiftCommandState: SwiftCommandState, enableCodeCoverage: Bool, shouldSkipBuilding: Bool, experimentalTestOutput: Bool, @@ -75,7 +82,7 @@ enum TestingSupport { $0.bundlePath, try Self.getTestSuites( fromTestAt: $0.bundlePath, - swiftTool: swiftTool, + swiftCommandState: swiftCommandState, enableCodeCoverage: enableCodeCoverage, shouldSkipBuilding: shouldSkipBuilding, experimentalTestOutput: experimentalTestOutput, @@ -97,7 +104,7 @@ enum TestingSupport { /// - Returns: Array of TestSuite static func getTestSuites( fromTestAt path: AbsolutePath, - swiftTool: SwiftTool, + swiftCommandState: SwiftCommandState, enableCodeCoverage: Bool, shouldSkipBuilding: Bool, experimentalTestOutput: Bool, @@ -107,30 +114,30 @@ enum TestingSupport { var args = [String]() #if os(macOS) let data: String = try withTemporaryFile { tempFile in - args = [try Self.xctestHelperPath(swiftTool: swiftTool).pathString, path.pathString, tempFile.path.pathString] + args = [try Self.xctestHelperPath(swiftCommandState: swiftCommandState).pathString, path.pathString, tempFile.path.pathString] let env = try Self.constructTestEnvironment( - toolchain: try swiftTool.getTargetToolchain(), - buildParameters: swiftTool.buildParametersForTest( + toolchain: try swiftCommandState.getTargetToolchain(), + destinationBuildParameters: swiftCommandState.buildParametersForTest( enableCodeCoverage: enableCodeCoverage, shouldSkipBuilding: shouldSkipBuilding, experimentalTestOutput: experimentalTestOutput, library: .xctest - ), + ).productsBuildParameters, sanitizers: sanitizers ) try TSCBasic.Process.checkNonZeroExit(arguments: args, environment: env) // Read the temporary file's content. - return try swiftTool.fileSystem.readFileContents(AbsolutePath(tempFile.path)) + return try swiftCommandState.fileSystem.readFileContents(AbsolutePath(tempFile.path)) } #else let env = try Self.constructTestEnvironment( - toolchain: try swiftTool.getTargetToolchain(), - buildParameters: swiftTool.buildParametersForTest( + toolchain: try swiftCommandState.getTargetToolchain(), + destinationBuildParameters: swiftCommandState.buildParametersForTest( enableCodeCoverage: enableCodeCoverage, shouldSkipBuilding: shouldSkipBuilding, library: .xctest - ), + ).productsBuildParameters, sanitizers: sanitizers ) args = [path.description, "--dump-tests-json"] @@ -143,7 +150,7 @@ enum TestingSupport { /// Creates the environment needed to test related tools. static func constructTestEnvironment( toolchain: UserToolchain, - buildParameters: BuildParameters, + destinationBuildParameters buildParameters: BuildParameters, sanitizers: [Sanitizer] ) throws -> EnvironmentVariables { var env = EnvironmentVariables.process() @@ -176,11 +183,12 @@ enum TestingSupport { #endif return env #else - // Add the sdk platform path if we have it. If this is not present, we might always end up failing. - let sdkPlatformFrameworksPath = try SwiftSDK.sdkPlatformFrameworkPaths() - // appending since we prefer the user setting (if set) to the one we inject - env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString) - env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString) + // Add the sdk platform path if we have it. + if let sdkPlatformFrameworksPath = try? SwiftSDK.sdkPlatformFrameworkPaths() { + // appending since we prefer the user setting (if set) to the one we inject + env.appendPath("DYLD_FRAMEWORK_PATH", value: sdkPlatformFrameworksPath.fwk.pathString) + env.appendPath("DYLD_LIBRARY_PATH", value: sdkPlatformFrameworksPath.lib.pathString) + } // Fast path when no sanitizers are enabled. if sanitizers.isEmpty { @@ -203,15 +211,42 @@ enum TestingSupport { } } -extension SwiftTool { +extension SwiftCommandState { func buildParametersForTest( enableCodeCoverage: Bool, enableTestability: Bool? = nil, shouldSkipBuilding: Bool = false, experimentalTestOutput: Bool = false, library: BuildParameters.Testing.Library - ) throws -> BuildParameters { - var parameters = try self.productsBuildParameters + ) throws -> (productsBuildParameters: BuildParameters, toolsBuildParameters: BuildParameters) { + let productsBuildParameters = buildParametersForTest( + modifying: try productsBuildParameters, + enableCodeCoverage: enableCodeCoverage, + enableTestability: enableTestability, + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, + library: library + ) + let toolsBuildParameters = buildParametersForTest( + modifying: try toolsBuildParameters, + enableCodeCoverage: enableCodeCoverage, + enableTestability: enableTestability, + shouldSkipBuilding: shouldSkipBuilding, + experimentalTestOutput: experimentalTestOutput, + library: library + ) + return (productsBuildParameters, toolsBuildParameters) + } + + private func buildParametersForTest( + modifying parameters: BuildParameters, + enableCodeCoverage: Bool, + enableTestability: Bool?, + shouldSkipBuilding: Bool, + experimentalTestOutput: Bool, + library: BuildParameters.Testing.Library + ) -> BuildParameters { + var parameters = parameters var explicitlyEnabledDiscovery = false var explicitlySpecifiedPath: AbsolutePath? diff --git a/Sources/Commands/Utilities/XCTEvents.swift b/Sources/Commands/Utilities/XCTEvents.swift index a264b205e3a..0ceedfce77f 100644 --- a/Sources/Commands/Utilities/XCTEvents.swift +++ b/Sources/Commands/Utilities/XCTEvents.swift @@ -237,12 +237,12 @@ extension TestErrorInfo { extension TestIssue { init(_ issue: XCTIssue) { self.init( - type: .init(destinationBuildParameters: issue.type), + type: .init(defaultBuildParameters: issue.type), compactDescription: issue.compactDescription, detailedDescription: issue.detailedDescription, - associatedError: issue.associatedError.map { .init(destinationBuildParameters: $0) }, - sourceCodeContext: .init(destinationBuildParameters: issue.sourceCodeContext), - attachments: issue.attachments.map { .init(destinationBuildParameters: $0) } + associatedError: issue.associatedError.map { .init(defaultBuildParameters: $0) }, + sourceCodeContext: .init(defaultBuildParameters: issue.sourceCodeContext), + attachments: issue.attachments.map { .init(defaultBuildParameters: $0) } ) } } @@ -275,8 +275,8 @@ extension TestLocation { extension TestSourceCodeContext { init(_ context: XCTSourceCodeContext) { self.init( - callStack: context.callStack.map { .init(destinationBuildParameters: $0) }, - location: context.location.map { .init(destinationBuildParameters: $0) } + callStack: context.callStack.map { .init(defaultBuildParameters: $0) }, + location: context.location.map { .init(defaultBuildParameters: $0) } ) } } @@ -285,8 +285,8 @@ extension TestSourceCodeFrame { init(_ frame: XCTSourceCodeFrame) { self.init( address: frame.address, - symbolInfo: (try? frame.symbolInfo()).map { .init(destinationBuildParameters: $0) }, - symbolicationError: frame.symbolicationError.map { .init(destinationBuildParameters: $0) } + symbolInfo: (try? frame.symbolInfo()).map { .init(defaultBuildParameters: $0) }, + symbolicationError: frame.symbolicationError.map { .init(defaultBuildParameters: $0) } ) } } @@ -296,7 +296,7 @@ extension TestSourceCodeSymbolInfo { self.init( imageName: symbolInfo.imageName, symbolName: symbolInfo.symbolName, - location: symbolInfo.location.map { .init(destinationBuildParameters: $0) } + location: symbolInfo.location.map { .init(defaultBuildParameters: $0) } ) } } diff --git a/Sources/CoreCommands/BuildSystemSupport.swift b/Sources/CoreCommands/BuildSystemSupport.swift index dcbc8aa6bbc..c2e7bd0c4c2 100644 --- a/Sources/CoreCommands/BuildSystemSupport.swift +++ b/Sources/CoreCommands/BuildSystemSupport.swift @@ -11,85 +11,90 @@ //===----------------------------------------------------------------------===// import Build + import SPMBuildCore + import XCBuildSupport import class Basics.ObservabilityScope -import struct PackageGraph.PackageGraph +import struct PackageGraph.ModulesGraph import struct PackageLoading.FileRuleDescription import protocol TSCBasic.OutputByteStream private struct NativeBuildSystemFactory: BuildSystemFactory { - let swiftTool: SwiftTool + let swiftCommandState: SwiftCommandState func makeBuildSystem( explicitProduct: String?, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, - packageGraphLoader: (() throws -> PackageGraph)?, + packageGraphLoader: (() throws -> ModulesGraph)?, outputStream: OutputByteStream?, logLevel: Diagnostic.Severity?, observabilityScope: ObservabilityScope? ) throws -> any BuildSystem { + let rootPackageInfo = try swiftCommandState.getRootPackageInformation() let testEntryPointPath = productsBuildParameters?.testingParameters.testProductStyle.explicitlySpecifiedEntryPointPath return try BuildOperation( - productsBuildParameters: try productsBuildParameters ?? self.swiftTool.productsBuildParameters, - toolsBuildParameters: try toolsBuildParameters ?? self.swiftTool.toolsBuildParameters, - cacheBuildManifest: cacheBuildManifest && self.swiftTool.canUseCachedBuildManifest(), + productsBuildParameters: try productsBuildParameters ?? self.swiftCommandState.productsBuildParameters, + toolsBuildParameters: try toolsBuildParameters ?? self.swiftCommandState.toolsBuildParameters, + cacheBuildManifest: cacheBuildManifest && self.swiftCommandState.canUseCachedBuildManifest(), packageGraphLoader: packageGraphLoader ?? { - try self.swiftTool.loadPackageGraph( + try self.swiftCommandState.loadPackageGraph( explicitProduct: explicitProduct, testEntryPointPath: testEntryPointPath ) }, pluginConfiguration: .init( - scriptRunner: self.swiftTool.getPluginScriptRunner(), - workDirectory: try self.swiftTool.getActiveWorkspace().location.pluginWorkingDirectory, - disableSandbox: self.swiftTool.shouldDisableSandbox + scriptRunner: self.swiftCommandState.getPluginScriptRunner(), + workDirectory: try self.swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory, + disableSandbox: self.swiftCommandState.shouldDisableSandbox ), additionalFileRules: FileRuleDescription.swiftpmFileTypes, - pkgConfigDirectories: self.swiftTool.options.locations.pkgConfigDirectories, - outputStream: outputStream ?? self.swiftTool.outputStream, - logLevel: logLevel ?? self.swiftTool.logLevel, - fileSystem: self.swiftTool.fileSystem, - observabilityScope: observabilityScope ?? self.swiftTool.observabilityScope) + pkgConfigDirectories: self.swiftCommandState.options.locations.pkgConfigDirectories, + dependenciesByRootPackageIdentity: rootPackageInfo.dependencies, + targetsByRootPackageIdentity: rootPackageInfo.targets, + outputStream: outputStream ?? self.swiftCommandState.outputStream, + logLevel: logLevel ?? self.swiftCommandState.logLevel, + fileSystem: self.swiftCommandState.fileSystem, + observabilityScope: observabilityScope ?? self.swiftCommandState.observabilityScope) } } private struct XcodeBuildSystemFactory: BuildSystemFactory { - let swiftTool: SwiftTool + let swiftCommandState: SwiftCommandState func makeBuildSystem( explicitProduct: String?, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, - packageGraphLoader: (() throws -> PackageGraph)?, + packageGraphLoader: (() throws -> ModulesGraph)?, outputStream: OutputByteStream?, logLevel: Diagnostic.Severity?, observabilityScope: ObservabilityScope? ) throws -> any BuildSystem { return try XcodeBuildSystem( - buildParameters: productsBuildParameters ?? self.swiftTool.productsBuildParameters, + buildParameters: productsBuildParameters ?? self.swiftCommandState.productsBuildParameters, packageGraphLoader: packageGraphLoader ?? { - try self.swiftTool.loadPackageGraph( + try self.swiftCommandState.loadPackageGraph( explicitProduct: explicitProduct ) }, - outputStream: outputStream ?? self.swiftTool.outputStream, - logLevel: logLevel ?? self.swiftTool.logLevel, - fileSystem: self.swiftTool.fileSystem, - observabilityScope: observabilityScope ?? self.swiftTool.observabilityScope + outputStream: outputStream ?? self.swiftCommandState.outputStream, + logLevel: logLevel ?? self.swiftCommandState.logLevel, + fileSystem: self.swiftCommandState.fileSystem, + observabilityScope: observabilityScope ?? self.swiftCommandState.observabilityScope ) } } -extension SwiftTool { - public var defaultBuildSystemProvider: BuildSystemProvider { +extension SwiftCommandState { + package var defaultBuildSystemProvider: BuildSystemProvider { .init(providers: [ - .native: NativeBuildSystemFactory(swiftTool: self), - .xcode: XcodeBuildSystemFactory(swiftTool: self) + .native: NativeBuildSystemFactory(swiftCommandState: self), + .xcode: XcodeBuildSystemFactory(swiftCommandState: self) ]) } } diff --git a/Sources/CoreCommands/CMakeLists.txt b/Sources/CoreCommands/CMakeLists.txt index 13d446033e1..d7ec0cbb632 100644 --- a/Sources/CoreCommands/CMakeLists.txt +++ b/Sources/CoreCommands/CMakeLists.txt @@ -8,8 +8,8 @@ add_library(CoreCommands BuildSystemSupport.swift - SwiftTool.swift - SwiftToolObservabilityHandler.swift + SwiftCommandState.swift + SwiftCommandObservabilityHandler.swift Options.swift) target_link_libraries(CoreCommands PUBLIC ArgumentParser diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift index 8b558320371..59def60256d 100644 --- a/Sources/CoreCommands/Options.swift +++ b/Sources/CoreCommands/Options.swift @@ -15,6 +15,7 @@ import ArgumentParser import var Basics.localFileSystem import struct Basics.AbsolutePath import struct Basics.Triple +import func Basics.temp_await import struct Foundation.URL @@ -22,67 +23,70 @@ import enum PackageModel.BuildConfiguration import struct PackageModel.BuildFlags import struct PackageModel.EnabledSanitizers import struct PackageModel.PackageIdentity +import class PackageModel.Manifest import enum PackageModel.Sanitizer +import struct SPMBuildCore.BuildParameters import struct SPMBuildCore.BuildSystemProvider import struct TSCBasic.StringError import struct TSCUtility.Version +import class Workspace.Workspace import struct Workspace.WorkspaceConfiguration -public struct GlobalOptions: ParsableArguments { - public init() {} +package struct GlobalOptions: ParsableArguments { + package init() {} @OptionGroup() - public var locations: LocationOptions + package var locations: LocationOptions @OptionGroup() - public var caching: CachingOptions + package var caching: CachingOptions @OptionGroup() - public var logging: LoggingOptions + package var logging: LoggingOptions @OptionGroup() - public var security: SecurityOptions + package var security: SecurityOptions @OptionGroup() - public var resolver: ResolverOptions + package var resolver: ResolverOptions @OptionGroup() - public var build: BuildOptions + package var build: BuildOptions @OptionGroup() - public var linker: LinkerOptions + package var linker: LinkerOptions } -public struct LocationOptions: ParsableArguments { - public init() {} +package struct LocationOptions: ParsableArguments { + package init() {} @Option( name: .customLong("package-path"), help: "Specify the package path to operate on (default current directory). This changes the working directory before any other operation", completion: .directory ) - public var packageDirectory: AbsolutePath? + package var packageDirectory: AbsolutePath? @Option(name: .customLong("cache-path"), help: "Specify the shared cache directory path", completion: .directory) - public var cacheDirectory: AbsolutePath? + package var cacheDirectory: AbsolutePath? @Option( name: .customLong("config-path"), help: "Specify the shared configuration directory path", completion: .directory ) - public var configurationDirectory: AbsolutePath? + package var configurationDirectory: AbsolutePath? @Option( name: .customLong("security-path"), help: "Specify the shared security directory path", completion: .directory ) - public var securityDirectory: AbsolutePath? + package var securityDirectory: AbsolutePath? /// The custom .build directory, if provided. @Option( @@ -101,15 +105,22 @@ public struct LocationOptions: ParsableArguments { /// The path to the file containing multiroot package data. This is currently Xcode's workspace file. @Option(name: .customLong("multiroot-data-file"), help: .hidden, completion: .directory) - public var multirootPackageDataFile: AbsolutePath? + package var multirootPackageDataFile: AbsolutePath? /// Path to the compilation destination describing JSON file. @Option(name: .customLong("destination"), help: .hidden, completion: .directory) - public var customCompileDestination: AbsolutePath? + package var customCompileDestination: AbsolutePath? - /// Path to the directory containing installed Swift SDKs. @Option(name: .customLong("experimental-swift-sdks-path"), help: .hidden, completion: .directory) - public var swiftSDKsDirectory: AbsolutePath? + package var deprecatedSwiftSDKsDirectory: AbsolutePath? + + /// Path to the directory containing installed Swift SDKs. + @Option( + name: .customLong("swift-sdks-path"), + help: "Path to the directory containing installed Swift SDKs", + completion: .directory + ) + package var swiftSDKsDirectory: AbsolutePath? @Option( name: .customLong("pkg-config-path"), @@ -120,11 +131,14 @@ public struct LocationOptions: ParsableArguments { """, completion: .directory ) - public var pkgConfigDirectories: [AbsolutePath] = [] + package var pkgConfigDirectories: [AbsolutePath] = [] + + @Flag(name: .customLong("ignore-lock"), help: .hidden) + package var ignoreLock: Bool = false } -public struct CachingOptions: ParsableArguments { - public init() {} +package struct CachingOptions: ParsableArguments { + package init() {} /// Disables package caching. @Flag( @@ -132,60 +146,60 @@ public struct CachingOptions: ParsableArguments { inversion: .prefixedEnableDisable, help: "Use a shared cache when fetching dependencies" ) - public var useDependenciesCache: Bool = true + package var useDependenciesCache: Bool = true /// Disables manifest caching. @Flag(name: .customLong("disable-package-manifest-caching"), help: .hidden) - public var shouldDisableManifestCaching: Bool = false + package var shouldDisableManifestCaching: Bool = false /// Whether to enable llbuild manifest caching. @Flag(name: .customLong("build-manifest-caching"), inversion: .prefixedEnableDisable) - public var cacheBuildManifest: Bool = true + package var cacheBuildManifest: Bool = true /// Disables manifest caching. @Option( name: .customLong("manifest-cache"), help: "Caching mode of Package.swift manifests (shared: shared cache, local: package's build directory, none: disabled" ) - public var manifestCachingMode: ManifestCachingMode = .shared + package var manifestCachingMode: ManifestCachingMode = .shared - public enum ManifestCachingMode: String, ExpressibleByArgument { + package enum ManifestCachingMode: String, ExpressibleByArgument { case none case local case shared - public init?(argument: String) { + package init?(argument: String) { self.init(rawValue: argument) } } } -public struct LoggingOptions: ParsableArguments { - public init() {} +package struct LoggingOptions: ParsableArguments { + package init() {} /// The verbosity of informational output. @Flag(name: .shortAndLong, help: "Increase verbosity to include informational output") - public var verbose: Bool = false + package var verbose: Bool = false /// The verbosity of informational output. @Flag(name: [.long, .customLong("vv")], help: "Increase verbosity to include debug output") - public var veryVerbose: Bool = false + package var veryVerbose: Bool = false /// Whether logging output should be limited to `.error`. @Flag(name: .shortAndLong, help: "Decrease verbosity to only include error output.") - public var quiet: Bool = false + package var quiet: Bool = false } -public struct SecurityOptions: ParsableArguments { - public init() {} +package struct SecurityOptions: ParsableArguments { + package init() {} /// Disables sandboxing when executing subprocesses. @Flag(name: .customLong("disable-sandbox"), help: "Disable using the sandbox when executing subprocesses") - public var shouldDisableSandbox: Bool = false + package var shouldDisableSandbox: Bool = false /// Force usage of the netrc file even in cases where it is not allowed. @Flag(name: .customLong("netrc"), help: "Use netrc file even in cases where other credential stores are preferred") - public var forceNetrc: Bool = false + package var forceNetrc: Bool = false /// Whether to load netrc files for authenticating with remote servers /// when downloading binary artifacts. This has no effects on registry @@ -195,7 +209,7 @@ public struct SecurityOptions: ParsableArguments { exclusivity: .exclusive, help: "Load credentials from a netrc file" ) - public var netrc: Bool = true + package var netrc: Bool = true /// The path to the netrc file used when `netrc` is `true`. @Option( @@ -203,7 +217,7 @@ public struct SecurityOptions: ParsableArguments { help: "Specify the netrc file path", completion: .file() ) - public var netrcFilePath: AbsolutePath? + package var netrcFilePath: AbsolutePath? /// Whether to use keychain for authenticating with remote servers /// when downloading binary artifacts. This has no effects on registry @@ -214,61 +228,61 @@ public struct SecurityOptions: ParsableArguments { exclusivity: .exclusive, help: "Search credentials in macOS keychain" ) - public var keychain: Bool = true + package var keychain: Bool = true #else @Flag( inversion: .prefixedEnableDisable, exclusivity: .exclusive, help: .hidden ) - public var keychain: Bool = false + package var keychain: Bool = false #endif @Option(name: .customLong("resolver-fingerprint-checking")) - public var fingerprintCheckingMode: WorkspaceConfiguration.CheckingMode = .strict + package var fingerprintCheckingMode: WorkspaceConfiguration.CheckingMode = .strict @Option(name: .customLong("resolver-signing-entity-checking")) - public var signingEntityCheckingMode: WorkspaceConfiguration.CheckingMode = .warn + package var signingEntityCheckingMode: WorkspaceConfiguration.CheckingMode = .warn @Flag( inversion: .prefixedEnableDisable, exclusivity: .exclusive, help: "Validate signature of a signed package release downloaded from registry" ) - public var signatureValidation: Bool = true + package var signatureValidation: Bool = true } -public struct ResolverOptions: ParsableArguments { - public init() {} +package struct ResolverOptions: ParsableArguments { + package init() {} /// Enable prefetching in resolver which will kick off parallel git cloning. @Flag(name: .customLong("prefetching"), inversion: .prefixedEnableDisable) - public var shouldEnableResolverPrefetching: Bool = true + package var shouldEnableResolverPrefetching: Bool = true /// Use Package.resolved file for resolving dependencies. @Flag( name: [.long, .customLong("disable-automatic-resolution"), .customLong("only-use-versions-from-resolved-file")], help: "Only use versions from the Package.resolved file and fail resolution if it is out-of-date" ) - public var forceResolvedVersions: Bool = false + package var forceResolvedVersions: Bool = false /// Skip updating dependencies from their remote during a resolution. @Flag(name: .customLong("skip-update"), help: "Skip updating dependencies from their remote during a resolution") - public var skipDependencyUpdate: Bool = false + package var skipDependencyUpdate: Bool = false @Flag(help: "Define automatic transformation of source control based dependencies to registry based ones") - public var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation = + package var sourceControlToRegistryDependencyTransformation: SourceControlToRegistryDependencyTransformation = .disabled @Option(help: "Default registry URL to use, instead of the registries.json configuration file") - public var defaultRegistryURL: URL? + package var defaultRegistryURL: URL? - public enum SourceControlToRegistryDependencyTransformation: EnumerableFlag { + package enum SourceControlToRegistryDependencyTransformation: EnumerableFlag { case disabled case identity case swizzle - public static func name(for value: Self) -> NameSpecification { + package static func name(for value: Self) -> NameSpecification { switch value { case .disabled: return .customLong("disable-scm-to-registry-transformation") @@ -279,7 +293,7 @@ public struct ResolverOptions: ParsableArguments { } } - public static func help(for value: SourceControlToRegistryDependencyTransformation) -> ArgumentHelp? { + package static func help(for value: SourceControlToRegistryDependencyTransformation) -> ArgumentHelp? { switch value { case .disabled: return "disable source control to registry transformation" @@ -292,12 +306,12 @@ public struct ResolverOptions: ParsableArguments { } } -public struct BuildOptions: ParsableArguments { - public init() {} +package struct BuildOptions: ParsableArguments { + package init() {} /// Build configuration. @Option(name: .shortAndLong, help: "Build with configuration") - public var configuration: BuildConfiguration = .debug + package var configuration: BuildConfiguration = .debug @Option( name: .customLong("Xcc", withSingleDash: true), @@ -335,7 +349,7 @@ public struct BuildOptions: ParsableArguments { visibility: .hidden ) ) - public var xcbuildFlags: [String] = [] + package var xcbuildFlags: [String] = [] @Option( name: .customLong("Xbuild-tools-swiftc", withSingleDash: true), @@ -345,7 +359,7 @@ public struct BuildOptions: ParsableArguments { visibility: .hidden ) ) - public var _buildToolsSwiftCFlags: [String] = [] + package var _buildToolsSwiftCFlags: [String] = [] @Option( name: .customLong("Xmanifest", withSingleDash: true), @@ -355,7 +369,7 @@ public struct BuildOptions: ParsableArguments { visibility: .hidden ) ) - public var _deprecated_manifestFlags: [String] = [] + package var _deprecated_manifestFlags: [String] = [] var manifestFlags: [String] { self._deprecated_manifestFlags.isEmpty ? @@ -367,7 +381,7 @@ public struct BuildOptions: ParsableArguments { self._buildToolsSwiftCFlags } - public var buildFlags: BuildFlags { + package var buildFlags: BuildFlags { BuildFlags( cCompilerFlags: self.cCompilerFlags, cxxCompilerFlags: self.cxxCompilerFlags, @@ -379,15 +393,15 @@ public struct BuildOptions: ParsableArguments { /// The compilation destination’s target triple. @Option(name: .customLong("triple"), transform: Triple.init) - public var customCompileTriple: Triple? + package var customCompileTriple: Triple? /// Path to the compilation destination’s SDK. @Option(name: .customLong("sdk")) - public var customCompileSDK: AbsolutePath? + package var customCompileSDK: AbsolutePath? /// Path to the compilation destination’s toolchain. @Option(name: .customLong("toolchain")) - public var customCompileToolchain: AbsolutePath? + package var customCompileToolchain: AbsolutePath? /// The architectures to compile for. @Option( @@ -397,47 +411,53 @@ public struct BuildOptions: ParsableArguments { visibility: .hidden ) ) - public var architectures: [String] = [] + package var architectures: [String] = [] - /// Filter for selecting a specific Swift SDK to build with. @Option(name: .customLong("experimental-swift-sdk"), help: .hidden) - public var swiftSDKSelector: String? + package var deprecatedSwiftSDKSelector: String? + + /// Filter for selecting a specific Swift SDK to build with. + @Option( + name: .customLong("swift-sdk"), + help: "Filter for selecting a specific Swift SDK to build with" + ) + package var swiftSDKSelector: String? /// Which compile-time sanitizers should be enabled. @Option( name: .customLong("sanitize"), help: "Turn on runtime checks for erroneous behavior, possible values: \(Sanitizer.formattedValues)" ) - public var sanitizers: [Sanitizer] = [] + package var sanitizers: [Sanitizer] = [] - public var enabledSanitizers: EnabledSanitizers { + package var enabledSanitizers: EnabledSanitizers { EnabledSanitizers(Set(sanitizers)) } @Flag(help: "Enable or disable indexing-while-building feature") - public var indexStoreMode: StoreMode = .autoIndexStore + package var indexStoreMode: StoreMode = .autoIndexStore /// Whether to enable generation of `.swiftinterface`s alongside `.swiftmodule`s. @Flag(name: .customLong("enable-parseable-module-interfaces")) - public var shouldEnableParseableModuleInterfaces: Bool = false + package var shouldEnableParseableModuleInterfaces: Bool = false /// The number of jobs for llbuild to start (aka the number of schedulerLanes) @Option(name: .shortAndLong, help: "The number of jobs to spawn in parallel during the build process") - public var jobs: UInt32? + package var jobs: UInt32? /// Whether to use the integrated Swift driver rather than shelling out /// to a separate process. @Flag() - public var useIntegratedSwiftDriver: Bool = false + package var useIntegratedSwiftDriver: Bool = false /// A flag that indicates this build should check whether targets only import /// their explicitly-declared dependencies @Option() - public var explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode = .none + package var explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode = .none /// Whether to use the explicit module build flow (with the integrated driver) @Flag(name: .customLong("experimental-explicit-module-build")) - public var useExplicitModuleBuild: Bool = false + package var useExplicitModuleBuild: Bool = false /// The build system to use. @Option(name: .customLong("build-system")) @@ -445,9 +465,9 @@ public struct BuildOptions: ParsableArguments { /// The Debug Information Format to use. @Option(name: .customLong("debug-info-format", withSingleDash: true)) - public var debugInfoFormat: DebugInfoFormat = .dwarf + package var debugInfoFormat: DebugInfoFormat = .dwarf - public var buildSystem: BuildSystemProvider.Kind { + package var buildSystem: BuildSystemProvider.Kind { #if os(macOS) // Force the Xcode build system if we want to build more than one arch. return self.architectures.count > 1 ? .xcode : self._buildSystem @@ -459,7 +479,7 @@ public struct BuildOptions: ParsableArguments { /// Whether to enable test discovery on platforms without Objective-C runtime. @Flag(help: .hidden) - public var enableTestDiscovery: Bool = false + package var enableTestDiscovery: Bool = false /// Path of test entry point file to use, instead of synthesizing one or using `XCTMain.swift` in the package (if /// present). @@ -468,40 +488,40 @@ public struct BuildOptions: ParsableArguments { name: .customLong("experimental-test-entry-point-path"), help: .hidden ) - public var testEntryPointPath: AbsolutePath? + package var testEntryPointPath: AbsolutePath? /// The lto mode to use if any. @Option( name: .customLong("experimental-lto-mode"), help: .hidden ) - public var linkTimeOptimizationMode: LinkTimeOptimizationMode? + package var linkTimeOptimizationMode: LinkTimeOptimizationMode? @Flag(inversion: .prefixedEnableDisable, help: .hidden) - public var getTaskAllowEntitlement: Bool? = nil + package var getTaskAllowEntitlement: Bool? = nil // Whether to omit frame pointers // this can be removed once the backtracer uses DWARF instead of frame pointers @Flag(inversion: .prefixedNo, help: .hidden) - public var omitFramePointers: Bool? = nil + package var omitFramePointers: Bool? = nil // @Flag works best when there is a default value present // if true, false aren't enough and a third state is needed // nil should not be the goto. Instead create an enum - public enum StoreMode: EnumerableFlag { + package enum StoreMode: EnumerableFlag { case autoIndexStore case enableIndexStore case disableIndexStore } - public enum TargetDependencyImportCheckingMode: String, Codable, ExpressibleByArgument { + package enum TargetDependencyImportCheckingMode: String, Codable, ExpressibleByArgument { case none case warn case error } /// See `BuildParameters.LinkTimeOptimizationMode` for details. - public enum LinkTimeOptimizationMode: String, Codable, ExpressibleByArgument { + package enum LinkTimeOptimizationMode: String, Codable, ExpressibleByArgument { /// See `BuildParameters.LinkTimeOptimizationMode.full` for details. case full /// See `BuildParameters.LinkTimeOptimizationMode.thin` for details. @@ -509,7 +529,7 @@ public struct BuildOptions: ParsableArguments { } /// See `BuildParameters.DebugInfoFormat` for details. - public enum DebugInfoFormat: String, Codable, ExpressibleByArgument { + package enum DebugInfoFormat: String, Codable, ExpressibleByArgument { /// See `BuildParameters.DebugInfoFormat.dwarf` for details. case dwarf /// See `BuildParameters.DebugInfoFormat.codeview` for details. @@ -519,19 +539,112 @@ public struct BuildOptions: ParsableArguments { } } -public struct LinkerOptions: ParsableArguments { - public init() {} +package struct LinkerOptions: ParsableArguments { + package init() {} @Flag( name: .customLong("dead-strip"), inversion: .prefixedEnableDisable, help: "Disable/enable dead code stripping by the linker" ) - public var linkerDeadStrip: Bool = true + package var linkerDeadStrip: Bool = true /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default") - public var shouldDisableLocalRpath: Bool = false + package var shouldDisableLocalRpath: Bool = false +} + +/// Which testing libraries to use (and any related options.) +package struct TestLibraryOptions: ParsableArguments { + package init() {} + + /// Whether to enable support for XCTest (as explicitly specified by the user.) + /// + /// Callers will generally want to use ``enableXCTestSupport`` since it will + /// have the correct default value if the user didn't specify one. + @Flag(name: .customLong("xctest"), + inversion: .prefixedEnableDisable, + help: "Enable support for XCTest") + package var explicitlyEnableXCTestSupport: Bool? + + /// Whether to enable support for XCTest. + package var enableXCTestSupport: Bool { + // Default to enabled. + explicitlyEnableXCTestSupport ?? true + } + + /// Whether to enable support for swift-testing (as explicitly specified by the user.) + /// + /// Callers (other than `swift package init`) will generally want to use + /// ``enableSwiftTestingLibrarySupport(swiftCommandState:)`` since it will + /// take into account whether the package has a dependency on swift-testing. + @Flag(name: .customLong("experimental-swift-testing"), + inversion: .prefixedEnableDisable, + help: "Enable experimental support for swift-testing") + package var explicitlyEnableSwiftTestingLibrarySupport: Bool? + + /// Whether to enable support for swift-testing. + package func enableSwiftTestingLibrarySupport( + swiftCommandState: SwiftCommandState + ) throws -> Bool { + // Honor the user's explicit command-line selection, if any. + if let callerSuppliedValue = explicitlyEnableSwiftTestingLibrarySupport { + return callerSuppliedValue + } + + // If the active package has a dependency on swift-testing, automatically enable support for it so that extra steps are not needed. + let workspace = try swiftCommandState.getActiveWorkspace() + let root = try swiftCommandState.getWorkspaceRoot() + let rootManifests = try temp_await { + workspace.loadRootManifests( + packages: root.packages, + observabilityScope: swiftCommandState.observabilityScope, + completion: $0 + ) + } + + // Is swift-testing among the dependencies of the package being built? + // If so, enable support. + let isEnabledByDependency = rootManifests.values.lazy + .flatMap(\.dependencies) + .map(\.identity) + .map(String.init(describing:)) + .contains("swift-testing") + if isEnabledByDependency { + swiftCommandState.observabilityScope.emit(debug: "Enabling swift-testing support due to its presence as a package dependency.") + return true + } + + // Is swift-testing the package being built itself (unlikely)? If so, + // enable support. + let isEnabledByName = root.packages.lazy + .map(PackageIdentity.init(path:)) + .map(String.init(describing:)) + .contains("swift-testing") + if isEnabledByName { + swiftCommandState.observabilityScope.emit(debug: "Enabling swift-testing support because it is a root package.") + return true + } + + // Default to disabled since swift-testing is experimental (opt-in.) + return false + } + + /// Get the set of enabled testing libraries. + package func enabledTestingLibraries( + swiftCommandState: SwiftCommandState + ) throws -> Set { + var result = Set() + + if enableXCTestSupport { + result.insert(.xctest) + } + if try enableSwiftTestingLibrarySupport(swiftCommandState: swiftCommandState) { + result.insert(.swiftTesting) + } + + return result + } } // MARK: - Extensions @@ -603,7 +716,7 @@ extension URL { } } -#if swift(<5.11) +#if swift(<6.0) extension BuildConfiguration: ExpressibleByArgument {} extension AbsolutePath: ExpressibleByArgument {} extension WorkspaceConfiguration.CheckingMode: ExpressibleByArgument {} diff --git a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift b/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift similarity index 92% rename from Sources/CoreCommands/SwiftToolObservabilityHandler.swift rename to Sources/CoreCommands/SwiftCommandObservabilityHandler.swift index 2c82fe21912..56cc2ce9359 100644 --- a/Sources/CoreCommands/SwiftToolObservabilityHandler.swift +++ b/Sources/CoreCommands/SwiftCommandObservabilityHandler.swift @@ -10,7 +10,6 @@ // //===----------------------------------------------------------------------===// - import Basics import Dispatch @@ -22,10 +21,10 @@ import class TSCUtility.MultiLineNinjaProgressAnimation import class TSCUtility.NinjaProgressAnimation import protocol TSCUtility.ProgressAnimationProtocol -public struct SwiftToolObservabilityHandler: ObservabilityHandlerProvider { +package struct SwiftCommandObservabilityHandler: ObservabilityHandlerProvider { private let outputHandler: OutputHandler - public var diagnosticsHandler: DiagnosticsHandler { + package var diagnosticsHandler: DiagnosticsHandler { self.outputHandler } @@ -34,7 +33,7 @@ public struct SwiftToolObservabilityHandler: ObservabilityHandlerProvider { /// - outputStream: an instance of a stream used for output. /// - logLevel: the lowest severity of diagnostics that this handler will forward to `outputStream`. Diagnostics /// emitted below this level will be ignored. - public init(outputStream: OutputByteStream, logLevel: Basics.Diagnostic.Severity) { + package init(outputStream: OutputByteStream, logLevel: Basics.Diagnostic.Severity) { let threadSafeOutputByteStream = outputStream as? ThreadSafeOutputByteStream ?? ThreadSafeOutputByteStream(outputStream) self.outputHandler = OutputHandler(logLevel: logLevel, outputStream: threadSafeOutputByteStream) } @@ -59,7 +58,7 @@ public struct SwiftToolObservabilityHandler: ObservabilityHandlerProvider { self.outputHandler.prompt(message: message, completion: completion) } - public func wait(timeout: DispatchTime) { + package func wait(timeout: DispatchTime) { self.outputHandler.wait(timeout: timeout) } @@ -76,9 +75,10 @@ public struct SwiftToolObservabilityHandler: ObservabilityHandlerProvider { self.logLevel = logLevel self.outputStream = outputStream self.writer = InteractiveWriter(stream: outputStream) - self.progressAnimation = logLevel.isVerbose ? - MultiLineNinjaProgressAnimation(stream: outputStream) : - NinjaProgressAnimation(stream: outputStream) + self.progressAnimation = ProgressAnimation.ninja( + stream: self.outputStream, + verbose: self.logLevel.isVerbose + ) } func handleDiagnostic(scope: ObservabilityScope, diagnostic: Basics.Diagnostic) { @@ -165,8 +165,8 @@ public struct SwiftToolObservabilityHandler: ObservabilityHandlerProvider { } } -extension SwiftToolObservabilityHandler.OutputHandler: @unchecked Sendable {} -extension SwiftToolObservabilityHandler.OutputHandler: DiagnosticsHandler {} +extension SwiftCommandObservabilityHandler.OutputHandler: @unchecked Sendable {} +extension SwiftCommandObservabilityHandler.OutputHandler: DiagnosticsHandler {} /// This type is used to write on the underlying stream. /// diff --git a/Sources/CoreCommands/SwiftTool.swift b/Sources/CoreCommands/SwiftCommandState.swift similarity index 85% rename from Sources/CoreCommands/SwiftTool.swift rename to Sources/CoreCommands/SwiftCommandState.swift index 3f20f707b8e..44a61945ad1 100644 --- a/Sources/CoreCommands/SwiftTool.swift +++ b/Sources/CoreCommands/SwiftCommandState.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -17,13 +17,16 @@ import class Foundation.NSLock import class Foundation.ProcessInfo import PackageGraph import PackageLoading -@_spi(SwiftPMInternal) + import PackageModel + import SPMBuildCore + import Workspace #if USE_IMPL_ONLY_IMPORTS -@_implementationOnly import DriverSupport +@_implementationOnly +import DriverSupport #else import DriverSupport #endif @@ -48,20 +51,16 @@ import var TSCBasic.stderrStream import class TSCBasic.TerminalController import class TSCBasic.ThreadSafeOutputByteStream -import class TSCUtility.MultiLineNinjaProgressAnimation -import class TSCUtility.NinjaProgressAnimation -import class TSCUtility.PercentProgressAnimation -import protocol TSCUtility.ProgressAnimationProtocol -import var TSCUtility.verbosity +import TSCUtility // cannot be scoped because of `String.spm_mangleToC99ExtendedIdentifier()` typealias Diagnostic = Basics.Diagnostic -public struct ToolWorkspaceConfiguration { +package struct ToolWorkspaceConfiguration { let shouldInstallSignalHandlers: Bool let wantsMultipleTestProducts: Bool let wantsREPLProduct: Bool - public init( + package init( shouldInstallSignalHandlers: Bool = true, wantsMultipleTestProducts: Bool = false, wantsREPLProduct: Bool = false @@ -72,38 +71,38 @@ public struct ToolWorkspaceConfiguration { } } -public typealias WorkspaceDelegateProvider = ( +package typealias WorkspaceDelegateProvider = ( _ observabilityScope: ObservabilityScope, _ outputHandler: @escaping (String, Bool) -> Void, _ progressHandler: @escaping (Int64, Int64, String?) -> Void, _ inputHandler: @escaping (String, (String?) -> Void) -> Void ) -> WorkspaceDelegate -public typealias WorkspaceLoaderProvider = (_ fileSystem: FileSystem, _ observabilityScope: ObservabilityScope) +package typealias WorkspaceLoaderProvider = (_ fileSystem: FileSystem, _ observabilityScope: ObservabilityScope) -> WorkspaceLoader -public protocol _SwiftCommand { +package protocol _SwiftCommand { var globalOptions: GlobalOptions { get } var toolWorkspaceConfiguration: ToolWorkspaceConfiguration { get } var workspaceDelegateProvider: WorkspaceDelegateProvider { get } var workspaceLoaderProvider: WorkspaceLoaderProvider { get } - func buildSystemProvider(_ swiftTool: SwiftTool) throws -> BuildSystemProvider + func buildSystemProvider(_ swiftCommandState: SwiftCommandState) throws -> BuildSystemProvider } extension _SwiftCommand { - public var toolWorkspaceConfiguration: ToolWorkspaceConfiguration { + package var toolWorkspaceConfiguration: ToolWorkspaceConfiguration { return .init() } } -public protocol SwiftCommand: ParsableCommand, _SwiftCommand { - func run(_ swiftTool: SwiftTool) throws +package protocol SwiftCommand: ParsableCommand, _SwiftCommand { + func run(_ swiftCommandState: SwiftCommandState) throws } extension SwiftCommand { - public static var _errorLabel: String { "error" } + package static var _errorLabel: String { "error" } - public func run() throws { - let swiftTool = try SwiftTool( + package func run() throws { + let swiftCommandState = try SwiftCommandState( options: globalOptions, toolWorkspaceConfiguration: self.toolWorkspaceConfiguration, workspaceDelegateProvider: self.workspaceDelegateProvider, @@ -111,23 +110,23 @@ extension SwiftCommand { ) // We use this to attempt to catch misuse of the locking APIs since we only release the lock from here. - swiftTool.setNeedsLocking() + swiftCommandState.setNeedsLocking() - swiftTool.buildSystemProvider = try buildSystemProvider(swiftTool) + swiftCommandState.buildSystemProvider = try buildSystemProvider(swiftCommandState) var toolError: Error? = .none do { - try self.run(swiftTool) - if swiftTool.observabilityScope.errorsReported || swiftTool.executionStatus == .failure { + try self.run(swiftCommandState) + if swiftCommandState.observabilityScope.errorsReported || swiftCommandState.executionStatus == .failure { throw ExitCode.failure } } catch { toolError = error } - swiftTool.releaseLockIfNeeded() + swiftCommandState.releaseLockIfNeeded() // wait for all observability items to process - swiftTool.waitForObservabilityEvents(timeout: .now() + 5) + swiftCommandState.waitForObservabilityEvents(timeout: .now() + 5) if let toolError { throw toolError @@ -135,16 +134,16 @@ extension SwiftCommand { } } -public protocol AsyncSwiftCommand: AsyncParsableCommand, _SwiftCommand { - func run(_ swiftTool: SwiftTool) async throws +package protocol AsyncSwiftCommand: AsyncParsableCommand, _SwiftCommand { + func run(_ swiftCommandState: SwiftCommandState) async throws } extension AsyncSwiftCommand { - public static var _errorLabel: String { "error" } + package static var _errorLabel: String { "error" } // FIXME: It doesn't seem great to have this be duplicated with `SwiftCommand`. - public func run() async throws { - let swiftTool = try SwiftTool( + package func run() async throws { + let swiftCommandState = try SwiftCommandState( options: globalOptions, toolWorkspaceConfiguration: self.toolWorkspaceConfiguration, workspaceDelegateProvider: self.workspaceDelegateProvider, @@ -152,23 +151,23 @@ extension AsyncSwiftCommand { ) // We use this to attempt to catch misuse of the locking APIs since we only release the lock from here. - swiftTool.setNeedsLocking() + swiftCommandState.setNeedsLocking() - swiftTool.buildSystemProvider = try buildSystemProvider(swiftTool) + swiftCommandState.buildSystemProvider = try buildSystemProvider(swiftCommandState) var toolError: Error? = .none do { - try await self.run(swiftTool) - if swiftTool.observabilityScope.errorsReported || swiftTool.executionStatus == .failure { + try await self.run(swiftCommandState) + if swiftCommandState.observabilityScope.errorsReported || swiftCommandState.executionStatus == .failure { throw ExitCode.failure } } catch { toolError = error } - swiftTool.releaseLockIfNeeded() + swiftCommandState.releaseLockIfNeeded() // wait for all observability items to process - swiftTool.waitForObservabilityEvents(timeout: .now() + 5) + swiftCommandState.waitForObservabilityEvents(timeout: .now() + 5) if let toolError { throw toolError @@ -176,23 +175,23 @@ extension AsyncSwiftCommand { } } -public final class SwiftTool { +package final class SwiftCommandState { #if os(Windows) // unfortunately this is needed for C callback handlers used by Windows shutdown handler static var cancellator: Cancellator? #endif /// The original working directory. - public let originalWorkingDirectory: AbsolutePath + package let originalWorkingDirectory: AbsolutePath /// The options of this tool. - public let options: GlobalOptions + package let options: GlobalOptions /// Path to the root package directory, nil if manifest is not found. private let packageRoot: AbsolutePath? /// Helper function to get package root or throw error if it is not found. - public func getPackageRoot() throws -> AbsolutePath { + package func getPackageRoot() throws -> AbsolutePath { guard let packageRoot = packageRoot else { throw StringError("Could not find \(Manifest.filename) in this directory or any of its parent directories.") } @@ -200,7 +199,7 @@ public final class SwiftTool { } /// Get the current workspace root object. - public func getWorkspaceRoot() throws -> PackageGraphRootInput { + package func getWorkspaceRoot() throws -> PackageGraphRootInput { let packages: [AbsolutePath] if let workspace = options.locations.multirootPackageDataFile { @@ -214,25 +213,25 @@ public final class SwiftTool { } /// Scratch space (.build) directory. - public let scratchDirectory: AbsolutePath + package let scratchDirectory: AbsolutePath /// Path to the shared security directory - public let sharedSecurityDirectory: AbsolutePath + package let sharedSecurityDirectory: AbsolutePath /// Path to the shared cache directory - public let sharedCacheDirectory: AbsolutePath + package let sharedCacheDirectory: AbsolutePath /// Path to the shared configuration directory - public let sharedConfigurationDirectory: AbsolutePath + package let sharedConfigurationDirectory: AbsolutePath /// Path to the cross-compilation Swift SDKs directory. - public let sharedSwiftSDKsDirectory: AbsolutePath + package let sharedSwiftSDKsDirectory: AbsolutePath /// Cancellator to handle cancellation of outstanding work when handling SIGINT - public let cancellator: Cancellator + package let cancellator: Cancellator /// The execution status of the tool. - public var executionStatus: ExecutionStatus = .success + package var executionStatus: ExecutionStatus = .success /// Holds the currently active workspace. /// @@ -242,19 +241,19 @@ public final class SwiftTool { private var _workspace: Workspace? private var _workspaceDelegate: WorkspaceDelegate? - private let observabilityHandler: SwiftToolObservabilityHandler + private let observabilityHandler: SwiftCommandObservabilityHandler /// The observability scope to emit diagnostics event on - public let observabilityScope: ObservabilityScope + package let observabilityScope: ObservabilityScope /// The min severity at which to log diagnostics - public let logLevel: Basics.Diagnostic.Severity + package let logLevel: Basics.Diagnostic.Severity // should use sandbox on external subcommands - public var shouldDisableSandbox: Bool + package var shouldDisableSandbox: Bool /// The file system in use - public let fileSystem: FileSystem + package let fileSystem: FileSystem /// Provider which can create a `WorkspaceDelegate` if needed. private let workspaceDelegateProvider: WorkspaceDelegateProvider @@ -269,7 +268,7 @@ public final class SwiftTool { /// Create an instance of this tool. /// /// - parameter options: The command line options to be passed to this tool. - public convenience init( + package convenience init( options: GlobalOptions, toolWorkspaceConfiguration: ToolWorkspaceConfiguration = .init(), workspaceDelegateProvider: @escaping WorkspaceDelegateProvider, @@ -300,7 +299,7 @@ public final class SwiftTool { self.fileSystem = localFileSystem // first, bootstrap the observability system self.logLevel = options.logging.logLevel - self.observabilityHandler = SwiftToolObservabilityHandler(outputStream: outputStream, logLevel: self.logLevel) + self.observabilityHandler = SwiftCommandObservabilityHandler(outputStream: outputStream, logLevel: self.logLevel) let observabilitySystem = ObservabilitySystem(self.observabilityHandler) self.observabilityScope = observabilitySystem.topScope self.shouldDisableSandbox = options.security.shouldDisableSandbox @@ -351,8 +350,13 @@ public final class SwiftTool { fileSystem: fileSystem ) self.sharedCacheDirectory = try getSharedCacheDirectory(options: options, fileSystem: fileSystem) + if options.locations.deprecatedSwiftSDKsDirectory != nil { + self.observabilityScope.emit( + warning: "`--experimental-swift-sdks-path` is deprecated and will be removed in a future version of SwiftPM. Use `--swift-sdks-path` instead." + ) + } self.sharedSwiftSDKsDirectory = try fileSystem.getSharedSwiftSDKsDirectory( - explicitDirectory: options.locations.swiftSDKsDirectory + explicitDirectory: options.locations.swiftSDKsDirectory ?? options.locations.deprecatedSwiftSDKsDirectory ) // set global process logging handler @@ -403,7 +407,7 @@ public final class SwiftTool { } /// Returns the currently active workspace. - public func getActiveWorkspace(emitDeprecatedConfigurationWarning: Bool = false) throws -> Workspace { + package func getActiveWorkspace(emitDeprecatedConfigurationWarning: Bool = false) throws -> Workspace { if let workspace = _workspace { return workspace } @@ -464,6 +468,29 @@ public final class SwiftTool { return workspace } + package func getRootPackageInformation() throws -> (dependencies: [PackageIdentity: [PackageIdentity]], targets: [PackageIdentity: [String]]) { + let workspace = try self.getActiveWorkspace() + let root = try self.getWorkspaceRoot() + let rootManifests = try temp_await { + workspace.loadRootManifests( + packages: root.packages, + observabilityScope: self.observabilityScope, + completion: $0 + ) + } + + var identities = [PackageIdentity: [PackageIdentity]]() + var targets = [PackageIdentity: [String]]() + + rootManifests.forEach { + let identity = PackageIdentity(path: $0.key) + identities[identity] = $0.value.dependencies.map(\.identity) + targets[identity] = $0.value.targets.map { $0.name.spm_mangledToC99ExtendedIdentifier() } + } + + return (identities, targets) + } + private func getEditsDirectory() throws -> AbsolutePath { // TODO: replace multiroot-data-file with explicit overrides if let multiRootPackageDataFile = options.locations.multirootPackageDataFile { @@ -508,7 +535,7 @@ public final class SwiftTool { } } - public func getAuthorizationProvider() throws -> AuthorizationProvider? { + package func getAuthorizationProvider() throws -> AuthorizationProvider? { var authorization = Workspace.Configuration.Authorization.default if !options.security.netrc { authorization.netrc = .disabled @@ -528,7 +555,7 @@ public final class SwiftTool { ) } - public func getRegistryAuthorizationProvider() throws -> AuthorizationProvider? { + package func getRegistryAuthorizationProvider() throws -> AuthorizationProvider? { var authorization = Workspace.Configuration.Authorization.default if let configuredPath = options.security.netrcFilePath { authorization.netrc = .custom(configuredPath) @@ -548,7 +575,7 @@ public final class SwiftTool { } /// Resolve the dependencies. - public func resolve() throws { + package func resolve() throws { let workspace = try getActiveWorkspace() let root = try getWorkspaceRoot() @@ -572,10 +599,10 @@ public final class SwiftTool { /// - explicitProduct: The product specified on the command line to a “swift run” or “swift build” command. This /// allows executables from dependencies to be run directly without having to hook them up to any particular target. @discardableResult - public func loadPackageGraph( + package func loadPackageGraph( explicitProduct: String? = nil, testEntryPointPath: AbsolutePath? = nil - ) throws -> PackageGraph { + ) throws -> ModulesGraph { do { let workspace = try getActiveWorkspace() @@ -599,7 +626,7 @@ public final class SwiftTool { } } - public func getPluginScriptRunner(customPluginsDir: AbsolutePath? = .none) throws -> PluginScriptRunner { + package func getPluginScriptRunner(customPluginsDir: AbsolutePath? = .none) throws -> PluginScriptRunner { let pluginsDir = try customPluginsDir ?? self.getActiveWorkspace().location.pluginWorkingDirectory let cacheDir = pluginsDir.appending("cache") let pluginScriptRunner = try DefaultPluginScriptRunner( @@ -616,11 +643,11 @@ public final class SwiftTool { } /// Returns the user toolchain to compile the actual product. - public func getTargetToolchain() throws -> UserToolchain { + package func getTargetToolchain() throws -> UserToolchain { try _targetToolchain.get() } - public func getHostToolchain() throws -> UserToolchain { + package func getHostToolchain() throws -> UserToolchain { try _hostToolchain.get() } @@ -628,7 +655,7 @@ public final class SwiftTool { try _manifestLoader.get() } - public func canUseCachedBuildManifest() throws -> Bool { + package func canUseCachedBuildManifest() throws -> Bool { if !self.options.caching.cacheBuildManifest { return false } @@ -657,14 +684,14 @@ public final class SwiftTool { // "customOutputStream" is designed to support build output redirection // but it is only expected to be used when invoking builds from "swift build" command. // in all other cases, the build output should go to the default which is stderr - public func createBuildSystem( + package func createBuildSystem( explicitBuildSystem: BuildSystemProvider.Kind? = .none, explicitProduct: String? = .none, cacheBuildManifest: Bool = true, shouldLinkStaticSwiftStdlib: Bool = false, productsBuildParameters: BuildParameters? = .none, toolsBuildParameters: BuildParameters? = .none, - packageGraphLoader: (() throws -> PackageGraph)? = .none, + packageGraphLoader: (() throws -> ModulesGraph)? = .none, outputStream: OutputByteStream? = .none, logLevel: Basics.Diagnostic.Severity? = .none, observabilityScope: ObservabilityScope? = .none @@ -737,7 +764,11 @@ public final class SwiftTool { enableParseableModuleInterfaces: options.build.shouldEnableParseableModuleInterfaces, explicitTargetDependencyImportCheckingMode: options.build.explicitTargetDependencyImportCheck.modeParameter, useIntegratedSwiftDriver: options.build.useIntegratedSwiftDriver, - useExplicitModuleBuild: options.build.useExplicitModuleBuild + useExplicitModuleBuild: options.build.useExplicitModuleBuild, + isPackageAccessModifierSupported: DriverSupport.isPackageNameSupported( + toolchain: toolchain, + fileSystem: self.fileSystem + ) ), linkingParameters: .init( linkerDeadStrip: options.linker.linkerDeadStrip, @@ -757,7 +788,7 @@ public final class SwiftTool { } /// Return the build parameters for the host toolchain. - public var toolsBuildParameters: BuildParameters { + package var toolsBuildParameters: BuildParameters { get throws { try _toolsBuildParameters.get() } @@ -769,7 +800,7 @@ public final class SwiftTool { }) }() - public var productsBuildParameters: BuildParameters { + package var productsBuildParameters: BuildParameters { get throws { try _productsBuildParameters.get() } @@ -794,6 +825,12 @@ public final class SwiftTool { do { let hostToolchain = try _hostToolchain.get() hostSwiftSDK = hostToolchain.swiftSDK + + if options.build.deprecatedSwiftSDKSelector != nil { + self.observabilityScope.emit( + warning: "`--experimental-swift-sdk` is deprecated and will be removed in a future version of SwiftPM. Use `--swift-sdk` instead." + ) + } swiftSDK = try SwiftSDK.deriveTargetSwiftSDK( hostSwiftSDK: hostSwiftSDK, hostTriple: hostToolchain.targetTriple, @@ -801,7 +838,7 @@ public final class SwiftTool { customCompileTriple: options.build.customCompileTriple, customCompileToolchain: options.build.customCompileToolchain, customCompileSDK: options.build.customCompileSDK, - swiftSDKSelector: options.build.swiftSDKSelector, + swiftSDKSelector: options.build.swiftSDKSelector ?? options.build.deprecatedSwiftSDKSelector, architectures: options.build.architectures, store: store, observabilityScope: self.observabilityScope, @@ -844,23 +881,6 @@ public final class SwiftTool { } var extraManifestFlags = self.options.build.manifestFlags - // Disable the implicit concurrency import if the compiler in use supports it to avoid warnings if we are building against an older SDK that does not contain a Concurrency module. - if DriverSupport.checkSupportedFrontendFlags( - flags: ["disable-implicit-concurrency-module-import"], - toolchain: try self.toolsBuildParameters.toolchain, - fileSystem: self.fileSystem - ) { - extraManifestFlags += ["-Xfrontend", "-disable-implicit-concurrency-module-import"] - } - // Disable the implicit string processing import if the compiler in use supports it to avoid warnings if we are building against an older SDK that does not contain a StringProcessing module. - if DriverSupport.checkSupportedFrontendFlags( - flags: ["disable-implicit-string-processing-module-import"], - toolchain: try self.toolsBuildParameters.toolchain, - fileSystem: self.fileSystem - ) { - extraManifestFlags += ["-Xfrontend", "-disable-implicit-string-processing-module-import"] - } - if self.logLevel <= .info { extraManifestFlags.append("-v") } @@ -877,7 +897,7 @@ public final class SwiftTool { }() /// An enum indicating the execution status of run commands. - public enum ExecutionStatus { + package enum ExecutionStatus { case success case failure } @@ -901,6 +921,9 @@ public final class SwiftTool { } fileprivate func acquireLockIfNeeded() throws { + guard packageRoot != nil else { + return + } assert(workspaceLockState == .needsLocking, "attempting to `acquireLockIfNeeded()` from unexpected state: \(workspaceLockState)") guard workspaceLock == nil else { throw InternalError("acquireLockIfNeeded() called multiple times") @@ -914,11 +937,16 @@ public final class SwiftTool { try workspaceLock.lock(type: .exclusive, blocking: false) } catch let ProcessLockError.unableToAquireLock(errno) { if errno == EWOULDBLOCK { - self.outputStream.write("Another instance of SwiftPM is already running using '\(self.scratchDirectory)', waiting until that process has finished execution...".utf8) - self.outputStream.flush() - - // Only if we fail because there's an existing lock we need to acquire again as blocking. - try workspaceLock.lock(type: .exclusive, blocking: true) + if self.options.locations.ignoreLock { + self.outputStream.write("Another instance of SwiftPM is already running using '\(self.scratchDirectory)', but this will be ignored since `--ignore-lock` has been passed".utf8) + self.outputStream.flush() + } else { + self.outputStream.write("Another instance of SwiftPM is already running using '\(self.scratchDirectory)', waiting until that process has finished execution...".utf8) + self.outputStream.flush() + + // Only if we fail because there's an existing lock we need to acquire again as blocking. + try workspaceLock.lock(type: .exclusive, blocking: true) + } } } @@ -998,16 +1026,16 @@ extension Basics.Diagnostic { // MARK: - Support for loading external workspaces -public protocol WorkspaceLoader { +package protocol WorkspaceLoader { func load(workspace: AbsolutePath) throws -> [AbsolutePath] } // MARK: - Diagnostics -extension SwiftTool { +extension SwiftCommandState { // FIXME: deprecate these one we are further along refactoring the call sites that use it /// The stream to print standard output on. - public var outputStream: OutputByteStream { + package var outputStream: OutputByteStream { self.observabilityHandler.outputStream } } @@ -1097,7 +1125,7 @@ extension BuildOptions.DebugInfoFormat { } extension Basics.Diagnostic { - public static func mutuallyExclusiveArgumentsError(arguments: [String]) -> Self { + package static func mutuallyExclusiveArgumentsError(arguments: [String]) -> Self { .error(arguments.map { "'\($0)'" }.spm_localizedJoin(type: .conjunction) + " are mutually exclusive") } } diff --git a/Sources/DriverSupport/DriverSupportUtils.swift b/Sources/DriverSupport/DriverSupportUtils.swift index 9e5bf00a739..1586dad73b1 100644 --- a/Sources/DriverSupport/DriverSupportUtils.swift +++ b/Sources/DriverSupport/DriverSupportUtils.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -20,7 +20,7 @@ import struct TSCBasic.ProcessResult public enum DriverSupport { private static var flagsMap = ThreadSafeBox<[String: Set]>() - // This checks _frontend_ supported flags, which are not necessarily supported in the driver. + /// This checks _frontend_ supported flags, which are not necessarily supported in the driver. public static func checkSupportedFrontendFlags( flags: Set, toolchain: PackageModel.Toolchain, @@ -54,7 +54,7 @@ public enum DriverSupport { // This checks if given flags are supported in the built-in toolchain driver. Currently // there's no good way to get the supported flags from it, so run `swiftc -h` directly // to get the flags and cache the result. - public static func checkToolchainDriverFlags( + static func checkToolchainDriverFlags( flags: Set, toolchain: PackageModel.Toolchain, fileSystem: FileSystem @@ -83,4 +83,8 @@ public enum DriverSupport { return false } } + + package static func isPackageNameSupported(toolchain: PackageModel.Toolchain, fileSystem: FileSystem) -> Bool { + DriverSupport.checkToolchainDriverFlags(flags: ["-package-name"], toolchain: toolchain, fileSystem: fileSystem) + } } diff --git a/Sources/LLBuildManifest/Command.swift b/Sources/LLBuildManifest/Command.swift index e91928ac102..b37e969e682 100644 --- a/Sources/LLBuildManifest/Command.swift +++ b/Sources/LLBuildManifest/Command.swift @@ -10,12 +10,12 @@ // //===----------------------------------------------------------------------===// -public struct Command { +package struct Command { /// The name of the command. - public var name: String + package var name: String /// The tool used for this command. - public var tool: ToolProtocol + package var tool: ToolProtocol init(name: String, tool: ToolProtocol) { self.name = name diff --git a/Sources/LLBuildManifest/LLBuildManifest.swift b/Sources/LLBuildManifest/LLBuildManifest.swift index 1c67c8c8209..6800ba87c40 100644 --- a/Sources/LLBuildManifest/LLBuildManifest.swift +++ b/Sources/LLBuildManifest/LLBuildManifest.swift @@ -15,14 +15,14 @@ import Foundation import class TSCBasic.Process -public protocol AuxiliaryFileType { +package protocol AuxiliaryFileType { static var name: String { get } static func getFileContents(inputs: [Node]) throws -> String } -public enum WriteAuxiliary { - public static let fileTypes: [AuxiliaryFileType.Type] = [ +package enum WriteAuxiliary { + package static let fileTypes: [AuxiliaryFileType.Type] = [ EntitlementPlist.self, LinkFileList.self, SourcesFileList.self, @@ -30,14 +30,14 @@ public enum WriteAuxiliary { XCTestInfoPlist.self ] - public struct EntitlementPlist: AuxiliaryFileType { - public static let name = "entitlement-plist" + package struct EntitlementPlist: AuxiliaryFileType { + package static let name = "entitlement-plist" - public static func computeInputs(entitlement: String) -> [Node] { + package static func computeInputs(entitlement: String) -> [Node] { [.virtual(Self.name), .virtual(entitlement)] } - public static func getFileContents(inputs: [Node]) throws -> String { + package static func getFileContents(inputs: [Node]) throws -> String { guard let entitlementName = inputs.last?.extractedVirtualNodeName else { throw Error.undefinedEntitlementName } @@ -54,15 +54,15 @@ public enum WriteAuxiliary { } } - public struct LinkFileList: AuxiliaryFileType { - public static let name = "link-file-list" + package struct LinkFileList: AuxiliaryFileType { + package static let name = "link-file-list" // FIXME: We should extend the `InProcessTool` support to allow us to specify these in a typed way, but today we have to flatten all the inputs into a generic `Node` array (rdar://109844243). - public static func computeInputs(objects: [AbsolutePath]) -> [Node] { + package static func computeInputs(objects: [AbsolutePath]) -> [Node] { return [.virtual(Self.name)] + objects.map { Node.file($0) } } - public static func getFileContents(inputs: [Node]) throws -> String { + package static func getFileContents(inputs: [Node]) throws -> String { let objects = inputs.compactMap { if $0.kind == .file { return $0.name @@ -84,14 +84,14 @@ public enum WriteAuxiliary { } } - public struct SourcesFileList: AuxiliaryFileType { - public static let name = "sources-file-list" + package struct SourcesFileList: AuxiliaryFileType { + package static let name = "sources-file-list" - public static func computeInputs(sources: [AbsolutePath]) -> [Node] { + package static func computeInputs(sources: [AbsolutePath]) -> [Node] { return [.virtual(Self.name)] + sources.map { Node.file($0) } } - public static func getFileContents(inputs: [Node]) throws -> String { + package static func getFileContents(inputs: [Node]) throws -> String { let sources = inputs.compactMap { if $0.kind == .file { return $0.name @@ -110,14 +110,14 @@ public enum WriteAuxiliary { } } - public struct SwiftGetVersion: AuxiliaryFileType { - public static let name = "swift-get-version" + package struct SwiftGetVersion: AuxiliaryFileType { + package static let name = "swift-get-version" - public static func computeInputs(swiftCompilerPath: AbsolutePath) -> [Node] { + package static func computeInputs(swiftCompilerPath: AbsolutePath) -> [Node] { return [.virtual(Self.name), .file(swiftCompilerPath)] } - public static func getFileContents(inputs: [Node]) throws -> String { + package static func getFileContents(inputs: [Node]) throws -> String { guard let swiftCompilerPathString = inputs.first(where: { $0.kind == .file })?.name else { throw Error.unknownSwiftCompilerPath } @@ -130,14 +130,14 @@ public enum WriteAuxiliary { } } - public struct XCTestInfoPlist: AuxiliaryFileType { - public static let name = "xctest-info-plist" + package struct XCTestInfoPlist: AuxiliaryFileType { + package static let name = "xctest-info-plist" - public static func computeInputs(principalClass: String) -> [Node] { + package static func computeInputs(principalClass: String) -> [Node] { return [.virtual(Self.name), .virtual(principalClass)] } - public static func getFileContents(inputs: [Node]) throws -> String { + package static func getFileContents(inputs: [Node]) throws -> String { guard let principalClass = inputs.last?.extractedVirtualNodeName else { throw Error.undefinedPrincipalClass } @@ -161,23 +161,23 @@ public enum WriteAuxiliary { } } -public struct LLBuildManifest { - public typealias TargetName = String - public typealias CmdName = String +package struct LLBuildManifest { + package typealias TargetName = String + package typealias CmdName = String /// The targets in the manifest. - public private(set) var targets: [TargetName: Target] = [:] + package private(set) var targets: [TargetName: Target] = [:] /// The commands in the manifest. - public private(set) var commands: [CmdName: Command] = [:] + package private(set) var commands: [CmdName: Command] = [:] /// The default target to build. - public var defaultTarget: String = "" + package var defaultTarget: String = "" - public init() { + package init() { } - public func getCmdToolMap(kind: T.Type) -> [CmdName: T] { + package func getCmdToolMap(kind: T.Type) -> [CmdName: T] { var result = [CmdName: T]() for (cmdName, cmd) in commands { if let tool = cmd.tool as? T { @@ -187,16 +187,16 @@ public struct LLBuildManifest { return result } - public mutating func createTarget(_ name: TargetName) { + package mutating func createTarget(_ name: TargetName) { guard !targets.keys.contains(name) else { return } targets[name] = Target(name: name, nodes: []) } - public mutating func addNode(_ node: Node, toTarget target: TargetName) { + package mutating func addNode(_ node: Node, toTarget target: TargetName) { targets[target, default: Target(name: target, nodes: [])].nodes.append(node) } - public mutating func addPhonyCmd( + package mutating func addPhonyCmd( name: String, inputs: [Node], outputs: [Node] @@ -206,7 +206,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addTestDiscoveryCmd( + package mutating func addTestDiscoveryCmd( name: String, inputs: [Node], outputs: [Node] @@ -216,7 +216,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addTestEntryPointCmd( + package mutating func addTestEntryPointCmd( name: String, inputs: [Node], outputs: [Node] @@ -226,7 +226,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addCopyCmd( + package mutating func addCopyCmd( name: String, inputs: [Node], outputs: [Node] @@ -236,14 +236,14 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addEntitlementPlistCommand(entitlement: String, outputPath: AbsolutePath) { + package mutating func addEntitlementPlistCommand(entitlement: String, outputPath: AbsolutePath) { let inputs = WriteAuxiliary.EntitlementPlist.computeInputs(entitlement: entitlement) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) let name = outputPath.pathString commands[name] = Command(name: name, tool: tool) } - public mutating func addWriteLinkFileListCommand( + package mutating func addWriteLinkFileListCommand( objects: [AbsolutePath], linkFileListPath: AbsolutePath ) { @@ -253,7 +253,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addWriteSourcesFileListCommand( + package mutating func addWriteSourcesFileListCommand( sources: [AbsolutePath], sourcesFileListPath: AbsolutePath ) { @@ -263,7 +263,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addSwiftGetVersionCommand( + package mutating func addSwiftGetVersionCommand( swiftCompilerPath: AbsolutePath, swiftVersionFilePath: AbsolutePath ) { @@ -273,14 +273,14 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addWriteInfoPlistCommand(principalClass: String, outputPath: AbsolutePath) { + package mutating func addWriteInfoPlistCommand(principalClass: String, outputPath: AbsolutePath) { let inputs = WriteAuxiliary.XCTestInfoPlist.computeInputs(principalClass: principalClass) let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) let name = outputPath.pathString commands[name] = Command(name: name, tool: tool) } - public mutating func addPkgStructureCmd( + package mutating func addPkgStructureCmd( name: String, inputs: [Node], outputs: [Node] @@ -290,7 +290,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addShellCmd( + package mutating func addShellCmd( name: String, description: String, inputs: [Node], @@ -313,7 +313,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addSwiftFrontendCmd( + package mutating func addSwiftFrontendCmd( name: String, moduleName: String, packageName: String, @@ -333,7 +333,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addClangCmd( + package mutating func addClangCmd( name: String, description: String, inputs: [Node], @@ -352,7 +352,7 @@ public struct LLBuildManifest { commands[name] = Command(name: name, tool: tool) } - public mutating func addSwiftCmd( + package mutating func addSwiftCmd( name: String, inputs: [Node], outputs: [Node], diff --git a/Sources/LLBuildManifest/LLBuildManifestWriter.swift b/Sources/LLBuildManifest/LLBuildManifestWriter.swift index b85a65c5098..2177778a2b9 100644 --- a/Sources/LLBuildManifest/LLBuildManifestWriter.swift +++ b/Sources/LLBuildManifest/LLBuildManifestWriter.swift @@ -14,7 +14,7 @@ import Basics private let namesToExclude = [".git", ".build"] -public struct LLBuildManifestWriter { +package struct LLBuildManifestWriter { private let manifest: LLBuildManifest // FIXME: since JSON is a superset of YAML and we don't need to parse these manifests, // we should just use `JSONEncoder` instead. @@ -38,7 +38,7 @@ public struct LLBuildManifestWriter { self.render(commands: manifest.commands) } - public static func write(_ manifest: LLBuildManifest, at path: AbsolutePath, fileSystem: FileSystem) throws { + package static func write(_ manifest: LLBuildManifest, at path: AbsolutePath, fileSystem: FileSystem) throws { let writer = LLBuildManifestWriter(manifest: manifest) try fileSystem.writeFileContents(path, string: writer.buffer) @@ -125,66 +125,66 @@ public struct LLBuildManifestWriter { } } -public struct ManifestToolStream { +package struct ManifestToolStream { fileprivate var buffer = "" - public subscript(key: String) -> Int { + package subscript(key: String) -> Int { get { fatalError() } set { self.buffer += " \(key): \(newValue.description.asJSON)\n" } } - public subscript(key: String) -> String { + package subscript(key: String) -> String { get { fatalError() } set { self.buffer += " \(key): \(newValue.asJSON)\n" } } - public subscript(key: String) -> ToolProtocol { + package subscript(key: String) -> ToolProtocol { get { fatalError() } set { self.buffer += " \(key): \(type(of: newValue).name)\n" } } - public subscript(key: String) -> AbsolutePath { + package subscript(key: String) -> AbsolutePath { get { fatalError() } set { self.buffer += " \(key): \(newValue.pathString.asJSON)\n" } } - public subscript(key: String) -> [AbsolutePath] { + package subscript(key: String) -> [AbsolutePath] { get { fatalError() } set { self.buffer += " \(key): \(newValue.map(\.pathString).asJSON)\n" } } - public subscript(key: String) -> [Node] { + package subscript(key: String) -> [Node] { get { fatalError() } set { self.buffer += " \(key): \(newValue.map(\.encodingName).asJSON)\n" } } - public subscript(key: String) -> Bool { + package subscript(key: String) -> Bool { get { fatalError() } set { self.buffer += " \(key): \(newValue.description)\n" } } - public subscript(key: String) -> [String] { + package subscript(key: String) -> [String] { get { fatalError() } set { self.buffer += " \(key): \(newValue.asJSON)\n" } } - public subscript(key: String) -> [String: String] { + package subscript(key: String) -> [String: String] { get { fatalError() } set { self.buffer += " \(key):\n" diff --git a/Sources/LLBuildManifest/Node.swift b/Sources/LLBuildManifest/Node.swift index 9dd898b6682..6ee8aabefcf 100644 --- a/Sources/LLBuildManifest/Node.swift +++ b/Sources/LLBuildManifest/Node.swift @@ -12,8 +12,8 @@ import Basics -public struct Node: Hashable, Codable { - public enum Kind: String, Hashable, Codable { +package struct Node: Hashable, Codable { + package enum Kind: String, Hashable, Codable { case virtual case file case directory @@ -26,10 +26,10 @@ public struct Node: Hashable, Codable { } /// The name used to identify the node. - public var name: String + package var name: String /// The kind of node. - public var kind: Kind + package var kind: Kind let attributes: Attributes? @@ -40,12 +40,12 @@ public struct Node: Hashable, Codable { } /// Extracts `name` property if this node was constructed as `Node//virtual`. - public var extractedVirtualNodeName: String { + package var extractedVirtualNodeName: String { precondition(kind == .virtual) return String(self.name.dropFirst().dropLast()) } - public static func virtual(_ name: String, isCommandTimestamp: Bool = false) -> Node { + package static func virtual(_ name: String, isCommandTimestamp: Bool = false) -> Node { precondition(name.first != "<" && name.last != ">", "<> will be inserted automatically") return Node( name: "<" + name + ">", @@ -54,11 +54,11 @@ public struct Node: Hashable, Codable { ) } - public static func file(_ name: AbsolutePath) -> Node { + package static func file(_ name: AbsolutePath) -> Node { Node(name: name.pathString, kind: .file) } - public static func file(_ name: AbsolutePath, isMutated: Bool) -> Node { + package static func file(_ name: AbsolutePath, isMutated: Bool) -> Node { Node( name: name.pathString, kind: .file, @@ -66,25 +66,25 @@ public struct Node: Hashable, Codable { ) } - public static func directory(_ name: AbsolutePath) -> Node { + package static func directory(_ name: AbsolutePath) -> Node { Node(name: name.pathString, kind: .directory) } - public static func directoryStructure(_ name: AbsolutePath) -> Node { + package static func directoryStructure(_ name: AbsolutePath) -> Node { Node(name: name.pathString, kind: .directoryStructure) } } extension Array where Element == Node { - public mutating func append(file path: AbsolutePath) { + package mutating func append(file path: AbsolutePath) { self.append(.file(path)) } - public mutating func append(directory path: AbsolutePath) { + package mutating func append(directory path: AbsolutePath) { self.append(.directory(path)) } - public mutating func append(directoryStructure path: AbsolutePath) { + package mutating func append(directoryStructure path: AbsolutePath) { self.append(.directoryStructure(path)) } } diff --git a/Sources/LLBuildManifest/Target.swift b/Sources/LLBuildManifest/Target.swift index 292dd520b39..263c54150e7 100644 --- a/Sources/LLBuildManifest/Target.swift +++ b/Sources/LLBuildManifest/Target.swift @@ -10,14 +10,14 @@ // //===----------------------------------------------------------------------===// -public struct Target { +package struct Target { /// The name of the target. - public var name: String + package var name: String /// The list of nodes that should be computed to build this target. - public var nodes: [Node] + package var nodes: [Node] - public init(name: String, nodes: [Node]) { + package init(name: String, nodes: [Node]) { self.name = name self.nodes = nodes } diff --git a/Sources/LLBuildManifest/Tools.swift b/Sources/LLBuildManifest/Tools.swift index 7a6172fe2fb..9fabe80aee6 100644 --- a/Sources/LLBuildManifest/Tools.swift +++ b/Sources/LLBuildManifest/Tools.swift @@ -13,7 +13,7 @@ import Basics import class Foundation.ProcessInfo -public protocol ToolProtocol: Codable { +package protocol ToolProtocol: Codable { /// The name of the tool. static var name: String { get } @@ -31,16 +31,16 @@ public protocol ToolProtocol: Codable { } extension ToolProtocol { - public var alwaysOutOfDate: Bool { return false } + package var alwaysOutOfDate: Bool { return false } - public func write(to stream: inout ManifestToolStream) {} + package func write(to stream: inout ManifestToolStream) {} } -public struct PhonyTool: ToolProtocol { - public static let name: String = "phony" +package struct PhonyTool: ToolProtocol { + package static let name: String = "phony" - public var inputs: [Node] - public var outputs: [Node] + package var inputs: [Node] + package var outputs: [Node] init(inputs: [Node], outputs: [Node]) { self.inputs = inputs @@ -48,12 +48,12 @@ public struct PhonyTool: ToolProtocol { } } -public struct TestDiscoveryTool: ToolProtocol { - public static let name: String = "test-discovery-tool" - public static let mainFileName: String = "all-discovered-tests.swift" +package struct TestDiscoveryTool: ToolProtocol { + package static let name: String = "test-discovery-tool" + package static let mainFileName: String = "all-discovered-tests.swift" - public var inputs: [Node] - public var outputs: [Node] + package var inputs: [Node] + package var outputs: [Node] init(inputs: [Node], outputs: [Node]) { self.inputs = inputs @@ -61,11 +61,11 @@ public struct TestDiscoveryTool: ToolProtocol { } } -public struct TestEntryPointTool: ToolProtocol { - public static let name: String = "test-entry-point-tool" +package struct TestEntryPointTool: ToolProtocol { + package static let name: String = "test-entry-point-tool" - public var inputs: [Node] - public var outputs: [Node] + package var inputs: [Node] + package var outputs: [Node] init(inputs: [Node], outputs: [Node]) { self.inputs = inputs @@ -73,18 +73,18 @@ public struct TestEntryPointTool: ToolProtocol { } } -public struct CopyTool: ToolProtocol { - public static let name: String = "copy-tool" +package struct CopyTool: ToolProtocol { + package static let name: String = "copy-tool" - public var inputs: [Node] - public var outputs: [Node] + package var inputs: [Node] + package var outputs: [Node] init(inputs: [Node], outputs: [Node]) { self.inputs = inputs self.outputs = outputs } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { stream["description"] = "Copying \(inputs[0].name)" } } @@ -93,33 +93,33 @@ public struct CopyTool: ToolProtocol { /// that requires regenerating the build manifest file. This allows us to skip a lot of /// redundant work (package graph loading, build planning, manifest generation) during /// incremental builds. -public struct PackageStructureTool: ToolProtocol { - public static let name: String = "package-structure-tool" +package struct PackageStructureTool: ToolProtocol { + package static let name: String = "package-structure-tool" - public var inputs: [Node] - public var outputs: [Node] + package var inputs: [Node] + package var outputs: [Node] init(inputs: [Node], outputs: [Node]) { self.inputs = inputs self.outputs = outputs } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { stream["description"] = "Planning build" stream["allow-missing-inputs"] = true } } -public struct ShellTool: ToolProtocol { - public static let name: String = "shell" +package struct ShellTool: ToolProtocol { + package static let name: String = "shell" - public var description: String - public var inputs: [Node] - public var outputs: [Node] - public var arguments: [String] - public var environment: EnvironmentVariables - public var workingDirectory: String? - public var allowMissingInputs: Bool + package var description: String + package var inputs: [Node] + package var outputs: [Node] + package var arguments: [String] + package var environment: EnvironmentVariables + package var workingDirectory: String? + package var allowMissingInputs: Bool init( description: String, @@ -139,7 +139,7 @@ public struct ShellTool: ToolProtocol { self.allowMissingInputs = allowMissingInputs } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { stream["description"] = description stream["args"] = arguments if !environment.isEmpty { @@ -154,36 +154,36 @@ public struct ShellTool: ToolProtocol { } } -public struct WriteAuxiliaryFile: Equatable, ToolProtocol { - public static let name: String = "write-auxiliary-file" +package struct WriteAuxiliaryFile: Equatable, ToolProtocol { + package static let name: String = "write-auxiliary-file" - public let inputs: [Node] + package let inputs: [Node] private let outputFilePath: AbsolutePath - public let alwaysOutOfDate: Bool + package let alwaysOutOfDate: Bool - public init(inputs: [Node], outputFilePath: AbsolutePath, alwaysOutOfDate: Bool = false) { + package init(inputs: [Node], outputFilePath: AbsolutePath, alwaysOutOfDate: Bool = false) { self.inputs = inputs self.outputFilePath = outputFilePath self.alwaysOutOfDate = alwaysOutOfDate } - public var outputs: [Node] { + package var outputs: [Node] { return [.file(outputFilePath)] } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { stream["description"] = "Write auxiliary file \(outputFilePath.pathString)" } } -public struct ClangTool: ToolProtocol { - public static let name: String = "clang" +package struct ClangTool: ToolProtocol { + package static let name: String = "clang" - public var description: String - public var inputs: [Node] - public var outputs: [Node] - public var arguments: [String] - public var dependencies: String? + package var description: String + package var inputs: [Node] + package var outputs: [Node] + package var arguments: [String] + package var dependencies: String? init( description: String, @@ -199,7 +199,7 @@ public struct ClangTool: ToolProtocol { self.dependencies = dependencies } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { stream["description"] = description stream["args"] = arguments if let dependencies { @@ -208,11 +208,11 @@ public struct ClangTool: ToolProtocol { } } -public struct ArchiveTool: ToolProtocol { - public static let name: String = "archive" +package struct ArchiveTool: ToolProtocol { + package static let name: String = "archive" - public var inputs: [Node] - public var outputs: [Node] + package var inputs: [Node] + package var outputs: [Node] init(inputs: [Node], outputs: [Node]) { self.inputs = inputs @@ -221,14 +221,14 @@ public struct ArchiveTool: ToolProtocol { } /// Swift frontend tool, which maps down to a shell tool. -public struct SwiftFrontendTool: ToolProtocol { - public static let name: String = "shell" +package struct SwiftFrontendTool: ToolProtocol { + package static let name: String = "shell" - public let moduleName: String - public var description: String - public var inputs: [Node] - public var outputs: [Node] - public var arguments: [String] + package let moduleName: String + package var description: String + package var inputs: [Node] + package var outputs: [Node] + package var arguments: [String] init( moduleName: String, @@ -244,33 +244,33 @@ public struct SwiftFrontendTool: ToolProtocol { self.arguments = arguments } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { ShellTool(description: description, inputs: inputs, outputs: outputs, arguments: arguments).write(to: &stream) } } /// Swift compiler llbuild tool. -public struct SwiftCompilerTool: ToolProtocol { - public static let name: String = "shell" - - public static let numThreads: Int = ProcessInfo.processInfo.activeProcessorCount - - public var inputs: [Node] - public var outputs: [Node] - - public var executable: AbsolutePath - public var moduleName: String - public var moduleAliases: [String: String]? - public var moduleOutputPath: AbsolutePath - public var importPath: AbsolutePath - public var tempsPath: AbsolutePath - public var objects: [AbsolutePath] - public var otherArguments: [String] - public var sources: [AbsolutePath] - public var fileList: AbsolutePath - public var isLibrary: Bool - public var wholeModuleOptimization: Bool - public var outputFileMapPath: AbsolutePath +package struct SwiftCompilerTool: ToolProtocol { + package static let name: String = "shell" + + package static let numThreads: Int = ProcessInfo.processInfo.activeProcessorCount + + package var inputs: [Node] + package var outputs: [Node] + + package var executable: AbsolutePath + package var moduleName: String + package var moduleAliases: [String: String]? + package var moduleOutputPath: AbsolutePath + package var importPath: AbsolutePath + package var tempsPath: AbsolutePath + package var objects: [AbsolutePath] + package var otherArguments: [String] + package var sources: [AbsolutePath] + package var fileList: AbsolutePath + package var isLibrary: Bool + package var wholeModuleOptimization: Bool + package var outputFileMapPath: AbsolutePath init( inputs: [Node], @@ -340,7 +340,7 @@ public struct SwiftCompilerTool: ToolProtocol { return arguments } - public func write(to stream: inout ManifestToolStream) { + package func write(to stream: inout ManifestToolStream) { ShellTool(description: description, inputs: inputs, outputs: outputs, arguments: arguments).write(to: &stream) } } diff --git a/Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift b/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift similarity index 91% rename from Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift rename to Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift index a6fa8e95c45..4eb6bb1a644 100644 --- a/Sources/PackageCollectionsTool/SwiftPackageCollectionsTool.swift +++ b/Sources/PackageCollectionsCommand/PackageCollectionsCommand.swift @@ -12,8 +12,11 @@ import ArgumentParser import Basics + import Commands + import CoreCommands + import Foundation import PackageCollections import PackageModel @@ -54,8 +57,8 @@ struct JSONOptions: ParsableArguments { var json: Bool = false } -public struct SwiftPackageCollectionsTool: AsyncParsableCommand { - public static var configuration = CommandConfiguration( +package struct PackageCollectionsCommand: AsyncParsableCommand { + package static var configuration = CommandConfiguration( commandName: "package-collection", _superCommandName: "swift", abstract: "Interact with package collections", @@ -72,7 +75,7 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) - public init() {} + package init() {} // MARK: Collections @@ -85,8 +88,8 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) async throws { - let collections = try await with(swiftTool) { collections in + func run(_ swiftCommandState: SwiftCommandState) async throws { + let collections = try await withState(swiftCommandState) { collections in try await collections.listCollections(identifiers: nil) } @@ -106,8 +109,8 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) async throws { - let collections = try await with(swiftTool) { collections in + func run(_ swiftCommandState: SwiftCommandState) async throws { + let collections = try await withState(swiftCommandState) { collections in try await collections.refreshCollections() } print("Refreshed \(collections.count) configured package collection\(collections.count == 1 ? "" : "s").") @@ -132,11 +135,11 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { let collectionURL = try url(self.collectionURL) let source = PackageCollectionsModel.CollectionSource(type: .json, url: collectionURL, skipSignatureCheck: self.skipSignatureCheck) - let collection: PackageCollectionsModel.Collection = try await with(swiftTool) { collections in + let collection: PackageCollectionsModel.Collection = try await withState(swiftCommandState) { collections in do { let userTrusted = self.trustUnsigned return try await collections.addCollection(source, order: order) { _, callback in @@ -166,11 +169,11 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { let collectionURL = try url(self.collectionURL) let source = PackageCollectionsModel.CollectionSource(type: .json, url: collectionURL) - try await with(swiftTool) { collections in + try await withState(swiftCommandState) { collections in let collection = try await collections.getCollection(source) _ = try await collections.removeCollection(source) print("Removed \"\(collection.name)\" from your package collections.") @@ -200,8 +203,8 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { @OptionGroup(visibility: .hidden) var globalOptions: GlobalOptions - func run(_ swiftTool: SwiftTool) async throws { - try await with(swiftTool) { collections in + func run(_ swiftCommandState: SwiftCommandState) async throws { + try await withState(swiftCommandState) { collections in switch searchMethod { case .keywords: let results = try await collections.findPackages(searchQuery, collections: nil) @@ -282,8 +285,8 @@ public struct SwiftPackageCollectionsTool: AsyncParsableCommand { """ } - func run(_ swiftTool: SwiftTool) async throws { - try await with(swiftTool) { collections in + func run(_ swiftCommandState: SwiftCommandState) async throws { + try await withState(swiftCommandState) { collections in let identity = PackageIdentity(urlString: self.packageURL) do { // assume URL is for a package in an imported collection @@ -381,15 +384,18 @@ private extension JSONEncoder { } private extension ParsableCommand { - func with(_ swiftTool: SwiftTool, handler: (_ collections: PackageCollectionsProtocol) async throws -> T) async throws -> T { - _ = try? swiftTool.getActiveWorkspace(emitDeprecatedConfigurationWarning: true) + func withState( + _ swiftCommandState: SwiftCommandState, + handler: (_ collections: PackageCollectionsProtocol) async throws -> T + ) async throws -> T { + _ = try? swiftCommandState.getActiveWorkspace(emitDeprecatedConfigurationWarning: true) let collections = PackageCollections( configuration: .init( - configurationDirectory: swiftTool.sharedConfigurationDirectory, - cacheDirectory: swiftTool.sharedCacheDirectory + configurationDirectory: swiftCommandState.sharedConfigurationDirectory, + cacheDirectory: swiftCommandState.sharedCacheDirectory ), - fileSystem: swiftTool.fileSystem, - observabilityScope: swiftTool.observabilityScope + fileSystem: swiftCommandState.fileSystem, + observabilityScope: swiftCommandState.observabilityScope ) defer { do { diff --git a/Sources/PackageDescription/BuildSettings.swift b/Sources/PackageDescription/BuildSettings.swift index d28b298ec64..e6e3b23ff6e 100644 --- a/Sources/PackageDescription/BuildSettings.swift +++ b/Sources/PackageDescription/BuildSettings.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// The build configuration, such as debug or release. -public struct BuildConfiguration { +public struct BuildConfiguration: Sendable { /// The configuration of the build. Valid values are `debug` and `release`. let config: String @@ -54,7 +54,7 @@ public struct BuildConfiguration { /// ] /// ), /// ``` -public struct BuildSettingCondition { +public struct BuildSettingCondition: Sendable { /// The applicable platforms for this build setting condition. let platforms: [Platform]? /// The applicable build configuration for this build setting condition. @@ -115,7 +115,7 @@ struct BuildSettingData { } /// A C language build setting. -public struct CSetting { +public struct CSetting: Sendable { /// The abstract build setting data. let data: BuildSettingData @@ -185,7 +185,7 @@ public struct CSetting { } /// A CXX-language build setting. -public struct CXXSetting { +public struct CXXSetting: Sendable { /// The data store for the CXX build setting. let data: BuildSettingData @@ -255,7 +255,7 @@ public struct CXXSetting { } /// A Swift language build setting. -public struct SwiftSetting { +public struct SwiftSetting: Sendable { /// The data store for the Swift build setting. let data: BuildSettingData @@ -387,10 +387,27 @@ public struct SwiftSetting { return SwiftSetting( name: "interoperabilityMode", value: [mode.rawValue], condition: condition) } + + /// Defines a `-swift-version` to pass to the + /// corresponding build tool. + /// + /// - Since: First available in PackageDescription 6.0. + /// + /// - Parameters: + /// - version: The Swift language version to use. + /// - condition: A condition that restricts the application of the build setting. + @available(_PackageDescription, introduced: 6.0) + public static func swiftLanguageVersion( + _ version: SwiftVersion, + _ condition: BuildSettingCondition? = nil + ) -> SwiftSetting { + return SwiftSetting( + name: "swiftLanguageVersion", value: [.init(describing: version)], condition: condition) + } } /// A linker build setting. -public struct LinkerSetting { +public struct LinkerSetting: Sendable { /// The data store for the Linker setting. let data: BuildSettingData diff --git a/Sources/PackageDescription/Context.swift b/Sources/PackageDescription/Context.swift index a931d880628..604e272e534 100644 --- a/Sources/PackageDescription/Context.swift +++ b/Sources/PackageDescription/Context.swift @@ -15,7 +15,7 @@ /// The context encapsulates states that are known when Swift Package Manager interprets the package manifest, /// for example the location in the file system where the current package resides. @available(_PackageDescription, introduced: 5.6) -public struct Context { +public struct Context: Sendable { private static let model = try! ContextModel.decode() /// The directory that contains `Package.swift`. @@ -24,7 +24,7 @@ public struct Context { } /// Information about the git status of a given package, if available. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public static var gitInformation: GitInformation? { model.gitInformation.map { GitInformation( @@ -45,8 +45,8 @@ public struct Context { } /// Information about the git status of a given package, if available. -@available(_PackageDescription, introduced: 5.11) -public struct GitInformation { +@available(_PackageDescription, introduced: 6.0) +public struct GitInformation: Sendable { public let currentTag: String? public let currentCommit: String public let hasUncommittedChanges: Bool diff --git a/Sources/PackageDescription/LanguageStandardSettings.swift b/Sources/PackageDescription/LanguageStandardSettings.swift index 23bf688277a..c1346f67a87 100644 --- a/Sources/PackageDescription/LanguageStandardSettings.swift +++ b/Sources/PackageDescription/LanguageStandardSettings.swift @@ -167,8 +167,25 @@ public enum SwiftVersion { @available(_PackageDescription, introduced: 5) case v5 + /// The identifier for the Swift 6 language version. + @available(_PackageDescription, introduced: 6) + case v6 + /// A user-defined value for the Swift version. /// /// The value is passed as-is to the Swift compiler's `-swift-version` flag. case version(String) } + +extension SwiftVersion: CustomStringConvertible { + public var description: String { + switch self { + case .v3: "3" + case .v4: "4" + case .v4_2: "4.2" + case .v5: "5" + case .v6: "6" + case .version(let version): version + } + } +} diff --git a/Sources/PackageDescription/PackageDescriptionSerialization.swift b/Sources/PackageDescription/PackageDescriptionSerialization.swift index a16cc995b81..ecc3808bb21 100644 --- a/Sources/PackageDescription/PackageDescriptionSerialization.swift +++ b/Sources/PackageDescription/PackageDescriptionSerialization.swift @@ -99,6 +99,7 @@ enum Serialization { case v4 case v4_2 case v5 + case v6 case version(String) } @@ -157,6 +158,7 @@ enum Serialization { case target(name: String, condition: Condition?) case product(name: String, package: String?, moduleAliases: [String: String]?, condition: Condition?) + case innerProduct(name: String, condition: Condition?) case byName(name: String, condition: Condition?) } diff --git a/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift b/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift index 26459c345a9..5335c1ff799 100644 --- a/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift +++ b/Sources/PackageDescription/PackageDescriptionSerializationConversion.swift @@ -112,6 +112,7 @@ extension Serialization.SwiftVersion { case .v4: self = .v4 case .v4_2: self = .v4_2 case .v5: self = .v5 + case .v6: self = .v6 case .version(let version): self = .version(version) } } @@ -204,6 +205,11 @@ extension Serialization.TargetDependency { moduleAliases: moduleAliases, condition: condition.map { .init($0) } ) + case .innerProductItem(let name, let condition): + self = .innerProduct( + name: name, + condition: condition.map { .init($0) } + ) case .byNameItem(let name, let condition): self = .byName(name: name, condition: condition.map { .init($0) }) } diff --git a/Sources/PackageDescription/Product.swift b/Sources/PackageDescription/Product.swift index d022eb91c50..73a533c5203 100644 --- a/Sources/PackageDescription/Product.swift +++ b/Sources/PackageDescription/Product.swift @@ -68,8 +68,7 @@ public class Product { } /// The executable product of a Swift package. - public final class Executable: Product { - + public final class Executable: Product, @unchecked Sendable { /// The names of the targets in this product. public let targets: [String] @@ -80,7 +79,7 @@ public class Product { } /// The library product of a Swift package. - public final class Library: Product { + public final class Library: Product, @unchecked Sendable { /// The different types of a library product. public enum LibraryType: String { /// A statically linked library. @@ -106,7 +105,7 @@ public class Product { } /// The plug-in product of a Swift package. - public final class Plugin: Product { + public final class Plugin: Product, @unchecked Sendable { /// The name of the plug-in target to vend as a product. public let targets: [String] @@ -149,7 +148,7 @@ public class Product { /// - name: The name of the executable product. /// - targets: The targets to bundle into an executable product. /// - Returns: A `Product` instance. -public static func executable( + public static func executable( name: String, targets: [String] ) -> Product { @@ -167,7 +166,7 @@ public static func executable( /// - name: The name of the plugin product. /// - targets: The plugin targets to vend as a product. /// - Returns: A `Product` instance. -@available(_PackageDescription, introduced: 5.5) + @available(_PackageDescription, introduced: 5.5) public static func plugin( name: String, targets: [String] diff --git a/Sources/PackageDescription/Resource.swift b/Sources/PackageDescription/Resource.swift index f195a133d45..dfd9272793f 100644 --- a/Sources/PackageDescription/Resource.swift +++ b/Sources/PackageDescription/Resource.swift @@ -33,10 +33,10 @@ /// To learn more about package resources, see /// . @available(_PackageDescription, introduced: 5.3) -public struct Resource { +public struct Resource: Sendable { /// Defines the explicit type of localization for resources. - public enum Localization: String { + public enum Localization: String, Sendable { /// A constant that represents default localization. case `default` diff --git a/Sources/PackageDescription/SupportedPlatforms.swift b/Sources/PackageDescription/SupportedPlatforms.swift index be1ee1462a5..f105e507d07 100644 --- a/Sources/PackageDescription/SupportedPlatforms.swift +++ b/Sources/PackageDescription/SupportedPlatforms.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// A platform supported by Swift Package Manager. -public struct Platform: Equatable { +public struct Platform: Equatable, Sendable { /// The name of the platform. let name: String @@ -88,7 +88,7 @@ public struct Platform: Equatable { /// package's deployment version. The deployment target of a package's /// dependencies must be lower than or equal to the top-level package's /// deployment target version for a particular platform. -public struct SupportedPlatform: Equatable { +public struct SupportedPlatform: Equatable, Sendable { /// The platform. let platform: Platform @@ -698,7 +698,7 @@ extension SupportedPlatform { } } -fileprivate protocol AppleOSVersion { +fileprivate protocol AppleOSVersion: Sendable { static var name: String { get } static var minimumMajorVersion: Int { get } init(uncheckedVersion: String) diff --git a/Sources/PackageDescription/Target.swift b/Sources/PackageDescription/Target.swift index ce77847d291..efc5b8c3275 100644 --- a/Sources/PackageDescription/Target.swift +++ b/Sources/PackageDescription/Target.swift @@ -57,6 +57,13 @@ public final class Target { /// - moduleAlias: The module aliases for targets in the product. /// - condition: A condition that limits the application of the target dependency. For example, only apply a dependency for a specific platform. case productItem(name: String, package: String?, moduleAliases: [String: String]?, condition: TargetDependencyCondition?) + /// A dependency on a product in the current package. + /// + /// - Parameters: + /// - name: The name of the product. + /// - moduleAlias: The module aliases for targets in the product. + /// - condition: A condition that limits the application of the target dependency. For example, only apply a dependency for a specific platform. + case innerProductItem(name: String, condition: TargetDependencyCondition?) /// A by-name dependency on either a target or a product. /// /// - Parameters: @@ -1263,6 +1270,20 @@ extension Target.Dependency { return .productItem(name: name, package: package, moduleAliases: nil, condition: nil) } + /// Creates a dependency on a product from the same package. + /// + /// - Parameters: + /// - name: The name of the product. + /// - condition: A condition that limits the application of the target dependency. For example, only apply a + /// dependency for a specific platform. + /// - Returns: A `Target.Dependency` instance. + public static func product( + name: String, + condition: TargetDependencyCondition? = nil + ) -> Target.Dependency { + return .innerProductItem(name: name, condition: condition) + } + /// Creates a dependency that resolves to either a target or a product with the specified name. /// /// - Parameter name: The name of the dependency, either a target or a product. diff --git a/Sources/PackageDescription/Version.swift b/Sources/PackageDescription/Version.swift index 661c4f74b3e..4e952063432 100644 --- a/Sources/PackageDescription/Version.swift +++ b/Sources/PackageDescription/Version.swift @@ -35,7 +35,7 @@ /// Increase the third digit of a version, or _patch version_, if you're making /// a backward-compatible bug fix. This allows clients to benefit from bugfixes /// to your package without incurring any maintenance burden. -public struct Version { +public struct Version: Sendable { /// The major version according to the semantic versioning standard. public let major: Int diff --git a/Sources/PackageGraph/BoundVersion.swift b/Sources/PackageGraph/BoundVersion.swift index 7aa90a33ff6..459dee7f956 100644 --- a/Sources/PackageGraph/BoundVersion.swift +++ b/Sources/PackageGraph/BoundVersion.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import struct PackageModel.ProvidedLibrary import struct TSCUtility.Version /// A bound version for a package within an assignment. @@ -22,7 +23,7 @@ public enum BoundVersion: Equatable, Hashable { case excluded /// The version of the package to include. - case version(Version) + case version(Version, library: ProvidedLibrary? = nil) /// The package assignment is unversioned. case unversioned @@ -36,7 +37,7 @@ extension BoundVersion: CustomStringConvertible { switch self { case .excluded: return "excluded" - case .version(let version): + case .version(let version, _): return version.description case .unversioned: return "unversioned" diff --git a/Sources/PackageGraph/BuildTriple.swift b/Sources/PackageGraph/BuildTriple.swift index 4e121a2c7bb..87d2daf21f1 100644 --- a/Sources/PackageGraph/BuildTriple.swift +++ b/Sources/PackageGraph/BuildTriple.swift @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +import class PackageModel.Target +import class PackageModel.Product + /// Triple for which code should be compiled for. /// > Note: We're not using "host" and "target" triple terminology in this enum, as that clashes with build /// > system "targets" and can lead to confusion in this context. @@ -20,3 +23,23 @@ public enum BuildTriple { /// Triple of the destination platform for which end products are compiled (the target triple). case destination } + +extension Target { + var buildTriple: BuildTriple { + if self.type == .macro || self.type == .plugin { + .tools + } else { + .destination + } + } +} + +extension Product { + var buildTriple: BuildTriple { + if self.type == .macro || self.type == .plugin { + .tools + } else { + .destination + } + } +} diff --git a/Sources/PackageGraph/CMakeLists.txt b/Sources/PackageGraph/CMakeLists.txt index c1742e54c44..b95b1eb9feb 100644 --- a/Sources/PackageGraph/CMakeLists.txt +++ b/Sources/PackageGraph/CMakeLists.txt @@ -13,9 +13,9 @@ add_library(PackageGraph Diagnostics.swift GraphLoadingNode.swift ModuleAliasTracker.swift + ModulesGraph.swift + ModulesGraph+Loading.swift PackageContainer.swift - PackageGraph.swift - PackageGraph+Loading.swift PackageGraphRoot.swift PackageModel+Extensions.swift PackageRequirement.swift @@ -35,7 +35,7 @@ add_library(PackageGraph Resolution/PlatformVersionProvider.swift Resolution/ResolvedPackage.swift Resolution/ResolvedProduct.swift - Resolution/ResolvedTarget.swift + Resolution/ResolvedModule.swift Version+Extensions.swift VersionSetSpecifier.swift) target_link_libraries(PackageGraph PUBLIC diff --git a/Sources/PackageGraph/PackageGraph+Loading.swift b/Sources/PackageGraph/ModulesGraph+Loading.swift similarity index 86% rename from Sources/PackageGraph/PackageGraph+Loading.swift rename to Sources/PackageGraph/ModulesGraph+Loading.swift index 7627e0d5439..7b431ca4b29 100644 --- a/Sources/PackageGraph/PackageGraph+Loading.swift +++ b/Sources/PackageGraph/ModulesGraph+Loading.swift @@ -15,9 +15,11 @@ import OrderedCollections import PackageLoading import PackageModel +import struct TSCBasic.KeyedPair import func TSCBasic.bestMatch +import func TSCBasic.findCycle -extension PackageGraph { +extension ModulesGraph { /// Load the package graph for the given package path. public static func load( root: PackageGraphRoot, @@ -34,7 +36,7 @@ extension PackageGraph { testEntryPointPath: AbsolutePath? = nil, fileSystem: FileSystem, observabilityScope: ObservabilityScope - ) throws -> PackageGraph { + ) throws -> ModulesGraph { let observabilityScope = observabilityScope.makeChildScope(description: "Loading Package Graph") @@ -44,17 +46,6 @@ extension PackageGraph { root.manifests.forEach { manifestMap[$0.key] = ($0.value, fileSystem) } - func nodeSuccessorsProvider(node: GraphLoadingNode) -> [GraphLoadingNode] { - node.requiredDependencies.compactMap { dependency in - manifestMap[dependency.identity].map { (manifest, fileSystem) in - GraphLoadingNode( - identity: dependency.identity, - manifest: manifest, - productFilter: dependency.productFilter - ) - } - } - } // Construct the root root dependencies set. let rootDependencies = Set(root.dependencies.compactMap{ @@ -75,33 +66,27 @@ extension PackageGraph { let inputManifests = rootManifestNodes + rootDependencyNodes // Collect the manifests for which we are going to build packages. - var allNodes: [GraphLoadingNode] + var allNodes = [GraphLoadingNode]() - // Detect cycles in manifest dependencies. - if let cycle = findCycle(inputManifests, successors: nodeSuccessorsProvider) { - observabilityScope.emit(PackageGraphError.cycleDetected(cycle)) - // Break the cycle so we can build a partial package graph. - allNodes = inputManifests.filter({ $0.manifest != cycle.cycle[0] }) - } else { - // Sort all manifests topologically. - allNodes = try topologicalSort(inputManifests, successors: nodeSuccessorsProvider) - } - - var flattenedManifests: [PackageIdentity: GraphLoadingNode] = [:] - for node in allNodes { - if let existing = flattenedManifests[node.identity] { - let merged = GraphLoadingNode( - identity: node.identity, - manifest: node.manifest, - productFilter: existing.productFilter.union(node.productFilter) - ) - flattenedManifests[node.identity] = merged - } else { - flattenedManifests[node.identity] = node + // Cycles in dependencies don't matter as long as there are no target cycles between packages. + depthFirstSearch(inputManifests.map { KeyedPair($0, key: $0.id) }) { + $0.item.requiredDependencies.compactMap { dependency in + manifestMap[dependency.identity].map { (manifest, fileSystem) in + KeyedPair( + GraphLoadingNode( + identity: dependency.identity, + manifest: manifest, + productFilter: dependency.productFilter + ), + key: dependency.identity + ) + } } + } onUnique: { + allNodes.append($0.item) + } onDuplicate: { _,_ in + // no de-duplication is required. } - // sort by identity - allNodes = flattenedManifests.keys.sorted().map { flattenedManifests[$0]! } // force unwrap fine since we are iterating on keys // Create the packages. var manifestToPackage: [Manifest: Package] = [:] @@ -163,33 +148,43 @@ extension PackageGraph { observabilityScope: observabilityScope ) - let rootPackages = resolvedPackages.filter{ root.manifests.values.contains($0.manifest) } - checkAllDependenciesAreUsed(rootPackages, observabilityScope: observabilityScope) + let rootPackages = resolvedPackages.filter { root.manifests.values.contains($0.manifest) } + checkAllDependenciesAreUsed(packages: resolvedPackages, rootPackages, observabilityScope: observabilityScope) - return try PackageGraph( + return try ModulesGraph( rootPackages: rootPackages, - rootDependencies: resolvedPackages.filter{ rootDependencies.contains($0.manifest) }, + rootDependencies: resolvedPackages.filter { rootDependencies.contains($0.manifest) }, + packages: resolvedPackages, dependencies: requiredDependencies, binaryArtifacts: binaryArtifacts ) } } -private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], observabilityScope: ObservabilityScope) { +private func checkAllDependenciesAreUsed( + packages: IdentifiableSet, + _ rootPackages: [ResolvedPackage], + observabilityScope: ObservabilityScope +) { for package in rootPackages { // List all dependency products dependent on by the package targets. - let productDependencies = IdentifiableSet(package.targets.flatMap({ target in - return target.dependencies.compactMap({ targetDependency in + let productDependencies = IdentifiableSet(package.targets.flatMap { target in + return target.dependencies.compactMap { targetDependency in switch targetDependency { case .product(let product, _): return product case .target: return nil } - }) - })) + } + }) + + for dependencyId in package.dependencies { + guard let dependency = packages[dependencyId] else { + observabilityScope.emit(.error("Unknown package: \(dependencyId)")) + return + } - for dependency in package.dependencies { // We continue if the dependency contains executable products to make sure we don't // warn on a valid use-case for a lone dependency: swift run dependency executables. guard !dependency.products.contains(where: { $0.type == .executable }) else { @@ -215,7 +210,12 @@ private func checkAllDependenciesAreUsed(_ rootPackages: [ResolvedPackage], obse ) // Otherwise emit a warning if none of the dependency package's products are used. - let dependencyIsUsed = dependency.products.contains(where: { productDependencies.contains(id: $0.id) }) + let dependencyIsUsed = dependency.products.contains { product in + // Don't compare by product ID, but by product name to make sure both build triples as properties of + // `ResolvedProduct.ID` are allowed. + productDependencies.contains { $0.name == product.name } + } + if !dependencyIsUsed && !observabilityScope.errorsReportedInAnyScope { packageDiagnosticsScope.emit(.unusedDependency(dependency.identity.description)) } @@ -245,7 +245,7 @@ private func createResolvedPackages( platformVersionProvider: PlatformVersionProvider, fileSystem: FileSystem, observabilityScope: ObservabilityScope -) throws -> [ResolvedPackage] { +) throws -> IdentifiableSet { // Create package builder objects from the input manifests. let packageBuilders: [ResolvedPackageBuilder] = nodes.compactMap{ node in @@ -272,7 +272,10 @@ private func createResolvedPackages( // Resolve module aliases, if specified, for targets and their dependencies // across packages. Aliasing will result in target renaming. - let moduleAliasingUsed = try resolveModuleAliases(packageBuilders: packageBuilders, observabilityScope: observabilityScope) + let moduleAliasingUsed = try resolveModuleAliases( + packageBuilders: packageBuilders, + observabilityScope: observabilityScope + ) // Scan and validate the dependencies for packageBuilder in packageBuilders { @@ -393,6 +396,8 @@ private func createResolvedPackages( return .target(targetBuilder, conditions: conditions) case .product: return nil + case .innerProduct: + return nil } } targetBuilder.defaultLocalization = packageBuilder.defaultLocalization @@ -400,13 +405,54 @@ private func createResolvedPackages( } // Create product builders for each product in the package. A product can only contain a target present in the same package. - packageBuilder.products = try package.products.map{ - try ResolvedProductBuilder(product: $0, packageBuilder: packageBuilder, targets: $0.targets.map { + packageBuilder.products = try package.products.map { product in + let productBuilder = try ResolvedProductBuilder(product: product, packageBuilder: packageBuilder, targets: product.targets.map { guard let target = targetMap[$0] else { throw InternalError("unknown target \($0)") } return target }) + for targetBuilder in targetBuilders { + var conditions: [PackageCondition]? + var dependency: Target.Dependency? + for dep in targetBuilder.target.dependencies { + dependency = dep + if case let .innerProduct(innerProduct, dependencyConditions) = dep, innerProduct.name == product.name { + conditions = dependencyConditions + } + } + if let conditions = conditions, + let dependency = dependency { + // TODO: Check for cycles + let exists = targetBuilder.dependencies.contains { + if case .target(let trgtBuilder, let _) = $0 { + return trgtBuilder.target.name == productBuilder.product.name + } + if case .product(let productBuilder, let _) = $0 { + return productBuilder.product.name == productBuilder.product.name + } + return false + } + if exists { + targetBuilder.dependencies += targetBuilder.dependencies.filter { + if case .target(let trgtBuilder, let _) = $0 { + return trgtBuilder.target.name == productBuilder.product.name + } + if case .product(let prodBuilder, let _) = $0 { + return prodBuilder.product.name == productBuilder.product.name + } + return false + }.map { _ in + return .product(productBuilder, conditions: conditions) + } + } + else + { + targetBuilder.dependencies.append(.product(productBuilder, conditions: conditions)) + } + } + } + return productBuilder } // add registry metadata if available @@ -431,6 +477,13 @@ private func createResolvedPackages( // Track if multiple targets are found with the same name. var foundDuplicateTarget = false + for packageBuilder in packageBuilders { + for target in packageBuilder.targets { + // Record if we see a duplicate target. + foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(target.target.name).inserted + } + } + // Do another pass and establish product dependencies of each target. for packageBuilder in packageBuilders { let package = packageBuilder.package @@ -490,9 +543,6 @@ private func createResolvedPackages( // Establish dependencies in each target. for targetBuilder in packageBuilder.targets { - // Record if we see a duplicate target. - foundDuplicateTarget = foundDuplicateTarget || !allTargetNames.insert(targetBuilder.target.name).inserted - // Directly add all the system module dependencies. targetBuilder.dependencies += implicitSystemTargetDeps.map { .target($0, conditions: []) } @@ -513,16 +563,25 @@ private func createResolvedPackages( }.map {$0.targets}.flatMap{$0}.filter { t in t.name != productRef.name } - + // Find a product name from the available product dependencies that is most similar to the required product name. let bestMatchedProductName = bestMatch(for: productRef.name, from: Array(allTargetNames)) + var packageContainingBestMatchedProduct: String? + if let bestMatchedProductName, productRef.name == bestMatchedProductName { + let dependentPackages = packageBuilder.dependencies.map(\.package) + for p in dependentPackages where p.targets.contains(where: { $0.name == bestMatchedProductName }) { + packageContainingBestMatchedProduct = p.identity.description + break + } + } let error = PackageGraphError.productDependencyNotFound( package: package.identity.description, targetName: targetBuilder.target.name, dependencyProductName: productRef.name, dependencyPackageName: productRef.package, dependencyProductInDecl: !declProductsAsDependency.isEmpty, - similarProductName: bestMatchedProductName + similarProductName: bestMatchedProductName, + packageContainingSimilarProduct: packageContainingBestMatchedProduct ) packageObservabilityScope.emit(error) } @@ -559,7 +618,7 @@ private func createResolvedPackages( // If a target with similar name was encountered before, we emit a diagnostic. if foundDuplicateTarget { var duplicateTargets = [String: [Package]]() - for targetName in allTargetNames.sorted() { + for targetName in Set(allTargetNames).sorted() { let packages = packageBuilders .filter({ $0.targets.contains(where: { $0.target.name == targetName }) }) .map{ $0.package } @@ -588,7 +647,7 @@ private func createResolvedPackages( case (.some(let registryIdentity), .none): observabilityScope.emit( ModuleError.duplicateModulesScmAndRegistry( - regsitryPackage: registryIdentity, + registryPackage: registryIdentity, scmPackage: potentiallyDuplicatePackage.key.package2.identity, targets: potentiallyDuplicatePackage.value ) @@ -596,7 +655,7 @@ private func createResolvedPackages( case (.none, .some(let registryIdentity)): observabilityScope.emit( ModuleError.duplicateModulesScmAndRegistry( - regsitryPackage: registryIdentity, + registryPackage: registryIdentity, scmPackage: potentiallyDuplicatePackage.key.package1.identity, targets: potentiallyDuplicatePackage.value ) @@ -618,12 +677,37 @@ private func createResolvedPackages( observabilityScope.emit( ModuleError.duplicateModule( targetName: entry.key, - packages: entry.value.map{ $0.identity }) + packages: entry.value.map { $0.identity }) ) } } - return try packageBuilders.map{ try $0.construct() } + do { + let targetBuilders = packageBuilders.flatMap { + $0.targets.map { + KeyedPair($0, key: $0.target) + } + } + if let cycle = findCycle(targetBuilders, successors: { + $0.item.dependencies.flatMap { + switch $0 { + case .product(let productBuilder, conditions: _): + return productBuilder.targets.map { KeyedPair($0, key: $0.target) } + case .target: + return [] // local targets were checked by PackageBuilder. + } + } + }) { + observabilityScope.emit( + ModuleError.cycleDetected( + (cycle.path.map(\.key.name), cycle.cycle.map(\.key.name)) + ) + ) + return IdentifiableSet() + } + } + + return IdentifiableSet(try packageBuilders.map { try $0.construct() }) } private func emitDuplicateProductDiagnostic( @@ -869,7 +953,7 @@ private final class ResolvedProductBuilder: ResolvedBuilder { } /// Builder for resolved target. -private final class ResolvedTargetBuilder: ResolvedBuilder { +private final class ResolvedTargetBuilder: ResolvedBuilder { /// Enumeration to represent target dependencies. enum Dependency { @@ -910,14 +994,14 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { self.platformVersionProvider = platformVersionProvider } - override func constructImpl() throws -> ResolvedTarget { + override func constructImpl() throws -> ResolvedModule { let diagnosticsEmitter = self.observabilityScope.makeDiagnosticsEmitter() { var metadata = ObservabilityMetadata() metadata.targetName = target.name return metadata } - let dependencies = try self.dependencies.map { dependency -> ResolvedTarget.Dependency in + let dependencies = try self.dependencies.map { dependency -> ResolvedModule.Dependency in switch dependency { case .target(let targetBuilder, let conditions): return .target(try targetBuilder.construct(), conditions: conditions) @@ -934,7 +1018,7 @@ private final class ResolvedTargetBuilder: ResolvedBuilder { } } - return ResolvedTarget( + return ResolvedModule( packageIdentity: self.packageIdentity, underlying: self.target, dependencies: dependencies, @@ -968,6 +1052,7 @@ extension Target { } } } + /// Builder for resolved package. private final class ResolvedPackageBuilder: ResolvedBuilder { /// The package reference. @@ -1020,67 +1105,19 @@ private final class ResolvedPackageBuilder: ResolvedBuilder { } override func constructImpl() throws -> ResolvedPackage { + let products = try self.products.map { try $0.construct() } + var targets = products.reduce(into: IdentifiableSet()) { $0.formUnion($1.targets) } + try targets.formUnion(self.targets.map { try $0.construct() }) + return ResolvedPackage( underlying: self.package, defaultLocalization: self.defaultLocalization, supportedPlatforms: self.supportedPlatforms, - dependencies: try self.dependencies.map{ try $0.construct() }, - targets: try self.targets.map{ try $0.construct() }, - products: try self.products.map{ try $0.construct() }, + dependencies: self.dependencies.map { $0.package.identity }, + targets: targets, + products: products, registryMetadata: self.registryMetadata, platformVersionProvider: self.platformVersionProvider ) } } - -/// Finds the first cycle encountered in a graph. -/// -/// This is different from the one in tools support core, in that it handles equality separately from node traversal. Nodes traverse product filters, but only the manifests must be equal for there to be a cycle. -fileprivate func findCycle( - _ nodes: [GraphLoadingNode], - successors: (GraphLoadingNode) throws -> [GraphLoadingNode] -) rethrows -> (path: [Manifest], cycle: [Manifest])? { - // Ordered set to hold the current traversed path. - var path = OrderedCollections.OrderedSet() - - var fullyVisitedManifests = Set() - - // Function to visit nodes recursively. - // FIXME: Convert to stack. - func visit( - _ node: GraphLoadingNode, - _ successors: (GraphLoadingNode) throws -> [GraphLoadingNode] - ) rethrows -> (path: [Manifest], cycle: [Manifest])? { - // Once all successors have been visited, this node cannot participate - // in a cycle. - if fullyVisitedManifests.contains(node.manifest) { - return nil - } - - // If this node is already in the current path then we have found a cycle. - if !path.append(node.manifest).inserted { - let index = path.firstIndex(of: node.manifest)! // forced unwrap safe - return (Array(path[path.startIndex.. 5.2). case productDependencyMissingPackage( @@ -38,38 +58,59 @@ enum PackageGraphError: Swift.Error { packageIdentifier: String ) /// Dependency between a plugin and a dependent target/product of a given type is unsupported - case unsupportedPluginDependency(targetName: String, dependencyName: String, dependencyType: String, dependencyPackage: String?) + case unsupportedPluginDependency( + targetName: String, + dependencyName: String, + dependencyType: String, + dependencyPackage: String? + ) + /// A product was found in multiple packages. case duplicateProduct(product: String, packages: [Package]) /// Duplicate aliases for a target found in a product. - case multipleModuleAliases(target: String, - product: String, - package: String, - aliases: [String]) + case multipleModuleAliases( + target: String, + product: String, + package: String, + aliases: [String] + ) } +@available(*, + deprecated, + renamed: "ModulesGraph", + message: "PackageGraph had a misleading name, it's a graph of dependencies between modules, not just packages" +) +public typealias PackageGraph = ModulesGraph + /// A collection of packages. -public struct PackageGraph { +public struct ModulesGraph { /// The root packages. public let rootPackages: IdentifiableSet - /// The complete list of contained packages, in topological order starting - /// with the root packages. - public let packages: [ResolvedPackage] + /// The complete set of contained packages. + public let packages: IdentifiableSet /// The list of all targets reachable from root targets. - public let reachableTargets: IdentifiableSet + public private(set) var reachableTargets: IdentifiableSet /// The list of all products reachable from root targets. - public let reachableProducts: IdentifiableSet + public private(set) var reachableProducts: IdentifiableSet /// Returns all the targets in the graph, regardless if they are reachable from the root targets or not. - public let allTargets: IdentifiableSet + public private(set) var allTargets: IdentifiableSet - /// Returns all the products in the graph, regardless if they are reachable from the root targets or not. + /// Returns all targets within the module graph in topological order, starting with low-level targets (that have no + /// dependencies). + package var allTargetsInTopologicalOrder: [ResolvedModule] { + get throws { + try topologicalSort(Array(allTargets)) { $0.dependencies.compactMap { $0.target } }.reversed() + } + } - public let allProducts: IdentifiableSet + /// Returns all the products in the graph, regardless if they are reachable from the root targets or not. + public private(set) var allProducts: IdentifiableSet /// Package dependencies required for a fully resolved graph. /// @@ -78,12 +119,16 @@ public struct PackageGraph { public let requiredDependencies: [PackageReference] /// Returns true if a given target is present in root packages and is not excluded for the given build environment. - public func isInRootPackages(_ target: ResolvedTarget, satisfying buildEnvironment: BuildEnvironment) -> Bool { + public func isInRootPackages(_ target: ResolvedModule, satisfying buildEnvironment: BuildEnvironment) -> Bool { // FIXME: This can be easily cached. - return rootPackages.reduce(into: IdentifiableSet()) { (accumulator: inout IdentifiableSet, package: ResolvedPackage) in + return rootPackages.reduce( + into: IdentifiableSet() + ) { (accumulator: inout IdentifiableSet, package: ResolvedPackage) in let allDependencies = package.targets.flatMap { $0.dependencies } let unsatisfiedDependencies = allDependencies.filter { !$0.satisfies(buildEnvironment) } - let unsatisfiedDependencyTargets = unsatisfiedDependencies.compactMap { (dep: ResolvedTarget.Dependency) -> ResolvedTarget? in + let unsatisfiedDependencyTargets = unsatisfiedDependencies.compactMap { ( + dep: ResolvedModule.Dependency + ) -> ResolvedModule? in switch dep { case .target(let target, _): return target @@ -101,17 +146,24 @@ public struct PackageGraph { return self.rootPackages.contains(id: package.id) } - private let targetsToPackages: [ResolvedTarget.ID: ResolvedPackage] - /// Returns the package that contains the target, or nil if the target isn't in the graph. - public func package(for target: ResolvedTarget) -> ResolvedPackage? { - return self.targetsToPackages[target.id] + /// Returns the package based on the given identity, or nil if the package isn't in the graph. + public func package(for identity: PackageIdentity) -> ResolvedPackage? { + packages[identity] } + /// Returns the package that contains the module, or nil if the module isn't in the graph. + public func package(for module: ResolvedModule) -> ResolvedPackage? { + self.package(for: module.packageIdentity) + } - private let productsToPackages: [ResolvedProduct.ID: ResolvedPackage] /// Returns the package that contains the product, or nil if the product isn't in the graph. public func package(for product: ResolvedProduct) -> ResolvedPackage? { - return self.productsToPackages[product.id] + self.package(for: product.packageIdentity) + } + + /// Returns all of the packages that the given package depends on directly. + public func directDependencies(for package: ResolvedPackage) -> [ResolvedPackage] { + package.dependencies.compactMap { self.package(for: $0) } } /// All root and root dependency packages provided as input to the graph. @@ -124,6 +176,7 @@ public struct PackageGraph { public init( rootPackages: [ResolvedPackage], rootDependencies: [ResolvedPackage] = [], + packages: IdentifiableSet, dependencies requiredDependencies: [PackageReference], binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]] ) throws { @@ -131,45 +184,48 @@ public struct PackageGraph { self.requiredDependencies = requiredDependencies self.inputPackages = rootPackages + rootDependencies self.binaryArtifacts = binaryArtifacts - self.packages = try topologicalSort(inputPackages, successors: { $0.dependencies }) - - // Create a mapping from targets to the packages that define them. Here - // we include all targets, including tests in non-root packages, since - // this is intended for lookup and not traversal. - self.targetsToPackages = packages.reduce(into: [:], { partial, package in - package.targets.forEach{ partial[$0.id] = package } - }) + self.packages = packages - let allTargets = IdentifiableSet(packages.flatMap({ package -> [ResolvedTarget] in + var allTargets = IdentifiableSet() + var allProducts = IdentifiableSet() + for package in self.packages { + let targetsToInclude: [ResolvedModule] if rootPackages.contains(id: package.id) { - return package.targets + targetsToInclude = Array(package.targets) } else { // Don't include tests targets from non-root packages so swift-test doesn't // try to run them. - return package.targets.filter({ $0.type != .test }) + targetsToInclude = package.targets.filter { $0.type != .test } } - })) - // Create a mapping from products to the packages that define them. Here - // we include all products, including tests in non-root packages, since - // this is intended for lookup and not traversal. - self.productsToPackages = packages.reduce(into: [:], { partial, package in - package.products.forEach { partial[$0.id] = package } - }) + for target in targetsToInclude { + allTargets.insert(target) + + // Explicitly include dependencies of host tools in the maps of all targets or all products + if target.buildTriple == .tools { + for dependency in try target.recursiveDependencies() { + switch dependency { + case .target(let targetDependency, _): + allTargets.insert(targetDependency) + case .product(let productDependency, _): + allProducts.insert(productDependency) + } + } + } + } - let allProducts = IdentifiableSet(packages.flatMap({ package -> [ResolvedProduct] in if rootPackages.contains(id: package.id) { - return package.products + allProducts.formUnion(package.products) } else { - // Don't include tests products from non-root packages so swift-test doesn't + // Don't include test products from non-root packages so swift-test doesn't // try to run them. - return package.products.filter({ $0.type != .test }) + allProducts.formUnion(package.products.filter { $0.type != .test }) } - })) + } // Compute the reachable targets and products. - let inputTargets = inputPackages.flatMap { $0.targets } - let inputProducts = inputPackages.flatMap { $0.products } + let inputTargets = self.inputPackages.flatMap { $0.targets } + let inputProducts = self.inputPackages.flatMap { $0.products } let recursiveDependencies = try inputTargets.lazy.flatMap { try $0.recursiveDependencies() } self.reachableTargets = IdentifiableSet(inputTargets).union(recursiveDependencies.compactMap { $0.target }) @@ -179,14 +235,39 @@ public struct PackageGraph { self.allProducts = allProducts } + package mutating func updateBuildTripleRecursively(_ buildTriple: BuildTriple) throws { + self.reachableTargets = IdentifiableSet(self.reachableTargets.map { + var target = $0 + target.buildTriple = buildTriple + return target + }) + self.reachableProducts = IdentifiableSet(self.reachableProducts.map { + var product = $0 + product.buildTriple = buildTriple + return product + }) + + self.allTargets = IdentifiableSet(self.allTargets.map { + var target = $0 + target.buildTriple = buildTriple + return target + }) + self.allProducts = IdentifiableSet(self.allProducts.map { + var product = $0 + product.buildTriple = buildTriple + return product + }) + } + /// Computes a map from each executable target in any of the root packages to the corresponding test targets. - func computeTestTargetsForExecutableTargets() throws -> [ResolvedTarget.ID: [ResolvedTarget]] { - var result = [ResolvedTarget.ID: [ResolvedTarget]]() + @_spi(SwiftPMInternal) + public func computeTestTargetsForExecutableTargets() throws -> [ResolvedModule.ID: [ResolvedModule]] { + var result = [ResolvedModule.ID: [ResolvedModule]]() let rootTargets = IdentifiableSet(rootPackages.flatMap { $0.targets }) // Create map of test target to set of its direct dependencies. - let testTargetDepMap: [ResolvedTarget.ID: IdentifiableSet] = try { + let testTargetDepMap: [ResolvedModule.ID: IdentifiableSet] = try { let testTargetDeps = rootTargets.filter({ $0.type == .test }).map({ ($0.id, IdentifiableSet($0.dependencies.compactMap { $0.target }.filter { $0.type != .plugin })) }) @@ -223,12 +304,14 @@ extension PackageGraphError: CustomStringConvertible { (cycle.path + cycle.cycle).map({ $0.displayName }).joined(separator: " -> ") + " -> " + cycle.cycle[0].displayName - case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName): + case .productDependencyNotFound(let package, let targetName, let dependencyProductName, let dependencyPackageName, let dependencyProductInDecl, let similarProductName, let packageContainingSimilarProduct): if dependencyProductInDecl { return "product '\(dependencyProductName)' is declared in the same package '\(package)' and can't be used as a dependency for target '\(targetName)'." } else { var description = "product '\(dependencyProductName)' required by package '\(package)' target '\(targetName)' \(dependencyPackageName.map{ "not found in package '\($0)'" } ?? "not found")." - if let similarProductName { + if let similarProductName, let packageContainingSimilarProduct { + description += " Did you mean '.product(name: \"\(similarProductName)\", package: \"\(packageContainingSimilarProduct)\")'?" + } else if let similarProductName { description += " Did you mean '\(similarProductName)'?" } return description @@ -346,3 +429,49 @@ func topologicalSort( return result.reversed() } + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +public func loadModulesGraph( + identityResolver: IdentityResolver = DefaultIdentityResolver(), + fileSystem: FileSystem, + manifests: [Manifest], + binaryArtifacts: [PackageIdentity: [String: BinaryArtifact]] = [:], + explicitProduct: String? = .none, + shouldCreateMultipleTestProducts: Bool = false, + createREPLProduct: Bool = false, + useXCBuildFileRules: Bool = false, + customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, + observabilityScope: ObservabilityScope +) throws -> ModulesGraph { + let rootManifests = manifests.filter(\.packageKind.isRoot).spm_createDictionary { ($0.path, $0) } + let externalManifests = try manifests.filter { !$0.packageKind.isRoot } + .reduce( + into: OrderedCollections + .OrderedDictionary() + ) { partial, item in + partial[try identityResolver.resolveIdentity(for: item.packageKind)] = (item, fileSystem) + } + + let packages = Array(rootManifests.keys) + let input = PackageGraphRootInput(packages: packages) + let graphRoot = PackageGraphRoot( + input: input, + manifests: rootManifests, + explicitProduct: explicitProduct, + observabilityScope: observabilityScope + ) + + return try ModulesGraph.load( + root: graphRoot, + identityResolver: identityResolver, + additionalFileRules: useXCBuildFileRules ? FileRuleDescription.xcbuildFileTypes : FileRuleDescription + .swiftpmFileTypes, + externalManifests: externalManifests, + binaryArtifacts: binaryArtifacts, + shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, + createREPLProduct: createREPLProduct, + customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, + fileSystem: fileSystem, + observabilityScope: observabilityScope + ) +} diff --git a/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift b/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift index 32a7e37a64f..c95a49b7f10 100644 --- a/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift +++ b/Sources/PackageGraph/Resolution/PlatformVersionProvider.swift @@ -32,7 +32,7 @@ func merge(into partial: inout [SupportedPlatform], platforms: [SupportedPlatfor public struct PlatformVersionProvider: Hashable { public enum Implementation: Hashable { - case mergingFromTargets(IdentifiableSet) + case mergingFromTargets(IdentifiableSet) case customXCTestMinimumDeploymentTargets([PackageModel.Platform: PlatformVersion]) case minimumDeploymentTargetDefault } diff --git a/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift index f952a066a99..c20021d3642 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/ContainerProvider.swift @@ -48,6 +48,12 @@ final class ContainerProvider { self.observabilityScope = observabilityScope } + func removeCachedContainers(for packages: [PackageReference]) { + for package in packages { + self.containersCache[package] = nil + } + } + /// Get a cached container for the given identifier, asserting / throwing if not found. func getCachedContainer(for package: PackageReference) throws -> PubGrubPackageContainer { guard let container = self.containersCache[package] else { @@ -83,11 +89,15 @@ final class ContainerProvider { self.underlying.getContainer( for: package, updateStrategy: self.skipUpdate ? .never : .always, // TODO: make this more elaborate - observabilityScope: self.observabilityScope.makeChildScope(description: "getting package container", metadata: package.diagnosticsMetadata), + observabilityScope: self.observabilityScope.makeChildScope( + description: "getting package container", + metadata: package.diagnosticsMetadata + ), on: .sharedConcurrent ) { result in let result = result.tryMap { container -> PubGrubPackageContainer in let pubGrubContainer = PubGrubPackageContainer(underlying: container, pins: self.pins) + // only cache positive results self.containersCache[package] = pubGrubContainer return pubGrubContainer @@ -112,13 +122,19 @@ final class ContainerProvider { self.underlying.getContainer( for: identifier, updateStrategy: self.skipUpdate ? .never : .always, // TODO: make this more elaborate - observabilityScope: self.observabilityScope.makeChildScope(description: "prefetching package container", metadata: identifier.diagnosticsMetadata), + observabilityScope: self.observabilityScope.makeChildScope( + description: "prefetching package container", + metadata: identifier.diagnosticsMetadata + ), on: .sharedConcurrent ) { result in defer { self.prefetches[identifier]?.leave() } // only cache positive results if case .success(let container) = result { - self.containersCache[identifier] = PubGrubPackageContainer(underlying: container, pins: self.pins) + self.containersCache[identifier] = PubGrubPackageContainer( + underlying: container, + pins: self.pins + ) } } } diff --git a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift index 6c02e492d0e..c2e28f21713 100644 --- a/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift +++ b/Sources/PackageGraph/Resolution/PubGrub/PubGrubDependencyResolver.swift @@ -25,7 +25,7 @@ public struct PubGrubDependencyResolver { public typealias Constraint = PackageContainerConstraint /// the mutable state that get computed - internal final class State { + final class State { /// The root package reference. let root: DependencyResolutionNode @@ -43,10 +43,11 @@ public struct PubGrubDependencyResolver { private let lock = NSLock() - init(root: DependencyResolutionNode, - overriddenPackages: [PackageReference: (version: BoundVersion, products: ProductFilter)] = [:], - solution: PartialSolution = PartialSolution()) - { + init( + root: DependencyResolutionNode, + overriddenPackages: [PackageReference: (version: BoundVersion, products: ProductFilter)] = [:], + solution: PartialSolution = PartialSolution() + ) { self.root = root self.overriddenPackages = overriddenPackages self.solution = solution @@ -103,6 +104,9 @@ public struct PubGrubDependencyResolver { /// Reference to the pins store, if provided. private let pins: PinsStore.Pins + /// The packages that are available in a prebuilt form in SDK or a toolchain + private let availableLibraries: [ProvidedLibrary] + /// The container provider used to load package containers. private let provider: ContainerProvider @@ -121,6 +125,7 @@ public struct PubGrubDependencyResolver { public init( provider: PackageContainerProvider, pins: PinsStore.Pins = [:], + availableLibraries: [ProvidedLibrary] = [], skipDependenciesUpdates: Bool = false, prefetchBasedOnResolvedFile: Bool = false, observabilityScope: ObservabilityScope, @@ -128,6 +133,7 @@ public struct PubGrubDependencyResolver { ) { self.packageContainerProvider = provider self.pins = pins + self.availableLibraries = availableLibraries self.skipDependenciesUpdates = skipDependenciesUpdates self.prefetchBasedOnResolvedFile = prefetchBasedOnResolvedFile self.provider = ContainerProvider( @@ -155,11 +161,13 @@ public struct PubGrubDependencyResolver { } do { - // strips state - return .success(try self.solve(root: root, constraints: constraints).bindings) + let bindings = try self.solve(root: root, constraints: constraints).bindings + return .success(bindings) } catch { // If version solving failing, build the user-facing diagnostic. - if let pubGrubError = error as? PubgrubError, let rootCause = pubGrubError.rootCause, let incompatibilities = pubGrubError.incompatibilities { + if let pubGrubError = error as? PubgrubError, let rootCause = pubGrubError.rootCause, + let incompatibilities = pubGrubError.incompatibilities + { do { var builder = DiagnosticReportBuilder( root: root, @@ -180,7 +188,10 @@ public struct PubGrubDependencyResolver { /// Find a set of dependencies that fit the given constraints. If dependency /// resolution is unable to provide a result, an error is thrown. /// - Warning: It is expected that the root package reference has been set before this is called. - internal func solve(root: DependencyResolutionNode, constraints: [Constraint]) throws -> (bindings: [DependencyResolverBinding], state: State) { + func solve(root: DependencyResolutionNode, constraints: [Constraint]) throws -> ( + bindings: [DependencyResolverBinding], + state: State + ) { // first process inputs let inputs = try self.processInputs(root: root, with: constraints) @@ -201,7 +212,10 @@ public struct PubGrubDependencyResolver { state.decide(state.root, at: "1.0.0") // Add the root incompatibility. - state.addIncompatibility(Incompatibility(terms: [Term(not: root, .exact("1.0.0"))], cause: .root), at: .topLevel) + state.addIncompatibility( + Incompatibility(terms: [Term(not: root, .exact("1.0.0"))], cause: .root), + at: .topLevel + ) // Add inputs root incompatibilities. for incompatibility in inputs.rootIncompatibilities { @@ -217,23 +231,38 @@ public struct PubGrubDependencyResolver { continue } + let package = assignment.term.node.package + let boundVersion: BoundVersion switch assignment.term.requirement { case .exact(let version): - boundVersion = .version(version) + if let library = package.matchingPrebuiltLibrary(in: availableLibraries), + version == library.version + { + boundVersion = .version(version, library: library) + } else { + boundVersion = .version(version) + } case .range, .any, .empty, .ranges: throw InternalError("unexpected requirement value for assignment \(assignment.term)") } let products = assignment.term.node.productFilter - // TODO: replace with async/await when available - let container = try temp_await { provider.getContainer(for: assignment.term.node.package, completion: $0) } - let updatePackage = try container.underlying.loadPackageReference(at: boundVersion) + let updatePackage: PackageReference + if case .version(_, let library) = boundVersion, library != nil { + updatePackage = package + } else { + // TODO: replace with async/await when available + let container = try temp_await { self.provider.getContainer(for: package, completion: $0) } + updatePackage = try container.underlying.loadPackageReference(at: boundVersion) + } if var existing = flattenedAssignments[updatePackage] { guard existing.binding == boundVersion else { - throw InternalError("Two products in one package resolved to different versions: \(existing.products)@\(existing.binding) vs \(products)@\(boundVersion)") + throw InternalError( + "Two products in one package resolved to different versions: \(existing.products)@\(existing.binding) vs \(products)@\(boundVersion)" + ) } existing.products.formUnion(products) flattenedAssignments[updatePackage] = existing @@ -250,12 +279,12 @@ public struct PubGrubDependencyResolver { // Add overridden packages to the result. for (package, override) in state.overriddenPackages { // TODO: replace with async/await when available - let container = try temp_await { provider.getContainer(for: package, completion: $0) } + let container = try temp_await { self.provider.getContainer(for: package, completion: $0) } let updatePackage = try container.underlying.loadPackageReference(at: override.version) finalAssignments.append(.init( - package: updatePackage, - boundVersion: override.version, - products: override.products + package: updatePackage, + boundVersion: override.version, + products: override.products )) } @@ -284,7 +313,10 @@ public struct PubGrubDependencyResolver { // The list of version-based references reachable via local and branch-based references. // These are added as top-level incompatibilities since they always need to be satisfied. // Some of these might be overridden as we discover local and branch-based references. - var versionBasedDependencies = OrderedCollections.OrderedDictionary() + var versionBasedDependencies = OrderedCollections.OrderedDictionary< + DependencyResolutionNode, + [VersionBasedConstraint] + >() // Process unversioned constraints in first phase. We go through all of the unversioned packages // and collect them and their dependencies. This gives us the complete list of unversioned @@ -296,7 +328,9 @@ public struct PubGrubDependencyResolver { // Mark the package as overridden. if var existing = overriddenPackages[constraint.package] { guard existing.version == .unversioned else { - throw InternalError("Overridden package is not unversioned: \(constraint.package)@\(existing.version)") + throw InternalError( + "Overridden package is not unversioned: \(constraint.package)@\(existing.version)" + ) } existing.products.formUnion(constraint.products) overriddenPackages[constraint.package] = existing @@ -308,11 +342,13 @@ public struct PubGrubDependencyResolver { // Process dependencies of this package. // // We collect all version-based dependencies in a separate structure so they can - // be process at the end. This allows us to override them when there is a non-version + // be processed at the end. This allows us to override them when there is a non-version // based (unversioned/branch-based) constraint present in the graph. // TODO: replace with async/await when available - let container = try temp_await { provider.getContainer(for: node.package, completion: $0) } - for dependency in try container.underlying.getUnversionedDependencies(productFilter: node.productFilter) { + let container = try temp_await { self.provider.getContainer(for: node.package, completion: $0) } + for dependency in try container.underlying + .getUnversionedDependencies(productFilter: node.productFilter) + { if let versionedBasedConstraints = VersionBasedConstraint.constraints(dependency) { for constraint in versionedBasedConstraints { versionBasedDependencies[node, default: []].append(constraint) @@ -347,9 +383,13 @@ public struct PubGrubDependencyResolver { case .revision(let existingRevision, let branch)?: // If this branch-based package was encountered before, ensure the references match. if (branch ?? existingRevision) != revision { - throw PubgrubError.unresolvable("\(package.identity) is required using two different revision-based requirements (\(existingRevision) and \(revision)), which is not supported") + throw PubgrubError + .unresolvable( + "\(package.identity) is required using two different revision-based requirements (\(existingRevision) and \(revision)), which is not supported" + ) } else { - // Otherwise, continue since we've already processed this constraint. Any cycles will be diagnosed separately. + // Otherwise, continue since we've already processed this constraint. Any cycles will be diagnosed + // separately. continue } case nil: @@ -359,7 +399,7 @@ public struct PubGrubDependencyResolver { // Process dependencies of this package, similar to the first phase but branch-based dependencies // are not allowed to contain local/unversioned packages. // TODO: replace with async/await when avail - let container = try temp_await { provider.getContainer(for: package, completion: $0) } + let container = try temp_await { self.provider.getContainer(for: package, completion: $0) } // If there is a pin for this revision-based dependency, get // the dependencies at the pinned revision instead of using @@ -370,7 +410,10 @@ public struct PubGrubDependencyResolver { revisionForDependencies = pinRevision // Mark the package as overridden with the pinned revision and record the branch as well. - overriddenPackages[package] = (version: .revision(revisionForDependencies, branch: revision), products: constraint.products) + overriddenPackages[package] = ( + version: .revision(revisionForDependencies, branch: revision), + products: constraint.products + ) } else { revisionForDependencies = revision @@ -391,7 +434,8 @@ public struct PubGrubDependencyResolver { case .versionSet(let req): for node in dependency.nodes() { let versionedBasedConstraint = VersionBasedConstraint(node: node, req: req) - versionBasedDependencies[.root(package: constraint.package), default: []].append(versionedBasedConstraint) + versionBasedDependencies[.root(package: constraint.package), default: []] + .append(versionedBasedConstraint) } case .revision: constraints.append(dependency) @@ -415,12 +459,15 @@ public struct PubGrubDependencyResolver { versionBasedDependencies[root, default: []].append(versionedBasedConstraint) } case .revision, .unversioned: - throw InternalError("Unexpected revision/unversioned requirement in the constraints list: \(constraints)") + throw InternalError( + "Unexpected revision/unversioned requirement in the constraints list: \(constraints)" + ) } } // Finally, compute the root incompatibilities (which will be all version-based). - // note versionBasedDependencies may point to the root package dependencies, or the dependencies of root's non-versioned dependencies + // note versionBasedDependencies may point to the root package dependencies, or the dependencies of root's + // non-versioned dependencies var rootIncompatibilities: [Incompatibility] = [] for (node, constraints) in versionBasedDependencies { for constraint in constraints { @@ -449,7 +496,11 @@ public struct PubGrubDependencyResolver { try self.propagate(state: state, node: nxt) // initiate prefetch of known packages that will be used to make the decision on the next step - self.provider.prefetch(containers: state.solution.undecided.map(\.node.package)) + self.provider.prefetch( + containers: state.solution.undecided.map(\.node.package).filter { + $0.matchingPrebuiltLibrary(in: self.availableLibraries) == nil + } + ) // If decision making determines that no more decisions are to be // made, it returns nil to signal that version solving is done. @@ -462,7 +513,7 @@ public struct PubGrubDependencyResolver { /// partial solution. /// If a conflict is found, the conflicting incompatibility is returned to /// resolve the conflict on. - internal func propagate(state: State, node: DependencyResolutionNode) throws { + func propagate(state: State, node: DependencyResolutionNode) throws { var changed: OrderedCollections.OrderedSet = [node] while !changed.isEmpty { @@ -517,7 +568,6 @@ public struct PubGrubDependencyResolver { return .conflict } - state.derive(unsatisfiedTerm.inverse, cause: incompatibility) self.delegate?.derived(term: unsatisfiedTerm.inverse) return .almostSatisfied(node: unsatisfiedTerm.node) @@ -526,7 +576,7 @@ public struct PubGrubDependencyResolver { // Based on: // https://github.com/dart-lang/pub/tree/master/doc/solver.md#conflict-resolution // https://github.com/dart-lang/pub/blob/master/lib/src/solver/version_solver.dart#L201 - internal func resolve(state: State, conflict: Incompatibility) throws -> Incompatibility { + func resolve(state: State, conflict: Incompatibility) throws -> Incompatibility { self.delegate?.conflict(conflict: conflict) var incompatibility = conflict @@ -537,7 +587,7 @@ public struct PubGrubDependencyResolver { let maxIterations = 1000 var iterations = 0 - while !isCompleteFailure(incompatibility, root: state.root) { + while !self.isCompleteFailure(incompatibility, root: state.root) { var mostRecentTerm: Term? var mostRecentSatisfier: Assignment? var difference: Term? @@ -566,7 +616,10 @@ public struct PubGrubDependencyResolver { if mostRecentTerm == term { difference = mostRecentSatisfier?.term.difference(with: term) if let difference { - previousSatisfierLevel = max(previousSatisfierLevel, try state.solution.satisfier(for: difference.inverse).decisionLevel) + previousSatisfierLevel = try max( + previousSatisfierLevel, + state.solution.satisfier(for: difference.inverse).decisionLevel + ) } } } @@ -605,9 +658,18 @@ public struct PubGrubDependencyResolver { if let mostRecentTerm { if let difference { - self.delegate?.partiallySatisfied(term: mostRecentTerm, by: _mostRecentSatisfier, incompatibility: incompatibility, difference: difference) + self.delegate?.partiallySatisfied( + term: mostRecentTerm, + by: _mostRecentSatisfier, + incompatibility: incompatibility, + difference: difference + ) } else { - self.delegate?.satisfied(term: mostRecentTerm, by: _mostRecentSatisfier, incompatibility: incompatibility) + self.delegate?.satisfied( + term: mostRecentTerm, + by: _mostRecentSatisfier, + incompatibility: incompatibility + ) } } @@ -630,7 +692,10 @@ public struct PubGrubDependencyResolver { incompatibility.terms.isEmpty || (incompatibility.terms.count == 1 && incompatibility.terms.first?.node == root) } - private func computeCounts(for terms: [Term], completion: @escaping (Result<[Term: Int], Error>) -> Void) { + private func computeCounts( + for terms: [Term], + completion: @escaping (Result<[Term: Int], Error>) -> Void + ) { if terms.isEmpty { return completion(.success([:])) } @@ -638,30 +703,56 @@ public struct PubGrubDependencyResolver { let sync = DispatchGroup() let results = ThreadSafeKeyValueStore>() - terms.forEach { term in + for term in terms { sync.enter() - provider.getContainer(for: term.node.package) { result in + self.provider.getContainer(for: term.node.package) { result in defer { sync.leave() } - results[term] = result.flatMap { container in Result(catching: { try container.versionCount(term.requirement) }) } + results[term] = result + .flatMap { container in Result(catching: { try container.versionCount(term.requirement) }) } } } sync.notify(queue: .sharedConcurrent) { do { - completion(.success(try results.mapValues { try $0.get() })) + try completion(.success(results.mapValues { try $0.get() })) } catch { completion(.failure(error)) } } } - internal func makeDecision(state: State, completion: @escaping (Result) -> Void) { + func makeDecision( + state: State, + completion: @escaping (Result) -> Void + ) { // If there are no more undecided terms, version solving is complete. let undecided = state.solution.undecided guard !undecided.isEmpty else { return completion(.success(nil)) } + // If prebuilt libraries are available, let's attempt their versions first before going for + // the latest viable version in the package. This way we archive multiple goals - prioritize + // prebuilt libraries if they satisfy all requirements, avoid counting and building package + // manifests and avoid (re-)building packages. + // + // Since the conflict resolution learns from incorrect terms this wouldn't be re-attempted. + if !self.availableLibraries.isEmpty { + let start = DispatchTime.now() + for pkgTerm in undecided { + let package = pkgTerm.node.package + guard let library = package.matchingPrebuiltLibrary(in: self.availableLibraries) else { + continue + } + + if pkgTerm.requirement.contains(library.version) { + self.delegate?.didResolve(term: pkgTerm, version: library.version, duration: start.distance(to: .now())) + state.decide(pkgTerm.node, at: library.version) + return completion(.success(pkgTerm.node)) + } + } + } + // Prefer packages with least number of versions that fit the current requirements so we // get conflicts (if any) sooner. self.computeCounts(for: undecided) { result in @@ -676,7 +767,10 @@ public struct PubGrubDependencyResolver { // Get the best available version for this package. guard let version = try container.getBestAvailableVersion(for: pkgTerm) else { - state.addIncompatibility(try Incompatibility(pkgTerm, root: state.root, cause: .noAvailableVersion), at: .decisionMaking) + try state.addIncompatibility( + Incompatibility(pkgTerm, root: state.root, cause: .noAvailableVersion), + at: .decisionMaking + ) return completion(.success(pkgTerm.node)) } @@ -717,15 +811,15 @@ public struct PubGrubDependencyResolver { } } -internal enum LogLocation: String { +enum LogLocation: String { case topLevel = "top level" case unitPropagation = "unit propagation" case decisionMaking = "decision making" case conflictResolution = "conflict resolution" } -public extension PubGrubDependencyResolver { - enum PubgrubError: Swift.Error, CustomStringConvertible { +extension PubGrubDependencyResolver { + public enum PubgrubError: Swift.Error, CustomStringConvertible { case _unresolvable(Incompatibility, [DependencyResolutionNode: [Incompatibility]]) case unresolvable(String) @@ -768,7 +862,7 @@ extension PubGrubDependencyResolver { self.requirement = req } - internal static func constraints(_ constraint: Constraint) -> [VersionBasedConstraint]? { + static func constraints(_ constraint: Constraint) -> [VersionBasedConstraint]? { switch constraint.requirement { case .versionSet(let req): return constraint.nodes().map { VersionBasedConstraint(node: $0, req: req) } @@ -787,8 +881,8 @@ private enum PropagationResult { case none } -private extension PackageRequirement { - var isRevision: Bool { +extension PackageRequirement { + fileprivate var isRevision: Bool { switch self { case .versionSet, .unversioned: return false @@ -797,3 +891,31 @@ private extension PackageRequirement { } } } + +extension PackageReference { + public func matchingPrebuiltLibrary(in availableLibraries: [ProvidedLibrary]) -> ProvidedLibrary? { + switch self.kind { + case .fileSystem, .localSourceControl, .root: + return nil // can never match a prebuilt library + case .registry(let identity): + if let registryIdentity = identity.registry { + return availableLibraries.first( + where: { $0.metadata.identities.contains( + where: { $0 == .packageIdentity( + scope: registryIdentity.scope.description, + name: registryIdentity.name.description + ) + } + ) + } + ) + } else { + return nil + } + case .remoteSourceControl(let url): + return availableLibraries.first(where: { + $0.metadata.identities.contains(where: { $0 == .sourceControl(url: url) }) + }) + } + } +} diff --git a/Sources/PackageGraph/Resolution/ResolvedTarget.swift b/Sources/PackageGraph/Resolution/ResolvedModule.swift similarity index 68% rename from Sources/PackageGraph/Resolution/ResolvedTarget.swift rename to Sources/PackageGraph/Resolution/ResolvedModule.swift index 4e9fecaf804..f218a35886d 100644 --- a/Sources/PackageGraph/Resolution/ResolvedTarget.swift +++ b/Sources/PackageGraph/Resolution/ResolvedModule.swift @@ -12,17 +12,20 @@ import PackageModel -/// Represents a fully resolved target. All the dependencies for this target are also stored as resolved. -public struct ResolvedTarget { +@available(*, deprecated, renamed: "ResolvedModule") +public typealias ResolvedTarget = ResolvedModule + +/// Represents a fully resolved module. All the dependencies for this module are also stored as resolved. +public struct ResolvedModule { /// Represents dependency of a resolved target. public enum Dependency { /// Direct dependency of the target. This target is in the same package and should be statically linked. - case target(_ target: ResolvedTarget, conditions: [PackageCondition]) + case target(_ target: ResolvedModule, conditions: [PackageCondition]) /// The target depends on this product. case product(_ product: ResolvedProduct, conditions: [PackageCondition]) - public var target: ResolvedTarget? { + public var target: ResolvedModule? { switch self { case .target(let target, _): return target case .product: return nil @@ -44,7 +47,7 @@ public struct ResolvedTarget { } /// Returns the direct dependencies of the underlying dependency, across the package graph. - public var dependencies: [ResolvedTarget.Dependency] { + public var dependencies: [ResolvedModule.Dependency] { switch self { case .target(let target, _): return target.dependencies @@ -54,7 +57,7 @@ public struct ResolvedTarget { } /// Returns the direct dependencies of the underlying dependency, limited to the target's package. - public var packageDependencies: [ResolvedTarget.Dependency] { + public var packageDependencies: [ResolvedModule.Dependency] { switch self { case .target(let target, _): return target.dependencies @@ -86,7 +89,7 @@ public struct ResolvedTarget { } /// Returns the recursive target dependencies, across the whole package-graph. - public func recursiveTargetDependencies() throws -> [ResolvedTarget] { + public func recursiveTargetDependencies() throws -> [ResolvedModule] { try topologicalSort(self.dependencies) { $0.dependencies }.compactMap { $0.target } } @@ -133,7 +136,7 @@ public struct ResolvedTarget { public let underlying: Target /// The dependencies of this target. - public let dependencies: [Dependency] + public internal(set) var dependencies: [Dependency] /// The default localization for resources. public let defaultLocalization: String? @@ -144,13 +147,17 @@ public struct ResolvedTarget { private let platformVersionProvider: PlatformVersionProvider /// Triple for which this resolved target should be compiled for. - public let buildTriple: BuildTriple + public package(set) var buildTriple: BuildTriple { + didSet { + self.updateBuildTriplesOfDependencies() + } + } /// Create a resolved target instance. public init( packageIdentity: PackageIdentity, underlying: Target, - dependencies: [ResolvedTarget.Dependency], + dependencies: [ResolvedModule.Dependency], defaultLocalization: String? = nil, supportedPlatforms: [SupportedPlatform], platformVersionProvider: PlatformVersionProvider @@ -161,7 +168,50 @@ public struct ResolvedTarget { self.defaultLocalization = defaultLocalization self.supportedPlatforms = supportedPlatforms self.platformVersionProvider = platformVersionProvider - self.buildTriple = .destination + + if underlying.type == .test { + // Make sure that test products are built for the tools triple if it has tools as direct dependencies. + // Without this workaround, `assertMacroExpansion` in tests can't be built, as it requires macros + // and SwiftSyntax to be built for the same triple as the tests. + // See https://github.com/apple/swift-package-manager/pull/7349 for more context. + var inferredBuildTriple = BuildTriple.destination + loop: for dependency in dependencies { + switch dependency { + case .target(let targetDependency, _): + if targetDependency.type == .macro { + inferredBuildTriple = .tools + break loop + } + case .product(let productDependency, _): + if productDependency.type == .macro { + inferredBuildTriple = .tools + break loop + } + } + } + self.buildTriple = inferredBuildTriple + } else { + self.buildTriple = underlying.buildTriple + } + self.updateBuildTriplesOfDependencies() + } + + mutating func updateBuildTriplesOfDependencies() { + if self.buildTriple == .tools { + for (i, dependency) in dependencies.enumerated() { + let updatedDependency: Dependency + switch dependency { + case .target(var target, let conditions): + target.buildTriple = self.buildTriple + updatedDependency = .target(target, conditions: conditions) + case .product(var product, let conditions): + product.buildTriple = self.buildTriple + updatedDependency = .product(product, conditions: conditions) + } + + dependencies[i] = updatedDependency + } + } } public func getSupportedPlatform(for platform: Platform, usingXCTest: Bool) -> SupportedPlatform { @@ -173,15 +223,15 @@ public struct ResolvedTarget { } } -extension ResolvedTarget: CustomStringConvertible { +extension ResolvedModule: CustomStringConvertible { public var description: String { - return "" + return "" } } -extension ResolvedTarget.Dependency: CustomStringConvertible { +extension ResolvedModule.Dependency: CustomStringConvertible { public var description: String { - var str = " Bool { +extension ResolvedModule.Dependency: Equatable { + public static func == (lhs: ResolvedModule.Dependency, rhs: ResolvedModule.Dependency) -> Bool { switch (lhs, rhs) { case (.target(let lhsTarget, _), .target(let rhsTarget, _)): return lhsTarget.id == rhsTarget.id @@ -228,7 +281,7 @@ extension ResolvedTarget.Dependency: Equatable { } } -extension ResolvedTarget.Dependency: Hashable { +extension ResolvedModule.Dependency: Hashable { public func hash(into hasher: inout Hasher) { switch self { case .target(let target, _): @@ -239,12 +292,12 @@ extension ResolvedTarget.Dependency: Hashable { } } -extension ResolvedTarget: Identifiable { +extension ResolvedModule: Identifiable { /// Resolved target identity that uniquely identifies it in a resolution graph. public struct ID: Hashable { public let targetName: String let packageIdentity: PackageIdentity - public let buildTriple: BuildTriple + public var buildTriple: BuildTriple } public var id: ID { diff --git a/Sources/PackageGraph/Resolution/ResolvedPackage.swift b/Sources/PackageGraph/Resolution/ResolvedPackage.swift index bf8d81eac78..32536f0dbb1 100644 --- a/Sources/PackageGraph/Resolution/ResolvedPackage.swift +++ b/Sources/PackageGraph/Resolution/ResolvedPackage.swift @@ -34,13 +34,13 @@ public struct ResolvedPackage { public let underlying: Package /// The targets contained in the package. - public let targets: [ResolvedTarget] + public let targets: IdentifiableSet /// The products produced by the package. public let products: [ResolvedProduct] /// The dependencies of the package. - public let dependencies: [ResolvedPackage] + public let dependencies: [PackageIdentity] /// The default localization for resources. public let defaultLocalization: String? @@ -57,15 +57,15 @@ public struct ResolvedPackage { underlying: Package, defaultLocalization: String?, supportedPlatforms: [SupportedPlatform], - dependencies: [ResolvedPackage], - targets: [ResolvedTarget], + dependencies: [PackageIdentity], + targets: IdentifiableSet, products: [ResolvedProduct], registryMetadata: RegistryReleaseMetadata?, platformVersionProvider: PlatformVersionProvider ) { self.underlying = underlying - self.targets = targets self.products = products + self.targets = targets self.dependencies = dependencies self.defaultLocalization = defaultLocalization self.supportedPlatforms = supportedPlatforms diff --git a/Sources/PackageGraph/Resolution/ResolvedProduct.swift b/Sources/PackageGraph/Resolution/ResolvedProduct.swift index 1208b5b0d7f..3217ef8d9ea 100644 --- a/Sources/PackageGraph/Resolution/ResolvedProduct.swift +++ b/Sources/PackageGraph/Resolution/ResolvedProduct.swift @@ -30,10 +30,10 @@ public struct ResolvedProduct { public let underlying: Product /// The top level targets contained in this product. - public let targets: IdentifiableSet + public internal(set) var targets: IdentifiableSet /// Executable target for test entry point file. - public let testEntryPointTarget: ResolvedTarget? + public let testEntryPointTarget: ResolvedModule? /// The default localization for resources. public let defaultLocalization: String? @@ -44,12 +44,16 @@ public struct ResolvedProduct { public let platformVersionProvider: PlatformVersionProvider /// Triple for which this resolved product should be compiled for. - public let buildTriple: BuildTriple + public internal(set) var buildTriple: BuildTriple { + didSet { + self.updateBuildTriplesOfDependencies() + } + } /// The main executable target of product. /// /// Note: This property is only valid for executable products. - public var executableTarget: ResolvedTarget { + public var executableTarget: ResolvedModule { get throws { guard self.type == .executable || self.type == .snippet || self.type == .macro else { throw InternalError("`executableTarget` should only be called for executable targets") @@ -63,7 +67,11 @@ public struct ResolvedProduct { } } - public init(packageIdentity: PackageIdentity, product: Product, targets: IdentifiableSet) { + public init( + packageIdentity: PackageIdentity, + product: Product, + targets: IdentifiableSet + ) { assert(product.targets.count == targets.count && product.targets.map(\.name).sorted() == targets.map(\.name).sorted()) self.packageIdentity = packageIdentity self.underlying = product @@ -87,7 +95,7 @@ public struct ResolvedProduct { packageAccess: true, // entry point target so treated as a part of the package testEntryPointPath: testEntryPointPath ) - return ResolvedTarget( + return ResolvedModule( packageIdentity: packageIdentity, underlying: swiftTarget, dependencies: targets.map { .target($0, conditions: []) }, @@ -97,7 +105,41 @@ public struct ResolvedProduct { ) } - self.buildTriple = .destination + if product.type == .test { + // Make sure that test products are built for the tools triple if it has tools as direct dependencies. + // Without this workaround, `assertMacroExpansion` in tests can't be built, as it requires macros + // and SwiftSyntax to be built for the same triple as the tests. + // See https://github.com/apple/swift-package-manager/pull/7349 for more context. + var inferredBuildTriple = BuildTriple.destination + targetsLoop: for target in targets { + for dependency in target.dependencies { + switch dependency { + case .target(let targetDependency, _): + if targetDependency.type == .macro { + inferredBuildTriple = .tools + break targetsLoop + } + case .product(let productDependency, _): + if productDependency.type == .macro { + inferredBuildTriple = .tools + break targetsLoop + } + } + } + } + self.buildTriple = inferredBuildTriple + } else { + self.buildTriple = product.buildTriple + } + self.updateBuildTriplesOfDependencies() + } + + mutating func updateBuildTriplesOfDependencies() { + self.targets = IdentifiableSet(self.targets.map { + var target = $0 + target.buildTriple = self.buildTriple + return target + }) } /// True if this product contains Swift targets. @@ -113,13 +155,13 @@ public struct ResolvedProduct { } /// Returns the recursive target dependencies. - public func recursiveTargetDependencies() throws -> [ResolvedTarget] { + public func recursiveTargetDependencies() throws -> [ResolvedModule] { let recursiveDependencies = try targets.lazy.flatMap { try $0.recursiveTargetDependencies() } return Array(IdentifiableSet(self.targets).union(recursiveDependencies)) } private static func computePlatforms( - targets: IdentifiableSet + targets: IdentifiableSet ) -> ([SupportedPlatform], PlatformVersionProvider) { let declaredPlatforms = targets.reduce(into: [SupportedPlatform]()) { partial, item in merge(into: &partial, platforms: item.supportedPlatforms) @@ -151,7 +193,7 @@ public struct ResolvedProduct { extension ResolvedProduct: CustomStringConvertible { public var description: String { - "" + "" } } @@ -166,13 +208,13 @@ extension ResolvedProduct { extension ResolvedProduct: Identifiable { /// Resolved target identity that uniquely identifies it in a resolution graph. public struct ID: Hashable { - public let targetName: String + public let productName: String let packageIdentity: PackageIdentity - public let buildTriple: BuildTriple + public var buildTriple: BuildTriple } public var id: ID { - ID(targetName: self.name, packageIdentity: self.packageIdentity, buildTriple: self.buildTriple) + ID(productName: self.name, packageIdentity: self.packageIdentity, buildTriple: self.buildTriple) } } diff --git a/Sources/PackageLoading/ManifestJSONParser.swift b/Sources/PackageLoading/ManifestJSONParser.swift index ad1322668ea..722c7b8bf88 100644 --- a/Sources/PackageLoading/ManifestJSONParser.swift +++ b/Sources/PackageLoading/ManifestJSONParser.swift @@ -150,6 +150,7 @@ enum ManifestJSONParser { case .v4: languageVersionString = "4" case .v4_2: languageVersionString = "4.2" case .v5: languageVersionString = "5" + case .v6: languageVersionString = "6" case .version(let version): languageVersionString = version } guard let languageVersion = SwiftLanguageVersion(string: languageVersionString) else { @@ -338,6 +339,8 @@ extension TargetDescription.Dependency { package = try identityResolver.mappedIdentity(for: .plain(packageName)).description } self = .product(name: name, package: package, moduleAliases: moduleAliases, condition: condition.map { .init($0) }) + case .innerProduct(let name, let condition): + self = .innerProduct(name: name, condition: condition.map { .init($0) }) case .byName(let name, let condition): self = .byName(name: name, condition: condition.map { .init($0) }) } @@ -533,6 +536,21 @@ extension TargetBuildSettingDescription.Kind { return .enableExperimentalFeature(value) case "unsafeFlags": return .unsafeFlags(values) + + case "swiftLanguageVersion": + guard let rawVersion = values.first else { + throw InternalError("invalid (empty) build settings value") + } + + if values.count > 1 { + throw InternalError("invalid build settings value") + } + + guard let version = SwiftLanguageVersion(string: rawVersion) else { + throw InternalError("unknown swift language version: \(rawVersion)") + } + + return .swiftLanguageVersion(version) default: throw InternalError("invalid build setting \(name)") } diff --git a/Sources/PackageLoading/ManifestLoader+Validation.swift b/Sources/PackageLoading/ManifestLoader+Validation.swift index af215dd5298..6a23d957720 100644 --- a/Sources/PackageLoading/ManifestLoader+Validation.swift +++ b/Sources/PackageLoading/ManifestLoader+Validation.swift @@ -196,6 +196,9 @@ public struct ManifestValidator { validPackages: self.manifest.dependencies )) } + case .innerProduct: + // TODO: Diagnose any possible issues here + break case .byName(let name, _): // Don't diagnose root manifests so we can emit a better diagnostic during package loading. if !self.manifest.packageKind.isRoot && diff --git a/Sources/PackageLoading/ManifestLoader.swift b/Sources/PackageLoading/ManifestLoader.swift index 1d8793b9f81..9424af7e617 100644 --- a/Sources/PackageLoading/ManifestLoader.swift +++ b/Sources/PackageLoading/ManifestLoader.swift @@ -232,6 +232,39 @@ extension ManifestLoaderProtocol { completion(.failure(error)) } } + } + + public func load( + packagePath: AbsolutePath, + packageIdentity: PackageIdentity, + packageKind: PackageReference.Kind, + packageLocation: String, + packageVersion: (version: Version?, revision: String?)?, + currentToolsVersion: ToolsVersion, + identityResolver: IdentityResolver, + dependencyMapper: DependencyMapper, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + delegateQueue: DispatchQueue, + callbackQueue: DispatchQueue + ) async throws -> Manifest { + try await withCheckedThrowingContinuation { + self.load( + packagePath: packagePath, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: packageVersion, + currentToolsVersion: currentToolsVersion, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: fileSystem, + observabilityScope: observabilityScope, + delegateQueue: delegateQueue, + callbackQueue: callbackQueue, + completion: $0.resume(with:) + ) + } } } @@ -824,7 +857,9 @@ public final class ManifestLoader: ManifestLoaderProtocol { // FIXME: Workaround for the module cache bug that's been haunting Swift CI // - let moduleCachePath = try (ProcessEnv.vars["SWIFTPM_MODULECACHE_OVERRIDE"] ?? ProcessEnv.vars["SWIFTPM_TESTS_MODULECACHE"]).flatMap{ try AbsolutePath(validating: $0) } + let moduleCachePath = try ( + ProcessEnv.block["SWIFTPM_MODULECACHE_OVERRIDE"] ?? + ProcessEnv.block["SWIFTPM_TESTS_MODULECACHE"]).flatMap { try AbsolutePath(validating: $0) } var cmd: [String] = [] cmd += [self.toolchain.swiftCompilerPathForManifests.pathString] @@ -922,7 +957,11 @@ public final class ManifestLoader: ManifestLoaderProtocol { evaluationResult.compilerCommandLine = cmd // Compile the manifest. - TSCBasic.Process.popen(arguments: cmd, environment: self.toolchain.swiftCompilerEnvironment, queue: callbackQueue) { result in + TSCBasic.Process.popen( + arguments: cmd, + environment: self.toolchain.swiftCompilerEnvironment, + queue: callbackQueue + ) { result in dispatchPrecondition(condition: .onQueue(callbackQueue)) var cleanupIfError = DelayableAction(target: tmpDir, action: cleanupTmpDir) @@ -1021,7 +1060,11 @@ public final class ManifestLoader: ManifestLoaderProtocol { #endif let cleanupAfterRunning = cleanupIfError.delay() - TSCBasic.Process.popen(arguments: cmd, environment: environment, queue: callbackQueue) { result in + TSCBasic.Process.popen( + arguments: cmd, + environment: environment, + queue: callbackQueue + ) { result in dispatchPrecondition(condition: .onQueue(callbackQueue)) defer { cleanupAfterRunning.perform() } diff --git a/Sources/PackageLoading/PackageBuilder.swift b/Sources/PackageLoading/PackageBuilder.swift index 9379a7d099d..e107fd24ef4 100644 --- a/Sources/PackageLoading/PackageBuilder.swift +++ b/Sources/PackageLoading/PackageBuilder.swift @@ -16,8 +16,8 @@ import OrderedCollections import PackageModel import func TSCBasic.findCycle -import func TSCBasic.topologicalSort import struct TSCBasic.KeyedPair +import func TSCBasic.topologicalSort /// An error in the structure or layout of a package. public enum ModuleError: Swift.Error { @@ -87,7 +87,7 @@ public enum ModuleError: Swift.Error { /// Indicates several targets with the same name exist in a registry and scm package case duplicateModulesScmAndRegistry( - regsitryPackage: PackageIdentity.RegistryIdentity, + registryPackage: PackageIdentity.RegistryIdentity, scmPackage: PackageIdentity, targets: [String] ) @@ -162,7 +162,9 @@ extension ModuleError: CustomStringConvertible { targetsDescription += " and \(targets.count - 3) others" } return """ - multiple similar targets \(targetsDescription) appear in registry package '\(registryPackage)' and source control package '\(scmPackage)', \ + multiple similar targets \(targetsDescription) appear in registry package '\( + registryPackage + )' and source control package '\(scmPackage)', \ this may indicate that the two packages are the same and can be de-duplicated \ by activating the automatic source-control to registry replacement, or by using mirrors. \ if they are not duplicate consider using the `moduleAliases` parameter in manifest to provide unique names @@ -175,11 +177,11 @@ extension ModuleError.InvalidLayoutType: CustomStringConvertible { public var description: String { switch self { case .multipleSourceRoots(let paths): - return "multiple source roots found: " + paths.map(\.description).sorted().joined(separator: ", ") + "multiple source roots found: " + paths.map(\.description).sorted().joined(separator: ", ") case .modulemapInSources(let path): - return "modulemap '\(path)' should be inside the 'include' directory" + "modulemap '\(path)' should be inside the 'include' directory" case .modulemapMissing(let path): - return "missing system target module map at '\(path)'" + "missing system target module map at '\(path)'" } } } @@ -203,9 +205,9 @@ extension Target.Error: CustomStringConvertible { var description: String { switch self { case .invalidName(let path, let problem): - return "invalid target name at '\(path)'; \(problem)" + "invalid target name at '\(path)'; \(problem)" case .mixedSources(let path): - return "target at '\(path)' contains mixed language source files; feature not supported" + "target at '\(path)' contains mixed language source files; feature not supported" } } } @@ -214,7 +216,7 @@ extension Target.Error.ModuleNameProblem: CustomStringConvertible { var description: String { switch self { case .emptyName: - return "target names can not be empty" + "target names can not be empty" } } } @@ -231,9 +233,9 @@ extension Product.Error: CustomStringConvertible { var description: String { switch self { case .emptyName: - return "product names can not be empty" + "product names can not be empty" case .moduleEmpty(let product, let target): - return "target '\(target)' referenced in product '\(product)' is empty" + "target '\(target)' referenced in product '\(product)' is empty" } } } @@ -313,6 +315,10 @@ public final class PackageBuilder { // The set of the sources computed so far, used to validate source overlap private var allSources = Set() + // Caches all declared versions for this package. + private var declaredSwiftVersionsCache: [SwiftLanguageVersion]? = nil + + // Caches the version we chose to build for. private var swiftVersionCache: SwiftLanguageVersion? = nil /// Create a builder for the given manifest and package `path`. @@ -497,15 +503,15 @@ public final class PackageBuilder { -> (targetDir: String, testTargetDir: String, pluginTargetDir: String) { let targetDir = PackageBuilder.predefinedSourceDirectories.first(where: { - fileSystem.isDirectory(packagePath.appending(component: $0)) + self.fileSystem.isDirectory(self.packagePath.appending(component: $0)) }) ?? PackageBuilder.predefinedSourceDirectories[0] let testTargetDir = PackageBuilder.predefinedTestDirectories.first(where: { - fileSystem.isDirectory(packagePath.appending(component: $0)) + self.fileSystem.isDirectory(self.packagePath.appending(component: $0)) }) ?? PackageBuilder.predefinedTestDirectories[0] let pluginTargetDir = PackageBuilder.predefinedPluginDirectories.first(where: { - fileSystem.isDirectory(packagePath.appending(component: $0)) + self.fileSystem.isDirectory(self.packagePath.appending(component: $0)) }) ?? PackageBuilder.predefinedPluginDirectories[0] return (targetDir, testTargetDir, pluginTargetDir) @@ -536,6 +542,16 @@ public final class PackageBuilder { throw ModuleError.artifactNotFound(targetName: target.name, expectedArtifactName: target.name) } return artifact.path + } else if let targetPath = target.path, target.type == .providedLibrary { + guard let path = try? AbsolutePath(validating: targetPath) else { + throw ModuleError.invalidCustomPath(target: target.name, path: targetPath) + } + + if !self.fileSystem.isDirectory(path) { + throw ModuleError.unsupportedTargetPath(targetPath) + } + + return path } else if let subpath = target.path { // If there is a custom path defined, use that. if subpath == "" || subpath == "." { return self.packagePath @@ -558,14 +574,13 @@ public final class PackageBuilder { } // Check if target is present in the predefined directory. - let predefinedDir: PredefinedTargetDirectory - switch target.type { + let predefinedDir: PredefinedTargetDirectory = switch target.type { case .test: - predefinedDir = predefinedTestTargetDirectory + predefinedTestTargetDirectory case .plugin: - predefinedDir = predefinedPluginTargetDirectory + predefinedPluginTargetDirectory default: - predefinedDir = predefinedTargetDirectory + predefinedTargetDirectory } let path = predefinedDir.path.appending(component: target.name) @@ -600,7 +615,12 @@ public final class PackageBuilder { let potentialTargets: [PotentialModule] potentialTargets = try self.manifest.targetsRequired(for: self.productFilter).map { target in let path = try findPath(for: target) - return PotentialModule(name: target.name, path: path, type: target.type, packageAccess: target.packageAccess) + return PotentialModule( + name: target.name, + path: path, + type: target.type, + packageAccess: target.packageAccess + ) } let targets = try createModules(potentialTargets) @@ -639,12 +659,13 @@ public final class PackageBuilder { let products = Dictionary(manifest.products.map { ($0.name, $0) }, uniquingKeysWith: { $1 }) - // If there happens to be a plugin product with the right name in the same package, we want to use that automatically. + // If there happens to be a plugin product with the right name in the same package, we want to use that + // automatically. func pluginTargetName(for productName: String) -> String? { if let product = products[productName], product.type == .plugin { - return product.targets.first + product.targets.first } else { - return nil + nil } } @@ -653,18 +674,23 @@ public final class PackageBuilder { // No reference of this target in manifest, i.e. it has no dependencies. guard let target = self.manifest.targetMap[$0.name] else { return [] } // Collect the successors from declared dependencies. - var successors: [PotentialModule] = target.dependencies.compactMap { + var successors: [PotentialModule] = target.dependencies.flatMap { switch $0 { case .target(let name, _): // Since we already checked above that all referenced targets // has to present, we always expect this target to be present in // potentialModules dictionary. - return potentialModuleMap[name]! + return [potentialModuleMap[name]!] case .product: - return nil + return [] + case .innerProduct(let product, _): + guard let product = self.manifest.products.first(where: { $0.name == product }) else { + return [] + } + return product.targets.compactMap { potentialModuleMap[$0] } case .byName(let name, _): // By name dependency may or may not be a target dependency. - return potentialModuleMap[name] + return potentialModuleMap[name].map { [$0] } ?? [] } } // If there are plugin usages, consider them to be dependencies too. @@ -672,16 +698,16 @@ public final class PackageBuilder { successors += pluginUsages.compactMap { switch $0 { case .plugin(_, .some(_)): - return nil + nil case .plugin(let name, nil): if let potentialModule = potentialModuleMap[name] { - return potentialModule + potentialModule } else if let targetName = pluginTargetName(for: name), let potentialModule = potentialModuleMap[targetName] { - return potentialModule + potentialModule } else { - return nil + nil } } } @@ -724,6 +750,11 @@ public final class PackageBuilder { .init(name: name, package: package, moduleAliases: moduleAliases), conditions: buildConditions(from: condition) ) + case .innerProduct(let name, let condition): + return .innerProduct( + .init(name: name), + conditions: buildConditions(from: condition) + ) case .byName(let name, let condition): // We don't create an object for targets which have no sources. if emptyModules.contains(name) { return nil } @@ -844,6 +875,11 @@ public final class PackageBuilder { path: potentialModule.path, origin: artifactOrigin ) + } else if potentialModule.type == .providedLibrary { + return ProvidedLibraryTarget( + name: potentialModule.name, + path: potentialModule.path + ) } // Check for duplicate target dependencies @@ -877,11 +913,16 @@ public final class PackageBuilder { } // Create the build setting assignment table for this target. - let buildSettings = try self.buildSettings(for: manifestTarget, targetRoot: potentialModule.path, cxxLanguageStandard: self.manifest.cxxLanguageStandard) + let buildSettings = try self.buildSettings( + for: manifestTarget, + targetRoot: potentialModule.path, + cxxLanguageStandard: self.manifest.cxxLanguageStandard, + toolsSwiftVersion: self.toolsSwiftVersion() + ) // Compute the path to public headers directory. let publicHeaderComponent = manifestTarget.publicHeadersPath ?? ClangTarget.defaultPublicHeadersComponent - let publicHeadersPath = potentialModule.path.appending(try RelativePath(validating: publicHeaderComponent)) + let publicHeadersPath = try potentialModule.path.appending(RelativePath(validating: publicHeaderComponent)) guard publicHeadersPath.isDescendantOfOrEqual(to: potentialModule.path) else { throw ModuleError.invalidPublicHeadersDirectory(potentialModule.name) } @@ -956,7 +997,7 @@ public final class PackageBuilder { // Create and return the right kind of target depending on what kind of sources we found. if sources.hasSwiftSources { - return SwiftTarget( + return try SwiftTarget( name: potentialModule.name, potentialBundleName: potentialBundleName, type: targetType, @@ -967,14 +1008,17 @@ public final class PackageBuilder { others: others, dependencies: dependencies, packageAccess: potentialModule.packageAccess, - swiftVersion: try self.swiftVersion(), + declaredSwiftVersions: self.declaredSwiftVersions(), buildSettings: buildSettings, + buildSettingsDescription: manifestTarget.settings, usesUnsafeFlags: manifestTarget.usesUnsafeFlags ) } else { - // It's not a Swift target, so it's a Clang target (those are the only two types of source target currently supported). + // It's not a Swift target, so it's a Clang target (those are the only two types of source target currently + // supported). - // First determine the type of module map that will be appropriate for the target based on its header layout. + // First determine the type of module map that will be appropriate for the target based on its header + // layout. let moduleMapType: ModuleMapType if self.fileSystem.exists(publicHeadersPath) { @@ -1011,18 +1055,28 @@ public final class PackageBuilder { ignored: ignored, dependencies: dependencies, buildSettings: buildSettings, + buildSettingsDescription: manifestTarget.settings, usesUnsafeFlags: manifestTarget.usesUnsafeFlags ) } } /// Creates build setting assignment table for the given target. - func buildSettings(for target: TargetDescription?, targetRoot: AbsolutePath, cxxLanguageStandard: String? = nil) throws -> BuildSettings - .AssignmentTable - { + func buildSettings( + for target: TargetDescription?, + targetRoot: AbsolutePath, + cxxLanguageStandard: String? = nil, + toolsSwiftVersion: SwiftLanguageVersion + ) throws -> BuildSettings.AssignmentTable { var table = BuildSettings.AssignmentTable() guard let target else { return table } + // First let's add a default assignments for tools swift version. + var versionAssignment = BuildSettings.Assignment(default: true) + versionAssignment.values = [toolsSwiftVersion.rawValue] + + table.add(versionAssignment, for: .SWIFT_VERSION) + // Process each setting. for setting in target.settings { let decl: BuildSettings.Declaration @@ -1089,7 +1143,8 @@ public final class PackageBuilder { } if lang == .Cxx { - values = ["-cxx-interoperability-mode=default"] + (cxxLanguageStandard.flatMap { ["-Xcc", "-std=\($0)"] } ?? []) + values = ["-cxx-interoperability-mode=default"] + + (cxxLanguageStandard.flatMap { ["-Xcc", "-std=\($0)"] } ?? []) } else { values = [] } @@ -1131,6 +1186,17 @@ public final class PackageBuilder { } values = ["-enable-experimental-feature", value] + + case .swiftLanguageVersion(let version): + switch setting.tool { + case .c, .cxx, .linker: + throw InternalError("only Swift supports swift language version") + + case .swift: + decl = .SWIFT_VERSION + } + + values = [version.rawValue] } // Create an assignment for this setting. @@ -1154,38 +1220,52 @@ public final class PackageBuilder { if let platforms = condition?.platformNames.map({ if let platform = platformRegistry.platformByName[$0] { - return platform + platform } else { - return PackageModel.Platform.custom(name: $0, oldestSupportedVersion: .unknown) + PackageModel.Platform.custom(name: $0, oldestSupportedVersion: .unknown) } - }), !platforms.isEmpty - { + }), !platforms.isEmpty { conditions.append(.init(platforms: platforms)) } return conditions } - /// Computes the swift version to use for this manifest. - private func swiftVersion() throws -> SwiftLanguageVersion { - if let swiftVersion = self.swiftVersionCache { - return swiftVersion + private func declaredSwiftVersions() throws -> [SwiftLanguageVersion] { + if let versions = self.declaredSwiftVersionsCache { + return versions } - let computedSwiftVersion: SwiftLanguageVersion - - // Figure out the swift version from declared list in the manifest. + let versions: [SwiftLanguageVersion] if let swiftLanguageVersions = manifest.swiftLanguageVersions { - guard let swiftVersion = swiftLanguageVersions.sorted(by: >).first(where: { $0 <= ToolsVersion.current }) - else { + versions = swiftLanguageVersions.sorted(by: >).filter { $0 <= ToolsVersion.current } + + if versions.isEmpty { throw ModuleError.incompatibleToolsVersions( package: self.identity.description, required: swiftLanguageVersions, current: .current ) } - computedSwiftVersion = swiftVersion + } else { + versions = [] + } + + self.declaredSwiftVersionsCache = versions + return versions + } + + /// Computes the swift version to use for this manifest. + private func toolsSwiftVersion() throws -> SwiftLanguageVersion { + if let swiftVersion = self.swiftVersionCache { + return swiftVersion + } + + // Figure out the swift version from declared list in the manifest. + let declaredSwiftVersions = try declaredSwiftVersions() + let computedSwiftVersion: SwiftLanguageVersion = if let declaredSwiftVersion = declaredSwiftVersions.first { + declaredSwiftVersion } else { // Otherwise, use the version depending on the manifest version. - computedSwiftVersion = self.manifest.toolsVersion.swiftLanguageVersion + self.manifest.toolsVersion.swiftLanguageVersion } self.swiftVersionCache = computedSwiftVersion return computedSwiftVersion @@ -1233,9 +1313,9 @@ public final class PackageBuilder { } // If we have already searched this path, skip. if !pathsSearched.contains(searchPath) { - SwiftTarget.testEntryPointNames.forEach { name in + for name in SwiftTarget.testEntryPointNames { let path = searchPath.appending(component: name) - if fileSystem.isFile(path) { + if self.fileSystem.isFile(path) { testEntryPointFiles.insert(path) } } @@ -1331,12 +1411,11 @@ public final class PackageBuilder { // First add explicit products. - let filteredProducts: [ProductDescription] - switch self.productFilter { + let filteredProducts: [ProductDescription] = switch self.productFilter { case .everything: - filteredProducts = self.manifest.products + self.manifest.products case .specific(let set): - filteredProducts = self.manifest.products.filter { set.contains($0.name) } + self.manifest.products.filter { set.contains($0.name) } } for product in filteredProducts { if product.name.isEmpty { @@ -1534,11 +1613,11 @@ public final class PackageBuilder { // These are static constants, safe to access by index; the first choice is preferred. switch type { case .test: - return self.predefinedTestDirectories[0] + self.predefinedTestDirectories[0] case .plugin: - return self.predefinedPluginDirectories[0] + self.predefinedPluginDirectories[0] default: - return self.predefinedSourceDirectories[0] + self.predefinedSourceDirectories[0] } } } @@ -1583,7 +1662,7 @@ extension Manifest { switch $0 { case .target(let name, _): return name - case .byName, .product: + case .byName, .product, .innerProduct: return nil } } @@ -1633,6 +1712,8 @@ extension Target.Dependency { return "target-\(name)" case .product: return "product-\(name)" + case .innerProduct: + return "innerproduct-\(name)" } } } @@ -1647,29 +1728,28 @@ extension PackageBuilder { } return try walk(snippetsDirectory, fileSystem: self.fileSystem) - .filter { fileSystem.isFile($0) && $0.extension == "swift" } + .filter { self.fileSystem.isFile($0) && $0.extension == "swift" } .map { sourceFile in let name = sourceFile.basenameWithoutExt let sources = Sources(paths: [sourceFile], root: sourceFile.parentDirectory) let buildSettings: BuildSettings.AssignmentTable - do { - let targetDescription = try TargetDescription( - name: name, - dependencies: dependencies - .map { - TargetDescription.Dependency.target(name: $0.name) - }, - path: sourceFile.parentDirectory.pathString, - sources: [sourceFile.pathString], - type: .executable, - packageAccess: false - ) - buildSettings = try self.buildSettings( - for: targetDescription, - targetRoot: sourceFile.parentDirectory - ) - } + let targetDescription = try TargetDescription( + name: name, + dependencies: dependencies + .map { + TargetDescription.Dependency.target(name: $0.name) + }, + path: sourceFile.parentDirectory.pathString, + sources: [sourceFile.pathString], + type: .executable, + packageAccess: false + ) + buildSettings = try self.buildSettings( + for: targetDescription, + targetRoot: sourceFile.parentDirectory, + toolsSwiftVersion: self.toolsSwiftVersion() + ) return SwiftTarget( name: name, @@ -1678,8 +1758,8 @@ extension PackageBuilder { sources: sources, dependencies: dependencies, packageAccess: false, - swiftVersion: try swiftVersion(), buildSettings: buildSettings, + buildSettingsDescription: targetDescription.settings, usesUnsafeFlags: false ) } diff --git a/Sources/PackageLoading/PkgConfig.swift b/Sources/PackageLoading/PkgConfig.swift index cb2a5c539de..8838be90135 100644 --- a/Sources/PackageLoading/PkgConfig.swift +++ b/Sources/PackageLoading/PkgConfig.swift @@ -47,6 +47,7 @@ public struct PkgConfig { name: String, additionalSearchPaths: [AbsolutePath]? = .none, brewPrefix: AbsolutePath? = .none, + sysrootDir: AbsolutePath? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope ) throws { @@ -54,6 +55,7 @@ public struct PkgConfig { name: name, additionalSearchPaths: additionalSearchPaths ?? [], brewPrefix: brewPrefix, + sysrootDir: sysrootDir, loadingContext: LoadingContext(), fileSystem: fileSystem, observabilityScope: observabilityScope @@ -64,6 +66,7 @@ public struct PkgConfig { name: String, additionalSearchPaths: [AbsolutePath], brewPrefix: AbsolutePath?, + sysrootDir: AbsolutePath?, loadingContext: LoadingContext, fileSystem: FileSystem, observabilityScope: ObservabilityScope @@ -85,7 +88,7 @@ public struct PkgConfig { ) } - var parser = try PkgConfigParser(pcFile: pcFile, fileSystem: fileSystem) + var parser = try PkgConfigParser(pcFile: pcFile, fileSystem: fileSystem, sysrootDir: ProcessEnv.block["PKG_CONFIG_SYSROOT_DIR"]) try parser.parse() func getFlags(from dependencies: [String]) throws -> (cFlags: [String], libs: [String]) { @@ -103,6 +106,7 @@ public struct PkgConfig { name: dep, additionalSearchPaths: additionalSearchPaths, brewPrefix: brewPrefix, + sysrootDir: sysrootDir, loadingContext: loadingContext, fileSystem: fileSystem, observabilityScope: observabilityScope @@ -126,7 +130,7 @@ public struct PkgConfig { private static var envSearchPaths: [AbsolutePath] { get throws { - if let configPath = ProcessEnv.vars["PKG_CONFIG_PATH"] { + if let configPath = ProcessEnv.block["PKG_CONFIG_PATH"] { #if os(Windows) return try configPath.split(separator: ";").map({ try AbsolutePath(validating: String($0)) }) #else @@ -162,13 +166,93 @@ internal struct PkgConfigParser { public private(set) var privateDependencies = [String]() public private(set) var cFlags = [String]() public private(set) var libs = [String]() + public private(set) var sysrootDir: String? - public init(pcFile: AbsolutePath, fileSystem: FileSystem) throws { + public init(pcFile: AbsolutePath, fileSystem: FileSystem, sysrootDir: String?) throws { guard fileSystem.isFile(pcFile) else { throw StringError("invalid pcfile \(pcFile)") } self.pcFile = pcFile self.fileSystem = fileSystem + self.sysrootDir = sysrootDir + } + + // Compress repeated path separators to one. + private func compressPathSeparators(_ value: String) -> String { + let components = value.components(separatedBy: "/").filter { !$0.isEmpty }.joined(separator: "/") + if value.hasPrefix("/") { + return "/" + components + } else { + return components + } + } + + // Trim duplicate sysroot prefixes, matching the approach of pkgconf + private func trimDuplicateSysroot(_ value: String) -> String { + // If sysroot has been applied more than once, remove the first instance. + // pkgconf makes this check after variable expansion to handle rare .pc + // files which expand ${pc_sysrootdir} directly: + // https://github.com/pkgconf/pkgconf/issues/123 + // + // For example: + // /sysroot/sysroot/remainder -> /sysroot/remainder + // + // However, pkgconf's algorithm searches for an additional sysrootdir anywhere in + // the string after the initial prefix, rather than looking for two sysrootdir prefixes + // directly next to each other: + // + // /sysroot/filler/sysroot/remainder -> /filler/sysroot/remainder + // + // It might seem more logical not to strip sysroot in this case, as it is not a double + // prefix, but for compatibility trimDuplicateSysroot is faithful to pkgconf's approach + // in the functions `pkgconf_tuple_parse` and `should_rewrite_sysroot`. + + // Only trim if sysroot is defined with a meaningful value + guard let sysrootDir, sysrootDir != "/" else { + return value + } + + // Only trim absolute paths starting with sysroot + guard value.hasPrefix("/"), value.hasPrefix(sysrootDir) else { + return value + } + + // If sysroot appears multiple times, trim the prefix + // N.B. sysroot can appear anywhere in the remainder + // of the value, mirroring pkgconf's logic + let pathSuffix = value.dropFirst(sysrootDir.count) + if pathSuffix.contains(sysrootDir) { + return String(pathSuffix) + } else { + return value + } + } + + // Apply sysroot to generated paths, matching the approach of pkgconf + private func applySysroot(_ value: String) -> String { + // The two main pkg-config implementations handle sysroot differently: + // + // `pkg-config` (freedesktop.org) prepends sysroot after variable expansion, when in creates the compiler flag lists + // `pkgconf` prepends sysroot to variables when they are defined, so sysroot is included when they are expanded + // + // pkg-config's method skips single character compiler flags, such as '-I' and '-L', and has special cases for longer options. + // It does not handle spaces between the flags and their values properly, and prepends sysroot multiple times in some cases, + // such as when the .pc file uses the sysroot_dir variable directly or has been rewritten to hard-code the sysroot prefix. + // + // pkgconf's method handles spaces correctly, although it also requires extra checks to ensure that sysroot is not applied + // more than once. + // + // In 2024 pkg-config is the more popular option according to Homebrew installation statistics, but the major Linux distributions + // have generally switched to pkgconf. + // + // We will use pkgconf's method here as it seems more robust than pkg-config's, and pkgconf's greater popularity on Linux + // means libraries developed there may depend on the specific way it handles .pc files. + + if value.hasPrefix("/"), let sysrootDir, !value.hasPrefix(sysrootDir) { + return compressPathSeparators(trimDuplicateSysroot(sysrootDir + value)) + } else { + return compressPathSeparators(trimDuplicateSysroot(value)) + } } public mutating func parse() throws { @@ -183,7 +267,9 @@ internal struct PkgConfigParser { variables["pcfiledir"] = pcFile.parentDirectory.pathString // Add pc_sysrootdir variable. This is the path of the sysroot directory for pc files. - variables["pc_sysrootdir"] = ProcessEnv.vars["PKG_CONFIG_SYSROOT_DIR"] ?? AbsolutePath.root.pathString + // pkgconf does not define pc_sysrootdir if the path of the .pc file is outside sysrootdir. + // SwiftPM does not currently make that check. + variables["pc_sysrootdir"] = sysrootDir ?? AbsolutePath.root.pathString let fileContents: String = try fileSystem.readFileContents(pcFile) for line in fileContents.components(separatedBy: "\n") { @@ -199,7 +285,7 @@ internal struct PkgConfigParser { // Found a variable. let (name, maybeValue) = line.spm_split(around: "=") let value = maybeValue?.spm_chuzzle() ?? "" - variables[name.spm_chuzzle() ?? ""] = try resolveVariables(value) + variables[name.spm_chuzzle() ?? ""] = try applySysroot(resolveVariables(value)) } else { // Unexpected thing in the pc file, abort. throw PkgConfigError.parsingError("Unexpected line: \(line) in \(pcFile)") diff --git a/Sources/PackageLoading/Platform.swift b/Sources/PackageLoading/Platform.swift index 324f0593458..297954498c1 100644 --- a/Sources/PackageLoading/Platform.swift +++ b/Sources/PackageLoading/Platform.swift @@ -20,23 +20,21 @@ private func isAndroid() -> Bool { (try? localFileSystem.isFile(AbsolutePath(validating: "/system/bin/toybox"))) ?? false } -public enum Platform: Equatable { +public enum Platform: Equatable, Sendable { case android case darwin case linux(LinuxFlavor) case windows /// Recognized flavors of linux. - public enum LinuxFlavor: Equatable { + public enum LinuxFlavor: Equatable, Sendable { case debian case fedora } } extension Platform { - // This is not just a computed property because the ToolchainRegistryTests - // change the value. - public static var current: Platform? = { + public static let current: Platform? = { #if os(Windows) return .windows #else diff --git a/Sources/PackageLoading/TargetSourcesBuilder.swift b/Sources/PackageLoading/TargetSourcesBuilder.swift index 37012f54de7..bff05448bf4 100644 --- a/Sources/PackageLoading/TargetSourcesBuilder.swift +++ b/Sources/PackageLoading/TargetSourcesBuilder.swift @@ -185,7 +185,7 @@ public struct TargetSourcesBuilder { } let additionalResources: [Resource] - if toolsVersion >= .v5_11 { + if toolsVersion >= .v6_0 { additionalResources = declaredResources.compactMap { resource in if handledResources.contains(resource.path) { return nil @@ -563,7 +563,7 @@ public struct TargetSourcesBuilder { } /// Describes a rule for including a source or resource file in a target. -public struct FileRuleDescription { +public struct FileRuleDescription: Sendable { /// A rule semantically describes a file/directory in a target. /// /// It is up to the build system to translate a rule into a build command. diff --git a/Sources/PackageLoading/ToolsVersionParser.swift b/Sources/PackageLoading/ToolsVersionParser.swift index 290556d3e05..f38e2e4eb07 100644 --- a/Sources/PackageLoading/ToolsVersionParser.swift +++ b/Sources/PackageLoading/ToolsVersionParser.swift @@ -71,10 +71,10 @@ public struct ToolsVersionParser { while let newlineIndex = string.firstIndex(where: { $0.isNewline }) { string = String(string[newlineIndex...].dropFirst()) if !string.isEmpty, let result = try? Self._parse(utf8String: string) { - if result >= ToolsVersion.v5_11 { + if result >= ToolsVersion.v6_0 { return result } else { - throw Error.backwardIncompatiblePre5_11(.toolsVersionNeedsToBeFirstLine, specifiedVersion: result) + throw Error.backwardIncompatiblePre6_0(.toolsVersionNeedsToBeFirstLine, specifiedVersion: result) } } } @@ -504,9 +504,9 @@ extension ToolsVersionParser { case unidentified } - /// Details of backward-incompatible contents with Swift tools version < 5.11. - public enum BackwardIncompatibilityPre5_11 { - /// Tools-versions on subsequent lines of the manifest are only accepted by 5.11 or later. + /// Details of backward-incompatible contents with Swift tools version < 6.0. + public enum BackwardIncompatibilityPre6_0 { + /// Tools-versions on subsequent lines of the manifest are only accepted by 6.0 or later. case toolsVersionNeedsToBeFirstLine } @@ -520,8 +520,8 @@ extension ToolsVersionParser { case malformedToolsVersionSpecification(_ malformationLocation: ToolsVersionSpecificationMalformationLocation) /// Backward-incompatible contents with Swift tools version < 5.4. case backwardIncompatiblePre5_4(_ incompatibility: BackwardIncompatibilityPre5_4, specifiedVersion: ToolsVersion) - /// Backward-incompatible contents with Swift tools version < 5.11. - case backwardIncompatiblePre5_11(_ incompatibility: BackwardIncompatibilityPre5_11, specifiedVersion: ToolsVersion) + /// Backward-incompatible contents with Swift tools version < 6.0. + case backwardIncompatiblePre6_0(_ incompatibility: BackwardIncompatibilityPre6_0, specifiedVersion: ToolsVersion) public var description: String { @@ -588,10 +588,10 @@ extension ToolsVersionParser { case .unidentified: return "the manifest is backward-incompatible with Swift < 5.4, but the package manager is unable to pinpoint the exact incompatibility; consider replacing the current Swift tools version specification with '\(specifiedVersion.specification())' to specify Swift \(specifiedVersion) as the lowest Swift version supported by the project, then move the new specification to the very beginning of this manifest file; additionally, please consider filing a bug report on https://bugs.swift.org with this file attached" } - case let .backwardIncompatiblePre5_11(incompatibility, _): + case let .backwardIncompatiblePre6_0(incompatibility, _): switch incompatibility { case .toolsVersionNeedsToBeFirstLine: - return "the manifest is backward-incompatible with Swift < 5.11 because the tools-version was specified in a subsequent line of the manifest, not the first line. Either move the tools-version specification or increase the required tools-version of your manifest" + return "the manifest is backward-incompatible with Swift < 6.0 because the tools-version was specified in a subsequent line of the manifest, not the first line. Either move the tools-version specification or increase the required tools-version of your manifest" } } diff --git a/Sources/PackageModel/ArtifactsArchiveMetadata.swift b/Sources/PackageModel/ArtifactsArchiveMetadata.swift index 6c10ac564aa..b76333ae4a0 100644 --- a/Sources/PackageModel/ArtifactsArchiveMetadata.swift +++ b/Sources/PackageModel/ArtifactsArchiveMetadata.swift @@ -52,9 +52,9 @@ public struct ArtifactsArchiveMetadata: Equatable { public struct Variant: Equatable { public let path: RelativePath - public let supportedTriples: [Triple] + public let supportedTriples: [Triple]? - public init(path: RelativePath, supportedTriples: [Triple]) { + public init(path: RelativePath, supportedTriples: [Triple]?) { self.path = path self.supportedTriples = supportedTriples } @@ -121,7 +121,7 @@ extension ArtifactsArchiveMetadata.Variant: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.supportedTriples = try container.decode([String].self, forKey: .supportedTriples).map { try Triple($0) } + self.supportedTriples = try container.decodeIfPresent([String].self, forKey: .supportedTriples)?.map { try Triple($0) } self.path = try RelativePath(validating: container.decode(String.self, forKey: .path)) } } diff --git a/Sources/PackageModel/BuildSettings.swift b/Sources/PackageModel/BuildSettings.swift index 14951dce9be..269997c7179 100644 --- a/Sources/PackageModel/BuildSettings.swift +++ b/Sources/PackageModel/BuildSettings.swift @@ -12,12 +12,13 @@ /// Namespace for build settings. public enum BuildSettings { - /// Build settings declarations. public struct Declaration: Hashable, Codable { // Swift. - public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS: Declaration = .init("SWIFT_ACTIVE_COMPILATION_CONDITIONS") + public static let SWIFT_ACTIVE_COMPILATION_CONDITIONS: Declaration = + .init("SWIFT_ACTIVE_COMPILATION_CONDITIONS") public static let OTHER_SWIFT_FLAGS: Declaration = .init("OTHER_SWIFT_FLAGS") + public static let SWIFT_VERSION: Declaration = .init("SWIFT_VERSION") // C family. public static let GCC_PREPROCESSOR_DEFINITIONS: Declaration = .init("GCC_PREPROCESSOR_DEFINITIONS") @@ -47,18 +48,23 @@ public enum BuildSettings { /// The condition associated with this assignment. public var conditions: [PackageCondition] { get { - return _conditions.map { $0.underlying } + self._conditions.map(\.underlying) } set { - _conditions = newValue.map { PackageConditionWrapper($0) } + self._conditions = newValue.map { PackageConditionWrapper($0) } } } private var _conditions: [PackageConditionWrapper] - public init() { + /// Indicates whether this assignment represents a default + /// that should be used only if no other assignments match. + public let `default`: Bool + + public init(default: Bool = false) { self._conditions = [] self.values = [] + self.default = `default` } } @@ -67,13 +73,13 @@ public enum BuildSettings { public private(set) var assignments: [Declaration: [Assignment]] public init() { - assignments = [:] + self.assignments = [:] } /// Add the given assignment to the table. - mutating public func add(_ assignment: Assignment, for decl: Declaration) { + public mutating func add(_ assignment: Assignment, for decl: Declaration) { // FIXME: We should check for duplicate assignments. - assignments[decl, default: []].append(assignment) + self.assignments[decl, default: []].append(assignment) } } @@ -100,12 +106,18 @@ public enum BuildSettings { } // Add values from each assignment if it satisfies the build environment. - let values = assignments + let allViableAssignments = assignments .lazy .filter { $0.conditions.allSatisfy { $0.satisfies(self.environment) } } - .flatMap { $0.values } - return Array(values) + let nonDefaultAssignments = allViableAssignments.filter { !$0.default } + + // If there are no non-default assignments, let's fallback to defaults. + if nonDefaultAssignments.isEmpty { + return allViableAssignments.filter(\.default).flatMap(\.values) + } + + return nonDefaultAssignments.flatMap(\.values) } } } diff --git a/Sources/PackageModel/CMakeLists.txt b/Sources/PackageModel/CMakeLists.txt index 4f9fce12ef4..a7b13798bb4 100644 --- a/Sources/PackageModel/CMakeLists.txt +++ b/Sources/PackageModel/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(PackageModel DependencyMapper.swift Diagnostics.swift IdentityResolver.swift + InstalledLibrariesSupport/LibraryMetadata.swift InstalledSwiftPMConfiguration.swift Manifest/Manifest.swift Manifest/PackageConditionDescription.swift @@ -50,6 +51,7 @@ add_library(PackageModel Target/BinaryTarget.swift Target/ClangTarget.swift Target/PluginTarget.swift + Target/ProvidedLibraryTarget.swift Target/SwiftTarget.swift Target/SystemLibraryTarget.swift Target/Target.swift diff --git a/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift b/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift new file mode 100644 index 00000000000..4a0312b6e78 --- /dev/null +++ b/Sources/PackageModel/InstalledLibrariesSupport/LibraryMetadata.swift @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import struct TSCUtility.Version + +public struct ProvidedLibrary: Hashable { + public let location: AbsolutePath + public let metadata: LibraryMetadata + + public var version: Version { + .init(stringLiteral: metadata.version) + } +} + +public struct LibraryMetadata: Hashable, Decodable { + public enum Identity: Hashable, Decodable { + case packageIdentity(scope: String, name: String) + case sourceControl(url: SourceControlURL) + } + + /// The package from which it was built (e.g., the URL https://github.com/apple/swift-syntax.git) + public let identities: [Identity] + /// The version that was built (e.g., 509.0.2) + public let version: String + /// The product name, if it differs from the module name (e.g., SwiftParser). + public let productName: String + + let schemaVersion: Int +} + +extension LibraryMetadata.Identity { + public var identity: PackageIdentity { + switch self { + case .packageIdentity(let scope, let name): + return PackageIdentity.plain("\(scope)/\(name)") + case .sourceControl(let url): + return PackageIdentity(url: url) + } + } + + public var kind: PackageReference.Kind { + switch self { + case .packageIdentity: + return .registry(self.identity) + case .sourceControl(let url): + return .remoteSourceControl(.init(url.absoluteString)) + } + } + + public var ref: PackageReference { + return PackageReference(identity: self.identity, kind: self.kind) + } +} diff --git a/Sources/PackageModel/InstalledSwiftPMConfiguration.swift b/Sources/PackageModel/InstalledSwiftPMConfiguration.swift index e89788cb2ad..00c637289d6 100644 --- a/Sources/PackageModel/InstalledSwiftPMConfiguration.swift +++ b/Sources/PackageModel/InstalledSwiftPMConfiguration.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public struct InstalledSwiftPMConfiguration: Codable { +public struct InstalledSwiftPMConfiguration { public struct Version: Codable, CustomStringConvertible { let major: Int let minor: Int @@ -31,8 +31,66 @@ public struct InstalledSwiftPMConfiguration: Codable { let version: Int public let swiftSyntaxVersionForMacroTemplate: Version + public let swiftTestingVersionForTestTemplate: Version public static var `default`: InstalledSwiftPMConfiguration { - return .init(version: 0, swiftSyntaxVersionForMacroTemplate: .init(major: 509, minor: 0, patch: 0)) + return .init( + version: 0, + swiftSyntaxVersionForMacroTemplate: .init( + major: 600, + minor: 0, + patch: 0, + prereleaseIdentifier: "latest" + ), + swiftTestingVersionForTestTemplate: defaultSwiftTestingVersionForTestTemplate + ) } + + private static var defaultSwiftTestingVersionForTestTemplate: Version { + .init( + major: 0, + minor: 8, + patch: 0, + prereleaseIdentifier: nil + ) + } +} + +extension InstalledSwiftPMConfiguration: Codable { + enum CodingKeys: CodingKey { + case version + case swiftSyntaxVersionForMacroTemplate + case swiftTestingVersionForTestTemplate + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.version = try container.decode( + Int.self, + forKey: CodingKeys.version + ) + self.swiftSyntaxVersionForMacroTemplate = try container.decode( + Version.self, + forKey: CodingKeys.swiftSyntaxVersionForMacroTemplate + ) + self.swiftTestingVersionForTestTemplate = try container.decodeIfPresent( + Version.self, + forKey: CodingKeys.swiftTestingVersionForTestTemplate + ) ?? InstalledSwiftPMConfiguration.defaultSwiftTestingVersionForTestTemplate + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.version, forKey: CodingKeys.version) + try container.encode( + self.swiftSyntaxVersionForMacroTemplate, + forKey: CodingKeys.swiftSyntaxVersionForMacroTemplate + ) + try container.encode( + self.swiftTestingVersionForTestTemplate, + forKey: CodingKeys.swiftTestingVersionForTestTemplate + ) + } } diff --git a/Sources/PackageModel/Manifest/Manifest.swift b/Sources/PackageModel/Manifest/Manifest.swift index 7556b79f7b9..bacd66fa65c 100644 --- a/Sources/PackageModel/Manifest/Manifest.swift +++ b/Sources/PackageModel/Manifest/Manifest.swift @@ -389,6 +389,9 @@ public final class Manifest: Sendable { } else { // < 5.2 registry.unknown.insert(product) } + case .innerProduct: + // All products in the root package are already requested by definition. + break case .byName(let product, _): if self.toolsVersion < .v5_2 { // A by‐name entry might be a product from anywhere. @@ -554,3 +557,50 @@ extension Manifest: Encodable { try container.encode(self.packageKind, forKey: .packageKind) } } + +extension Manifest { + package static func forProvidedLibrary( + fileSystem: FileSystem, + package: PackageReference, + libraryPath: AbsolutePath, + version: Version + ) throws -> Manifest { + let names = try fileSystem.getDirectoryContents(libraryPath).filter { + $0.hasSuffix("swiftmodule") + }.map { + let components = $0.split(separator: ".") + return String(components[0]) + } + + let products: [ProductDescription] = try names.map { + try .init(name: $0, type: .library(.automatic), targets: [$0]) + } + + let targets: [TargetDescription] = try names.map { + try .init( + name: $0, + path: libraryPath.pathString, + type: .providedLibrary + ) + } + + return .init( + displayName: package.identity.description, + path: libraryPath.appending(component: "provided-library.json"), + packageKind: package.kind, + packageLocation: package.locationString, + defaultLocalization: nil, + platforms: [], + version: version, + revision: nil, + toolsVersion: .v6_0, + pkgConfig: nil, + providers: nil, + cLanguageStandard: nil, + cxxLanguageStandard: nil, + swiftLanguageVersions: nil, + products: products, + targets: targets + ) + } +} diff --git a/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift b/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift index a0eb9be2575..e535c1c9b16 100644 --- a/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift +++ b/Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift @@ -12,7 +12,6 @@ /// A namespace for target-specific build settings. public enum TargetBuildSettingDescription { - /// The tool for which a build setting is declared. public enum Tool: String, Codable, Hashable, CaseIterable, Sendable { case c @@ -40,12 +39,15 @@ public enum TargetBuildSettingDescription { case unsafeFlags([String]) + case swiftLanguageVersion(SwiftLanguageVersion) + public var isUnsafeFlags: Bool { switch self { case .unsafeFlags(let flags): // If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it. return !flags.isEmpty - case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode, .enableUpcomingFeature, .enableExperimentalFeature: + case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode, + .enableUpcomingFeature, .enableExperimentalFeature, .swiftLanguageVersion: return false } } diff --git a/Sources/PackageModel/Manifest/TargetDescription.swift b/Sources/PackageModel/Manifest/TargetDescription.swift index d131c99f37e..b1b4b570641 100644 --- a/Sources/PackageModel/Manifest/TargetDescription.swift +++ b/Sources/PackageModel/Manifest/TargetDescription.swift @@ -21,12 +21,14 @@ public struct TargetDescription: Hashable, Encodable, Sendable { case binary case plugin case `macro` + case providedLibrary } /// Represents a target's dependency on another entity. public enum Dependency: Hashable, Sendable { case target(name: String, condition: PackageConditionDescription?) case product(name: String, package: String?, moduleAliases: [String: String]? = nil, condition: PackageConditionDescription?) + case innerProduct(name: String, condition: PackageConditionDescription?) case byName(name: String, condition: PackageConditionDescription?) public static func target(name: String) -> Dependency { @@ -92,7 +94,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable { } /// The declared target dependencies. - public let dependencies: [Dependency] + public package(set) var dependencies: [Dependency] /// The custom public headers path. public let publicHeadersPath: String? @@ -222,6 +224,19 @@ public struct TargetDescription: Hashable, Encodable, Sendable { if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") } if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") } if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } + case .providedLibrary: + if path == nil { throw Error.providedLibraryTargetRequiresPath(targetName: name) } + if url != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "url") } + if !dependencies.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "dependencies") } + if !exclude.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "exclude") } + if sources != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "sources") } + if !resources.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "resources") } + if publicHeadersPath != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "publicHeadersPath") } + if pkgConfig != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pkgConfig") } + if providers != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "providers") } + if pluginCapability != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginCapability") } + if !settings.isEmpty { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "settings") } + if pluginUsages != nil { throw Error.disallowedPropertyInTarget(targetName: name, propertyName: "pluginUsages") } } self.name = name @@ -245,7 +260,7 @@ public struct TargetDescription: Hashable, Encodable, Sendable { extension TargetDescription.Dependency: Codable { private enum CodingKeys: String, CodingKey { - case target, product, byName + case target, product, innerProduct, byName } public func encode(to encoder: Encoder) throws { @@ -261,6 +276,10 @@ extension TargetDescription.Dependency: Codable { try unkeyedContainer.encode(a2) try unkeyedContainer.encode(a3) try unkeyedContainer.encode(a4) + case let .innerProduct(a1, a2): + var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .innerProduct) + try unkeyedContainer.encode(a1) + try unkeyedContainer.encode(a2) case let .byName(a1, a2): var unkeyedContainer = container.nestedUnkeyedContainer(forKey: .byName) try unkeyedContainer.encode(a1) @@ -286,6 +305,11 @@ extension TargetDescription.Dependency: Codable { let a3 = try unkeyedValues.decode([String: String].self) let a4 = try unkeyedValues.decodeIfPresent(PackageConditionDescription.self) self = .product(name: a1, package: a2, moduleAliases: a3, condition: a4) + case .innerProduct: + var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) + let a1 = try unkeyedValues.decode(String.self) + let a2 = try unkeyedValues.decodeIfPresent(PackageConditionDescription.self) + self = .innerProduct(name: a1, condition: a2) case .byName: var unkeyedValues = try values.nestedUnkeyedContainer(forKey: key) let a1 = try unkeyedValues.decode(String.self) @@ -370,13 +394,16 @@ import protocol Foundation.LocalizedError private enum Error: LocalizedError, Equatable { case binaryTargetRequiresEitherPathOrURL(targetName: String) case disallowedPropertyInTarget(targetName: String, propertyName: String) - + case providedLibraryTargetRequiresPath(targetName: String) + var errorDescription: String? { switch self { case .binaryTargetRequiresEitherPathOrURL(let targetName): return "binary target '\(targetName)' neither defines neither path nor URL for its artifacts" case .disallowedPropertyInTarget(let targetName, let propertyName): return "target '\(targetName)' contains a value for disallowed property '\(propertyName)'" + case .providedLibraryTargetRequiresPath(let targetName): + return "provided library target '\(targetName)' does not define a path to the library" } } } diff --git a/Sources/PackageModel/ManifestSourceGeneration.swift b/Sources/PackageModel/ManifestSourceGeneration.swift index 8d630deb9cc..e1d68156242 100644 --- a/Sources/PackageModel/ManifestSourceGeneration.swift +++ b/Sources/PackageModel/ManifestSourceGeneration.swift @@ -63,7 +63,7 @@ public typealias ManifestCustomProductTypeSourceGenerator = (ProductDescription) /// Convenience initializers for package manifest structures. fileprivate extension SourceCodeFragment { - + /// Instantiates a SourceCodeFragment to represent an entire manifest. init( from manifest: Manifest, @@ -317,6 +317,8 @@ fileprivate extension SourceCodeFragment { self.init(enum: "plugin", subnodes: params, multiline: true) case .macro: self.init(enum: "macro", subnodes: params, multiline: true) + case .providedLibrary: + self.init(enum: "providedLibrary", subnodes: params, multiline: true) } } @@ -346,6 +348,13 @@ fileprivate extension SourceCodeFragment { } self.init(enum: "product", subnodes: params) + case .innerProduct(name: let name, condition: let condition): + params.append(SourceCodeFragment(key: "name", string: name)) + if let condition { + params.append(SourceCodeFragment(key: "condition", subnode: SourceCodeFragment(from: condition))) + } + self.init(enum: "product", subnodes: params) + case .byName(name: let name, condition: let condition): if let condition { params.append(SourceCodeFragment(key: "name", string: name)) @@ -525,6 +534,12 @@ fileprivate extension SourceCodeFragment { params.append(SourceCodeFragment(from: condition)) } self.init(enum: setting.kind.name, subnodes: params) + case .swiftLanguageVersion(let version): + params.append(SourceCodeFragment(from: version)) + if let condition = setting.condition { + params.append(SourceCodeFragment(from: condition)) + } + self.init(enum: setting.kind.name, subnodes: params) } } } @@ -677,6 +692,8 @@ extension TargetBuildSettingDescription.Kind { return "enableUpcomingFeature" case .enableExperimentalFeature: return "enableExperimentalFeature" + case .swiftLanguageVersion: + return "swiftLanguageVersion" } } } diff --git a/Sources/PackageModel/PackageReference.swift b/Sources/PackageModel/PackageReference.swift index ff6b448101c..d4cec83d3ac 100644 --- a/Sources/PackageModel/PackageReference.swift +++ b/Sources/PackageModel/PackageReference.swift @@ -203,7 +203,7 @@ extension PackageReference: CustomStringConvertible { extension PackageReference.Kind: Encodable { private enum CodingKeys: String, CodingKey { - case root, fileSystem, localSourceControl, remoteSourceControl, registry + case root, fileSystem, localSourceControl, remoteSourceControl, registry, providedLibrary } public func encode(to encoder: Encoder) throws { diff --git a/Sources/PackageModel/SwiftLanguageVersion.swift b/Sources/PackageModel/SwiftLanguageVersion.swift index 2aabc03c138..e7a1870c3a6 100644 --- a/Sources/PackageModel/SwiftLanguageVersion.swift +++ b/Sources/PackageModel/SwiftLanguageVersion.swift @@ -31,9 +31,12 @@ public struct SwiftLanguageVersion: Hashable, Sendable { /// Swift language version 5. public static let v5 = SwiftLanguageVersion(uncheckedString: "5") + /// Swift language version 6. + public static let v6 = SwiftLanguageVersion(uncheckedString: "6") + /// The list of known Swift language versions. public static let knownSwiftLanguageVersions = [ - v3, v4, v4_2, v5, + v3, v4, v4_2, v5, v6 ] /// The raw value of the language version. diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift index d66a2deea5c..2f979c8b26d 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDK.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -147,7 +147,31 @@ public struct SwiftSDK: Equatable { public var architectures: [String]? = nil /// Whether or not the receiver supports testing. - public let supportsTesting: Bool + @available(*, deprecated, message: "Use `xctestSupport` instead") + public var supportsTesting: Bool { + if case .supported = xctestSupport { + return true + } + return false + } + + /// Whether or not the receiver supports testing using XCTest. + @_spi(SwiftPMInternal) + public enum XCTestSupport: Sendable, Equatable { + /// XCTest is supported. + case supported + + /// XCTest is not supported. + /// + /// - Parameters: + /// - reason: A string explaining why XCTest is not supported. If + /// `nil`, no additional information is available. + case unsupported(reason: String?) + } + + /// Whether or not the receiver supports using XCTest. + @_spi(SwiftPMInternal) + public let xctestSupport: XCTestSupport /// Root directory path of the SDK used to compile for the target triple. @available(*, deprecated, message: "use `pathsConfiguration.sdkRootPath` instead") @@ -418,18 +442,44 @@ public struct SwiftSDK: Equatable { } /// Creates a Swift SDK with the specified properties. + @available(*, deprecated, message: "use `init(hostTriple:targetTriple:toolset:pathsConfiguration:xctestSupport:)` instead") + public init( + hostTriple: Triple? = nil, + targetTriple: Triple? = nil, + toolset: Toolset, + pathsConfiguration: PathsConfiguration, + supportsTesting: Bool + ) { + let xctestSupport: XCTestSupport + if supportsTesting { + xctestSupport = .supported + } else { + xctestSupport = .unsupported(reason: nil) + } + + self.init( + hostTriple: hostTriple, + targetTriple: targetTriple, + toolset: toolset, + pathsConfiguration: pathsConfiguration, + xctestSupport: xctestSupport + ) + } + + /// Creates a Swift SDK with the specified properties. + @_spi(SwiftPMInternal) public init( hostTriple: Triple? = nil, targetTriple: Triple? = nil, toolset: Toolset, pathsConfiguration: PathsConfiguration, - supportsTesting: Bool = true + xctestSupport: XCTestSupport = .supported ) { self.hostTriple = hostTriple self.targetTriple = targetTriple self.toolset = toolset self.pathsConfiguration = pathsConfiguration - self.supportsTesting = supportsTesting + self.xctestSupport = xctestSupport } /// Returns the bin directory for the host. @@ -465,10 +515,10 @@ public struct SwiftSDK: Equatable { ) throws -> SwiftSDK { let originalWorkingDirectory = originalWorkingDirectory ?? localFileSystem.currentWorkingDirectory // Select the correct binDir. - if ProcessEnv.vars["SWIFTPM_CUSTOM_BINDIR"] != nil { + if ProcessEnv.block["SWIFTPM_CUSTOM_BINDIR"] != nil { print("SWIFTPM_CUSTOM_BINDIR was deprecated in favor of SWIFTPM_CUSTOM_BIN_DIR") } - let customBinDir = (ProcessEnv.vars["SWIFTPM_CUSTOM_BIN_DIR"] ?? ProcessEnv.vars["SWIFTPM_CUSTOM_BINDIR"]) + let customBinDir = (ProcessEnv.block["SWIFTPM_CUSTOM_BIN_DIR"] ?? ProcessEnv.block["SWIFTPM_CUSTOM_BINDIR"]) .flatMap { try? AbsolutePath(validating: $0) } let binDir = try customBinDir ?? binDir ?? SwiftSDK.hostBinDir( fileSystem: localFileSystem, @@ -478,7 +528,7 @@ public struct SwiftSDK: Equatable { let sdkPath: AbsolutePath? #if os(macOS) // Get the SDK. - if let value = ProcessEnv.vars["SDKROOT"] { + if let value = ProcessEnv.block["SDKROOT"] { sdkPath = try AbsolutePath(validating: value) } else { // No value in env, so search for it. @@ -496,7 +546,7 @@ public struct SwiftSDK: Equatable { #endif // Compute common arguments for clang and swift. - let supportsTesting: Bool + let xctestSupport: XCTestSupport var extraCCFlags: [String] = [] var extraSwiftCFlags: [String] = [] #if os(macOS) @@ -506,13 +556,12 @@ public struct SwiftSDK: Equatable { extraSwiftCFlags += ["-F", sdkPaths.fwk.pathString] extraSwiftCFlags += ["-I", sdkPaths.lib.pathString] extraSwiftCFlags += ["-L", sdkPaths.lib.pathString] - supportsTesting = true + xctestSupport = .supported } catch { - supportsTesting = false - observabilityScope?.emit(warning: "could not determine XCTest paths: \(error)") + xctestSupport = .unsupported(reason: String(describing: error)) } #else - supportsTesting = true + xctestSupport = .supported #endif #if !os(Windows) @@ -528,7 +577,7 @@ public struct SwiftSDK: Equatable { rootPaths: [binDir] ), pathsConfiguration: .init(sdkRootPath: sdkPath), - supportsTesting: supportsTesting + xctestSupport: xctestSupport ) } @@ -599,8 +648,7 @@ public struct SwiftSDK: Equatable { } /// Computes the target Swift SDK for the given options. - @_spi(SwiftPMInternal) - public static func deriveTargetSwiftSDK( + package static func deriveTargetSwiftSDK( hostSwiftSDK: SwiftSDK, hostTriple: Triple, customCompileDestination: AbsolutePath? = nil, diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift index 8b743666244..a353c08ba53 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundle.swift @@ -33,6 +33,19 @@ public struct SwiftSDKBundle { public var name: String { path.basename } } +extension SwiftSDKBundle.Variant { + /// Whether the given host triple is supported by this SDK variant + internal func isSupporting(hostTriple: Triple) -> Bool { + guard let supportedTriples = metadata.supportedTriples else { + // No supportedTriples means the SDK can be universally usable + return true + } + return supportedTriples.contains(where: { variantTriple in + hostTriple.isRuntimeCompatible(with: variantTriple) + }) + } +} + extension [SwiftSDKBundle] { /// Select a Swift SDK with a given artifact ID from a `self` array of available Swift SDKs. /// - Parameters: @@ -40,7 +53,7 @@ extension [SwiftSDKBundle] { /// - hostTriple: triple of the machine on which the Swift SDK is building. /// - targetTriple: triple of the machine for which the Swift SDK is building. /// - Returns: ``SwiftSDK`` value with a given artifact ID, `nil` if none found. - public func selectSwiftSDK(id: String, hostTriple: Triple, targetTriple: Triple) -> SwiftSDK? { + public func selectSwiftSDK(id: String, hostTriple: Triple?, targetTriple: Triple) -> SwiftSDK? { for bundle in self { for (artifactID, variants) in bundle.artifacts { guard artifactID == id else { @@ -48,8 +61,10 @@ extension [SwiftSDKBundle] { } for variant in variants { - guard variant.metadata.supportedTriples.contains(hostTriple) else { - continue + if let hostTriple { + guard variant.isSupporting(hostTriple: hostTriple) else { + continue + } } return variant.swiftSDKs.first { $0.targetTriple == targetTriple } @@ -77,11 +92,7 @@ extension [SwiftSDKBundle] { for bundle in self { for (artifactID, variants) in bundle.artifacts { for variant in variants { - guard variant.metadata.supportedTriples.contains(where: { variantTriple in - hostTriple.isRuntimeCompatible(with: variantTriple) - }) else { - continue - } + guard variant.isSupporting(hostTriple: hostTriple) else { continue } for swiftSDK in variant.swiftSDKs { if artifactID == selector { diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift index f5e40bb2083..c1fcb320104 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKBundleStore.swift @@ -46,7 +46,7 @@ public final class SwiftSDKBundleStore { case let .noMatchingSwiftSDK(selector, hostTriple): return """ No Swift SDK found matching query `\(selector)` and host triple \ - `\(hostTriple.tripleString)`. Use `swift experimental-sdk list` command to see \ + `\(hostTriple.tripleString)`. Use `swift sdk list` command to see \ available Swift SDKs. """ } @@ -239,9 +239,10 @@ public final class SwiftSDKBundleStore { try await archiver.extract(from: bundlePath, to: extractionResultsDirectory) - guard let bundleName = try fileSystem.getDirectoryContents(extractionResultsDirectory).first, - bundleName.hasSuffix(".\(artifactBundleExtension)") - else { + guard let bundleName = try fileSystem.getDirectoryContents(extractionResultsDirectory).first(where: { + $0.hasSuffix(".\(artifactBundleExtension)") && + fileSystem.isDirectory(extractionResultsDirectory.appending($0)) + }) else { throw SwiftSDKError.invalidBundleArchive(bundlePath) } diff --git a/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift b/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift index 5002ce264dd..105fae09b6c 100644 --- a/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift +++ b/Sources/PackageModel/SwiftSDKs/SwiftSDKConfigurationStore.swift @@ -97,7 +97,7 @@ public final class SwiftSDKConfigurationStore { guard var swiftSDK = swiftSDKs.selectSwiftSDK( id: sdkID, - hostTriple: hostTriple, + hostTriple: nil, targetTriple: targetTriple ) else { return nil diff --git a/Sources/PackageModel/Target/BinaryTarget.swift b/Sources/PackageModel/Target/BinaryTarget.swift index cf61289be2b..8d42d7b7b78 100644 --- a/Sources/PackageModel/Target/BinaryTarget.swift +++ b/Sources/PackageModel/Target/BinaryTarget.swift @@ -41,6 +41,7 @@ public final class BinaryTarget: Target { dependencies: [], packageAccess: false, buildSettings: .init(), + buildSettingsDescription: [], pluginUsages: [], usesUnsafeFlags: false ) diff --git a/Sources/PackageModel/Target/ClangTarget.swift b/Sources/PackageModel/Target/ClangTarget.swift index d38f559a40d..e47b0baf516 100644 --- a/Sources/PackageModel/Target/ClangTarget.swift +++ b/Sources/PackageModel/Target/ClangTarget.swift @@ -53,6 +53,7 @@ public final class ClangTarget: Target { others: [AbsolutePath] = [], dependencies: [Target.Dependency] = [], buildSettings: BuildSettings.AssignmentTable = .init(), + buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [], usesUnsafeFlags: Bool ) throws { guard includeDir.isDescendantOfOrEqual(to: sources.root) else { @@ -76,6 +77,7 @@ public final class ClangTarget: Target { dependencies: dependencies, packageAccess: false, buildSettings: buildSettings, + buildSettingsDescription: buildSettingsDescription, pluginUsages: [], usesUnsafeFlags: usesUnsafeFlags ) diff --git a/Sources/PackageModel/Target/PluginTarget.swift b/Sources/PackageModel/Target/PluginTarget.swift index df195bcc799..84d04d0fee5 100644 --- a/Sources/PackageModel/Target/PluginTarget.swift +++ b/Sources/PackageModel/Target/PluginTarget.swift @@ -36,6 +36,7 @@ public final class PluginTarget: Target { dependencies: dependencies, packageAccess: packageAccess, buildSettings: .init(), + buildSettingsDescription: [], pluginUsages: [], usesUnsafeFlags: false ) diff --git a/Sources/PackageModel/Target/ProvidedLibraryTarget.swift b/Sources/PackageModel/Target/ProvidedLibraryTarget.swift new file mode 100644 index 00000000000..a8ed95b23ae --- /dev/null +++ b/Sources/PackageModel/Target/ProvidedLibraryTarget.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath + +/// Represents a target library that comes from a toolchain in prebuilt form. +public final class ProvidedLibraryTarget: Target { + public init( + name: String, + path: AbsolutePath + ) { + let sources = Sources(paths: [], root: path) + super.init( + name: name, + type: .providedLibrary, + path: sources.root, + sources: sources, + dependencies: [], + packageAccess: false, + buildSettings: .init(), + buildSettingsDescription: [], + pluginUsages: [], + usesUnsafeFlags: false + ) + } + + + public override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + } + + required public init(from decoder: Decoder) throws { + try super.init(from: decoder) + } +} diff --git a/Sources/PackageModel/Target/SwiftTarget.swift b/Sources/PackageModel/Target/SwiftTarget.swift index f23f79db6fe..566c90a52c7 100644 --- a/Sources/PackageModel/Target/SwiftTarget.swift +++ b/Sources/PackageModel/Target/SwiftTarget.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -23,7 +23,7 @@ public final class SwiftTarget: Target { } public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testDiscoverySrc: Sources) { - self.swiftVersion = .v5 + self.declaredSwiftVersions = [] super.init( name: name, @@ -33,13 +33,14 @@ public final class SwiftTarget: Target { dependencies: dependencies, packageAccess: packageAccess, buildSettings: .init(), + buildSettingsDescription: [], pluginUsages: [], usesUnsafeFlags: false ) } - /// The swift version of this target. - public let swiftVersion: SwiftLanguageVersion + /// The list of swift versions declared by the manifest. + public let declaredSwiftVersions: [SwiftLanguageVersion] public init( name: String, @@ -52,12 +53,13 @@ public final class SwiftTarget: Target { others: [AbsolutePath] = [], dependencies: [Target.Dependency] = [], packageAccess: Bool, - swiftVersion: SwiftLanguageVersion, + declaredSwiftVersions: [SwiftLanguageVersion] = [], buildSettings: BuildSettings.AssignmentTable = .init(), + buildSettingsDescription: [TargetBuildSettingDescription.Setting] = [], pluginUsages: [PluginUsage] = [], usesUnsafeFlags: Bool ) { - self.swiftVersion = swiftVersion + self.declaredSwiftVersions = declaredSwiftVersions super.init( name: name, potentialBundleName: potentialBundleName, @@ -70,13 +72,19 @@ public final class SwiftTarget: Target { dependencies: dependencies, packageAccess: packageAccess, buildSettings: buildSettings, + buildSettingsDescription: buildSettingsDescription, pluginUsages: pluginUsages, usesUnsafeFlags: usesUnsafeFlags ) } /// Create an executable Swift target from test entry point file. - public init(name: String, dependencies: [Target.Dependency], packageAccess: Bool, testEntryPointPath: AbsolutePath) { + public init( + name: String, + dependencies: [Target.Dependency], + packageAccess: Bool, + testEntryPointPath: AbsolutePath + ) { // Look for the first swift test target and use the same swift version // for linux main target. This will need to change if we move to a model // where we allow per target swift language version build settings. @@ -85,11 +93,23 @@ public final class SwiftTarget: Target { return target.type == .test }.flatMap { $0.target as? SwiftTarget } - // FIXME: This is not very correct but doesn't matter much in practice. // We need to select the latest Swift language version that can // satisfy the current tools version but there is not a good way to // do that currently. - self.swiftVersion = swiftTestTarget?.swiftVersion ?? SwiftLanguageVersion(string: String(SwiftVersion.current.major)) ?? .v4 + var buildSettings: BuildSettings.AssignmentTable = .init() + do { + let toolsSwiftVersion = swiftTestTarget?.buildSettings.assignments[.SWIFT_VERSION]? + .filter(\.default) + .filter(\.conditions.isEmpty) + .flatMap(\.values) + + var versionAssignment = BuildSettings.Assignment() + versionAssignment.values = toolsSwiftVersion ?? [String(SwiftVersion.current.major)] + + buildSettings.add(versionAssignment, for: .SWIFT_VERSION) + } + + self.declaredSwiftVersions = [] let sources = Sources(paths: [testEntryPointPath], root: testEntryPointPath.parentDirectory) super.init( @@ -99,25 +119,26 @@ public final class SwiftTarget: Target { sources: sources, dependencies: dependencies, packageAccess: packageAccess, - buildSettings: .init(), + buildSettings: buildSettings, + buildSettingsDescription: [], pluginUsages: [], usesUnsafeFlags: false ) } private enum CodingKeys: String, CodingKey { - case swiftVersion + case declaredSwiftVersions } - public override func encode(to encoder: Encoder) throws { + override public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(swiftVersion, forKey: .swiftVersion) + try container.encode(self.declaredSwiftVersions, forKey: .declaredSwiftVersions) try super.encode(to: encoder) } - required public init(from decoder: Decoder) throws { + public required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.swiftVersion = try container.decode(SwiftLanguageVersion.self, forKey: .swiftVersion) + self.declaredSwiftVersions = try container.decode([SwiftLanguageVersion].self, forKey: .declaredSwiftVersions) try super.init(from: decoder) } diff --git a/Sources/PackageModel/Target/SystemLibraryTarget.swift b/Sources/PackageModel/Target/SystemLibraryTarget.swift index beb99b670e5..a33af62afc5 100644 --- a/Sources/PackageModel/Target/SystemLibraryTarget.swift +++ b/Sources/PackageModel/Target/SystemLibraryTarget.swift @@ -43,6 +43,7 @@ public final class SystemLibraryTarget: Target { dependencies: [], packageAccess: false, buildSettings: .init(), + buildSettingsDescription: [], pluginUsages: [], usesUnsafeFlags: false ) diff --git a/Sources/PackageModel/Target/Target.swift b/Sources/PackageModel/Target/Target.swift index 541e37794de..00937ed1103 100644 --- a/Sources/PackageModel/Target/Target.swift +++ b/Sources/PackageModel/Target/Target.swift @@ -21,6 +21,7 @@ public class Target: PolymorphicCodableProtocol { SystemLibraryTarget.self, BinaryTarget.self, PluginTarget.self, + ProvidedLibraryTarget.self, ] /// The target kind. @@ -33,6 +34,7 @@ public class Target: PolymorphicCodableProtocol { case plugin case snippet case `macro` + case providedLibrary } /// A group a target belongs to that allows customizing access boundaries. A target is treated as @@ -75,6 +77,17 @@ public class Target: PolymorphicCodableProtocol { } } + /// A reference to a product within the same package as a target dependency. + public struct InnerProductReference: Codable { + /// The name of the product dependency. + public let name: String + + /// Creates an inner product reference instance. + public init(name: String) { + self.name = name + } + } + /// A target dependency to a target or product. public enum Dependency { /// A dependency referencing another target, with conditions. @@ -83,6 +96,9 @@ public class Target: PolymorphicCodableProtocol { /// A dependency referencing a product, with conditions. case product(_ product: ProductReference, conditions: [PackageCondition]) + /// A dependency referencing a product in the same package, with conditions. + case innerProduct(_ product: InnerProductReference, conditions: [PackageCondition]) + /// The target if the dependency is a target dependency. public var target: Target? { if case .target(let target, _) = self { @@ -101,6 +117,15 @@ public class Target: PolymorphicCodableProtocol { } } + /// The inner product reference if the dependency is an inner product dependency. + public var innerProduct: InnerProductReference? { + if case .innerProduct(let product, _) = self { + return product + } else { + return nil + } + } + /// The dependency conditions. public var conditions: [PackageCondition] { switch self { @@ -108,6 +133,8 @@ public class Target: PolymorphicCodableProtocol { return conditions case .product(_, let conditions): return conditions + case .innerProduct(_, let conditions): + return conditions } } @@ -118,6 +145,8 @@ public class Target: PolymorphicCodableProtocol { return target.name case .product(let product, _): return product.name + case .innerProduct(let product, _): + return product.name } } } @@ -131,7 +160,7 @@ public class Target: PolymorphicCodableProtocol { /// The name of the target. /// /// NOTE: This name is not the language-level target (i.e., the importable - /// name) name in many cases, instead use c99name if you need uniqueness. + /// name) name in many cases, instead use ``Target/c99name`` if you need uniqueness. public private(set) var name: String /// Module aliases needed to build this target. The key is an original name of a @@ -233,6 +262,9 @@ public class Target: PolymorphicCodableProtocol { /// The build settings assignments of this target. public let buildSettings: BuildSettings.AssignmentTable + @_spi(SwiftPMInternal) + public let buildSettingsDescription: [TargetBuildSettingDescription.Setting] + /// The usages of package plugins by this target. public let pluginUsages: [PluginUsage] @@ -251,6 +283,7 @@ public class Target: PolymorphicCodableProtocol { dependencies: [Target.Dependency], packageAccess: Bool, buildSettings: BuildSettings.AssignmentTable, + buildSettingsDescription: [TargetBuildSettingDescription.Setting], pluginUsages: [PluginUsage], usesUnsafeFlags: Bool ) { @@ -266,12 +299,27 @@ public class Target: PolymorphicCodableProtocol { self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() self.packageAccess = packageAccess self.buildSettings = buildSettings + self.buildSettingsDescription = buildSettingsDescription self.pluginUsages = pluginUsages self.usesUnsafeFlags = usesUnsafeFlags } private enum CodingKeys: String, CodingKey { - case name, potentialBundleName, defaultLocalization, platforms, type, path, sources, resources, ignored, others, packageAccess, buildSettings, pluginUsages, usesUnsafeFlags + case name + case potentialBundleName + case defaultLocalization + case platforms + case type + case path + case sources + case resources + case ignored + case others + case packageAccess + case buildSettings + case buildSettingsDescription + case pluginUsages + case usesUnsafeFlags } public func encode(to encoder: Encoder) throws { @@ -289,6 +337,7 @@ public class Target: PolymorphicCodableProtocol { try container.encode(others, forKey: .others) try container.encode(packageAccess, forKey: .packageAccess) try container.encode(buildSettings, forKey: .buildSettings) + try container.encode(buildSettingsDescription, forKey: .buildSettingsDescription) // FIXME: pluginUsages property is skipped on purpose as it points to // the actual target dependency object. try container.encode(usesUnsafeFlags, forKey: .usesUnsafeFlags) @@ -310,11 +359,23 @@ public class Target: PolymorphicCodableProtocol { self.c99name = self.name.spm_mangledToC99ExtendedIdentifier() self.packageAccess = try container.decode(Bool.self, forKey: .packageAccess) self.buildSettings = try container.decode(BuildSettings.AssignmentTable.self, forKey: .buildSettings) + self.buildSettingsDescription = try container.decode( + [TargetBuildSettingDescription.Setting].self, + forKey: .buildSettingsDescription + ) // FIXME: pluginUsages property is skipped on purpose as it points to // the actual target dependency object. self.pluginUsages = [] self.usesUnsafeFlags = try container.decode(Bool.self, forKey: .usesUnsafeFlags) } + + package var isEmbeddedSwiftTarget: Bool { + for case .enableExperimentalFeature("Embedded") in self.buildSettingsDescription.swiftSettings.map(\.kind) { + return true + } + + return false + } } extension Target: Hashable { @@ -347,3 +408,9 @@ public extension Sequence where Iterator.Element == Target { } } } + +extension [TargetBuildSettingDescription.Setting] { + package var swiftSettings: Self { + self.filter { $0.tool == .swift } + } +} diff --git a/Sources/PackageModel/Toolchain.swift b/Sources/PackageModel/Toolchain.swift index 63fe3a4efac..48764732c91 100644 --- a/Sources/PackageModel/Toolchain.swift +++ b/Sources/PackageModel/Toolchain.swift @@ -25,12 +25,6 @@ public protocol Toolchain { /// Path to `lib/swift_static` var swiftStaticResourcesPath: AbsolutePath? { get } - /// Whether the used compiler is from a open source development toolchain. - var isSwiftDevelopmentToolchain: Bool { get } - - /// Path to the Swift plugin server utility. - var swiftPluginServerPath: AbsolutePath? { get throws } - /// Path containing the macOS Swift stdlib. var macosSwiftStdlib: AbsolutePath { get throws } @@ -43,6 +37,9 @@ public protocol Toolchain { /// Configuration from the used toolchain. var installedSwiftPMConfiguration: InstalledSwiftPMConfiguration { get } + /// Metadata for libraries provided by the used toolchain. + var providedLibraries: [ProvidedLibrary] { get } + /// The root path to the Swift SDK used by this toolchain. var sdkRootPath: AbsolutePath? { get } diff --git a/Sources/PackageModel/ToolsVersion.swift b/Sources/PackageModel/ToolsVersion.swift index 5fe51fe4bf4..571882a2126 100644 --- a/Sources/PackageModel/ToolsVersion.swift +++ b/Sources/PackageModel/ToolsVersion.swift @@ -31,7 +31,7 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable { public static let v5_8 = ToolsVersion(version: "5.8.0") public static let v5_9 = ToolsVersion(version: "5.9.0") public static let v5_10 = ToolsVersion(version: "5.10.0") - public static let v5_11 = ToolsVersion(version: "5.11.0") + public static let v6_0 = ToolsVersion(version: "6.0.0") public static let vNext = ToolsVersion(version: "999.0.0") /// The current tools version in use. @@ -182,10 +182,11 @@ public struct ToolsVersion: Equatable, Hashable, Codable, Sendable { // Otherwise, use 4.2 return .v4_2 - - default: - // Anything above 4 major version uses version 5. + case 5: return .v5 + default: + // Anything above 5 major version uses version 6. + return .v6 } } } diff --git a/Sources/PackageModel/UserToolchain.swift b/Sources/PackageModel/UserToolchain.swift index fbb9013c3f4..eedce88bd60 100644 --- a/Sources/PackageModel/UserToolchain.swift +++ b/Sources/PackageModel/UserToolchain.swift @@ -84,10 +84,10 @@ public final class UserToolchain: Toolchain { private let environment: EnvironmentVariables - public let isSwiftDevelopmentToolchain: Bool - public let installedSwiftPMConfiguration: InstalledSwiftPMConfiguration + public let providedLibraries: [ProvidedLibrary] + /// Returns the runtime library for the given sanitizer. public func runtimeLibrary(for sanitizer: Sanitizer) throws -> AbsolutePath { // FIXME: This is only for SwiftPM development time support. It is OK @@ -484,7 +484,8 @@ public final class UserToolchain: Toolchain { environment: EnvironmentVariables = .process(), searchStrategy: SearchStrategy = .default, customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil, - customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil + customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil, + customProvidedLibraries: [ProvidedLibrary]? = nil ) throws { self.swiftSDK = swiftSDK self.environment = environment @@ -511,31 +512,35 @@ public final class UserToolchain: Toolchain { self.swiftCompilerPath = swiftCompilers.compile self.architectures = swiftSDK.architectures - #if canImport(Darwin) - let toolchainPlistPath = self.swiftCompilerPath.parentDirectory.parentDirectory.parentDirectory - .appending(component: "Info.plist") - if localFileSystem.exists(toolchainPlistPath), let toolchainPlist = try? NSDictionary( - contentsOf: URL(fileURLWithPath: toolchainPlistPath.pathString), - error: () - ), let overrideBuildSettings = toolchainPlist["OverrideBuildSettings"] as? NSDictionary, - let isSwiftDevelopmentToolchainStringValue = overrideBuildSettings["SWIFT_DEVELOPMENT_TOOLCHAIN"] as? String { - self.isSwiftDevelopmentToolchain = isSwiftDevelopmentToolchainStringValue == "YES" + if let customInstalledSwiftPMConfiguration { + self.installedSwiftPMConfiguration = customInstalledSwiftPMConfiguration } else { - self.isSwiftDevelopmentToolchain = false + let path = swiftCompilerPath.parentDirectory.parentDirectory.appending(components: [ + "share", "pm", "config.json", + ]) + self.installedSwiftPMConfiguration = try Self.loadJSONResource( + config: path, + type: InstalledSwiftPMConfiguration.self, + default: InstalledSwiftPMConfiguration.default) } - #else - self.isSwiftDevelopmentToolchain = false - #endif - if let customInstalledSwiftPMConfiguration { - self.installedSwiftPMConfiguration = customInstalledSwiftPMConfiguration + if let customProvidedLibraries { + self.providedLibraries = customProvidedLibraries } else { - let path = self.swiftCompilerPath.parentDirectory.parentDirectory.appending(components: ["share", "pm", "config.json"]) - if localFileSystem.exists(path) { - self.installedSwiftPMConfiguration = try JSONDecoder.makeWithDefaults().decode(path: path, fileSystem: localFileSystem, as: InstalledSwiftPMConfiguration.self) - } else { - // We *could* eventually make this an error, but not for a few releases. - self.installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default + let path = swiftCompilerPath.parentDirectory.parentDirectory.appending(components: [ + "share", "pm", "provided-libraries.json", + ]) + self.providedLibraries = try Self.loadJSONResource( + config: path, + type: [LibraryMetadata].self, + default: [] + ).map { + .init( + location: path.parentDirectory.appending(component: $0.productName), + metadata: $0 + ) + }.filter { + localFileSystem.isDirectory($0.location) } } @@ -856,13 +861,18 @@ public final class UserToolchain: Toolchain { configuration.xctestPath } - private let _swiftPluginServerPath = ThreadSafeBox() - - public var swiftPluginServerPath: AbsolutePath? { - get throws { - try _swiftPluginServerPath.memoize { - return try Self.derivePluginServerPath(triple: self.targetTriple) - } + private static func loadJSONResource( + config: AbsolutePath, type: T.Type, `default`: T + ) + throws -> T + { + if localFileSystem.exists(config) { + return try JSONDecoder.makeWithDefaults().decode( + path: config, + fileSystem: localFileSystem, + as: type) } + + return `default` } } diff --git a/Sources/PackageModelSyntax/AddPackageDependency.swift b/Sources/PackageModelSyntax/AddPackageDependency.swift new file mode 100644 index 00000000000..09231a49293 --- /dev/null +++ b/Sources/PackageModelSyntax/AddPackageDependency.swift @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageLoading +import PackageModel +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder + +/// Add a package dependency to a manifest's source code. +public struct AddPackageDependency { + /// The set of argument labels that can occur after the "dependencies" + /// argument in the Package initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterDependencies: Set = [ + "targets", + "swiftLanguageVersions", + "cLanguageStandard", + "cxxLanguageStandard" + ] + + /// Produce the set of source edits needed to add the given package + /// dependency to the given manifest file. + public static func addPackageDependency( + _ dependency: PackageDependency, + to manifest: SourceFileSyntax + ) throws -> PackageEditResult { + // Make sure we have a suitable tools version in the manifest. + try manifest.checkEditManifestToolsVersion() + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + let newPackageCall = try addPackageDependencyLocal( + dependency, to: packageCall + ) + + return PackageEditResult( + manifestEdits: [ + .replace(packageCall, with: newPackageCall.description) + ] + ) + } + + /// Implementation of adding a package dependency to an existing call. + static func addPackageDependencyLocal( + _ dependency: PackageDependency, + to packageCall: FunctionCallExprSyntax + ) throws -> FunctionCallExprSyntax { + try packageCall.appendingToArrayArgument( + label: "dependencies", + trailingLabels: Self.argumentLabelsAfterDependencies, + newElement: dependency.asSyntax() + ) + } +} diff --git a/Sources/PackageModelSyntax/AddProduct.swift b/Sources/PackageModelSyntax/AddProduct.swift new file mode 100644 index 00000000000..3e058232f3a --- /dev/null +++ b/Sources/PackageModelSyntax/AddProduct.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import PackageModel +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder + +/// Add a product to the manifest's source code. +public struct AddProduct { + /// The set of argument labels that can occur after the "products" + /// argument in the Package initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterProducts: Set = [ + "dependencies", + "targets", + "swiftLanguageVersions", + "cLanguageStandard", + "cxxLanguageStandard" + ] + + /// Produce the set of source edits needed to add the given package + /// dependency to the given manifest file. + public static func addProduct( + _ product: ProductDescription, + to manifest: SourceFileSyntax + ) throws -> PackageEditResult { + // Make sure we have a suitable tools version in the manifest. + try manifest.checkEditManifestToolsVersion() + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + let newPackageCall = try packageCall.appendingToArrayArgument( + label: "products", + trailingLabels: argumentLabelsAfterProducts, + newElement: product.asSyntax() + ) + + return PackageEditResult( + manifestEdits: [ + .replace(packageCall, with: newPackageCall.description) + ] + ) + } +} diff --git a/Sources/PackageModelSyntax/AddTarget.swift b/Sources/PackageModelSyntax/AddTarget.swift new file mode 100644 index 00000000000..23585174bf8 --- /dev/null +++ b/Sources/PackageModelSyntax/AddTarget.swift @@ -0,0 +1,415 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder +import struct TSCUtility.Version + +/// Add a target to a manifest's source code. +public struct AddTarget { + /// The set of argument labels that can occur after the "targets" + /// argument in the Package initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterTargets: Set = [ + "swiftLanguageVersions", + "cLanguageStandard", + "cxxLanguageStandard" + ] + + /// The kind of test harness to use. This isn't part of the manifest + /// itself, but is used to guide the generation process. + public enum TestHarness: String, Codable { + /// Don't use any library + case none + + /// Create a test using the XCTest library. + case xctest + + /// Create a test using the swift-testing package. + case swiftTesting = "swift-testing" + + /// The default testing library to use. + public static var `default`: TestHarness = .xctest + } + + /// Additional configuration information to guide the package editing + /// process. + public struct Configuration { + /// The test harness to use. + public var testHarness: TestHarness + + public init(testHarness: TestHarness = .default) { + self.testHarness = testHarness + } + } + + /// Add the given target to the manifest, producing a set of edit results + /// that updates the manifest and adds some source files to stub out the + /// new target. + public static func addTarget( + _ target: TargetDescription, + to manifest: SourceFileSyntax, + configuration: Configuration = .init(), + installedSwiftPMConfiguration: InstalledSwiftPMConfiguration = .default + ) throws -> PackageEditResult { + // Make sure we have a suitable tools version in the manifest. + try manifest.checkEditManifestToolsVersion() + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + // Create a mutable version of target to which we can add more + // content when needed. + var target = target + + // Add dependencies needed for various targets. + switch target.type { + case .macro: + // Macro targets need to depend on a couple of libraries from + // SwiftSyntax. + target.dependencies.append(contentsOf: macroTargetDependencies) + + case .test where configuration.testHarness == .swiftTesting: + // Testing targets using swift-testing need to depend on + // SwiftTesting from the swift-testing package. + target.dependencies.append(contentsOf: swiftTestingTestTargetDependencies) + + default: + break; + } + + var newPackageCall = try packageCall.appendingToArrayArgument( + label: "targets", + trailingLabels: Self.argumentLabelsAfterTargets, + newElement: target.asSyntax() + ) + + let outerDirectory: String? = switch target.type { + case .binary, .plugin, .system, .providedLibrary: nil + case .executable, .regular, .macro: "Sources" + case .test: "Tests" + } + + guard let outerDirectory else { + return PackageEditResult( + manifestEdits: [ + .replace(packageCall, with: newPackageCall.description) + ] + ) + } + + let outerPath = try RelativePath(validating: outerDirectory) + + /// The set of auxiliary files this refactoring will create. + var auxiliaryFiles: AuxiliaryFiles = [] + + // Add the primary source file. Every target type has this. + addPrimarySourceFile( + outerPath: outerPath, + target: target, + configuration: configuration, + to: &auxiliaryFiles + ) + + // Perform any other actions that are needed for this target type. + var extraManifestEdits: [SourceEdit] = [] + switch target.type { + case .macro: + addProvidedMacrosSourceFile( + outerPath: outerPath, + target: target, + to: &auxiliaryFiles + ) + + if !manifest.description.contains("swift-syntax") { + newPackageCall = try AddPackageDependency + .addPackageDependencyLocal( + .swiftSyntax( + configuration: installedSwiftPMConfiguration + ), + to: newPackageCall + ) + + // Look for the first import declaration and insert an + // import of `CompilerPluginSupport` there. + let newImport = "import CompilerPluginSupport\n" + for node in manifest.statements { + if let importDecl = node.item.as(ImportDeclSyntax.self) { + let insertPos = importDecl + .positionAfterSkippingLeadingTrivia + extraManifestEdits.append( + SourceEdit( + range: insertPos.. ExpressionMacro + /// @attached(member) macro --> MemberMacro + } + """ + + case .test: + switch configuration.testHarness { + case .none: + """ + \(imports) + // Test code here + """ + + case .xctest: + """ + \(imports) + class \(raw: target.name): XCTestCase { + func test\(raw: target.name)() { + XCTAssertEqual(42, 17 + 25) + } + } + """ + + case .swiftTesting: + """ + \(imports) + @Suite + struct \(raw: target.name)Tests { + @Test("\(raw: target.name) tests") + func example() { + #expect(42 == 17 + 25) + } + } + """ + } + + case .regular: + """ + \(imports) + """ + + case .executable: + """ + \(imports) + @main + struct \(raw: target.name)Main { + static func main() { + print("Hello, world") + } + } + """ + } + + auxiliaryFiles.addSourceFile( + path: sourceFilePath, + sourceCode: sourceFileText + ) + } + + /// Add a file that introduces the main entrypoint and provided macros + /// for a macro target. + fileprivate static func addProvidedMacrosSourceFile( + outerPath: RelativePath, + target: TargetDescription, + to auxiliaryFiles: inout AuxiliaryFiles + ) { + auxiliaryFiles.addSourceFile( + path: outerPath.appending( + components: [target.name, "ProvidedMacros.swift"] + ), + sourceCode: """ + import SwiftCompilerPlugin + + @main + struct \(raw: target.name)Macros: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + \(raw: target.name).self, + ] + } + """ + ) + } +} + +fileprivate extension TargetDescription.Dependency { + /// Retrieve the name of the dependency + var name: String { + switch self { + case .target(name: let name, condition: _), + .byName(name: let name, condition: _), + .product(name: let name, package: _, moduleAliases: _, condition: _), + .innerProduct(name: let name, condition: _): + name + } + } +} + +/// The array of auxiliary files that can be added by a package editing +/// operation. +fileprivate typealias AuxiliaryFiles = [(RelativePath, SourceFileSyntax)] + +fileprivate extension AuxiliaryFiles { + /// Add a source file to the list of auxiliary files. + mutating func addSourceFile( + path: RelativePath, + sourceCode: SourceFileSyntax + ) { + self.append((path, sourceCode)) + } +} + +/// The set of dependencies we need to introduce to a newly-created macro +/// target. +fileprivate let macroTargetDependencies: [TargetDescription.Dependency] = [ + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), +] + +/// The package dependency for swift-syntax, for use in macros. +fileprivate extension PackageDependency { + /// Source control URL for the swift-syntax package. + static var swiftSyntaxURL: SourceControlURL { + "https://github.com/apple/swift-syntax.git" + } + + /// Package dependency on the swift-syntax package. + static func swiftSyntax( + configuration: InstalledSwiftPMConfiguration + ) -> PackageDependency { + let swiftSyntaxVersionDefault = configuration + .swiftSyntaxVersionForMacroTemplate + let swiftSyntaxVersion = Version(swiftSyntaxVersionDefault.description)! + + return .sourceControl( + identity: PackageIdentity(url: swiftSyntaxURL), + nameForTargetDependencyResolutionOnly: nil, + location: .remote(swiftSyntaxURL), + requirement: .range(.upToNextMajor(from: swiftSyntaxVersion)), + productFilter: .everything + ) + } +} + +/// The set of dependencies we need to introduce to a newly-created macro +/// target. +fileprivate let swiftTestingTestTargetDependencies: [TargetDescription.Dependency] = [ + .product(name: "Testing", package: "swift-testing"), +] + + +/// The package dependency for swift-testing, for use in test files. +fileprivate extension PackageDependency { + /// Source control URL for the swift-syntax package. + static var swiftTestingURL: SourceControlURL { + "https://github.com/apple/swift-testing.git" + } + + /// Package dependency on the swift-testing package. + static func swiftTesting( + configuration: InstalledSwiftPMConfiguration + ) -> PackageDependency { + let swiftTestingVersionDefault = + configuration.swiftTestingVersionForTestTemplate + let swiftTestingVersion = Version(swiftTestingVersionDefault.description)! + + return .sourceControl( + identity: PackageIdentity(url: swiftTestingURL), + nameForTargetDependencyResolutionOnly: nil, + location: .remote(swiftTestingURL), + requirement: .range(.upToNextMajor(from: swiftTestingVersion)), + productFilter: .everything + ) + } +} diff --git a/Sources/PackageModelSyntax/AddTargetDependency.swift b/Sources/PackageModelSyntax/AddTargetDependency.swift new file mode 100644 index 00000000000..fde0a5e69e6 --- /dev/null +++ b/Sources/PackageModelSyntax/AddTargetDependency.swift @@ -0,0 +1,103 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageLoading +import PackageModel +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder + +/// Add a target dependency to a manifest's source code. +public struct AddTargetDependency { + /// The set of argument labels that can occur after the "dependencies" + /// argument in the various target initializers. + /// + /// TODO: Could we generate this from the the PackageDescription module, so + /// we don't have keep it up-to-date manually? + private static let argumentLabelsAfterDependencies: Set = [ + "path", + "exclude", + "sources", + "resources", + "publicHeadersPath", + "packageAccess", + "cSettings", + "cxxSettings", + "swiftSettings", + "linkerSettings", + "plugins", + ] + + /// Produce the set of source edits needed to add the given target + /// dependency to the given manifest file. + public static func addTargetDependency( + _ dependency: TargetDescription.Dependency, + targetName: String, + to manifest: SourceFileSyntax + ) throws -> PackageEditResult { + // Make sure we have a suitable tools version in the manifest. + try manifest.checkEditManifestToolsVersion() + + guard let packageCall = manifest.findCall(calleeName: "Package") else { + throw ManifestEditError.cannotFindPackage + } + + // Dig out the array of targets. + guard let targetsArgument = packageCall.findArgument(labeled: "targets"), + let targetArray = targetsArgument.expression.findArrayArgument() else { + throw ManifestEditError.cannotFindTargets + } + + // Look for a call whose name is a string literal matching the + // requested target name. + func matchesTargetCall(call: FunctionCallExprSyntax) -> Bool { + guard let nameArgument = call.findArgument(labeled: "name") else { + return false + } + + guard let stringLiteral = nameArgument.expression.as(StringLiteralExprSyntax.self), + let literalValue = stringLiteral.representedLiteralValue else { + return false + } + + return literalValue == targetName + } + + guard let targetCall = FunctionCallExprSyntax.findFirst(in: targetArray, matching: matchesTargetCall) else { + throw ManifestEditError.cannotFindTarget(targetName: targetName) + } + + let newTargetCall = try addTargetDependencyLocal( + dependency, to: targetCall + ) + + return PackageEditResult( + manifestEdits: [ + .replace(targetCall, with: newTargetCall.description) + ] + ) + } + + /// Implementation of adding a target dependency to an existing call. + static func addTargetDependencyLocal( + _ dependency: TargetDescription.Dependency, + to targetCall: FunctionCallExprSyntax + ) throws -> FunctionCallExprSyntax { + try targetCall.appendingToArrayArgument( + label: "dependencies", + trailingLabels: Self.argumentLabelsAfterDependencies, + newElement: dependency.asSyntax() + ) + } +} + diff --git a/Sources/PackageModelSyntax/CMakeLists.txt b/Sources/PackageModelSyntax/CMakeLists.txt new file mode 100644 index 00000000000..c034d8d1705 --- /dev/null +++ b/Sources/PackageModelSyntax/CMakeLists.txt @@ -0,0 +1,44 @@ +# This source file is part of the Swift open source project +# +# Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for Swift project authors + +add_library(PackageModelSyntax + AddPackageDependency.swift + AddProduct.swift + AddTarget.swift + AddTargetDependency.swift + ManifestEditError.swift + ManifestSyntaxRepresentable.swift + PackageDependency+Syntax.swift + PackageEditResult.swift + ProductDescription+Syntax.swift + SyntaxEditUtils.swift + TargetDescription+Syntax.swift +) + +target_link_libraries(PackageModelSyntax PUBLIC + Basics + PackageLoading + PackageModel + + SwiftSyntax::SwiftBasicFormat + SwiftSyntax::SwiftDiagnostics + SwiftSyntax::SwiftIDEUtils + SwiftSyntax::SwiftParser + SwiftSyntax::SwiftSyntax + SwiftSyntax::SwiftSyntaxBuilder +) + +# NOTE(compnerd) workaround for CMake not setting up include flags yet +set_target_properties(PackageModelSyntax PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +install(TARGETS PackageModelSyntax + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) +set_property(GLOBAL APPEND PROPERTY SwiftPM_EXPORTS PackageModelSyntax) diff --git a/Sources/PackageModelSyntax/ManifestEditError.swift b/Sources/PackageModelSyntax/ManifestEditError.swift new file mode 100644 index 00000000000..aaaf3351166 --- /dev/null +++ b/Sources/PackageModelSyntax/ManifestEditError.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import PackageLoading +import PackageModel +import SwiftSyntax + +/// An error describing problems that can occur when attempting to edit a +/// package manifest programattically. +package enum ManifestEditError: Error { + case cannotFindPackage + case cannotFindTargets + case cannotFindTarget(targetName: String) + case cannotFindArrayLiteralArgument(argumentName: String, node: Syntax) + case oldManifest(ToolsVersion) +} + +extension ToolsVersion { + /// The minimum tools version of the manifest file that we support edit + /// operations on. + static let minimumManifestEditVersion = v5_5 +} + +extension ManifestEditError: CustomStringConvertible { + package var description: String { + switch self { + case .cannotFindPackage: + "invalid manifest: unable to find 'Package' declaration" + case .cannotFindTargets: + "unable to find package targets in manifest" + case .cannotFindTarget(targetName: let name): + "unable to find target named '\(name)' in package" + case .cannotFindArrayLiteralArgument(argumentName: let name, node: _): + "unable to find array literal for '\(name)' argument" + case .oldManifest(let version): + "package manifest version \(version) is too old: please update to manifest version \(ToolsVersion.minimumManifestEditVersion) or newer" + } + } +} + +extension SourceFileSyntax { + /// Check that the manifest described by this source file meets the minimum + /// tools version requirements for editing the manifest. + func checkEditManifestToolsVersion() throws { + let toolsVersion = try ToolsVersionParser.parse(utf8String: description) + if toolsVersion < ToolsVersion.minimumManifestEditVersion { + throw ManifestEditError.oldManifest(toolsVersion) + } + } +} diff --git a/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift b/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift new file mode 100644 index 00000000000..8af1d379c18 --- /dev/null +++ b/Sources/PackageModelSyntax/ManifestSyntaxRepresentable.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import SwiftSyntax + +/// Describes an entity in the package model that can be represented as +/// a syntax node. +protocol ManifestSyntaxRepresentable { + /// The most specific kind of syntax node that best describes this entity + /// in the manifest. + /// + /// There might be other kinds of syntax nodes that can also represent + /// the syntax, but this is the one that a canonical manifest will use. + /// As an example, a package dependency is usually expressed as, e.g., + /// .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1") + /// + /// However, there could be other forms, e.g., this is also valid: + /// Package.Dependency.package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1") + associatedtype PreferredSyntax: SyntaxProtocol + + /// Provides a suitable syntax node to describe this entity in the package + /// model. + /// + /// The resulting syntax is a fragment that describes just this entity, + /// and it's enclosing entity will need to understand how to fit it in. + /// For example, a `PackageDependency` entity would map to syntax for + /// something like + /// .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1") + func asSyntax() -> PreferredSyntax +} + +extension String: ManifestSyntaxRepresentable { + typealias PreferredSyntax = ExprSyntax + + func asSyntax() -> ExprSyntax { "\(literal: self)" } +} diff --git a/Sources/PackageModelSyntax/PackageDependency+Syntax.swift b/Sources/PackageModelSyntax/PackageDependency+Syntax.swift new file mode 100644 index 00000000000..cf870669903 --- /dev/null +++ b/Sources/PackageModelSyntax/PackageDependency+Syntax.swift @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SwiftSyntax +import SwiftParser +import struct TSCUtility.Version + +extension PackageDependency: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + switch self { + case .fileSystem(let filesystem): filesystem.asSyntax() + case .sourceControl(let sourceControl): sourceControl.asSyntax() + case .registry(let registry): registry.asSyntax() + } + } +} + +extension PackageDependency.FileSystem: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + fatalError() + } +} + +extension PackageDependency.SourceControl: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + // TODO: Not handling identity, nameForTargetDependencyResolutionOnly, + // or productFilter yet. + switch location { + case .local(let path): + ".package(path: \(literal: path.description), \(requirement.asSyntax()))" + case .remote(let url): + ".package(url: \(literal: url.description), \(requirement.asSyntax()))" + } + } +} + +extension PackageDependency.Registry: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + fatalError() + } +} + +extension PackageDependency.SourceControl.Requirement: ManifestSyntaxRepresentable { + func asSyntax() -> LabeledExprSyntax { + switch self { + case .exact(let version): + LabeledExprSyntax( + label: "exact", + expression: version.asSyntax() + ) + + case .range(let range) where range == .upToNextMajor(from: range.lowerBound): + LabeledExprSyntax( + label: "from", + expression: range.lowerBound.asSyntax() + ) + + case .range(let range): + LabeledExprSyntax( + expression: "\(range.lowerBound.asSyntax())..<\(range.upperBound.asSyntax())" as ExprSyntax + ) + + case .revision(let revision): + LabeledExprSyntax( + label: "revision", + expression: "\(literal: revision)" as ExprSyntax + ) + + case .branch(let branch): + LabeledExprSyntax( + label: "branch", + expression: "\(literal: branch)" as ExprSyntax + ) + } + } +} + +extension Version: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + return "\(literal: description)" + } +} diff --git a/Sources/PackageModelSyntax/PackageEditResult.swift b/Sources/PackageModelSyntax/PackageEditResult.swift new file mode 100644 index 00000000000..6de70765eeb --- /dev/null +++ b/Sources/PackageModelSyntax/PackageEditResult.swift @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +@_spi(FixItApplier) import SwiftIDEUtils +import SwiftSyntax + +/// The result of editing a package, including any edits to the package +/// manifest and any new files that are introduced. +public struct PackageEditResult { + /// Edits to perform to the package manifest. + public var manifestEdits: [SourceEdit] = [] + + /// Auxiliary files to write. + public var auxiliaryFiles: [(RelativePath, SourceFileSyntax)] = [] +} + +extension PackageEditResult { + /// Apply the edits for the given manifest to the specified file system, + /// updating the manifest to the given manifest + public func applyEdits( + to filesystem: any FileSystem, + manifest: SourceFileSyntax, + manifestPath: AbsolutePath, + verbose: Bool + ) throws { + let rootPath = manifestPath.parentDirectory + + // Update the manifest + if verbose { + print("Updating package manifest at \(manifestPath.relative(to: rootPath))...", terminator: "") + } + + let updatedManifestSource = FixItApplier.apply( + edits: manifestEdits, + to: manifest + ) + try filesystem.writeFileContents( + manifestPath, + string: updatedManifestSource + ) + if verbose { + print(" done.") + } + + // Write all of the auxiliary files. + for (auxiliaryFileRelPath, auxiliaryFileSyntax) in auxiliaryFiles { + // If the file already exists, skip it. + let filePath = rootPath.appending(auxiliaryFileRelPath) + if filesystem.exists(filePath) { + if verbose { + print("Skipping \(filePath.relative(to: rootPath)) because it already exists.") + } + + continue + } + + // If the directory does not exist yet, create it. + let fileDir = filePath.parentDirectory + if !filesystem.exists(fileDir) { + if verbose { + print("Creating directory \(fileDir.relative(to: rootPath))...", terminator: "") + } + + try filesystem.createDirectory(fileDir, recursive: true) + + if verbose { + print(" done.") + } + } + + // Write the file. + if verbose { + print("Writing \(filePath.relative(to: rootPath))...", terminator: "") + } + + try filesystem.writeFileContents( + filePath, + string: auxiliaryFileSyntax.description + ) + + if verbose { + print(" done.") + } + } + } + +} diff --git a/Sources/PackageModelSyntax/ProductDescription+Syntax.swift b/Sources/PackageModelSyntax/ProductDescription+Syntax.swift new file mode 100644 index 00000000000..eed6650dfce --- /dev/null +++ b/Sources/PackageModelSyntax/ProductDescription+Syntax.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SwiftSyntax +import SwiftParser + +extension ProductDescription: ManifestSyntaxRepresentable { + /// The function name in the package manifest. + /// + /// Some of these are actually invalid, but it's up to the caller + /// to check the precondition. + private var functionName: String { + switch type { + case .executable: "executable" + case .library(_): "library" + case .macro: "macro" + case .plugin: "plugin" + case .snippet: "snippet" + case .test: "test" + } + } + + func asSyntax() -> ExprSyntax { + var arguments: [LabeledExprSyntax] = [] + arguments.append(label: "name", stringLiteral: name) + + // Libraries have a type. + if case .library(let libraryType) = type { + switch libraryType { + case .automatic: + break + + case .dynamic, .static: + arguments.append( + label: "type", + expression: ".\(raw: libraryType.rawValue)" + ) + } + } + + arguments.appendIfNonEmpty( + label: "targets", + arrayLiteral: targets + ) + + let separateParen: String = arguments.count > 1 ? "\n" : "" + let argumentsSyntax = LabeledExprListSyntax(arguments) + return ".\(raw: functionName)(\(argumentsSyntax)\(raw: separateParen))" + } +} diff --git a/Sources/PackageModelSyntax/SyntaxEditUtils.swift b/Sources/PackageModelSyntax/SyntaxEditUtils.swift new file mode 100644 index 00000000000..df64aa4c2d3 --- /dev/null +++ b/Sources/PackageModelSyntax/SyntaxEditUtils.swift @@ -0,0 +1,519 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SwiftBasicFormat +import SwiftSyntax +import SwiftParser + +/// Default indent when we have to introduce indentation but have no context +/// to get it right. +let defaultIndent = TriviaPiece.spaces(4) + +extension Trivia { + /// Determine whether this trivia has newlines or not. + var hasNewlines: Bool { + contains(where: \.isNewline) + } + + /// Produce trivia from the last newline to the end, dropping anything + /// prior to that. + func onlyLastLine() -> Trivia { + guard let lastNewline = pieces.lastIndex(where: { $0.isNewline }) else { + return self + } + + return Trivia(pieces: pieces[lastNewline...]) + } +} + +/// Syntax walker to find the first occurrence of a given node kind that +/// matches a specific predicate. +private class FirstNodeFinder: SyntaxAnyVisitor { + var predicate: (Node) -> Bool + var found: Node? = nil + + init(predicate: @escaping (Node) -> Bool) { + self.predicate = predicate + super.init(viewMode: .sourceAccurate) + } + + override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { + if found != nil { + return .skipChildren + } + + if let matchedNode = node.as(Node.self), predicate(matchedNode) { + found = matchedNode + return .skipChildren + } + + return .visitChildren + } +} + +extension SyntaxProtocol { + /// Find the first node of the Self type that matches the given predicate. + static func findFirst( + in node: some SyntaxProtocol, + matching predicate: (Self) -> Bool + ) -> Self? { + withoutActuallyEscaping(predicate) { escapingPredicate in + let visitor = FirstNodeFinder(predicate: escapingPredicate) + visitor.walk(node) + return visitor.found + } + } +} + +extension FunctionCallExprSyntax { + /// Check whether this call expression has a callee that is a reference + /// to a declaration with the given name. + func hasCallee(named name: String) -> Bool { + guard let calleeDeclRef = calledExpression.as(DeclReferenceExprSyntax.self) else { + return false + } + + return calleeDeclRef.baseName.text == name + } + + /// Find a call argument based on its label. + func findArgument(labeled label: String) -> LabeledExprSyntax? { + arguments.first { $0.label?.text == label } + } + + /// Find a call argument index based on its label. + func findArgumentIndex(labeled label: String) -> LabeledExprListSyntax.Index? { + arguments.firstIndex { $0.label?.text == label } + } +} + +extension LabeledExprListSyntax { + /// Find the index at which the one would insert a new argument given + /// the set of argument labels that could come after the argument we + /// want to insert. + func findArgumentInsertionPosition( + labelsAfter: Set + ) -> SyntaxChildrenIndex { + firstIndex { + guard let label = $0.label else { + return false + } + + return labelsAfter.contains(label.text) + } ?? endIndex + } + + /// Form a new argument list that inserts a new argument at the specified + /// position in this argument list. + /// + /// This operation will attempt to introduce trivia to match the + /// surrounding context where possible. The actual argument will be + /// created by the `generator` function, which is provided with leading + /// trivia and trailing comma it should use to match the surrounding + /// context. + func insertingArgument( + at position: SyntaxChildrenIndex, + generator: (Trivia, TokenSyntax?) -> LabeledExprSyntax + ) -> LabeledExprListSyntax { + // Turn the arguments into an array so we can manipulate them. + var arguments = Array(self) + + let positionIdx = distance(from: startIndex, to: position) + + let commaToken = TokenSyntax.commaToken() + + // Figure out leading trivia and adjust the prior argument (if there is + // one) by adding a comma, if necessary. + let leadingTrivia: Trivia + if position > startIndex { + let priorArgument = arguments[positionIdx - 1] + + // Our leading trivia will be based on the prior argument's leading + // trivia. + leadingTrivia = priorArgument.leadingTrivia + + // If the prior argument is missing a trailing comma, add one. + if priorArgument.trailingComma == nil { + arguments[positionIdx - 1].trailingComma = commaToken + } + } else if positionIdx + 1 < count { + leadingTrivia = arguments[positionIdx + 1].leadingTrivia + } else { + leadingTrivia = Trivia() + } + + // Determine whether we need a trailing comma on this argument. + let trailingComma: TokenSyntax? + if position < endIndex { + trailingComma = commaToken + } else { + trailingComma = nil + } + + // Create the argument and insert it into the argument list. + let argument = generator(leadingTrivia, trailingComma) + arguments.insert(argument, at: positionIdx) + + return LabeledExprListSyntax(arguments) + } +} + +extension SyntaxProtocol { + /// Look for a call expression to a callee with the given name. + func findCall(calleeName: String) -> FunctionCallExprSyntax? { + return FunctionCallExprSyntax.findFirst(in: self) { call in + return call.hasCallee(named: calleeName) + } + } +} + +extension ArrayExprSyntax { + /// Produce a new array literal expression that appends the given + /// element, while trying to maintain similar indentation. + func appending( + element: ExprSyntax, + outerLeadingTrivia: Trivia + ) -> ArrayExprSyntax { + var elements = self.elements + + let commaToken = TokenSyntax.commaToken() + + // If there are already elements, tack it on. + let leadingTrivia: Trivia + let trailingTrivia: Trivia + let leftSquareTrailingTrivia: Trivia + if let last = elements.last { + // The leading trivia of the new element should match that of the + // last element. + leadingTrivia = last.leadingTrivia.onlyLastLine() + + // Add a trailing comma to the last element if it isn't already + // there. + if last.trailingComma == nil { + var newElements = Array(elements) + newElements[newElements.count - 1].trailingComma = commaToken + newElements[newElements.count - 1].expression.trailingTrivia = + Trivia() + newElements[newElements.count - 1].trailingTrivia = last.trailingTrivia + elements = ArrayElementListSyntax(newElements) + } + + trailingTrivia = Trivia() + leftSquareTrailingTrivia = leftSquare.trailingTrivia + } else { + leadingTrivia = outerLeadingTrivia.appending(defaultIndent) + trailingTrivia = outerLeadingTrivia + if leftSquare.trailingTrivia.hasNewlines { + leftSquareTrailingTrivia = leftSquare.trailingTrivia + } else { + leftSquareTrailingTrivia = Trivia() + } + } + + elements.append( + ArrayElementSyntax( + expression: element.with(\.leadingTrivia, leadingTrivia), + trailingComma: commaToken.with(\.trailingTrivia, trailingTrivia) + ) + ) + + let newLeftSquare = leftSquare.with( + \.trailingTrivia, + leftSquareTrailingTrivia + ) + + return with(\.elements, elements).with(\.leftSquare, newLeftSquare) + } +} + +extension ExprSyntax { + /// Find an array argument either at the top level or within a sequence + /// expression. + func findArrayArgument() -> ArrayExprSyntax? { + if let arrayExpr = self.as(ArrayExprSyntax.self) { + return arrayExpr + } + + if let sequenceExpr = self.as(SequenceExprSyntax.self) { + return sequenceExpr.elements.lazy.compactMap { + $0.findArrayArgument() + }.first + } + + return nil + } +} + +// MARK: Utilities to oeprate on arrays of array literal elements. +extension Array { + /// Append a new argument expression. + mutating func append(expression: ExprSyntax) { + // Add a comma on the prior expression, if there is one. + let leadingTrivia: Trivia? + if count > 0 { + self[count - 1].trailingComma = TokenSyntax.commaToken() + leadingTrivia = .newline + + // Adjust the first element to start with a newline + if count == 1 { + self[0].leadingTrivia = .newline + } + } else { + leadingTrivia = nil + } + + append( + ArrayElementSyntax( + leadingTrivia: leadingTrivia, + expression: expression + ) + ) + } +} + +// MARK: Utilities to operate on arrays of call arguments. + +extension Array { + /// Append a potentially labeled argument with the argument expression. + mutating func append(label: String?, expression: ExprSyntax) { + // Add a comma on the prior expression, if there is one. + let leadingTrivia: Trivia + if count > 0 { + self[count - 1].trailingComma = TokenSyntax.commaToken() + leadingTrivia = .newline + + // Adjust the first element to start with a newline + if count == 1 { + self[0].leadingTrivia = .newline + } + } else { + leadingTrivia = Trivia() + } + + // Add the new expression. + append( + LabeledExprSyntax( + label: label, + expression: expression + ).with(\.leadingTrivia, leadingTrivia) + ) + } + + /// Append a potentially labeled argument with a string literal. + mutating func append(label: String?, stringLiteral: String) { + append(label: label, expression: "\(literal: stringLiteral)") + } + + /// Append a potentially labeled argument with a string literal, but only + /// when the string literal is not nil. + mutating func appendIf(label: String?, stringLiteral: String?) { + if let stringLiteral { + append(label: label, stringLiteral: stringLiteral) + } + } + + /// Append an array literal containing elements that can be rendered + /// into expression syntax nodes. + mutating func append( + label: String?, + arrayLiteral: [T] + ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + var elements: [ArrayElementSyntax] = [] + for element in arrayLiteral { + elements.append(expression: element.asSyntax()) + } + + // Figure out the trivia for the left and right square + let leftSquareTrailingTrivia: Trivia + let rightSquareLeadingTrivia: Trivia + switch elements.count { + case 0: + // Put a single space between the square brackets. + leftSquareTrailingTrivia = Trivia() + rightSquareLeadingTrivia = .space + + case 1: + // Put spaces around the single element + leftSquareTrailingTrivia = .space + rightSquareLeadingTrivia = .space + + default: + // Each of the elements will have a leading newline. Add a leading + // newline before the close bracket. + leftSquareTrailingTrivia = Trivia() + rightSquareLeadingTrivia = .newline + } + + let array = ArrayExprSyntax( + leftSquare: .leftSquareToken( + trailingTrivia: leftSquareTrailingTrivia + ), + elements: ArrayElementListSyntax(elements), + rightSquare: .rightSquareToken( + leadingTrivia: rightSquareLeadingTrivia + ) + ) + append(label: label, expression: ExprSyntax(array)) + } + + /// Append an array literal containing elements that can be rendered + /// into expression syntax nodes. + mutating func appendIf( + label: String?, + arrayLiteral: [T]? + ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + guard let arrayLiteral else { return } + append(label: label, arrayLiteral: arrayLiteral) + } + + /// Append an array literal containing elements that can be rendered + /// into expression syntax nodes, but only if it's not empty. + mutating func appendIfNonEmpty( + label: String?, + arrayLiteral: [T] + ) where T: ManifestSyntaxRepresentable, T.PreferredSyntax == ExprSyntax { + if arrayLiteral.isEmpty { return } + + append(label: label, arrayLiteral: arrayLiteral) + } +} + +// MARK: Utilities for adding arguments into calls. +fileprivate class ReplacingRewriter: SyntaxRewriter { + let childNode: Syntax + let newChildNode: Syntax + + init(childNode: Syntax, newChildNode: Syntax) { + self.childNode = childNode + self.newChildNode = newChildNode + super.init() + } + + override func visitAny(_ node: Syntax) -> Syntax? { + if node == childNode { + return newChildNode + } + + return nil + } +} + +fileprivate extension SyntaxProtocol { + /// Replace the given child with a new child node. + func replacingChild(_ childNode: Syntax, with newChildNode: Syntax) -> Self { + return ReplacingRewriter( + childNode: childNode, + newChildNode: newChildNode + ).rewrite(self).cast(Self.self) + } +} + +extension FunctionCallExprSyntax { + /// Produce source edits that will add the given new element to the + /// array for an argument with the given label (if there is one), or + /// introduce a new argument with an array literal containing only the + /// new element. + /// + /// - Parameters: + /// - label: The argument label for the argument whose array will be + /// added or modified. + /// - trailingLabels: The argument labels that could follow the label, + /// which helps determine where the argument should be inserted if + /// it doesn't exist yet. + /// - newElement: The new element. + /// - Returns: the function call after making this change. + func appendingToArrayArgument( + label: String, + trailingLabels: Set, + newElement: ExprSyntax + ) throws -> FunctionCallExprSyntax { + // If there is already an argument with this name, append to the array + // literal in there. + if let arg = findArgument(labeled: label) { + guard let argArray = arg.expression.findArrayArgument() else { + throw ManifestEditError.cannotFindArrayLiteralArgument( + argumentName: label, + node: Syntax(arg.expression) + ) + } + + // Format the element appropriately for the context. + let indentation = Trivia( + pieces: arg.leadingTrivia.filter { $0.isSpaceOrTab } + ) + let format = BasicFormat( + indentationWidth: [ defaultIndent ], + initialIndentation: indentation.appending(defaultIndent) + ) + let formattedElement = newElement.formatted(using: format) + .cast(ExprSyntax.self) + + let updatedArgArray = argArray.appending( + element: formattedElement, + outerLeadingTrivia: arg.leadingTrivia + ) + + return replacingChild(Syntax(argArray), with: Syntax(updatedArgArray)) + } + + // There was no argument, so we need to create one. + + // Insert the new argument at the appropriate place in the call. + let insertionPos = arguments.findArgumentInsertionPosition( + labelsAfter: trailingLabels + ) + let newArguments = arguments.insertingArgument( + at: insertionPos + ) { (leadingTrivia, trailingComma) in + // Format the element appropriately for the context. + let indentation = Trivia(pieces: leadingTrivia.filter { $0.isSpaceOrTab }) + let format = BasicFormat( + indentationWidth: [ defaultIndent ], + initialIndentation: indentation.appending(defaultIndent) + ) + let formattedElement = newElement.formatted(using: format) + .cast(ExprSyntax.self) + + // Form the array. + let newArgument = ArrayExprSyntax( + leadingTrivia: .space, + leftSquare: .leftSquareToken( + trailingTrivia: .newline + ), + elements: ArrayElementListSyntax( + [ + ArrayElementSyntax( + expression: formattedElement, + trailingComma: .commaToken() + ) + ] + ), + rightSquare: .rightSquareToken( + leadingTrivia: leadingTrivia + ) + ) + + // Create the labeled argument for the array. + return LabeledExprSyntax( + leadingTrivia: leadingTrivia, + label: "\(raw: label)", + colon: .colonToken(), + expression: ExprSyntax(newArgument), + trailingComma: trailingComma + ) + } + + return with(\.arguments, newArguments) + } +} diff --git a/Sources/PackageModelSyntax/TargetDescription+Syntax.swift b/Sources/PackageModelSyntax/TargetDescription+Syntax.swift new file mode 100644 index 00000000000..5081932bed8 --- /dev/null +++ b/Sources/PackageModelSyntax/TargetDescription+Syntax.swift @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import SwiftSyntax +import SwiftParser + + +extension TargetDescription: ManifestSyntaxRepresentable { + /// The function name in the package manifest. + private var functionName: String { + switch type { + case .binary: "binaryTarget" + case .executable: "executableTarget" + case .macro: "macro" + case .plugin: "plugin" + case .regular: "target" + case .system: "systemLibrary" + case .test: "testTarget" + case .providedLibrary: "providedLibrary" + } + } + + func asSyntax() -> ExprSyntax { + var arguments: [LabeledExprSyntax] = [] + arguments.append(label: "name", stringLiteral: name) + // FIXME: pluginCapability + + arguments.appendIfNonEmpty( + label: "dependencies", + arrayLiteral: dependencies + ) + + arguments.appendIf(label: "path", stringLiteral: path) + arguments.appendIf(label: "url", stringLiteral: url) + arguments.appendIfNonEmpty(label: "exclude", arrayLiteral: exclude) + arguments.appendIf(label: "sources", arrayLiteral: sources) + + // FIXME: resources + + arguments.appendIf( + label: "publicHeadersPath", + stringLiteral: publicHeadersPath + ) + + if !packageAccess { + arguments.append( + label: "packageAccess", + expression: "false" + ) + } + + // FIXME: cSettings + // FIXME: cxxSettings + // FIXME: swiftSettings + // FIXME: linkerSettings + // FIXME: plugins + + arguments.appendIf(label: "pkgConfig", stringLiteral: pkgConfig) + // FIXME: providers + + // Only for plugins + arguments.appendIf(label: "checksum", stringLiteral: checksum) + + let separateParen: String = arguments.count > 1 ? "\n" : "" + let argumentsSyntax = LabeledExprListSyntax(arguments) + return ".\(raw: functionName)(\(argumentsSyntax)\(raw: separateParen))" + } +} + +extension TargetDescription.Dependency: ManifestSyntaxRepresentable { + func asSyntax() -> ExprSyntax { + switch self { + case .byName(name: let name, condition: nil): + "\(literal: name)" + + case .target(name: let name, condition: nil): + ".target(name: \(literal: name))" + + case .product(name: let name, package: nil, moduleAliases: nil, condition: nil): + ".product(name: \(literal: name))" + + case .product(name: let name, package: let package, moduleAliases: nil, condition: nil): + ".product(name: \(literal: name), package: \(literal: package))" + + default: + fatalError() + } + } +} diff --git a/Sources/PackagePlugin/Command.swift b/Sources/PackagePlugin/Command.swift index 1a232ab824a..1c8c7f1e887 100644 --- a/Sources/PackagePlugin/Command.swift +++ b/Sources/PackagePlugin/Command.swift @@ -43,7 +43,7 @@ public enum Command { /// was generated as if in its source directory; other files are treated /// as resources as if explicitly listed in `Package.swift` using /// `.process(...)`. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) case buildCommand( displayName: String?, executable: URL, @@ -76,7 +76,7 @@ public enum Command { /// this command was generated as if in its source directory; other /// files are treated as resources as if explicitly listed in /// `Package.swift` using `.process(...)`. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) case prebuildCommand( displayName: String?, executable: URL, @@ -114,7 +114,7 @@ public extension Command { /// was generated as if in its source directory; other files are treated /// as resources as if explicitly listed in `Package.swift` using /// `.process(...)`. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) static func buildCommand( displayName: String?, executable: Path, @@ -204,7 +204,7 @@ public extension Command { /// this command was generated as if in its source directory; other /// files are treated as resources as if explicitly listed in /// `Package.swift` using `.process(...)`. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) static func prebuildCommand( displayName: String?, executable: Path, diff --git a/Sources/PackagePlugin/Context.swift b/Sources/PackagePlugin/Context.swift index 36b5124782d..521bfc95dce 100644 --- a/Sources/PackagePlugin/Context.swift +++ b/Sources/PackagePlugin/Context.swift @@ -31,7 +31,7 @@ public struct PluginContext { /// write its outputs to that directory. The plugin may also create other /// directories for cache files and other file system content that either /// it or the command will need. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let pluginWorkDirectory: Path /// The path of a writable directory into which the plugin or the build @@ -46,7 +46,7 @@ public struct PluginContext { /// write its outputs to that directory. The plugin may also create other /// directories for cache files and other file system content that either /// it or the command will need. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let pluginWorkDirectoryURL: URL /// Looks up and returns the path of a named command line executable tool. @@ -86,12 +86,12 @@ public struct PluginContext { /// The paths of directories of in which to search for tools that aren't in /// the `toolNamesToPaths` map. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) let toolSearchDirectories: [Path] /// The paths of directories of in which to search for tools that aren't in /// the `toolNamesToPaths` map. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) let toolSearchDirectoryURLs: [URL] /// Information about a particular tool that is available to a plugin. @@ -100,11 +100,11 @@ public struct PluginContext { public let name: String /// Full path of the built or provided tool in the file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let path: Path /// Full path of the built or provided tool in the file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let url: URL } } diff --git a/Sources/PackagePlugin/PackageManagerProxy.swift b/Sources/PackagePlugin/PackageManagerProxy.swift index ce01036185d..8268b439512 100644 --- a/Sources/PackagePlugin/PackageManagerProxy.swift +++ b/Sources/PackagePlugin/PackageManagerProxy.swift @@ -108,13 +108,13 @@ public struct PackageManager { /// Represents a single artifact produced during a build. public struct BuiltArtifact { /// Full path of the built artifact in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var path: Path { return Path(url: url) } /// Full path of the built artifact in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public var url: URL /// The kind of artifact that was built. @@ -182,14 +182,14 @@ public struct PackageManager { /// Path of a generated `.profdata` file suitable for processing using /// `llvm-cov`, if `enableCodeCoverage` was set in the test parameters. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var codeCoverageDataFile: Path? { return codeCoverageDataFileURL.map { Path(url: $0) } } /// Path of a generated `.profdata` file suitable for processing using /// `llvm-cov`, if `enableCodeCoverage` was set in the test parameters. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public var codeCoverageDataFileURL: URL? /// Represents the results of running some or all of the tests in a @@ -265,13 +265,13 @@ public struct PackageManager { /// Represents the result of symbol graph generation. public struct SymbolGraphResult { /// The directory that contains the symbol graph files for the target. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var directoryPath: Path { return Path(url: directoryURL) } /// The directory that contains the symbol graph files for the target. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public var directoryURL: URL } } diff --git a/Sources/PackagePlugin/PackageModel.swift b/Sources/PackagePlugin/PackageModel.swift index 99ef721d57a..fd61fe2b272 100644 --- a/Sources/PackagePlugin/PackageModel.swift +++ b/Sources/PackagePlugin/PackageModel.swift @@ -22,11 +22,11 @@ public struct Package { public let displayName: String /// The absolute path of the package directory in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let directory: Path /// The absolute path of the package directory in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let directoryURL: URL /// The origin of the package (root, local, repository, registry, etc). @@ -221,13 +221,13 @@ public protocol SourceModuleTarget: Target { /// Paths of any sources generated by other plugins that have been applied to the given target before the plugin currently being executed. /// /// Note: Plugins are applied in order of declaration in the package manifest. Generated files are vended to the target the current plugin is being applied to, but not necessarily to other targets in the package graph. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) var pluginGeneratedSources: [URL] { get } /// Paths of any resources generated by other plugins that have been applied to the given target before the plugin currently being executed. /// /// Note: Plugins are applied in order of declaration in the package manifest. Generated files are vended to the target the current plugin is being applied to, but not necessarily to other targets in the package graph. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) var pluginGeneratedResources: [URL] { get } } @@ -261,11 +261,11 @@ public struct SwiftSourceModuleTarget: SourceModuleTarget { public let kind: ModuleKind /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let directory: Path /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let directoryURL: URL /// Any other targets on which this target depends, in the same order as @@ -294,11 +294,11 @@ public struct SwiftSourceModuleTarget: SourceModuleTarget { public let linkedFrameworks: [String] /// Paths of any sources generated by other plugins that have been applied to the given target before the plugin currently being executed. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let pluginGeneratedSources: [URL] /// Paths of any resources generated by other plugins that have been applied to the given target before the plugin currently being executed. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let pluginGeneratedResources: [URL] } @@ -316,11 +316,11 @@ public struct ClangSourceModuleTarget: SourceModuleTarget { public let kind: ModuleKind /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let directory: Path /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let directoryURL: URL /// Any other targets on which this target depends, in the same order as @@ -344,12 +344,12 @@ public struct ClangSourceModuleTarget: SourceModuleTarget { /// The directory containing public C headers, if applicable. This will /// only be set for targets that have a directory of a public headers. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let publicHeadersDirectory: Path? /// The directory containing public C headers, if applicable. This will /// only be set for targets that have a directory of a public headers. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let publicHeadersDirectoryURL: URL? /// Any custom linked libraries required by the module, as specified in the @@ -361,11 +361,11 @@ public struct ClangSourceModuleTarget: SourceModuleTarget { public let linkedFrameworks: [String] /// Paths of any sources generated by other plugins that have been applied to the given target before the plugin currently being executed. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let pluginGeneratedSources: [URL] /// Paths of any resources generated by other plugins that have been applied to the given target before the plugin currently being executed. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let pluginGeneratedResources: [URL] } @@ -380,11 +380,11 @@ public struct BinaryArtifactTarget: Target { public let name: String /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let directory: Path /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let directoryURL: URL /// Any other targets on which this target depends, in the same order as @@ -399,11 +399,11 @@ public struct BinaryArtifactTarget: Target { public let origin: Origin /// The location of the binary artifact in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let artifact: Path /// The location of the binary artifact in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let artifactURL: URL /// Represents a kind of binary artifact. @@ -434,11 +434,11 @@ public struct SystemLibraryTarget: Target { public var name: String /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var directory: Path /// The absolute path of the target directory in the local file system. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public var directoryURL: URL /// Any other targets on which this target depends, in the same order as @@ -494,11 +494,11 @@ extension FileList: RandomAccessCollection { /// Provides information about a single file in a FileList. public struct File { /// The path of the file. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public let path: Path /// The path of the file. - @available(_PackageDescription, introduced: 5.11) + @available(_PackageDescription, introduced: 6.0) public let url: URL /// File type, as determined by SwiftPM. diff --git a/Sources/PackagePlugin/Path.swift b/Sources/PackagePlugin/Path.swift index b97427f1fca..19a6e3556fe 100644 --- a/Sources/PackagePlugin/Path.swift +++ b/Sources/PackagePlugin/Path.swift @@ -18,7 +18,7 @@ public struct Path: Hashable { /// Initializes the path from the contents a string, which should be an /// absolute path in platform representation. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public init(_ string: String) { self._string = string } @@ -28,7 +28,7 @@ public struct Path: Hashable { } /// A string representation of the path. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var string: String { return _string } @@ -39,7 +39,7 @@ public struct Path: Hashable { } /// The last path component (including any extension). - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var lastComponent: String { // Check for a special case of the root directory. if _string == "/" { @@ -58,7 +58,7 @@ public struct Path: Hashable { } /// The last path component (without any extension). - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var stem: String { let filename = self.lastComponent if let ext = self.extension { @@ -69,7 +69,7 @@ public struct Path: Hashable { } /// The filename extension, if any (without any leading dot). - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var `extension`: String? { // Find the last path separator, if any. let sIdx = _string.lastIndex(of: "/") @@ -92,7 +92,7 @@ public struct Path: Hashable { } /// The path except for the last path component. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public func removingLastComponent() -> Path { // Find the last path separator. guard let idx = string.lastIndex(of: "/") else { @@ -111,19 +111,19 @@ public struct Path: Hashable { /// The result of appending a subpath, which should be a relative path in /// platform representation. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public func appending(subpath: String) -> Path { return Path(_string + (_string.hasSuffix("/") ? "" : "/") + subpath) } /// The result of appending one or more path components. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public func appending(_ components: [String]) -> Path { return self.appending(subpath: components.joined(separator: "/")) } /// The result of appending one or more path components. - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public func appending(_ components: String...) -> Path { return self.appending(components) } @@ -131,7 +131,7 @@ public struct Path: Hashable { extension Path: CustomStringConvertible { - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public var description: String { return self.string } @@ -139,13 +139,13 @@ extension Path: CustomStringConvertible { extension Path: Codable { - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(self.string) } - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) @@ -155,7 +155,7 @@ extension Path: Codable { public extension String.StringInterpolation { - @available(_PackageDescription, deprecated: 5.11) + @available(_PackageDescription, deprecated: 6.0) mutating func appendInterpolation(_ path: Path) { self.appendInterpolation(path.string) } diff --git a/Sources/PackagePlugin/PluginContextDeserializer.swift b/Sources/PackagePlugin/PluginContextDeserializer.swift index 015bff9484e..716d8f56d5b 100644 --- a/Sources/PackagePlugin/PluginContextDeserializer.swift +++ b/Sources/PackagePlugin/PluginContextDeserializer.swift @@ -251,12 +251,22 @@ internal struct PluginContextDeserializer { } let products = try wirePackage.productIds.map { try self.product(for: $0) } let targets = try wirePackage.targetIds.map { try self.target(for: $0) } + let origin: PackageOrigin = switch wirePackage.origin { + case .root: + .root + case .local(let pathId): + try .local(path: url(for: pathId).path) + case .repository(let url, let displayVersion, let scmRevision): + .repository(url: url, displayVersion: displayVersion, scmRevision: scmRevision) + case .registry(let identity, let displayVersion): + .registry(identity: identity, displayVersion: displayVersion) + } let package = Package( id: wirePackage.identity, displayName: wirePackage.displayName, directory: Path(url: directory), directoryURL: directory, - origin: .root, + origin: origin, toolsVersion: toolsVersion, dependencies: dependencies, products: products, diff --git a/Sources/PackageRegistry/RegistryClient.swift b/Sources/PackageRegistry/RegistryClient.swift index ff8ec74b5c6..96445280282 100644 --- a/Sources/PackageRegistry/RegistryClient.swift +++ b/Sources/PackageRegistry/RegistryClient.swift @@ -983,7 +983,7 @@ public final class RegistryClient: Cancellable { package: PackageIdentity, version: Version, destinationPath: AbsolutePath, - progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + progressHandler: (@Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, timeout: DispatchTimeInterval? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1009,7 +1009,7 @@ public final class RegistryClient: Cancellable { package: PackageIdentity, version: Version, destinationPath: AbsolutePath, - progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + progressHandler: (@Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, timeout: DispatchTimeInterval? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1063,7 +1063,7 @@ public final class RegistryClient: Cancellable { package: PackageIdentity.RegistryIdentity, version: Version, destinationPath: AbsolutePath, - progressHandler: ((_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, + progressHandler: (@Sendable (_ bytesReceived: Int64, _ totalBytes: Int64?) -> Void)?, timeout: DispatchTimeInterval?, fileSystem: FileSystem, observabilityScope: ObservabilityScope, @@ -1651,7 +1651,7 @@ public final class RegistryClient: Cancellable { result.tryMap { response in observabilityScope .emit( - debug: "server response for \(request.url): \(response.statusCode) in \(start.distance(to: .now()).descriptionInSeconds)" + debug: "server response for \(url): \(response.statusCode) in \(start.distance(to: .now()).descriptionInSeconds)" ) switch response.statusCode { case 201: diff --git a/Sources/PackageRegistry/RegistryConfiguration.swift b/Sources/PackageRegistry/RegistryConfiguration.swift index 642e60a281d..8b6a15d5647 100644 --- a/Sources/PackageRegistry/RegistryConfiguration.swift +++ b/Sources/PackageRegistry/RegistryConfiguration.swift @@ -405,9 +405,9 @@ extension PackageModel.Registry: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.init( - url: try container.decode(URL.self, forKey: .url), - supportsAvailability: try container.decodeIfPresent(Bool.self, forKey: .supportsAvailability) ?? false + try self.init( + url: container.decode(URL.self, forKey: .url), + supportsAvailability: container.decodeIfPresent(Bool.self, forKey: .supportsAvailability) ?? false ) } diff --git a/Sources/PackageRegistry/RegistryDownloadsManager.swift b/Sources/PackageRegistry/RegistryDownloadsManager.swift index 3c24234bfef..12462ff744b 100644 --- a/Sources/PackageRegistry/RegistryDownloadsManager.swift +++ b/Sources/PackageRegistry/RegistryDownloadsManager.swift @@ -163,7 +163,7 @@ public class RegistryDownloadsManager: Cancellable { observabilityScope: ObservabilityScope, delegateQueue: DispatchQueue, callbackQueue: DispatchQueue, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { if let cachePath { do { diff --git a/Sources/PackageRegistry/SignatureValidation.swift b/Sources/PackageRegistry/SignatureValidation.swift index 3e5fccd64ae..4fee446f87f 100644 --- a/Sources/PackageRegistry/SignatureValidation.swift +++ b/Sources/PackageRegistry/SignatureValidation.swift @@ -91,7 +91,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { guard !self.skipSignatureValidation else { return completion(.success(.none)) @@ -138,7 +138,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { do { let versionMetadata = try self.versionMetadataProvider(package, version) @@ -240,7 +240,7 @@ struct SignatureValidation { configuration: RegistryConfiguration.Security.Signing, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { Task { do { @@ -353,7 +353,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { guard !self.skipSignatureValidation else { return completion(.success(.none)) @@ -402,7 +402,7 @@ struct SignatureValidation { fileSystem: FileSystem, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let manifestName = toolsVersion.map { "Package@swift-\($0).swift" } ?? Manifest.filename @@ -506,7 +506,7 @@ struct SignatureValidation { configuration: RegistryConfiguration.Security.Signing, fileSystem: FileSystem, observabilityScope: ObservabilityScope, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { Task { do { @@ -577,7 +577,7 @@ struct SignatureValidation { signatureFormat: SignatureFormat, configuration: RegistryConfiguration.Security.Signing, fileSystem: FileSystem, - completion: @Sendable @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { Task { do { diff --git a/Sources/PackageRegistryTool/PackageRegistryTool+Auth.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift similarity index 89% rename from Sources/PackageRegistryTool/PackageRegistryTool+Auth.swift rename to Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift index 82ccf7ad294..f129a011424 100644 --- a/Sources/PackageRegistryTool/PackageRegistryTool+Auth.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Auth.swift @@ -12,8 +12,11 @@ import ArgumentParser import Basics + import Commands + import CoreCommands + import Foundation import PackageModel import PackageRegistry @@ -26,7 +29,7 @@ import WinSDK private func readpassword(_ prompt: String) throws -> String { enum StaticStorage { static var buffer: UnsafeMutableBufferPointer = - .allocate(capacity: SwiftPackageRegistryTool.Login.passwordBufferSize) + .allocate(capacity: PackageRegistryCommand.Login.passwordBufferSize) } let hStdIn: HANDLE = GetStdHandle(STD_INPUT_HANDLE) @@ -56,9 +59,9 @@ private func readpassword(_ prompt: String) throws -> String { ) let password = String(cString: UnsafePointer(StaticStorage.buffer.baseAddress!)) - guard password.count <= SwiftPackageRegistryTool.Login.maxPasswordLength else { - throw SwiftPackageRegistryTool.ValidationError - .credentialLengthLimitExceeded(SwiftPackageRegistryTool.Login.maxPasswordLength) + guard password.count <= PackageRegistryCommand.Login.maxPasswordLength else { + throw PackageRegistryCommand.ValidationError + .credentialLengthLimitExceeded(PackageRegistryCommand.Login.maxPasswordLength) } return password } @@ -67,28 +70,29 @@ private func readpassword(_ prompt: String) throws -> String { let password: String #if canImport(Darwin) - var buffer = [CChar](repeating: 0, count: SwiftPackageRegistryTool.Login.passwordBufferSize) + var buffer = [CChar](repeating: 0, count: PackageRegistryCommand.Login.passwordBufferSize) + password = try withExtendedLifetime(buffer) { + guard let passwordPtr = readpassphrase(prompt, &buffer, buffer.count, 0) else { + throw StringError("unable to read input") + } - guard let passwordPtr = readpassphrase(prompt, &buffer, buffer.count, 0) else { - throw StringError("unable to read input") + return String(cString: passwordPtr) } - - password = String(cString: passwordPtr) #else // GNU C implementation of getpass has no limit on the password length // (https://man7.org/linux/man-pages/man3/getpass.3.html) password = String(cString: getpass(prompt)) #endif - guard password.count <= SwiftPackageRegistryTool.Login.maxPasswordLength else { - throw SwiftPackageRegistryTool.ValidationError - .credentialLengthLimitExceeded(SwiftPackageRegistryTool.Login.maxPasswordLength) + guard password.count <= PackageRegistryCommand.Login.maxPasswordLength else { + throw PackageRegistryCommand.ValidationError + .credentialLengthLimitExceeded(PackageRegistryCommand.Login.maxPasswordLength) } return password } #endif -extension SwiftPackageRegistryTool { +extension PackageRegistryCommand { struct Login: AsyncSwiftCommand { static func loginURL(from registryURL: URL, loginAPIPath: String?) throws -> URL { @@ -144,12 +148,12 @@ extension SwiftPackageRegistryTool { private static let PLACEHOLDER_TOKEN_USER = "token" - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { // We need to be able to read/write credentials // Make sure credentials store is available before proceeding let authorizationProvider: AuthorizationProvider? do { - authorizationProvider = try swiftTool.getRegistryAuthorizationProvider() + authorizationProvider = try swiftCommandState.getRegistryAuthorizationProvider() } catch { throw ValidationError.invalidCredentialStore(error) } @@ -159,7 +163,7 @@ extension SwiftPackageRegistryTool { } // Auth config is in user-level registries config only - let configuration = try getRegistriesConfig(swiftTool, global: true) + let configuration = try getRegistriesConfig(swiftCommandState, global: true) // compute and validate registry URL guard let registryURL = self.registryURL ?? configuration.configuration.defaultRegistry?.url else { @@ -258,7 +262,7 @@ extension SwiftPackageRegistryTool { try await registryClient.login( loginURL: loginURL, timeout: .seconds(5), - observabilityScope: swiftTool.observabilityScope, + observabilityScope: swiftCommandState.observabilityScope, callbackQueue: .sharedConcurrent ) @@ -330,9 +334,9 @@ extension SwiftPackageRegistryTool { self.url } - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { // Auth config is in user-level registries config only - let configuration = try getRegistriesConfig(swiftTool, global: true) + let configuration = try getRegistriesConfig(swiftCommandState, global: true) // compute and validate registry URL guard let registryURL = self.registryURL ?? configuration.configuration.defaultRegistry?.url else { @@ -342,7 +346,7 @@ extension SwiftPackageRegistryTool { try registryURL.validateRegistryURL() // We need to be able to read/write credentials - guard let authorizationProvider = try swiftTool.getRegistryAuthorizationProvider() else { + guard let authorizationProvider = try swiftCommandState.getRegistryAuthorizationProvider() else { throw ValidationError.unknownCredentialStore } diff --git a/Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift similarity index 94% rename from Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift rename to Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift index d049340aff7..2ce572df0d9 100644 --- a/Sources/PackageRegistryTool/PackageRegistryTool+Publish.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand+Publish.swift @@ -12,8 +12,11 @@ import ArgumentParser import Basics + import Commands + import CoreCommands + import Foundation import PackageModel import PackageRegistry @@ -32,7 +35,7 @@ import struct TSCBasic.SHA256 import struct TSCUtility.Version -extension SwiftPackageRegistryTool { +extension PackageRegistryCommand { struct Publish: AsyncSwiftCommand { static let metadataFilename = "package-metadata.json" @@ -82,15 +85,18 @@ extension SwiftPackageRegistryTool { ) var certificateChainPaths: [AbsolutePath] = [] + @Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL") + var allowInsecureHTTP: Bool = false + @Flag(help: "Dry run only; prepare the archive and sign it but do not publish to the registry.") var dryRun: Bool = false - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { // Require both local and user-level registries config - let configuration = try getRegistriesConfig(swiftTool, global: false).configuration + let configuration = try getRegistriesConfig(swiftCommandState, global: false).configuration // validate package location - let packageDirectory = try self.globalOptions.locations.packageDirectory ?? swiftTool.getPackageRoot() + let packageDirectory = try self.globalOptions.locations.packageDirectory ?? swiftCommandState.getPackageRoot() guard localFileSystem.isDirectory(packageDirectory) else { throw StringError("No package found at '\(packageDirectory)'.") } @@ -106,7 +112,8 @@ extension SwiftPackageRegistryTool { throw ValidationError.unknownRegistry } - try registryURL.validateRegistryURL() + let allowHTTP = try self.allowInsecureHTTP && (configuration.authentication(for: registryURL) == nil) + try registryURL.validateRegistryURL(allowHTTP: allowHTTP) // validate working directory path if let customWorkingDirectory { @@ -135,7 +142,7 @@ extension SwiftPackageRegistryTool { metadataLocation = .sourceTree(defaultMetadataPath) } - guard let authorizationProvider = try swiftTool.getRegistryAuthorizationProvider() else { + guard let authorizationProvider = try swiftCommandState.getRegistryAuthorizationProvider() else { throw ValidationError.unknownCredentialStore } @@ -175,9 +182,9 @@ extension SwiftPackageRegistryTool { workingDirectory: workingDirectory, mode: signingMode, signatureFormat: self.signatureFormat, - cancellator: swiftTool.cancellator, + cancellator: swiftCommandState.cancellator, fileSystem: localFileSystem, - observabilityScope: swiftTool.observabilityScope + observabilityScope: swiftCommandState.observabilityScope ) archivePath = result.archive.path archiveSignature = result.archive.signature @@ -185,15 +192,15 @@ extension SwiftPackageRegistryTool { } else { // step 2: generate source archive for the package release // step 3: signing not required - swiftTool.observabilityScope.emit(info: "archiving the source at '\(packageDirectory)'") + swiftCommandState.observabilityScope.emit(info: "archiving the source at '\(packageDirectory)'") archivePath = try PackageArchiver.archive( packageIdentity: self.packageIdentity, packageVersion: self.packageVersion, packageDirectory: packageDirectory, workingDirectory: workingDirectory, workingFilesToCopy: [], - cancellator: swiftTool.cancellator, - observabilityScope: swiftTool.observabilityScope + cancellator: swiftCommandState.cancellator, + observabilityScope: swiftCommandState.observabilityScope ) } @@ -205,7 +212,7 @@ extension SwiftPackageRegistryTool { return } - swiftTool.observabilityScope + swiftCommandState.observabilityScope .emit(info: "publishing \(self.packageIdentity) archive at '\(archivePath)' to \(registryURL)") let result = try await registryClient.publish( registryURL: registryURL, @@ -217,7 +224,7 @@ extension SwiftPackageRegistryTool { metadataSignature: metadataSignature, signatureFormat: self.signatureFormat, fileSystem: localFileSystem, - observabilityScope: swiftTool.observabilityScope, + observabilityScope: swiftCommandState.observabilityScope, callbackQueue: .sharedConcurrent ) @@ -243,7 +250,7 @@ extension SignatureFormat { } } -#if swift(<5.11) +#if swift(<6.0) extension SignatureFormat: ExpressibleByArgument {} #else extension SignatureFormat: @retroactive ExpressibleByArgument {} @@ -496,7 +503,7 @@ enum PackageArchiver { try localFileSystem.writeFileContents(toBeReplacedPath, bytes: replacement) } - try SwiftPackageTool.archiveSource( + try SwiftPackageCommand.archiveSource( at: sourceDirectory, to: archivePath, fileSystem: localFileSystem, diff --git a/Sources/PackageRegistryTool/PackageRegistryTool.swift b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift similarity index 77% rename from Sources/PackageRegistryTool/PackageRegistryTool.swift rename to Sources/PackageRegistryCommand/PackageRegistryCommand.swift index 4f218ac1733..19d289b8b07 100644 --- a/Sources/PackageRegistryTool/PackageRegistryTool.swift +++ b/Sources/PackageRegistryCommand/PackageRegistryCommand.swift @@ -12,14 +12,18 @@ import ArgumentParser import Basics + import CoreCommands + +import Commands + import Foundation import PackageModel import PackageRegistry import Workspace -public struct SwiftPackageRegistryTool: AsyncParsableCommand { - public static var configuration = CommandConfiguration( +package struct PackageRegistryCommand: AsyncParsableCommand { + package static var configuration = CommandConfiguration( commandName: "package-registry", _superCommandName: "swift", abstract: "Interact with package registry and manage related configuration", @@ -38,7 +42,7 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { @OptionGroup() var globalOptions: GlobalOptions - public init() {} + package init() {} struct Set: AsyncSwiftCommand { static let configuration = CommandConfiguration( @@ -54,6 +58,9 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { @Option(help: "Associate the registry with a given scope") var scope: String? + @Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL") + var allowInsecureHTTP: Bool = false + @Argument(help: "The registry URL") var url: URL @@ -61,20 +68,21 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { self.url } - func run(_ swiftTool: SwiftTool) async throws { - try self.registryURL.validateRegistryURL() + func run(_ swiftCommandState: SwiftCommandState) async throws { + try self.registryURL.validateRegistryURL(allowHTTP: self.allowInsecureHTTP) let scope = try scope.map(PackageIdentity.Scope.init(validating:)) let set: (inout RegistryConfiguration) throws -> Void = { configuration in + let registry = Registry(url: self.registryURL, supportsAvailability: false) if let scope { - configuration.scopedRegistries[scope] = .init(url: self.registryURL, supportsAvailability: false) + configuration.scopedRegistries[scope] = registry } else { - configuration.defaultRegistry = .init(url: self.registryURL, supportsAvailability: false) + configuration.defaultRegistry = registry } } - let configuration = try getRegistriesConfig(swiftTool, global: self.global) + let configuration = try getRegistriesConfig(swiftCommandState, global: self.global) if self.global { try configuration.updateShared(with: set) } else { @@ -97,7 +105,7 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { @Option(help: "Associate the registry with a given scope") var scope: String? - func run(_ swiftTool: SwiftTool) async throws { + func run(_ swiftCommandState: SwiftCommandState) async throws { let scope = try scope.map(PackageIdentity.Scope.init(validating:)) let unset: (inout RegistryConfiguration) throws -> Void = { configuration in @@ -114,7 +122,7 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { } } - let configuration = try getRegistriesConfig(swiftTool, global: self.global) + let configuration = try getRegistriesConfig(swiftCommandState, global: self.global) if self.global { try configuration.updateShared(with: unset) } else { @@ -138,21 +146,21 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { case credentialLengthLimitExceeded(Int) } - static func getRegistriesConfig(_ swiftTool: SwiftTool, global: Bool) throws -> Workspace.Configuration.Registries { + static func getRegistriesConfig(_ swiftCommandState: SwiftCommandState, global: Bool) throws -> Workspace.Configuration.Registries { if global { let sharedRegistriesFile = Workspace.DefaultLocations.registriesConfigurationFile( - at: swiftTool.sharedConfigurationDirectory + at: swiftCommandState.sharedConfigurationDirectory ) // Workspace not needed when working with user-level registries config return try .init( - fileSystem: swiftTool.fileSystem, + fileSystem: swiftCommandState.fileSystem, localRegistriesFile: .none, sharedRegistriesFile: sharedRegistriesFile ) } else { - let workspace = try swiftTool.getActiveWorkspace() + let workspace = try swiftCommandState.getActiveWorkspace() return try .init( - fileSystem: swiftTool.fileSystem, + fileSystem: swiftCommandState.fileSystem, localRegistriesFile: workspace.location.localRegistriesConfigurationFile, sharedRegistriesFile: workspace.location.sharedRegistriesConfigurationFile ) @@ -161,14 +169,14 @@ public struct SwiftPackageRegistryTool: AsyncParsableCommand { } extension URL { - func validateRegistryURL() throws { - guard self.scheme == "https" else { - throw SwiftPackageRegistryTool.ValidationError.invalidURL(self) + func validateRegistryURL(allowHTTP: Bool = false) throws { + guard self.scheme == "https" || (self.scheme == "http" && allowHTTP) else { + throw PackageRegistryCommand.ValidationError.invalidURL(self) } } } -extension SwiftPackageRegistryTool.ConfigurationError: CustomStringConvertible { +extension PackageRegistryCommand.ConfigurationError: CustomStringConvertible { var description: String { switch self { case .missingScope(let scope?): @@ -179,7 +187,7 @@ extension SwiftPackageRegistryTool.ConfigurationError: CustomStringConvertible { } } -extension SwiftPackageRegistryTool.ValidationError: CustomStringConvertible { +extension PackageRegistryCommand.ValidationError: CustomStringConvertible { var description: String { switch self { case .invalidURL(let url): diff --git a/Sources/QueryEngine/CacheKey.swift b/Sources/QueryEngine/CacheKey.swift new file mode 100644 index 00000000000..2fb5fa174fa --- /dev/null +++ b/Sources/QueryEngine/CacheKey.swift @@ -0,0 +1,169 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@_exported import protocol Crypto.HashFunction +import struct Foundation.URL +import struct SystemPackage.FilePath + +/// Indicates that values of a conforming type can be hashed with an arbitrary hashing function. Unlike `Hashable`, +/// this protocol doesn't utilize random seed values and produces consistent hash values across process launches. +package protocol CacheKey: Encodable { +} + +extension Bool: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + hashFunction.update(data: self ? [1] : [0]) + } +} + +extension Int: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension Int8: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension Int16: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension Int32: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension Int64: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension UInt: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension UInt8: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension UInt16: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension UInt32: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension UInt64: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension Float: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension Double: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + withUnsafeBytes(of: self) { + hashFunction.update(data: $0) + } + } +} + +extension String: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + var t = String(reflecting: Self.self) + t.withUTF8 { + hashFunction.update(data: $0) + } + var x = self + x.withUTF8 { + hashFunction.update(data: $0) + } + } +} + +extension FilePath: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + self.string.hash(with: &hashFunction) + } +} + +extension FilePath.Component: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + self.string.hash(with: &hashFunction) + } +} + +extension URL: CacheKey { + func hash(with hashFunction: inout some HashFunction) { + String(reflecting: Self.self).hash(with: &hashFunction) + self.description.hash(with: &hashFunction) + } +} diff --git a/Sources/QueryEngine/FileSystem/AsyncFileSystem.swift b/Sources/QueryEngine/FileSystem/AsyncFileSystem.swift new file mode 100644 index 00000000000..02e82ca5593 --- /dev/null +++ b/Sources/QueryEngine/FileSystem/AsyncFileSystem.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +import protocol _Concurrency.Actor +import protocol Crypto.HashFunction +import struct SystemPackage.Errno +import struct SystemPackage.FilePath + +package protocol AsyncFileSystem: Actor { + func withOpenReadableFile( + _ path: FilePath, + _ body: @Sendable (OpenReadableFile) async throws -> T + ) async throws -> T + + func withOpenWritableFile( + _ path: FilePath, + _ body: @Sendable (OpenWritableFile) async throws -> T + ) async throws -> T +} + +enum FileSystemError: Error { + case fileDoesNotExist(FilePath) + case bufferLimitExceeded(FilePath) + case systemError(FilePath, Errno) +} + +extension Error { + func attach(path: FilePath) -> any Error { + if let error = self as? Errno { + FileSystemError.systemError(path, error) + } else { + self + } + } +} diff --git a/Sources/QueryEngine/FileSystem/FileCacheRecord.swift b/Sources/QueryEngine/FileSystem/FileCacheRecord.swift new file mode 100644 index 00000000000..ed23fd78bb5 --- /dev/null +++ b/Sources/QueryEngine/FileSystem/FileCacheRecord.swift @@ -0,0 +1,39 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +// FIXME: need a new swift-system tag to remove `@preconcurrency` +@preconcurrency import struct SystemPackage.FilePath + +package struct FileCacheRecord: Sendable { + package let path: FilePath + package let hash: String +} + +extension FileCacheRecord: Codable { + enum CodingKeys: CodingKey { + case path + case hash + } + + // FIXME: `Codable` on `FilePath` is broken, thus all `Codable` types with `FilePath` properties need a custom impl. + package init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.path = try FilePath(container.decode(String.self, forKey: .path)) + self.hash = try container.decode(String.self, forKey: .hash) + } + + package func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.path.string, forKey: .path) + try container.encode(self.hash, forKey: .hash) + } +} diff --git a/Sources/QueryEngine/FileSystem/OpenReadableFile.swift b/Sources/QueryEngine/FileSystem/OpenReadableFile.swift new file mode 100644 index 00000000000..dae8d37fd87 --- /dev/null +++ b/Sources/QueryEngine/FileSystem/OpenReadableFile.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +import protocol Crypto.HashFunction +import struct SystemPackage.FileDescriptor + +package struct OpenReadableFile: Sendable { + let readChunkSize: Int + + enum FileHandle { + case local(FileDescriptor) + case virtual([UInt8]) + } + + let fileHandle: FileHandle + + func read() async throws -> ReadableFileStream { + switch self.fileHandle { + case .local(let fileDescriptor): + ReadableFileStream.local(.init(fileDescriptor: fileDescriptor, readChunkSize: self.readChunkSize)) + case .virtual(let array): + ReadableFileStream.virtual(.init(bytes: array)) + } + } + + func hash(with hashFunction: inout some HashFunction) async throws { + switch self.fileHandle { + case .local(let fileDescriptor): + var buffer = [UInt8](repeating: 0, count: readChunkSize) + var bytesRead = 0 + repeat { + bytesRead = try buffer.withUnsafeMutableBytes { + try fileDescriptor.read(into: $0) + } + + if bytesRead > 0 { + hashFunction.update(data: buffer[0 ..< bytesRead]) + } + + } while bytesRead > 0 + case .virtual(let array): + hashFunction.update(data: array) + } + } +} diff --git a/Sources/QueryEngine/FileSystem/OpenWritableFile.swift b/Sources/QueryEngine/FileSystem/OpenWritableFile.swift new file mode 100644 index 00000000000..be69f3cdc7c --- /dev/null +++ b/Sources/QueryEngine/FileSystem/OpenWritableFile.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +import struct SystemPackage.FileDescriptor +import struct SystemPackage.FilePath + +package struct OpenWritableFile: Sendable { + enum FileHandle: Sendable { + case local(FileDescriptor) + case virtual(VirtualFileSystem.Storage, FilePath) + } + + let fileHandle: FileHandle + + func write(_ bytes: some Sequence) async throws { + switch self.fileHandle { + case .local(let fileDescriptor): + _ = try fileDescriptor.writeAll(bytes) + case .virtual(let storage, let path): + storage.content[path, default: []].append(contentsOf: bytes) + } + } +} diff --git a/Sources/QueryEngine/FileSystem/ReadableFileStream.swift b/Sources/QueryEngine/FileSystem/ReadableFileStream.swift new file mode 100644 index 00000000000..f1fc7864ed5 --- /dev/null +++ b/Sources/QueryEngine/FileSystem/ReadableFileStream.swift @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +import _Concurrency +import SystemPackage + +package enum ReadableFileStream: AsyncSequence { + package typealias Element = [UInt8] + + case local(LocalReadableFileStream) + case virtual(VirtualReadableFileStream) + + package enum Iterator: AsyncIteratorProtocol { + case local(LocalReadableFileStream.Iterator) + case virtual(VirtualReadableFileStream.Iterator) + + package func next() async throws -> [UInt8]? { + switch self { + case .local(let local): + try await local.next() + case .virtual(let virtual): + try await virtual.next() + } + } + } + + package func makeAsyncIterator() -> Iterator { + switch self { + case .local(let local): + .local(local.makeAsyncIterator()) + case .virtual(let virtual): + .virtual(virtual.makeAsyncIterator()) + } + } +} + +package struct LocalReadableFileStream: AsyncSequence { + package typealias Element = [UInt8] + + let fileDescriptor: FileDescriptor + let readChunkSize: Int + + package final class Iterator: AsyncIteratorProtocol { + init(_ fileDescriptor: FileDescriptor, readChunkSize: Int) { + self.fileDescriptor = fileDescriptor + self.readChunkSize = readChunkSize + } + + private let fileDescriptor: FileDescriptor + private let readChunkSize: Int + + package func next() async throws -> [UInt8]? { + var buffer = [UInt8](repeating: 0, count: readChunkSize) + + let bytesRead = try buffer.withUnsafeMutableBytes { + try self.fileDescriptor.read(into: $0) + } + + guard bytesRead > 0 else { + return nil + } + + buffer.removeLast(self.readChunkSize - bytesRead) + return buffer + } + } + + package func makeAsyncIterator() -> Iterator { + Iterator(self.fileDescriptor, readChunkSize: self.readChunkSize) + } +} + +package struct VirtualReadableFileStream: AsyncSequence { + package typealias Element = [UInt8] + + package final class Iterator: AsyncIteratorProtocol { + init(bytes: [UInt8]? = nil) { + self.bytes = bytes + } + + var bytes: [UInt8]? + + package func next() async throws -> [UInt8]? { + defer { bytes = nil } + + return self.bytes + } + } + + let bytes: [UInt8] + + package func makeAsyncIterator() -> Iterator { + Iterator(bytes: self.bytes) + } +} diff --git a/Sources/QueryEngine/FileSystem/VirtualFileSystem.swift b/Sources/QueryEngine/FileSystem/VirtualFileSystem.swift new file mode 100644 index 00000000000..db8af267bbf --- /dev/null +++ b/Sources/QueryEngine/FileSystem/VirtualFileSystem.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +import struct SystemPackage.FilePath + +actor VirtualFileSystem: AsyncFileSystem { + package static let defaultChunkSize = 512 * 1024 + + let readChunkSize: Int + + final class Storage { + init(_ content: [FilePath: [UInt8]]) { + self.content = content + } + + var content: [FilePath: [UInt8]] + } + + private let storage: Storage + + init(content: [FilePath: [UInt8]] = [:], readChunkSize: Int = defaultChunkSize) { + self.storage = .init(content) + self.readChunkSize = readChunkSize + } + + func withOpenReadableFile( + _ path: FilePath, + _ body: (OpenReadableFile) async throws -> T + ) async throws -> T { + guard let bytes = storage.content[path] else { + throw FileSystemError.fileDoesNotExist(path) + } + return try await body(.init(readChunkSize: self.readChunkSize, fileHandle: .virtual(bytes))) + } + + func withOpenWritableFile( + _ path: FilePath, + _ body: (OpenWritableFile) async throws -> T + ) async throws -> T { + try await body(.init(fileHandle: .virtual(self.storage, path))) + } +} diff --git a/Sources/QueryEngine/Query.swift b/Sources/QueryEngine/Query.swift new file mode 100644 index 00000000000..e04fc7d6879 --- /dev/null +++ b/Sources/QueryEngine/Query.swift @@ -0,0 +1,266 @@ +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +import Crypto +import struct SystemPackage.FilePath + +package protocol Query: CacheKey, Sendable { + func run(engine: QueryEngine) async throws -> FilePath +} + +// SwiftPM has to be built with Swift 5.8 on CI and also needs to support CMake for bootstrapping on Windows. +// This means we can't implement persistable hashing with macros (unavailable in Swift 5.8 and additional effort to +// set up with CMake when Swift 5.9 is available for all CI jobs) and have to stick to `Encodable` for now. +final class HashEncoder: Encoder { + enum Error: Swift.Error { + case noCacheKeyConformance(Encodable.Type) + } + + var codingPath: [any CodingKey] + + var userInfo: [CodingUserInfoKey : Any] + + func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { + String(reflecting: Key.self).hash(with: &self.hashFunction) + return .init(KeyedContainer(encoder: self)) + } + + func unkeyedContainer() -> any UnkeyedEncodingContainer { + self + } + + func singleValueContainer() -> any SingleValueEncodingContainer { + self + } + + init() { + self.hashFunction = Hash() + self.codingPath = [] + self.userInfo = [:] + } + + fileprivate var hashFunction = Hash() + + func finalize() -> Hash.Digest { + hashFunction.finalize() + } +} + +extension HashEncoder: SingleValueEncodingContainer { + func encodeNil() throws { + // FIXME: this doesn't encode the name of the underlying optional type, + // but `Encoder` protocol is limited and can't provide this for us. + var str = "nil" + str.withUTF8 { + self.hashFunction.update(data: $0) + } + } + + func encode(_ value: Bool) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: String) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Double) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Float) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Int) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Int8) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Int16) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Int32) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: Int64) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: UInt) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: UInt8) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: UInt16) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: UInt32) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: UInt64) throws { + value.hash(with: &self.hashFunction) + } + + func encode(_ value: T) throws where T : Encodable { + guard value is CacheKey else { + throw Error.noCacheKeyConformance(T.self) + } + + try value.encode(to: self) + } +} + +extension HashEncoder: UnkeyedEncodingContainer { + var count: Int { + 0 + } + + func nestedContainer(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer where NestedKey : CodingKey { + KeyedEncodingContainer(KeyedContainer(encoder: self)) + } + + func nestedUnkeyedContainer() -> any UnkeyedEncodingContainer { + self + } + + func superEncoder() -> any Encoder { + fatalError() + } +} + +extension HashEncoder { + struct KeyedContainer: KeyedEncodingContainerProtocol { + var encoder: HashEncoder + var codingPath: [any CodingKey] { self.encoder.codingPath } + + mutating func encodeNil(forKey key: K) throws { + // FIXME: this doesn't encode the name of the underlying optional type, + // but `Encoder` protocol is limited and can't provide this for us. + var str = "nil" + str.withUTF8 { + self.encoder.hashFunction.update(data: $0) + } + } + + mutating func encode(_ value: Bool, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: String, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Double, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Float, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Int, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Int8, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Int16, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Int32, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: Int64, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: UInt, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: UInt8, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: UInt16, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: UInt32, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: UInt64, forKey key: K) throws { + key.stringValue.hash(with: &self.encoder.hashFunction) + value.hash(with: &self.encoder.hashFunction) + } + + mutating func encode(_ value: T, forKey key: K) throws where T : Encodable { + guard value is CacheKey else { + throw Error.noCacheKeyConformance(T.self) + } + + key.stringValue.hash(with: &self.encoder.hashFunction) + try value.encode(to: self.encoder) + } + + mutating func nestedContainer( + keyedBy keyType: NestedKey.Type, + forKey key: K + ) -> KeyedEncodingContainer where NestedKey : CodingKey { + key.stringValue.hash(with: &self.encoder.hashFunction) + return self.encoder.nestedContainer(keyedBy: keyType) + } + + mutating func nestedUnkeyedContainer(forKey key: K) -> any UnkeyedEncodingContainer { + key.stringValue.hash(with: &self.encoder.hashFunction) + return self.encoder + } + + mutating func superEncoder() -> any Encoder { + fatalError() + } + + mutating func superEncoder(forKey key: K) -> any Encoder { + fatalError() + } + + typealias Key = K + } +} diff --git a/Sources/QueryEngine/QueryEngine.swift b/Sources/QueryEngine/QueryEngine.swift new file mode 100644 index 00000000000..dfeb3997bd2 --- /dev/null +++ b/Sources/QueryEngine/QueryEngine.swift @@ -0,0 +1,118 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023-2024 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 +// +//===----------------------------------------------------------------------===// + +import Basics +import Crypto +@preconcurrency import SystemPackage + +package func withQueryEngine( + _ fileSystem: any AsyncFileSystem, + _ observabilityScope: ObservabilityScope, + cacheLocation: SQLite.Location, + _ body: @Sendable (QueryEngine) async throws -> Void +) async throws { + let engine = QueryEngine( + fileSystem, + observabilityScope, + cacheLocation: cacheLocation + ) + + try await withAsyncThrowing { + try await body(engine) + } defer: { + try await engine.shutDown() + } +} + +/// Cacheable computations engine. Currently the engine makes an assumption that computations produce same results for +/// the same query values and write results to a single file path. +package actor QueryEngine { + private(set) var cacheHits = 0 + private(set) var cacheMisses = 0 + + package let fileSystem: any AsyncFileSystem + package let httpClient = HTTPClient() + package let observabilityScope: ObservabilityScope + private let resultsCache: SQLiteBackedCache + private var isShutDown = false + + /// Creates a new instance of the ``QueryEngine`` actor. Requires an explicit call + /// to ``QueryEngine//shutdown`` before the instance is deinitialized. The recommended approach to resource + /// management is to place `engine.shutDown()` when the engine is no longer used, but is not deinitialized yet. + /// - Parameter fileSystem: Implementation of a file system this engine should use. + /// - Parameter cacheLocation: Location of cache storage used by the engine. + /// - Parameter logger: Logger to use during queries execution. + init( + _ fileSystem: any AsyncFileSystem, + _ observabilityScope: ObservabilityScope, + cacheLocation: SQLite.Location + ) { + self.fileSystem = fileSystem + self.observabilityScope = observabilityScope + self.resultsCache = SQLiteBackedCache(tableName: "cache_table", location: cacheLocation) + } + + package func shutDown() async throws { + precondition(!self.isShutDown, "`QueryEngine/shutDown` should be called only once") + try self.resultsCache.close() + + self.isShutDown = true + } + + deinit { + let isShutDown = self.isShutDown + precondition( + isShutDown, + "`QueryEngine/shutDown` should be called explicitly on instances of `Engine` before deinitialization" + ) + } + + /// Executes a given query if no cached result of it is available. Otherwise fetches the result from engine's cache. + /// - Parameter query: A query value to execute. + /// - Returns: A file path to query's result recorded in a file. + package subscript(_ query: some Query) -> FileCacheRecord { + get async throws { + let hashEncoder = HashEncoder() + try query.encode(to: hashEncoder) + let key = hashEncoder.finalize() + + if let fileRecord = try resultsCache.get(blobKey: key) { + + let fileHash = try await self.fileSystem.withOpenReadableFile(fileRecord.path) { + var hashFunction = SHA512() + try await $0.hash(with: &hashFunction) + return hashFunction.finalize().description + } + + if fileHash == fileRecord.hash { + self.cacheHits += 1 + return fileRecord + } + } + + self.cacheMisses += 1 + let resultPath = try await query.run(engine: self) + + let resultHash = try await self.fileSystem.withOpenReadableFile(resultPath) { + var hashFunction = SHA512() + try await $0.hash(with: &hashFunction) + return hashFunction.finalize().description + } + let result = FileCacheRecord(path: resultPath, hash: resultHash) + + // FIXME: update `SQLiteBackedCache` to store `resultHash` directly instead of relying on string conversions + try self.resultsCache.put(blobKey: key, value: result) + + return result + } + } +} diff --git a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift index 278c3b0167e..64f398394d6 100644 --- a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift +++ b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift @@ -69,7 +69,10 @@ extension BinaryTarget { // Filter supported triples with versionLessTriple and pass into // ExecutableInfo; empty if non matching triples found. try entry.value.variants.map { - let filteredSupportedTriples = try $0.supportedTriples + guard let supportedTriples = $0.supportedTriples else { + throw StringError("No \"supportedTriples\" found in the artifact metadata for \(entry.key) in \(self.artifactPath)") + } + let filteredSupportedTriples = try supportedTriples .filter { try $0.withoutVersion() == versionLessTriple } return ExecutableInfo( name: entry.key, diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift index 6007d1d6046..a526bcf3855 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters+Driver.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2020-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -25,13 +25,15 @@ extension BuildParameters { enableParseableModuleInterfaces: Bool = false, explicitTargetDependencyImportCheckingMode: TargetDependencyImportCheckingMode = .none, useIntegratedSwiftDriver: Bool = false, - useExplicitModuleBuild: Bool = false + useExplicitModuleBuild: Bool = false, + isPackageAccessModifierSupported: Bool = false ) { self.canRenameEntrypointFunctionName = canRenameEntrypointFunctionName self.enableParseableModuleInterfaces = enableParseableModuleInterfaces self.explicitTargetDependencyImportCheckingMode = explicitTargetDependencyImportCheckingMode self.useIntegratedSwiftDriver = useIntegratedSwiftDriver self.useExplicitModuleBuild = useExplicitModuleBuild + self.isPackageAccessModifierSupported = isPackageAccessModifierSupported } /// Whether to enable the entry-point-function-name feature. @@ -45,11 +47,15 @@ extension BuildParameters { /// `.swiftmodule`s. public var enableParseableModuleInterfaces: Bool - /// Whether to use the integrated Swift driver rather than shelling out + /// Whether to use the integrated Swift Driver rather than shelling out /// to a separate process. public var useIntegratedSwiftDriver: Bool /// Whether to use the explicit module build flow (with the integrated driver). public var useExplicitModuleBuild: Bool + + /// Whether the version of Swift Driver used in the currently selected toolchain + /// supports `-package-name` options. + package var isPackageAccessModifierSupported: Bool } } diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift index cbc5cb5499c..baf1e966d1a 100644 --- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift +++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift @@ -243,24 +243,24 @@ public struct BuildParameters: Encodable { /// Returns the path to the dynamic library of a product for the current build parameters. func potentialDynamicLibraryPath(for product: ResolvedProduct) throws -> RelativePath { - try RelativePath(validating: "\(self.triple.dynamicLibraryPrefix)\(product.name)\(self.triple.dynamicLibraryExtension)") + try RelativePath(validating: "\(self.triple.dynamicLibraryPrefix)\(product.name)\(self.suffix(triple: product.buildTriple))\(self.triple.dynamicLibraryExtension)") } /// Returns the path to the binary of a product for the current build parameters, relative to the build directory. public func binaryRelativePath(for product: ResolvedProduct) throws -> RelativePath { - let potentialExecutablePath = try RelativePath(validating: "\(product.name)\(self.triple.executableExtension)") + let potentialExecutablePath = try RelativePath(validating: "\(product.name)\(self.suffix(triple: product.buildTriple))\(self.triple.executableExtension)") switch product.type { case .executable, .snippet: return potentialExecutablePath case .library(.static): - return try RelativePath(validating: "lib\(product.name)\(self.triple.staticLibraryExtension)") + return try RelativePath(validating: "lib\(product.name)\(self.suffix(triple: product.buildTriple))\(self.triple.staticLibraryExtension)") case .library(.dynamic): return try potentialDynamicLibraryPath(for: product) case .library(.automatic), .plugin: fatalError() case .test: - guard !self.triple.isWASI() else { + guard !self.triple.isWasm else { return try RelativePath(validating: "\(product.name).wasm") } switch testingParameters.library { @@ -329,3 +329,11 @@ extension Triple { return !self.isWindows() } } + +extension BuildParameters { + /// Suffix appended to build manifest nodes to distinguish nodes created for tools from nodes created for + /// end products, i.e. nodes for host vs target triples. + package func suffix(triple: BuildTriple) -> String { + if triple == .tools { "-tool" } else { "" } + } +} diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift index e8d1fd3efe1..fc52987ce01 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystem.swift @@ -33,7 +33,7 @@ public enum BuildSubset { /// A protocol that represents a build system used by SwiftPM for all build operations. This allows factoring out the /// implementation details between SwiftPM's `BuildOperation` and the XCBuild backed `XCBuildSystem`. -public protocol BuildSystem: Cancellable { +package protocol BuildSystem: Cancellable { /// The delegate used by the build system. var delegate: BuildSystemDelegate? { get } @@ -42,7 +42,7 @@ public protocol BuildSystem: Cancellable { var builtTestProducts: [BuiltTestProduct] { get } /// Returns the package graph used by the build system. - func getPackageGraph() throws -> PackageGraph + func getPackageGraph() throws -> ModulesGraph /// Builds a subset of the package graph. /// - Parameters: @@ -74,7 +74,7 @@ extension ProductBuildDescription { /// The path to the product binary produced. public var binaryPath: AbsolutePath { get throws { - return try self.buildParameters.binaryPath(for: product) + try self.buildParameters.binaryPath(for: product) } } } @@ -94,7 +94,7 @@ public protocol BuildPlan { extension BuildPlan { /// Parameters used for building a given target. - public func buildParameters(for target: ResolvedTarget) -> BuildParameters { + public func buildParameters(for target: ResolvedModule) -> BuildParameters { switch target.buildTriple { case .tools: return self.toolsBuildParameters @@ -114,20 +114,20 @@ extension BuildPlan { } } -public protocol BuildSystemFactory { +package protocol BuildSystemFactory { func makeBuildSystem( explicitProduct: String?, cacheBuildManifest: Bool, productsBuildParameters: BuildParameters?, toolsBuildParameters: BuildParameters?, - packageGraphLoader: (() throws -> PackageGraph)?, + packageGraphLoader: (() throws -> ModulesGraph)?, outputStream: OutputByteStream?, logLevel: Diagnostic.Severity?, observabilityScope: ObservabilityScope? ) throws -> any BuildSystem } -public struct BuildSystemProvider { +package struct BuildSystemProvider { // TODO: In the future, we may want this to be about specific capabilities of a build system rather than choosing a concrete one. public enum Kind: String, CaseIterable { case native @@ -146,7 +146,7 @@ public struct BuildSystemProvider { cacheBuildManifest: Bool = true, productsBuildParameters: BuildParameters? = .none, toolsBuildParameters: BuildParameters? = .none, - packageGraphLoader: (() throws -> PackageGraph)? = .none, + packageGraphLoader: (() throws -> ModulesGraph)? = .none, outputStream: OutputByteStream? = .none, logLevel: Diagnostic.Severity? = .none, observabilityScope: ObservabilityScope? = .none @@ -171,12 +171,12 @@ private enum Errors: Swift.Error { case buildSystemProviderNotRegistered(kind: BuildSystemProvider.Kind) } -public enum BuildSystemUtilities { +package enum BuildSystemUtilities { /// Returns the build path from the environment, if present. public static func getEnvBuildPath(workingDir: AbsolutePath) throws -> AbsolutePath? { // Don't rely on build path from env for SwiftPM's own tests. - guard ProcessEnv.vars["SWIFTPM_TESTS_MODULECACHE"] == nil else { return nil } - guard let env = ProcessEnv.vars["SWIFTPM_BUILD_DIR"] else { return nil } + guard ProcessEnv.block["SWIFTPM_TESTS_MODULECACHE"] == nil else { return nil } + guard let env = ProcessEnv.block["SWIFTPM_BUILD_DIR"] else { return nil } return try AbsolutePath(validating: env, relativeTo: workingDir) } } diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift index 4fd1273ead4..cc8a13c49e0 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystemCommand.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public struct BuildSystemCommand: Hashable { +package struct BuildSystemCommand: Hashable { public let name: String public let description: String public let verboseDescription: String? diff --git a/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift b/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift index 163c16f16e3..9f8a32ecc4d 100644 --- a/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift +++ b/Sources/SPMBuildCore/BuildSystem/BuildSystemDelegate.swift @@ -12,8 +12,8 @@ import Foundation -/// BuildSystem delegate -public protocol BuildSystemDelegate: AnyObject { +/// ``BuildSystem`` delegate +package protocol BuildSystemDelegate: AnyObject { ///Called when build command is about to start. func buildSystem(_ buildSystem: BuildSystem, willStartCommand command: BuildSystemCommand) @@ -35,7 +35,7 @@ public protocol BuildSystemDelegate: AnyObject { func buildSystemDidCancel(_ buildSystem: BuildSystem) } -public extension BuildSystemDelegate { +extension BuildSystemDelegate { func buildSystem(_ buildSystem: BuildSystem, willStartCommand command: BuildSystemCommand) { } func buildSystem(_ buildSystem: BuildSystem, didStartCommand command: BuildSystemCommand) { } func buildSystem(_ buildSystem: BuildSystem, didUpdateTaskProgress text: String) { } diff --git a/Sources/SPMBuildCore/CMakeLists.txt b/Sources/SPMBuildCore/CMakeLists.txt index d5c3ee43344..ed5d5d5d828 100644 --- a/Sources/SPMBuildCore/CMakeLists.txt +++ b/Sources/SPMBuildCore/CMakeLists.txt @@ -23,6 +23,7 @@ add_library(SPMBuildCore Plugins/PluginMessages.swift Plugins/PluginScriptRunner.swift PrebuildCommandResult.swift + ResolvedPackage+Extensions.swift Triple+Extensions.swift XCFrameworkMetadata.swift) # NOTE(compnerd) workaround for CMake not setting up include flags yet diff --git a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift index 83852545d3e..340f02d1cf8 100644 --- a/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift +++ b/Sources/SPMBuildCore/Plugins/PluginContextSerializer.swift @@ -22,13 +22,14 @@ typealias WireInput = HostToPluginMessage.InputContext /// the input information to a plugin. internal struct PluginContextSerializer { let fileSystem: FileSystem + let modulesGraph: ModulesGraph let buildEnvironment: BuildEnvironment let pkgConfigDirectories: [AbsolutePath] let sdkRootPath: AbsolutePath? var paths: [WireInput.URL] = [] var pathsToIds: [AbsolutePath: WireInput.URL.Id] = [:] var targets: [WireInput.Target] = [] - var targetsToWireIDs: [ResolvedTarget.ID: WireInput.Target.Id] = [:] + var targetsToWireIDs: [ResolvedModule.ID: WireInput.Target.Id] = [:] var products: [WireInput.Product] = [] var productsToWireIDs: [ResolvedProduct.ID: WireInput.Product.Id] = [:] var packages: [WireInput.Package] = [] @@ -55,7 +56,7 @@ internal struct PluginContextSerializer { // Adds a target to the serialized structure, if it isn't already there and // if it is of a kind that should be passed to the plugin. If so, this func- // tion returns the target's wire ID. If not, it returns nil. - mutating func serialize(target: ResolvedTarget) throws -> WireInput.Target.Id? { + mutating func serialize(target: ResolvedModule) throws -> WireInput.Target.Id? { // If we've already seen the target, just return the wire ID we already assigned to it. if let id = targetsToWireIDs[target.id] { return id } @@ -244,7 +245,7 @@ internal struct PluginContextSerializer { } // Serialize the dependencies. It is important to do this before the `let id = package.count` below so the correct wire ID gets assigned. - let dependencies = try package.dependencies.map { + let dependencies = try modulesGraph.directDependencies(for: package).map { WireInput.Package.Dependency(packageId: try serialize(package: $0)) } @@ -280,7 +281,7 @@ fileprivate extension WireInput.Target.TargetInfo.SourceModuleKind { self = .test case .macro: self = .macro - case .binary, .plugin, .systemModule: + case .binary, .plugin, .systemModule, .providedLibrary: throw StringError("unexpected target kind \(kind) for source module") } } diff --git a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift index 72dcef42431..7eb97d7c781 100644 --- a/Sources/SPMBuildCore/Plugins/PluginInvocation.swift +++ b/Sources/SPMBuildCore/Plugins/PluginInvocation.swift @@ -21,7 +21,7 @@ import protocol TSCBasic.DiagnosticLocation public enum PluginAction { case createBuildToolCommands( package: ResolvedPackage, - target: ResolvedTarget, + target: ResolvedModule, pluginGeneratedSources: [AbsolutePath], pluginGeneratedResources: [AbsolutePath] ) @@ -43,6 +43,7 @@ extension PluginTarget { pkgConfigDirectories: [AbsolutePath], sdkRootPath: AbsolutePath?, fileSystem: FileSystem, + modulesGraph: ModulesGraph, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, delegate: PluginInvocationDelegate @@ -62,6 +63,7 @@ extension PluginTarget { pkgConfigDirectories: pkgConfigDirectories, sdkRootPath: sdkRootPath, fileSystem: fileSystem, + modulesGraph: modulesGraph, observabilityScope: observabilityScope, callbackQueue: callbackQueue, delegate: delegate, @@ -107,6 +109,7 @@ extension PluginTarget { pkgConfigDirectories: [AbsolutePath], sdkRootPath: AbsolutePath?, fileSystem: FileSystem, + modulesGraph: ModulesGraph, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, delegate: PluginInvocationDelegate, @@ -125,6 +128,7 @@ extension PluginTarget { do { var serializer = PluginContextSerializer( fileSystem: fileSystem, + modulesGraph: modulesGraph, buildEnvironment: buildEnvironment, pkgConfigDirectories: pkgConfigDirectories, sdkRootPath: sdkRootPath @@ -366,7 +370,7 @@ fileprivate extension PluginToHostMessage { } } -extension PackageGraph { +extension ModulesGraph { /// Traverses the graph of reachable targets in a package graph, and applies plugins to targets as needed. Each /// plugin is passed an input context that provides information about the target to which it is being applied @@ -398,8 +402,8 @@ extension PackageGraph { observabilityScope: ObservabilityScope, fileSystem: FileSystem, builtToolHandler: (_ name: String, _ path: RelativePath) throws -> AbsolutePath? = { _, _ in return nil } - ) throws -> [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] { - var pluginResultsByTarget: [ResolvedTarget.ID: (target: ResolvedTarget, results: [BuildToolPluginInvocationResult])] = [:] + ) throws -> [ResolvedModule.ID: (target: ResolvedModule, results: [BuildToolPluginInvocationResult])] { + var pluginResultsByTarget: [ResolvedModule.ID: (target: ResolvedModule, results: [BuildToolPluginInvocationResult])] = [:] for target in self.allTargets.sorted(by: { $0.name < $1.name }) { // Infer plugins from the declared dependencies, and collect them as well as any regular dependencies. Although usage of build tool plugins is declared separately from dependencies in the manifest, in the internal model we currently consider both to be dependencies. var pluginTargets: [PluginTarget] = [] @@ -529,10 +533,10 @@ extension PackageGraph { } let delegate = PluginDelegate(fileSystem: fileSystem, delegateQueue: delegateQueue, toolPaths: toolPaths, builtToolNames: builtToolNames) - // In tools version 5.11 and newer, we vend the list of files generated by previous plugins. + // In tools version 6.0 and newer, we vend the list of files generated by previous plugins. let pluginDerivedSources: Sources let pluginDerivedResources: [Resource] - if package.manifest.toolsVersion >= .v5_11 { + if package.manifest.toolsVersion >= .v6_0 { // Set up dummy observability because we don't want to emit diagnostics for this before the actual build. let observability = ObservabilitySystem({ _, _ in }) // Compute the generated files based on all results we have computed so far. @@ -571,6 +575,7 @@ extension PackageGraph { pkgConfigDirectories: pkgConfigDirectories, sdkRootPath: buildParameters.toolchain.sdkRootPath, fileSystem: fileSystem, + modulesGraph: self, observabilityScope: observabilityScope, callbackQueue: delegateQueue, delegate: delegate, @@ -592,13 +597,15 @@ extension PackageGraph { } // Associate the list of results with the target. The list will have one entry for each plugin used by the target. - pluginResultsByTarget[target.id] = (target, buildToolPluginResults) + var targetID = target.id + targetID.buildTriple = .destination + pluginResultsByTarget[targetID] = (target, buildToolPluginResults) } return pluginResultsByTarget } public static func computePluginGeneratedFiles( - target: ResolvedTarget, + target: ResolvedModule, toolsVersion: ToolsVersion, additionalFileRules: [FileRuleDescription], buildParameters: BuildParameters, @@ -660,7 +667,7 @@ public extension PluginTarget { } /// The set of tools that are accessible to this plugin. - private func accessibleTools(packageGraph: PackageGraph, fileSystem: FileSystem, environment: BuildEnvironment, for hostTriple: Triple) throws -> Set { + private func accessibleTools(packageGraph: ModulesGraph, fileSystem: FileSystem, environment: BuildEnvironment, for hostTriple: Triple) throws -> Set { return try Set(self.dependencies(satisfying: environment).flatMap { dependency -> [PluginAccessibleTool] in let builtToolName: String let executableOrBinaryTarget: Target @@ -677,6 +684,15 @@ public extension PluginTarget { } builtToolName = productRef.name executableOrBinaryTarget = executableTarget + case .innerProduct(let productRef, _): + guard + let product = packageGraph.allProducts.first(where: { $0.name == productRef.name }), + let executableTarget = product.targets.map({ $0.underlying }).executables.spm_only + else { + throw StringError("no product named \(productRef.name)") + } + builtToolName = productRef.name + executableOrBinaryTarget = executableTarget } // For a binary target we create a `vendedTool`. @@ -695,7 +711,7 @@ public extension PluginTarget { }) } - func processAccessibleTools(packageGraph: PackageGraph, fileSystem: FileSystem, environment: BuildEnvironment, for hostTriple: Triple, builtToolHandler: (_ name: String, _ path: RelativePath) throws -> AbsolutePath?) throws -> [String: (path: AbsolutePath, triples: [String]?)] { + func processAccessibleTools(packageGraph: ModulesGraph, fileSystem: FileSystem, environment: BuildEnvironment, for hostTriple: Triple, builtToolHandler: (_ name: String, _ path: RelativePath) throws -> AbsolutePath?) throws -> [String: (path: AbsolutePath, triples: [String]?)] { var pluginAccessibleTools: [String: (path: AbsolutePath, triples: [String]?)] = [:] for dep in try accessibleTools(packageGraph: packageGraph, fileSystem: fileSystem, environment: environment, for: hostTriple) { @@ -723,6 +739,7 @@ fileprivate extension Target.Dependency { switch self { case .target(_, let conditions): return conditions case .product(_, let conditions): return conditions + case .innerProduct(_, let conditions): return conditions } } @@ -744,7 +761,7 @@ public struct BuildToolPluginInvocationResult { public var package: ResolvedPackage /// The target in that package to which the plugin was applied. - public var target: ResolvedTarget + public var target: ResolvedModule /// If the plugin finished successfully. public var succeeded: Bool diff --git a/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift b/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift new file mode 100644 index 00000000000..f7fb52c75c4 --- /dev/null +++ b/Sources/SPMBuildCore/ResolvedPackage+Extensions.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct PackageGraph.ResolvedPackage +import struct PackageGraph.ResolvedModule + +extension ResolvedPackage { + package func packageNameArgument(target: ResolvedModule, isPackageNameSupported: Bool) -> [String] { + if self.manifest.usePackageNameFlag, target.packageAccess { + ["-package-name", self.identity.description.spm_mangledToC99ExtendedIdentifier()] + } else { + [] + } + } +} diff --git a/Sources/SPMBuildCore/Triple+Extensions.swift b/Sources/SPMBuildCore/Triple+Extensions.swift index bf9a1216c3d..38da6166597 100644 --- a/Sources/SPMBuildCore/Triple+Extensions.swift +++ b/Sources/SPMBuildCore/Triple+Extensions.swift @@ -23,7 +23,7 @@ extension Triple { } extension Triple { - public func platformBuildPathComponent(buildSystem: BuildSystemProvider.Kind) -> String { + package func platformBuildPathComponent(buildSystem: BuildSystemProvider.Kind) -> String { // Use "apple" as the subdirectory because in theory Xcode build system // can be used to build for any Apple platform and it has its own // conventions for build subpaths based on platforms. diff --git a/Sources/SPMTestSupport/GitRepositoryExtensions.swift b/Sources/SPMTestSupport/GitRepositoryExtensions.swift index 9f3c4db0d9c..5a61fd9925b 100644 --- a/Sources/SPMTestSupport/GitRepositoryExtensions.swift +++ b/Sources/SPMTestSupport/GitRepositoryExtensions.swift @@ -18,7 +18,7 @@ import enum TSCUtility.Git /// Extensions useful for unit testing purposes. /// Note: These are not thread safe. -public extension GitRepository { +package extension GitRepository { /// Create the repository using git init. func create() throws { try systemQuietly([Git.tool, "-C", self.path.pathString, "init"]) diff --git a/Sources/SPMTestSupport/InMemoryGitRepository.swift b/Sources/SPMTestSupport/InMemoryGitRepository.swift index 5d8ea5bc622..550dd3ed298 100644 --- a/Sources/SPMTestSupport/InMemoryGitRepository.swift +++ b/Sources/SPMTestSupport/InMemoryGitRepository.swift @@ -21,7 +21,7 @@ import struct TSCBasic.FileSystemError import class TSCBasic.InMemoryFileSystem /// The error encountered during in memory git repository operations. -public enum InMemoryGitRepositoryError: Swift.Error { +package enum InMemoryGitRepositoryError: Swift.Error { case unknownRevision case unknownTag case tagAlreadyPresent @@ -34,9 +34,9 @@ public enum InMemoryGitRepositoryError: Swift.Error { /// repository path, as well as on the file system interface of this class. /// Note: This class is intended to be used as testing infrastructure only. /// Note: This class is not thread safe yet. -public final class InMemoryGitRepository { +package final class InMemoryGitRepository { /// The revision identifier. - public typealias RevisionIdentifier = String + package typealias RevisionIdentifier = String /// A struct representing a revision state. Minimally it contains a hash identifier for the revision /// and the file system state. @@ -74,7 +74,7 @@ public final class InMemoryGitRepository { private let lock = NSLock() /// Create a new repository at the given path and filesystem. - public init(path: AbsolutePath, fs: InMemoryFileSystem) { + package init(path: AbsolutePath, fs: InMemoryFileSystem) { self.path = path self.fs = fs // Point head to a new revision state with empty hash to begin with. @@ -82,14 +82,14 @@ public final class InMemoryGitRepository { } /// The array of current tags in the repository. - public func getTags() throws -> [String] { + package func getTags() throws -> [String] { self.lock.withLock { Array(self.tagsMap.keys) } } /// The list of revisions in the repository. - public var revisions: [RevisionIdentifier] { + package var revisions: [RevisionIdentifier] { self.lock.withLock { Array(self.history.keys) } @@ -112,7 +112,7 @@ public final class InMemoryGitRepository { /// Commits the current state of the repository filesystem and returns the commit identifier. @discardableResult - public func commit(hash: String? = nil) throws -> String { + package func commit(hash: String? = nil) throws -> String { // Create a fake hash for this commit. let hash = hash ?? String((UUID().uuidString + UUID().uuidString).prefix(40)) self.lock.withLock { @@ -128,7 +128,7 @@ public final class InMemoryGitRepository { } /// Checks out the provided revision. - public func checkout(revision: RevisionIdentifier) throws { + package func checkout(revision: RevisionIdentifier) throws { guard let state = (self.lock.withLock { history[revision] }) else { throw InMemoryGitRepositoryError.unknownRevision } @@ -142,7 +142,7 @@ public final class InMemoryGitRepository { } /// Checks out a given tag. - public func checkout(tag: String) throws { + package func checkout(tag: String) throws { guard let hash = (self.lock.withLock { tagsMap[tag] }) else { throw InMemoryGitRepositoryError.unknownTag } @@ -191,7 +191,7 @@ public final class InMemoryGitRepository { } /// Tag the current HEAD with the given name. - public func tag(name: String) throws { + package func tag(name: String) throws { guard (self.lock.withLock { self.tagsMap[name] }) == nil else { throw InMemoryGitRepositoryError.tagAlreadyPresent } @@ -200,124 +200,124 @@ public final class InMemoryGitRepository { } } - public func hasUncommittedChanges() -> Bool { + package func hasUncommittedChanges() -> Bool { self.lock.withLock { isDirty } } - public func fetch() throws { + package func fetch() throws { // TODO. } } extension InMemoryGitRepository: FileSystem { - public func exists(_ path: TSCAbsolutePath, followSymlink: Bool) -> Bool { + package func exists(_ path: TSCAbsolutePath, followSymlink: Bool) -> Bool { self.lock.withLock { self.head.fileSystem.exists(path, followSymlink: followSymlink) } } - public func isDirectory(_ path: TSCAbsolutePath) -> Bool { + package func isDirectory(_ path: TSCAbsolutePath) -> Bool { self.lock.withLock { self.head.fileSystem.isDirectory(path) } } - public func isFile(_ path: TSCAbsolutePath) -> Bool { + package func isFile(_ path: TSCAbsolutePath) -> Bool { self.lock.withLock { self.head.fileSystem.isFile(path) } } - public func isSymlink(_ path: TSCAbsolutePath) -> Bool { + package func isSymlink(_ path: TSCAbsolutePath) -> Bool { self.lock.withLock { self.head.fileSystem.isSymlink(path) } } - public func isExecutableFile(_ path: TSCAbsolutePath) -> Bool { + package func isExecutableFile(_ path: TSCAbsolutePath) -> Bool { self.lock.withLock { self.head.fileSystem.isExecutableFile(path) } } - public func isReadable(_ path: TSCAbsolutePath) -> Bool { + package func isReadable(_ path: TSCAbsolutePath) -> Bool { return self.exists(path) } - public func isWritable(_ path: TSCAbsolutePath) -> Bool { + package func isWritable(_ path: TSCAbsolutePath) -> Bool { return false } - public var currentWorkingDirectory: TSCAbsolutePath? { + package var currentWorkingDirectory: TSCAbsolutePath? { return .root } - public func changeCurrentWorkingDirectory(to path: TSCAbsolutePath) throws { + package func changeCurrentWorkingDirectory(to path: TSCAbsolutePath) throws { throw FileSystemError(.unsupported, path) } - public var homeDirectory: TSCAbsolutePath { + package var homeDirectory: TSCAbsolutePath { fatalError("Unsupported") } - public var cachesDirectory: TSCAbsolutePath? { + package var cachesDirectory: TSCAbsolutePath? { fatalError("Unsupported") } - public var tempDirectory: TSCAbsolutePath { + package var tempDirectory: TSCAbsolutePath { fatalError("Unsupported") } - public func getDirectoryContents(_ path: TSCAbsolutePath) throws -> [String] { + package func getDirectoryContents(_ path: TSCAbsolutePath) throws -> [String] { try self.lock.withLock { try self.head.fileSystem.getDirectoryContents(path) } } - public func createDirectory(_ path: TSCAbsolutePath, recursive: Bool) throws { + package func createDirectory(_ path: TSCAbsolutePath, recursive: Bool) throws { try self.lock.withLock { try self.head.fileSystem.createDirectory(path, recursive: recursive) } } - public func createSymbolicLink(_ path: TSCAbsolutePath, pointingAt destination: TSCAbsolutePath, relative: Bool) throws { + package func createSymbolicLink(_ path: TSCAbsolutePath, pointingAt destination: TSCAbsolutePath, relative: Bool) throws { throw FileSystemError(.unsupported, path) } - public func readFileContents(_ path: TSCAbsolutePath) throws -> ByteString { + package func readFileContents(_ path: TSCAbsolutePath) throws -> ByteString { try self.lock.withLock { return try head.fileSystem.readFileContents(path) } } - public func writeFileContents(_ path: TSCAbsolutePath, bytes: ByteString) throws { + package func writeFileContents(_ path: TSCAbsolutePath, bytes: ByteString) throws { try self.lock.withLock { try self.head.fileSystem.writeFileContents(path, bytes: bytes) self.isDirty = true } } - public func removeFileTree(_ path: TSCAbsolutePath) throws { + package func removeFileTree(_ path: TSCAbsolutePath) throws { try self.lock.withLock { try self.head.fileSystem.removeFileTree(path) } } - public func chmod(_ mode: FileMode, path: TSCAbsolutePath, options: Set) throws { + package func chmod(_ mode: FileMode, path: TSCAbsolutePath, options: Set) throws { try self.lock.withLock { try self.head.fileSystem.chmod(mode, path: path, options: options) } } - public func copy(from sourcePath: TSCAbsolutePath, to destinationPath: TSCAbsolutePath) throws { + package func copy(from sourcePath: TSCAbsolutePath, to destinationPath: TSCAbsolutePath) throws { try self.lock.withLock { try self.head.fileSystem.copy(from: sourcePath, to: destinationPath) } } - public func move(from sourcePath: TSCAbsolutePath, to destinationPath: TSCAbsolutePath) throws { + package func move(from sourcePath: TSCAbsolutePath, to destinationPath: TSCAbsolutePath) throws { try self.lock.withLock { try self.head.fileSystem.move(from: sourcePath, to: destinationPath) } @@ -325,7 +325,7 @@ extension InMemoryGitRepository: FileSystem { } extension InMemoryGitRepository: Repository { - public func resolveRevision(tag: String) throws -> Revision { + package func resolveRevision(tag: String) throws -> Revision { try self.lock.withLock { guard let revision = self.tagsMap[tag] else { throw InternalError("unknown tag \(tag)") @@ -334,19 +334,19 @@ extension InMemoryGitRepository: Repository { } } - public func resolveRevision(identifier: String) throws -> Revision { + package func resolveRevision(identifier: String) throws -> Revision { self.lock.withLock { return Revision(identifier: self.tagsMap[identifier] ?? identifier) } } - public func exists(revision: Revision) -> Bool { + package func exists(revision: Revision) -> Bool { self.lock.withLock { return self.history[revision.identifier] != nil } } - public func openFileView(revision: Revision) throws -> FileSystem { + package func openFileView(revision: Revision) throws -> FileSystem { try self.lock.withLock { guard let entry = self.history[revision.identifier] else { throw InternalError("unknown revision \(revision)") @@ -355,70 +355,70 @@ extension InMemoryGitRepository: Repository { } } - public func openFileView(tag: String) throws -> FileSystem { + package func openFileView(tag: String) throws -> FileSystem { let revision = try self.resolveRevision(tag: tag) return try self.openFileView(revision: revision) } } extension InMemoryGitRepository: WorkingCheckout { - public func getCurrentRevision() throws -> Revision { + package func getCurrentRevision() throws -> Revision { self.lock.withLock { return Revision(identifier: self.head.hash) } } - public func checkout(revision: Revision) throws { + package func checkout(revision: Revision) throws { // will lock try checkout(revision: revision.identifier) } - public func hasUnpushedCommits() throws -> Bool { + package func hasUnpushedCommits() throws -> Bool { return false } - public func checkout(newBranch: String) throws { + package func checkout(newBranch: String) throws { self.lock.withLock { self.history[newBranch] = head } } - public func isAlternateObjectStoreValid(expected: AbsolutePath) -> Bool { + package func isAlternateObjectStoreValid(expected: AbsolutePath) -> Bool { return true } - public func areIgnored(_ paths: [AbsolutePath]) throws -> [Bool] { + package func areIgnored(_ paths: [AbsolutePath]) throws -> [Bool] { return [false] } } -// Public mutation of `InMemoryGitRepository` is protected with a lock. +// package mutation of `InMemoryGitRepository` is protected with a lock. extension InMemoryGitRepository: @unchecked Sendable {} /// This class implement provider for in memory git repository. -public final class InMemoryGitRepositoryProvider: RepositoryProvider { +package final class InMemoryGitRepositoryProvider: RepositoryProvider { /// Contains the repository added to this provider. - public var specifierMap = ThreadSafeKeyValueStore() + package var specifierMap = ThreadSafeKeyValueStore() /// Contains the repositories which are fetched using this provider. - public var fetchedMap = ThreadSafeKeyValueStore() + package var fetchedMap = ThreadSafeKeyValueStore() /// Contains the repositories which are checked out using this provider. - public var checkoutsMap = ThreadSafeKeyValueStore() + package var checkoutsMap = ThreadSafeKeyValueStore() /// Create a new provider. - public init() { + package init() { } /// Add a repository to this provider. Only the repositories added with this interface can be operated on /// with this provider. - public func add(specifier: RepositorySpecifier, repository: InMemoryGitRepository) { + package func add(specifier: RepositorySpecifier, repository: InMemoryGitRepository) { // Save the repository in specifier map. specifierMap[specifier] = repository } /// This method returns the stored reference to the git repository which was fetched or checked out. - public func openRepo(at path: AbsolutePath) throws -> InMemoryGitRepository { + package func openRepo(at path: AbsolutePath) throws -> InMemoryGitRepository { if let fetch = fetchedMap[path] { return fetch } @@ -431,7 +431,7 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider { // MARK: - RepositoryProvider conformance // Note: These methods use force unwrap (instead of throwing) to honor their preconditions. - public func fetch(repository: RepositorySpecifier, to path: AbsolutePath, progressHandler: FetchProgress.Handler? = nil) throws { + package func fetch(repository: RepositorySpecifier, to path: AbsolutePath, progressHandler: FetchProgress.Handler? = nil) throws { guard let repo = specifierMap[RepositorySpecifier(location: repository.location)] else { throw InternalError("unknown repo at \(repository.location)") } @@ -439,25 +439,25 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider { add(specifier: RepositorySpecifier(path: path), repository: repo) } - public func repositoryExists(at path: AbsolutePath) throws -> Bool { + package func repositoryExists(at path: AbsolutePath) throws -> Bool { return fetchedMap[path] != nil } - public func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws { + package func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws { guard let repo = fetchedMap[sourcePath] else { throw InternalError("unknown repo at \(sourcePath)") } fetchedMap[destinationPath] = try repo.copy() } - public func open(repository: RepositorySpecifier, at path: AbsolutePath) throws -> Repository { + package func open(repository: RepositorySpecifier, at path: AbsolutePath) throws -> Repository { guard let repository = self.fetchedMap[path] else { throw InternalError("unknown repository at \(path)") } return repository } - public func createWorkingCopy( + package func createWorkingCopy( repository: RepositorySpecifier, sourcePath: AbsolutePath, at destinationPath: AbsolutePath, @@ -471,26 +471,26 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider { return copy } - public func workingCopyExists(at path: AbsolutePath) throws -> Bool { + package func workingCopyExists(at path: AbsolutePath) throws -> Bool { return checkoutsMap.contains(path) } - public func openWorkingCopy(at path: AbsolutePath) throws -> WorkingCheckout { + package func openWorkingCopy(at path: AbsolutePath) throws -> WorkingCheckout { guard let checkout = checkoutsMap[path] else { throw InternalError("unknown checkout at \(path)") } return checkout } - public func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { + package func isValidDirectory(_ directory: AbsolutePath) throws -> Bool { return true } - public func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { + package func isValidDirectory(_ directory: AbsolutePath, for repository: RepositorySpecifier) throws -> Bool { return true } - public func cancel(deadline: DispatchTime) throws { + package func cancel(deadline: DispatchTime) throws { // noop } } diff --git a/Sources/SPMTestSupport/ManifestExtensions.swift b/Sources/SPMTestSupport/ManifestExtensions.swift index 30cbd5bc3a2..0d51ed6c9e7 100644 --- a/Sources/SPMTestSupport/ManifestExtensions.swift +++ b/Sources/SPMTestSupport/ManifestExtensions.swift @@ -17,7 +17,7 @@ import PackageModel import struct TSCUtility.Version extension Manifest { - public static func createRootManifest( + package static func createRootManifest( displayName: String, path: AbsolutePath = .root, defaultLocalization: String? = nil, @@ -53,7 +53,7 @@ extension Manifest { ) } - public static func createFileSystemManifest( + package static func createFileSystemManifest( displayName: String, path: AbsolutePath, defaultLocalization: String? = nil, @@ -89,7 +89,7 @@ extension Manifest { ) } - public static func createLocalSourceControlManifest( + package static func createLocalSourceControlManifest( displayName: String, path: AbsolutePath, defaultLocalization: String? = nil, @@ -125,7 +125,7 @@ extension Manifest { ) } - public static func createRemoteSourceControlManifest( + package static func createRemoteSourceControlManifest( displayName: String, url: SourceControlURL, path: AbsolutePath, @@ -162,7 +162,7 @@ extension Manifest { ) } - public static func createRegistryManifest( + package static func createRegistryManifest( displayName: String, identity: PackageIdentity, path: AbsolutePath = .root, @@ -199,7 +199,7 @@ extension Manifest { ) } - public static func createManifest( + package static func createManifest( displayName: String, path: AbsolutePath = .root, packageKind: PackageReference.Kind, @@ -238,7 +238,7 @@ extension Manifest { ) } - public func with(location: String) -> Manifest { + package func with(location: String) -> Manifest { Manifest( displayName: self.displayName, path: self.path, diff --git a/Sources/SPMTestSupport/MockArchiver.swift b/Sources/SPMTestSupport/MockArchiver.swift index 2bfc63546a3..cfc57d3f2d7 100644 --- a/Sources/SPMTestSupport/MockArchiver.swift +++ b/Sources/SPMTestSupport/MockArchiver.swift @@ -12,53 +12,53 @@ import Basics -public class MockArchiver: Archiver { - public typealias ExtractionHandler = ( +package final class MockArchiver: Archiver { + package typealias ExtractionHandler = ( MockArchiver, AbsolutePath, AbsolutePath, (Result) -> Void ) throws -> Void - public typealias CompressionHandler = ( + package typealias CompressionHandler = ( MockArchiver, AbsolutePath, AbsolutePath, (Result) -> Void ) throws -> Void - public typealias ValidationHandler = (MockArchiver, AbsolutePath, (Result) -> Void) throws -> Void + package typealias ValidationHandler = (MockArchiver, AbsolutePath, (Result) -> Void) throws -> Void - public struct Extraction: Equatable { - public let archivePath: AbsolutePath - public let destinationPath: AbsolutePath + package struct Extraction: Equatable { + package let archivePath: AbsolutePath + package let destinationPath: AbsolutePath - public init(archivePath: AbsolutePath, destinationPath: AbsolutePath) { + package init(archivePath: AbsolutePath, destinationPath: AbsolutePath) { self.archivePath = archivePath self.destinationPath = destinationPath } } - public struct Compression: Equatable { - public let directory: AbsolutePath - public let destinationPath: AbsolutePath + package struct Compression: Equatable { + package let directory: AbsolutePath + package let destinationPath: AbsolutePath - public init(directory: AbsolutePath, destinationPath: AbsolutePath) { + package init(directory: AbsolutePath, destinationPath: AbsolutePath) { self.directory = directory self.destinationPath = destinationPath } } - public let supportedExtensions: Set = ["zip"] - public let extractions = ThreadSafeArrayStore() - public let compressions = ThreadSafeArrayStore() - public let extractionHandler: ExtractionHandler? - public let compressionHandler: CompressionHandler? - public let validationHandler: ValidationHandler? + package let supportedExtensions: Set = ["zip"] + package let extractions = ThreadSafeArrayStore() + package let compressions = ThreadSafeArrayStore() + package let extractionHandler: ExtractionHandler? + package let compressionHandler: CompressionHandler? + package let validationHandler: ValidationHandler? - public convenience init(handler: ExtractionHandler? = .none) { + package convenience init(handler: ExtractionHandler? = .none) { self.init(extractionHandler: handler, compressionHandler: .none, validationHandler: .none) } - public init( + package init( extractionHandler: ExtractionHandler? = .none, compressionHandler: CompressionHandler? = .none, validationHandler: ValidationHandler? = .none @@ -68,7 +68,7 @@ public class MockArchiver: Archiver { self.validationHandler = validationHandler } - public func extract( + package func extract( from archivePath: AbsolutePath, to destinationPath: AbsolutePath, completion: @escaping (Result) -> Void @@ -85,7 +85,7 @@ public class MockArchiver: Archiver { } } - public func compress( + package func compress( directory: AbsolutePath, to destinationPath: AbsolutePath, completion: @escaping (Result) -> Void @@ -102,7 +102,7 @@ public class MockArchiver: Archiver { } } - public func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { + package func validate(path: AbsolutePath, completion: @escaping (Result) -> Void) { do { if let handler = self.validationHandler { try handler(self, path, completion) diff --git a/Sources/SPMTestSupport/MockBuildTestHelper.swift b/Sources/SPMTestSupport/MockBuildTestHelper.swift index d18050e953d..798204d51b1 100644 --- a/Sources/SPMTestSupport/MockBuildTestHelper.swift +++ b/Sources/SPMTestSupport/MockBuildTestHelper.swift @@ -12,38 +12,38 @@ import Basics -@_spi(SwiftPMInternal) import Build +import struct PackageGraph.ResolvedModule +import struct PackageGraph.ResolvedProduct import PackageModel import SPMBuildCore import TSCUtility import XCTest -public struct MockToolchain: PackageModel.Toolchain { +package struct MockToolchain: PackageModel.Toolchain { #if os(Windows) - public let librarianPath = AbsolutePath("/fake/path/to/link.exe") + package let librarianPath = AbsolutePath("/fake/path/to/link.exe") #elseif os(iOS) || os(macOS) || os(tvOS) || os(watchOS) - public let librarianPath = AbsolutePath("/fake/path/to/libtool") + package let librarianPath = AbsolutePath("/fake/path/to/libtool") #else - public let librarianPath = AbsolutePath("/fake/path/to/llvm-ar") + package let librarianPath = AbsolutePath("/fake/path/to/llvm-ar") #endif - public let swiftCompilerPath = AbsolutePath("/fake/path/to/swiftc") - public let includeSearchPaths = [AbsolutePath]() - public let librarySearchPaths = [AbsolutePath]() - public let swiftResourcesPath: AbsolutePath? = nil - public let swiftStaticResourcesPath: AbsolutePath? = nil - public let isSwiftDevelopmentToolchain = false - public let sdkRootPath: AbsolutePath? = nil - public let swiftPluginServerPath: AbsolutePath? = nil - public let extraFlags = PackageModel.BuildFlags() - public let installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default - - public func getClangCompiler() throws -> AbsolutePath { + package let swiftCompilerPath = AbsolutePath("/fake/path/to/swiftc") + package let includeSearchPaths = [AbsolutePath]() + package let librarySearchPaths = [AbsolutePath]() + package let swiftResourcesPath: AbsolutePath? + package let swiftStaticResourcesPath: AbsolutePath? = nil + package let sdkRootPath: AbsolutePath? = nil + package let extraFlags = PackageModel.BuildFlags() + package let installedSwiftPMConfiguration = InstalledSwiftPMConfiguration.default + package let providedLibraries = [ProvidedLibrary]() + + package func getClangCompiler() throws -> AbsolutePath { "/fake/path/to/clang" } - public func _isClangCompilerVendorApple() throws -> Bool? { + package func _isClangCompilerVendorApple() throws -> Bool? { #if os(macOS) return true #else @@ -51,35 +51,37 @@ public struct MockToolchain: PackageModel.Toolchain { #endif } - public init() {} + package init(swiftResourcesPath: AbsolutePath? = nil) { + self.swiftResourcesPath = swiftResourcesPath + } } extension Basics.Triple { - public static let x86_64MacOS = try! Self("x86_64-apple-macosx") - public static let x86_64Linux = try! Self("x86_64-unknown-linux-gnu") - public static let arm64Linux = try! Self("aarch64-unknown-linux-gnu") - public static let arm64Android = try! Self("aarch64-unknown-linux-android") - public static let windows = try! Self("x86_64-unknown-windows-msvc") - public static let wasi = try! Self("wasm32-unknown-wasi") - public static let arm64iOS = try! Self("arm64-apple-ios") + package static let x86_64MacOS = try! Self("x86_64-apple-macosx") + package static let x86_64Linux = try! Self("x86_64-unknown-linux-gnu") + package static let arm64Linux = try! Self("aarch64-unknown-linux-gnu") + package static let arm64Android = try! Self("aarch64-unknown-linux-android") + package static let windows = try! Self("x86_64-unknown-windows-msvc") + package static let wasi = try! Self("wasm32-unknown-wasi") + package static let arm64iOS = try! Self("arm64-apple-ios") } -public let hostTriple = try! UserToolchain.default.targetTriple +package let hostTriple = try! UserToolchain.default.targetTriple #if os(macOS) -public let defaultTargetTriple: String = hostTriple.tripleString(forPlatformVersion: "10.13") +package let defaultTargetTriple: String = hostTriple.tripleString(forPlatformVersion: "10.13") #else -public let defaultTargetTriple: String = hostTriple.tripleString +package let defaultTargetTriple: String = hostTriple.tripleString #endif -public func mockBuildParameters( - buildPath: AbsolutePath = "/path/to/build", +package func mockBuildParameters( + buildPath: AbsolutePath? = nil, config: BuildConfiguration = .debug, toolchain: PackageModel.Toolchain = MockToolchain(), flags: PackageModel.BuildFlags = PackageModel.BuildFlags(), shouldLinkStaticSwiftStdlib: Bool = false, shouldDisableLocalRpath: Bool = false, canRenameEntrypointFunctionName: Bool = false, - targetTriple: Basics.Triple = hostTriple, + triple: Basics.Triple = hostTriple, indexStoreMode: BuildParameters.IndexStoreMode = .off, useExplicitModuleBuild: Bool = false, linkerDeadStrip: Bool = true, @@ -87,16 +89,16 @@ public func mockBuildParameters( omitFramePointers: Bool? = nil ) -> BuildParameters { try! BuildParameters( - dataPath: buildPath, + dataPath: buildPath ?? AbsolutePath("/path/to/build").appending(triple.tripleString), configuration: config, toolchain: toolchain, - triple: targetTriple, + triple: triple, flags: flags, pkgConfigDirectories: [], workers: 3, indexStoreMode: indexStoreMode, debuggingParameters: .init( - triple: targetTriple, + triple: triple, shouldEnableDebuggingEntitlement: config == .debug, omitFramePointers: omitFramePointers ), @@ -113,7 +115,7 @@ public func mockBuildParameters( ) } -public func mockBuildParameters(environment: BuildEnvironment) -> BuildParameters { +package func mockBuildParameters(environment: BuildEnvironment) -> BuildParameters { let triple: Basics.Triple switch environment.platform { case .macOS: @@ -128,24 +130,24 @@ public func mockBuildParameters(environment: BuildEnvironment) -> BuildParameter fatalError("unsupported platform in tests") } - return mockBuildParameters(config: environment.configuration ?? .debug, targetTriple: triple) + return mockBuildParameters(config: environment.configuration ?? .debug, triple: triple) } enum BuildError: Swift.Error { case error(String) } -public struct BuildPlanResult { - public let plan: Build.BuildPlan - public let targetMap: [String: TargetBuildDescription] - public let productMap: [String: Build.ProductBuildDescription] +package struct BuildPlanResult { + package let plan: Build.BuildPlan + package let targetMap: [ResolvedModule.ID: TargetBuildDescription] + package let productMap: [ResolvedProduct.ID: Build.ProductBuildDescription] - public init(plan: Build.BuildPlan) throws { + package init(plan: Build.BuildPlan) throws { self.plan = plan self.productMap = try Dictionary( throwingUniqueKeysWithValues: plan.buildProducts .compactMap { $0 as? Build.ProductBuildDescription } - .map { ($0.product.name, $0) } + .map { ($0.product.id, $0) } ) self.targetMap = try Dictionary( throwingUniqueKeysWithValues: plan.targetMap.compactMap { @@ -155,37 +157,47 @@ public struct BuildPlanResult { else { throw BuildError.error("Target \($0) not found.") } - return (target.name, $1) + return (target.id, $1) } ) } - public func checkTargetsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) { + package func checkTargetsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(self.plan.targetMap.count, count, file: file, line: line) } - public func checkProductsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) { + package func checkProductsCount(_ count: Int, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(self.plan.productMap.count, count, file: file, line: line) } - public func target(for name: String) throws -> TargetBuildDescription { - guard let target = targetMap[name] else { - throw BuildError.error("Target \(name) not found.") + package func target(for name: String) throws -> TargetBuildDescription { + let matchingIDs = targetMap.keys.filter({ $0.targetName == name }) + guard matchingIDs.count == 1, let target = targetMap[matchingIDs[0]] else { + if matchingIDs.isEmpty { + throw BuildError.error("Target \(name) not found.") + } else { + throw BuildError.error("More than one target \(name) found.") + } } return target } - public func buildProduct(for name: String) throws -> Build.ProductBuildDescription { - guard let product = productMap[name] else { - // Display the thrown error on macOS - throw BuildError.error("Product \(name) not found.") + package func buildProduct(for name: String) throws -> Build.ProductBuildDescription { + let matchingIDs = productMap.keys.filter({ $0.productName == name }) + guard matchingIDs.count == 1, let product = productMap[matchingIDs[0]] else { + if matchingIDs.isEmpty { + // Display the thrown error on macOS + throw BuildError.error("Product \(name) not found.") + } else { + throw BuildError.error("More than one target \(name) found.") + } } return product } } extension TargetBuildDescription { - public func swiftTarget() throws -> SwiftTargetBuildDescription { + package func swiftTarget() throws -> SwiftTargetBuildDescription { switch self { case .swift(let target): return target @@ -194,7 +206,7 @@ extension TargetBuildDescription { } } - public func clangTarget() throws -> ClangTargetBuildDescription { + package func clangTarget() throws -> ClangTargetBuildDescription { switch self { case .clang(let target): return target diff --git a/Sources/SPMTestSupport/MockDependency.swift b/Sources/SPMTestSupport/MockDependency.swift index 5d519724217..321f54214a9 100644 --- a/Sources/SPMTestSupport/MockDependency.swift +++ b/Sources/SPMTestSupport/MockDependency.swift @@ -15,13 +15,13 @@ import Foundation import PackageLoading import PackageModel -public typealias SourceControlRequirement = PackageDependency.SourceControl.Requirement -public typealias RegistryRequirement = PackageDependency.Registry.Requirement +package typealias SourceControlRequirement = PackageDependency.SourceControl.Requirement +package typealias RegistryRequirement = PackageDependency.Registry.Requirement -public struct MockDependency { - public let deprecatedName: String? - public let location: Location - public let products: ProductFilter +package struct MockDependency { + package let deprecatedName: String? + package let location: Location + package let products: ProductFilter init(deprecatedName: String? = nil, location: Location, products: ProductFilter = .everything) { self.deprecatedName = deprecatedName @@ -29,7 +29,7 @@ public struct MockDependency { self.products = products } - public func convert(baseURL: AbsolutePath, identityResolver: IdentityResolver) throws -> PackageDependency { + package func convert(baseURL: AbsolutePath, identityResolver: IdentityResolver) throws -> PackageDependency { switch self.location { case .fileSystem(let path): let absolutePath = baseURL.appending(path) @@ -119,39 +119,39 @@ public struct MockDependency { } - public static func fileSystem(path: String, products: ProductFilter = .everything) -> MockDependency { + package static func fileSystem(path: String, products: ProductFilter = .everything) -> MockDependency { try! MockDependency(location: .fileSystem(path: RelativePath(validating: path)), products: products) } - public static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func sourceControl(path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { try! .sourceControl(path: RelativePath(validating: path), requirement: requirement, products: products) } - public static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func sourceControl(path: RelativePath, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { MockDependency(location: .localSourceControl(path: path, requirement: requirement), products: products) } - public static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func sourceControlWithDeprecatedName(name: String, path: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { try! MockDependency(deprecatedName: name, location: .localSourceControl(path: RelativePath(validating: path), requirement: requirement), products: products) } - public static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func sourceControl(url: String, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { .sourceControl(url: SourceControlURL(url), requirement: requirement, products: products) } - public static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func sourceControl(url: SourceControlURL, requirement: SourceControlRequirement, products: ProductFilter = .everything) -> MockDependency { MockDependency(location: .remoteSourceControl(url: url, requirement: requirement), products: products) } - public static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func registry(identity: String, requirement: RegistryRequirement, products: ProductFilter = .everything) -> MockDependency { .registry(identity: .plain(identity), requirement: requirement) } - public static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything) -> MockDependency { + package static func registry(identity: PackageIdentity, requirement: RegistryRequirement, products: ProductFilter = .everything) -> MockDependency { MockDependency(location: .registry(identity: identity, requirement: requirement), products: products) } - public enum Location { + package enum Location { case fileSystem(path: RelativePath) case localSourceControl(path: RelativePath, requirement: SourceControlRequirement) case remoteSourceControl(url: SourceControlURL, requirement: SourceControlRequirement) diff --git a/Sources/SPMTestSupport/MockDependencyGraph.swift b/Sources/SPMTestSupport/MockDependencyGraph.swift index 6a17968ab0b..5b2c8488654 100644 --- a/Sources/SPMTestSupport/MockDependencyGraph.swift +++ b/Sources/SPMTestSupport/MockDependencyGraph.swift @@ -16,20 +16,20 @@ import PackageModel import struct TSCUtility.Version -public struct MockDependencyGraph { - public let name: String - public let constraints: [MockPackageContainer.Constraint] - public let containers: [MockPackageContainer] - public let result: [PackageReference: Version] +package struct MockDependencyGraph { + package let name: String + package let constraints: [MockPackageContainer.Constraint] + package let containers: [MockPackageContainer] + package let result: [PackageReference: Version] - public init(name: String, constraints: [MockPackageContainer.Constraint], containers: [MockPackageContainer], result: [PackageReference : Version]) { + package init(name: String, constraints: [MockPackageContainer.Constraint], containers: [MockPackageContainer], result: [PackageReference : Version]) { self.name = name self.constraints = constraints self.containers = containers self.result = result } - public func checkResult( + package func checkResult( _ output: [(container: PackageReference, version: Version)], file: StaticString = #file, line: UInt = #line diff --git a/Sources/SPMTestSupport/MockHTTPClient.swift b/Sources/SPMTestSupport/MockHTTPClient.swift index c680bcc2612..87474faf0dc 100644 --- a/Sources/SPMTestSupport/MockHTTPClient.swift +++ b/Sources/SPMTestSupport/MockHTTPClient.swift @@ -13,7 +13,7 @@ import Basics extension LegacyHTTPClient { - public static func mock(fileSystem: FileSystem) -> LegacyHTTPClient { + package static func mock(fileSystem: FileSystem) -> LegacyHTTPClient { let handler: LegacyHTTPClient.Handler = { request, _, completion in switch request.kind { case.generic: diff --git a/Sources/SPMTestSupport/MockHashAlgorithm.swift b/Sources/SPMTestSupport/MockHashAlgorithm.swift index 4d8c88e4e81..374e4c77874 100644 --- a/Sources/SPMTestSupport/MockHashAlgorithm.swift +++ b/Sources/SPMTestSupport/MockHashAlgorithm.swift @@ -15,17 +15,17 @@ import Basics import struct TSCBasic.ByteString import protocol TSCBasic.HashAlgorithm -public final class MockHashAlgorithm { - public typealias Handler = @Sendable (ByteString) -> ByteString +package final class MockHashAlgorithm { + package typealias Handler = @Sendable (ByteString) -> ByteString - public let hashes = ThreadSafeArrayStore() + package let hashes = ThreadSafeArrayStore() private let handler: Handler? - public init(handler: Handler? = nil) { + package init(handler: Handler? = nil) { self.handler = handler } - public func hash(_ hash: ByteString) -> ByteString { + package func hash(_ hash: ByteString) -> ByteString { if let handler = self.handler { return handler(hash) } else { diff --git a/Sources/SPMTestSupport/MockManifestLoader.swift b/Sources/SPMTestSupport/MockManifestLoader.swift index f1b0ebfd56a..fc1f345ed64 100644 --- a/Sources/SPMTestSupport/MockManifestLoader.swift +++ b/Sources/SPMTestSupport/MockManifestLoader.swift @@ -18,9 +18,10 @@ import PackageGraph import func XCTest.XCTFail +import enum TSCBasic.ProcessEnv import struct TSCUtility.Version -public enum MockManifestLoaderError: Swift.Error { +package enum MockManifestLoaderError: Swift.Error { case unknownRequest(String) } @@ -33,24 +34,24 @@ public enum MockManifestLoaderError: Swift.Error { /// /// This implementation will throw an error if a request to load an unknown /// manifest is made. -public final class MockManifestLoader: ManifestLoaderProtocol { - public struct Key: Hashable { - public let url: String - public let version: Version? +package final class MockManifestLoader: ManifestLoaderProtocol { + package struct Key: Hashable { + package let url: String + package let version: Version? - public init(url: String, version: Version? = nil) { + package init(url: String, version: Version? = nil) { self.url = url self.version = version } } - public let manifests: ThreadSafeKeyValueStore + package let manifests: ThreadSafeKeyValueStore - public init(manifests: [Key: Manifest]) { + package init(manifests: [Key: Manifest]) { self.manifests = ThreadSafeKeyValueStore(manifests) } - public func load( + package func load( manifestPath: AbsolutePath, manifestToolsVersion: ToolsVersion, packageIdentity: PackageIdentity, @@ -75,12 +76,12 @@ public final class MockManifestLoader: ManifestLoaderProtocol { } } - public func resetCache(observabilityScope: ObservabilityScope) {} - public func purgeCache(observabilityScope: ObservabilityScope) {} + package func resetCache(observabilityScope: ObservabilityScope) {} + package func purgeCache(observabilityScope: ObservabilityScope) {} } extension ManifestLoader { - public func load( + package func load( manifestPath: AbsolutePath, packageKind: PackageReference.Kind, toolsVersion manifestToolsVersion: ToolsVersion, @@ -88,7 +89,7 @@ extension ManifestLoader { dependencyMapper: DependencyMapper? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope - ) throws -> Manifest{ + ) async throws -> Manifest{ let packageIdentity: PackageIdentity let packageLocation: String switch packageKind { @@ -109,29 +110,25 @@ extension ManifestLoader { // FIXME: placeholder packageLocation = identity.description } - return try temp_await { - self.load( - manifestPath: manifestPath, - manifestToolsVersion: manifestToolsVersion, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: nil, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), - fileSystem: fileSystem, - observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) - } + return try await self.load( + manifestPath: manifestPath, + manifestToolsVersion: manifestToolsVersion, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: nil, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), + fileSystem: fileSystem, + observabilityScope: observabilityScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) } } - extension ManifestLoader { - public func load( + package func load( packagePath: AbsolutePath, packageKind: PackageReference.Kind, currentToolsVersion: ToolsVersion, @@ -139,7 +136,7 @@ extension ManifestLoader { dependencyMapper: DependencyMapper? = .none, fileSystem: FileSystem, observabilityScope: ObservabilityScope - ) throws -> Manifest{ + ) async throws -> Manifest{ let packageIdentity: PackageIdentity let packageLocation: String switch packageKind { @@ -160,22 +157,44 @@ extension ManifestLoader { // FIXME: placeholder packageLocation = identity.description } - return try temp_await { - self.load( - packagePath: packagePath, - packageIdentity: packageIdentity, - packageKind: packageKind, - packageLocation: packageLocation, - packageVersion: nil, - currentToolsVersion: currentToolsVersion, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), - fileSystem: fileSystem, - observabilityScope: observabilityScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent, - completion: $0 - ) + return try await self.load( + packagePath: packagePath, + packageIdentity: packageIdentity, + packageKind: packageKind, + packageLocation: packageLocation, + packageVersion: nil, + currentToolsVersion: currentToolsVersion, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper ?? DefaultDependencyMapper(identityResolver: identityResolver), + fileSystem: fileSystem, + observabilityScope: observabilityScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) + } +} + +/// Temporary override environment variables +/// +/// WARNING! This method is not thread-safe. POSIX environments are shared +/// between threads. This means that when this method is called simultaneously +/// from different threads, the environment will neither be setup nor restored +/// correctly. +package func withCustomEnv(_ env: [String: String], body: () async throws -> Void) async throws { + let state = env.map { ($0, $1) } + let restore = { + for (key, value) in state { + try ProcessEnv.setVar(key, value: value) + } + } + do { + for (key, value) in env { + try ProcessEnv.setVar(key, value: value) } + try await body() + } catch { + try? restore() + throw error } + try restore() } diff --git a/Sources/SPMTestSupport/MockPackage.swift b/Sources/SPMTestSupport/MockPackage.swift index d692b883930..ed56edcb2a7 100644 --- a/Sources/SPMTestSupport/MockPackage.swift +++ b/Sources/SPMTestSupport/MockPackage.swift @@ -14,20 +14,20 @@ import Basics import Foundation import PackageModel -public struct MockPackage { - public let name: String - public let platforms: [PlatformDescription] - public let location: Location - public let targets: [MockTarget] - public let products: [MockProduct] - public let dependencies: [MockDependency] - public let versions: [String?] +package struct MockPackage { + package let name: String + package let platforms: [PlatformDescription] + package let location: Location + package let targets: [MockTarget] + package let products: [MockProduct] + package let dependencies: [MockDependency] + package let versions: [String?] /// Provides revision identifier for the given version. A random identifier might be assigned if this is nil. - public let revisionProvider: ((String) -> String)? + package let revisionProvider: ((String) -> String)? // FIXME: This should be per-version. - public let toolsVersion: ToolsVersion? + package let toolsVersion: ToolsVersion? - public init( + package init( name: String, platforms: [PlatformDescription] = [], path: String? = nil, @@ -50,7 +50,7 @@ public struct MockPackage { self.toolsVersion = toolsVersion } - public init( + package init( name: String, platforms: [PlatformDescription] = [], url: String, @@ -72,7 +72,7 @@ public struct MockPackage { self.toolsVersion = toolsVersion } - public init( + package init( name: String, platforms: [PlatformDescription] = [], identity: String, @@ -100,7 +100,7 @@ public struct MockPackage { self.toolsVersion = toolsVersion } - public static func genericPackage(named name: String) throws -> MockPackage { + package static func genericPackage(named name: String) throws -> MockPackage { return MockPackage( name: name, targets: [ @@ -113,7 +113,7 @@ public struct MockPackage { ) } - public enum Location { + package enum Location { case fileSystem(path: RelativePath) case sourceControl(url: SourceControlURL) case registry(identity: PackageIdentity, alternativeURLs: [URL]?, metadata: RegistryReleaseMetadata?) diff --git a/Sources/SPMTestSupport/MockPackageContainer.swift b/Sources/SPMTestSupport/MockPackageContainer.swift index 730ddffe12b..0dc608943fc 100644 --- a/Sources/SPMTestSupport/MockPackageContainer.swift +++ b/Sources/SPMTestSupport/MockPackageContainer.swift @@ -19,12 +19,12 @@ import XCTest import struct TSCUtility.Version -public class MockPackageContainer: CustomPackageContainer { - public typealias Constraint = PackageContainerConstraint +package class MockPackageContainer: CustomPackageContainer { + package typealias Constraint = PackageContainerConstraint - public typealias Dependency = (container: PackageReference, requirement: PackageRequirement) + package typealias Dependency = (container: PackageReference, requirement: PackageRequirement) - public let package: PackageReference + package let package: PackageReference let dependencies: [String: [Dependency]] let filteredMode: Bool @@ -32,26 +32,26 @@ public class MockPackageContainer: CustomPackageContainer { let fileSystem: FileSystem? let customRetrievalPath: AbsolutePath? - public var unversionedDeps: [MockPackageContainer.Constraint] = [] + package var unversionedDeps: [MockPackageContainer.Constraint] = [] /// Contains the versions for which the dependencies were requested by resolver using getDependencies(). - public var requestedVersions: Set = [] + package var requestedVersions: Set = [] - public let _versions: [Version] - public func toolsVersionsAppropriateVersionsDescending() throws -> [Version] { + package let _versions: [Version] + package func toolsVersionsAppropriateVersionsDescending() throws -> [Version] { return try self.versionsDescending() } - public func versionsAscending() throws -> [Version] { + package func versionsAscending() throws -> [Version] { return _versions } - public func getDependencies(at version: Version, productFilter: ProductFilter) -> [MockPackageContainer.Constraint] { + package func getDependencies(at version: Version, productFilter: ProductFilter) -> [MockPackageContainer.Constraint] { requestedVersions.insert(version) return getDependencies(at: version.description, productFilter: productFilter) } - public func getDependencies(at revision: String, productFilter: ProductFilter) -> [MockPackageContainer.Constraint] { + package func getDependencies(at revision: String, productFilter: ProductFilter) -> [MockPackageContainer.Constraint] { let dependencies: [Dependency] if filteredMode { dependencies = filteredDependencies[productFilter]! @@ -64,27 +64,27 @@ public class MockPackageContainer: CustomPackageContainer { } } - public func getUnversionedDependencies(productFilter: ProductFilter) -> [MockPackageContainer.Constraint] { + package func getUnversionedDependencies(productFilter: ProductFilter) -> [MockPackageContainer.Constraint] { return unversionedDeps } - public func loadPackageReference(at boundVersion: BoundVersion) throws -> PackageReference { + package func loadPackageReference(at boundVersion: BoundVersion) throws -> PackageReference { return self.package } - public func isToolsVersionCompatible(at version: Version) -> Bool { + package func isToolsVersionCompatible(at version: Version) -> Bool { return true } - public func toolsVersion(for version: Version) throws -> ToolsVersion { + package func toolsVersion(for version: Version) throws -> ToolsVersion { return ToolsVersion.current } - public var isRemoteContainer: Bool? { + package var isRemoteContainer: Bool? { return true } - public func retrieve(at version: Version, progressHandler: ((Int64, Int64?) -> Void)?, observabilityScope: ObservabilityScope) throws -> AbsolutePath { + package func retrieve(at version: Version, progressHandler: ((Int64, Int64?) -> Void)?, observabilityScope: ObservabilityScope) throws -> AbsolutePath { if let customRetrievalPath { return customRetrievalPath } else { @@ -92,11 +92,11 @@ public class MockPackageContainer: CustomPackageContainer { } } - public func getFileSystem() throws -> FileSystem? { + package func getFileSystem() throws -> FileSystem? { return fileSystem } - public convenience init( + package convenience init( name: String, dependenciesByVersion: [Version: [(container: String, versionRequirement: VersionSetSpecifier)]] ) throws { @@ -113,7 +113,7 @@ public class MockPackageContainer: CustomPackageContainer { self.init(package: ref, dependencies: dependencies) } - public init( + package init( package: PackageReference, dependencies: [String: [Dependency]] = [:], fileSystem: FileSystem? = nil, @@ -129,7 +129,7 @@ public class MockPackageContainer: CustomPackageContainer { self.customRetrievalPath = customRetrievalPath } - public init( + package init( name: String, dependenciesByProductFilter: [ProductFilter: [(container: String, versionRequirement: VersionSetSpecifier)]] ) throws { @@ -154,16 +154,16 @@ public class MockPackageContainer: CustomPackageContainer { } } -public struct MockPackageContainerProvider: PackageContainerProvider { - public let containers: [MockPackageContainer] - public let containersByIdentifier: [PackageReference: MockPackageContainer] +package struct MockPackageContainerProvider: PackageContainerProvider { + package let containers: [MockPackageContainer] + package let containersByIdentifier: [PackageReference: MockPackageContainer] - public init(containers: [MockPackageContainer]) { + package init(containers: [MockPackageContainer]) { self.containers = containers self.containersByIdentifier = Dictionary(uniqueKeysWithValues: containers.map { ($0.package, $0) }) } - public func getContainer( + package func getContainer( for package: PackageReference, updateStrategy: ContainerUpdateStrategy, observabilityScope: ObservabilityScope, diff --git a/Sources/SPMTestSupport/MockPackageFingerprintStorage.swift b/Sources/SPMTestSupport/MockPackageFingerprintStorage.swift index 2c331346ad5..fe08d4fc8d9 100644 --- a/Sources/SPMTestSupport/MockPackageFingerprintStorage.swift +++ b/Sources/SPMTestSupport/MockPackageFingerprintStorage.swift @@ -18,18 +18,18 @@ import PackageModel import struct TSCUtility.Version -public class MockPackageFingerprintStorage: PackageFingerprintStorage { +package class MockPackageFingerprintStorage: PackageFingerprintStorage { private var packageFingerprints: [PackageIdentity: [Version: [Fingerprint .Kind: [Fingerprint.ContentType: Fingerprint]]]] private let lock = NSLock() - public init(_ packageFingerprints: [PackageIdentity: [Version: [Fingerprint + package init(_ packageFingerprints: [PackageIdentity: [Version: [Fingerprint .Kind: [Fingerprint.ContentType: Fingerprint]]]] = [:]) { self.packageFingerprints = packageFingerprints } - public func get( + package func get( package: PackageIdentity, version: Version, observabilityScope: ObservabilityScope, @@ -47,7 +47,7 @@ public class MockPackageFingerprintStorage: PackageFingerprintStorage { } } - public func put( + package func put( package: PackageIdentity, version: Version, fingerprint: Fingerprint, @@ -86,7 +86,7 @@ public class MockPackageFingerprintStorage: PackageFingerprintStorage { } } - public func get( + package func get( package: PackageReference, version: Version, observabilityScope: ObservabilityScope, @@ -102,7 +102,7 @@ public class MockPackageFingerprintStorage: PackageFingerprintStorage { ) } - public func put( + package func put( package: PackageReference, version: Version, fingerprint: Fingerprint, diff --git a/Sources/SPMTestSupport/MockPackageGraphs.swift b/Sources/SPMTestSupport/MockPackageGraphs.swift new file mode 100644 index 00000000000..df828675119 --- /dev/null +++ b/Sources/SPMTestSupport/MockPackageGraphs.swift @@ -0,0 +1,395 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath +import class Basics.ObservabilitySystem +import class Basics.ObservabilityScope + +import struct PackageGraph.ModulesGraph + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +import func PackageGraph.loadModulesGraph + +import class PackageModel.Manifest +import struct PackageModel.ProductDescription +import enum PackageModel.ProductType +import struct PackageModel.TargetDescription +import protocol TSCBasic.FileSystem +import class TSCBasic.InMemoryFileSystem + +package typealias MockPackageGraph = ( + graph: ModulesGraph, + fileSystem: any FileSystem, + observabilityScope: ObservabilityScope +) + +package func macrosPackageGraph() throws -> MockPackageGraph { + let fs = InMemoryFileSystem(emptyFiles: + "/swift-firmware/Sources/Core/source.swift", + "/swift-firmware/Sources/HAL/source.swift", + "/swift-firmware/Tests/CoreTests/source.swift", + "/swift-firmware/Tests/HALTests/source.swift", + "/swift-mmio/Sources/MMIO/source.swift", + "/swift-mmio/Sources/MMIOMacros/source.swift", + "/swift-syntax/Sources/SwiftSyntax/source.swift", + "/swift-syntax/Tests/SwiftSyntaxTests/source.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "swift-firmware", + path: "/swift-firmware", + dependencies: [ + .localSourceControl( + path: "/swift-mmio", + requirement: .upToNextMajor(from: "1.0.0") + ) + ], + products: [ + ProductDescription( + name: "Core", + type: .executable, + targets: ["Core"] + ) + ], + targets: [ + TargetDescription( + name: "Core", + dependencies: ["HAL"], + type: .executable + ), + TargetDescription( + name: "HAL", + dependencies: [.product(name: "MMIO", package: "swift-mmio")] + ), + TargetDescription(name: "CoreTests", dependencies: ["Core"], type: .test), + TargetDescription(name: "HALTests", dependencies: ["HAL"], type: .test), + ] + ), + Manifest.createFileSystemManifest( + displayName: "swift-mmio", + path: "/swift-mmio", + dependencies: [ + .localSourceControl( + path: "/swift-syntax", + requirement: .upToNextMajor(from: "1.0.0") + ) + ], + products: [ + ProductDescription( + name: "MMIO", + type: .library(.automatic), + targets: ["MMIO"] + ) + ], + targets: [ + TargetDescription( + name: "MMIO", + dependencies: [.target(name: "MMIOMacros")] + ), + TargetDescription( + name: "MMIOMacros", + dependencies: [.product(name: "SwiftSyntax", package: "swift-syntax")], + type: .macro + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "swift-syntax", + path: "/swift-syntax", + products: [ + ProductDescription( + name: "SwiftSyntax", + type: .library(.automatic), + targets: ["SwiftSyntax"] + ) + ], + targets: [ + TargetDescription(name: "SwiftSyntax", dependencies: []), + TargetDescription(name: "SwiftSyntaxTests", dependencies: ["SwiftSyntax"], type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + + return (graph, fs, observability.topScope) +} + +package func macrosTestsPackageGraph() throws -> MockPackageGraph { + let fs = InMemoryFileSystem(emptyFiles: + "/swift-mmio/Sources/MMIO/source.swift", + "/swift-mmio/Sources/MMIOMacros/source.swift", + "/swift-mmio/Sources/MMIOMacrosTests/source.swift", + "/swift-syntax/Sources/SwiftSyntax/source.swift", + "/swift-syntax/Sources/SwiftSyntaxMacrosTestSupport/source.swift", + "/swift-syntax/Sources/SwiftSyntaxMacros/source.swift", + "/swift-syntax/Sources/SwiftCompilerPlugin/source.swift", + "/swift-syntax/Sources/SwiftCompilerPluginMessageHandling/source.swift", + "/swift-syntax/Tests/SwiftSyntaxTests/source.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "swift-mmio", + path: "/swift-mmio", + dependencies: [ + .localSourceControl( + path: "/swift-syntax", + requirement: .upToNextMajor(from: "1.0.0") + ) + ], + products: [ + ProductDescription( + name: "MMIO", + type: .library(.automatic), + targets: ["MMIO"] + ) + ], + targets: [ + TargetDescription( + name: "MMIO", + dependencies: [.target(name: "MMIOMacros")] + ), + TargetDescription( + name: "MMIOMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ], + type: .macro + ), + TargetDescription( + name: "MMIOMacrosTests", + dependencies: [ + .target(name: "MMIOMacros"), + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax") + ], + type: .test + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "swift-syntax", + path: "/swift-syntax", + products: [ + ProductDescription( + name: "SwiftSyntaxMacros", + type: .library(.automatic), + targets: ["SwiftSyntax"] + ), + ProductDescription( + name: "SwiftSyntax", + type: .library(.automatic), + targets: ["SwiftSyntax"] + ), + ProductDescription( + name: "SwiftSyntaxMacrosTestSupport", + type: .library(.automatic), + targets: ["SwiftSyntaxMacrosTestSupport"] + ), + ProductDescription( + name: "SwiftCompilerPlugin", + type: .library(.automatic), + targets: ["SwiftCompilerPlugin"] + ), + ProductDescription( + name: "SwiftCompilerPluginMessageHandling", + type: .library(.automatic), + targets: ["SwiftCompilerPluginMessageHandling"] + ), + ], + targets: [ + TargetDescription( + name: "SwiftSyntax", + dependencies: [] + ), + TargetDescription( + name: "SwiftSyntaxMacros", + dependencies: [.target(name: "SwiftSyntax")] + ), + TargetDescription( + name: "SwiftCompilerPlugin", + dependencies: [ + .target(name: "SwiftCompilerPluginMessageHandling"), + .target(name: "SwiftSyntaxMacros"), + ] + ), + TargetDescription( + name: "SwiftCompilerPluginMessageHandling", + dependencies: [ + .target(name: "SwiftSyntax"), + .target(name: "SwiftSyntaxMacros"), + ] + ), + TargetDescription( + name: "SwiftSyntaxMacrosTestSupport", + dependencies: [.target(name: "SwiftSyntax")] + ), + TargetDescription( + name: "SwiftSyntaxTests", + dependencies: ["SwiftSyntax"], + type: .test + ), + ] + ), + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + + return (graph, fs, observability.topScope) +} + +package func trivialPackageGraph() throws -> MockPackageGraph { + let fs = InMemoryFileSystem( + emptyFiles: + "/Pkg/Sources/app/main.swift", + "/Pkg/Sources/lib/lib.c", + "/Pkg/Sources/lib/include/lib.h", + "/Pkg/Tests/test/TestCase.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [ + TargetDescription(name: "app", dependencies: ["lib"]), + TargetDescription(name: "lib", dependencies: []), + TargetDescription(name: "test", dependencies: ["lib"], type: .test), + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + return (graph, fs, observability.topScope) +} + +package func embeddedCxxInteropPackageGraph() throws -> MockPackageGraph { + let fs = InMemoryFileSystem( + emptyFiles: + "/Pkg/Sources/app/main.swift", + "/Pkg/Sources/lib/lib.cpp", + "/Pkg/Sources/lib/include/lib.h", + "/Pkg/Tests/test/TestCase.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [ + TargetDescription( + name: "app", + dependencies: ["lib"], + settings: [.init(tool: .swift, kind: .enableExperimentalFeature("Embedded"))] + ), + TargetDescription( + name: "lib", + dependencies: [], + settings: [.init(tool: .swift, kind: .interoperabilityMode(.Cxx))] + ), + TargetDescription( + name: "test", + dependencies: ["lib"], + type: .test + ), + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + return (graph, fs, observability.topScope) +} + +package func toolsExplicitLibrariesGraph(linkage: ProductType.LibraryType) throws -> MockPackageGraph { + let fs = InMemoryFileSystem(emptyFiles: + "/swift-mmio/Sources/MMIOMacros/source.swift", + "/swift-mmio/Sources/MMIOMacrosTests/source.swift", + "/swift-syntax/Sources/SwiftSyntax/source.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "swift-mmio", + path: "/swift-mmio", + dependencies: [ + .localSourceControl( + path: "/swift-syntax", + requirement: .upToNextMajor(from: "1.0.0") + ) + ], + targets: [ + TargetDescription( + name: "MMIOMacros", + dependencies: [ + .product(name: "SwiftSyntax", package: "swift-syntax"), + ], + type: .macro + ), + TargetDescription( + name: "MMIOMacrosTests", + dependencies: [ + .target(name: "MMIOMacros"), + ], + type: .test + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "swift-syntax", + path: "/swift-syntax", + products: [ + ProductDescription( + name: "SwiftSyntax", + type: .library(linkage), + targets: ["SwiftSyntax"] + ), + ], + targets: [ + TargetDescription( + name: "SwiftSyntax", + dependencies: [] + ), + ] + ), + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + + return (graph, fs, observability.topScope) +} diff --git a/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift b/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift index c53aff4014a..727ff1bf67a 100644 --- a/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift +++ b/Sources/SPMTestSupport/MockPackageSigningEntityStorage.swift @@ -18,15 +18,15 @@ import PackageSigning import struct TSCUtility.Version -public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { +package class MockPackageSigningEntityStorage: PackageSigningEntityStorage { private var packageSigners: [PackageIdentity: PackageSigners] private let lock = NSLock() - public init(_ packageSigners: [PackageIdentity: PackageSigners] = [:]) { + package init(_ packageSigners: [PackageIdentity: PackageSigners] = [:]) { self.packageSigners = packageSigners } - public func get( + package func get( package: PackageIdentity, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue @@ -42,7 +42,7 @@ public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { } @available(*, noasync, message: "Use the async alternative") - public func get( + package func get( package: PackageIdentity, observabilityScope: ObservabilityScope, callbackQueue: DispatchQueue, @@ -59,7 +59,7 @@ public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { } } - public func put( + package func put( package: PackageIdentity, version: Version, signingEntity: SigningEntity, @@ -100,7 +100,7 @@ public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { } } - public func add( + package func add( package: PackageIdentity, version: Version, signingEntity: SigningEntity, @@ -126,7 +126,7 @@ public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { } } - public func changeSigningEntityFromVersion( + package func changeSigningEntityFromVersion( package: PackageIdentity, version: Version, signingEntity: SigningEntity, @@ -157,7 +157,7 @@ public class MockPackageSigningEntityStorage: PackageSigningEntityStorage { } } - public func changeSigningEntityForAllVersions( + package func changeSigningEntityForAllVersions( package: PackageIdentity, version: Version, signingEntity: SigningEntity, diff --git a/Sources/SPMTestSupport/MockProduct.swift b/Sources/SPMTestSupport/MockProduct.swift index 8e7659e8d54..b35c4fa9e4f 100644 --- a/Sources/SPMTestSupport/MockProduct.swift +++ b/Sources/SPMTestSupport/MockProduct.swift @@ -10,11 +10,11 @@ // //===----------------------------------------------------------------------===// -public struct MockProduct { - public let name: String - public let targets: [String] +package struct MockProduct { + package let name: String + package let targets: [String] - public init(name: String, targets: [String]) { + package init(name: String, targets: [String]) { self.name = name self.targets = targets } diff --git a/Sources/SPMTestSupport/MockRegistry.swift b/Sources/SPMTestSupport/MockRegistry.swift index 04b622864e9..c361afccfe7 100644 --- a/Sources/SPMTestSupport/MockRegistry.swift +++ b/Sources/SPMTestSupport/MockRegistry.swift @@ -23,12 +23,12 @@ import protocol TSCBasic.HashAlgorithm import struct TSCUtility.Version -public class MockRegistry { +package class MockRegistry { private let baseURL: URL private let fileSystem: FileSystem private let identityResolver: IdentityResolver private let checksumAlgorithm: HashAlgorithm - public var registryClient: RegistryClient! + package var registryClient: RegistryClient! private let jsonEncoder: JSONEncoder private var packageVersions = [PackageIdentity: [String: InMemoryRegistryPackageSource]]() @@ -36,7 +36,7 @@ public class MockRegistry { private var sourceControlURLs = [URL: PackageIdentity]() private let packagesLock = NSLock() - public init( + package init( filesystem: FileSystem, identityResolver: IdentityResolver, checksumAlgorithm: HashAlgorithm, @@ -74,7 +74,7 @@ public class MockRegistry { ) } - public func addPackage( + package func addPackage( identity: PackageIdentity, versions: [Version], sourceControlURLs: [URL]? = .none, @@ -88,7 +88,7 @@ public class MockRegistry { ) } - public func addPackage( + package func addPackage( identity: PackageIdentity, versions: [String], sourceControlURLs: [URL]? = .none, @@ -356,16 +356,16 @@ public class MockRegistry { } } -public struct InMemoryRegistryPackageSource { +package struct InMemoryRegistryPackageSource { let fileSystem: FileSystem - public let path: AbsolutePath + package let path: AbsolutePath - public init(fileSystem: FileSystem, path: AbsolutePath, writeContent: Bool = true) { + package init(fileSystem: FileSystem, path: AbsolutePath, writeContent: Bool = true) { self.fileSystem = fileSystem self.path = path } - public func writePackageContent(targets: [String] = [], toolsVersion: ToolsVersion = .current) throws { + package func writePackageContent(targets: [String] = [], toolsVersion: ToolsVersion = .current) throws { try self.fileSystem.createDirectory(self.path, recursive: true) let sourcesDir = self.path.appending("Sources") for target in targets { @@ -377,7 +377,7 @@ public struct InMemoryRegistryPackageSource { try self.fileSystem.writeFileContents(manifestPath, string: "// swift-tools-version:\(toolsVersion)") } - public func listFiles(root: AbsolutePath? = .none) throws -> [AbsolutePath] { + package func listFiles(root: AbsolutePath? = .none) throws -> [AbsolutePath] { var files = [AbsolutePath]() let root = root ?? self.path let entries = try self.fileSystem.getDirectoryContents(root) @@ -455,7 +455,7 @@ private struct MockRegistryArchiver: Archiver { } extension RegistryConfiguration.Security { - public static let testDefault: RegistryConfiguration.Security = { + package static let testDefault: RegistryConfiguration.Security = { var signing = RegistryConfiguration.Security.Signing() signing.onUnsigned = .silentAllow signing.onUntrustedCertificate = .silentAllow diff --git a/Sources/SPMTestSupport/MockTarget.swift b/Sources/SPMTestSupport/MockTarget.swift index b2f065a81cb..2d9750d9d27 100644 --- a/Sources/SPMTestSupport/MockTarget.swift +++ b/Sources/SPMTestSupport/MockTarget.swift @@ -13,21 +13,21 @@ import PackageGraph import PackageModel -public struct MockTarget { - public enum `Type` { +package struct MockTarget { + package enum `Type` { case regular, test, binary } - public let name: String - public let dependencies: [TargetDescription.Dependency] - public let path: String? - public let url: String? - public let checksum: String? - public let packageAccess: Bool - public let settings: [TargetBuildSettingDescription.Setting] - public let type: Type + package let name: String + package let dependencies: [TargetDescription.Dependency] + package let path: String? + package let url: String? + package let checksum: String? + package let packageAccess: Bool + package let settings: [TargetBuildSettingDescription.Setting] + package let type: Type - public init( + package init( name: String, dependencies: [TargetDescription.Dependency] = [], type: Type = .regular, diff --git a/Sources/SPMTestSupport/MockWorkspace.swift b/Sources/SPMTestSupport/MockWorkspace.swift index 29e98d45f86..f2f24792322 100644 --- a/Sources/SPMTestSupport/MockWorkspace.swift +++ b/Sources/SPMTestSupport/MockWorkspace.swift @@ -23,7 +23,7 @@ import class TSCBasic.InMemoryFileSystem import struct TSCUtility.Version -public final class MockWorkspace { +package final class MockWorkspace { let sandbox: AbsolutePath let fileSystem: InMemoryFileSystem let roots: [MockPackage] @@ -32,20 +32,20 @@ public final class MockWorkspace { let fingerprints: MockPackageFingerprintStorage let signingEntities: MockPackageSigningEntityStorage let mirrors: DependencyMirrors - public var registryClient: RegistryClient + package var registryClient: RegistryClient let registry: MockRegistry let customBinaryArtifactsManager: Workspace.CustomBinaryArtifactsManager - public var checksumAlgorithm: MockHashAlgorithm - public private(set) var manifestLoader: MockManifestLoader - public let repositoryProvider: InMemoryGitRepositoryProvider + package var checksumAlgorithm: MockHashAlgorithm + package private(set) var manifestLoader: MockManifestLoader + package let repositoryProvider: InMemoryGitRepositoryProvider let identityResolver: IdentityResolver let customPackageContainerProvider: MockPackageContainerProvider? - public let delegate = MockWorkspaceDelegate() + package let delegate = MockWorkspaceDelegate() let skipDependenciesUpdates: Bool - public var sourceControlToRegistryDependencyTransformation: WorkspaceConfiguration.SourceControlToRegistryDependencyTransformation + package var sourceControlToRegistryDependencyTransformation: WorkspaceConfiguration.SourceControlToRegistryDependencyTransformation var defaultRegistry: Registry? - public init( + package init( sandbox: AbsolutePath, fileSystem: InMemoryFileSystem, roots: [MockPackage], @@ -96,23 +96,23 @@ public final class MockWorkspace { try self.create() } - public var rootsDir: AbsolutePath { + package var rootsDir: AbsolutePath { return self.sandbox.appending("roots") } - public var packagesDir: AbsolutePath { + package var packagesDir: AbsolutePath { return self.sandbox.appending("pkgs") } - public var artifactsDir: AbsolutePath { + package var artifactsDir: AbsolutePath { return self.sandbox.appending(components: ".build", "artifacts") } - public func pathToRoot(withName name: String) throws -> AbsolutePath { + package func pathToRoot(withName name: String) throws -> AbsolutePath { return try AbsolutePath(validating: name, relativeTo: self.rootsDir) } - public func pathToPackage(withName name: String) throws -> AbsolutePath { + package func pathToPackage(withName name: String) throws -> AbsolutePath { return try AbsolutePath(validating: name, relativeTo: self.packagesDir) } @@ -267,7 +267,7 @@ public final class MockWorkspace { self.manifestLoader = MockManifestLoader(manifests: manifests) } - public func getOrCreateWorkspace() throws -> Workspace { + package func getOrCreateWorkspace() throws -> Workspace { if let workspace = self._workspace { return workspace } @@ -317,7 +317,7 @@ public final class MockWorkspace { private var _workspace: Workspace? - public func closeWorkspace(resetState: Bool = true, resetResolvedFile: Bool = true) throws { + package func closeWorkspace(resetState: Bool = true, resetResolvedFile: Bool = true) throws { if resetState { try self._workspace?.resetState() } @@ -329,11 +329,11 @@ public final class MockWorkspace { self._workspace = nil } - public func rootPaths(for packages: [String]) throws -> [AbsolutePath] { + package func rootPaths(for packages: [String]) throws -> [AbsolutePath] { return try packages.map { try AbsolutePath(validating: $0, relativeTo: rootsDir) } } - public func checkEdit( + package func checkEdit( packageName: String, path: AbsolutePath? = nil, revision: Revision? = nil, @@ -354,7 +354,7 @@ public final class MockWorkspace { result(observability.diagnostics) } - public func checkUnedit( + package func checkUnedit( packageName: String, roots: [String], forceRemove: Bool = false, @@ -364,12 +364,17 @@ public final class MockWorkspace { observability.topScope.trap { let rootInput = PackageGraphRootInput(packages: try rootPaths(for: roots)) let ws = try self.getOrCreateWorkspace() - try ws.unedit(packageName: packageName, forceRemove: forceRemove, root: rootInput, observabilityScope: observability.topScope) + try ws.unedit( + packageName: packageName, + forceRemove: forceRemove, + root: rootInput, + observabilityScope: observability.topScope + ) } result(observability.diagnostics) } - public func checkResolve(pkg: String, roots: [String], version: TSCUtility.Version, _ result: ([Basics.Diagnostic]) -> Void) { + package func checkResolve(pkg: String, roots: [String], version: TSCUtility.Version, _ result: ([Basics.Diagnostic]) -> Void) { let observability = ObservabilitySystem.makeForTesting() observability.topScope.trap { let rootInput = PackageGraphRootInput(packages: try rootPaths(for: roots)) @@ -379,7 +384,7 @@ public final class MockWorkspace { result(observability.diagnostics) } - public func checkClean(_ result: ([Basics.Diagnostic]) -> Void) { + package func checkClean(_ result: ([Basics.Diagnostic]) -> Void) { let observability = ObservabilitySystem.makeForTesting() observability.topScope.trap { let workspace = try self.getOrCreateWorkspace() @@ -388,7 +393,7 @@ public final class MockWorkspace { result(observability.diagnostics) } - public func checkReset(_ result: ([Basics.Diagnostic]) -> Void) { + package func checkReset(_ result: ([Basics.Diagnostic]) -> Void) { let observability = ObservabilitySystem.makeForTesting() observability.topScope.trap { let workspace = try self.getOrCreateWorkspace() @@ -397,7 +402,7 @@ public final class MockWorkspace { result(observability.diagnostics) } - public func checkUpdate( + package func checkUpdate( roots: [String] = [], deps: [MockDependency] = [], packages: [String] = [], @@ -417,7 +422,7 @@ public final class MockWorkspace { result(observability.diagnostics) } - public func checkUpdateDryRun( + package func checkUpdateDryRun( roots: [String] = [], deps: [MockDependency] = [], _ result: ([(PackageReference, Workspace.PackageStateChange)]?, [Basics.Diagnostic]) -> Void @@ -436,21 +441,21 @@ public final class MockWorkspace { result(changes, observability.diagnostics) } - public func checkPackageGraph( + package func checkPackageGraph( roots: [String] = [], deps: [MockDependency], - _ result: (PackageGraph, [Basics.Diagnostic]) -> Void + _ result: (ModulesGraph, [Basics.Diagnostic]) -> Void ) throws { let dependencies = try deps.map { try $0.convert(baseURL: packagesDir, identityResolver: self.identityResolver) } try self.checkPackageGraph(roots: roots, dependencies: dependencies, result) } - public func checkPackageGraph( + package func checkPackageGraph( roots: [String] = [], dependencies: [PackageDependency] = [], forceResolvedVersions: Bool = false, expectedSigningEntities: [PackageIdentity: RegistryReleaseMetadata.SigningEntity] = [:], - _ result: (PackageGraph, [Basics.Diagnostic]) throws -> Void + _ result: (ModulesGraph, [Basics.Diagnostic]) throws -> Void ) throws { let observability = ObservabilitySystem.makeForTesting() let rootInput = PackageGraphRootInput( @@ -474,7 +479,7 @@ public final class MockWorkspace { } } - public func checkPackageGraphFailure( + package func checkPackageGraphFailure( roots: [String] = [], deps: [MockDependency], _ result: ([Basics.Diagnostic]) -> Void @@ -483,7 +488,7 @@ public final class MockWorkspace { self.checkPackageGraphFailure(roots: roots, dependencies: dependencies, result) } - public func checkPackageGraphFailure( + package func checkPackageGraphFailure( roots: [String] = [], dependencies: [PackageDependency] = [], forceResolvedVersions: Bool = false, @@ -505,12 +510,12 @@ public final class MockWorkspace { result(observability.diagnostics) } - public struct ResolutionPrecomputationResult { - public let result: Workspace.ResolutionPrecomputationResult - public let diagnostics: [Basics.Diagnostic] + package struct ResolutionPrecomputationResult { + package let result: Workspace.ResolutionPrecomputationResult + package let diagnostics: [Basics.Diagnostic] } - public func checkPrecomputeResolution() async throws -> ResolutionPrecomputationResult { + package func checkPrecomputeResolution() async throws -> ResolutionPrecomputationResult { let observability = ObservabilitySystem.makeForTesting() let workspace = try self.getOrCreateWorkspace() let pinsStore = try workspace.pinsStore.load() @@ -522,7 +527,10 @@ public final class MockWorkspace { ) let root = PackageGraphRoot(input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope) - let dependencyManifests = try workspace.loadDependencyManifests(root: root, observabilityScope: observability.topScope) + let dependencyManifests = try workspace.loadDependencyManifests( + root: root, + observabilityScope: observability.topScope + ) let result = try workspace.precomputeResolution( root: root, @@ -535,7 +543,7 @@ public final class MockWorkspace { return ResolutionPrecomputationResult(result: result, diagnostics: observability.diagnostics) } - public func set( + package func set( pins: [PackageReference: CheckoutState] = [:], managedDependencies: [AbsolutePath: Workspace.ManagedDependency] = [:], managedArtifacts: [Workspace.ManagedArtifact] = [] @@ -553,7 +561,7 @@ public final class MockWorkspace { try self.set(pins: pins, managedDependencies: managedDependencies, managedArtifacts: managedArtifacts) } - public func set( + package func set( pins: [PackageReference: PinsStore.PinState], managedDependencies: [AbsolutePath: Workspace.ManagedDependency] = [:], managedArtifacts: [Workspace.ManagedArtifact] = [] @@ -586,13 +594,13 @@ public final class MockWorkspace { try workspace.state.save() } - public func resetState() throws { + package func resetState() throws { let workspace = try self.getOrCreateWorkspace() try workspace.resetState() } - public enum State { - public enum CheckoutState { + package enum State { + package enum CheckoutState { case version(TSCUtility.Version) case revision(String) case branch(String) @@ -605,31 +613,31 @@ public final class MockWorkspace { case custom(TSCUtility.Version, AbsolutePath) } - public struct ManagedDependencyResult { - public let managedDependencies: Workspace.ManagedDependencies + package struct ManagedDependencyResult { + package let managedDependencies: Workspace.ManagedDependencies - public init(_ managedDependencies: Workspace.ManagedDependencies) { + package init(_ managedDependencies: Workspace.ManagedDependencies) { self.managedDependencies = managedDependencies } - public func check(notPresent name: String, file: StaticString = #file, line: UInt = #line) { + package func check(notPresent name: String, file: StaticString = #file, line: UInt = #line) { self.check(notPresent: .plain(name), file: file, line: line) } - public func check(notPresent dependencyId: PackageIdentity, file: StaticString = #file, line: UInt = #line) { + package func check(notPresent dependencyId: PackageIdentity, file: StaticString = #file, line: UInt = #line) { let dependency = self.managedDependencies[dependencyId] XCTAssertNil(dependency, "Unexpectedly found \(dependencyId) in managed dependencies", file: file, line: line) } - public func checkEmpty(file: StaticString = #file, line: UInt = #line) { + package func checkEmpty(file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(self.managedDependencies.count, 0, file: file, line: line) } - public func check(dependency name: String, at state: State, file: StaticString = #file, line: UInt = #line) { + package func check(dependency name: String, at state: State, file: StaticString = #file, line: UInt = #line) { self.check(dependency: .plain(name), at: state, file: file, line: line) } - public func check(dependency dependencyId: PackageIdentity, at state: State, file: StaticString = #file, line: UInt = #line) { + package func check(dependency dependencyId: PackageIdentity, at state: State, file: StaticString = #file, line: UInt = #line) { guard let dependency = managedDependencies[dependencyId] else { return XCTFail("\(dependencyId) does not exists", file: file, line: line) } @@ -670,18 +678,18 @@ public final class MockWorkspace { } } - public struct ManagedArtifactResult { - public let managedArtifacts: Workspace.ManagedArtifacts + package struct ManagedArtifactResult { + package let managedArtifacts: Workspace.ManagedArtifacts - public init(_ managedArtifacts: Workspace.ManagedArtifacts) { + package init(_ managedArtifacts: Workspace.ManagedArtifacts) { self.managedArtifacts = managedArtifacts } - public func checkNotPresent(packageName: String, targetName: String, file: StaticString = #file, line: UInt = #line) { + package func checkNotPresent(packageName: String, targetName: String, file: StaticString = #file, line: UInt = #line) { self.checkNotPresent(packageIdentity: .plain(packageName), targetName: targetName, file : file, line: line) } - public func checkNotPresent( + package func checkNotPresent( packageIdentity: PackageIdentity, targetName: String, file: StaticString = #file, @@ -691,15 +699,15 @@ public final class MockWorkspace { XCTAssert(artifact == nil, "Unexpectedly found \(packageIdentity).\(targetName) in managed artifacts", file: file, line: line) } - public func checkEmpty(file: StaticString = #file, line: UInt = #line) { + package func checkEmpty(file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(self.managedArtifacts.count, 0, file: file, line: line) } - public func check(packageName: String, targetName: String, source: Workspace.ManagedArtifact.Source, path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { + package func check(packageName: String, targetName: String, source: Workspace.ManagedArtifact.Source, path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { self.check(packageIdentity: .plain(packageName), targetName: targetName, source: source, path: path, file: file, line: line) } - public func check( + package func check( packageIdentity: PackageIdentity, targetName: String, source: Workspace.ManagedArtifact.Source, @@ -724,7 +732,7 @@ public final class MockWorkspace { } } - public func loadDependencyManifests( + package func loadDependencyManifests( roots: [String] = [], deps: [MockDependency] = [], _ result: (Workspace.DependencyManifests, [Basics.Diagnostic]) -> Void @@ -737,11 +745,14 @@ public final class MockWorkspace { ) let rootManifests = try temp_await { workspace.loadRootManifests(packages: rootInput.packages, observabilityScope: observability.topScope, completion: $0) } let graphRoot = PackageGraphRoot(input: rootInput, manifests: rootManifests, observabilityScope: observability.topScope) - let manifests = try workspace.loadDependencyManifests(root: graphRoot, observabilityScope: observability.topScope) + let manifests = try workspace.loadDependencyManifests( + root: graphRoot, + observabilityScope: observability.topScope + ) result(manifests, observability.diagnostics) } - public func checkManagedDependencies(file: StaticString = #file, line: UInt = #line, _ result: (ManagedDependencyResult) throws -> Void) { + package func checkManagedDependencies(file: StaticString = #file, line: UInt = #line, _ result: (ManagedDependencyResult) throws -> Void) { do { let workspace = try self.getOrCreateWorkspace() try result(ManagedDependencyResult(workspace.state.dependencies)) @@ -750,7 +761,7 @@ public final class MockWorkspace { } } - public func checkManagedArtifacts(file: StaticString = #file, line: UInt = #line, _ result: (ManagedArtifactResult) throws -> Void) { + package func checkManagedArtifacts(file: StaticString = #file, line: UInt = #line, _ result: (ManagedArtifactResult) throws -> Void) { do { let workspace = try self.getOrCreateWorkspace() try result(ManagedArtifactResult(workspace.state.artifacts)) @@ -759,18 +770,18 @@ public final class MockWorkspace { } } - public struct ResolvedResult { - public let store: PinsStore + package struct ResolvedResult { + package let store: PinsStore - public init(_ store: PinsStore) { + package init(_ store: PinsStore) { self.store = store } - public func check(notPresent name: String, file: StaticString = #file, line: UInt = #line) { + package func check(notPresent name: String, file: StaticString = #file, line: UInt = #line) { XCTAssertFalse(self.store.pins.keys.contains(where: { $0.description == name }), "Unexpectedly found \(name) in Package.resolved", file: file, line: line) } - public func check(dependency package: String, at state: State, file: StaticString = #file, line: UInt = #line) { + package func check(dependency package: String, at state: State, file: StaticString = #file, line: UInt = #line) { guard let pin = store.pins.first(where: { $0.key.description == package })?.value else { XCTFail("Pin for \(package) not found", file: file, line: line) return @@ -797,7 +808,7 @@ public final class MockWorkspace { } } - public func check(dependency package: String, url: String, file: StaticString = #file, line: UInt = #line) { + package func check(dependency package: String, url: String, file: StaticString = #file, line: UInt = #line) { guard let pin = store.pins.first(where: { $0.key.description == package })?.value else { XCTFail("Pin for \(package) not found", file: file, line: line) return @@ -807,7 +818,7 @@ public final class MockWorkspace { } } - public func checkResolved(file: StaticString = #file, line: UInt = #line, _ result: (ResolvedResult) throws -> Void) { + package func checkResolved(file: StaticString = #file, line: UInt = #line, _ result: (ResolvedResult) throws -> Void) { do { let workspace = try self.getOrCreateWorkspace() try result(ResolvedResult(workspace.pinsStore.load())) @@ -817,66 +828,66 @@ public final class MockWorkspace { } } -public final class MockWorkspaceDelegate: WorkspaceDelegate { +package final class MockWorkspaceDelegate: WorkspaceDelegate { private let lock = NSLock() private var _events = [String]() private var _manifest: Manifest? private var _manifestLoadingDiagnostics: [Basics.Diagnostic]? - public init() {} + package init() {} - public func willUpdateRepository(package: PackageIdentity, repository url: String) { + package func willUpdateRepository(package: PackageIdentity, repository url: String) { self.append("updating repo: \(url)") } - public func didUpdateRepository(package: PackageIdentity, repository url: String, duration: DispatchTimeInterval) { + package func didUpdateRepository(package: PackageIdentity, repository url: String, duration: DispatchTimeInterval) { self.append("finished updating repo: \(url)") } - public func dependenciesUpToDate() { + package func dependenciesUpToDate() { self.append("Everything is already up-to-date") } - public func willFetchPackage(package: PackageIdentity, packageLocation: String?, fetchDetails: PackageFetchDetails) { + package func willFetchPackage(package: PackageIdentity, packageLocation: String?, fetchDetails: PackageFetchDetails) { self.append("fetching package: \(packageLocation ?? package.description)") } - public func fetchingPackage(package: PackageIdentity, packageLocation: String?, progress: Int64, total: Int64?) { + package func fetchingPackage(package: PackageIdentity, packageLocation: String?, progress: Int64, total: Int64?) { } - public func didFetchPackage(package: PackageIdentity, packageLocation: String?, result: Result, duration: DispatchTimeInterval) { + package func didFetchPackage(package: PackageIdentity, packageLocation: String?, result: Result, duration: DispatchTimeInterval) { self.append("finished fetching package: \(packageLocation ?? package.description)") } - public func willCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath) { + package func willCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath) { self.append("creating working copy for: \(url)") } - public func didCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath, duration: DispatchTimeInterval) { + package func didCreateWorkingCopy(package: PackageIdentity, repository url: String, at path: AbsolutePath, duration: DispatchTimeInterval) { self.append("finished creating working copy for: \(url)") } - public func willCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath) { + package func willCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath) { self.append("checking out repo: \(url)") } - public func didCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath, duration: DispatchTimeInterval) { + package func didCheckOut(package: PackageIdentity, repository url: String, revision: String, at path: AbsolutePath, duration: DispatchTimeInterval) { self.append("finished checking out repo: \(url)") } - public func removing(package: PackageIdentity, packageLocation: String?) { + package func removing(package: PackageIdentity, packageLocation: String?) { self.append("removing repo: \(packageLocation ?? package.description)") } - public func willResolveDependencies(reason: WorkspaceResolveReason) { + package func willResolveDependencies(reason: WorkspaceResolveReason) { self.append("will resolve dependencies") } - public func willLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) { + package func willLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind) { self.append("will load manifest for \(packageKind.displayName) package: \(url) (identity: \(packageIdentity))") } - public func didLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind, manifest: Manifest?, diagnostics: [Basics.Diagnostic], duration: DispatchTimeInterval) { + package func didLoadManifest(packageIdentity: PackageIdentity, packagePath: AbsolutePath, url: String, version: Version?, packageKind: PackageReference.Kind, manifest: Manifest?, diagnostics: [Basics.Diagnostic], duration: DispatchTimeInterval) { self.append("did load manifest for \(packageKind.displayName) package: \(url) (identity: \(packageIdentity))") self.lock.withLock { self._manifest = manifest @@ -884,71 +895,71 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate { } } - public func willComputeVersion(package: PackageIdentity, location: String) { + package func willComputeVersion(package: PackageIdentity, location: String) { // noop } - public func didComputeVersion(package: PackageIdentity, location: String, version: String, duration: DispatchTimeInterval) { + package func didComputeVersion(package: PackageIdentity, location: String, version: String, duration: DispatchTimeInterval) { // noop } - public func resolvedFileChanged() { + package func resolvedFileChanged() { // noop } - public func willDownloadBinaryArtifact(from url: String, fromCache: Bool) { + package func willDownloadBinaryArtifact(from url: String, fromCache: Bool) { self.append("downloading binary artifact package: \(url)") } - public func didDownloadBinaryArtifact(from url: String, result: Result<(path: AbsolutePath, fromCache: Bool), Error>, duration: DispatchTimeInterval) { + package func didDownloadBinaryArtifact(from url: String, result: Result<(path: AbsolutePath, fromCache: Bool), Error>, duration: DispatchTimeInterval) { self.append("finished downloading binary artifact package: \(url)") } - public func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { + package func downloadingBinaryArtifact(from url: String, bytesDownloaded: Int64, totalBytesToDownload: Int64?) { // noop } - public func didDownloadAllBinaryArtifacts() { + package func didDownloadAllBinaryArtifacts() { // noop } - public func willUpdateDependencies() { + package func willUpdateDependencies() { // noop } - public func didUpdateDependencies(duration: DispatchTimeInterval) { + package func didUpdateDependencies(duration: DispatchTimeInterval) { // noop } - public func willResolveDependencies() { + package func willResolveDependencies() { // noop } - public func didResolveDependencies(duration: DispatchTimeInterval) { + package func didResolveDependencies(duration: DispatchTimeInterval) { // noop } - public func willLoadGraph() { + package func willLoadGraph() { // noop } - public func didLoadGraph(duration: DispatchTimeInterval) { + package func didLoadGraph(duration: DispatchTimeInterval) { // noop } - public func willCompileManifest(packageIdentity: PackageIdentity, packageLocation: String) { + package func willCompileManifest(packageIdentity: PackageIdentity, packageLocation: String) { // noop } - public func didCompileManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + package func didCompileManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { // noop } - public func willEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String) { + package func willEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String) { // noop } - public func didEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { + package func didEvaluateManifest(packageIdentity: PackageIdentity, packageLocation: String, duration: DispatchTimeInterval) { // noop } @@ -958,25 +969,25 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate { } } - public var events: [String] { + package var events: [String] { self.lock.withLock { self._events } } - public func clear() { + package func clear() { self.lock.withLock { self._events = [] } } - public var manifest: Manifest? { + package var manifest: Manifest? { self.lock.withLock { self._manifest } } - public var manifestLoadingDiagnostics: [Basics.Diagnostic]? { + package var manifestLoadingDiagnostics: [Basics.Diagnostic]? { self.lock.withLock { self._manifestLoadingDiagnostics } @@ -984,7 +995,7 @@ public final class MockWorkspaceDelegate: WorkspaceDelegate { } extension CheckoutState { - public var version: Version? { + package var version: Version? { get { switch self { case .revision: @@ -997,7 +1008,7 @@ extension CheckoutState { } } - public var branch: String? { + package var branch: String? { get { switch self { case .revision: @@ -1044,11 +1055,11 @@ extension CheckoutState { } extension Array where Element == Basics.Diagnostic { - public var hasErrors: Bool { + package var hasErrors: Bool { self.contains(where: { $0.severity == .error }) } - public var hasWarnings: Bool { + package var hasWarnings: Bool { self.contains(where: { $0.severity == .warning }) } } diff --git a/Sources/SPMTestSupport/Observability.swift b/Sources/SPMTestSupport/Observability.swift index 198e5b898f2..c27349bf00d 100644 --- a/Sources/SPMTestSupport/Observability.swift +++ b/Sources/SPMTestSupport/Observability.swift @@ -19,43 +19,39 @@ import struct TSCBasic.StringError import TSCTestSupport extension ObservabilitySystem { - public static func makeForTesting(verbose: Bool = true) -> TestingObservability { + package static func makeForTesting(verbose: Bool = true) -> TestingObservability { let collector = TestingObservability.Collector(verbose: verbose) let observabilitySystem = ObservabilitySystem(collector) return TestingObservability(collector: collector, topScope: observabilitySystem.topScope) } - - public static var NOOP: ObservabilityScope { - ObservabilitySystem { _, _ in }.topScope - } } -public struct TestingObservability { +package struct TestingObservability { private let collector: Collector - public let topScope: ObservabilityScope + package let topScope: ObservabilityScope fileprivate init(collector: Collector, topScope: ObservabilityScope) { self.collector = collector self.topScope = topScope } - public var diagnostics: [Basics.Diagnostic] { + package var diagnostics: [Basics.Diagnostic] { self.collector.diagnostics.get() } - public var errors: [Basics.Diagnostic] { + package var errors: [Basics.Diagnostic] { self.diagnostics.filter { $0.severity == .error } } - public var warnings: [Basics.Diagnostic] { + package var warnings: [Basics.Diagnostic] { self.diagnostics.filter { $0.severity == .warning } } - public var hasErrorDiagnostics: Bool { + package var hasErrorDiagnostics: Bool { self.collector.hasErrors } - public var hasWarningDiagnostics: Bool { + package var hasWarningDiagnostics: Bool { self.collector.hasWarnings } @@ -93,7 +89,7 @@ public struct TestingObservability { } } -public func XCTAssertNoDiagnostics( +package func XCTAssertNoDiagnostics( _ diagnostics: [Basics.Diagnostic], problemsOnly: Bool = true, file: StaticString = #file, @@ -105,7 +101,7 @@ public func XCTAssertNoDiagnostics( XCTFail("Found unexpected diagnostics: \n\(description)", file: file, line: line) } -public func testDiagnostics( +package func testDiagnostics( _ diagnostics: [Basics.Diagnostic], problemsOnly: Bool = true, file: StaticString = #file, @@ -121,7 +117,7 @@ public func testDiagnostics( ) } -public func testDiagnostics( +package func testDiagnostics( _ diagnostics: [Basics.Diagnostic], minSeverity: Basics.Diagnostic.Severity, file: StaticString = #file, @@ -142,7 +138,7 @@ public func testDiagnostics( } } -public func testPartialDiagnostics( +package func testPartialDiagnostics( _ diagnostics: [Basics.Diagnostic], minSeverity: Basics.Diagnostic.Severity, file: StaticString = #file, @@ -160,7 +156,7 @@ public func testPartialDiagnostics( } /// Helper to check diagnostics in the engine. -public class DiagnosticsTestResult { +package class DiagnosticsTestResult { fileprivate var uncheckedDiagnostics: [Basics.Diagnostic] init(_ diagnostics: [Basics.Diagnostic]) { @@ -168,7 +164,7 @@ public class DiagnosticsTestResult { } @discardableResult - public func check( + package func check( diagnostic message: StringPattern, severity: Basics.Diagnostic.Severity, //metadata: ObservabilityMetadata? = .none, @@ -192,7 +188,7 @@ public class DiagnosticsTestResult { } @discardableResult - public func checkUnordered( + package func checkUnordered( diagnostic diagnosticPattern: StringPattern, severity: Basics.Diagnostic.Severity, //metadata: ObservabilityMetadata? = .none, diff --git a/Sources/SPMTestSupport/PIFTester.swift b/Sources/SPMTestSupport/PIFTester.swift index 4e4641fa016..b03b1a4e827 100644 --- a/Sources/SPMTestSupport/PIFTester.swift +++ b/Sources/SPMTestSupport/PIFTester.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2020 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -14,11 +14,11 @@ import Basics import XCBuildSupport import XCTest -public func PIFTester(_ pif: PIF.TopLevelObject, _ body: (PIFWorkspaceTester) throws -> Void) throws { +package func PIFTester(_ pif: PIF.TopLevelObject, _ body: (PIFWorkspaceTester) throws -> Void) throws { try body(PIFWorkspaceTester(workspace: pif.workspace)) } -public final class PIFWorkspaceTester { +package final class PIFWorkspaceTester { private let workspace: PIF.Workspace private let projectMap: [PIF.GUID: PIF.Project] private let targetMap: [PIF.GUID: PIF.BaseTarget] @@ -32,7 +32,12 @@ public final class PIFWorkspaceTester { targetMap = Dictionary(uniqueKeysWithValues: targetsByGUID) } - public func checkProject(_ guid: PIF.GUID, file: StaticString = #file, line: UInt = #line, body: (PIFProjectTester) -> Void) throws { + package func checkProject( + _ guid: PIF.GUID, + file: StaticString = #file, + line: UInt = #line, + body: (PIFProjectTester) -> Void + ) throws { guard let project = projectMap[guid] else { return XCTFail("project \(guid) not found", file: file, line: line) } @@ -41,24 +46,34 @@ public final class PIFWorkspaceTester { } } -public final class PIFProjectTester { +package final class PIFProjectTester { private let project: PIF.Project private let targetMap: [PIF.GUID: PIF.BaseTarget] private let fileMap: [PIF.GUID: String] - public var guid: PIF.GUID { project.guid } - public var path: AbsolutePath { project.path } - public var projectDirectory: AbsolutePath { project.projectDirectory } - public var name: String { project.name } - public var developmentRegion: String { project.developmentRegion } + package var guid: PIF.GUID { project.guid } + package var path: AbsolutePath { project.path } + package var projectDirectory: AbsolutePath { project.projectDirectory } + package var name: String { project.name } + package var developmentRegion: String { project.developmentRegion } fileprivate init(project: PIF.Project, targetMap: [PIF.GUID: PIF.BaseTarget]) throws { self.project = project self.targetMap = targetMap - self.fileMap = try collectFiles(from: project.groupTree, parentPath: project.path, projectPath: project.path, builtProductsPath: project.path) + self.fileMap = try collectFiles( + from: project.groupTree, + parentPath: project.path, + projectPath: project.path, + builtProductsPath: project.path + ) } - public func checkTarget(_ guid: PIF.GUID, file: StaticString = #file, line: UInt = #line, body: ((PIFTargetTester) -> Void)? = nil) { + package func checkTarget( + _ guid: PIF.GUID, + file: StaticString = #file, + line: UInt = #line, + body: ((PIFTargetTester) -> Void)? = nil + ) { guard let baseTarget = baseTarget(withGUID: guid) else { let guids = project.targets.map { $0.guid }.joined(separator: ", ") return XCTFail("target \(guid) not found among \(guids)", file: file, line: line) @@ -71,13 +86,23 @@ public final class PIFProjectTester { body?(PIFTargetTester(target: target, targetMap: targetMap, fileMap: fileMap)) } - public func checkNoTarget(_ guid: PIF.GUID, file: StaticString = #file, line: UInt = #line, body: ((PIFTargetTester) -> Void)? = nil) { + package func checkNoTarget( + _ guid: PIF.GUID, + file: StaticString = #file, + line: UInt = #line, + body: ((PIFTargetTester) -> Void)? = nil + ) { if baseTarget(withGUID: guid) != nil { XCTFail("target \(guid) found", file: file, line: line) } } - public func checkAggregateTarget(_ guid: PIF.GUID, file: StaticString = #file, line: UInt = #line, body: ((PIFAggregateTargetTester) -> Void)? = nil) { + package func checkAggregateTarget( + _ guid: PIF.GUID, + file: StaticString = #file, + line: UInt = #line, + body: ((PIFAggregateTargetTester) -> Void)? = nil + ) { guard let baseTarget = baseTarget(withGUID: guid) else { let guids = project.targets.map { $0.guid }.joined(separator: ", ") return XCTFail("target \(guid) not found among \(guids)", file: file, line: line) @@ -90,7 +115,12 @@ public final class PIFProjectTester { body?(PIFAggregateTargetTester(target: target, targetMap: targetMap, fileMap: fileMap)) } - public func checkBuildConfiguration(_ name: String, file: StaticString = #file, line: UInt = #line, body: (PIFBuildConfigurationTester) -> Void) { + package func checkBuildConfiguration( + _ name: String, + file: StaticString = #file, + line: UInt = #line, + body: (PIFBuildConfigurationTester) -> Void + ) { guard let configuration = buildConfiguration(withName: name) else { let names = project.buildConfigurations.map { $0.name }.joined(separator: ", ") return XCTFail("build configuration \(name) not found among \(names)", file: file, line: line) @@ -99,24 +129,24 @@ public final class PIFProjectTester { body(PIFBuildConfigurationTester(buildConfiguration: configuration)) } - public func buildConfiguration(withName name: String) -> PIF.BuildConfiguration? { + package func buildConfiguration(withName name: String) -> PIF.BuildConfiguration? { return project.buildConfigurations.first { $0.name == name } } - public func baseTarget(withGUID guid: PIF.GUID) -> PIF.BaseTarget? { + package func baseTarget(withGUID guid: PIF.GUID) -> PIF.BaseTarget? { return project.targets.first { $0.guid == guid } } } -public class PIFBaseTargetTester { - public let baseTarget: PIF.BaseTarget +package class PIFBaseTargetTester { + package let baseTarget: PIF.BaseTarget - public var guid: PIF.GUID { baseTarget.guid } - public var name: String { baseTarget.name } - public let dependencies: Set - public let sources: Set - public let frameworks: Set - public let resources: Set + package var guid: PIF.GUID { baseTarget.guid } + package var name: String { baseTarget.name } + package let dependencies: Set + package let sources: Set + package let frameworks: Set + package let resources: Set fileprivate init(baseTarget: PIF.BaseTarget, targetMap: [PIF.GUID: PIF.BaseTarget], fileMap: [PIF.GUID: String]) { self.baseTarget = baseTarget @@ -151,7 +181,12 @@ public class PIFBaseTargetTester { }) } - public func checkBuildConfiguration(_ name: String, file: StaticString = #file, line: UInt = #line, body: (PIFBuildConfigurationTester) -> Void) { + package func checkBuildConfiguration( + _ name: String, + file: StaticString = #file, + line: UInt = #line, + body: (PIFBuildConfigurationTester) -> Void + ) { guard let configuration = buildConfiguration(withName: name) else { return XCTFail("build configuration \(name) not found", file: file, line: line) } @@ -159,31 +194,45 @@ public class PIFBaseTargetTester { body(PIFBuildConfigurationTester(buildConfiguration: configuration)) } - public func buildConfiguration(withName name: String) -> PIF.BuildConfiguration? { + package func buildConfiguration(withName name: String) -> PIF.BuildConfiguration? { return baseTarget.buildConfigurations.first { $0.name == name } } - public func checkImpartedBuildSettings(file: StaticString = #file, line: UInt = #line, _ body: (PIFBuildSettingsTester) -> Void) { - let buildSettingsTester = PIFBuildSettingsTester(buildSettings: baseTarget.buildConfigurations.first!.impartedBuildProperties.buildSettings) + package func checkImpartedBuildSettings( + file: StaticString = #file, + line: UInt = #line, + _ body: (PIFBuildSettingsTester) -> Void + ) { + let buildSettingsTester = PIFBuildSettingsTester( + buildSettings: baseTarget.buildConfigurations.first!.impartedBuildProperties.buildSettings + ) body(buildSettingsTester) } - public func checkAllImpartedBuildSettings(file: StaticString = #file, line: UInt = #line, _ body: (PIFBuildSettingsTester) -> Void) { - let buildSettingsTester = PIFBuildSettingsTester(buildSettings: baseTarget.buildConfigurations.first!.impartedBuildProperties.buildSettings) + package func checkAllImpartedBuildSettings( + file: StaticString = #file, + line: UInt = #line, + _ body: (PIFBuildSettingsTester) -> Void + ) { + let buildSettingsTester = PIFBuildSettingsTester( + buildSettings: baseTarget.buildConfigurations.first!.impartedBuildProperties.buildSettings + ) body(buildSettingsTester) buildSettingsTester.checkUncheckedSettings(file: file, line: line) } - public func checkNoImpartedBuildSettings(file: StaticString = #file, line: UInt = #line) { - let buildSettingsTester = PIFBuildSettingsTester(buildSettings: baseTarget.buildConfigurations.first!.impartedBuildProperties.buildSettings) + package func checkNoImpartedBuildSettings(file: StaticString = #file, line: UInt = #line) { + let buildSettingsTester = PIFBuildSettingsTester( + buildSettings: baseTarget.buildConfigurations.first!.impartedBuildProperties.buildSettings + ) buildSettingsTester.checkUncheckedSettings(file: file, line: line) } } -public final class PIFTargetTester: PIFBaseTargetTester { +package final class PIFTargetTester: PIFBaseTargetTester { private let target: PIF.Target - public var productType: PIF.Target.ProductType { target.productType } - public var productName: String { target.productName } + package var productType: PIF.Target.ProductType { target.productType } + package var productName: String { target.productName } fileprivate init(target: PIF.Target, targetMap: [PIF.GUID: PIF.BaseTarget], fileMap: [PIF.GUID: String]) { self.target = target @@ -191,7 +240,7 @@ public final class PIFTargetTester: PIFBaseTargetTester { } } -public final class PIFAggregateTargetTester: PIFBaseTargetTester { +package final class PIFAggregateTargetTester: PIFBaseTargetTester { private let target: PIF.AggregateTarget fileprivate init(target: PIF.AggregateTarget, targetMap: [PIF.GUID: PIF.BaseTarget], fileMap: [PIF.GUID: String]) { @@ -200,41 +249,41 @@ public final class PIFAggregateTargetTester: PIFBaseTargetTester { } } -public final class PIFBuildConfigurationTester { +package final class PIFBuildConfigurationTester { private let buildConfiguration: PIF.BuildConfiguration - public var guid: PIF.GUID { buildConfiguration.guid } - public var name: String { buildConfiguration.name } + package var guid: PIF.GUID { buildConfiguration.guid } + package var name: String { buildConfiguration.name } fileprivate init(buildConfiguration: PIF.BuildConfiguration) { self.buildConfiguration = buildConfiguration } - public func checkBuildSettings(file: StaticString = #file, line: UInt = #line, _ body: (PIFBuildSettingsTester) -> Void) { + package func checkBuildSettings(file: StaticString = #file, line: UInt = #line, _ body: (PIFBuildSettingsTester) -> Void) { let buildSettingsTester = PIFBuildSettingsTester(buildSettings: buildConfiguration.buildSettings) body(buildSettingsTester) } - public func checkAllBuildSettings(file: StaticString = #file, line: UInt = #line, _ body: (PIFBuildSettingsTester) -> Void) { + package func checkAllBuildSettings(file: StaticString = #file, line: UInt = #line, _ body: (PIFBuildSettingsTester) -> Void) { let buildSettingsTester = PIFBuildSettingsTester(buildSettings: buildConfiguration.buildSettings) body(buildSettingsTester) buildSettingsTester.checkUncheckedSettings(file: file, line: line) } - public func checkNoBuildSettings(file: StaticString = #file, line: UInt = #line) { + package func checkNoBuildSettings(file: StaticString = #file, line: UInt = #line) { let buildSettingsTester = PIFBuildSettingsTester(buildSettings: buildConfiguration.buildSettings) buildSettingsTester.checkUncheckedSettings(file: file, line: line) } } -public final class PIFBuildSettingsTester { +package final class PIFBuildSettingsTester { private var buildSettings: PIF.BuildSettings fileprivate init(buildSettings: PIF.BuildSettings) { self.buildSettings = buildSettings } - public subscript(_ key: PIF.BuildSettings.SingleValueSetting) -> String? { + package subscript(_ key: PIF.BuildSettings.SingleValueSetting) -> String? { if let value = buildSettings[key] { buildSettings[key] = nil return value @@ -243,7 +292,7 @@ public final class PIFBuildSettingsTester { } } - public subscript(_ key: PIF.BuildSettings.SingleValueSetting, for platform: PIF.BuildSettings.Platform) -> String? { + package subscript(_ key: PIF.BuildSettings.SingleValueSetting, for platform: PIF.BuildSettings.Platform) -> String? { if let value = buildSettings[key, for: platform] { buildSettings[key, for: platform] = nil return value @@ -252,7 +301,7 @@ public final class PIFBuildSettingsTester { } } - public subscript(_ key: PIF.BuildSettings.MultipleValueSetting) -> [String]? { + package subscript(_ key: PIF.BuildSettings.MultipleValueSetting) -> [String]? { if let value = buildSettings[key] { buildSettings[key] = nil return value @@ -261,7 +310,7 @@ public final class PIFBuildSettingsTester { } } - public subscript(_ key: PIF.BuildSettings.MultipleValueSetting, for platform: PIF.BuildSettings.Platform) -> [String]? { + package subscript(_ key: PIF.BuildSettings.MultipleValueSetting, for platform: PIF.BuildSettings.Platform) -> [String]? { if let value = buildSettings[key, for: platform] { buildSettings[key, for: platform] = nil return value @@ -270,7 +319,7 @@ public final class PIFBuildSettingsTester { } } - public func checkUncheckedSettings(file: StaticString = #file, line: UInt = #line) { + package func checkUncheckedSettings(file: StaticString = #file, line: UInt = #line) { let uncheckedKeys = Array(buildSettings.singleValueSettings.keys.map { $0.rawValue }) + Array(buildSettings.multipleValueSettings.keys.map { $0.rawValue }) @@ -313,7 +362,12 @@ private func collectFiles( files[reference.guid] = referencePath.pathString } else if let group = reference as? PIF.Group { for child in group.children { - let childFiles = try collectFiles(from: child, parentPath: referencePath, projectPath: projectPath, builtProductsPath: builtProductsPath) + let childFiles = try collectFiles( + from: child, + parentPath: referencePath, + projectPath: projectPath, + builtProductsPath: builtProductsPath + ) files.merge(childFiles, uniquingKeysWith: { _, _ in fatalError("non-unique GUID") }) } } diff --git a/Sources/SPMTestSupport/PackageDependencyDescriptionExtensions.swift b/Sources/SPMTestSupport/PackageDependencyDescriptionExtensions.swift index 3a167448a6f..16c289dc583 100644 --- a/Sources/SPMTestSupport/PackageDependencyDescriptionExtensions.swift +++ b/Sources/SPMTestSupport/PackageDependencyDescriptionExtensions.swift @@ -16,7 +16,7 @@ import PackageModel import struct TSCUtility.Version -public extension PackageDependency { +package extension PackageDependency { static func fileSystem(identity: PackageIdentity? = nil, deprecatedName: String? = nil, path: AbsolutePath, @@ -70,19 +70,19 @@ public extension PackageDependency { // backwards compatibility with existing tests extension PackageDependency.SourceControl.Requirement { - public static func upToNextMajor(from version: Version) -> Self { + package static func upToNextMajor(from version: Version) -> Self { return .range(.upToNextMajor(from: version)) } - public static func upToNextMinor(from version: Version) -> Self { + package static func upToNextMinor(from version: Version) -> Self { return .range(.upToNextMinor(from: version)) } } extension PackageDependency.Registry.Requirement { - public static func upToNextMajor(from version: Version) -> Self { + package static func upToNextMajor(from version: Version) -> Self { return .range(.upToNextMajor(from: version)) } - public static func upToNextMinor(from version: Version) -> Self { + package static func upToNextMinor(from version: Version) -> Self { return .range(.upToNextMinor(from: version)) } } diff --git a/Sources/SPMTestSupport/PackageGraphTester.swift b/Sources/SPMTestSupport/PackageGraphTester.swift index fc4d3c231b5..1ebeda753fe 100644 --- a/Sources/SPMTestSupport/PackageGraphTester.swift +++ b/Sources/SPMTestSupport/PackageGraphTester.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2017 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -17,56 +17,56 @@ import struct Basics.IdentifiableSet import PackageModel import PackageGraph -public func PackageGraphTester(_ graph: PackageGraph, _ result: (PackageGraphResult) -> Void) { - result(PackageGraphResult(graph)) +package func PackageGraphTester(_ graph: ModulesGraph, _ result: (PackageGraphResult) throws -> Void) rethrows { + try result(PackageGraphResult(graph)) } -public final class PackageGraphResult { - public let graph: PackageGraph +package final class PackageGraphResult { + package let graph: ModulesGraph - public init(_ graph: PackageGraph) { + package init(_ graph: ModulesGraph) { self.graph = graph } // TODO: deprecate / transition to PackageIdentity - public func check(roots: String..., file: StaticString = #file, line: UInt = #line) { + package func check(roots: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(graph.rootPackages.map{$0.manifest.displayName }.sorted(), roots.sorted(), file: file, line: line) } - public func check(roots: PackageIdentity..., file: StaticString = #file, line: UInt = #line) { + package func check(roots: PackageIdentity..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(graph.rootPackages.map{$0.identity }.sorted(), roots.sorted(), file: file, line: line) } // TODO: deprecate / transition to PackageIdentity - public func check(packages: String..., file: StaticString = #file, line: UInt = #line) { + package func check(packages: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(graph.packages.map {$0.manifest.displayName }.sorted(), packages.sorted(), file: file, line: line) } - public func check(packages: PackageIdentity..., file: StaticString = #file, line: UInt = #line) { + package func check(packages: PackageIdentity..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(graph.packages.map {$0.identity }.sorted(), packages.sorted(), file: file, line: line) } - public func check(targets: String..., file: StaticString = #file, line: UInt = #line) { + package func check(targets: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual( graph.allTargets - .filter{ $0.type != .test } - .map{ $0.name } + .filter { $0.type != .test } + .map { $0.name } .sorted(), targets.sorted(), file: file, line: line) } - public func check(products: String..., file: StaticString = #file, line: UInt = #line) { + package func check(products: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(Set(graph.allProducts.map { $0.name }), Set(products), file: file, line: line) } - public func check(reachableTargets: String..., file: StaticString = #file, line: UInt = #line) { + package func check(reachableTargets: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(Set(graph.reachableTargets.map { $0.name }), Set(reachableTargets), file: file, line: line) } - public func check(reachableProducts: String..., file: StaticString = #file, line: UInt = #line) { + package func check(reachableProducts: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(Set(graph.reachableProducts.map { $0.name }), Set(reachableProducts), file: file, line: line) } - public func check( + package func check( reachableBuildTargets: String..., in environment: BuildEnvironment, file: StaticString = #file, @@ -76,7 +76,7 @@ public final class PackageGraphResult { XCTAssertEqual(targets, Set(reachableBuildTargets), file: file, line: line) } - public func check( + package func check( reachableBuildProducts: String..., in environment: BuildEnvironment, file: StaticString = #file, @@ -86,31 +86,51 @@ public final class PackageGraphResult { XCTAssertEqual(products, Set(reachableBuildProducts), file: file, line: line) } - public func checkTarget( + package func checkTarget( _ name: String, file: StaticString = #file, line: UInt = #line, body: (ResolvedTargetResult) -> Void ) { - guard let target = find(target: name) else { + let targets = find(target: name) + + guard targets.count > 0 else { return XCTFail("Target \(name) not found", file: file, line: line) } - body(ResolvedTargetResult(target)) + guard targets.count == 1 else { + return XCTFail("More than a single target with name \(name) found", file: file, line: line) + } + body(ResolvedTargetResult(targets[0])) } - public func checkProduct( + package func checkTargets( + _ name: String, + file: StaticString = #file, + line: UInt = #line, + body: ([ResolvedTargetResult]) throws -> Void + ) rethrows { + try body(graph.allTargets.filter { $0.name == name }.map(ResolvedTargetResult.init)) + } + + package func checkProduct( _ name: String, file: StaticString = #file, line: UInt = #line, body: (ResolvedProductResult) -> Void ) { - guard let target = find(product: name) else { + let products = find(product: name) + + guard products.count > 0 else { return XCTFail("Product \(name) not found", file: file, line: line) } - body(ResolvedProductResult(target)) + + guard products.count == 1 else { + return XCTFail("More than a single product with name \(name) found", file: file, line: line) + } + body(ResolvedProductResult(products[0])) } - public func check(testModules: String..., file: StaticString = #file, line: UInt = #line) { + package func check(testModules: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual( graph.allTargets .filter{ $0.type == .test } @@ -118,19 +138,19 @@ public final class PackageGraphResult { .sorted(), testModules.sorted(), file: file, line: line) } - public func find(target: String) -> ResolvedTarget? { - return graph.allTargets.first(where: { $0.name == target }) + package func find(target: String) -> [ResolvedModule] { + return graph.allTargets.filter { $0.name == target } } - public func find(product: String) -> ResolvedProduct? { - return graph.allProducts.first(where: { $0.name == product }) + package func find(product: String) -> [ResolvedProduct] { + return graph.allProducts.filter { $0.name == product } } - public func find(package: PackageIdentity) -> ResolvedPackage? { - return graph.packages.first(where: { $0.identity == package }) + package func find(package: PackageIdentity) -> ResolvedPackage? { + return graph.package(for: package) } - private func reachableBuildTargets(in environment: BuildEnvironment) throws -> IdentifiableSet { + private func reachableBuildTargets(in environment: BuildEnvironment) throws -> IdentifiableSet { let inputTargets = graph.inputPackages.lazy.flatMap { $0.targets } let recursiveBuildTargetDependencies = try inputTargets .flatMap { try $0.recursiveDependencies(satisfying: environment) } @@ -148,18 +168,22 @@ public final class PackageGraphResult { } } -public final class ResolvedTargetResult { - private let target: ResolvedTarget +package final class ResolvedTargetResult { + let target: ResolvedModule - init(_ target: ResolvedTarget) { + init(_ target: ResolvedModule) { self.target = target } - public func check(dependencies: String..., file: StaticString = #file, line: UInt = #line) { + package func check(dependencies: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(Set(dependencies), Set(target.dependencies.map({ $0.name })), file: file, line: line) } - public func checkDependency( + package func check(dependencies: [String], file: StaticString = #file, line: UInt = #line) { + XCTAssertEqual(Set(dependencies), Set(target.dependencies.map({ $0.name })), file: file, line: line) + } + + package func checkDependency( _ name: String, file: StaticString = #file, line: UInt = #line, @@ -171,75 +195,107 @@ public final class ResolvedTargetResult { body(ResolvedTargetDependencyResult(dependency)) } - public func check(type: Target.Kind, file: StaticString = #file, line: UInt = #line) { + package func check(type: Target.Kind, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(type, target.type, file: file, line: line) } - public func checkDeclaredPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { - let targetPlatforms = Dictionary(uniqueKeysWithValues: target.supportedPlatforms.map({ ($0.platform.name, $0.version.versionString) })) + package func checkDeclaredPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { + let targetPlatforms = Dictionary( + uniqueKeysWithValues: target.supportedPlatforms.map { ($0.platform.name, $0.version.versionString) } + ) XCTAssertEqual(platforms, targetPlatforms, file: file, line: line) } - public func checkDerivedPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { + package func checkDerivedPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { let derived = platforms.map { let platform = PlatformRegistry.default.platformByName[$0.key] ?? PackageModel.Platform .custom(name: $0.key, oldestSupportedVersion: $0.value) return self.target.getSupportedPlatform(for: platform, usingXCTest: self.target.type == .test) } let targetPlatforms = Dictionary( - uniqueKeysWithValues: derived - .map { ($0.platform.name, $0.version.versionString) } + uniqueKeysWithValues: derived.map { ($0.platform.name, $0.version.versionString) } ) XCTAssertEqual(platforms, targetPlatforms, file: file, line: line) } - public func checkDerivedPlatformOptions(_ platform: PackageModel.Platform, options: [String], file: StaticString = #file, line: UInt = #line) { - let platform = target.getSupportedPlatform(for: platform, usingXCTest: target.type == .test) + package func checkDerivedPlatformOptions( + _ platform: PackageModel.Platform, + options: [String], + file: StaticString = #file, + line: UInt = #line + ) { + let platform = self.target.getSupportedPlatform(for: platform, usingXCTest: target.type == .test) XCTAssertEqual(platform.options, options, file: file, line: line) } + + public func check(buildTriple: BuildTriple, file: StaticString = #file, line: UInt = #line) { + XCTAssertEqual(self.target.buildTriple, buildTriple, file: file, line: line) + } } -public final class ResolvedTargetDependencyResult { - private let dependency: ResolvedTarget.Dependency +package final class ResolvedTargetDependencyResult { + private let dependency: ResolvedModule.Dependency - init(_ dependency: ResolvedTarget.Dependency) { + init(_ dependency: ResolvedModule.Dependency) { self.dependency = dependency } - public func checkConditions(satisfy environment: BuildEnvironment, file: StaticString = #file, line: UInt = #line) { + package func checkConditions(satisfy environment: BuildEnvironment, file: StaticString = #file, line: UInt = #line) { XCTAssert(dependency.conditions.allSatisfy({ $0.satisfies(environment) }), file: file, line: line) } - public func checkConditions( + package func checkConditions( dontSatisfy environment: BuildEnvironment, file: StaticString = #file, line: UInt = #line ) { XCTAssert(!dependency.conditions.allSatisfy({ $0.satisfies(environment) }), file: file, line: line) } + + public func checkTarget( + file: StaticString = #file, + line: UInt = #line, + body: (ResolvedTargetResult) -> Void + ) { + guard case let .target(target, _) = self.dependency else { + return XCTFail("Dependency \(dependency) is not a target", file: file, line: line) + } + body(ResolvedTargetResult(target)) + } + + public func checkProduct( + file: StaticString = #file, + line: UInt = #line, + body: (ResolvedProductResult) -> Void + ) { + guard case let .product(product, _) = self.dependency else { + return XCTFail("Dependency \(dependency) is not a product", file: file, line: line) + } + body(ResolvedProductResult(product)) + } } -public final class ResolvedProductResult { +package final class ResolvedProductResult { private let product: ResolvedProduct init(_ product: ResolvedProduct) { self.product = product } - public func check(targets: String..., file: StaticString = #file, line: UInt = #line) { + package func check(targets: String..., file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(Set(targets), Set(product.targets.map({ $0.name })), file: file, line: line) } - public func check(type: ProductType, file: StaticString = #file, line: UInt = #line) { + package func check(type: ProductType, file: StaticString = #file, line: UInt = #line) { XCTAssertEqual(type, product.type, file: file, line: line) } - public func checkDeclaredPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { + package func checkDeclaredPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { let targetPlatforms = Dictionary(uniqueKeysWithValues: product.supportedPlatforms.map({ ($0.platform.name, $0.version.versionString) })) XCTAssertEqual(platforms, targetPlatforms, file: file, line: line) } - public func checkDerivedPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { + package func checkDerivedPlatforms(_ platforms: [String: String], file: StaticString = #file, line: UInt = #line) { let derived = platforms.map { let platform = PlatformRegistry.default.platformByName[$0.key] ?? PackageModel.Platform.custom(name: $0.key, oldestSupportedVersion: $0.value) return product.getSupportedPlatform(for: platform, usingXCTest: product.isLinkingXCTest) @@ -248,14 +304,30 @@ public final class ResolvedProductResult { XCTAssertEqual(platforms, targetPlatforms, file: file, line: line) } - public func checkDerivedPlatformOptions(_ platform: PackageModel.Platform, options: [String], file: StaticString = #file, line: UInt = #line) { + package func checkDerivedPlatformOptions(_ platform: PackageModel.Platform, options: [String], file: StaticString = #file, line: UInt = #line) { let platform = product.getSupportedPlatform(for: platform, usingXCTest: product.isLinkingXCTest) XCTAssertEqual(platform.options, options, file: file, line: line) } + + public func check(buildTriple: BuildTriple, file: StaticString = #file, line: UInt = #line) { + XCTAssertEqual(self.product.buildTriple, buildTriple, file: file, line: line) + } + + public func checkTarget( + _ name: String, + file: StaticString = #file, + line: UInt = #line, + body: (ResolvedTargetResult) -> Void + ) { + guard let target = product.targets.first(where: { $0.name == name }) else { + return XCTFail("Target \(name) not found", file: file, line: line) + } + body(ResolvedTargetResult(target)) + } } -extension ResolvedTarget.Dependency { - public var name: String { +extension ResolvedModule.Dependency { + package var name: String { switch self { case .target(let target, _): return target.name diff --git a/Sources/SPMTestSupport/ResolvedTarget+Mock.swift b/Sources/SPMTestSupport/ResolvedTarget+Mock.swift index 0a95c7aa8e1..61ddb1cf7b8 100644 --- a/Sources/SPMTestSupport/ResolvedTarget+Mock.swift +++ b/Sources/SPMTestSupport/ResolvedTarget+Mock.swift @@ -13,14 +13,14 @@ import PackageGraph import PackageModel -extension ResolvedTarget { - public static func mock( +extension ResolvedModule { + package static func mock( packageIdentity: PackageIdentity, name: String, - deps: ResolvedTarget..., + deps: ResolvedModule..., conditions: [PackageCondition] = [] - ) -> ResolvedTarget { - ResolvedTarget( + ) -> ResolvedModule { + ResolvedModule( packageIdentity: packageIdentity, underlying: SwiftTarget( name: name, @@ -29,7 +29,6 @@ extension ResolvedTarget { sources: Sources(paths: [], root: "/"), dependencies: [], packageAccess: false, - swiftVersion: .v4, usesUnsafeFlags: false ), dependencies: deps.map { .target($0, conditions: conditions) }, diff --git a/Sources/SPMTestSupport/SwiftPMProduct.swift b/Sources/SPMTestSupport/SwiftPMProduct.swift index b62a99b428c..93bd49e4857 100644 --- a/Sources/SPMTestSupport/SwiftPMProduct.swift +++ b/Sources/SPMTestSupport/SwiftPMProduct.swift @@ -19,17 +19,19 @@ import struct TSCBasic.ProcessResult /// Defines the executables used by SwiftPM. /// Contains path to the currently built executable and /// helper method to execute them. -public enum SwiftPM { +package enum SwiftPM { case Build case Package case Registry case Test case Run + case experimentalSDK + case sdk } extension SwiftPM { /// Executable name. - private var executableName: RelativePath { + private var executableName: String { switch self { case .Build: return "swift-build" @@ -41,14 +43,18 @@ extension SwiftPM { return "swift-test" case .Run: return "swift-run" + case .experimentalSDK: + return "swift-experimental-sdk" + case .sdk: + return "swift-sdk" } } - public var xctestBinaryPath: AbsolutePath { - Self.xctestBinaryPath(for: executableName) + package var xctestBinaryPath: AbsolutePath { + Self.xctestBinaryPath(for: RelativePath("swift-package-manager")) } - public static func xctestBinaryPath(for executableName: RelativePath) -> AbsolutePath { + package static func xctestBinaryPath(for executableName: RelativePath) -> AbsolutePath { #if canImport(Darwin) for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { return try! AbsolutePath(AbsolutePath(validating: bundle.bundlePath).parentDirectory, executableName) @@ -71,7 +77,7 @@ extension SwiftPM { /// /// - Returns: The output of the process. @discardableResult - public func execute( + package func execute( _ args: [String] = [], packagePath: AbsolutePath? = nil, env: [String: String]? = nil @@ -114,11 +120,12 @@ extension SwiftPM { #endif // FIXME: We use this private environment variable hack to be able to // create special conditions in swift-build for swiftpm tests. - environment["SWIFTPM_TESTS_MODULECACHE"] = xctestBinaryPath.parentDirectory.pathString - + environment["SWIFTPM_TESTS_MODULECACHE"] = self.xctestBinaryPath.parentDirectory.pathString + // Unset the internal env variable that allows skipping certain tests. environment["_SWIFTPM_SKIP_TESTS_LIST"] = nil - + environment["SWIFTPM_EXEC_NAME"] = self.executableName + for (key, value) in env ?? [:] { environment[key] = value } @@ -128,13 +135,13 @@ extension SwiftPM { completeArgs += ["--package-path", packagePath.pathString] } completeArgs += args - + return try Process.popen(arguments: completeArgs, environment: environment) } } extension SwiftPM { - public static func packagePath(for packageName: String, packageRoot: AbsolutePath) throws -> AbsolutePath { + package static func packagePath(for packageName: String, packageRoot: AbsolutePath) throws -> AbsolutePath { // FIXME: The directory paths are hard coded right now and should be replaced by --get-package-path // whenever we design that. https://bugs.swift.org/browse/SR-2753 let packagesPath = packageRoot.appending(components: ".build", "checkouts") @@ -147,12 +154,12 @@ extension SwiftPM { } } -public enum SwiftPMError: Error { +package enum SwiftPMError: Error { case packagePathNotFound case executionFailure(underlying: Error, stdout: String, stderr: String) } -public enum SwiftPMProductError: Swift.Error { +package enum SwiftPMProductError: Swift.Error { case packagePathNotFound case executionFailure(error: Swift.Error, output: String, stderr: String) } diff --git a/Sources/SPMTestSupport/Toolchain.swift b/Sources/SPMTestSupport/Toolchain.swift index ff7e403c6b9..35a1a2c3c11 100644 --- a/Sources/SPMTestSupport/Toolchain.swift +++ b/Sources/SPMTestSupport/Toolchain.swift @@ -38,7 +38,7 @@ private func resolveBinDir() throws -> AbsolutePath { } extension SwiftSDK { - public static var `default`: Self { + package static var `default`: Self { get throws { let binDir = try resolveBinDir() return try! SwiftSDK.hostSwiftSDK(binDir) @@ -47,7 +47,7 @@ extension SwiftSDK { } extension UserToolchain { - public static var `default`: Self { + package static var `default`: Self { get throws { return try .init(swiftSDK: SwiftSDK.default) } @@ -56,7 +56,7 @@ extension UserToolchain { extension UserToolchain { /// Helper function to determine if async await actually works in the current environment. - public func supportsSwiftConcurrency() -> Bool { + package func supportsSwiftConcurrency() -> Bool { #if os(macOS) if #available(macOS 12.0, *) { // On macOS 12 and later, concurrency is assumed to work. @@ -67,7 +67,7 @@ extension UserToolchain { do { try testWithTemporaryDirectory { tmpPath in let inputPath = tmpPath.appending("foo.swift") - try localFileSystem.writeFileContents(inputPath, string: "public func foo() async {}\nTask { await foo() }") + try localFileSystem.writeFileContents(inputPath, string: "package func foo() async {}\nTask { await foo() }") let outputPath = tmpPath.appending("foo") let toolchainPath = self.swiftCompilerPath.parentDirectory.parentDirectory let backDeploymentLibPath = toolchainPath.appending(components: "lib", "swift-5.5", "macosx") @@ -88,7 +88,7 @@ extension UserToolchain { } /// Helper function to determine whether serialized diagnostics work properly in the current environment. - public func supportsSerializedDiagnostics(otherSwiftFlags: [String] = []) -> Bool { + package func supportsSerializedDiagnostics(otherSwiftFlags: [String] = []) -> Bool { do { try testWithTemporaryDirectory { tmpPath in let inputPath = tmpPath.appending("best.swift") @@ -112,7 +112,7 @@ extension UserToolchain { } /// Helper function to determine whether we should run SDK-dependent tests. - public func supportsSDKDependentTests() -> Bool { + package func supportsSDKDependentTests() -> Bool { return ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] == nil } } diff --git a/Sources/SPMTestSupport/XCTAssertHelpers.swift b/Sources/SPMTestSupport/XCTAssertHelpers.swift index a0fcf5e5360..e63d65a2b91 100644 --- a/Sources/SPMTestSupport/XCTAssertHelpers.swift +++ b/Sources/SPMTestSupport/XCTAssertHelpers.swift @@ -26,31 +26,31 @@ import struct TSCUtility.Version @_exported import func TSCTestSupport.XCTAssertResultSuccess @_exported import func TSCTestSupport.XCTAssertThrows -public func XCTAssertFileExists(_ path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { +package func XCTAssertFileExists(_ path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { TSCTestSupport.XCTAssertFileExists(TSCAbsolutePath(path), file: file, line: line) } -public func XCTAssertDirectoryExists(_ path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { +package func XCTAssertDirectoryExists(_ path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { TSCTestSupport.XCTAssertDirectoryExists(TSCAbsolutePath(path), file: file, line: line) } -public func XCTAssertNoSuchPath(_ path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { +package func XCTAssertNoSuchPath(_ path: AbsolutePath, file: StaticString = #file, line: UInt = #line) { TSCTestSupport.XCTAssertNoSuchPath(TSCAbsolutePath(path), file: file, line: line) } -public func XCTAssertEqual (_ lhs:(T,U), _ rhs:(T,U), file: StaticString = #file, line: UInt = #line) { +package func XCTAssertEqual (_ lhs:(T,U), _ rhs:(T,U), file: StaticString = #file, line: UInt = #line) { TSCTestSupport.XCTAssertEqual(lhs, rhs, file: file, line: line) } -public func XCTSkipIfCI(file: StaticString = #filePath, line: UInt = #line) throws { +package func XCTSkipIfCI(file: StaticString = #filePath, line: UInt = #line) throws { if let ci = ProcessInfo.processInfo.environment["CI"] as? NSString, ci.boolValue { throw XCTSkip("Skipping because the test is being run on CI", file: file, line: line) } } /// An `async`-friendly replacement for `XCTAssertThrowsError`. -public func XCTAssertAsyncThrowsError( +package func XCTAssertAsyncThrowsError( _ expression: @autoclosure () async throws -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, @@ -66,7 +66,7 @@ public func XCTAssertAsyncThrowsError( } -public func XCTAssertBuilds( +package func XCTAssertBuilds( _ path: AbsolutePath, configurations: Set = [.Debug, .Release], extraArgs: [String] = [], @@ -94,7 +94,7 @@ public func XCTAssertBuilds( } } -public func XCTAssertSwiftTest( +package func XCTAssertSwiftTest( _ path: AbsolutePath, configuration: Configuration = .Debug, extraArgs: [String] = [], @@ -121,7 +121,7 @@ public func XCTAssertSwiftTest( } @discardableResult -public func XCTAssertBuildFails( +package func XCTAssertBuildFails( _ path: AbsolutePath, Xcc: [String] = [], Xld: [String] = [], @@ -137,7 +137,7 @@ public func XCTAssertBuildFails( return failure } -public func XCTAssertEqual( +package func XCTAssertEqual( _ assignment: [(container: T, version: Version)], _ expected: [T: Version], file: StaticString = #file, @@ -150,7 +150,7 @@ public func XCTAssertEqual( XCTAssertEqual(actual, expected, file: file, line: line) } -public func XCTAssertAsyncTrue( +package func XCTAssertAsyncTrue( _ expression: @autoclosure () async throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, @@ -160,7 +160,7 @@ public func XCTAssertAsyncTrue( XCTAssertTrue(result, message(), file: file, line: line) } -public func XCTAssertAsyncFalse( +package func XCTAssertAsyncFalse( _ expression: @autoclosure () async throws -> Bool, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, @@ -170,7 +170,17 @@ public func XCTAssertAsyncFalse( XCTAssertFalse(result, message(), file: file, line: line) } -public func XCTAssertThrowsCommandExecutionError( +package func XCTAssertAsyncNil( + _ expression: @autoclosure () async throws -> Any?, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) async rethrows { + let result = try await expression() + XCTAssertNil(result, message(), file: file, line: line) +} + +package func XCTAssertThrowsCommandExecutionError( _ expression: @autoclosure () throws -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, @@ -187,8 +197,37 @@ public func XCTAssertThrowsCommandExecutionError( } } -public struct CommandExecutionError: Error { - public let result: ProcessResult - public let stdout: String - public let stderr: String +package func XCTAssertAsyncEqual( + _ expression1: @autoclosure () async throws -> T, + _ expression2: @autoclosure () async throws -> T, + _ message: @autoclosure () -> String = "", + file: StaticString = #file, + line: UInt = #line +) async rethrows { + let value1 = try await expression1() + let value2 = try await expression2() + + XCTAssertEqual(value1, value2, message(), file: file, line: line) +} + +struct XCAsyncTestErrorWhileUnwrappingOptional: Error {} + +package func XCTAsyncUnwrap( + _ expression: @autoclosure () async throws -> T?, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) async throws -> T { + guard let result = try await expression() else { + throw XCAsyncTestErrorWhileUnwrappingOptional() + } + + return result +} + + +package struct CommandExecutionError: Error { + package let result: ProcessResult + package let stdout: String + package let stderr: String } diff --git a/Sources/SPMTestSupport/XCTSkipHelpers.swift b/Sources/SPMTestSupport/XCTSkipHelpers.swift new file mode 100644 index 00000000000..4c96a34ac6c --- /dev/null +++ b/Sources/SPMTestSupport/XCTSkipHelpers.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import PackageModel +import XCTest + +import class TSCBasic.Process +import struct TSCBasic.StringError + +extension Toolchain { + package func skipUnlessAtLeastSwift6( + file: StaticString = #file, + line: UInt = #line + ) async throws { + #if compiler(<6.0) + try XCTSkipIf(true, "Skipping because test requires at least Swift 6.0") + #endif + } +} diff --git a/Sources/SPMTestSupport/misc.swift b/Sources/SPMTestSupport/misc.swift index 938343b7540..5f22bf7e1e2 100644 --- a/Sources/SPMTestSupport/misc.swift +++ b/Sources/SPMTestSupport/misc.swift @@ -16,7 +16,10 @@ import struct Foundation.URL import class Foundation.Bundle #endif import OrderedCollections + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph + import PackageLoading import PackageModel import SourceControl @@ -34,7 +37,7 @@ import enum TSCUtility.Git @_exported import enum TSCTestSupport.StringPattern /// Test helper utility for executing a block with a temporary directory. -public func testWithTemporaryDirectory( +package func testWithTemporaryDirectory( function: StaticString = #function, body: (AbsolutePath) throws -> Void ) throws { @@ -49,7 +52,7 @@ public func testWithTemporaryDirectory( } @discardableResult -public func testWithTemporaryDirectory( +package func testWithTemporaryDirectory( function: StaticString = #function, body: (AbsolutePath) async throws -> Result ) async throws -> Result { @@ -74,7 +77,7 @@ public func testWithTemporaryDirectory( /// The temporary copy is deleted after the block returns. The fixture name may /// contain `/` characters, which are treated as path separators, exactly as if /// the name were a relative path. -@discardableResult public func fixture( +@discardableResult package func fixture( name: String, createGitRepo: Bool = true, file: StaticString = #file, @@ -112,7 +115,7 @@ public func testWithTemporaryDirectory( } } -@discardableResult public func fixture( +@discardableResult package func fixture( name: String, createGitRepo: Bool = true, file: StaticString = #file, @@ -197,7 +200,7 @@ fileprivate func setup(fixtureDir: AbsolutePath, in tmpDirPath: AbsolutePath, co /// Test-helper function that creates a new Git repository in a directory. The new repository will contain /// exactly one empty file unless `addFile` is `false`, and if a tag name is provided, a tag with that name will be /// created. -public func initGitRepo( +package func initGitRepo( _ dir: AbsolutePath, tag: String? = nil, addFile: Bool = true, @@ -207,7 +210,7 @@ public func initGitRepo( initGitRepo(dir, tags: tag.flatMap { [$0] } ?? [], addFile: addFile, file: file, line: line) } -public func initGitRepo( +package func initGitRepo( _ dir: AbsolutePath, tags: [String], addFile: Bool = true, @@ -237,7 +240,7 @@ public func initGitRepo( } @discardableResult -public func executeSwiftBuild( +package func executeSwiftBuild( _ packagePath: AbsolutePath, configuration: Configuration = .Debug, extraArgs: [String] = [], @@ -251,7 +254,7 @@ public func executeSwiftBuild( } @discardableResult -public func executeSwiftRun( +package func executeSwiftRun( _ packagePath: AbsolutePath, _ executable: String, configuration: Configuration = .Debug, @@ -267,7 +270,7 @@ public func executeSwiftRun( } @discardableResult -public func executeSwiftPackage( +package func executeSwiftPackage( _ packagePath: AbsolutePath, configuration: Configuration = .Debug, extraArgs: [String] = [], @@ -281,7 +284,7 @@ public func executeSwiftPackage( } @discardableResult -public func executeSwiftTest( +package func executeSwiftTest( _ packagePath: AbsolutePath, configuration: Configuration = .Debug, extraArgs: [String] = [], @@ -316,7 +319,12 @@ private func swiftArgs( return args } -public func loadPackageGraph( +@available(*, + deprecated, + renamed: "loadModulesGraph", + message: "Renamed for consistency: the type of this functions return value is named `ModulesGraph`." +) +package func loadPackageGraph( identityResolver: IdentityResolver = DefaultIdentityResolver(), fileSystem: FileSystem, manifests: [Manifest], @@ -327,56 +335,37 @@ public func loadPackageGraph( useXCBuildFileRules: Bool = false, customXCTestMinimumDeploymentTargets: [PackageModel.Platform: PlatformVersion]? = .none, observabilityScope: ObservabilityScope -) throws -> PackageGraph { - let rootManifests = manifests.filter(\.packageKind.isRoot).spm_createDictionary { ($0.path, $0) } - let externalManifests = try manifests.filter { !$0.packageKind.isRoot } - .reduce( - into: OrderedCollections - .OrderedDictionary() - ) { partial, item in - partial[try identityResolver.resolveIdentity(for: item.packageKind)] = (item, fileSystem) - } - - let packages = Array(rootManifests.keys) - let input = PackageGraphRootInput(packages: packages) - let graphRoot = PackageGraphRoot( - input: input, - manifests: rootManifests, - explicitProduct: explicitProduct, - observabilityScope: observabilityScope - ) - - return try PackageGraph.load( - root: graphRoot, +) throws -> ModulesGraph { + try loadModulesGraph( identityResolver: identityResolver, - additionalFileRules: useXCBuildFileRules ? FileRuleDescription.xcbuildFileTypes : FileRuleDescription - .swiftpmFileTypes, - externalManifests: externalManifests, + fileSystem: fileSystem, + manifests: manifests, binaryArtifacts: binaryArtifacts, + explicitProduct: explicitProduct, shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts, createREPLProduct: createREPLProduct, + useXCBuildFileRules: useXCBuildFileRules, customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, - fileSystem: fileSystem, observabilityScope: observabilityScope ) } -public let emptyZipFile = ByteString([0x80, 0x75, 0x05, 0x06] + [UInt8](repeating: 0x00, count: 18)) +package let emptyZipFile = ByteString([0x80, 0x75, 0x05, 0x06] + [UInt8](repeating: 0x00, count: 18)) extension FileSystem { @_disfavoredOverload - public func createEmptyFiles(at root: AbsolutePath, files: String...) { + package func createEmptyFiles(at root: AbsolutePath, files: String...) { self.createEmptyFiles(at: TSCAbsolutePath(root), files: files) } @_disfavoredOverload - public func createEmptyFiles(at root: AbsolutePath, files: [String]) { + package func createEmptyFiles(at root: AbsolutePath, files: [String]) { self.createEmptyFiles(at: TSCAbsolutePath(root), files: files) } } extension URL { - public init(_ value: StringLiteralType) { + package init(_ value: StringLiteralType) { self.init(string: value)! } } @@ -394,13 +383,13 @@ extension PackageIdentity { } extension PackageIdentity { - public static func registry(_ value: String) -> RegistryIdentity { + package static func registry(_ value: String) -> RegistryIdentity { Self.plain(value).registry! } } extension AbsolutePath { - public init(_ value: StringLiteralType) { + package init(_ value: StringLiteralType) { try! self.init(validating: value) } } @@ -412,14 +401,14 @@ extension AbsolutePath { } extension AbsolutePath { - public init(_ path: StringLiteralType, relativeTo basePath: AbsolutePath) { + package init(_ path: StringLiteralType, relativeTo basePath: AbsolutePath) { try! self.init(validating: path, relativeTo: basePath) } } extension RelativePath { @available(*, deprecated, message: "use direct string instead") - public init(static path: StaticString) { + package init(static path: StaticString) { let pathString = path.withUTF8Buffer { String(decoding: $0, as: UTF8.self) } @@ -428,7 +417,7 @@ extension RelativePath { } extension RelativePath { - public init(_ value: StringLiteralType) { + package init(_ value: StringLiteralType) { try! self.init(validating: value) } } @@ -440,7 +429,7 @@ extension RelativePath { } extension InitPackage { - public convenience init( + package convenience init( name: String, packageType: PackageType, supportedTestingLibraries: Set = [.xctest], @@ -457,7 +446,7 @@ extension InitPackage { } } -#if swift(<5.11) +#if swift(<6.0) extension RelativePath: ExpressibleByStringLiteral {} extension RelativePath: ExpressibleByStringInterpolation {} extension URL: ExpressibleByStringLiteral {} diff --git a/Sources/SourceControl/GitRepository.swift b/Sources/SourceControl/GitRepository.swift index 5185f444b5f..5661f838062 100644 --- a/Sources/SourceControl/GitRepository.swift +++ b/Sources/SourceControl/GitRepository.swift @@ -1105,7 +1105,7 @@ private class GitFileSystemView: FileSystem { case .symlink: let path = try repository.readLink(hash: hash) return try readFileContents(AbsolutePath(validating: path)) - case .blob: + case .blob, .executableBlob: return try self.repository.readBlob(hash: hash) default: throw InternalError("unsupported git entry type \(entry.type) at path \(path)") diff --git a/Sources/SourceControl/RepositoryManager.swift b/Sources/SourceControl/RepositoryManager.swift index 7c48f8004dd..1891753663f 100644 --- a/Sources/SourceControl/RepositoryManager.swift +++ b/Sources/SourceControl/RepositoryManager.swift @@ -331,7 +331,7 @@ public class RepositoryManager: Cancellable { } // We are expecting handle.repository.url to always be a resolved absolute path. - let shouldCacheLocalPackages = ProcessEnv.vars["SWIFTPM_TESTS_PACKAGECACHE"] == "1" || cacheLocalPackages + let shouldCacheLocalPackages = ProcessEnv.block["SWIFTPM_TESTS_PACKAGECACHE"] == "1" || cacheLocalPackages if let cachePath, !(handle.repository.isLocal && !shouldCacheLocalPackages) { let cachedRepositoryPath = try cachePath.appending(handle.repository.storagePath()) diff --git a/Sources/SourceKitLSPAPI/BuildDescription.swift b/Sources/SourceKitLSPAPI/BuildDescription.swift index 824df666a0d..74614635454 100644 --- a/Sources/SourceKitLSPAPI/BuildDescription.swift +++ b/Sources/SourceKitLSPAPI/BuildDescription.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -10,38 +10,81 @@ // //===----------------------------------------------------------------------===// -import Foundation +import struct Foundation.URL -/*private*/ import struct Basics.AbsolutePath -/*private*/ import func Basics.resolveSymlinks -// FIXME: should not import this module -import Build -// FIXME: should be internal imports -import PackageGraph -/*private*/ import SPMBuildCore +private import struct Basics.AbsolutePath +private import func Basics.resolveSymlinks + +private import SPMBuildCore + +// FIXME: should import these module with `private` or `internal` access control +import class Build.BuildPlan +import class Build.ClangTargetBuildDescription +import class Build.SwiftTargetBuildDescription +import struct PackageGraph.ResolvedModule +import struct PackageGraph.ModulesGraph +import enum PackageGraph.BuildTriple + +public typealias BuildTriple = PackageGraph.BuildTriple public protocol BuildTarget { var sources: [URL] { get } + /// The name of the target. It should be possible to build a target by passing this name to `swift build --target` + var name: String { get } + + var buildTriple: BuildTriple { get } + + /// Whether the target is part of the root package that the user opened or if it's part of a package dependency. + var isPartOfRootPackage: Bool { get } + func compileArguments(for fileURL: URL) throws -> [String] - } +} + +private struct WrappedClangTargetBuildDescription: BuildTarget { + private let description: ClangTargetBuildDescription + let isPartOfRootPackage: Bool + + init(description: ClangTargetBuildDescription, isPartOfRootPackage: Bool) { + self.description = description + self.isPartOfRootPackage = isPartOfRootPackage + } -extension ClangTargetBuildDescription: BuildTarget { public var sources: [URL] { - return (try? compilePaths().map { URL(fileURLWithPath: $0.source.pathString) }) ?? [] + return (try? description.compilePaths().map { URL(fileURLWithPath: $0.source.pathString) }) ?? [] + } + + public var name: String { + return description.clangTarget.name + } + + public var buildTriple: BuildTriple { + return description.target.buildTriple } public func compileArguments(for fileURL: URL) throws -> [String] { let filePath = try resolveSymlinks(try AbsolutePath(validating: fileURL.path)) - return try self.emitCommandLine(for: filePath) + let commandLine = try description.emitCommandLine(for: filePath) + // First element on the command line is the compiler itself, not an argument. + return Array(commandLine.dropFirst()) } } private struct WrappedSwiftTargetBuildDescription: BuildTarget { private let description: SwiftTargetBuildDescription + let isPartOfRootPackage: Bool - init(description: SwiftTargetBuildDescription) { + init(description: SwiftTargetBuildDescription, isPartOfRootPackage: Bool) { self.description = description + self.isPartOfRootPackage = isPartOfRootPackage + } + + public var name: String { + return description.target.name + } + + public var buildTriple: BuildTriple { + return description.target.buildTriple } var sources: [URL] { @@ -49,8 +92,11 @@ private struct WrappedSwiftTargetBuildDescription: BuildTarget { } func compileArguments(for fileURL: URL) throws -> [String] { - // Note: we ignore the `fileURL` here as the expectation is that we get a commandline for the entire target in case of Swift. - return try description.emitCommandLine(scanInvocation: false) + // Note: we ignore the `fileURL` here as the expectation is that we get a command line for the entire target + // in case of Swift. + let commandLine = try description.emitCommandLine(scanInvocation: false) + // First element on the command line is the compiler itself, not an argument. + return Array(commandLine.dropFirst()) } } @@ -63,19 +109,37 @@ public struct BuildDescription { } // FIXME: should not use `ResolvedTarget` in the public interface - public func getBuildTarget(for target: ResolvedTarget) -> BuildTarget? { + public func getBuildTarget(for target: ResolvedModule, in modulesGraph: ModulesGraph) -> BuildTarget? { if let description = buildPlan.targetMap[target.id] { switch description { case .clang(let description): - return description + return WrappedClangTargetBuildDescription( + description: description, + isPartOfRootPackage: modulesGraph.rootPackages.map(\.id).contains(description.package.id) + ) case .swift(let description): - return WrappedSwiftTargetBuildDescription(description: description) + return WrappedSwiftTargetBuildDescription( + description: description, + isPartOfRootPackage: modulesGraph.rootPackages.map(\.id).contains(description.package.id) + ) } } else { if target.type == .plugin, let package = self.buildPlan.graph.package(for: target) { - return PluginTargetBuildDescription(target: target, toolsVersion: package.manifest.toolsVersion) + return PluginTargetBuildDescription( + target: target, + toolsVersion: package.manifest.toolsVersion, + isPartOfRootPackage: modulesGraph.rootPackages.map(\.id).contains(package.id) + ) } return nil } } + + /// Returns all targets within the module graph in topological order, starting with low-level targets (that have no + /// dependencies). + public func allTargetsInTopologicalOrder(in modulesGraph: ModulesGraph) throws -> [BuildTarget] { + try modulesGraph.allTargetsInTopologicalOrder.compactMap { + getBuildTarget(for: $0, in: modulesGraph) + } + } } diff --git a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift index f1aa33b86d1..93a01d0dad3 100644 --- a/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift +++ b/Sources/SourceKitLSPAPI/PluginTargetBuildDescription.swift @@ -10,29 +10,39 @@ // //===----------------------------------------------------------------------===// -import Foundation +import struct Foundation.URL -// FIXME: should be an internal import -import struct PackageGraph.ResolvedTarget +import struct PackageGraph.ResolvedModule -/*private*/ import class PackageLoading.ManifestLoader -/*private*/ import struct PackageModel.ToolsVersion -/*private*/ import class PackageModel.UserToolchain +private import class PackageLoading.ManifestLoader +internal import struct PackageModel.ToolsVersion +private import class PackageModel.UserToolchain +import enum PackageGraph.BuildTriple struct PluginTargetBuildDescription: BuildTarget { - private let target: ResolvedTarget + private let target: ResolvedModule private let toolsVersion: ToolsVersion + let isPartOfRootPackage: Bool - init(target: ResolvedTarget, toolsVersion: ToolsVersion) { + init(target: ResolvedModule, toolsVersion: ToolsVersion, isPartOfRootPackage: Bool) { assert(target.type == .plugin) self.target = target self.toolsVersion = toolsVersion + self.isPartOfRootPackage = isPartOfRootPackage } var sources: [URL] { return target.sources.paths.map { URL(fileURLWithPath: $0.pathString) } } + var name: String { + return target.name + } + + var buildTriple: BuildTriple { + return target.buildTriple + } + func compileArguments(for fileURL: URL) throws -> [String] { // FIXME: This is very odd and we should clean this up by merging `ManifestLoader` and `DefaultPluginScriptRunner` again. let loader = ManifestLoader(toolchain: try UserToolchain(swiftSDK: .hostSwiftSDK())) diff --git a/Sources/SwiftSDKTool/CMakeLists.txt b/Sources/SwiftSDKCommand/CMakeLists.txt similarity index 76% rename from Sources/SwiftSDKTool/CMakeLists.txt rename to Sources/SwiftSDKCommand/CMakeLists.txt index 607582ab432..4dcfa36a2af 100644 --- a/Sources/SwiftSDKTool/CMakeLists.txt +++ b/Sources/SwiftSDKCommand/CMakeLists.txt @@ -6,28 +6,29 @@ # See http://swift.org/LICENSE.txt for license information # See http://swift.org/CONTRIBUTORS.txt for Swift project authors -add_library(SwiftSDKTool +add_library(SwiftSDKCommand Configuration/ConfigurationSubcommand.swift - Configuration/ConfigureSwiftSDK.swift + Configuration/DeprecatedSwiftSDKConfigurationCommand.swift Configuration/ResetConfiguration.swift Configuration/SetConfiguration.swift Configuration/ShowConfiguration.swift + ConfigureSwiftSDK.swift SwiftSDKSubcommand.swift InstallSwiftSDK.swift ListSwiftSDKs.swift RemoveSwiftSDK.swift - SwiftSDKTool.swift) -target_link_libraries(SwiftSDKTool PUBLIC + SwiftSDKCommand.swift) +target_link_libraries(SwiftSDKCommand PUBLIC ArgumentParser Basics CoreCommands SPMBuildCore PackageModel) # NOTE(compnerd) workaround for CMake not setting up include flags yet -set_target_properties(SwiftSDKTool PROPERTIES +set_target_properties(SwiftSDKCommand PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) -install(TARGETS SwiftSDKTool +install(TARGETS SwiftSDKCommand ARCHIVE DESTINATION lib LIBRARY DESTINATION lib RUNTIME DESTINATION bin) diff --git a/Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift b/Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift similarity index 93% rename from Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift rename to Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift index 3c8e6b4a71e..2c7098a02f8 100644 --- a/Sources/SwiftSDKTool/Configuration/ConfigurationSubcommand.swift +++ b/Sources/SwiftSDKCommand/Configuration/ConfigurationSubcommand.swift @@ -12,6 +12,7 @@ import ArgumentParser import Basics +import Foundation import PackageModel protocol ConfigurationSubcommand: SwiftSDKSubcommand { @@ -47,6 +48,8 @@ extension ConfigurationSubcommand { _ swiftSDKsDirectory: AbsolutePath, _ observabilityScope: ObservabilityScope ) throws { + fputs("warning: `swift sdk configuration` command is deprecated and will be removed in a future version of SwiftPM. Use `swift sdk configure` instead.\n", stderr) + let bundleStore = SwiftSDKBundleStore( swiftSDKsDirectory: swiftSDKsDirectory, fileSystem: self.fileSystem, diff --git a/Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift b/Sources/SwiftSDKCommand/Configuration/DeprecatedSwiftSDKConfigurationCommand.swift similarity index 79% rename from Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift rename to Sources/SwiftSDKCommand/Configuration/DeprecatedSwiftSDKConfigurationCommand.swift index fdf0bd24151..86cd4332b57 100644 --- a/Sources/SwiftSDKTool/Configuration/ConfigureSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/Configuration/DeprecatedSwiftSDKConfigurationCommand.swift @@ -11,11 +11,15 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import Basics +import PackageModel -public struct ConfigureSwiftSDK: ParsableCommand { - public static let configuration = CommandConfiguration( +struct DeprecatedSwiftSDKConfigurationCommand: ParsableCommand { + static let configuration = CommandConfiguration( commandName: "configuration", abstract: """ + Deprecated: use `swift sdk configure` instead. + Manages configuration options for installed Swift SDKs. """, subcommands: [ @@ -24,6 +28,4 @@ public struct ConfigureSwiftSDK: ParsableCommand { ShowConfiguration.self, ] ) - - public init() {} } diff --git a/Sources/SwiftSDKTool/Configuration/ResetConfiguration.swift b/Sources/SwiftSDKCommand/Configuration/ResetConfiguration.swift similarity index 100% rename from Sources/SwiftSDKTool/Configuration/ResetConfiguration.swift rename to Sources/SwiftSDKCommand/Configuration/ResetConfiguration.swift diff --git a/Sources/SwiftSDKTool/Configuration/SetConfiguration.swift b/Sources/SwiftSDKCommand/Configuration/SetConfiguration.swift similarity index 100% rename from Sources/SwiftSDKTool/Configuration/SetConfiguration.swift rename to Sources/SwiftSDKCommand/Configuration/SetConfiguration.swift diff --git a/Sources/SwiftSDKTool/Configuration/ShowConfiguration.swift b/Sources/SwiftSDKCommand/Configuration/ShowConfiguration.swift similarity index 100% rename from Sources/SwiftSDKTool/Configuration/ShowConfiguration.swift rename to Sources/SwiftSDKCommand/Configuration/ShowConfiguration.swift diff --git a/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift b/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift new file mode 100644 index 00000000000..3708bf7eba9 --- /dev/null +++ b/Sources/SwiftSDKCommand/ConfigureSwiftSDK.swift @@ -0,0 +1,252 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Basics +import CoreCommands +import Dispatch +import PackageModel + +import var TSCBasic.stdoutStream + +struct ConfigureSwiftSDK: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "configure", + abstract: """ + Manages configuration options for installed Swift SDKs. + """ + ) + + @OptionGroup(visibility: .hidden) + var locations: LocationOptions + + @Option(help: "A path to a directory containing the SDK root.") + var sdkRootPath: String? = nil + + @Option(help: "A path to a directory containing Swift resources for dynamic linking.") + var swiftResourcesPath: String? = nil + + @Option(help: "A path to a directory containing Swift resources for static linking.") + var swiftStaticResourcesPath: String? = nil + + @Option( + parsing: .singleValue, + help: """ + A path to a directory containing headers. Multiple paths can be specified by providing this option multiple \ + times to the command. + """ + ) + var includeSearchPath: [String] = [] + + @Option( + parsing: .singleValue, + help: """ + "A path to a directory containing libraries. Multiple paths can be specified by providing this option multiple \ + times to the command. + """ + ) + var librarySearchPath: [String] = [] + + @Option( + parsing: .singleValue, + help: """ + "A path to a toolset file. Multiple paths can be specified by providing this option multiple times to the command. + """ + ) + var toolsetPath: [String] = [] + + @Flag( + name: .customLong("reset"), + help: """ + Resets configuration properties currently applied to a given Swift SDK and target triple. If no specific \ + property is specified, all of them are reset for the Swift SDK. + """ + ) + var shouldReset: Bool = false + + @Flag( + name: .customLong("show-configuration"), + help: """ + Prints all configuration properties currently applied to a given Swift SDK and target triple. + """ + ) + var shouldShowConfiguration: Bool = false + + @Argument( + help: """ + An identifier of an already installed Swift SDK. Use the `list` subcommand to see all available \ + identifiers. + """ + ) + var sdkID: String + + @Argument(help: "The target triple of the Swift SDK to configure.") + var targetTriple: String + + /// The file system used by default by this command. + private var fileSystem: FileSystem { localFileSystem } + + /// Parses Swift SDKs directory option if provided or uses the default path for Swift SDKs + /// on the file system. A new directory at this path is created if one doesn't exist already. + /// - Returns: existing or a newly created directory at the computed location. + private func getOrCreateSwiftSDKsDirectory() throws -> AbsolutePath { + var swiftSDKsDirectory = try fileSystem.getSharedSwiftSDKsDirectory( + explicitDirectory: locations.swiftSDKsDirectory + ) + + if !self.fileSystem.exists(swiftSDKsDirectory) { + swiftSDKsDirectory = try self.fileSystem.getOrCreateSwiftPMSwiftSDKsDirectory() + } + + return swiftSDKsDirectory + } + + func run() async throws { + let observabilityHandler = SwiftCommandObservabilityHandler(outputStream: stdoutStream, logLevel: .info) + let observabilitySystem = ObservabilitySystem(observabilityHandler) + let observabilityScope = observabilitySystem.topScope + let swiftSDKsDirectory = try self.getOrCreateSwiftSDKsDirectory() + + let hostToolchain = try UserToolchain(swiftSDK: SwiftSDK.hostSwiftSDK()) + let triple = try Triple.getHostTriple(usingSwiftCompiler: hostToolchain.swiftCompilerPath) + + var commandError: Error? = nil + do { + let bundleStore = SwiftSDKBundleStore( + swiftSDKsDirectory: swiftSDKsDirectory, + fileSystem: self.fileSystem, + observabilityScope: observabilityScope, + outputHandler: { print($0) } + ) + let configurationStore = try SwiftSDKConfigurationStore( + hostTimeTriple: triple, + swiftSDKBundleStore: bundleStore + ) + let targetTriple = try Triple(self.targetTriple) + + guard let swiftSDK = try configurationStore.readConfiguration( + sdkID: sdkID, + targetTriple: targetTriple + ) else { + throw SwiftSDKError.swiftSDKNotFound( + artifactID: sdkID, + hostTriple: triple, + targetTriple: targetTriple + ) + } + + if self.shouldShowConfiguration { + print(swiftSDK.pathsConfiguration) + return + } + + var configuration = swiftSDK.pathsConfiguration + if self.shouldReset { + if try !configurationStore.resetConfiguration(sdkID: sdkID, targetTriple: targetTriple) { + observabilityScope.emit( + warning: "No configuration for Swift SDK `\(sdkID)`" + ) + } else { + observabilityScope.emit( + info: """ + All configuration properties of Swift SDK `\(sdkID)` for target triple \ + `\(targetTriple)` were successfully reset. + """ + ) + } + } else { + var updatedProperties = [String]() + + let currentWorkingDirectory: AbsolutePath? = fileSystem.currentWorkingDirectory + + if let sdkRootPath { + configuration.sdkRootPath = try AbsolutePath(validating: sdkRootPath, relativeTo: currentWorkingDirectory) + updatedProperties.append(CodingKeys.sdkRootPath.stringValue) + } + + if let swiftResourcesPath { + configuration.swiftResourcesPath = + try AbsolutePath(validating: swiftResourcesPath, relativeTo: currentWorkingDirectory) + updatedProperties.append(CodingKeys.swiftResourcesPath.stringValue) + } + + if let swiftStaticResourcesPath { + configuration.swiftResourcesPath = + try AbsolutePath(validating: swiftStaticResourcesPath, relativeTo: currentWorkingDirectory) + updatedProperties.append(CodingKeys.swiftStaticResourcesPath.stringValue) + } + + if !includeSearchPath.isEmpty { + configuration.includeSearchPaths = + try includeSearchPath.map { try AbsolutePath(validating: $0, relativeTo: currentWorkingDirectory) } + updatedProperties.append(CodingKeys.includeSearchPath.stringValue) + } + + if !librarySearchPath.isEmpty { + configuration.librarySearchPaths = + try librarySearchPath.map { try AbsolutePath(validating: $0, relativeTo: currentWorkingDirectory) } + updatedProperties.append(CodingKeys.librarySearchPath.stringValue) + } + + if !toolsetPath.isEmpty { + configuration.toolsetPaths = + try toolsetPath.map { try AbsolutePath(validating: $0, relativeTo: currentWorkingDirectory) } + updatedProperties.append(CodingKeys.toolsetPath.stringValue) + } + + guard !updatedProperties.isEmpty else { + observabilityScope.emit( + error: """ + No properties of Swift SDK `\(sdkID)` for target triple `\(targetTriple)` were updated \ + since none were specified. Pass `--help` flag to see the list of all available properties. + """ + ) + return + } + + var swiftSDK = swiftSDK + swiftSDK.pathsConfiguration = configuration + try configurationStore.updateConfiguration(sdkID: sdkID, swiftSDK: swiftSDK) + + observabilityScope.emit( + info: """ + These properties of Swift SDK `\(sdkID)` for target triple \ + `\(targetTriple)` were successfully updated: \(updatedProperties.joined(separator: ", ")). + """ + ) + } + + if observabilityScope.errorsReported { + throw ExitCode.failure + } + } catch { + commandError = error + } + + // wait for all observability items to process + observabilityHandler.wait(timeout: .now() + 5) + + if let commandError { + throw commandError + } + } +} + +extension AbsolutePath { + fileprivate init(validating string: String, relativeTo basePath: AbsolutePath?) throws { + if let basePath { + try self.init(validating: string, relativeTo: basePath) + } else { + try self.init(validating: string) + } + } +} diff --git a/Sources/SwiftSDKTool/InstallSwiftSDK.swift b/Sources/SwiftSDKCommand/InstallSwiftSDK.swift similarity index 81% rename from Sources/SwiftSDKTool/InstallSwiftSDK.swift rename to Sources/SwiftSDKCommand/InstallSwiftSDK.swift index 9ab54a0a8dd..354b97b00d2 100644 --- a/Sources/SwiftSDKTool/InstallSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/InstallSwiftSDK.swift @@ -11,17 +11,15 @@ //===----------------------------------------------------------------------===// import ArgumentParser -@_spi(SwiftPMInternal) import Basics import CoreCommands import Foundation import PackageModel import var TSCBasic.stdoutStream -import class TSCUtility.PercentProgressAnimation -public struct InstallSwiftSDK: SwiftSDKSubcommand { - public static let configuration = CommandConfiguration( +struct InstallSwiftSDK: SwiftSDKSubcommand { + static let configuration = CommandConfiguration( commandName: "install", abstract: """ Installs a given Swift SDK bundle to a location discoverable by SwiftPM. If the artifact bundle \ @@ -35,8 +33,6 @@ public struct InstallSwiftSDK: SwiftSDKSubcommand { @Argument(help: "A local filesystem path or a URL of a Swift SDK bundle to install.") var bundlePathOrURL: String - public init() {} - func run( hostTriple: Triple, _ swiftSDKsDirectory: AbsolutePath, @@ -50,9 +46,9 @@ public struct InstallSwiftSDK: SwiftSDKSubcommand { fileSystem: self.fileSystem, observabilityScope: observabilityScope, outputHandler: { print($0.description) }, - downloadProgressAnimation: ThrottledProgressAnimation( - PercentProgressAnimation(stream: stdoutStream, header: "Downloading"), interval: .milliseconds(300) - ) + downloadProgressAnimation: ProgressAnimation + .percent(stream: stdoutStream, verbose: false, header: "Downloading") + .throttled(interval: .milliseconds(300)) ) try await store.install( bundlePathOrURL: bundlePathOrURL, diff --git a/Sources/SwiftSDKTool/ListSwiftSDKs.swift b/Sources/SwiftSDKCommand/ListSwiftSDKs.swift similarity index 91% rename from Sources/SwiftSDKTool/ListSwiftSDKs.swift rename to Sources/SwiftSDKCommand/ListSwiftSDKs.swift index 23e221788fb..a6f257d0289 100644 --- a/Sources/SwiftSDKTool/ListSwiftSDKs.swift +++ b/Sources/SwiftSDKCommand/ListSwiftSDKs.swift @@ -16,8 +16,8 @@ import CoreCommands import PackageModel import SPMBuildCore -public struct ListSwiftSDKs: SwiftSDKSubcommand { - public static let configuration = CommandConfiguration( +struct ListSwiftSDKs: SwiftSDKSubcommand { + static let configuration = CommandConfiguration( commandName: "list", abstract: """ @@ -28,8 +28,6 @@ public struct ListSwiftSDKs: SwiftSDKSubcommand { @OptionGroup() var locations: LocationOptions - public init() {} - func run( hostTriple: Triple, _ swiftSDKsDirectory: AbsolutePath, diff --git a/Sources/SwiftSDKTool/README.md b/Sources/SwiftSDKCommand/README.md similarity index 55% rename from Sources/SwiftSDKTool/README.md rename to Sources/SwiftSDKCommand/README.md index 15fb0924c16..94180372b7a 100644 --- a/Sources/SwiftSDKTool/README.md +++ b/Sources/SwiftSDKCommand/README.md @@ -1,4 +1,4 @@ -# Swift SDKs Tool +# Swift SDKs Command -This module implements `swift experimental-sdk` command and its subcommands, which allow managing artifact +This module implements `swift sdk` command and its subcommands, which allow managing artifact bundles used as Swift SDKs, as specified in [SE-0387](https://github.com/apple/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md). diff --git a/Sources/SwiftSDKTool/RemoveSwiftSDK.swift b/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift similarity index 96% rename from Sources/SwiftSDKTool/RemoveSwiftSDK.swift rename to Sources/SwiftSDKCommand/RemoveSwiftSDK.swift index 2c0a735c257..d74e0022cf0 100644 --- a/Sources/SwiftSDKTool/RemoveSwiftSDK.swift +++ b/Sources/SwiftSDKCommand/RemoveSwiftSDK.swift @@ -15,8 +15,8 @@ import Basics import CoreCommands import PackageModel -public struct RemoveSwiftSDK: SwiftSDKSubcommand { - public static let configuration = CommandConfiguration( +struct RemoveSwiftSDK: SwiftSDKSubcommand { + static let configuration = CommandConfiguration( commandName: "remove", abstract: """ Removes a previously installed Swift SDK bundle from the filesystem. @@ -29,8 +29,6 @@ public struct RemoveSwiftSDK: SwiftSDKSubcommand { @Argument(help: "Name of the Swift SDK bundle or ID of the Swift SDK to remove from the filesystem.") var sdkIDOrBundleName: String - public init() {} - func run( hostTriple: Triple, _ swiftSDKsDirectory: AbsolutePath, diff --git a/Sources/SwiftSDKTool/SwiftSDKTool.swift b/Sources/SwiftSDKCommand/SwiftSDKCommand.swift similarity index 81% rename from Sources/SwiftSDKTool/SwiftSDKTool.swift rename to Sources/SwiftSDKCommand/SwiftSDKCommand.swift index 3b2799ac783..e167cc548ac 100644 --- a/Sources/SwiftSDKTool/SwiftSDKTool.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKCommand.swift @@ -13,14 +13,15 @@ import ArgumentParser import Basics -public struct SwiftSDKTool: AsyncParsableCommand { - public static let configuration = CommandConfiguration( - commandName: "experimental-sdk", +package struct SwiftSDKCommand: AsyncParsableCommand { + package static let configuration = CommandConfiguration( + commandName: "sdk", _superCommandName: "swift", abstract: "Perform operations on Swift SDKs.", version: SwiftVersion.current.completeDisplayString, subcommands: [ ConfigureSwiftSDK.self, + DeprecatedSwiftSDKConfigurationCommand.self, InstallSwiftSDK.self, ListSwiftSDKs.self, RemoveSwiftSDK.self, @@ -28,5 +29,5 @@ public struct SwiftSDKTool: AsyncParsableCommand { helpNames: [.short, .long, .customLong("help", withSingleDash: true)] ) - public init() {} + package init() {} } diff --git a/Sources/SwiftSDKTool/SwiftSDKSubcommand.swift b/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift similarity index 95% rename from Sources/SwiftSDKTool/SwiftSDKSubcommand.swift rename to Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift index c721e918de9..1ec68898a6e 100644 --- a/Sources/SwiftSDKTool/SwiftSDKSubcommand.swift +++ b/Sources/SwiftSDKCommand/SwiftSDKSubcommand.swift @@ -54,8 +54,8 @@ extension SwiftSDKSubcommand { return swiftSDKsDirectory } - public func run() async throws { - let observabilityHandler = SwiftToolObservabilityHandler(outputStream: stdoutStream, logLevel: .info) + func run() async throws { + let observabilityHandler = SwiftCommandObservabilityHandler(outputStream: stdoutStream, logLevel: .info) let observabilitySystem = ObservabilitySystem(observabilityHandler) let observabilityScope = observabilitySystem.topScope let swiftSDKsDirectory = try self.getOrCreateSwiftSDKsDirectory() diff --git a/Sources/Workspace/DefaultPluginScriptRunner.swift b/Sources/Workspace/DefaultPluginScriptRunner.swift index be98dcf9f01..5fff93e0915 100644 --- a/Sources/Workspace/DefaultPluginScriptRunner.swift +++ b/Sources/Workspace/DefaultPluginScriptRunner.swift @@ -209,7 +209,7 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable { #endif // Honor any module cache override that's set in the environment. - let moduleCachePath = ProcessEnv.vars["SWIFTPM_MODULECACHE_OVERRIDE"] ?? ProcessEnv.vars["SWIFTPM_TESTS_MODULECACHE"] + let moduleCachePath = ProcessEnv.block["SWIFTPM_MODULECACHE_OVERRIDE"] ?? ProcessEnv.block["SWIFTPM_TESTS_MODULECACHE"] if let moduleCachePath { commandLine += ["-module-cache-path", moduleCachePath] } diff --git a/Sources/Workspace/Diagnostics.swift b/Sources/Workspace/Diagnostics.swift index 8e52c7dd575..8bf918875bc 100644 --- a/Sources/Workspace/Diagnostics.swift +++ b/Sources/Workspace/Diagnostics.swift @@ -247,7 +247,7 @@ extension FileSystemError { } } -#if swift(<5.11) +#if swift(<6.0) extension FileSystemError: CustomStringConvertible {} #else extension FileSystemError: @retroactive CustomStringConvertible {} diff --git a/Sources/Workspace/InitPackage.swift b/Sources/Workspace/InitPackage.swift index a03355ba247..28db61c183a 100644 --- a/Sources/Workspace/InitPackage.swift +++ b/Sources/Workspace/InitPackage.swift @@ -186,8 +186,8 @@ public final class InitPackage { var platforms = options.platforms - // Macros require macOS 10.15, iOS 13, etc. - if packageType == .macro { + // Macros and swift-testing require macOS 10.15, iOS 13, etc. + if packageType == .macro || options.supportedTestingLibraries.contains(.swiftTesting) { func addIfMissing(_ newPlatform: SupportedPlatform) { if platforms.contains(where: { platform in platform.platform == newPlatform.platform @@ -836,7 +836,7 @@ public final class InitPackage { of node: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext ) -> ExprSyntax { - guard let argument = node.argumentList.first?.expression else { + guard let argument = node.arguments.first?.expression else { fatalError("compiler bug: the macro does not have any arguments") } diff --git a/Sources/Workspace/ManagedDependency.swift b/Sources/Workspace/ManagedDependency.swift index b771f37221c..5f2fd75cfc5 100644 --- a/Sources/Workspace/ManagedDependency.swift +++ b/Sources/Workspace/ManagedDependency.swift @@ -34,6 +34,9 @@ extension Workspace { /// The dependency is downloaded from a registry. case registryDownload(version: Version) + /// The dependency is part of the toolchain in a binary form. + case providedLibrary(at: AbsolutePath, version: Version) + /// The dependency is in edited state. /// /// If the path is non-nil, the dependency is managed by a user and is @@ -51,6 +54,8 @@ extension Workspace { return "sourceControlCheckout (\(checkoutState))" case .registryDownload(let version): return "registryDownload (\(version))" + case .providedLibrary(let path, let version): + return "library (\(path) @ \(version)" case .edited: return "edited" case .custom: @@ -146,6 +151,17 @@ extension Workspace { ) } + public static func providedLibrary( + packageRef: PackageReference, + library: ProvidedLibrary + ) throws -> ManagedDependency { + ManagedDependency( + packageRef: packageRef, + state: .providedLibrary(at: library.location, version: library.version), + subpath: try RelativePath(validating: packageRef.identity.description) + ) + } + /// Create an edited dependency public static func edited( packageRef: PackageReference, diff --git a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift index 6e634df9fd4..03f48dd0120 100644 --- a/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift +++ b/Sources/Workspace/PackageContainer/SourceControlPackageContainer.swift @@ -354,7 +354,7 @@ internal final class SourceControlPackageContainer: PackageContainer, CustomStri let revision: Revision var version: Version? switch boundVersion { - case .version(let v): + case .version(let v, _): guard let tag = try self.knownVersions()[v] else { throw StringError("unknown tag \(v)") } diff --git a/Sources/Workspace/ResolverPrecomputationProvider.swift b/Sources/Workspace/ResolverPrecomputationProvider.swift index c5eaca741e8..7308bbcfcac 100644 --- a/Sources/Workspace/ResolverPrecomputationProvider.swift +++ b/Sources/Workspace/ResolverPrecomputationProvider.swift @@ -63,7 +63,9 @@ struct ResolverPrecomputationProvider: PackageContainerProvider { ) { queue.async { // Start by searching manifests from the Workspace's resolved dependencies. - if let manifest = self.dependencyManifests.dependencies.first(where: { _, managed, _, _ in managed.packageRef == package }) { + if let manifest = self.dependencyManifests.dependencies + .first(where: { _, managed, _, _ in managed.packageRef == package }) + { let container = LocalPackageContainer( package: package, manifest: manifest.manifest, @@ -99,7 +101,7 @@ private struct LocalPackageContainer: PackageContainer { let shouldInvalidatePinnedVersions = false func versionsAscending() throws -> [Version] { - switch dependency?.state { + switch self.dependency?.state { case .sourceControlCheckout(.version(let version, revision: _)): return [version] case .registryDownload(let version): @@ -111,7 +113,10 @@ private struct LocalPackageContainer: PackageContainer { func isToolsVersionCompatible(at version: Version) -> Bool { do { - try manifest.toolsVersion.validateToolsVersion(currentToolsVersion, packageIdentity: .plain("unknown")) + try self.manifest.toolsVersion.validateToolsVersion( + self.currentToolsVersion, + packageIdentity: .plain("unknown") + ) return true } catch { return false @@ -119,31 +124,36 @@ private struct LocalPackageContainer: PackageContainer { } func toolsVersion(for version: Version) throws -> ToolsVersion { - return currentToolsVersion + self.currentToolsVersion } func toolsVersionsAppropriateVersionsDescending() throws -> [Version] { - return try self.versionsDescending() + try self.versionsDescending() } func getDependencies(at version: Version, productFilter: ProductFilter) throws -> [PackageContainerConstraint] { // Because of the implementation of `reversedVersions`, we should only get the exact same version. - switch dependency?.state { + switch self.dependency?.state { case .sourceControlCheckout(.version(version, revision: _)): - return try manifest.dependencyConstraints(productFilter: productFilter) + return try self.manifest.dependencyConstraints(productFilter: productFilter) case .registryDownload(version: version): - return try manifest.dependencyConstraints(productFilter: productFilter) + return try self.manifest.dependencyConstraints(productFilter: productFilter) default: - throw InternalError("expected version based state, but state was \(String(describing: dependency?.state))") + throw InternalError( + "expected version based state, but state was \(String(describing: self.dependency?.state))" + ) } } - func getDependencies(at revisionString: String, productFilter: ProductFilter) throws -> [PackageContainerConstraint] { + func getDependencies( + at revisionString: String, + productFilter: ProductFilter + ) throws -> [PackageContainerConstraint] { let revision = Revision(identifier: revisionString) - switch dependency?.state { + switch self.dependency?.state { case .sourceControlCheckout(.branch(_, revision: revision)), .sourceControlCheckout(.revision(revision)): // Return the dependencies if the checkout state matches the revision. - return try manifest.dependencyConstraints(productFilter: productFilter) + return try self.manifest.dependencyConstraints(productFilter: productFilter) default: // Throw an error when the dependency is not revision based to fail resolution. throw ResolverPrecomputationError.differentRequirement( @@ -155,14 +165,14 @@ private struct LocalPackageContainer: PackageContainer { } func getUnversionedDependencies(productFilter: ProductFilter) throws -> [PackageContainerConstraint] { - switch dependency?.state { + switch self.dependency?.state { case .none, .fileSystem, .edited: - return try manifest.dependencyConstraints(productFilter: productFilter) + return try self.manifest.dependencyConstraints(productFilter: productFilter) default: // Throw an error when the dependency is not unversioned to fail resolution. throw ResolverPrecomputationError.differentRequirement( - package: package, - state: dependency?.state, + package: self.package, + state: self.dependency?.state, requirement: .unversioned ) } diff --git a/Sources/Workspace/Workspace+Configuration.swift b/Sources/Workspace/Workspace+Configuration.swift index 103a0f13aeb..773d32301ab 100644 --- a/Sources/Workspace/Workspace+Configuration.swift +++ b/Sources/Workspace/Workspace+Configuration.swift @@ -89,7 +89,7 @@ extension Workspace { public var localMirrorsConfigurationFile: AbsolutePath { get throws { // backwards compatibility - if let customPath = ProcessEnv.vars["SWIFTPM_MIRROR_CONFIG"] { + if let customPath = ProcessEnv.block["SWIFTPM_MIRROR_CONFIG"] { return try AbsolutePath(validating: customPath) } return DefaultLocations.mirrorsConfigurationFile(at: self.localConfigurationDirectory) diff --git a/Sources/Workspace/Workspace+Dependencies.swift b/Sources/Workspace/Workspace+Dependencies.swift index 7c62fc49fcf..2e99b7d2107 100644 --- a/Sources/Workspace/Workspace+Dependencies.swift +++ b/Sources/Workspace/Workspace+Dependencies.swift @@ -37,6 +37,7 @@ import class PackageGraph.PinsStore import struct PackageGraph.PubGrubDependencyResolver import struct PackageGraph.Term import class PackageLoading.ManifestLoader +import struct PackageModel.ProvidedLibrary import enum PackageModel.PackageDependency import struct PackageModel.PackageIdentity import struct PackageModel.PackageReference @@ -84,7 +85,10 @@ extension Workspace { dependencyMapper: self.dependencyMapper, observabilityScope: observabilityScope ) - let currentManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) + let currentManifests = try self.loadDependencyManifests( + root: graphRoot, + observabilityScope: observabilityScope + ) // Abort if we're unable to load the pinsStore or have any diagnostics. guard let pinsStore = observabilityScope.trap({ try self.pinsStore.load() }) else { return nil } @@ -115,7 +119,10 @@ extension Workspace { } // Resolve the dependencies. - let resolver = try self.createResolver(pins: pins, observabilityScope: observabilityScope) + let resolver = try self.createResolver( + pins: pins, + observabilityScope: observabilityScope + ) self.activeResolver = resolver let updateResults = self.resolveDependencies( @@ -350,7 +357,10 @@ extension Workspace { !observabilityScope.errorsReported else { return try ( - self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope), + self.loadDependencyManifests( + root: graphRoot, + observabilityScope: observabilityScope + ), .notRequired ) } @@ -361,6 +371,20 @@ extension Workspace { // automatically manage the parallelism. let group = DispatchGroup() for pin in pinsStore.pins.values { + // Provided library doesn't have a container, we need to inject a special depedency. + if let library = pin.packageRef.matchingPrebuiltLibrary(in: self.providedLibraries), + case .version(library.version, _) = pin.state + { + try self.state.dependencies.add( + .providedLibrary( + packageRef: pin.packageRef, + library: library + ) + ) + try self.state.save() + continue + } + group.enter() let observabilityScope = observabilityScope.makeChildScope( description: "requesting package containers", @@ -408,6 +432,8 @@ extension Workspace { return !pin.state.equals(checkoutState) case .registryDownload(let version): return !pin.state.equals(version) + case .providedLibrary: + return false case .edited, .fileSystem, .custom: return true } @@ -496,7 +522,10 @@ extension Workspace { dependencyMapper: self.dependencyMapper, observabilityScope: observabilityScope ) - let currentManifests = try self.loadDependencyManifests(root: graphRoot, observabilityScope: observabilityScope) + let currentManifests = try self.loadDependencyManifests( + root: graphRoot, + observabilityScope: observabilityScope + ) guard !observabilityScope.errorsReported else { return currentManifests } @@ -564,7 +593,10 @@ extension Workspace { computedConstraints += try graphRoot.constraints() + constraints // Perform dependency resolution. - let resolver = try self.createResolver(pins: pinsStore.pins, observabilityScope: observabilityScope) + let resolver = try self.createResolver( + pins: pinsStore.pins, + observabilityScope: observabilityScope + ) self.activeResolver = resolver let result = self.resolveDependencies( @@ -655,7 +687,7 @@ extension Workspace { metadata: packageRef.diagnosticsMetadata ).trap { switch state { - case .added, .updated, .unchanged: + case .added, .updated, .unchanged, .usesLibrary: break case .removed: try self.remove(package: packageRef) @@ -684,12 +716,27 @@ extension Workspace { productFilter: state.products, observabilityScope: observabilityScope ) - case .removed, .unchanged: + case .removed, .unchanged, .usesLibrary: break } } } + // Handle provided libraries + for (packageRef, state) in packageStateChanges { + observabilityScope.makeChildScope( + description: "adding provided libraries", + metadata: packageRef.diagnosticsMetadata + ).trap { + if case .usesLibrary(let library) = state { + try self.state.dependencies.add( + .providedLibrary(packageRef: packageRef, library: library) + ) + try self.state.save() + } + } + } + // Inform the delegate if nothing was updated. if packageStateChanges.filter({ $0.1 == .unchanged }).count == packageStateChanges.count { delegate?.dependenciesUpToDate() @@ -817,6 +864,7 @@ extension Workspace { let resolver = PubGrubDependencyResolver( provider: precomputationProvider, pins: pinsStore.pins, + availableLibraries: self.providedLibraries, observabilityScope: observabilityScope ) let result = resolver.solve(constraints: computedConstraints) @@ -929,6 +977,9 @@ extension Workspace { /// The package is updated. case updated(State) + /// The package is replaced with a prebuilt library + case usesLibrary(ProvidedLibrary) + public var description: String { switch self { case .added(let requirement): @@ -939,15 +990,17 @@ extension Workspace { return "unchanged" case .updated(let requirement): return "updated(\(requirement))" + case .usesLibrary(let library): + return "usesLibrary(\(library.metadata.productName))" } } public var isAddedOrUpdated: Bool { switch self { case .added, .updated: - return true - case .unchanged, .removed: - return false + true + case .unchanged, .removed, .usesLibrary: + false } } } @@ -996,6 +1049,8 @@ extension Workspace { packageStateChanges[binding.package.identity] = (binding.package, .updated(newState)) case .registryDownload: throw InternalError("Unexpected unversioned binding for downloaded dependency") + case .providedLibrary: + throw InternalError("Unexpected unversioned binding for library dependency") case .custom: throw InternalError("Unexpected unversioned binding for custom dependency") } @@ -1016,7 +1071,9 @@ extension Workspace { completion: $0 ) }) as? SourceControlPackageContainer else { - throw InternalError("invalid container for \(binding.package) expected a SourceControlPackageContainer") + throw InternalError( + "invalid container for \(binding.package) expected a SourceControlPackageContainer" + ) } var revision = try container.getRevision(forIdentifier: identifier) let branch = branch ?? (identifier == revision.identifier ? nil : identifier) @@ -1061,15 +1118,20 @@ extension Workspace { packageStateChanges[binding.package.identity] = (binding.package, .added(newState)) } - case .version(let version): - let stateChange: PackageStateChange - switch currentDependency?.state { - case .sourceControlCheckout(.version(version, _)), .registryDownload(version), .custom(version, _): - stateChange = .unchanged - case .edited, .fileSystem, .sourceControlCheckout, .registryDownload, .custom: - stateChange = .updated(.init(requirement: .version(version), products: binding.products)) + case .version(let version, let library): + let stateChange: PackageStateChange = switch currentDependency?.state { + case .sourceControlCheckout(.version(version, _)), + .registryDownload(version), + .providedLibrary(_, version: version), + .custom(version, _): + library.flatMap { .usesLibrary($0) } ?? .unchanged + case .edited, .fileSystem, .sourceControlCheckout, .registryDownload, .providedLibrary, .custom: + .updated(.init(requirement: .version(version), products: binding.products)) case nil: - stateChange = .added(.init(requirement: .version(version), products: binding.products)) + library.flatMap { .usesLibrary($0) } ?? .added(.init( + requirement: .version(version), + products: binding.products + )) } packageStateChanges[binding.package.identity] = (binding.package, stateChange) } @@ -1103,6 +1165,7 @@ extension Workspace { return PubGrubDependencyResolver( provider: packageContainerProvider, pins: pins, + availableLibraries: self.providedLibraries, skipDependenciesUpdates: self.configuration.skipDependenciesUpdates, prefetchBasedOnResolvedFile: self.configuration.prefetchBasedOnResolvedFile, observabilityScope: observabilityScope, @@ -1118,6 +1181,7 @@ extension Workspace { ) -> [DependencyResolverBinding] { os_signpost(.begin, name: SignpostName.pubgrub) let result = resolver.solve(constraints: constraints) + os_signpost(.end, name: SignpostName.pubgrub) // Take an action based on the result. diff --git a/Sources/Workspace/Workspace+Editing.swift b/Sources/Workspace/Workspace+Editing.swift index 1b4d1442617..cf429ad2eaa 100644 --- a/Sources/Workspace/Workspace+Editing.swift +++ b/Sources/Workspace/Workspace+Editing.swift @@ -15,6 +15,7 @@ import class Basics.ObservabilityScope import struct Basics.RelativePath import func Basics.temp_await import struct PackageGraph.PackageGraphRootInput +import struct PackageModel.ProvidedLibrary import struct SourceControl.Revision import class TSCBasic.InMemoryFileSystem @@ -51,6 +52,9 @@ extension Workspace { case .registryDownload: observabilityScope.emit(error: "registry dependency '\(dependency.packageRef.identity)' can't be edited") return + case .providedLibrary: + observabilityScope.emit(error: "library dependency '\(dependency.packageRef.identity)' can't be edited") + return case .custom: observabilityScope.emit(error: "custom dependency '\(dependency.packageRef.identity)' can't be edited") return diff --git a/Sources/Workspace/Workspace+Manifests.swift b/Sources/Workspace/Workspace+Manifests.swift index 35eb95c863f..8b48dea8df2 100644 --- a/Sources/Workspace/Workspace+Manifests.swift +++ b/Sources/Workspace/Workspace+Manifests.swift @@ -16,6 +16,7 @@ import struct Basics.InternalError import class Basics.ObservabilityScope import struct Basics.SwiftVersion import func Basics.temp_await +import func Basics.depthFirstSearch import class Basics.ThreadSafeKeyValueStore import class Dispatch.DispatchGroup import struct Dispatch.DispatchTime @@ -28,6 +29,7 @@ import struct PackageGraph.PackageGraphRoot import class PackageLoading.ManifestLoader import struct PackageLoading.ManifestValidator import struct PackageLoading.ToolsVersionParser +import struct PackageModel.ProvidedLibrary import class PackageModel.Manifest import struct PackageModel.PackageIdentity import struct PackageModel.PackageReference @@ -135,10 +137,17 @@ extension Workspace { let dependency = dependency.dependency switch dependency.state { case .sourceControlCheckout(let checkout): - if checkout.isBranchOrRevisionBased { - result.insert(dependency.packageRef) + let packageRef = dependency.packageRef + + if checkout.isBranchOrRevisionBased + // FIXME: Remove this once we have a general mechanism + // for passing "safe" flags. + || packageRef.identity == .plain("swift-corelibs-foundation") + { + result.insert(packageRef) } - case .registryDownload, .edited, .custom: + + case .registryDownload, .edited, .providedLibrary, .custom: continue case .fileSystem: result.insert(dependency.packageRef) @@ -175,6 +184,21 @@ extension Workspace { } ) + let availableIdentities: Set = try Set(manifestsMap.map { + // FIXME: adding this guard to ensure refactoring is correct 9/21 + // we only care about remoteSourceControl for this validation. it would otherwise trigger for + // a dependency is put into edit mode, which we want to deprecate anyways + if case .remoteSourceControl = $0.1.packageKind { + let effectiveURL = workspace.mirrors.effective(for: $0.1.packageLocation) + guard effectiveURL == $0.1.packageKind.locationString else { + throw InternalError( + "effective url for \($0.1.packageLocation) is \(effectiveURL), different from expected \($0.1.packageKind.locationString)" + ) + } + } + return PackageReference(identity: $0.key, kind: $0.1.packageKind) + }) + var inputIdentities: OrderedCollections.OrderedSet = [] let inputNodes: [GraphLoadingNode] = root.packages.map { identity, package in inputIdentities.append(package.reference) @@ -252,20 +276,6 @@ extension Workspace { } requiredIdentities = inputIdentities.union(requiredIdentities) - let availableIdentities: Set = try Set(manifestsMap.map { - // FIXME: adding this guard to ensure refactoring is correct 9/21 - // we only care about remoteSourceControl for this validation. it would otherwise trigger for - // a dependency is put into edit mode, which we want to deprecate anyways - if case .remoteSourceControl = $0.1.packageKind { - let effectiveURL = workspace.mirrors.effective(for: $0.1.packageLocation) - guard effectiveURL == $0.1.packageKind.locationString else { - throw InternalError( - "effective url for \($0.1.packageLocation) is \(effectiveURL), different from expected \($0.1.packageKind.locationString)" - ) - } - } - return PackageReference(identity: $0.key, kind: $0.1.packageKind) - }) // We should never have loaded a manifest we don't need. assert( availableIdentities.isSubset(of: requiredIdentities), @@ -312,7 +322,7 @@ extension Workspace { products: productFilter ) allConstraints.append(constraint) - case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: + case .sourceControlCheckout, .registryDownload, .fileSystem, .providedLibrary, .custom: break } allConstraints += try externalManifest.dependencyConstraints(productFilter: productFilter) @@ -327,7 +337,7 @@ extension Workspace { for (_, managedDependency, productFilter, _) in dependencies { switch managedDependency.state { - case .sourceControlCheckout, .registryDownload, .fileSystem, .custom: continue + case .sourceControlCheckout, .registryDownload, .fileSystem, .providedLibrary, .custom: continue case .edited: break } // FIXME: We shouldn't need to construct a new package reference object here. @@ -363,6 +373,8 @@ extension Workspace { return path ?? self.location.editSubdirectory(for: dependency) case .fileSystem(let path): return path + case .providedLibrary(let path, _): + return path case .custom(_, let path): return path } @@ -446,7 +458,9 @@ extension Workspace { } // Validates that all the managed dependencies are still present in the file system. - self.fixManagedDependencies(observabilityScope: observabilityScope) + self.fixManagedDependencies( + observabilityScope: observabilityScope + ) guard !observabilityScope.errorsReported else { // return partial results return DependencyManifests( @@ -481,12 +495,7 @@ extension Workspace { // Continue to load the rest of the manifest for this graph // Creates a map of loaded manifests. We do this to avoid reloading the shared nodes. var loadedManifests = firstLevelManifests - // Compute the transitive closure of available dependencies. - let topologicalSortInput = topLevelManifests.map { identity, manifest in KeyedPair( - manifest, - key: Key(identity: identity, productFilter: .everything) - ) } - let topologicalSortSuccessors: (KeyedPair) throws -> [KeyedPair] = { pair in + let successorManifests: (KeyedPair) throws -> [KeyedPair] = { pair in // optimization: preload manifest we know about in parallel let dependenciesRequired = pair.item.dependenciesRequired(for: pair.key.productFilter) let dependenciesToLoad = dependenciesRequired.map(\.packageRef) @@ -514,36 +523,26 @@ extension Workspace { } } - // Look for any cycle in the dependencies. - if let cycle = try findCycle(topologicalSortInput, successors: topologicalSortSuccessors) { - observabilityScope.emit( - error: "cyclic dependency declaration found: " + - (cycle.path + cycle.cycle).map(\.key.identity.description).joined(separator: " -> ") + - " -> " + cycle.cycle[0].key.identity.description - ) - // return partial results - return DependencyManifests( - root: root, - dependencies: [], - workspace: self, - observabilityScope: observabilityScope - ) - } - let allManifestsWithPossibleDuplicates = try topologicalSort( - topologicalSortInput, - successors: topologicalSortSuccessors - ) - - // merge the productFilter of the same package (by identity) - var deduplication = [PackageIdentity: Int]() var allManifests = [(identity: PackageIdentity, manifest: Manifest, productFilter: ProductFilter)]() - for pair in allManifestsWithPossibleDuplicates { - if let index = deduplication[pair.key.identity] { - let productFilter = allManifests[index].productFilter.merge(pair.key.productFilter) - allManifests[index] = (pair.key.identity, pair.item, productFilter) - } else { + do { + let manifestGraphRoots = topLevelManifests.map { identity, manifest in + KeyedPair( + manifest, + key: Key(identity: identity, productFilter: .everything) + ) + } + + var deduplication = [PackageIdentity: Int]() + try depthFirstSearch( + manifestGraphRoots, + successors: successorManifests + ) { pair in deduplication[pair.key.identity] = allManifests.count allManifests.append((pair.key.identity, pair.item, pair.key.productFilter)) + } onDuplicate: { old, new in + let index = deduplication[old.key.identity]! + let productFilter = allManifests[index].productFilter.merge(new.key.productFilter) + allManifests[index] = (new.key.identity, new.item, productFilter) } } @@ -646,6 +645,14 @@ extension Workspace { case .registryDownload(let downloadedVersion): packageKind = managedDependency.packageRef.kind packageVersion = downloadedVersion + case .providedLibrary(let path, let version): + let manifest: Manifest? = try? .forProvidedLibrary( + fileSystem: fileSystem, + package: managedDependency.packageRef, + libraryPath: path, + version: version + ) + return completion(manifest) case .custom(let availableVersion, _): packageKind = managedDependency.packageRef.kind packageVersion = availableVersion @@ -777,7 +784,9 @@ extension Workspace { /// If some checkout dependency is removed form the file system, clone it again. /// If some edited dependency is removed from the file system, mark it as unedited and /// fallback on the original checkout. - private func fixManagedDependencies(observabilityScope: ObservabilityScope) { + private func fixManagedDependencies( + observabilityScope: ObservabilityScope + ) { // Reset managed dependencies if the state file was removed during the lifetime of the Workspace object. if !self.state.dependencies.isEmpty && !self.state.stateFileExists() { try? self.state.reset() @@ -846,11 +855,19 @@ extension Workspace { // Note: We don't resolve the dependencies when unediting // here because we expect this method to be called as part // of some other resolve operation (i.e. resolve, update, etc). - try self.unedit(dependency: dependency, forceRemove: true, observabilityScope: observabilityScope) + try self.unedit( + dependency: dependency, + forceRemove: true, + observabilityScope: observabilityScope + ) observabilityScope .emit(.editedDependencyMissing(packageName: dependency.packageRef.identity.description)) + case .providedLibrary(_, version: _): + // TODO: If the dependency is not available we can turn it into a source control dependency + break + case .fileSystem: self.state.dependencies.remove(dependency.packageRef.identity) try self.state.save() diff --git a/Sources/Workspace/Workspace+Pinning.swift b/Sources/Workspace/Workspace+Pinning.swift index b51e69fb04f..6a652a937ad 100644 --- a/Sources/Workspace/Workspace+Pinning.swift +++ b/Sources/Workspace/Workspace+Pinning.swift @@ -145,7 +145,7 @@ extension PinsStore.Pin { packageRef: dependency.packageRef, state: .version(version, revision: .none) ) - case .edited, .fileSystem, .custom: + case .edited, .fileSystem, .providedLibrary, .custom: // NOOP return nil } diff --git a/Sources/Workspace/Workspace+Signing.swift b/Sources/Workspace/Workspace+Signing.swift index 1da24b02d0e..1db01de2dd5 100644 --- a/Sources/Workspace/Workspace+Signing.swift +++ b/Sources/Workspace/Workspace+Signing.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// import enum PackageFingerprint.FingerprintCheckingMode -import struct PackageGraph.PackageGraph +import struct PackageGraph.ModulesGraph import struct PackageModel.PackageIdentity import struct PackageModel.RegistryReleaseMetadata import enum PackageSigning.SigningEntityCheckingMode @@ -42,11 +42,11 @@ extension SigningEntityCheckingMode { extension Workspace { func validateSignatures( - packageGraph: PackageGraph, + packageGraph: ModulesGraph, expectedSigningEntities: [PackageIdentity: RegistryReleaseMetadata.SigningEntity] ) throws { try expectedSigningEntities.forEach { identity, expectedSigningEntity in - if let package = packageGraph.packages.first(where: { $0.identity == identity }) { + if let package = packageGraph.package(for: identity) { guard let actualSigningEntity = package.registryMetadata?.signature?.signedBy else { throw SigningError.unsigned(package: identity, expected: expectedSigningEntity) } @@ -68,7 +68,7 @@ extension Workspace { expected: expectedSigningEntity ) } - guard let package = packageGraph.packages.first(where: { $0.identity == mirroredIdentity }) else { + guard let package = packageGraph.package(for: mirroredIdentity) else { // Unsure if this case is reachable in practice. throw SigningError.expectedIdentityNotFound(package: identity) } diff --git a/Sources/Workspace/Workspace+State.swift b/Sources/Workspace/Workspace+State.swift index ec4ef688a55..4c4e971b16f 100644 --- a/Sources/Workspace/Workspace+State.swift +++ b/Sources/Workspace/Workspace+State.swift @@ -76,7 +76,6 @@ public final class WorkspaceState { self.storage.fileExists() } - /// Returns true if the state file exists on the filesystem. func reload() throws { let storedState = try self.storage.load() self.dependencies = storedState.dependencies @@ -257,6 +256,15 @@ extension WorkspaceStateStorage { let version = try container.decode(String.self, forKey: .version) return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) + case "providedLibrary": + let path = try container.decode(AbsolutePath.self, forKey: .path) + let version = try container.decode(String.self, forKey: .version) + return try self.init( + underlying: .providedLibrary( + at: path, + version: TSCUtility.Version(versionString: version) + ) + ) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( @@ -287,6 +295,10 @@ extension WorkspaceStateStorage { case .registryDownload(let version): try container.encode("registryDownload", forKey: .name) try container.encode(version, forKey: .version) + case .providedLibrary(let path, let version): + try container.encode("providedLibrary", forKey: .name) + try container.encode(path, forKey: .path) + try container.encode(version, forKey: .version) case .edited(_, let path): try container.encode("edited", forKey: .name) try container.encode(path, forKey: .path) @@ -613,6 +625,15 @@ extension WorkspaceStateStorage { let version = try container.decode(String.self, forKey: .version) return try self .init(underlying: .registryDownload(version: TSCUtility.Version(versionString: version))) + case "providedLibrary": + let path = try container.decode(AbsolutePath.self, forKey: .path) + let version = try container.decode(String.self, forKey: .version) + return try self.init( + underlying: .providedLibrary( + at: path, + version: TSCUtility.Version(versionString: version) + ) + ) case "edited": let path = try container.decode(AbsolutePath?.self, forKey: .path) return try self.init(underlying: .edited( @@ -643,6 +664,10 @@ extension WorkspaceStateStorage { case .registryDownload(let version): try container.encode("registryDownload", forKey: .name) try container.encode(version, forKey: .version) + case .providedLibrary(let path, let version): + try container.encode("providedLibrary", forKey: .name) + try container.encode(path, forKey: .path) + try container.encode(version, forKey: .version) case .edited(_, let path): try container.encode("edited", forKey: .name) try container.encode(path, forKey: .path) diff --git a/Sources/Workspace/Workspace.swift b/Sources/Workspace/Workspace.swift index 92250c85d15..816fb2dcbe7 100644 --- a/Sources/Workspace/Workspace.swift +++ b/Sources/Workspace/Workspace.swift @@ -96,7 +96,7 @@ public class Workspace { public let pinsStore: LoadableResult /// The file system on which the workspace will operate. - let fileSystem: any FileSystem + package let fileSystem: any FileSystem /// The host toolchain to use. private let hostToolchain: UserToolchain @@ -571,6 +571,11 @@ public class Workspace { initializationWarningHandler: initializationWarningHandler ) } + + var providedLibraries: [ProvidedLibrary] { + // Note: Eventually, we should get these from the individual SDKs, but the first step is providing the metadata centrally in the toolchain. + self.hostToolchain.providedLibraries + } } // MARK: - Public API @@ -700,6 +705,8 @@ extension Workspace { defaultRequirement = checkoutState.requirement case .registryDownload(let version), .custom(let version, _): defaultRequirement = .versionSet(.exact(version)) + case .providedLibrary(_, version: let version): + defaultRequirement = .versionSet(.exact(version)) case .fileSystem: throw StringError("local dependency '\(dependency.packageRef.identity)' can't be resolved") case .edited: @@ -868,7 +875,7 @@ extension Workspace { testEntryPointPath: AbsolutePath? = nil, expectedSigningEntities: [PackageIdentity: RegistryReleaseMetadata.SigningEntity] = [:], observabilityScope: ObservabilityScope - ) throws -> PackageGraph { + ) throws -> ModulesGraph { let start = DispatchTime.now() self.delegate?.willLoadGraph() defer { @@ -899,7 +906,7 @@ extension Workspace { } // Load the graph. - let packageGraph = try PackageGraph.load( + let packageGraph = try ModulesGraph.load( root: manifests.root, identityResolver: self.identityResolver, additionalFileRules: self.configuration.additionalFileRules, @@ -928,7 +935,7 @@ extension Workspace { rootPath: AbsolutePath, explicitProduct: String? = nil, observabilityScope: ObservabilityScope - ) throws -> PackageGraph { + ) throws -> ModulesGraph { try self.loadPackageGraph( rootInput: PackageGraphRootInput(packages: [rootPath]), explicitProduct: explicitProduct, @@ -1095,7 +1102,7 @@ extension Workspace { } public func loadPluginImports( - packageGraph: PackageGraph + packageGraph: ModulesGraph ) async throws -> [PackageIdentity: [String: [String]]] { let pluginTargets = packageGraph.allTargets.filter { $0.type == .plugin } let scanner = SwiftcImportScanner( @@ -1126,7 +1133,7 @@ extension Workspace { public func loadPackage( with identity: PackageIdentity, - packageGraph: PackageGraph, + packageGraph: ModulesGraph, observabilityScope: ObservabilityScope ) async throws -> Package { try await safe_async { @@ -1139,11 +1146,11 @@ extension Workspace { @available(*, noasync, message: "Use the async alternative") public func loadPackage( with identity: PackageIdentity, - packageGraph: PackageGraph, + packageGraph: ModulesGraph, observabilityScope: ObservabilityScope, completion: @escaping (Result) -> Void ) { - guard let previousPackage = packageGraph.packages.first(where: { $0.identity == identity }) else { + guard let previousPackage = packageGraph.package(for: identity) else { return completion(.failure(StringError("could not find package with identity \(identity)"))) } @@ -1176,7 +1183,7 @@ extension Workspace { /// Returns `true` if the file at the given path might influence build settings for a `swiftc` or `clang` invocation /// generated by SwiftPM. - public func fileAffectsSwiftOrClangBuildSettings(filePath: AbsolutePath, packageGraph: PackageGraph) -> Bool { + public func fileAffectsSwiftOrClangBuildSettings(filePath: AbsolutePath, packageGraph: ModulesGraph) -> Bool { // TODO: Implement a more sophisticated check that also verifies if the file is in the sources directories of the passed in `packageGraph`. FileRuleDescription.builtinRules.contains { fileRuleDescription in fileRuleDescription.match(path: filePath, toolsVersion: self.currentToolsVersion) @@ -1335,6 +1342,8 @@ extension Workspace { } case .registryDownload(let version)?, .custom(let version, _): result.append("resolved to '\(version)'") + case .providedLibrary(_, version: let version): + result.append("resolved to '\(version)'") case .edited?: result.append("edited") case .fileSystem?: @@ -1456,7 +1465,7 @@ private func warnToStderr(_ message: String) { } // used for manifest validation -#if swift(<5.11) +#if swift(<6.0) extension RepositoryManager: ManifestSourceControlValidator {} #else extension RepositoryManager: @retroactive ManifestSourceControlValidator {} diff --git a/Sources/XCBuildSupport/CMakeLists.txt b/Sources/XCBuildSupport/CMakeLists.txt index 178e0bc1ba7..b478bdb56bd 100644 --- a/Sources/XCBuildSupport/CMakeLists.txt +++ b/Sources/XCBuildSupport/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(XCBuildSupport STATIC XcodeBuildSystem.swift) target_link_libraries(XCBuildSupport PUBLIC Build + DriverSupport TSCBasic TSCUtility PackageGraph diff --git a/Sources/XCBuildSupport/CODEOWNERS b/Sources/XCBuildSupport/CODEOWNERS deleted file mode 100644 index cb9a8e18841..00000000000 --- a/Sources/XCBuildSupport/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -@jakepetroules diff --git a/Sources/XCBuildSupport/PIF.swift b/Sources/XCBuildSupport/PIF.swift index 2029bddca49..37953654c70 100644 --- a/Sources/XCBuildSupport/PIF.swift +++ b/Sources/XCBuildSupport/PIF.swift @@ -941,8 +941,6 @@ public enum PIF { case INSTALL_PATH case SUPPORTS_MACCATALYST case SWIFT_SERIALIZE_DEBUGGING_OPTIONS - case SWIFT_FORCE_STATIC_LINK_STDLIB - case SWIFT_FORCE_DYNAMIC_LINK_STDLIB case SWIFT_INSTALL_OBJC_HEADER case SWIFT_OBJC_INTERFACE_HEADER_NAME case SWIFT_OBJC_INTERFACE_HEADER_DIR diff --git a/Sources/XCBuildSupport/PIFBuilder.swift b/Sources/XCBuildSupport/PIFBuilder.swift index 6907fb9b3bb..be4e78b6902 100644 --- a/Sources/XCBuildSupport/PIFBuilder.swift +++ b/Sources/XCBuildSupport/PIFBuilder.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -10,18 +10,21 @@ // //===----------------------------------------------------------------------===// -import Foundation import Basics -import PackageModel -import PackageLoading +import Foundation import PackageGraph +import PackageLoading +import PackageModel + import SPMBuildCore -import func TSCBasic.topologicalSort import func TSCBasic.memoize +import func TSCBasic.topologicalSort /// The parameters required by `PIFBuilder`. struct PIFBuilderParameters { + /// Whether the toolchain supports `-package-name` option. + let isPackageAccessModifierSupported: Bool /// Whether or not build for testability is enabled. let enableTestability: Bool @@ -37,11 +40,13 @@ struct PIFBuilderParameters { /// The toolchain's SDK root path. let sdkRootPath: AbsolutePath? + + /// The Swift language versions supported by the XCBuild being used for the buid. + let supportedSwiftVersions: [SwiftLanguageVersion] } /// PIF object builder for a package graph. public final class PIFBuilder { - /// Name of the PIF target aggregating all targets (excluding tests). public static let allExcludingTestsTargetName = "AllExcludingTests" @@ -49,7 +54,7 @@ public final class PIFBuilder { public static let allIncludingTestsTargetName = "AllIncludingTests" /// The package graph to build from. - let graph: PackageGraph + let graph: ModulesGraph /// The parameters used to configure the PIF. let parameters: PIFBuilderParameters @@ -69,7 +74,7 @@ public final class PIFBuilder { /// - fileSystem: The file system to read from. /// - observabilityScope: The ObservabilityScope to emit diagnostics to. init( - graph: PackageGraph, + graph: ModulesGraph, parameters: PIFBuilderParameters, fileSystem: FileSystem, observabilityScope: ObservabilityScope @@ -105,14 +110,15 @@ public final class PIFBuilder { /// Constructs a `PIF.TopLevelObject` representing the package graph. public func construct() throws -> PIF.TopLevelObject { - try memoize(to: &pif) { - let rootPackage = self.graph.rootPackages[graph.rootPackages.startIndex] + try memoize(to: &self.pif) { + let rootPackage = self.graph.rootPackages[self.graph.rootPackages.startIndex] - let sortedPackages = graph.packages.sorted { $0.manifest.displayName < $1.manifest.displayName } // TODO: use identity instead? + let sortedPackages = self.graph.packages + .sorted { $0.manifest.displayName < $1.manifest.displayName } // TODO: use identity instead? var projects: [PIFProjectBuilder] = try sortedPackages.map { package in try PackagePIFProjectBuilder( package: package, - parameters: parameters, + parameters: self.parameters, fileSystem: self.fileSystem, observabilityScope: self.observabilityScope ) @@ -120,11 +126,11 @@ public final class PIFBuilder { projects.append(AggregatePIFProjectBuilder(projects: projects)) - let workspace = PIF.Workspace( + let workspace = try PIF.Workspace( guid: "Workspace:\(rootPackage.path.pathString)", - name: rootPackage.manifest.displayName, // TODO: use identity instead? + name: rootPackage.manifest.displayName, // TODO: use identity instead? path: rootPackage.path, - projects: try projects.map { try $0.construct() } + projects: projects.map { try $0.construct() } ) return PIF.TopLevelObject(workspace: workspace) @@ -132,9 +138,15 @@ public final class PIFBuilder { } // Convenience method for generating PIF. - public static func generatePIF(buildParameters: BuildParameters, packageGraph: PackageGraph, fileSystem: FileSystem, observabilityScope: ObservabilityScope, preservePIFModelStructure: Bool) throws -> String { - let parameters = PIFBuilderParameters(buildParameters) - let builder = Self.init( + public static func generatePIF( + buildParameters: BuildParameters, + packageGraph: ModulesGraph, + fileSystem: FileSystem, + observabilityScope: ObservabilityScope, + preservePIFModelStructure: Bool + ) throws -> String { + let parameters = PIFBuilderParameters(buildParameters, supportedSwiftVersions: []) + let builder = Self( graph: packageGraph, parameters: parameters, fileSystem: fileSystem, @@ -161,9 +173,9 @@ class PIFProjectBuilder { var developmentRegion: String fileprivate init() { - groupTree = PIFGroupBuilder(path: "") - targets = [] - buildConfigurations = [] + self.groupTree = PIFGroupBuilder(path: "") + self.targets = [] + self.buildConfigurations = [] } /// Creates and adds a new empty build configuration, i.e. one that does not initially have any build settings. @@ -172,10 +184,15 @@ class PIFProjectBuilder { func addBuildConfiguration( name: String, settings: PIF.BuildSettings = PIF.BuildSettings(), - impartedBuildProperties: PIF.ImpartedBuildProperties = PIF.ImpartedBuildProperties(settings: PIF.BuildSettings()) + impartedBuildProperties: PIF.ImpartedBuildProperties = PIF + .ImpartedBuildProperties(settings: PIF.BuildSettings()) ) -> PIFBuildConfigurationBuilder { - let builder = PIFBuildConfigurationBuilder(name: name, settings: settings, impartedBuildProperties: impartedBuildProperties) - buildConfigurations.append(builder) + let builder = PIFBuildConfigurationBuilder( + name: name, + settings: settings, + impartedBuildProperties: impartedBuildProperties + ) + self.buildConfigurations.append(builder) return builder } @@ -191,34 +208,34 @@ class PIFProjectBuilder { productName: String ) -> PIFTargetBuilder { let target = PIFTargetBuilder(guid: guid, name: name, productType: productType, productName: productName) - targets.append(target) + self.targets.append(target) return target } @discardableResult func addAggregateTarget(guid: PIF.GUID, name: String) -> PIFAggregateTargetBuilder { let target = PIFAggregateTargetBuilder(guid: guid, name: name) - targets.append(target) + self.targets.append(target) return target } func construct() throws -> PIF.Project { let buildConfigurations = self.buildConfigurations.map { builder -> PIF.BuildConfiguration in - builder.guid = "\(guid)::BUILDCONFIG_\(builder.name)" + builder.guid = "\(self.guid)::BUILDCONFIG_\(builder.name)" return builder.construct() } // Construct group tree before targets to make sure file references have GUIDs. - groupTree.guid = "\(guid)::MAINGROUP" + groupTree.guid = "\(self.guid)::MAINGROUP" let groupTree = self.groupTree.construct() as! PIF.Group let targets = try self.targets.map { try $0.construct() } return PIF.Project( - guid: guid, - name: name, - path: path, - projectDirectory: projectDirectory, - developmentRegion: developmentRegion, + guid: self.guid, + name: self.name, + path: self.path, + projectDirectory: self.projectDirectory, + developmentRegion: self.developmentRegion, buildConfigurations: buildConfigurations, targets: targets, groupTree: groupTree @@ -232,9 +249,9 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { private let fileSystem: FileSystem private let observabilityScope: ObservabilityScope private var binaryGroup: PIFGroupBuilder! - private let executableTargetProductMap: [ResolvedTarget.ID: ResolvedProduct] + private let executableTargetProductMap: [ResolvedModule.ID: ResolvedProduct] - var isRootPackage: Bool { package.manifest.packageKind.isRoot } + var isRootPackage: Bool { self.package.manifest.packageKind.isRoot } init( package: ResolvedPackage, @@ -258,12 +275,12 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { super.init() - guid = package.pifProjectGUID - name = package.manifest.displayName // TODO: use identity instead? - path = package.path - projectDirectory = package.path - developmentRegion = package.manifest.defaultLocalization ?? "en" - binaryGroup = groupTree.addGroup(path: "/", sourceTree: .absolute, name: "Binaries") + self.guid = package.pifProjectGUID + self.name = package.manifest.displayName // TODO: use identity instead? + self.path = package.path + self.projectDirectory = package.path + self.developmentRegion = package.manifest.defaultLocalization ?? "en" + self.binaryGroup = groupTree.addGroup(path: "/", sourceTree: .absolute, name: "Binaries") // Configure the project-wide build settings. First we set those that are in common between the "Debug" and // "Release" configurations, and then we set those that are different. @@ -340,30 +357,39 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { addBuildConfiguration(name: "Release", settings: releaseSettings) for product in package.products.sorted(by: { $0.name < $1.name }) { - try addTarget(for: product) + let productScope = observabilityScope.makeChildScope( + description: "Adding \(product.name) product", + metadata: package.underlying.diagnosticsMetadata + ) + + productScope.trap { try self.addTarget(for: product) } } for target in package.targets.sorted(by: { $0.name < $1.name }) { - try self.addTarget(for: target) + let targetScope = observabilityScope.makeChildScope( + description: "Adding \(target.name) module", + metadata: package.underlying.diagnosticsMetadata + ) + targetScope.trap { try self.addTarget(for: target) } } - if binaryGroup.children.isEmpty { - groupTree.removeChild(binaryGroup) + if self.binaryGroup.children.isEmpty { + groupTree.removeChild(self.binaryGroup) } } private func addTarget(for product: ResolvedProduct) throws { switch product.type { case .executable, .snippet, .test: - try addMainModuleTarget(for: product) + try self.addMainModuleTarget(for: product) case .library: - addLibraryTarget(for: product) + self.addLibraryTarget(for: product) case .plugin, .macro: return } } - private func addTarget(for target: ResolvedTarget) throws { + private func addTarget(for target: ResolvedModule) throws { switch target.type { case .library: try self.addLibraryTarget(for: target) @@ -382,22 +408,25 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { case .macro: // Macros are not supported when using XCBuild, similar to package plugins. return + case .providedLibrary: + // Provided libraries don't need to be built. + return } } private func targetName(for product: ResolvedProduct) -> String { - return Self.targetName(for: product.name) + Self.targetName(for: product.name) } static func targetName(for productName: String) -> String { - return "\(productName)_\(String(productName.hash, radix: 16, uppercase: true))_PackageProduct" + "\(productName)_\(String(productName.hash, radix: 16, uppercase: true))_PackageProduct" } private func addMainModuleTarget(for product: ResolvedProduct) throws { let productType: PIF.Target.ProductType = product.type == .executable ? .executable : .unitTest - let pifTarget = addTarget( + let pifTarget = self.addTarget( guid: product.pifTargetGUID, - name: targetName(for: product), + name: self.targetName(for: product), productType: productType, productName: product.name ) @@ -405,11 +434,11 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { // We'll be infusing the product's main module target into the one for the product itself. let mainTarget = product.mainTarget - addSources(mainTarget.sources, to: pifTarget) + self.addSources(mainTarget.sources, to: pifTarget) let dependencies = try! topologicalSort(mainTarget.dependencies) { $0.packageDependencies }.sorted() for dependency in dependencies { - addDependency(to: dependency, in: pifTarget, linkProduct: true) + self.addDependency(to: dependency, in: pifTarget, linkProduct: true) } // Configure the target-wide build settings. The details depend on the kind of product we're building, but are @@ -423,11 +452,12 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.EXECUTABLE_NAME] = product.name settings[.CLANG_ENABLE_MODULES] = "YES" settings[.DEFINES_MODULE] = "YES" - settings[.SWIFT_FORCE_STATIC_LINK_STDLIB] = "NO" - settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB] = "YES" if product.type == .executable || product.type == .test { - settings[.LIBRARY_SEARCH_PATHS] = ["$(inherited)", "\(parameters.toolchainLibDir.pathString)/swift/macosx"] + settings[.LIBRARY_SEARCH_PATHS] = [ + "$(inherited)", + "\(self.parameters.toolchainLibDir.pathString)/swift/macosx", + ] } // Tests can have a custom deployment target based on the minimum supported by XCTest. @@ -445,7 +475,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.SUPPORTED_PLATFORMS] = ["macosx", "linux"] // Setup install path for executables if it's in root of a pure Swift package. - if isRootPackage { + if self.isRootPackage { settings[.SKIP_INSTALL] = "NO" settings[.INSTALL_PATH] = "/usr/local/bin" settings[.LD_RUNPATH_SEARCH_PATHS, default: ["$(inherited)"]].append("@executable_path/../lib") @@ -464,7 +494,8 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.GCC_C_LANGUAGE_STANDARD] = clangTarget.cLanguageStandard settings[.CLANG_CXX_LANGUAGE_STANDARD] = clangTarget.cxxLanguageStandard } else if let swiftTarget = mainTarget.underlying as? SwiftTarget { - settings[.SWIFT_VERSION] = swiftTarget.swiftVersion.description + try settings.addSwiftVersionSettings(target: swiftTarget, parameters: self.parameters) + settings.addCommonSwiftSettings(package: self.package, target: mainTarget, parameters: self.parameters) } if let resourceBundle = addResourceBundle(for: mainTarget, in: pifTarget) { @@ -478,7 +509,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { var releaseSettings = settings var impartedSettings = PIF.BuildSettings() - try addManifestBuildSettings( + try self.addManifestBuildSettings( from: mainTarget.underlying, debugSettings: &debugSettings, releaseSettings: &releaseSettings, @@ -486,8 +517,16 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { ) let impartedBuildProperties = PIF.ImpartedBuildProperties(settings: impartedSettings) - pifTarget.addBuildConfiguration(name: "Debug", settings: debugSettings, impartedBuildProperties: impartedBuildProperties) - pifTarget.addBuildConfiguration(name: "Release", settings: releaseSettings, impartedBuildProperties: impartedBuildProperties) + pifTarget.addBuildConfiguration( + name: "Debug", + settings: debugSettings, + impartedBuildProperties: impartedBuildProperties + ) + pifTarget.addBuildConfiguration( + name: "Release", + settings: releaseSettings, + impartedBuildProperties: impartedBuildProperties + ) } private func addLibraryTarget(for product: ResolvedProduct) { @@ -495,7 +534,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { let executableName: String let productType: PIF.Target.ProductType if product.type == .library(.dynamic) { - if parameters.shouldCreateDylibForDynamicProducts { + if self.parameters.shouldCreateDylibForDynamicProducts { pifTargetProductName = "lib\(product.name).dylib" executableName = pifTargetProductName productType = .dynamicLibrary @@ -514,9 +553,9 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { // depend on. XCBuild will not produce a separate artifact for a package product, but will instead consider any // dependency on the package product to be a dependency on the whole set of targets on which the package product // depends. - let pifTarget = addTarget( + let pifTarget = self.addTarget( guid: product.pifTargetGUID, - name: targetName(for: product), + name: self.targetName(for: product), productType: productType, productName: pifTargetProductName ) @@ -528,10 +567,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { switch dependency { case .target(let target, let conditions): if target.type != .systemModule { - addDependency(to: target, in: pifTarget, conditions: conditions, linkProduct: true) + self.addDependency(to: target, in: pifTarget, conditions: conditions, linkProduct: true) } case .product(let product, let conditions): - addDependency(to: product, in: pifTarget, conditions: conditions, linkProduct: true) + self.addDependency(to: product, in: pifTarget, conditions: conditions, linkProduct: true) } } @@ -556,9 +595,12 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.DEFINES_MODULE] = "YES" settings[.SKIP_INSTALL] = "NO" settings[.INSTALL_PATH] = "/usr/local/lib" - settings[.LIBRARY_SEARCH_PATHS] = ["$(inherited)", "\(parameters.toolchainLibDir.pathString)/swift/macosx"] + settings[.LIBRARY_SEARCH_PATHS] = [ + "$(inherited)", + "\(self.parameters.toolchainLibDir.pathString)/swift/macosx", + ] - if !parameters.shouldCreateDylibForDynamicProducts { + if !self.parameters.shouldCreateDylibForDynamicProducts { settings[.GENERATE_INFOPLIST_FILE] = "YES" // If the built framework is named same as one of the target in the package, it can be picked up // automatically during indexing since the build system always adds a -F flag to the built products dir. @@ -580,8 +622,8 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { pifTarget.addBuildConfiguration(name: "Release", settings: settings) } - private func addLibraryTarget(for target: ResolvedTarget) throws { - let pifTarget = addTarget( + private func addLibraryTarget(for target: ResolvedModule) throws { + let pifTarget = self.addTarget( guid: target.pifTargetGUID, name: target.name, productType: .objectFile, @@ -603,7 +645,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { // symbols when there are more than one targets that produce .o as their product. settings[.CLANG_COVERAGE_MAPPING_LINKER_ARGS] = "NO" if let aliases = target.moduleAliases { - settings[.SWIFT_MODULE_ALIASES] = aliases.map{ $0.key + "=" + $0.value } + settings[.SWIFT_MODULE_ALIASES] = aliases.map { $0.key + "=" + $0.value } } // Create a set of build settings that will be imparted to any target that depends on this one. @@ -623,16 +665,16 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { // Also propagate this search path to all direct and indirect clients. impartedSettings[.HEADER_SEARCH_PATHS, default: ["$(inherited)"]].append(clangTarget.includeDir.pathString) - if !fileSystem.exists(clangTarget.moduleMapPath) { + if !self.fileSystem.exists(clangTarget.moduleMapPath) { impartedSettings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]] += ["-Xcc", "-fmodule-map-file=\(moduleMapFile)"] moduleMapFileContents = """ - module \(target.c99name) { - umbrella "\(clangTarget.includeDir.pathString)" - export * - } - """ + module \(target.c99name) { + umbrella "\(clangTarget.includeDir.pathString)" + export * + } + """ shouldImpartModuleMap = true } else { @@ -640,17 +682,20 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { shouldImpartModuleMap = false } } else if let swiftTarget = target.underlying as? SwiftTarget { - settings[.SWIFT_VERSION] = swiftTarget.swiftVersion.description + try settings.addSwiftVersionSettings(target: swiftTarget, parameters: self.parameters) + // Generate ObjC compatibility header for Swift library targets. settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR] = "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)" settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME] = "\(target.name)-Swift.h" + settings.addCommonSwiftSettings(package: self.package, target: target, parameters: self.parameters) + moduleMapFileContents = """ - module \(target.c99name) { - header "\(target.name)-Swift.h" - export * - } - """ + module \(target.c99name) { + header "\(target.name)-Swift.h" + export * + } + """ shouldImpartModuleMap = true } else { @@ -672,12 +717,15 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { impartedSettings[.OTHER_LDFLAGS, default: ["$(inherited)"]].append("-lc++") } - addSources(target.sources, to: pifTarget) + // radar://112671586 supress unnecessary warnings + impartedSettings[.OTHER_LDFLAGS, default: ["$(inherited)"]].append("-Wl,-no_warn_duplicate_libraries") + + self.addSources(target.sources, to: pifTarget) // Handle the target's dependencies (but don't link against them). let dependencies = try! topologicalSort(target.dependencies) { $0.packageDependencies }.sorted() for dependency in dependencies { - addDependency(to: dependency, in: pifTarget, linkProduct: false) + self.addDependency(to: dependency, in: pifTarget, linkProduct: false) } if let resourceBundle = addResourceBundle(for: target, in: pifTarget) { @@ -699,12 +747,20 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { ) let impartedBuildProperties = PIF.ImpartedBuildProperties(settings: impartedSettings) - pifTarget.addBuildConfiguration(name: "Debug", settings: debugSettings, impartedBuildProperties: impartedBuildProperties) - pifTarget.addBuildConfiguration(name: "Release", settings: releaseSettings, impartedBuildProperties: impartedBuildProperties) + pifTarget.addBuildConfiguration( + name: "Debug", + settings: debugSettings, + impartedBuildProperties: impartedBuildProperties + ) + pifTarget.addBuildConfiguration( + name: "Release", + settings: releaseSettings, + impartedBuildProperties: impartedBuildProperties + ) pifTarget.impartedBuildSettings = impartedSettings } - private func addSystemTarget(for target: ResolvedTarget) throws { + private func addSystemTarget(for target: ResolvedModule) throws { guard let systemTarget = target.underlying as? SystemLibraryTarget else { throw InternalError("unexpected target type") } @@ -715,10 +771,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { var cFlags: [String] = [] for result in try pkgConfigArgs( for: systemTarget, - pkgConfigDirectories: parameters.pkgConfigDirectories, - sdkRootPath: parameters.sdkRootPath, - fileSystem: fileSystem, - observabilityScope: observabilityScope + pkgConfigDirectories: self.parameters.pkgConfigDirectories, + sdkRootPath: self.parameters.sdkRootPath, + fileSystem: self.fileSystem, + observabilityScope: self.observabilityScope ) { if let error = result.error { self.observabilityScope.emit( @@ -732,14 +788,24 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } impartedSettings[.OTHER_LDRFLAGS] = [] - impartedSettings[.OTHER_CFLAGS, default: ["$(inherited)"]] += ["-fmodule-map-file=\(systemTarget.moduleMapPath)"] + cFlags - impartedSettings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]] += ["-Xcc", "-fmodule-map-file=\(systemTarget.moduleMapPath)"] + cFlags + impartedSettings[.OTHER_CFLAGS, default: ["$(inherited)"]] += + ["-fmodule-map-file=\(systemTarget.moduleMapPath)"] + cFlags + impartedSettings[.OTHER_SWIFT_FLAGS, default: ["$(inherited)"]] += + ["-Xcc", "-fmodule-map-file=\(systemTarget.moduleMapPath)"] + cFlags let impartedBuildProperties = PIF.ImpartedBuildProperties(settings: impartedSettings) // Create an aggregate PIF target (which doesn't have an actual product). let pifTarget = addAggregateTarget(guid: target.pifTargetGUID, name: target.name) - pifTarget.addBuildConfiguration(name: "Debug", settings: PIF.BuildSettings(), impartedBuildProperties: impartedBuildProperties) - pifTarget.addBuildConfiguration(name: "Release", settings: PIF.BuildSettings(), impartedBuildProperties: impartedBuildProperties) + pifTarget.addBuildConfiguration( + name: "Debug", + settings: PIF.BuildSettings(), + impartedBuildProperties: impartedBuildProperties + ) + pifTarget.addBuildConfiguration( + name: "Release", + settings: PIF.BuildSettings(), + impartedBuildProperties: impartedBuildProperties + ) pifTarget.impartedBuildSettings = impartedSettings } @@ -747,7 +813,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { // Create a group for the target's source files. For now we use an absolute path for it, but we should really // make it be container-relative, since it's always inside the package directory. let targetGroup = groupTree.addGroup( - path: sources.root.relative(to: package.path).pathString, + path: sources.root.relative(to: self.package.path).pathString, sourceTree: .group ) @@ -758,20 +824,20 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } private func addDependency( - to dependency: ResolvedTarget.Dependency, + to dependency: ResolvedModule.Dependency, in pifTarget: PIFTargetBuilder, linkProduct: Bool ) { switch dependency { case .target(let target, let conditions): - addDependency( + self.addDependency( to: target, in: pifTarget, conditions: conditions, linkProduct: linkProduct ) case .product(let product, let conditions): - addDependency( + self.addDependency( to: product, in: pifTarget, conditions: conditions, @@ -781,24 +847,25 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } private func addDependency( - to target: ResolvedTarget, + to target: ResolvedModule, in pifTarget: PIFTargetBuilder, conditions: [PackageCondition], linkProduct: Bool ) { // Only add the binary target as a library when we want to link against the product. if let binaryTarget = target.underlying as? BinaryTarget { - let ref = binaryGroup.addFileReference(path: binaryTarget.artifactPath.pathString) + let ref = self.binaryGroup.addFileReference(path: binaryTarget.artifactPath.pathString) pifTarget.addLibrary(ref, platformFilters: conditions.toPlatformFilters()) } else { // If this is an executable target, the dependency should be to the PIF target created from the its // product, as we don't have PIF targets corresponding to executable targets. - let targetGUID = executableTargetProductMap[target.id]?.pifTargetGUID ?? target.pifTargetGUID + let targetGUID = self.executableTargetProductMap[target.id]?.pifTargetGUID ?? target.pifTargetGUID let linkProduct = linkProduct && target.type != .systemModule && target.type != .executable pifTarget.addDependency( toTargetWithGUID: targetGUID, platformFilters: conditions.toPlatformFilters(), - linkProduct: linkProduct) + linkProduct: linkProduct + ) } } @@ -815,13 +882,13 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { ) } - private func addResourceBundle(for target: ResolvedTarget, in pifTarget: PIFTargetBuilder) -> String? { + private func addResourceBundle(for target: ResolvedModule, in pifTarget: PIFTargetBuilder) -> String? { guard !target.underlying.resources.isEmpty else { return nil } let bundleName = "\(package.manifest.displayName)_\(target.name)" // TODO: use identity instead? - let resourcesTarget = addTarget( + let resourcesTarget = self.addTarget( guid: target.pifResourceTargetGUID, name: bundleName, productType: .bundle, @@ -838,7 +905,8 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { settings[.TARGET_NAME] = bundleName settings[.PRODUCT_NAME] = bundleName settings[.PRODUCT_MODULE_NAME] = bundleName - let bundleIdentifier = "\(package.manifest.displayName).\(target.name).resources".spm_mangledToBundleIdentifier() // TODO: use identity instead? + let bundleIdentifier = "\(package.manifest.displayName).\(target.name).resources" + .spm_mangledToBundleIdentifier() // TODO: use identity instead? settings[.PRODUCT_BUNDLE_IDENTIFIER] = bundleIdentifier settings[.GENERATE_INFOPLIST_FILE] = "YES" settings[.PACKAGE_RESOURCE_TARGET_KIND] = "resource" @@ -846,7 +914,7 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { resourcesTarget.addBuildConfiguration(name: "Debug", settings: settings) resourcesTarget.addBuildConfiguration(name: "Release", settings: settings) - let coreDataFileTypes = [XCBuildFileType.xcdatamodeld, .xcdatamodel].flatMap { $0.fileTypes } + let coreDataFileTypes = [XCBuildFileType.xcdatamodeld, .xcdatamodel].flatMap(\.fileTypes) for resource in target.underlying.resources { // FIXME: Handle rules here. let resourceFile = groupTree.addFileReference( @@ -864,7 +932,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { } let targetGroup = groupTree.addGroup(path: "/", sourceTree: .group) - pifTarget.addResourceFile(targetGroup.addFileReference(path: "\(bundleName).bundle", sourceTree: .builtProductsDir)) + pifTarget.addResourceFile(targetGroup.addFileReference( + path: "\(bundleName).bundle", + sourceTree: .builtProductsDir + )) return bundleName } @@ -895,7 +966,8 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { for assignment in assignments { var value = assignment.value if setting == .HEADER_SEARCH_PATHS { - value = try value.map { try AbsolutePath(validating: $0, relativeTo: target.sources.root).pathString } + value = try value + .map { try AbsolutePath(validating: $0, relativeTo: target.sources.root).pathString } } if let platforms = assignment.platforms { @@ -904,10 +976,22 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { switch configuration { case .debug: debugSettings[setting, for: platform, default: ["$(inherited)"]] += value - addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .debug, settings: &debugSettings) + self.addInferredBuildSettings( + for: setting, + value: value, + platform: platform, + configuration: .debug, + settings: &debugSettings + ) case .release: releaseSettings[setting, for: platform, default: ["$(inherited)"]] += value - addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .release, settings: &releaseSettings) + self.addInferredBuildSettings( + for: setting, + value: value, + platform: platform, + configuration: .release, + settings: &releaseSettings + ) } } @@ -920,10 +1004,20 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder { switch configuration { case .debug: debugSettings[setting, default: ["$(inherited)"]] += value - addInferredBuildSettings(for: setting, value: value, configuration: .debug, settings: &debugSettings) + self.addInferredBuildSettings( + for: setting, + value: value, + configuration: .debug, + settings: &debugSettings + ) case .release: releaseSettings[setting, default: ["$(inherited)"]] += value - addInferredBuildSettings(for: setting, value: value, configuration: .release, settings: &releaseSettings) + self.addInferredBuildSettings( + for: setting, + value: value, + configuration: .release, + settings: &releaseSettings + ) } } @@ -975,10 +1069,18 @@ final class AggregatePIFProjectBuilder: PIFProjectBuilder { for case let project as PackagePIFProjectBuilder in projects where project.isRootPackage { for case let target as PIFTargetBuilder in project.targets { if target.productType != .unitTest { - allExcludingTestsTarget.addDependency(toTargetWithGUID: target.guid, platformFilters: [], linkProduct: false) + allExcludingTestsTarget.addDependency( + toTargetWithGUID: target.guid, + platformFilters: [], + linkProduct: false + ) } - allIncludingTestsTarget.addDependency(toTargetWithGUID: target.guid, platformFilters: [], linkProduct: false) + allIncludingTestsTarget.addDependency( + toTargetWithGUID: target.guid, + platformFilters: [], + linkProduct: false + ) } } } @@ -1007,12 +1109,12 @@ final class PIFFileReferenceBuilder: PIFReferenceBuilder { } func construct() -> PIF.Reference { - return PIF.FileReference( - guid: guid, - path: path, - sourceTree: sourceTree, - name: name, - fileType: fileType + PIF.FileReference( + guid: self.guid, + path: self.path, + sourceTree: self.sourceTree, + name: self.name, + fileType: self.fileType ) } } @@ -1030,7 +1132,7 @@ final class PIFGroupBuilder: PIFReferenceBuilder { self.path = path self.sourceTree = sourceTree self.name = name - children = [] + self.children = [] } /// Creates and appends a new Group to the list of children. The new group is returned so that it can be configured. @@ -1040,7 +1142,7 @@ final class PIFGroupBuilder: PIFReferenceBuilder { name: String? = nil ) -> PIFGroupBuilder { let group = PIFGroupBuilder(path: path, sourceTree: sourceTree, name: name) - children.append(group) + self.children.append(group) return group } @@ -1052,26 +1154,26 @@ final class PIFGroupBuilder: PIFReferenceBuilder { fileType: String? = nil ) -> PIFFileReferenceBuilder { let file = PIFFileReferenceBuilder(path: path, sourceTree: sourceTree, name: name, fileType: fileType) - children.append(file) + self.children.append(file) return file } func removeChild(_ reference: PIFReferenceBuilder) { - children.removeAll { $0 === reference } + self.children.removeAll { $0 === reference } } func construct() -> PIF.Reference { let children = self.children.enumerated().map { kvp -> PIF.Reference in let (index, builder) = kvp - builder.guid = "\(guid)::REF_\(index)" + builder.guid = "\(self.guid)::REF_\(index)" return builder.construct() } return PIF.Group( - guid: guid, - path: path, - sourceTree: sourceTree, - name: name, + guid: self.guid, + path: self.path, + sourceTree: self.sourceTree, + name: self.name, children: children ) } @@ -1101,10 +1203,15 @@ class PIFBaseTargetBuilder { public func addBuildConfiguration( name: String, settings: PIF.BuildSettings = PIF.BuildSettings(), - impartedBuildProperties: PIF.ImpartedBuildProperties = PIF.ImpartedBuildProperties(settings: PIF.BuildSettings()) + impartedBuildProperties: PIF.ImpartedBuildProperties = PIF + .ImpartedBuildProperties(settings: PIF.BuildSettings()) ) -> PIFBuildConfigurationBuilder { - let builder = PIFBuildConfigurationBuilder(name: name, settings: settings, impartedBuildProperties: impartedBuildProperties) - buildConfigurations.append(builder) + let builder = PIFBuildConfigurationBuilder( + name: name, + settings: settings, + impartedBuildProperties: impartedBuildProperties + ) + self.buildConfigurations.append(builder) return builder } @@ -1117,7 +1224,7 @@ class PIFBaseTargetBuilder { @discardableResult func addHeadersBuildPhase() -> PIFHeadersBuildPhaseBuilder { let buildPhase = PIFHeadersBuildPhaseBuilder() - buildPhases.append(buildPhase) + self.buildPhases.append(buildPhase) return buildPhase } @@ -1126,7 +1233,7 @@ class PIFBaseTargetBuilder { @discardableResult func addSourcesBuildPhase() -> PIFSourcesBuildPhaseBuilder { let buildPhase = PIFSourcesBuildPhaseBuilder() - buildPhases.append(buildPhase) + self.buildPhases.append(buildPhase) return buildPhase } @@ -1135,14 +1242,14 @@ class PIFBaseTargetBuilder { @discardableResult func addFrameworksBuildPhase() -> PIFFrameworksBuildPhaseBuilder { let buildPhase = PIFFrameworksBuildPhaseBuilder() - buildPhases.append(buildPhase) + self.buildPhases.append(buildPhase) return buildPhase } @discardableResult func addResourcesBuildPhase() -> PIFResourcesBuildPhaseBuilder { let buildPhase = PIFResourcesBuildPhaseBuilder() - buildPhases.append(buildPhase) + self.buildPhases.append(buildPhase) return buildPhase } @@ -1151,52 +1258,60 @@ class PIFBaseTargetBuilder { /// true, the receiver will also be configured to link against the product produced by the other target (this /// presumes that the product type is one that can be linked against). func addDependency(toTargetWithGUID targetGUID: String, platformFilters: [PIF.PlatformFilter], linkProduct: Bool) { - dependencies.append(.init(targetGUID: targetGUID, platformFilters: platformFilters)) + self.dependencies.append(.init(targetGUID: targetGUID, platformFilters: platformFilters)) if linkProduct { - let frameworksPhase = buildPhases.first { $0 is PIFFrameworksBuildPhaseBuilder } - ?? addFrameworksBuildPhase() + let frameworksPhase = self.buildPhases.first { $0 is PIFFrameworksBuildPhaseBuilder } + ?? self.addFrameworksBuildPhase() frameworksPhase.addBuildFile(toTargetWithGUID: targetGUID, platformFilters: platformFilters) } } /// Convenience function to add a file reference to the Headers build phase, after creating it if needed. @discardableResult - public func addHeaderFile(_ fileReference: PIFFileReferenceBuilder, headerVisibility: PIF.BuildFile.HeaderVisibility) -> PIFBuildFileBuilder { - let headerPhase = buildPhases.first { $0 is PIFHeadersBuildPhaseBuilder } ?? addHeadersBuildPhase() + public func addHeaderFile( + _ fileReference: PIFFileReferenceBuilder, + headerVisibility: PIF.BuildFile.HeaderVisibility + ) -> PIFBuildFileBuilder { + let headerPhase = self.buildPhases.first { $0 is PIFHeadersBuildPhaseBuilder } ?? self.addHeadersBuildPhase() return headerPhase.addBuildFile(to: fileReference, platformFilters: [], headerVisibility: headerVisibility) } /// Convenience function to add a file reference to the Sources build phase, after creating it if needed. @discardableResult public func addSourceFile(_ fileReference: PIFFileReferenceBuilder) -> PIFBuildFileBuilder { - let sourcesPhase = buildPhases.first { $0 is PIFSourcesBuildPhaseBuilder } ?? addSourcesBuildPhase() + let sourcesPhase = self.buildPhases.first { $0 is PIFSourcesBuildPhaseBuilder } ?? self.addSourcesBuildPhase() return sourcesPhase.addBuildFile(to: fileReference, platformFilters: []) } /// Convenience function to add a file reference to the Frameworks build phase, after creating it if needed. @discardableResult - public func addLibrary(_ fileReference: PIFFileReferenceBuilder, platformFilters: [PIF.PlatformFilter]) -> PIFBuildFileBuilder { - let frameworksPhase = buildPhases.first { $0 is PIFFrameworksBuildPhaseBuilder } ?? addFrameworksBuildPhase() + public func addLibrary( + _ fileReference: PIFFileReferenceBuilder, + platformFilters: [PIF.PlatformFilter] + ) -> PIFBuildFileBuilder { + let frameworksPhase = self.buildPhases.first { $0 is PIFFrameworksBuildPhaseBuilder } ?? self + .addFrameworksBuildPhase() return frameworksPhase.addBuildFile(to: fileReference, platformFilters: platformFilters) } @discardableResult public func addResourceFile(_ fileReference: PIFFileReferenceBuilder) -> PIFBuildFileBuilder { - let resourcesPhase = buildPhases.first { $0 is PIFResourcesBuildPhaseBuilder } ?? addResourcesBuildPhase() + let resourcesPhase = self.buildPhases.first { $0 is PIFResourcesBuildPhaseBuilder } ?? self + .addResourcesBuildPhase() return resourcesPhase.addBuildFile(to: fileReference, platformFilters: []) } fileprivate func constructBuildConfigurations() -> [PIF.BuildConfiguration] { - buildConfigurations.map { builder -> PIF.BuildConfiguration in - builder.guid = "\(guid)::BUILDCONFIG_\(builder.name)" + self.buildConfigurations.map { builder -> PIF.BuildConfiguration in + builder.guid = "\(self.guid)::BUILDCONFIG_\(builder.name)" return builder.construct() } } fileprivate func constructBuildPhases() throws -> [PIF.BuildPhase] { - try buildPhases.enumerated().map { kvp in + try self.buildPhases.enumerated().map { kvp in let (index, builder) = kvp - builder.guid = "\(guid)::BUILDPHASE_\(index)" + builder.guid = "\(self.guid)::BUILDPHASE_\(index)" return try builder.construct() } } @@ -1204,11 +1319,11 @@ class PIFBaseTargetBuilder { final class PIFAggregateTargetBuilder: PIFBaseTargetBuilder { override func construct() throws -> PIF.BaseTarget { - return PIF.AggregateTarget( + try PIF.AggregateTarget( guid: guid, name: name, buildConfigurations: constructBuildConfigurations(), - buildPhases: try self.constructBuildPhases(), + buildPhases: self.constructBuildPhases(), dependencies: dependencies, impartedBuildSettings: impartedBuildSettings ) @@ -1227,13 +1342,13 @@ final class PIFTargetBuilder: PIFBaseTargetBuilder { } override func construct() throws -> PIF.BaseTarget { - return PIF.Target( + try PIF.Target( guid: guid, name: name, - productType: productType, - productName: productName, + productType: self.productType, + productName: self.productName, buildConfigurations: constructBuildConfigurations(), - buildPhases: try self.constructBuildPhases(), + buildPhases: self.constructBuildPhases(), dependencies: dependencies, impartedBuildSettings: impartedBuildSettings ) @@ -1247,16 +1362,24 @@ class PIFBuildPhaseBuilder { var guid: PIF.GUID fileprivate init() { - buildFiles = [] + self.buildFiles = [] } /// Adds a new build file builder that refers to a file reference. /// - Parameters: /// - file: The builder for the file reference. @discardableResult - func addBuildFile(to file: PIFFileReferenceBuilder, platformFilters: [PIF.PlatformFilter], headerVisibility: PIF.BuildFile.HeaderVisibility? = nil) -> PIFBuildFileBuilder { - let builder = PIFBuildFileBuilder(file: file, platformFilters: platformFilters, headerVisibility: headerVisibility) - buildFiles.append(builder) + func addBuildFile( + to file: PIFFileReferenceBuilder, + platformFilters: [PIF.PlatformFilter], + headerVisibility: PIF.BuildFile.HeaderVisibility? = nil + ) -> PIFBuildFileBuilder { + let builder = PIFBuildFileBuilder( + file: file, + platformFilters: platformFilters, + headerVisibility: headerVisibility + ) + self.buildFiles.append(builder) return builder } @@ -1264,9 +1387,12 @@ class PIFBuildPhaseBuilder { /// - Parameters: /// - targetGUID: The GIUD referencing the target. @discardableResult - func addBuildFile(toTargetWithGUID targetGUID: PIF.GUID, platformFilters: [PIF.PlatformFilter]) -> PIFBuildFileBuilder { + func addBuildFile( + toTargetWithGUID targetGUID: PIF.GUID, + platformFilters: [PIF.PlatformFilter] + ) -> PIFBuildFileBuilder { let builder = PIFBuildFileBuilder(targetGUID: targetGUID, platformFilters: platformFilters) - buildFiles.append(builder) + self.buildFiles.append(builder) return builder } @@ -1275,9 +1401,9 @@ class PIFBuildPhaseBuilder { } fileprivate func constructBuildFiles() -> [PIF.BuildFile] { - return buildFiles.enumerated().map { kvp -> PIF.BuildFile in + self.buildFiles.enumerated().map { kvp -> PIF.BuildFile in let (index, builder) = kvp - builder.guid = "\(guid)::\(index)" + builder.guid = "\(self.guid)::\(index)" return builder.construct() } } @@ -1315,9 +1441,9 @@ final class PIFBuildFileBuilder { var pifReference: PIF.BuildFile.Reference { switch self { case .file(let builder): - return .file(guid: builder.guid) + .file(guid: builder.guid) case .target(let guid): - return .target(guid: guid) + .target(guid: guid) } } } @@ -1331,20 +1457,33 @@ final class PIFBuildFileBuilder { let headerVisibility: PIF.BuildFile.HeaderVisibility? - fileprivate init(file: PIFFileReferenceBuilder, platformFilters: [PIF.PlatformFilter], headerVisibility: PIF.BuildFile.HeaderVisibility? = nil) { - reference = .file(builder: file) + fileprivate init( + file: PIFFileReferenceBuilder, + platformFilters: [PIF.PlatformFilter], + headerVisibility: PIF.BuildFile.HeaderVisibility? = nil + ) { + self.reference = .file(builder: file) self.platformFilters = platformFilters self.headerVisibility = headerVisibility } - fileprivate init(targetGUID: PIF.GUID, platformFilters: [PIF.PlatformFilter], headerVisibility: PIF.BuildFile.HeaderVisibility? = nil) { - reference = .target(guid: targetGUID) + fileprivate init( + targetGUID: PIF.GUID, + platformFilters: [PIF.PlatformFilter], + headerVisibility: PIF.BuildFile.HeaderVisibility? = nil + ) { + self.reference = .target(guid: targetGUID) self.platformFilters = platformFilters self.headerVisibility = headerVisibility } func construct() -> PIF.BuildFile { - PIF.BuildFile(guid: guid, reference: reference.pifReference, platformFilters: platformFilters, headerVisibility: headerVisibility) + PIF.BuildFile( + guid: self.guid, + reference: self.reference.pifReference, + platformFilters: self.platformFilters, + headerVisibility: self.headerVisibility + ) } } @@ -1364,7 +1503,12 @@ final class PIFBuildConfigurationBuilder { } func construct() -> PIF.BuildConfiguration { - PIF.BuildConfiguration(guid: guid, name: name, buildSettings: settings, impartedBuildProperties: impartedBuildProperties) + PIF.BuildConfiguration( + guid: self.guid, + name: self.name, + buildSettings: self.settings, + impartedBuildProperties: self.impartedBuildProperties + ) } } @@ -1379,7 +1523,7 @@ extension ResolvedPackage { extension ResolvedProduct { var pifTargetGUID: PIF.GUID { "PACKAGE-PRODUCT:\(name)" } - var mainTarget: ResolvedTarget { + var mainTarget: ResolvedModule { targets.first { $0.type == underlying.type.targetType }! } @@ -1387,33 +1531,32 @@ extension ResolvedProduct { /// based on their conditions and in a stable order. /// - Parameters: /// - environment: The build environment to use to filter dependencies on. - public func recursivePackageDependencies() -> [ResolvedTarget.Dependency] { - let initialDependencies = targets.map { ResolvedTarget.Dependency.target($0, conditions: []) } + public func recursivePackageDependencies() -> [ResolvedModule.Dependency] { + let initialDependencies = targets.map { ResolvedModule.Dependency.target($0, conditions: []) } return try! topologicalSort(initialDependencies) { dependency in - return dependency.packageDependencies + dependency.packageDependencies }.sorted() } } -extension ResolvedTarget { +extension ResolvedModule { var pifTargetGUID: PIF.GUID { "PACKAGE-TARGET:\(name)" } var pifResourceTargetGUID: PIF.GUID { "PACKAGE-RESOURCE:\(name)" } } -extension Array where Element == ResolvedTarget.Dependency { - +extension [ResolvedModule.Dependency] { /// Sorts to get products first, sorted by name, followed by targets, sorted by name. - func sorted() -> [ResolvedTarget.Dependency] { - sorted { lhsDependency, rhsDependency in + func sorted() -> [ResolvedModule.Dependency] { + self.sorted { lhsDependency, rhsDependency in switch (lhsDependency, rhsDependency) { case (.product, .target): - return true + true case (.target, .product): - return false + false case (.product(let lhsProduct, _), .product(let rhsProduct, _)): - return lhsProduct.name < rhsProduct.name + lhsProduct.name < rhsProduct.name case (.target(let lhsTarget, _), .target(let rhsTarget, _)): - return lhsTarget.name < rhsTarget.name + lhsTarget.name < rhsTarget.name } } } @@ -1421,13 +1564,13 @@ extension Array where Element == ResolvedTarget.Dependency { extension ResolvedPackage { func deploymentTarget(for platform: PackageModel.Platform, usingXCTest: Bool = false) -> String? { - return self.getSupportedPlatform(for: platform, usingXCTest: usingXCTest).version.versionString + self.getSupportedPlatform(for: platform, usingXCTest: usingXCTest).version.versionString } } -extension ResolvedTarget { +extension ResolvedModule { func deploymentTarget(for platform: PackageModel.Platform, usingXCTest: Bool = false) -> String? { - return self.getSupportedPlatform(for: platform, usingXCTest: usingXCTest).version.versionString + self.getSupportedPlatform(for: platform, usingXCTest: usingXCTest).version.versionString } } @@ -1441,17 +1584,17 @@ extension ProductType { var targetType: Target.Kind { switch self { case .executable: - return .executable + .executable case .snippet: - return .snippet + .snippet case .test: - return .test + .test case .library: - return .library + .library case .plugin: - return .plugin + .plugin case .macro: - return .macro + .macro } } } @@ -1467,8 +1610,8 @@ private struct PIFBuildSettingAssignment { let platforms: [PIF.BuildSettings.Platform]? } -private extension BuildSettings.AssignmentTable { - var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] { +extension BuildSettings.AssignmentTable { + fileprivate var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] { var pifAssignments: [PIF.BuildSettings.MultipleValueSetting: [PIFBuildSettingAssignment]] = [:] for (declaration, assignments) in self.assignments { @@ -1494,7 +1637,8 @@ private extension BuildSettings.AssignmentTable { let pifAssignment = PIFBuildSettingAssignment( value: value, configurations: assignment.configurations, - platforms: assignment.pifPlatforms) + platforms: assignment.pifPlatforms + ) pifAssignments[setting, default: []].append(pifAssignment) } @@ -1504,20 +1648,20 @@ private extension BuildSettings.AssignmentTable { } } -private extension BuildSettings.Assignment { - var configurations: [BuildConfiguration] { +extension BuildSettings.Assignment { + fileprivate var configurations: [BuildConfiguration] { if let configurationCondition = conditions.lazy.compactMap(\.configurationCondition).first { - return [configurationCondition.configuration] + [configurationCondition.configuration] } else { - return BuildConfiguration.allCases + BuildConfiguration.allCases } } - var pifPlatforms: [PIF.BuildSettings.Platform]? { + fileprivate var pifPlatforms: [PIF.BuildSettings.Platform]? { if let platformsCondition = conditions.lazy.compactMap(\.platformsCondition).first { - return platformsCondition.platforms.compactMap { PIF.BuildSettings.Platform(rawValue: $0.name) } + platformsCondition.platforms.compactMap { PIF.BuildSettings.Platform(rawValue: $0.name) } } else { - return nil + nil } } } @@ -1526,8 +1670,7 @@ private extension BuildSettings.Assignment { public struct DelayedImmutable { private var _value: Value? = nil - public init() { - } + public init() {} public var wrappedValue: Value { get { @@ -1537,10 +1680,10 @@ public struct DelayedImmutable { return value } set { - if _value != nil { + if self._value != nil { fatalError("property initialized twice") } - _value = newValue + self._value = newValue } } } @@ -1548,7 +1691,7 @@ public struct DelayedImmutable { extension [PackageCondition] { func toPlatformFilters() -> [PIF.PlatformFilter] { var result: [PIF.PlatformFilter] = [] - let platformConditions = self.compactMap(\.platformsCondition).flatMap { $0.platforms } + let platformConditions = self.compactMap(\.platformsCondition).flatMap(\.platforms) for condition in platformConditions { switch condition { @@ -1567,6 +1710,9 @@ extension [PackageCondition] { case .watchOS: result += PIF.PlatformFilter.watchOSFilters + case .visionOS: + result += PIF.PlatformFilter.visionOSFilters + case .linux: result += PIF.PlatformFilter.linuxFilters @@ -1587,7 +1733,6 @@ extension [PackageCondition] { default: assertionFailure("Unhandled platform condition: \(condition)") - break } } return result @@ -1595,31 +1740,30 @@ extension [PackageCondition] { } extension PIF.PlatformFilter { - /// macOS platform filters. public static let macOSFilters: [PIF.PlatformFilter] = [.init(platform: "macos")] /// Mac Catalyst platform filters. public static let macCatalystFilters: [PIF.PlatformFilter] = [ - .init(platform: "ios", environment: "maccatalyst") + .init(platform: "ios", environment: "maccatalyst"), ] /// iOS platform filters. public static let iOSFilters: [PIF.PlatformFilter] = [ .init(platform: "ios"), - .init(platform: "ios", environment: "simulator") + .init(platform: "ios", environment: "simulator"), ] /// tvOS platform filters. public static let tvOSFilters: [PIF.PlatformFilter] = [ .init(platform: "tvos"), - .init(platform: "tvos", environment: "simulator") + .init(platform: "tvos", environment: "simulator"), ] /// watchOS platform filters. public static let watchOSFilters: [PIF.PlatformFilter] = [ .init(platform: "watchos"), - .init(platform: "watchos", environment: "simulator") + .init(platform: "watchos", environment: "simulator"), ] /// DriverKit platform filters. @@ -1640,11 +1784,9 @@ extension PIF.PlatformFilter { ] /// Common Linux platform filters. - public static let linuxFilters: [PIF.PlatformFilter] = { - ["", "eabi", "gnu", "gnueabi", "gnueabihf"].map { - .init(platform: "linux", environment: $0) - } - }() + public static let linuxFilters: [PIF.PlatformFilter] = ["", "eabi", "gnu", "gnueabi", "gnueabihf"].map { + .init(platform: "linux", environment: $0) + } /// OpenBSD filters. public static let openBSDFilters: [PIF.PlatformFilter] = [ @@ -1655,19 +1797,151 @@ extension PIF.PlatformFilter { public static let webAssemblyFilters: [PIF.PlatformFilter] = [ .init(platform: "wasi"), ] + + /// VisionOS platform filters. + public static let visionOSFilters: [PIF.PlatformFilter] = [ + .init(platform: "xros"), + .init(platform: "xros", environment: "simulator"), + .init(platform: "visionos"), + .init(platform: "visionos", environment: "simulator"), + ] +} + +extension PIF.BuildSettings { + fileprivate mutating func addSwiftVersionSettings( + target: SwiftTarget, + parameters: PIFBuilderParameters + ) throws { + guard let versionAssignments = target.buildSettings.assignments[.SWIFT_VERSION] else { + // This should never happens in practice because there is always a default tools version based value. + return + } + + func isSupportedVersion(_ version: SwiftLanguageVersion) -> Bool { + parameters.supportedSwiftVersions.isEmpty || parameters.supportedSwiftVersions.contains(version) + } + + func computeEffectiveSwiftVersions(for versions: [SwiftLanguageVersion]) -> [String] { + versions + .filter { target.declaredSwiftVersions.contains($0) } + .filter { isSupportedVersion($0) }.map(\.description) + } + + func computeEffectiveTargetVersion(for assignment: BuildSettings.Assignment) throws -> String { + let versions = assignment.values.compactMap { SwiftLanguageVersion(string: $0) } + if let effectiveVersion = computeEffectiveSwiftVersions(for: versions).last { + return effectiveVersion + } + + throw PIFGenerationError.unsupportedSwiftLanguageVersions( + targetName: target.name, + versions: versions, + supportedVersions: parameters.supportedSwiftVersions + ) + } + + var toolsSwiftVersion: SwiftLanguageVersion? = nil + // First, check whether there are any target specific settings. + for assignment in versionAssignments { + if assignment.default { + toolsSwiftVersion = assignment.values.first.flatMap { .init(string: $0) } + continue + } + + if assignment.conditions.isEmpty { + self[.SWIFT_VERSION] = try computeEffectiveTargetVersion(for: assignment) + continue + } + + for condition in assignment.conditions { + if let platforms = condition.platformsCondition { + for platform: Platform in platforms.platforms.compactMap({ .init(rawValue: $0.name) }) { + self[.SWIFT_VERSION, for: platform] = try computeEffectiveTargetVersion(for: assignment) + } + } + } + } + + // If there were no target specific assignments, let's add a fallback tools version based value. + if let toolsSwiftVersion, self[.SWIFT_VERSION] == nil { + // Use tools based version if it's supported. + if isSupportedVersion(toolsSwiftVersion) { + self[.SWIFT_VERSION] = toolsSwiftVersion.description + return + } + + // Otherwise pick the newest supported tools version based value. + + // We have to normalize to two component strings to match the results from XCBuild w.r.t. to hashing of + // `SwiftLanguageVersion` instances. + let normalizedDeclaredVersions = Set(target.declaredSwiftVersions.compactMap { + SwiftLanguageVersion(string: "\($0.major).\($0.minor)") + }) + + let declaredSwiftVersions = Array( + normalizedDeclaredVersions + .intersection(parameters.supportedSwiftVersions) + ).sorted(by: >) + if let swiftVersion = declaredSwiftVersions.first { + self[.SWIFT_VERSION] = swiftVersion.description + return + } + + throw PIFGenerationError.unsupportedSwiftLanguageVersions( + targetName: target.name, + versions: Array(normalizedDeclaredVersions), + supportedVersions: parameters.supportedSwiftVersions + ) + } + } + + fileprivate mutating func addCommonSwiftSettings( + package: ResolvedPackage, + target: ResolvedModule, + parameters: PIFBuilderParameters + ) { + let packageOptions = package.packageNameArgument( + target: target, + isPackageNameSupported: parameters.isPackageAccessModifierSupported + ) + if !packageOptions.isEmpty { + self[.OTHER_SWIFT_FLAGS] = packageOptions + } + } } -private extension PIF.BuildSettings.Platform { - static func from(platform: PackageModel.Platform) -> PIF.BuildSettings.Platform? { +extension PIF.BuildSettings.Platform { + fileprivate static func from(platform: PackageModel.Platform) -> PIF.BuildSettings.Platform? { switch platform { - case .iOS: return .iOS - case .linux: return .linux - case .macCatalyst: return .macCatalyst - case .macOS: return .macOS - case .tvOS: return .tvOS - case .watchOS: return .watchOS - case .driverKit: return .driverKit - default: return nil + case .iOS: .iOS + case .linux: .linux + case .macCatalyst: .macCatalyst + case .macOS: .macOS + case .tvOS: .tvOS + case .watchOS: .watchOS + case .driverKit: .driverKit + default: nil + } + } +} + +public enum PIFGenerationError: Error { + case unsupportedSwiftLanguageVersions( + targetName: String, + versions: [SwiftLanguageVersion], + supportedVersions: [SwiftLanguageVersion] + ) +} + +extension PIFGenerationError: CustomStringConvertible { + public var description: String { + switch self { + case .unsupportedSwiftLanguageVersions( + targetName: let target, + versions: let given, + supportedVersions: let supported + ): + "Some of the Swift language versions used in target '\(target)' settings are supported. (given: \(given), supported: \(supported))" } } } diff --git a/Sources/XCBuildSupport/XCBuildDelegate.swift b/Sources/XCBuildSupport/XCBuildDelegate.swift index f7c0ae13bf7..6fbbdf48cd1 100644 --- a/Sources/XCBuildSupport/XCBuildDelegate.swift +++ b/Sources/XCBuildSupport/XCBuildDelegate.swift @@ -12,6 +12,7 @@ import Basics import Foundation + import SPMBuildCore import class TSCBasic.ThreadSafeOutputByteStream @@ -20,7 +21,7 @@ import protocol TSCBasic.OutputByteStream import enum TSCUtility.Diagnostics import protocol TSCUtility.ProgressAnimationProtocol -public class XCBuildDelegate { +package class XCBuildDelegate { private let buildSystem: SPMBuildCore.BuildSystem private var parser: XCBuildOutputParser! private let observabilityScope: ObservabilityScope @@ -145,9 +146,8 @@ private extension Basics.Diagnostic { } } -// FIXME: Move to TSC. +@available(*, deprecated, message: "use ProgressAnimation.ninja(stream:) instead") public final class VerboseProgressAnimation: ProgressAnimationProtocol { - private let stream: OutputByteStream public init(stream: OutputByteStream) { diff --git a/Sources/XCBuildSupport/XcodeBuildSystem.swift b/Sources/XCBuildSupport/XcodeBuildSystem.swift index 9eeb82ac32d..88a7c55036b 100644 --- a/Sources/XCBuildSupport/XcodeBuildSystem.swift +++ b/Sources/XCBuildSupport/XcodeBuildSystem.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -13,26 +13,27 @@ import Basics import Dispatch import class Foundation.JSONEncoder +import class Foundation.NSArray +import class Foundation.NSDictionary import PackageGraph import PackageModel + import SPMBuildCore +import func TSCBasic.memoize import protocol TSCBasic.OutputByteStream import class TSCBasic.Process import enum TSCBasic.ProcessEnv import func TSCBasic.withTemporaryFile -import func TSCBasic.memoize -import class TSCUtility.MultiLinePercentProgressAnimation import enum TSCUtility.Diagnostics -import protocol TSCUtility.ProgressAnimationProtocol -public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { +package final class XcodeBuildSystem: SPMBuildCore.BuildSystem { private let buildParameters: BuildParameters - private let packageGraphLoader: () throws -> PackageGraph + private let packageGraphLoader: () throws -> ModulesGraph private let logLevel: Basics.Diagnostic.Severity private let xcbuildPath: AbsolutePath - private var packageGraph: PackageGraph? + private var packageGraph: ModulesGraph? private var pifBuilder: PIFBuilder? private let fileSystem: FileSystem private let observabilityScope: ObservabilityScope @@ -46,9 +47,9 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { public var builtTestProducts: [BuiltTestProduct] { do { let graph = try getPackageGraph() - + var builtProducts: [BuiltTestProduct] = [] - + for package in graph.rootPackages { for product in package.products where product.type == .test { let binaryPath = try buildParameters.binaryPath(for: product) @@ -62,7 +63,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { ) } } - + return builtProducts } catch { self.observabilityScope.emit(error) @@ -78,7 +79,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { public init( buildParameters: BuildParameters, - packageGraphLoader: @escaping () throws -> PackageGraph, + packageGraphLoader: @escaping () throws -> ModulesGraph, outputStream: OutputByteStream, logLevel: Basics.Diagnostic.Severity, fileSystem: FileSystem, @@ -91,12 +92,15 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { self.fileSystem = fileSystem self.observabilityScope = observabilityScope.makeChildScope(description: "Xcode Build System") - if let xcbuildTool = ProcessEnv.vars["XCBUILD_TOOL"] { + if let xcbuildTool = ProcessEnv.block["XCBUILD_TOOL"] { xcbuildPath = try AbsolutePath(validating: xcbuildTool) } else { let xcodeSelectOutput = try TSCBasic.Process.popen(args: "xcode-select", "-p").utf8Output().spm_chomp() let xcodeDirectory = try AbsolutePath(validating: xcodeSelectOutput) - xcbuildPath = try AbsolutePath(validating: "../SharedFrameworks/XCBuild.framework/Versions/A/Support/xcbuild", relativeTo: xcodeDirectory) + xcbuildPath = try AbsolutePath( + validating: "../SharedFrameworks/XCBuild.framework/Versions/A/Support/xcbuild", + relativeTo: xcodeDirectory + ) } guard fileSystem.exists(xcbuildPath) else { @@ -104,6 +108,42 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { } } + private func supportedSwiftVersions() throws -> [SwiftLanguageVersion] { + for path in [ + "../../../../../Developer/Library/Xcode/Plug-ins/XCBSpecifications.ideplugin/Contents/Resources/Swift.xcspec", + "../PlugIns/XCBBuildService.bundle/Contents/PlugIns/XCBSpecifications.ideplugin/Contents/Resources/Swift.xcspec", + ] { + let swiftSpecPath = try AbsolutePath(validating: path, relativeTo: xcbuildPath.parentDirectory) + if !fileSystem.exists(swiftSpecPath) { + continue + } + + let swiftSpec = NSArray(contentsOfFile: swiftSpecPath.pathString) + let compilerSpec = swiftSpec?.compactMap { $0 as? NSDictionary }.first { + if let identifier = $0["Identifier"] as? String { + identifier == "com.apple.xcode.tools.swift.compiler" + } else { + false + } + } + let supportedSwiftVersions: [SwiftLanguageVersion] = if let versions = + compilerSpec?["SupportedLanguageVersions"] as? NSArray + { + versions.compactMap { + if let stringValue = $0 as? String { + SwiftLanguageVersion(string: stringValue) + } else { + nil + } + } + } else { + [] + } + return supportedSwiftVersions + } + return [] + } + public func build(subset: BuildSubset) throws { guard !buildParameters.shouldSkipBuilding else { return @@ -122,7 +162,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { "--derivedDataPath", buildParameters.dataPath.pathString, "--target", - subset.pifTargetName + subset.pifTargetName, ] let buildParamsFile: AbsolutePath? @@ -155,11 +195,16 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { stderrBuffer.append(contentsOf: bytes) }) - // We need to sanitize the environment we are passing to XCBuild because we could otherwise interfere with its linked dependencies e.g. when we have a custom swift-driver dynamic library in the path. + // We need to sanitize the environment we are passing to XCBuild because we could otherwise interfere with its + // linked dependencies e.g. when we have a custom swift-driver dynamic library in the path. var sanitizedEnvironment = ProcessEnv.vars sanitizedEnvironment["DYLD_LIBRARY_PATH"] = nil - let process = TSCBasic.Process(arguments: arguments, environment: sanitizedEnvironment, outputRedirection: redirection) + let process = TSCBasic.Process( + arguments: arguments, + environment: sanitizedEnvironment, + outputRedirection: redirection + ) try process.launch() let result = try process.waitUntilExit() @@ -199,28 +244,29 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { var settings: [String: String] = [:] // An error with determining the override should not be fatal here. settings["CC"] = try? buildParameters.toolchain.getClangCompiler().pathString - // Always specify the path of the effective Swift compiler, which was determined in the same way as for the native build system. + // Always specify the path of the effective Swift compiler, which was determined in the same way as for the + // native build system. settings["SWIFT_EXEC"] = buildParameters.toolchain.swiftCompilerPath.pathString - settings["LIBRARY_SEARCH_PATHS"] = "$(inherited) \(try buildParameters.toolchain.toolchainLibDir.pathString)" + settings["LIBRARY_SEARCH_PATHS"] = try "$(inherited) \(buildParameters.toolchain.toolchainLibDir.pathString)" settings["OTHER_CFLAGS"] = ( ["$(inherited)"] - + buildParameters.toolchain.extraFlags.cCompilerFlags.map { $0.spm_shellEscaped() } - + buildParameters.flags.cCompilerFlags.map { $0.spm_shellEscaped() } + + buildParameters.toolchain.extraFlags.cCompilerFlags.map { $0.spm_shellEscaped() } + + buildParameters.flags.cCompilerFlags.map { $0.spm_shellEscaped() } ).joined(separator: " ") settings["OTHER_CPLUSPLUSFLAGS"] = ( ["$(inherited)"] - + buildParameters.toolchain.extraFlags.cxxCompilerFlags.map { $0.spm_shellEscaped() } - + buildParameters.flags.cxxCompilerFlags.map { $0.spm_shellEscaped() } + + buildParameters.toolchain.extraFlags.cxxCompilerFlags.map { $0.spm_shellEscaped() } + + buildParameters.flags.cxxCompilerFlags.map { $0.spm_shellEscaped() } ).joined(separator: " ") settings["OTHER_SWIFT_FLAGS"] = ( ["$(inherited)"] - + buildParameters.toolchain.extraFlags.swiftCompilerFlags.map { $0.spm_shellEscaped() } - + buildParameters.flags.swiftCompilerFlags.map { $0.spm_shellEscaped() } + + buildParameters.toolchain.extraFlags.swiftCompilerFlags.map { $0.spm_shellEscaped() } + + buildParameters.flags.swiftCompilerFlags.map { $0.spm_shellEscaped() } ).joined(separator: " ") settings["OTHER_LDFLAGS"] = ( ["$(inherited)"] - + buildParameters.toolchain.extraFlags.linkerFlags.map { $0.spm_shellEscaped() } - + buildParameters.flags.linkerFlags.map { $0.spm_shellEscaped() } + + buildParameters.toolchain.extraFlags.linkerFlags.map { $0.spm_shellEscaped() } + + buildParameters.flags.linkerFlags.map { $0.spm_shellEscaped() } ).joined(separator: " ") // Optionally also set the list of architectures to build for. @@ -243,14 +289,15 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { return file } - public func cancel(deadline: DispatchTime) throws { - } + public func cancel(deadline: DispatchTime) throws {} /// Returns a new instance of `XCBuildDelegate` for a build operation. private func createBuildDelegate() -> XCBuildDelegate { - let progressAnimation: ProgressAnimationProtocol = self.logLevel.isVerbose - ? VerboseProgressAnimation(stream: self.outputStream) - : MultiLinePercentProgressAnimation(stream: self.outputStream, header: "") + let progressAnimation = ProgressAnimation.percent( + stream: self.outputStream, + verbose: self.logLevel.isVerbose, + header: "" + ) let delegate = XCBuildDelegate( buildSystem: self, outputStream: self.outputStream, @@ -264,9 +311,9 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { private func getPIFBuilder() throws -> PIFBuilder { try memoize(to: &pifBuilder) { let graph = try getPackageGraph() - let pifBuilder = PIFBuilder( + let pifBuilder = try PIFBuilder( graph: graph, - parameters: .init(buildParameters), + parameters: .init(buildParameters, supportedSwiftVersions: supportedSwiftVersions()), fileSystem: self.fileSystem, observabilityScope: self.observabilityScope ) @@ -277,7 +324,7 @@ public final class XcodeBuildSystem: SPMBuildCore.BuildSystem { /// Returns the package graph using the graph loader closure. /// /// First access will cache the graph. - public func getPackageGraph() throws -> PackageGraph { + public func getPackageGraph() throws -> ModulesGraph { try memoize(to: &packageGraph) { try packageGraphLoader() } @@ -310,20 +357,22 @@ struct XCBBuildParameters: Encodable { extension BuildConfiguration { public var xcbuildName: String { switch self { - case .debug: return "Debug" - case .release: return "Release" + case .debug: "Debug" + case .release: "Release" } } } extension PIFBuilderParameters { - public init(_ buildParameters: BuildParameters) { + public init(_ buildParameters: BuildParameters, supportedSwiftVersions: [SwiftLanguageVersion]) { self.init( + isPackageAccessModifierSupported: buildParameters.driverParameters.isPackageAccessModifierSupported, enableTestability: buildParameters.testingParameters.enableTestability, shouldCreateDylibForDynamicProducts: buildParameters.shouldCreateDylibForDynamicProducts, toolchainLibDir: (try? buildParameters.toolchain.toolchainLibDir) ?? .root, pkgConfigDirectories: buildParameters.pkgConfigDirectories, - sdkRootPath: buildParameters.toolchain.sdkRootPath + sdkRootPath: buildParameters.toolchain.sdkRootPath, + supportedSwiftVersions: supportedSwiftVersions ) } } @@ -332,19 +381,19 @@ extension BuildSubset { var pifTargetName: String { switch self { case .product(let name): - return PackagePIFProjectBuilder.targetName(for: name) + PackagePIFProjectBuilder.targetName(for: name) case .target(let name): - return name + name case .allExcludingTests: - return PIFBuilder.allExcludingTestsTargetName + PIFBuilder.allExcludingTestsTargetName case .allIncludingTests: - return PIFBuilder.allIncludingTestsTargetName + PIFBuilder.allIncludingTestsTargetName } } } extension Basics.Diagnostic.Severity { var isVerbose: Bool { - return self <= .info + self <= .info } } diff --git a/Sources/swift-bootstrap/CMakeLists.txt b/Sources/swift-bootstrap/CMakeLists.txt index f2c40f4c8a0..92052ac7098 100644 --- a/Sources/swift-bootstrap/CMakeLists.txt +++ b/Sources/swift-bootstrap/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries(swift-bootstrap PRIVATE PackageGraph PackageLoading PackageModel + SwiftDriver TSCBasic TSCUtility XCBuildSupport) diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/main.swift index 7891cd06ade..5b7ca511708 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/main.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Copyright (c) 2022-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -12,14 +12,21 @@ import ArgumentParser import Basics + import Build + import Dispatch + +import DriverSupport + import Foundation import OrderedCollections import PackageGraph import PackageLoading import PackageModel + import SPMBuildCore + import XCBuildSupport import struct TSCBasic.KeyedPair @@ -41,7 +48,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { @Option(name: .customLong("package-path"), help: "Specify the package path to operate on (default current directory). This changes the working directory before any other operation", completion: .directory) - public var packageDirectory: AbsolutePath? + package var packageDirectory: AbsolutePath? /// The custom .build directory, if provided. @Option(name: .customLong("scratch-path"), help: "Specify a custom scratch directory path (default .build)", completion: .directory) @@ -55,7 +62,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { } @Option(name: .shortAndLong, help: "Build with configuration") - public var configuration: BuildConfiguration = .debug + package var configuration: BuildConfiguration = .debug @Option(name: .customLong("Xcc", withSingleDash: true), parsing: .unconditionalSingleValue, @@ -82,36 +89,36 @@ struct SwiftBootstrapBuildTool: ParsableCommand { help: ArgumentHelp( "Pass flag through to the Xcode build system invocations", visibility: .hidden)) - public var xcbuildFlags: [String] = [] + package var xcbuildFlags: [String] = [] @Option(name: .customLong("Xbuild-tools-swiftc", withSingleDash: true), parsing: .unconditionalSingleValue, help: ArgumentHelp("Pass flag to the manifest build invocation", visibility: .hidden)) - public var manifestFlags: [String] = [] + package var manifestFlags: [String] = [] @Option( name: .customLong("arch"), help: ArgumentHelp("Build the package for the these architectures", visibility: .hidden)) - public var architectures: [String] = [] + package var architectures: [String] = [] /// The verbosity of informational output. @Flag(name: .shortAndLong, help: "Increase verbosity to include informational output") - public var verbose: Bool = false + package var verbose: Bool = false /// The verbosity of informational output. @Flag(name: [.long, .customLong("vv")], help: "Increase verbosity to include debug output") - public var veryVerbose: Bool = false + package var veryVerbose: Bool = false /// Whether to use the integrated Swift driver rather than shelling out /// to a separate process. @Flag() - public var useIntegratedSwiftDriver: Bool = false + package var useIntegratedSwiftDriver: Bool = false /// A flag that indicates this build should check whether targets only import /// their explicitly-declared dependencies @Option() - public var explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode = .none + package var explicitTargetDependencyImportCheck: TargetDependencyImportCheckingMode = .none enum TargetDependencyImportCheckingMode: String, Codable, ExpressibleByArgument { case none @@ -120,7 +127,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { /// Disables adding $ORIGIN/@loader_path to the rpath, useful when deploying @Flag(name: .customLong("disable-local-rpath"), help: "Disable adding $ORIGIN/@loader_path to the rpath by default") - public var shouldDisableLocalRpath: Bool = false + package var shouldDisableLocalRpath: Bool = false private var buildSystem: BuildSystemProvider.Kind { #if os(macOS) @@ -132,7 +139,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { #endif } - public var buildFlags: BuildFlags { + package var buildFlags: BuildFlags { BuildFlags( cCompilerFlags: self.cCompilerFlags, cxxCompilerFlags: self.cxxCompilerFlags, @@ -152,9 +159,9 @@ struct SwiftBootstrapBuildTool: ParsableCommand { } } - public init() {} + package init() {} - public func run() throws { + package func run() throws { do { let fileSystem = localFileSystem @@ -287,7 +294,11 @@ struct SwiftBootstrapBuildTool: ParsableCommand { isXcodeBuildSystemEnabled: buildSystem == .xcode, driverParameters: .init( explicitTargetDependencyImportCheckingMode: explicitTargetDependencyImportCheck == .error ? .error : .none, - useIntegratedSwiftDriver: useIntegratedSwiftDriver + useIntegratedSwiftDriver: useIntegratedSwiftDriver, + isPackageAccessModifierSupported: DriverSupport.isPackageNameSupported( + toolchain: targetToolchain, + fileSystem: self.fileSystem + ) ), linkingParameters: .init( shouldDisableLocalRpath: shouldDisableLocalRpath @@ -301,7 +312,6 @@ struct SwiftBootstrapBuildTool: ParsableCommand { let packageGraphLoader = { try self.loadPackageGraph(packagePath: packagePath, manifestLoader: manifestLoader) - } switch buildSystem { @@ -314,6 +324,8 @@ struct SwiftBootstrapBuildTool: ParsableCommand { packageGraphLoader: packageGraphLoader, additionalFileRules: [], pkgConfigDirectories: [], + dependenciesByRootPackageIdentity: [:], + targetsByRootPackageIdentity: [:], outputStream: TSCBasic.stdoutStream, logLevel: logLevel, fileSystem: self.fileSystem, @@ -344,7 +356,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { ) } - func loadPackageGraph(packagePath: AbsolutePath, manifestLoader: ManifestLoader) throws -> PackageGraph { + func loadPackageGraph(packagePath: AbsolutePath, manifestLoader: ManifestLoader) throws -> ModulesGraph { let rootPackageRef = PackageReference(identity: .init(path: packagePath), kind: .root(packagePath)) let rootPackageManifest = try temp_await { self.loadManifest(manifestLoader: manifestLoader, package: rootPackageRef, completion: $0) } @@ -371,7 +383,7 @@ struct SwiftBootstrapBuildTool: ParsableCommand { observabilityScope: observabilityScope ) - return try PackageGraph.load( + return try ModulesGraph.load( root: packageGraphRoot, identityResolver: identityResolver, externalManifests: loadedManifests.reduce(into: OrderedCollections.OrderedDictionary()) { partial, item in @@ -471,7 +483,7 @@ extension BuildConfiguration { } } -#if swift(<5.11) +#if swift(<6.0) extension AbsolutePath: ExpressibleByArgument {} extension BuildConfiguration: ExpressibleByArgument {} #else diff --git a/Sources/swift-build/CMakeLists.txt b/Sources/swift-build/CMakeLists.txt index 4488799b1f5..936298a6fad 100644 --- a/Sources/swift-build/CMakeLists.txt +++ b/Sources/swift-build/CMakeLists.txt @@ -7,9 +7,12 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_executable(swift-build - main.swift) + Entrypoint.swift) target_link_libraries(swift-build PRIVATE Commands) +target_compile_options(swift-build PRIVATE + -parse-as-library) + install(TARGETS swift-build DESTINATION bin) diff --git a/Sources/swift-test/main.swift b/Sources/swift-build/Entrypoint.swift similarity index 83% rename from Sources/swift-test/main.swift rename to Sources/swift-build/Entrypoint.swift index 9d76e0158eb..0dcb53302ec 100644 --- a/Sources/swift-test/main.swift +++ b/Sources/swift-build/Entrypoint.swift @@ -12,4 +12,9 @@ import Commands -SwiftTestTool.main() +@main +struct Entrypoint { + static func main() async { + await SwiftBuildCommand.main() + } +} diff --git a/Sources/swift-experimental-sdk/CMakeLists.txt b/Sources/swift-experimental-sdk/CMakeLists.txt index b7d9cace5f8..edad12be83b 100644 --- a/Sources/swift-experimental-sdk/CMakeLists.txt +++ b/Sources/swift-experimental-sdk/CMakeLists.txt @@ -9,7 +9,7 @@ add_executable(swift-experimental-sdk Entrypoint.swift) target_link_libraries(swift-experimental-sdk PRIVATE - SwiftSDKTool) + SwiftSDKCommand) target_compile_options(swift-experimental-sdk PRIVATE -parse-as-library) diff --git a/Sources/swift-experimental-sdk/Entrypoint.swift b/Sources/swift-experimental-sdk/Entrypoint.swift index 385c109192e..392d07a4105 100644 --- a/Sources/swift-experimental-sdk/Entrypoint.swift +++ b/Sources/swift-experimental-sdk/Entrypoint.swift @@ -10,11 +10,13 @@ // //===----------------------------------------------------------------------===// -import SwiftSDKTool +import SwiftSDKCommand +import Foundation @main struct Entrypoint { static func main() async { - await SwiftSDKTool.main() + fputs("warning: `swift experimental-sdk` command is deprecated and will be removed in a future version of SwiftPM. Use `swift sdk` instead.\n", stderr) + await SwiftSDKCommand.main() } } diff --git a/Sources/swift-package-collection/Entrypoint.swift b/Sources/swift-package-collection/Entrypoint.swift index 2ad651a0f4f..be7f68312ed 100644 --- a/Sources/swift-package-collection/Entrypoint.swift +++ b/Sources/swift-package-collection/Entrypoint.swift @@ -11,12 +11,11 @@ //===----------------------------------------------------------------------===// import Commands -import PackageCollectionsTool +import PackageCollectionsCommand @main struct Entrypoint { static func main() async { - await SwiftPackageCollectionsTool.main() + await PackageCollectionsCommand.main() } } - diff --git a/Sources/swift-package-manager/SwiftPM.swift b/Sources/swift-package-manager/SwiftPM.swift index e441a621873..3bc47218db5 100644 --- a/Sources/swift-package-manager/SwiftPM.swift +++ b/Sources/swift-package-manager/SwiftPM.swift @@ -11,33 +11,49 @@ //===----------------------------------------------------------------------===// import Basics + import Commands -import SwiftSDKTool -import PackageCollectionsTool -import PackageRegistryTool +import Foundation +import SwiftSDKCommand +import PackageCollectionsCommand +import PackageRegistryCommand let firstArg = CommandLine.arguments[0] -let execName = (try? AbsolutePath(validating: firstArg).basenameWithoutExt) ?? +let baseNameWithoutExtension = (try? AbsolutePath(validating: firstArg).basenameWithoutExt) ?? (try? RelativePath(validating: firstArg).basenameWithoutExt) @main struct SwiftPM { static func main() async { + // Workaround a bug in Swift 5.9, where multiple executables with an `async` main entrypoint can't be linked + // into the same test bundle. We're then linking single `swift-package-manager` binary instead and passing + // executable name via `SWIFTPM_EXEC_NAME`. + if baseNameWithoutExtension == "swift-package-manager" { + await main(execName: EnvironmentVariables.process()["SWIFTPM_EXEC_NAME"]) + } else { + await main(execName: baseNameWithoutExtension) + } + } + + private static func main(execName: String?) async { switch execName { case "swift-package": - await SwiftPackageTool.main() + await SwiftPackageCommand.main() case "swift-build": - SwiftBuildTool.main() + await SwiftBuildCommand.main() case "swift-experimental-sdk": - await SwiftSDKTool.main() + fputs("warning: `swift experimental-sdk` command is deprecated and will be removed in a future version of SwiftPM. Use `swift sdk` instead.\n", stderr) + fallthrough + case "swift-sdk": + await SwiftSDKCommand.main() case "swift-test": - SwiftTestTool.main() + await SwiftTestCommand.main() case "swift-run": - SwiftRunTool.main() + await SwiftRunCommand.main() case "swift-package-collection": - await SwiftPackageCollectionsTool.main() + await PackageCollectionsCommand.main() case "swift-package-registry": - await SwiftPackageRegistryTool.main() + await PackageRegistryCommand.main() default: fatalError("swift-package-manager launched with unexpected name: \(execName ?? "(unknown)")") } diff --git a/Sources/swift-package-registry/runner.swift b/Sources/swift-package-registry/runner.swift index 50de66b7f43..654123014ee 100644 --- a/Sources/swift-package-registry/runner.swift +++ b/Sources/swift-package-registry/runner.swift @@ -11,11 +11,11 @@ //===----------------------------------------------------------------------===// import Commands -import PackageRegistryTool +import PackageRegistryCommand @main struct Runner { static func main() async { - await SwiftPackageRegistryTool.main() + await PackageRegistryCommand.main() } } diff --git a/Sources/swift-package/Entrypoint.swift b/Sources/swift-package/Entrypoint.swift index 3498f0a5bd5..d6113fbbaef 100644 --- a/Sources/swift-package/Entrypoint.swift +++ b/Sources/swift-package/Entrypoint.swift @@ -15,6 +15,6 @@ import Commands @main struct Entrypoint { static func main() async { - await SwiftPackageTool.main() + await SwiftPackageCommand.main() } } diff --git a/Sources/swift-run/CMakeLists.txt b/Sources/swift-run/CMakeLists.txt index 26b4ac9e321..9c609f84ea1 100644 --- a/Sources/swift-run/CMakeLists.txt +++ b/Sources/swift-run/CMakeLists.txt @@ -7,9 +7,12 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_executable(swift-run - main.swift) + Entrypoint.swift) target_link_libraries(swift-run PRIVATE Commands) +target_compile_options(swift-run PRIVATE + -parse-as-library) + install(TARGETS swift-run RUNTIME DESTINATION bin) diff --git a/Sources/swift-build/main.swift b/Sources/swift-run/Entrypoint.swift similarity index 83% rename from Sources/swift-build/main.swift rename to Sources/swift-run/Entrypoint.swift index dbf30ff272b..4976cccdb15 100644 --- a/Sources/swift-build/main.swift +++ b/Sources/swift-run/Entrypoint.swift @@ -12,4 +12,9 @@ import Commands -SwiftBuildTool.main() +@main +struct Entrypoint { + static func main() async { + await SwiftRunCommand.main() + } +} diff --git a/Sources/swift-sdk/CMakeLists.txt b/Sources/swift-sdk/CMakeLists.txt new file mode 100644 index 00000000000..ee3be128bae --- /dev/null +++ b/Sources/swift-sdk/CMakeLists.txt @@ -0,0 +1,18 @@ +# This source file is part of the Swift open source project +# +# Copyright (c) 2023 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See http://swift.org/LICENSE.txt for license information +# See http://swift.org/CONTRIBUTORS.txt for Swift project authors + +add_executable(swift-sdk + Entrypoint.swift) +target_link_libraries(swift-sdk PRIVATE + SwiftSDKCommand) + +target_compile_options(swift-sdk PRIVATE + -parse-as-library) + +install(TARGETS swift-sdk + RUNTIME DESTINATION bin) diff --git a/Sources/swift-sdk/Entrypoint.swift b/Sources/swift-sdk/Entrypoint.swift new file mode 100644 index 00000000000..71d84e8443d --- /dev/null +++ b/Sources/swift-sdk/Entrypoint.swift @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftSDKCommand + +@main +struct Entrypoint { + static func main() async { + await SwiftSDKCommand.main() + } +} diff --git a/Sources/swift-test/CMakeLists.txt b/Sources/swift-test/CMakeLists.txt index 896da188ad9..79c910294fd 100644 --- a/Sources/swift-test/CMakeLists.txt +++ b/Sources/swift-test/CMakeLists.txt @@ -7,9 +7,12 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_executable(swift-test - main.swift) + Entrypoint.swift) target_link_libraries(swift-test PRIVATE Commands) +target_compile_options(swift-test PRIVATE + -parse-as-library) + install(TARGETS swift-test RUNTIME DESTINATION bin) diff --git a/Sources/swift-run/main.swift b/Sources/swift-test/Entrypoint.swift similarity index 83% rename from Sources/swift-run/main.swift rename to Sources/swift-test/Entrypoint.swift index f0f3ca216d2..eb1c4d1b37a 100644 --- a/Sources/swift-run/main.swift +++ b/Sources/swift-test/Entrypoint.swift @@ -12,4 +12,9 @@ import Commands -SwiftRunTool.main() +@main +struct Entrypoint { + static func main() async { + await SwiftTestCommand.main() + } +} diff --git a/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift b/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift new file mode 100644 index 00000000000..c19178ef410 --- /dev/null +++ b/Tests/BasicsTests/Graph/AdjacencyMatrixTests.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@testable import Basics +import XCTest + +final class AdjacencyMatrixTests: XCTestCase { + func testEmpty() { + var matrix = AdjacencyMatrix(rows: 0, columns: 0) + XCTAssertEqual(matrix.bitCount, 0) + + matrix = AdjacencyMatrix(rows: 0, columns: 42) + XCTAssertEqual(matrix.bitCount, 0) + + matrix = AdjacencyMatrix(rows: 42, columns: 0) + XCTAssertEqual(matrix.bitCount, 0) + } + + func testBits() { + for count in 1..<10 { + var matrix = AdjacencyMatrix(rows: count, columns: count) + for row in 0.. BuildPlanResult { try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(targetTriple: dest), + buildParameters: mockBuildParameters(triple: dest), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -4075,6 +4043,7 @@ final class BuildPlanTests: XCTestCase { let dep = try result.target(for: "t1").swiftTarget().compileArguments() XCTAssertMatch(dep, [.anySequence, "-DDEP", .anySequence]) + XCTAssertMatch(dep, [.anySequence, "-swift-version", "4", .anySequence]) let cbar = try result.target(for: "cbar").clangTarget().basicArguments(isCXX: false) XCTAssertMatch( @@ -4099,6 +4068,7 @@ final class BuildPlanTests: XCTestCase { bar, [ .anySequence, + "-swift-version", "5", "-DLINUX", "-Isfoo", "-L", "sbar", @@ -4113,20 +4083,23 @@ final class BuildPlanTests: XCTestCase { ) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) + XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) let linkExe = try result.buildProduct(for: "exe").linkArguments() XCTAssertMatch(linkExe, [.anySequence, "-lsqlite3", "-llibz", "-Ilfoo", "-L", "lbar", "-g", .end]) let testDiscovery = try result.target(for: "APackageDiscoveredTests").swiftTarget().compileArguments() XCTAssertMatch(testDiscovery, [.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++17"]) + + let testEntryPoint = try result.target(for: "APackageTests").swiftTarget().compileArguments() + XCTAssertMatch(testEntryPoint, [.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++17"]) } // omit frame pointers explicitly set to true do { let result = try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters( - targetTriple: .x86_64Linux, + triple: .x86_64Linux, omitFramePointers: true ), graph: graph, @@ -4160,6 +4133,7 @@ final class BuildPlanTests: XCTestCase { bar, [ .anySequence, + "-swift-version", "5", "-DLINUX", "-Isfoo", "-L", "sbar", @@ -4175,14 +4149,14 @@ final class BuildPlanTests: XCTestCase { ) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fomit-frame-pointer", .end]) + XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fomit-frame-pointer", .end]) } // omit frame pointers explicitly set to false do { let result = try BuildPlanResult(plan: BuildPlan( buildParameters: mockBuildParameters( - targetTriple: .x86_64Linux, + triple: .x86_64Linux, omitFramePointers: false ), graph: graph, @@ -4216,6 +4190,7 @@ final class BuildPlanTests: XCTestCase { bar, [ .anySequence, + "-swift-version", "5", "-DLINUX", "-Isfoo", "-L", "sbar", @@ -4231,7 +4206,7 @@ final class BuildPlanTests: XCTestCase { ) let exe = try result.target(for: "exe").swiftTarget().compileArguments() - XCTAssertMatch(exe, [.anySequence, "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) + XCTAssertMatch(exe, [.anySequence, "-swift-version", "5", "-DFOO", "-g", "-Xcc", "-g", "-Xcc", "-fno-omit-frame-pointer", .end]) } do { @@ -4259,6 +4234,7 @@ final class BuildPlanTests: XCTestCase { bar, [ .anySequence, + "-swift-version", "5", "-DDMACOS", "-Isfoo", "-L", "sbar", @@ -4277,6 +4253,7 @@ final class BuildPlanTests: XCTestCase { exe, [ .anySequence, + "-swift-version", "4", "-DFOO", "-cxx-interoperability-mode=default", "-Xcc", "-std=c++17", @@ -4320,7 +4297,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [aManifest], observabilityScope: observability.topScope @@ -4359,7 +4336,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -4493,7 +4470,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -4543,7 +4520,7 @@ final class BuildPlanTests: XCTestCase { swiftCompilerFlags: [cliFlag(tool: .swiftCompiler)], linkerFlags: [cliFlag(tool: .linker)] ), - targetTriple: targetTriple + triple: targetTriple ) let result = try BuildPlanResult(plan: BuildPlan( buildParameters: buildParameters, @@ -4660,7 +4637,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -4728,7 +4705,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -4812,7 +4789,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -4828,8 +4805,9 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) + let buildParameters = mockBuildParameters() let plan = try BuildPlan( - buildParameters: mockBuildParameters(), + buildParameters: buildParameters, graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -4845,7 +4823,7 @@ final class BuildPlanTests: XCTestCase { [ .anySequence, "-emit-objc-header", - "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", + "-emit-objc-header-path", "/path/to/build/\(buildParameters.triple)/debug/Foo.build/Foo-Swift.h", .anySequence, ] ) @@ -4855,7 +4833,7 @@ final class BuildPlanTests: XCTestCase { [ .anySequence, "-emit-objc-header", - "-emit-objc-header-path", "/path/to/build/debug/Foo.build/Foo-Swift.h", + "-emit-objc-header-path", "/path/to/build/\(buildParameters.triple)/Foo.build/Foo-Swift.h", .anySequence, ] ) @@ -4865,12 +4843,12 @@ final class BuildPlanTests: XCTestCase { #if os(macOS) XCTAssertMatch( barTarget, - [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence] + [.anySequence, "-fmodule-map-file=/path/to/build/\(buildParameters.triple)/debug/Foo.build/module.modulemap", .anySequence] ) #else XCTAssertNoMatch( barTarget, - [.anySequence, "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", .anySequence] + [.anySequence, "-fmodule-map-file=/path/to/build/\(buildParameters.triple)/debug/Foo.build/module.modulemap", .anySequence] ) #endif @@ -4900,7 +4878,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -4928,8 +4906,9 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) + let buildParameters = mockBuildParameters() let plan = try BuildPlan( - buildParameters: mockBuildParameters(), + buildParameters: buildParameters, graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -4946,7 +4925,7 @@ final class BuildPlanTests: XCTestCase { .anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Foo.build/Foo-Swift.h", + "/path/to/build/\(buildParameters.triple)/debug/Foo.build/Foo-Swift.h", .anySequence, ] ) @@ -4957,7 +4936,7 @@ final class BuildPlanTests: XCTestCase { .anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Foo.build/Foo-Swift.h", + "/path/to/build/\(buildParameters.triple)/debug/Foo.build/Foo-Swift.h", .anySequence, ] ) @@ -4969,7 +4948,7 @@ final class BuildPlanTests: XCTestCase { barTarget, [ .anySequence, - "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", + "-fmodule-map-file=/path/to/build/\(buildParameters.triple)/debug/Foo.build/module.modulemap", .anySequence, ] ) @@ -4978,7 +4957,7 @@ final class BuildPlanTests: XCTestCase { barTarget, [ .anySequence, - "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", + "-fmodule-map-file=/path/to/build/\(buildParameters.triple)/debug/Foo.build/module.modulemap", .anySequence, ] ) @@ -5010,7 +4989,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5038,8 +5017,9 @@ final class BuildPlanTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) + let buildParameters = mockBuildParameters() let plan = try BuildPlan( - buildParameters: mockBuildParameters(), + buildParameters: buildParameters, graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5060,7 +5040,7 @@ final class BuildPlanTests: XCTestCase { .anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Foo.build/Foo-Swift.h", + "/path/to/build/\(buildParameters.triple)/debug/Foo.build/Foo-Swift.h", .anySequence, ] ) @@ -5071,7 +5051,7 @@ final class BuildPlanTests: XCTestCase { .anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Foo.build/Foo-Swift.h", + "/path/to/build/\(buildParameters.triple)/debug/Foo.build/Foo-Swift.h", .anySequence, ] ) @@ -5083,7 +5063,7 @@ final class BuildPlanTests: XCTestCase { barTarget, [ .anySequence, - "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", + "-fmodule-map-file=/path/to/build/\(buildParameters.triple)/debug/Foo.build/module.modulemap", .anySequence, ] ) @@ -5092,7 +5072,7 @@ final class BuildPlanTests: XCTestCase { barTarget, [ .anySequence, - "-fmodule-map-file=/path/to/build/debug/Foo.build/module.modulemap", + "-fmodule-map-file=/path/to/build/\(buildParameters.triple)/debug/Foo.build/module.modulemap", .anySequence, ] ) @@ -5125,7 +5105,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5142,7 +5122,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(targetTriple: .x86_64Linux), + buildParameters: mockBuildParameters(triple: .x86_64Linux), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5208,7 +5188,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5351,7 +5331,7 @@ final class BuildPlanTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5419,7 +5399,7 @@ final class BuildPlanTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5446,7 +5426,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let plan = try BuildPlan( - buildParameters: mockBuildParameters(targetTriple: .wasi), + buildParameters: mockBuildParameters(triple: .wasi), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5487,7 +5467,7 @@ final class BuildPlanTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5561,7 +5541,7 @@ final class BuildPlanTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5579,7 +5559,7 @@ final class BuildPlanTests: XCTestCase { let supportingTriples: [Basics.Triple] = [.x86_64Linux, .arm64Linux, .wasi] for triple in supportingTriples { let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true, targetTriple: triple), + buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true, triple: triple), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5673,7 +5653,7 @@ final class BuildPlanTests: XCTestCase { let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5704,7 +5684,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(targetTriple: targetTriple), + buildParameters: mockBuildParameters(triple: targetTriple), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5808,7 +5788,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5833,7 +5813,7 @@ final class BuildPlanTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let result = try BuildPlanResult(plan: BuildPlan( - buildParameters: mockBuildParameters(targetTriple: targetTriple), + buildParameters: mockBuildParameters(triple: targetTriple), graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -5896,7 +5876,7 @@ final class BuildPlanTests: XCTestCase { ) let buildPath = AbsolutePath("/Pkg/.build") let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -5952,7 +5932,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -6006,7 +5986,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -6063,7 +6043,7 @@ final class BuildPlanTests: XCTestCase { let fs = InMemoryFileSystem(emptyFiles: "/Pkg/Sources/exe/main.swift", "/ExtPkg/Sources/ExtLib/best.swift") let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -6106,7 +6086,13 @@ final class BuildPlanTests: XCTestCase { observabilityScope: observability.topScope )) - switch try XCTUnwrap(result.targetMap["ExtLib"]) { + switch try XCTUnwrap( + result.targetMap[.init( + targetName: "ExtLib", + packageIdentity: "ExtPkg", + buildTriple: .destination + )] + ) { case .swift(let swiftTarget): if #available(macOS 13, *) { // `.contains` is only available in macOS 13 or newer XCTAssertTrue(try swiftTarget.compileArguments().contains(["-user-module-version", "1.0.0"])) @@ -6124,7 +6110,7 @@ final class BuildPlanTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -6158,6 +6144,7 @@ final class BuildPlanTests: XCTestCase { "-L", buildPath.pathString, "-o", buildPath.appending(components: "exe").pathString, "-module-name", "exe", + "-Xlinker", "-no_warn_duplicate_libraries", "-emit-executable", "@\(buildPath.appending(components: "exe.product", "Objects.LinkFileList"))", "-Xlinker", "-rpath", "-Xlinker", "/fake/path/lib/swift-5.5/macosx", @@ -6205,7 +6192,7 @@ final class BuildPlanTests: XCTestCase { "/barPkg/Sources/BarLogging/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -6263,8 +6250,169 @@ final class BuildPlanTests: XCTestCase { result.checkTargetsCount(3) XCTAssertTrue(result.targetMap.values.contains { $0.target.name == "FooLogging" }) XCTAssertTrue(result.targetMap.values.contains { $0.target.name == "BarLogging" }) - let buildProduct = try XCTUnwrap(result.productMap["exe"]) + let buildProduct = try XCTUnwrap( + result.productMap[.init( + productName: "exe", + packageIdentity: "thisPkg", + buildTriple: .destination + )] + ) let dylibs = Array(buildProduct.dylibs.map({$0.product.name})).sorted() XCTAssertEqual(dylibs, ["BarLogging", "FooLogging"]) } + + func testSwiftPackageWithProvidedLibraries() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/A/Sources/ATarget/main.swift", + "/Libraries/B/BTarget.swiftmodule", + "/Libraries/C/CTarget.swiftmodule" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "A", + path: "/A", + dependencies: [ + .localSourceControl(path: "/B", requirement: .upToNextMajor(from: "1.0.0")), + .localSourceControl(path: "/C", requirement: .upToNextMajor(from: "1.0.0")), + ], + products: [ + ProductDescription( + name: "A", + type: .executable, + targets: ["ATarget"] + ) + ], + targets: [ + TargetDescription(name: "ATarget", dependencies: ["BLibrary", "CLibrary"]) + ] + ), + Manifest.createFileSystemManifest( + displayName: "B", + path: "/B", + products: [ + ProductDescription(name: "BLibrary", type: .library(.automatic), targets: ["BTarget"]), + ], + targets: [ + TargetDescription( + name: "BTarget", + path: "/Libraries/B", + type: .providedLibrary + ) + ] + ), + Manifest.createFileSystemManifest( + displayName: "C", + path: "/C", + products: [ + ProductDescription(name: "CLibrary", type: .library(.automatic), targets: ["CTarget"]), + ], + targets: [ + TargetDescription( + name: "CTarget", + path: "/Libraries/C", + type: .providedLibrary + ) + ] + ), + ], + observabilityScope: observability.topScope + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + + let plan = try BuildPlan( + buildParameters: mockBuildParameters(), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + ) + let result = try BuildPlanResult(plan: plan) + + result.checkProductsCount(1) + result.checkTargetsCount(1) + + XCTAssertMatch( + try result.target(for: "ATarget").swiftTarget().compileArguments(), + [ + .anySequence, + "-I", "/Libraries/C", + "-I", "/Libraries/B", + .anySequence + ] + ) + + let linkerArgs = try result.buildProduct(for: "A").linkArguments() + + XCTAssertMatch( + linkerArgs, + [ + .anySequence, + "-L", "/Libraries/B", + "-l", "BTarget", + .anySequence + ] + ) + + XCTAssertMatch( + linkerArgs, + [ + .anySequence, + "-L", "/Libraries/C", + "-l", "CTarget", + .anySequence + ] + ) + } + + func testDefaultVersions() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Pkg/Sources/foo/foo.swift" + ) + + let expectedVersions = [ + ToolsVersion.v4: "4", + ToolsVersion.v4_2: "4.2", + ToolsVersion.v5: "5", + ToolsVersion.v6_0: "6", + ToolsVersion.vNext: "6" + ] + for (toolsVersion, expectedVersionString) in expectedVersions { + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + toolsVersion: toolsVersion, + targets: [ + TargetDescription( + name: "foo" + ), + ] + ), + ], + observabilityScope: observability.topScope + ) + + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: mockBuildParameters(), + graph: graph, + fileSystem: fs, + observabilityScope: observability.topScope + )) + + XCTAssertMatch( + try result.target(for: "foo").swiftTarget().compileArguments(), + [ + "-swift-version", .equal(expectedVersionString) + ] + ) + } + } } diff --git a/Tests/BuildTests/BuildSystemDelegateTests.swift b/Tests/BuildTests/BuildSystemDelegateTests.swift index 2251dde6d44..d01f4935d06 100644 --- a/Tests/BuildTests/BuildSystemDelegateTests.swift +++ b/Tests/BuildTests/BuildSystemDelegateTests.swift @@ -17,7 +17,8 @@ import XCTest import var TSCBasic.localFileSystem final class BuildSystemDelegateTests: XCTestCase { - func testDoNotFilterLinkerDiagnostics() throws { + func testDoNotFilterLinkerDiagnostics() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() try XCTSkipIf(!UserToolchain.default.supportsSDKDependentTests(), "skipping because test environment doesn't support this test") try fixture(name: "Miscellaneous/DoNotFilterLinkerDiagnostics") { fixturePath in #if !os(macOS) @@ -25,7 +26,7 @@ final class BuildSystemDelegateTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let (fullLog, _) = try executeSwiftBuild(fixturePath) - XCTAssertTrue(fullLog.contains("ld: warning: ignoring duplicate libraries: '-lz'"), "log didn't contain expected linker diagnostics") + XCTAssertTrue(fullLog.contains("ld: warning: search path 'foobar' not found"), "log didn't contain expected linker diagnostics") } } diff --git a/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift b/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift index cc052cfe146..32b24c92fc4 100644 --- a/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift +++ b/Tests/BuildTests/ClangTargetBuildDescriptionTests.swift @@ -14,13 +14,41 @@ import Basics @testable import Build import PackageGraph import PackageModel +import SPMBuildCore import SPMTestSupport import XCTest final class ClangTargetBuildDescriptionTests: XCTestCase { func testClangIndexStorePath() throws { - let targetDescription = try makeTargetBuildDescription() + let targetDescription = try makeTargetBuildDescription("test") XCTAssertTrue(try targetDescription.basicArguments().contains("-index-store-path")) + XCTAssertFalse(try targetDescription.basicArguments().contains("-w")) + } + + func testSwiftCorelibsFoundationIncludeWorkaround() throws { + let toolchain = MockToolchain(swiftResourcesPath: AbsolutePath("/fake/path/lib/swift")) + + let macosParameters = mockBuildParameters(toolchain: toolchain, triple: .macOS) + let linuxParameters = mockBuildParameters(toolchain: toolchain, triple: .arm64Linux) + let androidParameters = mockBuildParameters(toolchain: toolchain, triple: .arm64Android) + + let macDescription = try makeTargetBuildDescription("swift-corelibs-foundation", + buildParameters: macosParameters) + XCTAssertFalse(try macDescription.basicArguments().contains("\(macosParameters.toolchain.swiftResourcesPath!)")) + + let linuxDescription = try makeTargetBuildDescription("swift-corelibs-foundation", + buildParameters: linuxParameters) + print(try linuxDescription.basicArguments()) + XCTAssertTrue(try linuxDescription.basicArguments().contains("\(linuxParameters.toolchain.swiftResourcesPath!)")) + + let androidDescription = try makeTargetBuildDescription("swift-corelibs-foundation", + buildParameters: androidParameters) + XCTAssertTrue(try androidDescription.basicArguments().contains("\(androidParameters.toolchain.swiftResourcesPath!)")) + } + + func testWarningSuppressionForRemotePackages() throws { + let targetDescription = try makeTargetBuildDescription("test-warning-supression", usesSourceControl: true) + XCTAssertTrue(try targetDescription.basicArguments().contains("-w")) } private func makeClangTarget() throws -> ClangTarget { @@ -37,8 +65,8 @@ final class ClangTargetBuildDescriptionTests: XCTestCase { ) } - private func makeResolvedTarget() throws -> ResolvedTarget { - ResolvedTarget( + private func makeResolvedTarget() throws -> ResolvedModule { + ResolvedModule( packageIdentity: .plain("dummy"), underlying: try makeClangTarget(), dependencies: [], @@ -47,12 +75,44 @@ final class ClangTargetBuildDescriptionTests: XCTestCase { ) } - private func makeTargetBuildDescription() throws -> ClangTargetBuildDescription { + private func makeTargetBuildDescription(_ packageName: String, + buildParameters: BuildParameters? = nil, + usesSourceControl: Bool = false) throws -> ClangTargetBuildDescription { let observability = ObservabilitySystem.makeForTesting(verbose: false) + + let manifest: Manifest + if usesSourceControl { + manifest = Manifest.createLocalSourceControlManifest( + displayName: packageName, path: AbsolutePath("/\(packageName)")) + } else { + manifest = Manifest.createRootManifest( + displayName: packageName, + toolsVersion: .v5, + targets: [try TargetDescription(name: "dummy")]) + } + + let target = try makeResolvedTarget() + + let package = Package(identity: .plain(packageName), + manifest: manifest, + path: .root, + targets: [target.underlying], + products: [], + targetSearchPath: .root, + testTargetSearchPath: .root) + return try ClangTargetBuildDescription( - target: try makeResolvedTarget(), + package: .init(underlying: package, + defaultLocalization: nil, + supportedPlatforms: [], + dependencies: [], + targets: .init([target]), + products: [], + registryMetadata: nil, + platformVersionProvider: .init(implementation: .minimumDeploymentTargetDefault)), + target: target, toolsVersion: .current, - buildParameters: mockBuildParameters( + buildParameters: buildParameters ?? mockBuildParameters( toolchain: try UserToolchain.default, indexStoreMode: .on ), diff --git a/Tests/BuildTests/CrossCompilationBuildPlanTests.swift b/Tests/BuildTests/CrossCompilationBuildPlanTests.swift new file mode 100644 index 00000000000..34ad1c16269 --- /dev/null +++ b/Tests/BuildTests/CrossCompilationBuildPlanTests.swift @@ -0,0 +1,384 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import struct Basics.AbsolutePath +import class Basics.ObservabilitySystem +import class Build.BuildPlan +import class Build.ProductBuildDescription +import enum Build.TargetBuildDescription +import class Build.SwiftTargetBuildDescription +import struct Basics.Triple +import enum PackageGraph.BuildTriple +import class PackageModel.Manifest +import struct PackageModel.TargetDescription +import enum PackageModel.ProductType +import func SPMTestSupport.loadPackageGraph + +import func SPMTestSupport.embeddedCxxInteropPackageGraph +import func SPMTestSupport.macrosPackageGraph +import func SPMTestSupport.macrosTestsPackageGraph +import func SPMTestSupport.mockBuildParameters +import func SPMTestSupport.toolsExplicitLibrariesGraph +import func SPMTestSupport.trivialPackageGraph + +import struct SPMTestSupport.BuildPlanResult +import func SPMTestSupport.XCTAssertMatch +import func SPMTestSupport.XCTAssertNoDiagnostics +import class TSCBasic.InMemoryFileSystem + +import XCTest + +final class CrossCompilationBuildPlanTests: XCTestCase { + func testEmbeddedWasmTarget() throws { + var (graph, fs, observabilityScope) = try trivialPackageGraph() + + let triple = try Triple("wasm32-unknown-none-wasm") + var parameters = mockBuildParameters(triple: triple) + parameters.linkingParameters.shouldLinkStaticSwiftStdlib = true + var result = try BuildPlanResult(plan: BuildPlan( + buildParameters: parameters, + graph: graph, + fileSystem: fs, + observabilityScope: observabilityScope + )) + result.checkProductsCount(2) + // There are two additional targets on non-Apple platforms, for test discovery and + // test entry point + result.checkTargetsCount(5) + + let buildPath = result.plan.productsBuildPath + var appBuildDescription = try result.buildProduct(for: "app") + XCTAssertEqual( + try appBuildDescription.linkArguments(), + [ + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "app.wasm").pathString, + "-module-name", "app", "-static-stdlib", "-emit-executable", + "@\(buildPath.appending(components: "app.product", "Objects.LinkFileList"))", + "-target", triple.tripleString, + "-g", + ] + ) + + (graph, fs, observabilityScope) = try embeddedCxxInteropPackageGraph() + + result = try BuildPlanResult(plan: BuildPlan( + buildParameters: parameters, + graph: graph, + fileSystem: fs, + observabilityScope: observabilityScope + )) + result.checkProductsCount(2) + // There are two additional targets on non-Apple platforms, for test discovery and + // test entry point + result.checkTargetsCount(5) + + appBuildDescription = try result.buildProduct(for: "app") + XCTAssertEqual( + try appBuildDescription.linkArguments(), + [ + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "app.wasm").pathString, + "-module-name", "app", "-static-stdlib", "-emit-executable", + "@\(buildPath.appending(components: "app.product", "Objects.LinkFileList"))", + "-enable-experimental-feature", "Embedded", + "-target", triple.tripleString, + "-g", + ] + ) + } + + func testWasmTargetRelease() throws { + let (graph, fs, observabilityScope) = try trivialPackageGraph() + + var parameters = mockBuildParameters( + config: .release, triple: .wasi, linkerDeadStrip: true + ) + parameters.linkingParameters.shouldLinkStaticSwiftStdlib = true + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: parameters, + graph: graph, + fileSystem: fs, + observabilityScope: observabilityScope + )) + let buildPath = result.plan.productsBuildPath + + let appBuildDescription = try result.buildProduct(for: "app") + XCTAssertEqual( + try appBuildDescription.linkArguments(), + [ + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "app.wasm").pathString, + "-module-name", "app", "-static-stdlib", "-emit-executable", + "-Xlinker", "--gc-sections", + "@\(buildPath.appending(components: "app.product", "Objects.LinkFileList"))", + "-target", "wasm32-unknown-wasi", + "-g", + ] + ) + } + + func testWASITarget() throws { + let pkgPath = AbsolutePath("/Pkg") + + let (graph, fs, observabilityScope) = try trivialPackageGraph() + + var parameters = mockBuildParameters(triple: .wasi) + parameters.linkingParameters.shouldLinkStaticSwiftStdlib = true + let result = try BuildPlanResult(plan: BuildPlan( + buildParameters: parameters, + graph: graph, + fileSystem: fs, + observabilityScope: observabilityScope + )) + result.checkProductsCount(2) + // There are two additional targets on non-Apple platforms, for test discovery and + // test entry point + result.checkTargetsCount(5) + + let buildPath = result.plan.productsBuildPath + + let lib = try XCTUnwrap( + result.allTargets(named: "lib") + .map { try $0.clangTarget() } + .first { $0.target.buildTriple == .destination } + ) + + XCTAssertEqual(try lib.basicArguments(isCXX: false), [ + "-target", "wasm32-unknown-wasi", + "-O0", "-DSWIFT_PACKAGE=1", "-DDEBUG=1", + "-fblocks", + "-I", pkgPath.appending(components: "Sources", "lib", "include").pathString, + "-g", + ]) + XCTAssertEqual(try lib.objects, [buildPath.appending(components: "lib.build", "lib.c.o")]) + XCTAssertEqual(lib.moduleMap, buildPath.appending(components: "lib.build", "module.modulemap")) + + let exe = try result.target(for: "app").swiftTarget().compileArguments() + XCTAssertMatch( + exe, + [ + "-enable-batch-mode", "-Onone", "-enable-testing", + "-j3", "-DSWIFT_PACKAGE", "-DDEBUG", "-Xcc", + "-fmodule-map-file=\(buildPath.appending(components: "lib.build", "module.modulemap"))", + "-Xcc", "-I", "-Xcc", "\(pkgPath.appending(components: "Sources", "lib", "include"))", + "-module-cache-path", "\(buildPath.appending(components: "ModuleCache"))", .anySequence, + "-swift-version", "4", "-g", .anySequence, + ] + ) + + let appBuildDescription = try result.buildProduct(for: "app") + XCTAssertEqual( + try appBuildDescription.linkArguments(), + [ + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "app.wasm").pathString, + "-module-name", "app", "-static-stdlib", "-emit-executable", + "@\(buildPath.appending(components: "app.product", "Objects.LinkFileList"))", + "-target", "wasm32-unknown-wasi", + "-g", + ] + ) + + let executablePathExtension = try appBuildDescription.binaryPath.extension + XCTAssertEqual(executablePathExtension, "wasm") + + let testBuildDescription = try result.buildProduct(for: "PkgPackageTests") + XCTAssertEqual( + try testBuildDescription.linkArguments(), + [ + result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString, + "-L", buildPath.pathString, + "-o", buildPath.appending(components: "PkgPackageTests.wasm").pathString, + "-module-name", "PkgPackageTests", + "-emit-executable", + "@\(buildPath.appending(components: "PkgPackageTests.product", "Objects.LinkFileList"))", + "-target", "wasm32-unknown-wasi", + "-g", + ] + ) + + let testPathExtension = try testBuildDescription.binaryPath.extension + XCTAssertEqual(testPathExtension, "wasm") + } + + func testMacros() throws { + let (graph, fs, scope) = try macrosPackageGraph() + + let destinationTriple = Triple.arm64Linux + let toolsTriple = Triple.x86_64MacOS + let plan = try BuildPlan( + destinationBuildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true, triple: destinationTriple), + toolsBuildParameters: mockBuildParameters(triple: toolsTriple), + graph: graph, + fileSystem: fs, + observabilityScope: scope + ) + let result = try BuildPlanResult(plan: plan) + result.checkProductsCount(3) + result.checkTargetsCount(10) + + XCTAssertTrue(try result.allTargets(named: "SwiftSyntax") + .map { try $0.swiftTarget() } + .contains { $0.target.buildTriple == .tools }) + try result.check(buildTriple: .tools, triple: toolsTriple, for: "MMIOMacros") + try result.check(buildTriple: .destination, triple: destinationTriple, for: "MMIO") + try result.check(buildTriple: .destination, triple: destinationTriple, for: "Core") + try result.check(buildTriple: .destination, triple: destinationTriple, for: "HAL") + + let macroProducts = result.allProducts(named: "MMIOMacros") + XCTAssertEqual(macroProducts.count, 1) + let macroProduct = try XCTUnwrap(macroProducts.first) + XCTAssertEqual(macroProduct.buildParameters.triple, toolsTriple) + + let mmioTargets = try result.allTargets(named: "MMIO").map { try $0.swiftTarget() } + XCTAssertEqual(mmioTargets.count, 1) + let mmioTarget = try XCTUnwrap(mmioTargets.first) + let compileArguments = try mmioTarget.emitCommandLine() + XCTAssertMatch( + compileArguments, + [ + "-I", .equal(mmioTarget.moduleOutputPath.parentDirectory.pathString), + .anySequence, + "-Xfrontend", "-load-plugin-executable", + // Verify that macros are located in the tools triple directory. + "-Xfrontend", .contains(toolsTriple.tripleString) + ] + ) + } + + func testMacrosTests() throws { + let (graph, fs, scope) = try macrosTestsPackageGraph() + + let destinationTriple = Triple.arm64Linux + let toolsTriple = Triple.x86_64MacOS + let plan = try BuildPlan( + destinationBuildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true, triple: destinationTriple), + toolsBuildParameters: mockBuildParameters(triple: toolsTriple), + graph: graph, + fileSystem: fs, + observabilityScope: scope + ) + let result = try BuildPlanResult(plan: plan) + result.checkProductsCount(2) + result.checkTargetsCount(15) + + XCTAssertTrue(try result.allTargets(named: "SwiftSyntax") + .map { try $0.swiftTarget() } + .contains { $0.target.buildTriple == .tools }) + + try result.check(buildTriple: .tools, triple: toolsTriple, for: "swift-mmioPackageTests") + try result.check(buildTriple: .tools, triple: toolsTriple, for: "swift-mmioPackageDiscoveredTests") + try result.check(buildTriple: .tools, triple: toolsTriple, for: "MMIOMacros") + try result.check(buildTriple: .destination, triple: destinationTriple, for: "MMIO") + try result.check(buildTriple: .tools, triple: toolsTriple, for: "MMIOMacrosTests") + + let macroProducts = result.allProducts(named: "MMIOMacros") + XCTAssertEqual(macroProducts.count, 1) + let macroProduct = try XCTUnwrap(macroProducts.first) + XCTAssertEqual(macroProduct.buildParameters.triple, toolsTriple) + + let mmioTargets = try result.allTargets(named: "MMIO").map { try $0.swiftTarget() } + XCTAssertEqual(mmioTargets.count, 1) + let mmioTarget = try XCTUnwrap(mmioTargets.first) + let compileArguments = try mmioTarget.emitCommandLine() + XCTAssertMatch( + compileArguments, + [ + "-I", .equal(mmioTarget.moduleOutputPath.parentDirectory.pathString), + .anySequence, + "-Xfrontend", "-load-plugin-executable", + // Verify that macros are located in the tools triple directory. + "-Xfrontend", .contains(toolsTriple.tripleString) + ] + ) + } + + func testToolsExplicitLibraries() throws { + let destinationTriple = Triple.arm64Linux + let toolsTriple = Triple.x86_64MacOS + + for (linkage, productFileName) in [(ProductType.LibraryType.static, "libSwiftSyntax-tool.a"), (.dynamic, "libSwiftSyntax-tool.dylib")] { + let (graph, fs, scope) = try toolsExplicitLibrariesGraph(linkage: linkage) + let plan = try BuildPlan( + destinationBuildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true, triple: destinationTriple), + toolsBuildParameters: mockBuildParameters(triple: toolsTriple), + graph: graph, + fileSystem: fs, + observabilityScope: scope + ) + let result = try BuildPlanResult(plan: plan) + result.checkProductsCount(4) + result.checkTargetsCount(6) + + XCTAssertTrue(try result.allTargets(named: "SwiftSyntax") + .map { try $0.swiftTarget() } + .contains { $0.target.buildTriple == .tools }) + + try result.check(buildTriple: .tools, triple: toolsTriple, for: "swift-mmioPackageTests") + try result.check(buildTriple: .tools, triple: toolsTriple, for: "swift-mmioPackageDiscoveredTests") + try result.check(buildTriple: .tools, triple: toolsTriple, for: "MMIOMacros") + try result.check(buildTriple: .tools, triple: toolsTriple, for: "MMIOMacrosTests") + + let macroProducts = result.allProducts(named: "MMIOMacros") + XCTAssertEqual(macroProducts.count, 1) + let macroProduct = try XCTUnwrap(macroProducts.first) + XCTAssertEqual(macroProduct.buildParameters.triple, toolsTriple) + + let swiftSyntaxProducts = result.allProducts(named: "SwiftSyntax") + XCTAssertEqual(swiftSyntaxProducts.count, 2) + let swiftSyntaxToolsProduct = try XCTUnwrap(swiftSyntaxProducts.first { $0.product.buildTriple == .tools }) + let archiveArguments = try swiftSyntaxToolsProduct.archiveArguments() + + // Verify that produced library file has a correct name + XCTAssertMatch(archiveArguments, [.contains(productFileName)]) + } + } +} + +extension BuildPlanResult { + func allTargets(named name: String) throws -> some Collection { + self.targetMap + .filter { $0.0.targetName == name } + .values + } + + func allProducts(named name: String) -> some Collection { + self.productMap + .filter { $0.0.productName == name } + .values + } + + func check( + buildTriple: BuildTriple, + triple: Triple, + for target: String, + file: StaticString = #file, + line: UInt = #line + ) throws { + let targets = self.targetMap.filter { + $0.key.targetName == target && $0.key.buildTriple == buildTriple + } + XCTAssertEqual(targets.count, 1, file: file, line: line) + + let target = try XCTUnwrap( + targets.first?.value, + file: file, + line: line + ).swiftTarget() + XCTAssertMatch(try target.emitCommandLine(), [.contains(triple.tripleString)], file: file, line: line) + } +} diff --git a/Tests/BuildTests/LLBuildManifestBuilderTests.swift b/Tests/BuildTests/LLBuildManifestBuilderTests.swift index d577e577c7a..5ba597003d1 100644 --- a/Tests/BuildTests/LLBuildManifestBuilderTests.swift +++ b/Tests/BuildTests/LLBuildManifestBuilderTests.swift @@ -13,10 +13,15 @@ import Basics @testable import Build import LLBuildManifest + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph + import PackageModel import struct SPMBuildCore.BuildParameters + import SPMTestSupport + import class TSCBasic.InMemoryFileSystem import XCTest @@ -29,7 +34,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -50,7 +55,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { configuration: .release )) var plan = try BuildPlan( - productsBuildParameters: buildParameters, + destinationBuildParameters: buildParameters, toolsBuildParameters: buildParameters, graph: graph, fileSystem: fs, @@ -67,8 +72,8 @@ final class LLBuildManifestBuilderTests: XCTestCase { ) try llbuild.createProductCommand(buildProduct) - let basicReleaseCommandNames = [ - AbsolutePath("/path/to/build/release/exe.product/Objects.LinkFileList").pathString, + var basicReleaseCommandNames = [ + AbsolutePath("/path/to/build/\(buildParameters.triple)/release/exe.product/Objects.LinkFileList").pathString, "", "C.exe-release.exe", ] @@ -85,7 +90,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { configuration: .debug )) plan = try BuildPlan( - productsBuildParameters: buildParameters, + destinationBuildParameters: buildParameters, toolsBuildParameters: buildParameters, graph: graph, fileSystem: fs, @@ -95,12 +100,12 @@ final class LLBuildManifestBuilderTests: XCTestCase { result = try BuildPlanResult(plan: plan) buildProduct = try result.buildProduct(for: "exe") - llbuild = LLBuildManifestBuilder(plan, fileSystem: localFileSystem, observabilityScope: observability.topScope) + llbuild = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: observability.topScope) try llbuild.createProductCommand(buildProduct) let entitlementsCommandName = "C.exe-debug.exe-entitlements" - let basicDebugCommandNames = [ - AbsolutePath("/path/to/build/debug/exe.product/Objects.LinkFileList").pathString, + var basicDebugCommandNames = [ + AbsolutePath("/path/to/build/\(buildParameters.triple)/debug/exe.product/Objects.LinkFileList").pathString, "", "C.exe-debug.exe", ] @@ -108,7 +113,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { XCTAssertEqual( llbuild.manifest.commands.map(\.key).sorted(), (basicDebugCommandNames + [ - AbsolutePath("/path/to/build/debug/exe-entitlement.plist").pathString, + AbsolutePath("/path/to/build/\(buildParameters.triple)/debug/exe-entitlement.plist").pathString, entitlementsCommandName, ]).sorted() ) @@ -121,8 +126,8 @@ final class LLBuildManifestBuilderTests: XCTestCase { XCTAssertEqual( entitlementsCommand.inputs, [ - .file("/path/to/build/debug/exe", isMutated: true), - .file("/path/to/build/debug/exe-entitlement.plist"), + .file("/path/to/build/\(buildParameters.triple)/debug/exe", isMutated: true), + .file("/path/to/build/\(buildParameters.triple)/debug/exe-entitlement.plist"), ] ) XCTAssertEqual( @@ -139,7 +144,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { configuration: .release )) plan = try BuildPlan( - productsBuildParameters: buildParameters, + destinationBuildParameters: buildParameters, toolsBuildParameters: buildParameters, graph: graph, fileSystem: fs, @@ -152,6 +157,12 @@ final class LLBuildManifestBuilderTests: XCTestCase { llbuild = LLBuildManifestBuilder(plan, fileSystem: localFileSystem, observabilityScope: observability.topScope) try llbuild.createProductCommand(buildProduct) + basicReleaseCommandNames = [ + AbsolutePath("/path/to/build/\(buildParameters.triple)/release/exe.product/Objects.LinkFileList").pathString, + "", + "C.exe-release.exe", + ] + XCTAssertEqual( llbuild.manifest.commands.map(\.key).sorted(), basicReleaseCommandNames.sorted() @@ -164,7 +175,7 @@ final class LLBuildManifestBuilderTests: XCTestCase { configuration: .debug )) plan = try BuildPlan( - productsBuildParameters: buildParameters, + destinationBuildParameters: buildParameters, toolsBuildParameters: buildParameters, graph: graph, fileSystem: fs, @@ -174,12 +185,38 @@ final class LLBuildManifestBuilderTests: XCTestCase { result = try BuildPlanResult(plan: plan) buildProduct = try result.buildProduct(for: "exe") - llbuild = LLBuildManifestBuilder(plan, fileSystem: localFileSystem, observabilityScope: observability.topScope) + llbuild = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: observability.topScope) try llbuild.createProductCommand(buildProduct) + basicDebugCommandNames = [ + AbsolutePath("/path/to/build/\(buildParameters.triple)/debug/exe.product/Objects.LinkFileList").pathString, + "", + "C.exe-debug.exe", + ] + XCTAssertEqual( llbuild.manifest.commands.map(\.key).sorted(), basicDebugCommandNames.sorted() ) } + + /// Verifies that two targets with the same name but different triples don't share same build manifest keys. + func testToolsBuildTriple() throws { + let (graph, fs, scope) = try macrosPackageGraph() + let productsTriple = Triple.x86_64MacOS + let toolsTriple = Triple.arm64Linux + + let plan = try BuildPlan( + destinationBuildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true, triple: productsTriple), + toolsBuildParameters: mockBuildParameters(triple: toolsTriple), + graph: graph, + fileSystem: fs, + observabilityScope: scope + ) + + let builder = LLBuildManifestBuilder(plan, fileSystem: fs, observabilityScope: scope) + let manifest = try builder.generateManifest(at: "/manifest") + + XCTAssertNotNil(manifest.commands["C.SwiftSyntax-debug-tool.module"]) + } } diff --git a/Tests/BuildTests/ModuleAliasingBuildTests.swift b/Tests/BuildTests/ModuleAliasingBuildTests.swift index 090dedb431a..baa21707d21 100644 --- a/Tests/BuildTests/ModuleAliasingBuildTests.swift +++ b/Tests/BuildTests/ModuleAliasingBuildTests.swift @@ -12,7 +12,10 @@ import Basics @testable import Build + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) @testable import PackageGraph + import PackageLoading @testable import PackageModel import SPMBuildCore @@ -33,7 +36,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let _ = try loadPackageGraph( + let _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -83,7 +86,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let _ = try loadPackageGraph( + let _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -135,7 +138,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/barPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -214,7 +217,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/barPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadPackageGraph( + XCTAssertThrowsError(try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -276,7 +279,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/barPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadPackageGraph( + XCTAssertThrowsError(try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -337,7 +340,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/bazPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -421,7 +424,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/bazPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -507,7 +510,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/yPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -638,7 +641,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -694,8 +697,9 @@ final class ModuleAliasingBuildTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) + let buildParameters = mockBuildParameters(shouldLinkStaticSwiftStdlib: true) let result = try BuildPlanResult(plan: try BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + buildParameters: buildParameters, graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -724,33 +728,33 @@ final class ModuleAliasingBuildTests: XCTestCase { XCTAssertMatch( fooLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/FooLogging.build/FooLogging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/FooLogging.build/FooLogging-Swift.h", .anySequence] ) XCTAssertMatch( barLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/BarLogging.build/BarLogging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/BarLogging.build/BarLogging-Swift.h", .anySequence] ) XCTAssertMatch( loggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/Logging.build/Logging-Swift.h", .anySequence] ) #else XCTAssertNoMatch( fooLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/FooLogging.build/FooLogging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/FooLogging.build/FooLogging-Swift.h", .anySequence] ) XCTAssertNoMatch( barLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/BarLogging.build/BarLogging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/BarLogging.build/BarLogging-Swift.h", .anySequence] ) XCTAssertNoMatch( loggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/Logging.build/Logging-Swift.h", .anySequence] ) #endif } @@ -767,7 +771,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -812,8 +816,9 @@ final class ModuleAliasingBuildTests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) + let buildParameters = mockBuildParameters(shouldLinkStaticSwiftStdlib: true) let result = try BuildPlanResult(plan: try BuildPlan( - buildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + buildParameters: buildParameters, graph: graph, fileSystem: fs, observabilityScope: observability.topScope @@ -842,23 +847,23 @@ final class ModuleAliasingBuildTests: XCTestCase { XCTAssertMatch( otherLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/OtherLogging.build/OtherLogging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/OtherLogging.build/OtherLogging-Swift.h", .anySequence] ) XCTAssertMatch( loggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/Logging.build/Logging-Swift.h", .anySequence] ) #else XCTAssertNoMatch( otherLoggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/OtherLogging.build/OtherLogging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/OtherLogging.build/OtherLogging-Swift.h", .anySequence] ) XCTAssertNoMatch( loggingArgs, [.anySequence, "-emit-objc-header", "-emit-objc-header-path", - "/path/to/build/debug/Logging.build/Logging-Swift.h", .anySequence] + "/path/to/build/\(buildParameters.triple)/debug/Logging.build/Logging-Swift.h", .anySequence] ) #endif } @@ -873,7 +878,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadPackageGraph( + XCTAssertThrowsError(try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -936,7 +941,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/swift-metrics/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -1017,7 +1022,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/fooPkg/Sources/Logging/include/fileLogging.h" ) let observability = ObservabilitySystem.makeForTesting() - let _ = try loadPackageGraph( + let _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1073,7 +1078,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let _ = try loadPackageGraph( + let _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1145,7 +1150,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/fooPkg/Sources/Perf/include/filePerf.h" ) let observability = ObservabilitySystem.makeForTesting() - XCTAssertNoThrow(try loadPackageGraph( + XCTAssertNoThrow(try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1198,7 +1203,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - XCTAssertNoThrow(try loadPackageGraph( + XCTAssertNoThrow(try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1262,7 +1267,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/barPkg/Sources/Logging/fileLogging.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1351,7 +1356,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -1471,7 +1476,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -1683,7 +1688,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -1884,7 +1889,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -2094,7 +2099,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -2308,7 +2313,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -2458,7 +2463,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -2595,7 +2600,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -2741,7 +2746,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -2893,7 +2898,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -3044,7 +3049,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -3144,7 +3149,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -3257,7 +3262,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/xPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -3325,7 +3330,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/cPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -3484,7 +3489,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/zPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -3655,7 +3660,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/zPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -3783,7 +3788,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/zPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -3912,7 +3917,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/yPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -4070,7 +4075,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/zPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -4236,7 +4241,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/zPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -4411,7 +4416,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/aPkg/Sources/Utils/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -4568,7 +4573,7 @@ final class ModuleAliasingBuildTests: XCTestCase { "/Other/Sources/Other/file.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -4638,7 +4643,7 @@ final class ModuleAliasingBuildTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() do { - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( diff --git a/Tests/BuildTests/PluginsBuildPlanTests.swift b/Tests/BuildTests/PluginsBuildPlanTests.swift index 5ef99e3fc59..7b2a1a78378 100644 --- a/Tests/BuildTests/PluginsBuildPlanTests.swift +++ b/Tests/BuildTests/PluginsBuildPlanTests.swift @@ -19,7 +19,7 @@ import PackageModel final class PluginsBuildPlanTests: XCTestCase { func testBuildToolsDatabasePath() throws { try fixture(name: "Miscellaneous/Plugins/MySourceGenPlugin") { fixturePath in - let (stdout, stderr) = try executeSwiftBuild(fixturePath) + let (stdout, _) = try executeSwiftBuild(fixturePath) XCTAssertMatch(stdout, .contains("Build complete!")) XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/plugins/tools/build.db")))) } @@ -48,8 +48,16 @@ final class PluginsBuildPlanTests: XCTestCase { let (stdout, stderr) = try executeSwiftPackage(fixturePath, extraArgs: ["-v", "build-plugin-dependency"]) XCTAssertMatch(stdout, .contains("Hello from dependencies-stub")) XCTAssertMatch(stderr, .contains("Build of product 'plugintool' complete!")) - XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/plugintool")))) - XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/placeholder")))) + XCTAssertTrue( + localFileSystem.exists( + fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/plugintool-tool")) + ) + ) + XCTAssertTrue( + localFileSystem.exists( + fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/placeholder")) + ) + ) } // When cross compiling the final product, plugin dependencies should still be built for the host @@ -57,8 +65,16 @@ final class PluginsBuildPlanTests: XCTestCase { let (stdout, stderr) = try executeSwiftPackage(fixturePath, extraArgs: ["--triple", targetTriple, "-v", "build-plugin-dependency"]) XCTAssertMatch(stdout, .contains("Hello from dependencies-stub")) XCTAssertMatch(stderr, .contains("Build of product 'plugintool' complete!")) - XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/plugintool")))) - XCTAssertTrue(localFileSystem.exists(fixturePath.appending(RelativePath(".build/\(targetTriple)/debug/placeholder")))) + XCTAssertTrue( + localFileSystem.exists( + fixturePath.appending(RelativePath(".build/\(hostTriple)/debug/plugintool-tool")) + ) + ) + XCTAssertTrue( + localFileSystem.exists( + fixturePath.appending(RelativePath(".build/\(targetTriple)/debug/placeholder")) + ) + ) } } } diff --git a/Tests/BuildTests/ProductBuildDescriptionTests.swift b/Tests/BuildTests/ProductBuildDescriptionTests.swift new file mode 100644 index 00000000000..a314782fdae --- /dev/null +++ b/Tests/BuildTests/ProductBuildDescriptionTests.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@testable +import Build + +import class Basics.ObservabilitySystem +import class TSCBasic.InMemoryFileSystem + +import class PackageModel.Manifest +import struct PackageModel.TargetDescription + +@testable +import struct PackageGraph.ResolvedProduct + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +import func PackageGraph.loadModulesGraph + +import func SPMTestSupport.mockBuildParameters +import func SPMTestSupport.XCTAssertNoDiagnostics +import XCTest + +final class ProductBuildDescriptionTests: XCTestCase { + func testEmbeddedProducts() throws { + let fs = InMemoryFileSystem( + emptyFiles: + "/Pkg/Sources/exe/main.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Pkg", + path: "/Pkg", + targets: [ + TargetDescription( + name: "exe", + settings: [.init(tool: .swift, kind: .enableExperimentalFeature("Embedded"))] + ), + ] + ), + ], + observabilityScope: observability.topScope + ) + XCTAssertNoDiagnostics(observability.diagnostics) + + let id = ResolvedProduct.ID(productName: "exe", packageIdentity: .plain("pkg"), buildTriple: .destination) + let package = try XCTUnwrap(graph.rootPackages.first) + let product = try XCTUnwrap(graph.allProducts[id]) + + let buildDescription = try ProductBuildDescription( + package: package, + product: product, + toolsVersion: .v5_9, + buildParameters: mockBuildParameters(environment: .init(platform: .macOS)), + fileSystem: fs, + observabilityScope: observability.topScope + ) + + XCTAssertTrue( + try buildDescription.linkArguments() + .joined(separator: " ") + .contains("-enable-experimental-feature Embedded") + ) + } +} diff --git a/Tests/CommandsTests/APIDiffTests.swift b/Tests/CommandsTests/APIDiffTests.swift index 7ab9a5fdd20..602c36c532c 100644 --- a/Tests/CommandsTests/APIDiffTests.swift +++ b/Tests/CommandsTests/APIDiffTests.swift @@ -13,7 +13,9 @@ import Basics import Build import Commands + import DriverSupport + import Foundation import PackageModel import SourceControl @@ -40,7 +42,7 @@ final class APIDiffTests: CommandsTestCase { try skipIfApiDigesterUnsupported() // The following is added to separate out the integration point testing of the API // diff digester with SwiftPM from the functionality tests of the digester itself - guard ProcessEnv.vars["SWIFTPM_TEST_API_DIFF_OUTPUT"] == "1" else { + guard ProcessEnv.block["SWIFTPM_TEST_API_DIFF_OUTPUT"] == "1" else { throw XCTSkip("Env var SWIFTPM_TEST_API_DIFF_OUTPUT must be set to test the output") } } diff --git a/Tests/CommandsTests/BuildToolTests.swift b/Tests/CommandsTests/BuildCommandTests.swift similarity index 92% rename from Tests/CommandsTests/BuildToolTests.swift rename to Tests/CommandsTests/BuildCommandTests.swift index 6ea803fc1eb..b81616d9bcd 100644 --- a/Tests/CommandsTests/BuildToolTests.swift +++ b/Tests/CommandsTests/BuildCommandTests.swift @@ -12,7 +12,10 @@ import Basics @testable import Commands -@testable import CoreCommands + +@testable +import CoreCommands + import PackageGraph import PackageLoading import PackageModel @@ -29,7 +32,7 @@ struct BuildResult { let moduleContents: [String] } -final class BuildToolTests: CommandsTestCase { +final class BuildCommandTests: CommandsTestCase { @discardableResult private func execute( _ args: [String] = [], @@ -39,10 +42,14 @@ final class BuildToolTests: CommandsTestCase { try SwiftPM.Build.execute(args, packagePath: packagePath, env: environment) } - func build(_ args: [String], packagePath: AbsolutePath? = nil, isRelease: Bool = false) throws -> BuildResult { + func build(_ args: [String], packagePath: AbsolutePath? = nil, isRelease: Bool = false, cleanAfterward: Bool = true) throws -> BuildResult { let buildConfigurationArguments = isRelease ? ["-c", "release"] : [] let (stdout, stderr) = try execute(args + buildConfigurationArguments, packagePath: packagePath) - defer { try! SwiftPM.Package.execute(["clean"], packagePath: packagePath) } + defer { + if cleanAfterward { + try! SwiftPM.Package.execute(["clean"], packagePath: packagePath) + } + } let (binPathOutput, _) = try execute( ["--show-bin-path"] + buildConfigurationArguments, packagePath: packagePath @@ -624,7 +631,7 @@ final class BuildToolTests: CommandsTestCase { ) XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) - XCTAssertMatch(buildResult.stderr, .contains(SwiftTool.entitlementsMacOSWarning)) + XCTAssertMatch(buildResult.stderr, .contains(SwiftCommandState.entitlementsMacOSWarning)) buildResult = try self.build( ["--enable-get-task-allow-entitlement", "-v"], @@ -633,7 +640,7 @@ final class BuildToolTests: CommandsTestCase { ) XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) - XCTAssertMatch(buildResult.stderr, .contains(SwiftTool.entitlementsMacOSWarning)) + XCTAssertMatch(buildResult.stderr, .contains(SwiftCommandState.entitlementsMacOSWarning)) #endif buildResult = try self.build(["-c", "release", "-v"], packagePath: fixturePath, isRelease: true) @@ -641,4 +648,37 @@ final class BuildToolTests: CommandsTestCase { XCTAssertNoMatch(buildResult.stdout, .contains("codesign --force --sign - --entitlements")) } } + +#if !canImport(Darwin) + func testIgnoresLinuxMain() throws { + try fixture(name: "Miscellaneous/TestDiscovery/IgnoresLinuxMain") { fixturePath in + let buildResult = try self.build(["-v", "--build-tests", "--enable-test-discovery"], packagePath: fixturePath, cleanAfterward: false) + let testBinaryPath = buildResult.binPath.appending("IgnoresLinuxMainPackageTests.xctest") + + let processTerminated = expectation(description: "Process terminated") + _ = try Process.run(testBinaryPath.asURL, arguments: [] ) { process in + XCTAssertEqual(process.terminationStatus, EXIT_SUCCESS) + processTerminated.fulfill() + } + wait(for: [processTerminated], timeout: .infinity) + } + } +#endif + + func testCodeCoverage() throws { + // Test that no codecov directory is created if not specified when building. + try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in + let buildResult = try self.build(["--build-tests"], packagePath: path, cleanAfterward: false) + XCTAssertThrowsError(try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path)) + } + + // Test that enabling code coverage during building produces the expected folder. + try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in + let buildResult = try self.build(["--build-tests", "--enable-code-coverage"], packagePath: path, cleanAfterward: false) + try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path) + let codeCovPath = buildResult.binPath.appending("codecov") + let codeCovFiles = try localFileSystem.getDirectoryContents(codeCovPath) + XCTAssertGreaterThan(codeCovFiles.count, 0) + } + } } diff --git a/Tests/CommandsTests/MermaidPackageSerializerTests.swift b/Tests/CommandsTests/MermaidPackageSerializerTests.swift index 7427267895f..d14e7f30d9e 100644 --- a/Tests/CommandsTests/MermaidPackageSerializerTests.swift +++ b/Tests/CommandsTests/MermaidPackageSerializerTests.swift @@ -11,11 +11,14 @@ //===----------------------------------------------------------------------===// import class Basics.ObservabilitySystem + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +import func PackageGraph.loadModulesGraph + import class PackageModel.Manifest import struct PackageModel.ProductDescription import struct PackageModel.TargetDescription import class TSCBasic.InMemoryFileSystem -import func SPMTestSupport.loadPackageGraph import func SPMTestSupport.XCTAssertNoDiagnostics @testable @@ -31,7 +34,7 @@ final class MermaidPackageSerializerTests: XCTestCase { "/A/Sources/ATarget/main.swift", "/A/Tests/ATargetTests/TestCases.swift" ) - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -76,7 +79,7 @@ final class MermaidPackageSerializerTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -107,7 +110,7 @@ final class MermaidPackageSerializerTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(graph.packages.count, 2) - let package = try XCTUnwrap(graph.packages.first) + let package = try XCTUnwrap(graph.package(for: .plain("A"))) let serializer = MermaidPackageSerializer(package: package.underlying) XCTAssertEqual( serializer.renderedMarkdown, @@ -135,7 +138,7 @@ final class MermaidPackageSerializerTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -166,7 +169,7 @@ final class MermaidPackageSerializerTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(graph.packages.count, 2) - let package = try XCTUnwrap(graph.packages.first) + let package = try XCTUnwrap(graph.package(for: .plain("A"))) let serializer = MermaidPackageSerializer(package: package.underlying) XCTAssertEqual( serializer.renderedMarkdown, diff --git a/Tests/CommandsTests/PackageToolTests.swift b/Tests/CommandsTests/PackageCommandTests.swift similarity index 95% rename from Tests/CommandsTests/PackageToolTests.swift rename to Tests/CommandsTests/PackageCommandTests.swift index 1fc2d01d9e7..92b47f8695d 100644 --- a/Tests/CommandsTests/PackageToolTests.swift +++ b/Tests/CommandsTests/PackageCommandTests.swift @@ -11,10 +11,18 @@ //===----------------------------------------------------------------------===// import Basics -@testable import CoreCommands -@testable import Commands + +@testable +import CoreCommands + +@testable +import Commands + import Foundation + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph + import PackageLoading import PackageModel import SourceControl @@ -28,7 +36,7 @@ import class TSCBasic.InMemoryFileSystem import enum TSCBasic.JSON import class TSCBasic.Process -final class PackageToolTests: CommandsTestCase { +final class PackageCommandTests: CommandsTestCase { @discardableResult private func execute( _ args: [String] = [], @@ -493,7 +501,7 @@ final class PackageToolTests: CommandsTestCase { // Returns symbol graph with or without pretty printing. private func symbolGraph(atPath path: AbsolutePath, withPrettyPrinting: Bool, file: StaticString = #file, line: UInt = #line) throws -> Data? { - let tool = try SwiftTool.createSwiftToolForTest(options: GlobalOptions.parse(["--package-path", path.pathString])) + let tool = try SwiftCommandState.makeMockState(options: GlobalOptions.parse(["--package-path", path.pathString])) let symbolGraphExtractorPath = try tool.getTargetToolchain().getSymbolGraphExtract() let arguments = withPrettyPrinting ? ["dump-symbol-graph", "--pretty-print"] : ["dump-symbol-graph"] @@ -630,7 +638,7 @@ final class PackageToolTests: CommandsTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [manifestA, manifestB, manifestC, manifestD], observabilityScope: observability.topScope @@ -638,7 +646,8 @@ final class PackageToolTests: CommandsTestCase { XCTAssertNoDiagnostics(observability.diagnostics) let output = BufferedOutputByteStream() - SwiftPackageTool.ShowDependencies.dumpDependenciesOf( + SwiftPackageCommand.ShowDependencies.dumpDependenciesOf( + graph: graph, rootPackage: graph.rootPackages[graph.rootPackages.startIndex], mode: .dot, on: output @@ -785,6 +794,94 @@ final class PackageToolTests: CommandsTestCase { } } + func testPackageAddDependency() throws { + try testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + + try fs.writeFileContents(path.appending("Package.swift"), string: + """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client", + targets: [ .target(name: "client", dependencies: [ "library" ]) ] + ) + """ + ) + + _ = try execute(["add-dependency", "--branch", "main", "https://github.com/apple/swift-syntax.git"], packagePath: path) + + let manifest = path.appending("Package.swift") + XCTAssertFileExists(manifest) + let contents: String = try fs.readFileContents(manifest) + + XCTAssertMatch(contents, .contains(#".package(url: "https://github.com/apple/swift-syntax.git", branch: "main"),"#)) + } + } + + func testPackageAddTarget() throws { + try testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + + try fs.writeFileContents(path.appending("Package.swift"), string: + """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client" + ) + """ + ) + + _ = try execute(["add-target", "client", "--dependencies", "MyLib", "OtherLib", "--type", "executable"], packagePath: path) + + let manifest = path.appending("Package.swift") + XCTAssertFileExists(manifest) + let contents: String = try fs.readFileContents(manifest) + + XCTAssertMatch(contents, .contains(#"targets:"#)) + XCTAssertMatch(contents, .contains(#".executableTarget"#)) + XCTAssertMatch(contents, .contains(#"name: "client""#)) + XCTAssertMatch(contents, .contains(#"dependencies:"#)) + XCTAssertMatch(contents, .contains(#""MyLib""#)) + XCTAssertMatch(contents, .contains(#""OtherLib""#)) + } + } + + func testPackageAddProduct() throws { + try testWithTemporaryDirectory { tmpPath in + let fs = localFileSystem + let path = tmpPath.appending("PackageB") + try fs.createDirectory(path) + + try fs.writeFileContents(path.appending("Package.swift"), string: + """ + // swift-tools-version: 5.9 + import PackageDescription + let package = Package( + name: "client" + ) + """ + ) + + _ = try execute(["add-product", "MyLib", "--targets", "MyLib", "--type", "static-library"], packagePath: path) + + let manifest = path.appending("Package.swift") + XCTAssertFileExists(manifest) + let contents: String = try fs.readFileContents(manifest) + + XCTAssertMatch(contents, .contains(#"products:"#)) + XCTAssertMatch(contents, .contains(#".library"#)) + XCTAssertMatch(contents, .contains(#"name: "MyLib""#)) + XCTAssertMatch(contents, .contains(#"type: .static"#)) + XCTAssertMatch(contents, .contains(#"targets:"#)) + XCTAssertMatch(contents, .contains(#""MyLib""#)) + } + } func testPackageEditAndUnedit() throws { try fixture(name: "Miscellaneous/PackageEdit") { fixturePath in let fooPath = fixturePath.appending("foo") @@ -1714,9 +1811,15 @@ final class PackageToolTests: CommandsTestCase { """ ) let hostTriple = try UserToolchain(swiftSDK: .hostSwiftSDK()).targetTriple - let hostTripleString = hostTriple.isDarwin() ? hostTriple.tripleString(forPlatformVersion: "") : hostTriple.tripleString - try localFileSystem.writeFileContents(packageDir.appending(components: "Binaries", "LocalBinaryTool.artifactbundle", "info.json"), string: - """ + let hostTripleString = if hostTriple.isDarwin() { + hostTriple.tripleString(forPlatformVersion: "") + } else { + hostTriple.tripleString + } + + try localFileSystem.writeFileContents( + packageDir.appending(components: "Binaries", "LocalBinaryTool.artifactbundle", "info.json"), + string: """ { "schemaVersion": "1.0", "artifacts": { "LocalBinaryTool": { @@ -1732,11 +1835,13 @@ final class PackageToolTests: CommandsTestCase { } """ ) - try localFileSystem.writeFileContents(packageDir.appending(components: "Sources", "LocalBuiltTool", "main.swift"), string: - #"print("Hello")"# + try localFileSystem.writeFileContents( + packageDir.appending(components: "Sources", "LocalBuiltTool", "main.swift"), + string: #"print("Hello")"# ) - try localFileSystem.writeFileContents(packageDir.appending(components: "Plugins", "MyPlugin", "plugin.swift"), string: - """ + try localFileSystem.writeFileContents( + packageDir.appending(components: "Plugins", "MyPlugin", "plugin.swift"), + string: """ import PackagePlugin import Foundation @main @@ -1786,14 +1891,20 @@ final class PackageToolTests: CommandsTestCase { print(" \\(file.path): \\(file.type)") } } + + // Print out the dependencies so that we can check them. + for dependency in context.package.dependencies { + print(" dependency \\(dependency.package.displayName): \\(dependency.package.origin)") + } } } """ ) // Create the sample vendored dependency package. - try localFileSystem.writeFileContents(packageDir.appending(components: "VendoredDependencies", "HelperPackage", "Package.swift"), string: - """ + try localFileSystem.writeFileContents( + packageDir.appending(components: "VendoredDependencies", "HelperPackage", "Package.swift"), + string: """ // swift-tools-version: 5.5 import PackageDescription let package = Package( @@ -1819,9 +1930,25 @@ final class PackageToolTests: CommandsTestCase { ) """ ) - try localFileSystem.writeFileContents(packageDir.appending(components: "VendoredDependencies", "HelperPackage", "Sources", "HelperLibrary", "library.swift"), string: "public func Bar() { }" + try localFileSystem.writeFileContents( + packageDir.appending( + components: "VendoredDependencies", + "HelperPackage", + "Sources", + "HelperLibrary", + "library.swift" + ), + string: "public func Bar() { }" ) - try localFileSystem.writeFileContents(packageDir.appending(components: "VendoredDependencies", "HelperPackage", "Sources", "RemoteBuiltTool", "main.swift"), string: #"print("Hello")"# + try localFileSystem.writeFileContents( + packageDir.appending( + components: "VendoredDependencies", + "HelperPackage", + "Sources", + "RemoteBuiltTool", + "main.swift" + ), + string: #"print("Hello")"# ) // Check that we can invoke the plugin with the "plugin" subcommand. @@ -1865,6 +1992,12 @@ final class PackageToolTests: CommandsTestCase { let (stdout, _) = try SwiftPM.Package.execute(["mycmd"], packagePath: packageDir) XCTAssertMatch(stdout, .contains("Initial working directory: \(workingDirectory)")) } + + // Check that information about the dependencies was properly sent to the plugin. + do { + let (stdout, _) = try SwiftPM.Package.execute(["mycmd", "--target", "MyLibrary"], packagePath: packageDir) + XCTAssertMatch(stdout, .contains("dependency HelperPackage: local")) + } } } @@ -1893,13 +2026,13 @@ final class PackageToolTests: CommandsTestCase { try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in func runPlugin(flags: [String], diagnostics: [String], completion: (String, String) -> Void) throws { - let (stdout, stderr) = try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath) + let (stdout, stderr) = try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) completion(stdout, stderr) } // Diagnostics.error causes SwiftPM to return a non-zero exit code, but we still need to check stdout and stderr func runPluginWithError(flags: [String], diagnostics: [String], completion: (String, String) -> Void) throws { - XCTAssertThrowsError(try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath)) { error in + XCTAssertThrowsError(try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"])) { error in guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = error else { return XCTFail("invalid error \(error)") } @@ -2091,28 +2224,28 @@ final class PackageToolTests: CommandsTestCase { // Check than nothing is echoed when echoLogs is false try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in - let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build"], packagePath: fixturePath) + let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build"], packagePath: fixturePath, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertMatch(stdout, isEmpty) XCTAssertMatch(stderr, isEmpty) } // Check that logs are returned to the plugin when echoLogs is false try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in - let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build", "printlogs"], packagePath: fixturePath) + let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build", "printlogs"], packagePath: fixturePath, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertMatch(stdout, containsLogtext) XCTAssertMatch(stderr, isEmpty) } // Check that logs echoed to the console (on stderr) when echoLogs is true try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in - let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build", "echologs"], packagePath: fixturePath) + let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build", "echologs"], packagePath: fixturePath, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertMatch(stdout, isEmpty) XCTAssertMatch(stderr, containsLogecho) } // Check that logs are returned to the plugin and echoed to the console (on stderr) when echoLogs is true try fixture(name: "Miscellaneous/Plugins/CommandPluginTestStub") { fixturePath in - let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build", "printlogs", "echologs"], packagePath: fixturePath) + let (stdout, stderr) = try SwiftPM.Package.execute(["print-diagnostics", "build", "printlogs", "echologs"], packagePath: fixturePath, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertMatch(stdout, containsLogtext) XCTAssertMatch(stderr, containsLogecho) } @@ -2405,14 +2538,14 @@ final class PackageToolTests: CommandsTestCase { // Check arguments do { - let (stdout, stderr) = try SwiftPM.Package.execute(["plugin", "MyPlugin", "--foo", "--help", "--version", "--verbose"], packagePath: packageDir) + let (stdout, stderr) = try SwiftPM.Package.execute(["plugin", "MyPlugin", "--foo", "--help", "--version", "--verbose"], packagePath: packageDir, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertMatch(stdout, .contains("success")) XCTAssertEqual(stderr, "") } // Check default command arguments do { - let (stdout, stderr) = try SwiftPM.Package.execute(["MyPlugin", "--foo", "--help", "--version", "--verbose"], packagePath: packageDir) + let (stdout, stderr) = try SwiftPM.Package.execute(["MyPlugin", "--foo", "--help", "--version", "--verbose"], packagePath: packageDir, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertMatch(stdout, .contains("success")) XCTAssertEqual(stderr, "") } @@ -2933,9 +3066,9 @@ final class PackageToolTests: CommandsTestCase { let execProducts = context.package.products(ofType: ExecutableProduct.self) print("execProducts: \\(execProducts.map{ $0.name })") let swiftTargets = context.package.targets(ofType: SwiftSourceModuleTarget.self) - print("swiftTargets: \\(swiftTargets.map{ $0.name })") + print("swiftTargets: \\(swiftTargets.map{ $0.name }.sorted())") let swiftSources = swiftTargets.flatMap{ $0.sourceFiles(withSuffix: ".swift") } - print("swiftSources: \\(swiftSources.map{ $0.path.lastComponent })") + print("swiftSources: \\(swiftSources.map{ $0.path.lastComponent }.sorted())") if let target = target.sourceModule { print("Module kind of '\\(target.name)': \\(target.kind)") @@ -2999,8 +3132,8 @@ final class PackageToolTests: CommandsTestCase { do { let (stdout, _) = try SwiftPM.Package.execute(["print-target-dependencies", "--target", "FifthTarget"], packagePath: packageDir) XCTAssertMatch(stdout, .contains("execProducts: [\"FifthTarget\"]")) - XCTAssertMatch(stdout, .contains("swiftTargets: [\"ThirdTarget\", \"TestTarget\", \"SecondTarget\", \"FourthTarget\", \"FirstTarget\", \"FifthTarget\"]")) - XCTAssertMatch(stdout, .contains("swiftSources: [\"library.swift\", \"tests.swift\", \"library.swift\", \"library.swift\", \"library.swift\", \"main.swift\"]")) + XCTAssertMatch(stdout, .contains("swiftTargets: [\"FifthTarget\", \"FirstTarget\", \"FourthTarget\", \"SecondTarget\", \"TestTarget\", \"ThirdTarget\"]")) + XCTAssertMatch(stdout, .contains("swiftSources: [\"library.swift\", \"library.swift\", \"library.swift\", \"library.swift\", \"main.swift\", \"tests.swift\"]")) XCTAssertMatch(stdout, .contains("Module kind of 'FifthTarget': executable")) } @@ -3205,7 +3338,10 @@ final class PackageToolTests: CommandsTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let _ = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let _ = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) } } diff --git a/Tests/CommandsTests/PackageRegistryToolTests.swift b/Tests/CommandsTests/PackageRegistryCommandTests.swift similarity index 81% rename from Tests/CommandsTests/PackageRegistryToolTests.swift rename to Tests/CommandsTests/PackageRegistryCommandTests.swift index fd0a33e23c9..90e842413ad 100644 --- a/Tests/CommandsTests/PackageRegistryToolTests.swift +++ b/Tests/CommandsTests/PackageRegistryCommandTests.swift @@ -15,7 +15,8 @@ import Commands import Foundation import PackageLoading import PackageModel -@testable import PackageRegistryTool +import PackageRegistry +@testable import PackageRegistryCommand import PackageSigning import SPMTestSupport import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported @@ -28,7 +29,7 @@ import struct TSCBasic.ProcessResult let defaultRegistryBaseURL = URL("https://packages.example.com") let customRegistryBaseURL = URL("https://custom.packages.example.com") -final class PackageRegistryToolTests: CommandsTestCase { +final class PackageRegistryCommandTests: CommandsTestCase { @discardableResult private func execute( _ args: [String], @@ -117,6 +118,19 @@ final class PackageRegistryToolTests: CommandsTestCase { XCTAssertEqual(json["version"], .int(1)) } + // Set default registry with allow-insecure-http option + do { + try execute(["set", "\(customRegistryBaseURL)", "--allow-insecure-http"], packagePath: packageRoot) + + let json = try JSON(data: localFileSystem.readFileContents(configurationFilePath)) + XCTAssertEqual(json["registries"]?.dictionary?.count, 1) + XCTAssertEqual( + json["registries"]?.dictionary?["[default]"]?.dictionary?["url"]?.string, + "\(customRegistryBaseURL)" + ) + XCTAssertEqual(json["version"], .int(1)) + } + // Unset default registry do { try execute(["unset"], packagePath: packageRoot) @@ -215,6 +229,40 @@ final class PackageRegistryToolTests: CommandsTestCase { } } + func testSetInsecureURL() throws { + try fixture(name: "DependencyResolution/External/Simple") { fixturePath in + let packageRoot = fixturePath.appending("Bar") + let configurationFilePath = AbsolutePath( + ".swiftpm/configuration/registries.json", + relativeTo: packageRoot + ) + + XCTAssertFalse(localFileSystem.exists(configurationFilePath)) + + // Set default registry + XCTAssertThrowsError(try execute(["set", "http://package.example.com"], packagePath: packageRoot)) + + XCTAssertFalse(localFileSystem.exists(configurationFilePath)) + } + } + + func testSetAllowedInsecureURL() throws { + try fixture(name: "DependencyResolution/External/Simple") { fixturePath in + let packageRoot = fixturePath.appending("Bar") + let configurationFilePath = AbsolutePath( + ".swiftpm/configuration/registries.json", + relativeTo: packageRoot + ) + + XCTAssertFalse(localFileSystem.exists(configurationFilePath)) + + // Set default registry + try execute(["set", "http://package.example.com", "--allow-insecure-http"], packagePath: packageRoot) + + XCTAssertTrue(localFileSystem.exists(configurationFilePath)) + } + } + func testSetInvalidScope() throws { try fixture(name: "DependencyResolution/External/Simple") { fixturePath in let packageRoot = fixturePath.appending("Bar") @@ -290,7 +338,7 @@ final class PackageRegistryToolTests: CommandsTestCase { let observability = ObservabilitySystem.makeForTesting() let packageIdentity = PackageIdentity.plain("org.package") - let metadataFilename = SwiftPackageRegistryTool.Publish.metadataFilename + let metadataFilename = PackageRegistryCommand.Publish.metadataFilename // git repo try await withTemporaryDirectory { temporaryDirectory in @@ -402,6 +450,133 @@ final class PackageRegistryToolTests: CommandsTestCase { } } + func testPublishingToHTTPRegistry() throws { + #if os(Linux) + // needed for archiving + guard SPM_posix_spawn_file_actions_addchdir_np_supported() else { + throw XCTSkip("working directory not supported on this platform") + } + #endif + + let packageIdentity = "test.my-package" + let version = "0.1.0" + let registryURL = "http://packages.example.com" + + _ = try withTemporaryDirectory { temporaryDirectory in + let packageDirectory = temporaryDirectory.appending("MyPackage") + try localFileSystem.createDirectory(packageDirectory) + + let initPackage = try InitPackage( + name: "MyPackage", + packageType: .executable, + destinationPath: packageDirectory, + fileSystem: localFileSystem + ) + try initPackage.writePackageStructure() + XCTAssertFileExists(packageDirectory.appending("Package.swift")) + + let workingDirectory = temporaryDirectory.appending(component: UUID().uuidString) + try localFileSystem.createDirectory(workingDirectory) + + XCTAssertThrowsError(try SwiftPM.Registry.execute( + [ + "publish", + packageIdentity, + version, + "--url=\(registryURL)", + "--scratch-directory=\(workingDirectory.pathString)", + "--package-path=\(packageDirectory.pathString)", + "--dry-run", + ] + )) + } + } + + func testPublishingToAllowedHTTPRegistry() throws { + #if os(Linux) + // needed for archiving + guard SPM_posix_spawn_file_actions_addchdir_np_supported() else { + throw XCTSkip("working directory not supported on this platform") + } + #endif + + let packageIdentity = "test.my-package" + let version = "0.1.0" + let registryURL = "http://packages.example.com" + + // with no authentication configured for registry + _ = try withTemporaryDirectory { temporaryDirectory in + let packageDirectory = temporaryDirectory.appending("MyPackage") + try localFileSystem.createDirectory(packageDirectory) + + let initPackage = try InitPackage( + name: "MyPackage", + packageType: .executable, + destinationPath: packageDirectory, + fileSystem: localFileSystem + ) + try initPackage.writePackageStructure() + XCTAssertFileExists(packageDirectory.appending("Package.swift")) + + let workingDirectory = temporaryDirectory.appending(component: UUID().uuidString) + try localFileSystem.createDirectory(workingDirectory) + + try SwiftPM.Registry.execute( + [ + "publish", + packageIdentity, + version, + "--url=\(registryURL)", + "--scratch-directory=\(workingDirectory.pathString)", + "--package-path=\(packageDirectory.pathString)", + "--allow-insecure-http", + "--dry-run", + ] + ) + } + + // with authentication configured for registry + _ = try withTemporaryDirectory { temporaryDirectory in + let packageDirectory = temporaryDirectory.appending("MyPackage") + try localFileSystem.createDirectory(packageDirectory) + + let initPackage = try InitPackage( + name: "MyPackage", + packageType: .executable, + destinationPath: packageDirectory, + fileSystem: localFileSystem + ) + try initPackage.writePackageStructure() + XCTAssertFileExists(packageDirectory.appending("Package.swift")) + + let workingDirectory = temporaryDirectory.appending(component: UUID().uuidString) + try localFileSystem.createDirectory(workingDirectory) + + let configurationFilePath = AbsolutePath( + ".swiftpm/configuration/registries.json", + relativeTo: packageDirectory + ) + + try localFileSystem.createDirectory(configurationFilePath.parentDirectory, recursive: true) + var configuration = RegistryConfiguration() + try configuration.add(authentication: .init(type: .basic), for: URL(registryURL)) + try localFileSystem.writeFileContents(configurationFilePath, data: JSONEncoder().encode(configuration)) + + XCTAssertThrowsError(try SwiftPM.Registry.execute( + [ + "publish", + packageIdentity, + version, + "--url=\(registryURL)", + "--scratch-directory=\(workingDirectory.pathString)", + "--package-path=\(packageDirectory.pathString)", + "--allow-insecure-http", + "--dry-run", + ] + )) + } + } + func testPublishingUnsignedPackage() throws { #if os(Linux) // needed for archiving @@ -475,7 +650,7 @@ final class PackageRegistryToolTests: CommandsTestCase { let workingDirectory = temporaryDirectory.appending(component: UUID().uuidString) try localFileSystem.createDirectory(workingDirectory) - let metadataPath = packageDirectory.appending(SwiftPackageRegistryTool.Publish.metadataFilename) + let metadataPath = packageDirectory.appending(PackageRegistryCommand.Publish.metadataFilename) try localFileSystem.writeFileContents(metadataPath, string: "{}") try SwiftPM.Registry.execute( @@ -714,7 +889,7 @@ final class PackageRegistryToolTests: CommandsTestCase { let workingDirectory = temporaryDirectory.appending(component: UUID().uuidString) try localFileSystem.createDirectory(workingDirectory) - let metadataPath = packageDirectory.appending(SwiftPackageRegistryTool.Publish.metadataFilename) + let metadataPath = packageDirectory.appending(PackageRegistryCommand.Publish.metadataFilename) try localFileSystem.writeFileContents(metadataPath, string: "{}") let certificatePath = temporaryDirectory.appending(component: "certificate.cer") @@ -903,21 +1078,38 @@ final class PackageRegistryToolTests: CommandsTestCase { } } + func testLoginRequiresHTTPS() { + let registryURL = URL(string: "http://packages.example.com")! + + XCTAssertThrowsError(try SwiftPM.Registry.execute(["login", "--url", registryURL.absoluteString])) + } + func testCreateLoginURL() { let registryURL = URL(string: "https://packages.example.com")! - XCTAssertEqual(try SwiftPackageRegistryTool.Login.loginURL(from: registryURL, loginAPIPath: nil).absoluteString, "https://packages.example.com/login") - - XCTAssertEqual(try SwiftPackageRegistryTool.Login.loginURL(from: registryURL, loginAPIPath: "/secret-sign-in").absoluteString, "https://packages.example.com/secret-sign-in") + XCTAssertEqual(try PackageRegistryCommand.Login.loginURL(from: registryURL, loginAPIPath: nil).absoluteString, "https://packages.example.com/login") + XCTAssertEqual(try PackageRegistryCommand.Login.loginURL(from: registryURL, loginAPIPath: "/secret-sign-in").absoluteString, "https://packages.example.com/secret-sign-in") } func testCreateLoginURLMaintainsPort() { let registryURL = URL(string: "https://packages.example.com:8081")! - XCTAssertEqual(try SwiftPackageRegistryTool.Login.loginURL(from: registryURL, loginAPIPath: nil).absoluteString, "https://packages.example.com:8081/login") + XCTAssertEqual(try PackageRegistryCommand.Login.loginURL(from: registryURL, loginAPIPath: nil).absoluteString, "https://packages.example.com:8081/login") + + XCTAssertEqual(try PackageRegistryCommand.Login.loginURL(from: registryURL, loginAPIPath: "/secret-sign-in").absoluteString, "https://packages.example.com:8081/secret-sign-in") + } + + func testValidateRegistryURL() throws { + // Valid + try URL(string: "https://packages.example.com")!.validateRegistryURL() + try URL(string: "http://packages.example.com")!.validateRegistryURL(allowHTTP: true) - XCTAssertEqual(try SwiftPackageRegistryTool.Login.loginURL(from: registryURL, loginAPIPath: "/secret-sign-in").absoluteString, "https://packages.example.com:8081/secret-sign-in") + // Invalid + XCTAssertThrowsError(try URL(string: "http://packages.example.com")!.validateRegistryURL()) + XCTAssertThrowsError(try URL(string: "http://packages.example.com")!.validateRegistryURL(allowHTTP: false)) + XCTAssertThrowsError(try URL(string: "ssh://packages.example.com")!.validateRegistryURL()) + XCTAssertThrowsError(try URL(string: "ftp://packages.example.com")!.validateRegistryURL(allowHTTP: true)) } private func testRoots() throws -> [[UInt8]] { diff --git a/Tests/CommandsTests/RunToolTests.swift b/Tests/CommandsTests/RunCommandTests.swift similarity index 96% rename from Tests/CommandsTests/RunToolTests.swift rename to Tests/CommandsTests/RunCommandTests.swift index d2089e023c2..ec50b40b3ea 100644 --- a/Tests/CommandsTests/RunToolTests.swift +++ b/Tests/CommandsTests/RunCommandTests.swift @@ -16,9 +16,9 @@ import SPMTestSupport import XCTest import class TSCBasic.Process +import enum TSCBasic.ProcessEnv -final class RunToolTests: CommandsTestCase { - +final class RunCommandTests: CommandsTestCase { private func execute( _ args: [String] = [], packagePath: AbsolutePath? = nil @@ -122,8 +122,12 @@ final class RunToolTests: CommandsTestCase { let sync = DispatchGroup() let outputHandler = OutputHandler(sync: sync) + + var environmentBlock = ProcessEnv.block + environmentBlock["SWIFTPM_EXEC_NAME"] = "swift-run" let process = Process( arguments: [SwiftPM.Run.xctestBinaryPath.pathString, "--package-path", fixturePath.pathString], + environmentBlock: environmentBlock, outputRedirection: .stream(stdout: outputHandler.handle(bytes:), stderr: outputHandler.handle(bytes:)) ) diff --git a/Tests/CommandsTests/SwiftToolTests.swift b/Tests/CommandsTests/SwiftCommandStateTests.swift similarity index 87% rename from Tests/CommandsTests/SwiftToolTests.swift rename to Tests/CommandsTests/SwiftCommandStateTests.swift index cc5c291de0e..bafa0b5f801 100644 --- a/Tests/CommandsTests/SwiftToolTests.swift +++ b/Tests/CommandsTests/SwiftCommandStateTests.swift @@ -12,8 +12,15 @@ @testable import Basics @testable import Build -@testable import CoreCommands + +@testable +import CoreCommands + @testable import Commands + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) +import func PackageGraph.loadModulesGraph + @testable import PackageModel import SPMTestSupport import XCTest @@ -23,13 +30,13 @@ import class TSCBasic.InMemoryFileSystem import protocol TSCBasic.OutputByteStream import var TSCBasic.stderrStream -final class SwiftToolTests: CommandsTestCase { +final class SwiftCommandStateTests: CommandsTestCase { func testVerbosityLogLevel() throws { try fixture(name: "Miscellaneous/Simple") { fixturePath in do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .warning) tool.observabilityScope.emit(error: "error") @@ -48,7 +55,7 @@ final class SwiftToolTests: CommandsTestCase { do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "--verbose"]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .info) tool.observabilityScope.emit(error: "error") @@ -67,7 +74,7 @@ final class SwiftToolTests: CommandsTestCase { do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "-v"]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .info) tool.observabilityScope.emit(error: "error") @@ -86,7 +93,7 @@ final class SwiftToolTests: CommandsTestCase { do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "--very-verbose"]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .debug) tool.observabilityScope.emit(error: "error") @@ -105,7 +112,7 @@ final class SwiftToolTests: CommandsTestCase { do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "--vv"]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .debug) tool.observabilityScope.emit(error: "error") @@ -124,7 +131,7 @@ final class SwiftToolTests: CommandsTestCase { do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "--quiet"]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .error) tool.observabilityScope.emit(error: "error") @@ -143,7 +150,7 @@ final class SwiftToolTests: CommandsTestCase { do { let outputStream = BufferedOutputByteStream() let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "-q"]) - let tool = try SwiftTool.createSwiftToolForTest(outputStream: outputStream, options: options) + let tool = try SwiftCommandState.makeMockState(outputStream: outputStream, options: options) XCTAssertEqual(tool.logLevel, .error) tool.observabilityScope.emit(error: "error") @@ -174,7 +181,7 @@ final class SwiftToolTests: CommandsTestCase { ) let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "--netrc-file", customPath.pathString]) - let tool = try SwiftTool.createSwiftToolForTest(options: options) + let tool = try SwiftCommandState.makeMockState(options: options) let authorizationProvider = try tool.getAuthorizationProvider() as? CompositeAuthorizationProvider let netrcProviders = authorizationProvider?.providers.compactMap { $0 as? NetrcAuthorizationProvider } ?? [] @@ -209,7 +216,7 @@ final class SwiftToolTests: CommandsTestCase { ) let options = try GlobalOptions.parse(["--package-path", fixturePath.pathString, "--netrc-file", customPath.pathString]) - let tool = try SwiftTool.createSwiftToolForTest(options: options) + let tool = try SwiftCommandState.makeMockState(options: options) // There is only one AuthorizationProvider depending on platform #if canImport(Security) @@ -242,7 +249,7 @@ final class SwiftToolTests: CommandsTestCase { ]) let observer = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph(fileSystem: fs, manifests: [ + let graph = try loadModulesGraph(fileSystem: fs, manifests: [ Manifest.createRootManifest(displayName: "Pkg", path: "/Pkg", targets: [TargetDescription(name: "exe")]) @@ -253,9 +260,9 @@ final class SwiftToolTests: CommandsTestCase { /* -debug-info-format dwarf */ let explicitDwarfOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc", "-debug-info-format", "dwarf"]) - let explicitDwarf = try SwiftTool.createSwiftToolForTest(options: explicitDwarfOptions) + let explicitDwarf = try SwiftCommandState.makeMockState(options: explicitDwarfOptions) plan = try BuildPlan( - productsBuildParameters: explicitDwarf.productsBuildParameters, + destinationBuildParameters: explicitDwarf.productsBuildParameters, toolsBuildParameters: explicitDwarf.toolsBuildParameters, graph: graph, fileSystem: fs, @@ -267,10 +274,10 @@ final class SwiftToolTests: CommandsTestCase { /* -debug-info-format codeview */ let explicitCodeViewOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc", "-debug-info-format", "codeview"]) - let explicitCodeView = try SwiftTool.createSwiftToolForTest(options: explicitCodeViewOptions) + let explicitCodeView = try SwiftCommandState.makeMockState(options: explicitCodeViewOptions) plan = try BuildPlan( - productsBuildParameters: explicitCodeView.productsBuildParameters, + destinationBuildParameters: explicitCodeView.productsBuildParameters, toolsBuildParameters: explicitCodeView.productsBuildParameters, graph: graph, fileSystem: fs, @@ -279,11 +286,11 @@ final class SwiftToolTests: CommandsTestCase { try XCTAssertMatch(plan.buildProducts.compactMap { $0 as? Build.ProductBuildDescription }.first?.linkArguments() ?? [], [.anySequence, "-g", "-debug-info-format=codeview", "-Xlinker", "-debug"]) - // Explicitly pass Linux as when the SwiftTool tests are enabled on + // Explicitly pass Linux as when the `SwiftCommandState` tests are enabled on // Windows, this would fail otherwise as CodeView is supported on the // native host. let unsupportedCodeViewOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-linux-gnu", "-debug-info-format", "codeview"]) - let unsupportedCodeView = try SwiftTool.createSwiftToolForTest(options: unsupportedCodeViewOptions) + let unsupportedCodeView = try SwiftCommandState.makeMockState(options: unsupportedCodeViewOptions) XCTAssertThrowsError(try unsupportedCodeView.productsBuildParameters) { XCTAssertEqual($0 as? StringError, StringError("CodeView debug information is currently not supported on linux")) @@ -291,9 +298,9 @@ final class SwiftToolTests: CommandsTestCase { /* <> */ let implicitDwarfOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc"]) - let implicitDwarf = try SwiftTool.createSwiftToolForTest(options: implicitDwarfOptions) + let implicitDwarf = try SwiftCommandState.makeMockState(options: implicitDwarfOptions) plan = try BuildPlan( - productsBuildParameters: implicitDwarf.productsBuildParameters, + destinationBuildParameters: implicitDwarf.productsBuildParameters, toolsBuildParameters: implicitDwarf.toolsBuildParameters, graph: graph, fileSystem: fs, @@ -304,9 +311,9 @@ final class SwiftToolTests: CommandsTestCase { /* -debug-info-format none */ let explicitNoDebugInfoOptions = try GlobalOptions.parse(["--triple", "x86_64-unknown-windows-msvc", "-debug-info-format", "none"]) - let explicitNoDebugInfo = try SwiftTool.createSwiftToolForTest(options: explicitNoDebugInfoOptions) + let explicitNoDebugInfo = try SwiftCommandState.makeMockState(options: explicitNoDebugInfoOptions) plan = try BuildPlan( - productsBuildParameters: explicitNoDebugInfo.productsBuildParameters, + destinationBuildParameters: explicitNoDebugInfo.productsBuildParameters, toolsBuildParameters: explicitNoDebugInfo.toolsBuildParameters, graph: graph, fileSystem: fs, @@ -317,17 +324,17 @@ final class SwiftToolTests: CommandsTestCase { } } -extension SwiftTool { - static func createSwiftToolForTest( +extension SwiftCommandState { + static func makeMockState( outputStream: OutputByteStream = stderrStream, options: GlobalOptions - ) throws -> SwiftTool { - return try SwiftTool( + ) throws -> SwiftCommandState { + return try SwiftCommandState( outputStream: outputStream, options: options, toolWorkspaceConfiguration: .init(shouldInstallSignalHandlers: false), workspaceDelegateProvider: { - ToolWorkspaceDelegate( + CommandWorkspaceDelegate( observabilityScope: $0, outputHandler: $1, progressHandler: $2, diff --git a/Tests/CommandsTests/SwiftSDKCommandTests.swift b/Tests/CommandsTests/SwiftSDKCommandTests.swift new file mode 100644 index 00000000000..b9e80667059 --- /dev/null +++ b/Tests/CommandsTests/SwiftSDKCommandTests.swift @@ -0,0 +1,265 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Basics +import Commands +import SPMTestSupport +import XCTest + +import class TSCBasic.Process +import enum TSCBasic.ProcessEnv + +private let sdkCommandDeprecationWarning = """ + warning: `swift experimental-sdk` command is deprecated and will be removed in a future version of SwiftPM. Use \ + `swift sdk` instead. + + """ + + +final class SwiftSDKCommandTests: CommandsTestCase { + func testUsage() throws { + for command in [SwiftPM.sdk, SwiftPM.experimentalSDK] { + let stdout = try command.execute(["-help"]).stdout + XCTAssert(stdout.contains("USAGE: swift sdk ") || stdout.contains("USAGE: swift sdk []"), "got stdout:\n" + stdout) + } + } + + func testVersion() throws { + for command in [SwiftPM.sdk, SwiftPM.experimentalSDK] { + let stdout = try command.execute(["--version"]).stdout + XCTAssert(stdout.contains("Swift Package Manager"), "got stdout:\n" + stdout) + } + } + + func testInstallSubcommand() throws { + for command in [SwiftPM.sdk, SwiftPM.experimentalSDK] { + try fixture(name: "SwiftSDKs") { fixturePath in + for bundle in ["test-sdk.artifactbundle.tar.gz", "test-sdk.artifactbundle.zip"] { + var (stdout, stderr) = try command.execute( + [ + "install", + "--swift-sdks-path", fixturePath.pathString, + fixturePath.appending(bundle).pathString + ] + ) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + XCTAssertNoMatch(stdout, .contains(sdkCommandDeprecationWarning)) + } + + // We only expect tool's output on the stdout stream. + XCTAssertMatch( + stdout, + .contains("\(bundle)` successfully installed as test-sdk.artifactbundle.") + ) + + (stdout, stderr) = try command.execute( + ["list", "--swift-sdks-path", fixturePath.pathString]) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + XCTAssertNoMatch(stdout, .contains(sdkCommandDeprecationWarning)) + } + + // We only expect tool's output on the stdout stream. + XCTAssertMatch(stdout, .contains("test-artifact")) + + XCTAssertThrowsError(try command.execute( + [ + "install", + "--swift-sdks-path", fixturePath.pathString, + fixturePath.appending(bundle).pathString + ] + )) { error in + guard case SwiftPMError.executionFailure(_, _, let stderr) = error else { + XCTFail() + return + } + + XCTAssertTrue( + stderr.contains( + "Error: Swift SDK bundle with name `test-sdk.artifactbundle` is already installed. Can't install a new bundle with the same name." + ), + "got stderr: \(stderr)" + ) + } + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + } + + (stdout, stderr) = try command.execute( + ["remove", "--swift-sdks-path", fixturePath.pathString, "test-artifact"]) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + XCTAssertNoMatch(stdout, .contains(sdkCommandDeprecationWarning)) + } + + // We only expect tool's output on the stdout stream. + XCTAssertMatch(stdout, .contains("test-sdk.artifactbundle` was successfully removed from the file system.")) + + (stdout, stderr) = try command.execute( + ["list", "--swift-sdks-path", fixturePath.pathString]) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + XCTAssertNoMatch(stdout, .contains(sdkCommandDeprecationWarning)) + } + + // We only expect tool's output on the stdout stream. + XCTAssertNoMatch(stdout, .contains("test-artifact")) + } + } + } + } + + func testConfigureSubcommand() throws { + let deprecationWarning = """ + warning: `swift sdk configuration` command is deprecated and will be removed in a future version of \ + SwiftPM. Use `swift sdk configure` instead. + + """ + + for command in [SwiftPM.sdk, SwiftPM.experimentalSDK] { + try fixture(name: "SwiftSDKs") { fixturePath in + let bundle = "test-sdk.artifactbundle.zip" + + var (stdout, stderr) = try command.execute([ + "install", + "--swift-sdks-path", fixturePath.pathString, + fixturePath.appending(bundle).pathString + ]) + + // We only expect tool's output on the stdout stream. + XCTAssertMatch( + stdout, + .contains("\(bundle)` successfully installed as test-sdk.artifactbundle.") + ) + + let deprecatedShowSubcommand = ["configuration", "show"] + + for showSubcommand in [deprecatedShowSubcommand, ["configure", "--show-configuration"]] { + let invocation = showSubcommand + [ + "--swift-sdks-path", fixturePath.pathString, + "test-artifact", + "aarch64-unknown-linux-gnu", + ] + (stdout, stderr) = try command.execute(invocation) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + } + + if showSubcommand == deprecatedShowSubcommand { + XCTAssertMatch(stderr, .contains(deprecationWarning)) + } + + let sdkSubpath = "test-sdk.artifactbundle/sdk/sdk" + + XCTAssertEqual(stdout, + """ + sdkRootPath: \(fixturePath.pathString)/\(sdkSubpath) + swiftResourcesPath: not set + swiftStaticResourcesPath: not set + includeSearchPaths: not set + librarySearchPaths: not set + toolsetPaths: not set + + """, + invocation.joined(separator: " ") + ) + + let deprecatedSetSubcommand = ["configuration", "set"] + let deprecatedResetSubcommand = ["configuration", "reset"] + for setSubcommand in [deprecatedSetSubcommand, ["configure"]] { + for resetSubcommand in [deprecatedResetSubcommand, ["configure", "--reset"]] { + var invocation = setSubcommand + [ + "--swift-resources-path", fixturePath.appending("foo").pathString, + "--swift-sdks-path", fixturePath.pathString, + "test-artifact", + "aarch64-unknown-linux-gnu", + ] + (stdout, stderr) = try command.execute(invocation) + + XCTAssertEqual(stdout, """ + info: These properties of Swift SDK `test-artifact` for target triple `aarch64-unknown-linux-gnu` \ + were successfully updated: swiftResourcesPath. + + """, + invocation.joined(separator: " ") + ) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + } + + if setSubcommand == deprecatedSetSubcommand { + XCTAssertMatch(stderr, .contains(deprecationWarning)) + } + + invocation = showSubcommand + [ + "--swift-sdks-path", fixturePath.pathString, + "test-artifact", + "aarch64-unknown-linux-gnu", + ] + (stdout, stderr) = try command.execute(invocation) + + XCTAssertEqual(stdout, + """ + sdkRootPath: \(fixturePath.pathString)/\(sdkSubpath) + swiftResourcesPath: \(fixturePath.pathString)/foo + swiftStaticResourcesPath: not set + includeSearchPaths: not set + librarySearchPaths: not set + toolsetPaths: not set + + """, + invocation.joined(separator: " ") + ) + + invocation = resetSubcommand + [ + "--swift-sdks-path", fixturePath.pathString, + "test-artifact", + "aarch64-unknown-linux-gnu", + ] + (stdout, stderr) = try command.execute(invocation) + + if command == .experimentalSDK { + XCTAssertMatch(stderr, .contains(sdkCommandDeprecationWarning)) + } + + if resetSubcommand == deprecatedResetSubcommand { + XCTAssertMatch(stderr, .contains(deprecationWarning)) + } + + XCTAssertEqual(stdout, + """ + info: All configuration properties of Swift SDK `test-artifact` for target triple `aarch64-unknown-linux-gnu` were successfully reset. + + """, + invocation.joined(separator: " ") + ) + } + } + } + + (stdout, stderr) = try command.execute( + ["remove", "--swift-sdks-path", fixturePath.pathString, "test-artifact"]) + + // We only expect tool's output on the stdout stream. + XCTAssertMatch(stdout, .contains("test-sdk.artifactbundle` was successfully removed from the file system.")) + } + } + } +} diff --git a/Tests/CommandsTests/TestToolTests.swift b/Tests/CommandsTests/TestCommandTests.swift similarity index 99% rename from Tests/CommandsTests/TestToolTests.swift rename to Tests/CommandsTests/TestCommandTests.swift index 72e1a99b51d..2f485ded9a4 100644 --- a/Tests/CommandsTests/TestToolTests.swift +++ b/Tests/CommandsTests/TestCommandTests.swift @@ -16,7 +16,7 @@ import PackageModel import SPMTestSupport import XCTest -final class TestToolTests: CommandsTestCase { +final class TestCommandTests: CommandsTestCase { private func execute(_ args: [String], packagePath: AbsolutePath? = nil) throws -> (stdout: String, stderr: String) { return try SwiftPM.Test.execute(args, packagePath: packagePath) diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index fb3f5033ed1..badae071951 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -89,7 +89,8 @@ class MiscellaneousTestCase: XCTestCase { return XCTFail("failed in an unexpected manner: \(error)") } XCTAssertMatch(error.stdout + error.stderr, .contains("Compiling CompileFails Foo.swift")) - XCTAssertMatch(error.stdout + error.stderr, .regex("error: .*\n.*compile_failure")) + XCTAssertMatch(error.stdout + error.stderr, .regex(".*compile_failure.*")) + XCTAssertMatch(error.stdout + error.stderr, .regex(".*error:.*")) } } } @@ -664,7 +665,7 @@ class MiscellaneousTestCase: XCTestCase { func testRootPackageWithConditionals() throws { try fixture(name: "Miscellaneous/RootPackageWithConditionals") { path in - let (_, stderr) = try SwiftPM.Build.execute(packagePath: path) + let (_, stderr) = try SwiftPM.Build.execute(packagePath: path, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) let errors = stderr.components(separatedBy: .newlines).filter { !$0.contains("[logging] misuse") && !$0.isEmpty } XCTAssertEqual(errors, [], "unexpected errors: \(errors)") } diff --git a/Tests/FunctionalTests/PluginTests.swift b/Tests/FunctionalTests/PluginTests.swift index e2d5e03e827..d33a0c17da1 100644 --- a/Tests/FunctionalTests/PluginTests.swift +++ b/Tests/FunctionalTests/PluginTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,6 +11,8 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) @testable import PackageGraph import PackageLoading import PackageModel @@ -19,8 +21,7 @@ import SPMTestSupport import Workspace import XCTest -class PluginTests: XCTestCase { - +final class PluginTests: XCTestCase { func testUseOfBuildToolPluginTargetByExecutableInSamePackage() throws { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -177,7 +178,9 @@ class PluginTests: XCTestCase { } } - func testBuildToolWithoutOutputs() throws { + func testBuildToolWithoutOutputs() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -226,12 +229,12 @@ class PluginTests: XCTestCase { let pathOfGeneratedFile = packageDir.appending(components: [".build", "plugins", "outputs", "mypackage", "SomeTarget", "Plugin", "best.txt"]) try createPackageUnderTest(packageDir: packageDir, toolsVersion: .v5_9) - let (_, stderr) = try executeSwiftBuild(packageDir) + let (_, stderr) = try executeSwiftBuild(packageDir, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertTrue(stderr.contains("warning: Build tool command 'empty' (applied to target 'SomeTarget') does not declare any output files"), "expected warning not emitted") XCTAssertFalse(localFileSystem.exists(pathOfGeneratedFile), "plugin generated file unexpectedly exists at \(pathOfGeneratedFile.pathString)") - try createPackageUnderTest(packageDir: packageDir, toolsVersion: .v5_11) - let (_, stderr2) = try executeSwiftBuild(packageDir) + try createPackageUnderTest(packageDir: packageDir, toolsVersion: .v6_0) + let (_, stderr2) = try executeSwiftBuild(packageDir, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) XCTAssertEqual("", stderr2) XCTAssertTrue(localFileSystem.exists(pathOfGeneratedFile), "plugin did not run, generated file does not exist at \(pathOfGeneratedFile.pathString)") } @@ -442,7 +445,10 @@ class PluginTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssert(packageGraph.packages.count == 2, "\(packageGraph.packages)") XCTAssert(packageGraph.rootPackages.count == 1, "\(packageGraph.rootPackages)") @@ -544,6 +550,7 @@ class PluginTests: XCTestCase { pkgConfigDirectories: [], sdkRootPath: nil, fileSystem: localFileSystem, + modulesGraph: packageGraph, observabilityScope: observability.topScope, callbackQueue: delegateQueue, delegate: delegate, @@ -625,7 +632,10 @@ class PluginTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) // Make sure that the use of plugins doesn't bleed into the use of plugins by tools. @@ -719,7 +729,10 @@ class PluginTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssert(packageGraph.packages.count == 1, "\(packageGraph.packages)") XCTAssert(packageGraph.rootPackages.count == 1, "\(packageGraph.rootPackages)") @@ -815,6 +828,7 @@ class PluginTests: XCTestCase { pkgConfigDirectories: [], sdkRootPath: try UserToolchain.default.sdkRootPath, fileSystem: localFileSystem, + modulesGraph: packageGraph, observabilityScope: observability.topScope, callbackQueue: delegateQueue, delegate: delegate @@ -1031,7 +1045,10 @@ class PluginTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssert(packageGraph.packages.count == 4, "\(packageGraph.packages)") XCTAssert(packageGraph.rootPackages.count == 1, "\(packageGraph.rootPackages)") @@ -1142,7 +1159,9 @@ class PluginTests: XCTestCase { } } - func testURLBasedPluginAPI() throws { + func testURLBasedPluginAPI() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -1152,7 +1171,9 @@ class PluginTests: XCTestCase { } } - func testDependentPlugins() throws { + func testDependentPlugins() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") try fixture(name: "Miscellaneous/Plugins/DependentPlugins") { fixturePath in diff --git a/Tests/FunctionalTests/ResourcesTests.swift b/Tests/FunctionalTests/ResourcesTests.swift index f37e531ad0e..dec955a5b1e 100644 --- a/Tests/FunctionalTests/ResourcesTests.swift +++ b/Tests/FunctionalTests/ResourcesTests.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// import Basics +import PackageModel import SPMTestSupport import XCTest @@ -126,7 +127,9 @@ class ResourcesTests: XCTestCase { } } - func testResourcesOutsideOfTargetCanBeIncluded() throws { + func testResourcesOutsideOfTargetCanBeIncluded() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try testWithTemporaryDirectory { tmpPath in let packageDir = tmpPath.appending(components: "MyPackage") @@ -135,7 +138,7 @@ class ResourcesTests: XCTestCase { try localFileSystem.writeFileContents( manifestFile, string: """ - // swift-tools-version: 5.11 + // swift-tools-version: 6.0 import PackageDescription let package = Package(name: "MyPackage", targets: [ @@ -157,7 +160,7 @@ class ResourcesTests: XCTestCase { try localFileSystem.createDirectory(resource.parentDirectory, recursive: true) try localFileSystem.writeFileContents(resource, string: "best") - let (_, stderr) = try executeSwiftBuild(packageDir) + let (_, stderr) = try executeSwiftBuild(packageDir, env: ["SWIFT_DRIVER_SWIFTSCAN_LIB" : "/this/is/a/bad/path"]) // Filter some unrelated output that could show up on stderr. let filteredStderr = stderr.components(separatedBy: "\n").filter { !$0.contains("[logging]") }.joined(separator: "\n") XCTAssertEqual(filteredStderr, "", "unexpectedly received error output: \(stderr)") diff --git a/Tests/FunctionalTests/ToolsVersionTests.swift b/Tests/FunctionalTests/ToolsVersionTests.swift index 067f6b893c8..4b77ed0b7e4 100644 --- a/Tests/FunctionalTests/ToolsVersionTests.swift +++ b/Tests/FunctionalTests/ToolsVersionTests.swift @@ -18,8 +18,7 @@ import SPMTestSupport import Workspace import XCTest -class ToolsVersionTests: XCTestCase { - +final class ToolsVersionTests: XCTestCase { func testToolsVersion() throws { try testWithTemporaryDirectory{ path in let fs = localFileSystem @@ -124,6 +123,7 @@ class ToolsVersionTests: XCTestCase { XCTAssertTrue(error.stderr.contains("package 'primary' requires minimum Swift language version 1000 which is not supported by the current tools version (\(ToolsVersion.current))"), error.stderr) } +#if compiler(>=6.0) try fs.writeFileContents( primaryPath.appending("Package.swift"), string: """ @@ -138,6 +138,7 @@ class ToolsVersionTests: XCTestCase { _ = try SwiftPM.Package.execute( ["tools-version", "--set", "4.2"], packagePath: primaryPath).stdout.spm_chomp() _ = try SwiftPM.Build.execute(packagePath: primaryPath) +#endif } } } diff --git a/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift b/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift index 26fc46b16d9..5de852f11ca 100644 --- a/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift +++ b/Tests/PackageCollectionsTests/GitHubPackageMetadataProviderTests.swift @@ -339,7 +339,7 @@ class GitHubPackageMetadataProviderTests: XCTestCase { httpClient.configuration.requestHeaders = .init() httpClient.configuration.requestHeaders!.add(name: "Cache-Control", value: "no-cache") var configuration = GitHubPackageMetadataProvider.Configuration(disableCache: true) // Disable cache so we hit the API - if let token = ProcessEnv.vars["GITHUB_API_TOKEN"] { + if let token = ProcessEnv.block["GITHUB_API_TOKEN"] { configuration.authTokens = { [.github("github.com"): token] } } configuration.apiLimitWarningThreshold = 50 diff --git a/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift b/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift index e5c0998676d..054cf84ea20 100644 --- a/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/DependencyResolverPerfTests.swift @@ -69,7 +69,7 @@ class DependencyResolverRealWorldPerfTests: XCTestCasePerf { switch resolver.solve(constraints: graph.constraints) { case .success(let result): let result: [(container: PackageReference, version: Version)] = result.compactMap { - guard case .version(let version) = $0.boundVersion else { + guard case .version(let version, _) = $0.boundVersion else { XCTFail("Unexpected result") return nil } @@ -95,7 +95,7 @@ class DependencyResolverRealWorldPerfTests: XCTestCasePerf { // MARK: - JSON -public extension MockDependencyGraph { +extension MockDependencyGraph { init(_ json: JSON) { guard case .dictionary(let dict) = json else { fatalError() } guard case .string(let name)? = dict["name"] else { fatalError() } @@ -201,7 +201,7 @@ extension ProductFilter { } } -#if swift(<5.11) +#if swift(<6.0) extension ProductFilter: JSONSerializable, JSONMappable {} #else extension ProductFilter: @retroactive JSONSerializable, @retroactive JSONMappable {} diff --git a/Tests/PackageGraphPerformanceTests/Inputs/kitura.json b/Tests/PackageGraphPerformanceTests/Inputs/kitura.json index 1fe7e8f72b5..c3fd458a10a 100644 --- a/Tests/PackageGraphPerformanceTests/Inputs/kitura.json +++ b/Tests/PackageGraphPerformanceTests/Inputs/kitura.json @@ -118,7 +118,7 @@ "0.5.0": [], "0.5.1": [], "0.5.10": [], - "0.5.11": [], + "0.6.0": [], "0.5.12": [], "0.5.13": [], "0.5.14": [], diff --git a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift index 6cd5f755c80..c469caa9e88 100644 --- a/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift +++ b/Tests/PackageGraphPerformanceTests/PackageGraphPerfTests.swift @@ -12,7 +12,10 @@ import Basics import OrderedCollections + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph + import PackageLoading import PackageModel import SPMTestSupport @@ -85,7 +88,7 @@ final class PackageGraphPerfTests: XCTestCasePerf { measure { let observability = ObservabilitySystem.makeForTesting() - let g = try! PackageGraph.load( + let g = try! ModulesGraph.load( root: PackageGraphRoot( input: PackageGraphRootInput(packages: [rootManifest.path]), manifests: [rootManifest.path: rootManifest], @@ -150,7 +153,7 @@ final class PackageGraphPerfTests: XCTestCasePerf { measure { do { for _ in 0.. Foo -> Bar", severity: .error) + } + } + + func testDependencyCycleWithoutTargetCycle() throws { let fs = InMemoryFileSystem(emptyFiles: "/Foo/Sources/Foo/source.swift", "/Bar/Sources/Bar/source.swift", - "/Baz/Sources/Baz/source.swift" + "/Bar/Sources/Baz/source.swift" ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( displayName: "Foo", path: "/Foo", dependencies: [ - .localSourceControl(path: "/Foo", requirement: .upToNextMajor(from: "1.0.0")) + .localSourceControl(path: "/Bar", requirement: .upToNextMajor(from: "1.0.0")) + ], + products: [ + ProductDescription(name: "Foo", type: .library(.automatic), targets: ["Foo"]) ], targets: [ - TargetDescription(name: "Foo"), + TargetDescription(name: "Foo", dependencies: ["Bar"]), ]), + Manifest.createFileSystemManifest( + displayName: "Bar", + path: "/Bar", + dependencies: [ + .localSourceControl(path: "/Foo", requirement: .upToNextMajor(from: "1.0.0")) + ], + products: [ + ProductDescription(name: "Bar", type: .library(.automatic), targets: ["Bar"]), + ProductDescription(name: "Baz", type: .library(.automatic), targets: ["Baz"]) + ], + targets: [ + TargetDescription(name: "Bar"), + TargetDescription(name: "Baz", dependencies: ["Foo"]), + ]) ], observabilityScope: observability.topScope ) - testDiagnostics(observability.diagnostics) { result in - result.check(diagnostic: "cyclic dependency declaration found: Foo -> Foo", severity: .error) + XCTAssertNoDiagnostics(observability.diagnostics) + PackageGraphTester(graph) { result in + result.check(packages: "Foo", "Bar") + result.check(targets: "Bar", "Baz", "Foo") } } @@ -228,7 +275,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let g = try loadPackageGraph( + let g = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -272,7 +319,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let g = try loadPackageGraph( + let g = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -312,7 +359,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -350,7 +397,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -413,7 +460,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -481,7 +528,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createFileSystemManifest( @@ -548,7 +595,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -598,7 +645,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -648,7 +695,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -693,7 +740,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -736,7 +783,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let g = try loadPackageGraph( + let g = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -765,7 +812,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -793,7 +840,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -827,7 +874,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -861,7 +908,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -892,7 +939,7 @@ class PackageGraphTests: XCTestCase { "/XYZ/Tests/XYZTests/tests.swift" ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -916,7 +963,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -941,7 +988,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -970,7 +1017,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1002,7 +1049,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1079,7 +1126,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1118,7 +1165,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1180,7 +1227,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1214,7 +1261,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1270,7 +1317,7 @@ class PackageGraphTests: XCTestCase { let bazPkg: AbsolutePath = "/Baz" let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadPackageGraph( + XCTAssertThrowsError(try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1321,7 +1368,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1393,7 +1440,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1475,7 +1522,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1596,7 +1643,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -1682,7 +1729,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1719,7 +1766,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1763,7 +1810,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1800,7 +1847,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1868,7 +1915,7 @@ class PackageGraphTests: XCTestCase { do { let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) testDiagnostics(observability.diagnostics) { result in result.check( diagnostic: """ @@ -1890,7 +1937,7 @@ class PackageGraphTests: XCTestCase { ] let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } } @@ -1928,7 +1975,7 @@ class PackageGraphTests: XCTestCase { do { let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) testDiagnostics(observability.diagnostics) { result in result.check( diagnostic: """ @@ -1951,7 +1998,7 @@ class PackageGraphTests: XCTestCase { ] let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } } @@ -1987,7 +2034,7 @@ class PackageGraphTests: XCTestCase { do { let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) testDiagnostics(observability.diagnostics) { result in result.check( diagnostic: """ @@ -2009,7 +2056,7 @@ class PackageGraphTests: XCTestCase { ] let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } } @@ -2045,7 +2092,7 @@ class PackageGraphTests: XCTestCase { do { let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) testDiagnostics(observability.diagnostics) { result in let diagnostic = result.check( diagnostic: """ @@ -2068,7 +2115,7 @@ class PackageGraphTests: XCTestCase { ] let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } } @@ -2105,7 +2152,7 @@ class PackageGraphTests: XCTestCase { ] let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } @@ -2142,7 +2189,7 @@ class PackageGraphTests: XCTestCase { do { let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: manifests, observabilityScope: observability.topScope) testDiagnostics(observability.diagnostics) { result in let diagnostic = result.check( diagnostic: """ @@ -2165,7 +2212,7 @@ class PackageGraphTests: XCTestCase { ] let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) + _ = try loadModulesGraph(fileSystem: fs, manifests: fixedManifests, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } } @@ -2277,7 +2324,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [manifest], customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, @@ -2373,7 +2420,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [manifest], observabilityScope: observability.topScope @@ -2433,7 +2480,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [manifest], customXCTestMinimumDeploymentTargets: customXCTestMinimumDeploymentTargets, @@ -2506,7 +2553,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [manifest], observabilityScope: observability.topScope @@ -2549,7 +2596,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [manifest], observabilityScope: observability.topScope @@ -2589,7 +2636,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -2648,7 +2695,7 @@ class PackageGraphTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - _ = try loadPackageGraph( + _ = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -2677,6 +2724,57 @@ class PackageGraphTests: XCTestCase { XCTAssertEqual(observability.diagnostics.count, 0, "unexpected diagnostics: \(observability.diagnostics.map { $0.description })") } + + func testDependencyResolutionWithErrorMessages() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/aaa/Sources/aaa/main.swift", + "/zzz/Sources/zzz/source.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let _ = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "aaa", + path: "/aaa", + dependencies: [ + .localSourceControl(path: "/zzz", requirement: .upToNextMajor(from: "1.0.0")) + ], + products: [], + targets: [ + TargetDescription( + name: "aaa", + dependencies: ["zzy"], + type: .executable + ) + ]), + Manifest.createRootManifest( + displayName: "zzz", + path: "/zzz", + products: [ + ProductDescription( + name: "zzz", + type: .library(.automatic), + targets: ["zzz"] + ) + ], + targets: [ + TargetDescription( + name: "zzz" + ) + ]) + ], + observabilityScope: observability.topScope + ) + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "product 'zzy' required by package 'aaa' target 'aaa' not found. Did you mean 'zzz'?", + severity: .error + ) + } + } } diff --git a/Tests/PackageGraphTests/PubgrubTests.swift b/Tests/PackageGraphTests/PubgrubTests.swift index 79285eb5f91..850ab22a48b 100644 --- a/Tests/PackageGraphTests/PubgrubTests.swift +++ b/Tests/PackageGraphTests/PubgrubTests.swift @@ -54,11 +54,11 @@ private let v1_1: Version = "1.1.0" private let v1_5: Version = "1.5.0" private let v2: Version = "2.0.0" private let v3: Version = "3.0.0" -private let v1Range: VersionSetSpecifier = .range(v1..=1.0.0 <1.5.0 XCTAssertEqual( Term("a^1.0.0").intersect(with: Term("¬a@1.5.0")), - Term("a-1.0.0-1.5.0")) + Term("a-1.0.0-1.5.0") + ) // a^1.0.0 ∩ a >=1.5.0 <3.0.0 → a^1.5.0 XCTAssertEqual( Term("a^1.0.0").intersect(with: Term("a-1.5.0-3.0.0")), - Term("a^1.5.0")) + Term("a^1.5.0") + ) // ¬a^1.0.0 ∩ ¬a >=1.5.0 <3.0.0 → ¬a >=1.0.0 <3.0.0 XCTAssertEqual( Term("¬a^1.0.0").intersect(with: Term("¬a-1.5.0-3.0.0")), - Term("¬a-1.0.0-3.0.0")) + Term("¬a-1.0.0-3.0.0") + ) XCTAssertEqual( Term("a^1.0.0").intersect(with: Term("a^1.0.0")), - Term("a^1.0.0")) + Term("a^1.0.0") + ) XCTAssertEqual( Term("¬a^1.0.0").intersect(with: Term("¬a^1.0.0")), - Term("¬a^1.0.0")) + Term("¬a^1.0.0") + ) XCTAssertNil(Term("a^1.0.0").intersect(with: Term("¬a^1.0.0"))) XCTAssertNil(Term("a@1.0.0").difference(with: Term("a@1.0.0"))) XCTAssertEqual( Term("¬a^1.0.0").intersect(with: Term("a^2.0.0")), - Term("a^2.0.0")) + Term("a^2.0.0") + ) XCTAssertEqual( Term("a^2.0.0").intersect(with: Term("¬a^1.0.0")), - Term("a^2.0.0")) + Term("a^2.0.0") + ) XCTAssertEqual( Term("¬a^1.0.0").intersect(with: Term("¬a^1.0.0")), - Term("¬a^1.0.0")) + Term("¬a^1.0.0") + ) XCTAssertEqual( Term("¬a@1.0.0").intersect(with: Term("¬a@1.0.0")), - Term("¬a@1.0.0")) + Term("¬a@1.0.0") + ) // Check difference. let anyA = Term(.empty(package: "a"), .any) @@ -184,7 +193,7 @@ final class PubgrubTests: XCTestCase { func testTermIsValidDecision() { let solution100_150 = PartialSolution(assignments: [ .derivation("a^1.0.0", cause: _cause, decisionLevel: 1), - .derivation("a^1.5.0", cause: _cause, decisionLevel: 2) + .derivation("a^1.5.0", cause: _cause, decisionLevel: 2), ]) let allSatisfied = Term("a@1.6.0") @@ -194,26 +203,33 @@ final class PubgrubTests: XCTestCase { } func testIncompatibilityNormalizeTermsOnInit() throws { - let i1 = try Incompatibility(Term("a^1.0.0"), Term("a^1.5.0"), Term("¬b@1.0.0"), - root: rootNode) + let i1 = try Incompatibility( + Term("a^1.0.0"), + Term("a^1.5.0"), + Term("¬b@1.0.0"), + root: rootNode + ) XCTAssertEqual(i1.terms.count, 2) let a1 = i1.terms.first { $0.node.package == "a" } let b1 = i1.terms.first { $0.node.package == "b" } XCTAssertEqual(a1?.requirement, v1_5Range) XCTAssertEqual(b1?.requirement, .exact(v1)) - let i2 = try Incompatibility(Term("¬a^1.0.0"), Term("a^2.0.0"), - root: rootNode) + let i2 = try Incompatibility( + Term("¬a^1.0.0"), + Term("a^2.0.0"), + root: rootNode + ) XCTAssertEqual(i2.terms.count, 1) let a2 = i2.terms.first XCTAssertEqual(a2?.requirement, v2Range) } func testSolutionPositive() { - let s1 = PartialSolution(assignments:[ + let s1 = PartialSolution(assignments: [ .derivation("a^1.5.0", cause: _cause, decisionLevel: 0), .derivation("b@2.0.0", cause: _cause, decisionLevel: 0), - .derivation("a^1.0.0", cause: _cause, decisionLevel: 0) + .derivation("a^1.0.0", cause: _cause, decisionLevel: 0), ]) let a1 = s1._positive.first { $0.key.package.identity == PackageIdentity("a") }?.value XCTAssertEqual(a1?.requirement, v1_5Range) @@ -222,10 +238,10 @@ final class PubgrubTests: XCTestCase { let s2 = PartialSolution(assignments: [ .derivation("¬a^1.5.0", cause: _cause, decisionLevel: 0), - .derivation("a^1.0.0", cause: _cause, decisionLevel: 0) + .derivation("a^1.0.0", cause: _cause, decisionLevel: 0), ]) let a2 = s2._positive.first { $0.key.package.identity == PackageIdentity("a") }?.value - XCTAssertEqual(a2?.requirement, .range(v1.. non-versioned -> version func testHappyPath2() throws { try builder.serve("foo", at: v1_1, with: ["foo": ["config": (.versionSet(v1Range), .specific(["config"]))]]) - try builder.serve("bar", at: .unversioned, with: ["bar": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) - try builder.serve("config", at: v1) - try builder.serve("config", at: v1_1) + try builder.serve( + "bar", + at: .unversioned, + with: ["bar": ["config": (.versionSet(v1_1Range), .specific(["config"]))]] + ) + try builder.serve("config", at: v1) + try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.unversioned, .specific(["bar"])) + "bar": (.unversioned, .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) @@ -997,14 +1091,18 @@ final class PubgrubTests: XCTestCase { func testHappyPath3() throws { try builder.serve("foo", at: v1_1, with: ["foo": ["config": (.versionSet(v1Range), .specific(["config"]))]]) try builder.serve("bar", at: .unversioned, with: ["bar": ["baz": (.unversioned, .specific(["baz"]))]]) - try builder.serve("baz", at: .unversioned, with: ["baz": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) + try builder.serve( + "baz", + at: .unversioned, + with: ["baz": ["config": (.versionSet(v1_1Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.unversioned, .specific(["bar"])) + "bar": (.unversioned, .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) @@ -1027,7 +1125,7 @@ final class PubgrubTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.versionSet(v1Range), .specific(["foo"])) + "foo": (.versionSet(v1Range), .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) @@ -1041,14 +1139,18 @@ final class PubgrubTests: XCTestCase { // root -> version // root -> non-versioned -> version func testHappyPath5() throws { - try builder.serve("foo", at: .unversioned, with: ["foo": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) + try builder.serve( + "foo", + at: .unversioned, + with: ["foo": ["config": (.versionSet(v1_1Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) + "foo": (.unversioned, .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) @@ -1059,19 +1161,22 @@ final class PubgrubTests: XCTestCase { ]) } - // root -> version // root -> non-versioned -> non-versioned -> version func testHappyPath6() throws { try builder.serve("foo", at: .unversioned, with: ["foo": ["bar": (.unversioned, .specific(["bar"]))]]) - try builder.serve("bar", at: .unversioned, with: ["bar": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) + try builder.serve( + "bar", + at: .unversioned, + with: ["bar": ["config": (.versionSet(v1_1Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) + "foo": (.unversioned, .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) @@ -1090,15 +1195,16 @@ final class PubgrubTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.versionSet(v1Range), .specific(["foo"])) - ]]) + "foo": (.versionSet(v1Range), .specific(["foo"])), + ], + ]) try builder.serve("foo", at: v1_1, with: ["foo": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) try builder.serve("config", at: v1) try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) @@ -1110,7 +1216,6 @@ final class PubgrubTests: XCTestCase { ]) } - // top level package -> version // top level package -> non-versioned -> version func testHappyPath8() throws { @@ -1118,15 +1223,20 @@ final class PubgrubTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) - ]]) - try builder.serve("foo", at: .unversioned, with: ["foo": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) + "foo": (.unversioned, .specific(["foo"])), + ], + ]) + try builder.serve( + "foo", + at: .unversioned, + with: ["foo": ["config": (.versionSet(v1_1Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) @@ -1145,17 +1255,22 @@ final class PubgrubTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) - ]]) + "foo": (.unversioned, .specific(["foo"])), + ], + ]) try builder.serve("foo", at: .unversioned, with: ["foo": ["bar": (.unversioned, .specific(["bar"]))]]) try builder.serve("bar", at: .unversioned, with: ["bar": ["baz": (.unversioned, .specific(["baz"]))]]) - try builder.serve("baz", at: .unversioned, with: ["baz": ["config": (.versionSet(v1_1Range), .specific(["config"]))]]) + try builder.serve( + "baz", + at: .unversioned, + with: ["baz": ["config": (.versionSet(v1_1Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v1_1) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) @@ -1175,10 +1290,19 @@ final class PubgrubTests: XCTestCase { let package = PackageReference.root(identity: .plain("package"), path: .root) try builder.serve(package, at: .unversioned, with: [ "module": [ - "foo": (.versionSet(.range("1.0.0-alpha" ..< "2.0.0")), .specific(["foo"])) - ]]) - try builder.serve("foo", at: "1.0.0-alpha.1", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) - try builder.serve("foo", at: "1.0.0-alpha.2", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) + "foo": (.versionSet(.range("1.0.0-alpha" ..< "2.0.0")), .specific(["foo"])), + ], + ]) + try builder.serve( + "foo", + at: "1.0.0-alpha.1", + with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]] + ) + try builder.serve( + "foo", + at: "1.0.0-alpha.2", + with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]] + ) try builder.serve("foo", at: "1.0.0-beta.1", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) try builder.serve("foo", at: "1.0.0-beta.2", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) try builder.serve("foo", at: "1.0.0-beta.3", with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) @@ -1188,7 +1312,7 @@ final class PubgrubTests: XCTestCase { let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) @@ -1196,29 +1320,37 @@ final class PubgrubTests: XCTestCase { AssertResult(result, [ ("package", .unversioned), ("foo", .version("1.0.0-beta.3")), - ("bar", .version(v1_5)) + ("bar", .version(v1_5)), ]) } func testResolutionWithSimpleBranchBasedDependency() throws { - try builder.serve("foo", at: .revision("master"), with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) + try builder.serve( + "foo", + at: .revision("master"), + with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]] + ) try builder.serve("bar", at: v1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.revision("master"), .specific(["foo"])), - "bar": (.versionSet(v1Range), .specific(["bar"])) + "bar": (.versionSet(v1Range), .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) AssertResult(result, [ ("foo", .revision("master")), - ("bar", .version(v1)) + ("bar", .version(v1)), ]) } func testResolutionWithSimpleBranchBasedDependency2() throws { - try builder.serve("foo", at: .revision("master"), with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) + try builder.serve( + "foo", + at: .revision("master"), + with: ["foo": ["bar": (.versionSet(v1Range), .specific(["bar"]))]] + ) try builder.serve("bar", at: v1) let resolver = builder.create() @@ -1229,7 +1361,7 @@ final class PubgrubTests: XCTestCase { AssertResult(result, [ ("foo", .revision("master")), - ("bar", .version(v1)) + ("bar", .version(v1)), ]) } @@ -1247,7 +1379,7 @@ final class PubgrubTests: XCTestCase { AssertResult(result, [ ("foo", .revision("master")), - ("bar", .version(v1)) + ("bar", .version(v1)), ]) } @@ -1264,17 +1396,25 @@ final class PubgrubTests: XCTestCase { AssertResult(result, [ ("foo", .revision("master")), - ("bar", .version(v1)) + ("bar", .version(v1)), ]) } func testResolutionWithOverridingBranchBasedDependency3() throws { - try builder.serve("foo", at: .revision("master"), with: ["foo": ["bar": (.revision("master"), .specific(["bar"]))]]) + try builder.serve( + "foo", + at: .revision("master"), + with: ["foo": ["bar": (.revision("master"), .specific(["bar"]))]] + ) try builder.serve("bar", at: .revision("master")) try builder.serve("bar", at: v1) - try builder.serve("baz", at: .revision("master"), with: ["baz": ["bar": (.versionSet(v1Range), .specific(["bar"]))]]) + try builder.serve( + "baz", + at: .revision("master"), + with: ["baz": ["bar": (.versionSet(v1Range), .specific(["bar"]))]] + ) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ @@ -1295,7 +1435,7 @@ final class PubgrubTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "foo": (.revision("master"), .specific(["foo"])) + "foo": (.revision("master"), .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) @@ -1303,7 +1443,11 @@ final class PubgrubTests: XCTestCase { } func testResolutionWithRevisionConflict() throws { - try builder.serve("foo", at: .revision("master"), with: ["foo": ["bar": (.revision("master"), .specific(["bar"]))]]) + try builder.serve( + "foo", + at: .revision("master"), + with: ["foo": ["bar": (.revision("master"), .specific(["bar"]))]] + ) try builder.serve("bar", at: .version(v1)) try builder.serve("bar", at: .revision("master")) @@ -1341,7 +1485,7 @@ final class PubgrubTests: XCTestCase { AssertResult(result, [ ("swift-nio-ssl", .revision("master")), ("swift-nio", .revision("master")), - ("foo", .version(v1)) + ("foo", .version(v1)), ]) } @@ -1355,13 +1499,13 @@ final class PubgrubTests: XCTestCase { "nio-postgres": [ "swift-nio": (.revision("master"), .specific(["swift-nio"])), "swift-nio-ssl": (.revision("master"), .specific(["swift-nio-ssl"])), - ] + ], ]) try builder.serve("http-client", at: v1, with: [ "http-client": [ "swift-nio": (.versionSet(v1Range), .specific(["swift-nio"])), "boring-ssl": (.versionSet(v1Range), .specific(["boring-ssl"])), - ] + ], ]) try builder.serve("boring-ssl", at: v1, with: [ "boring-ssl": ["swift-nio": (.versionSet(v1Range), .specific(["swift-nio"]))], @@ -1386,7 +1530,7 @@ final class PubgrubTests: XCTestCase { func testNonVersionDependencyInVersionDependency2() throws { try builder.serve("foo", at: v1_1, with: [ - "foo": ["bar": (.revision("master"), .specific(["bar"]))] + "foo": ["bar": (.revision("master"), .specific(["bar"]))], ]) try builder.serve("foo", at: v1) let resolver = builder.create() @@ -1420,12 +1564,15 @@ final class PubgrubTests: XCTestCase { let result = try resolver.solve(root: rootNode, constraints: dependencies) // Since a was pinned, we shouldn't have computed bounds for its incomaptibilities. - let aIncompat = result.state.positiveIncompatibilities(for: .product("a", package: try builder.reference(for: "a")))![0] + let aIncompat = try result.state.positiveIncompatibilities(for: .product( + "a", + package: builder.reference(for: "a") + ))![0] XCTAssertEqual(aIncompat.terms[0].requirement, .exact("1.0.0")) AssertResult(Result.success(result.bindings), [ ("a", .version(v1)), - ("b", .version(v1)) + ("b", .version(v1)), ]) } @@ -1436,7 +1583,7 @@ final class PubgrubTests: XCTestCase { try builder.serve("a", at: v1_1) try builder.serve("b", at: v1) try builder.serve("b", at: v1_1) - try builder.serve("c", at: v1, with: ["c": ["b": (.versionSet(.range(v1_1..= 1.0.0 practically depends on 'baz' 3.0.0..<4.0.0. - 'bar' >= 2.0.0 practically depends on 'baz' 3.0.0..<4.0.0 because 'bar' 2.0.0 depends on 'baz' 3.0.0..<4.0.0 and no versions of 'bar' match the requirement 2.0.1..<3.0.0. - 'foo' >= 1.0.0 practically depends on 'bar' 2.0.0..<3.0.0 because 'foo' 1.0.0 depends on 'bar' 2.0.0..<3.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. - """) + Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'baz' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 practically depends on 'baz' 3.0.0..<4.0.0. + 'bar' >= 2.0.0 practically depends on 'baz' 3.0.0..<4.0.0 because 'bar' 2.0.0 depends on 'baz' 3.0.0..<4.0.0 and no versions of 'bar' match the requirement 2.0.1..<3.0.0. + 'foo' >= 1.0.0 practically depends on 'bar' 2.0.0..<3.0.0 because 'foo' 1.0.0 depends on 'bar' 2.0.0..<3.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. + """) } func testResolutionBranchingErrorReporting() throws { try builder.serve("foo", at: v1, with: [ "foo": [ "a": (.versionSet(v1Range), .specific(["a"])), - "b": (.versionSet(v1Range), .specific(["b"])) - ] + "b": (.versionSet(v1Range), .specific(["b"])), + ], ]) try builder.serve("foo", at: v1_1, with: [ "foo": [ "x": (.versionSet(v1Range), .specific(["x"])), - "y": (.versionSet(v1Range), .specific(["y"])) - ] + "y": (.versionSet(v1Range), .specific(["y"])), + ], ]) try builder.serve("a", at: v1, with: ["a": ["b": (.versionSet(v2Range), .specific(["b"]))]]) try builder.serve("b", at: v1) @@ -2006,14 +2315,14 @@ final class PubGrubDiagnosticsTests: XCTestCase { print(result.errorMsg!) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0. - 'foo' >= 1.0.0 cannot be used because 'foo' {1.0.0..<1.1.0, 1.1.1..<2.0.0} cannot be used (1). - 'foo' 1.1.0 cannot be used because 'foo' 1.1.0 depends on 'x' 1.0.0..<2.0.0 and 'foo' 1.1.0 depends on 'y' 1.0.0..<2.0.0. - 'x' >= 1.0.0 practically depends on 'y' 2.0.0..<3.0.0 because 'x' 1.0.0 depends on 'y' 2.0.0..<3.0.0 and no versions of 'x' match the requirement 1.0.1..<2.0.0. - 'foo' 1.0.0 practically depends on 'b' 2.0.0..<3.0.0 because 'foo' 1.0.0 depends on 'a' 1.0.0..<2.0.0. - 'a' >= 1.0.0 practically depends on 'b' 2.0.0..<3.0.0 because 'a' 1.0.0 depends on 'b' 2.0.0..<3.0.0 and no versions of 'a' match the requirement 1.0.1..<2.0.0. - (1) As a result, 'foo' {1.0.0..<1.1.0, 1.1.1..<2.0.0} cannot be used because 'foo' 1.0.0 depends on 'b' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement {1.0.1..<1.1.0, 1.1.1..<2.0.0}. - """) + Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 cannot be used because 'foo' {1.0.0..<1.1.0, 1.1.1..<2.0.0} cannot be used (1). + 'foo' 1.1.0 cannot be used because 'foo' 1.1.0 depends on 'x' 1.0.0..<2.0.0 and 'foo' 1.1.0 depends on 'y' 1.0.0..<2.0.0. + 'x' >= 1.0.0 practically depends on 'y' 2.0.0..<3.0.0 because 'x' 1.0.0 depends on 'y' 2.0.0..<3.0.0 and no versions of 'x' match the requirement 1.0.1..<2.0.0. + 'foo' 1.0.0 practically depends on 'b' 2.0.0..<3.0.0 because 'foo' 1.0.0 depends on 'a' 1.0.0..<2.0.0. + 'a' >= 1.0.0 practically depends on 'b' 2.0.0..<3.0.0 because 'a' 1.0.0 depends on 'b' 2.0.0..<3.0.0 and no versions of 'a' match the requirement 1.0.1..<2.0.0. + (1) As a result, 'foo' {1.0.0..<1.1.0, 1.1.1..<2.0.0} cannot be used because 'foo' 1.0.0 depends on 'b' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement {1.0.1..<1.1.0, 1.1.1..<2.0.0}. + """) } func testConflict1() throws { @@ -2025,15 +2334,15 @@ final class PubGrubDiagnosticsTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.versionSet(v1Range), .specific(["bar"])) + "bar": (.versionSet(v1Range), .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'bar' 1.0.0..<2.0.0. - 'bar' is incompatible with 'foo' because 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. - 'bar' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'bar' match the requirement 1.0.1..<2.0.0 and 'bar' 1.0.0 depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'bar' 1.0.0..<2.0.0. + 'bar' is incompatible with 'foo' because 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. + 'bar' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'bar' match the requirement 1.0.1..<2.0.0 and 'bar' 1.0.0 depends on 'config' 2.0.0..<3.0.0. + """) } func testConflict2() throws { @@ -2052,9 +2361,9 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result1 = resolver1.solve(constraints: dependencies1) XCTAssertEqual(result1.errorMsg, """ - Dependencies could not be resolved because root depends on 'config' 2.0.0..<3.0.0 and root depends on 'foo' 1.0.0..<2.0.0. - 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0. - """) + Dependencies could not be resolved because root depends on 'config' 2.0.0..<3.0.0 and root depends on 'foo' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0. + """) let dependencies2 = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), @@ -2065,9 +2374,9 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result2 = resolver2.solve(constraints: dependencies2) XCTAssertEqual(result2.errorMsg, """ - Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'config' 2.0.0..<3.0.0. - 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. - """) + Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'config' 2.0.0..<3.0.0. + 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. + """) } func testConflict3() throws { @@ -2082,16 +2391,16 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because no versions of 'config' match the requirement 2.0.0..<3.0.0 and root depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because no versions of 'config' match the requirement 2.0.0..<3.0.0 and root depends on 'config' 2.0.0..<3.0.0. + """) } func testConflict4() throws { try builder.serve("foo", at: v1, with: [ - "foo": ["shared": (.versionSet(.range("2.0.0"..<"3.0.0")), .specific(["shared"]))], + "foo": ["shared": (.versionSet(.range("2.0.0" ..< "3.0.0")), .specific(["shared"]))], ]) try builder.serve("bar", at: v1, with: [ - "bar": ["shared": (.versionSet(.range("2.9.0"..<"4.0.0")), .specific(["shared"]))], + "bar": ["shared": (.versionSet(.range("2.9.0" ..< "4.0.0")), .specific(["shared"]))], ]) try builder.serve("shared", at: "2.5.0") try builder.serve("shared", at: "3.5.0") @@ -2105,10 +2414,10 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'bar' 1.0.0 and root depends on 'foo' 1.0.0. - 'foo' is incompatible with 'bar' because 'foo' 1.0.0 depends on 'shared' 2.0.0..<3.0.0. - 'bar' 1.0.0 practically depends on 'shared' 3.0.0..<4.0.0 because 'bar' 1.0.0 depends on 'shared' 2.9.0..<4.0.0 and no versions of 'shared' match the requirement 2.9.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'bar' 1.0.0 and root depends on 'foo' 1.0.0. + 'foo' is incompatible with 'bar' because 'foo' 1.0.0 depends on 'shared' 2.0.0..<3.0.0. + 'bar' 1.0.0 practically depends on 'shared' 3.0.0..<4.0.0 because 'bar' 1.0.0 depends on 'shared' 2.9.0..<4.0.0 and no versions of 'shared' match the requirement 2.9.0..<3.0.0. + """) } func testConflict5() throws { @@ -2127,19 +2436,19 @@ final class PubGrubDiagnosticsTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "b": (.versionSet(.range("0.0.0"..<"5.0.0")), .specific(["b"])), - "a": (.versionSet(.range("0.0.0"..<"5.0.0")), .specific(["a"])), + "b": (.versionSet(.range("0.0.0" ..< "5.0.0")), .specific(["b"])), + "a": (.versionSet(.range("0.0.0" ..< "5.0.0")), .specific(["a"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'a' 0.0.0..<5.0.0. - 'a' cannot be used. - 'a' 2.0.0 cannot be used because 'b' 2.0.0 depends on 'a' 1.0.0 and 'a' 2.0.0 depends on 'b' 2.0.0. - 'a' {0.0.0..<2.0.0, 2.0.1..<5.0.0} cannot be used because 'b' 1.0.0 depends on 'a' 2.0.0. - 'a' {0.0.0..<2.0.0, 2.0.1..<5.0.0} practically depends on 'b' 1.0.0 because no versions of 'a' match the requirement {0.0.0..<1.0.0, 1.0.1..<2.0.0, 2.0.1..<5.0.0} and 'a' 1.0.0 depends on 'b' 1.0.0. - """) + Dependencies could not be resolved because root depends on 'a' 0.0.0..<5.0.0. + 'a' cannot be used. + 'a' 2.0.0 cannot be used because 'b' 2.0.0 depends on 'a' 1.0.0 and 'a' 2.0.0 depends on 'b' 2.0.0. + 'a' {0.0.0..<2.0.0, 2.0.1..<5.0.0} cannot be used because 'b' 1.0.0 depends on 'a' 2.0.0. + 'a' {0.0.0..<2.0.0, 2.0.1..<5.0.0} practically depends on 'b' 1.0.0 because no versions of 'a' match the requirement {0.0.0..<1.0.0, 1.0.1..<2.0.0, 2.0.1..<5.0.0} and 'a' 1.0.0 depends on 'b' 1.0.0. + """) } // root -> version -> version @@ -2153,38 +2462,42 @@ final class PubGrubDiagnosticsTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.versionSet(v1Range), .specific(["bar"])) + "bar": (.versionSet(v1Range), .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'bar' 1.0.0..<2.0.0. - 'bar' is incompatible with 'foo' because 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. - 'bar' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'bar' match the requirement 1.0.1..<2.0.0 and 'bar' 1.0.0 depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0 and root depends on 'bar' 1.0.0..<2.0.0. + 'bar' is incompatible with 'foo' because 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0 and no versions of 'foo' match the requirement 1.0.1..<2.0.0. + 'bar' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'bar' match the requirement 1.0.1..<2.0.0 and 'bar' 1.0.0 depends on 'config' 2.0.0..<3.0.0. + """) } // root -> version -> version // root -> non-versioned -> conflicting version func testConflict7() throws { try builder.serve("foo", at: v1, with: ["foo": ["config": (.versionSet(v1Range), .specific(["config"]))]]) - try builder.serve("bar", at: .unversioned, with: ["bar": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "bar", + at: .unversioned, + with: ["bar": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v2) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.unversioned, .specific(["bar"])) + "bar": (.unversioned, .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'bar' depends on 'config' 2.0.0..<3.0.0 and root depends on 'foo' 1.0.0..<2.0.0. - 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0. - """) + Dependencies could not be resolved because 'bar' depends on 'config' 2.0.0..<3.0.0 and root depends on 'foo' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0. + """) } // root -> version -> version @@ -2192,81 +2505,97 @@ final class PubGrubDiagnosticsTests: XCTestCase { func testConflict8() throws { try builder.serve("foo", at: v1, with: ["foo": ["config": (.versionSet(v1Range), .specific(["config"]))]]) try builder.serve("bar", at: .unversioned, with: ["bar": ["baz": (.unversioned, .specific(["baz"]))]]) - try builder.serve("baz", at: .unversioned, with: ["baz": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "baz", + at: .unversioned, + with: ["baz": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v2) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.unversioned, .specific(["bar"])) + "bar": (.unversioned, .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'baz' depends on 'config' 2.0.0..<3.0.0 and root depends on 'foo' 1.0.0..<2.0.0. - 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0. - """) + Dependencies could not be resolved because 'baz' depends on 'config' 2.0.0..<3.0.0 and root depends on 'foo' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 practically depends on 'config' 1.0.0..<2.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 1.0.0..<2.0.0. + """) } // root -> version -> version // root -> non-versioned -> non-existing version func testConflict9() throws { try builder.serve("foo", at: v1, with: ["foo": ["config": (.versionSet(v1Range), .specific(["config"]))]]) - try builder.serve("bar", at: .unversioned, with: ["bar": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "bar", + at: .unversioned, + with: ["bar": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "foo": (.versionSet(v1Range), .specific(["foo"])), - "bar": (.unversioned, .specific(["bar"])) + "bar": (.unversioned, .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because no versions of 'config' match the requirement 2.0.0..<3.0.0 and 'bar' depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because no versions of 'config' match the requirement 2.0.0..<3.0.0 and 'bar' depends on 'config' 2.0.0..<3.0.0. + """) } // root -> version // root -> non-versioned -> conflicting version func testConflict10() throws { - try builder.serve("foo", at: .unversioned, with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "foo", + at: .unversioned, + with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v2) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) + "foo": (.unversioned, .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'foo' depends on 'config' 2.0.0..<3.0.0 and root depends on 'config' 1.0.0..<2.0.0. - """) + Dependencies could not be resolved because 'foo' depends on 'config' 2.0.0..<3.0.0 and root depends on 'config' 1.0.0..<2.0.0. + """) } // root -> version // root -> non-versioned -> non-existing version func testConflict11() throws { - try builder.serve("foo", at: .unversioned, with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "foo", + at: .unversioned, + with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) + "foo": (.unversioned, .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'foo' depends on 'config' 2.0.0..<3.0.0 and root depends on 'config' 1.0.0..<2.0.0. - """) + Dependencies could not be resolved because 'foo' depends on 'config' 2.0.0..<3.0.0 and root depends on 'config' 1.0.0..<2.0.0. + """) } // root -> version @@ -2274,20 +2603,24 @@ final class PubGrubDiagnosticsTests: XCTestCase { func testConflict12() throws { try builder.serve("foo", at: .unversioned, with: ["foo": ["bar": (.unversioned, .specific(["bar"]))]]) try builder.serve("bar", at: .unversioned, with: ["bar": ["baz": (.unversioned, .specific(["baz"]))]]) - try builder.serve("baz", at: .unversioned, with: ["baz": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "baz", + at: .unversioned, + with: ["baz": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) + "foo": (.unversioned, .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'baz' depends on 'config' 2.0.0..<3.0.0 and root depends on 'config' 1.0.0..<2.0.0. - """) + Dependencies could not be resolved because 'baz' depends on 'config' 2.0.0..<3.0.0 and root depends on 'config' 1.0.0..<2.0.0. + """) } // top level package -> version @@ -2297,23 +2630,24 @@ final class PubGrubDiagnosticsTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.versionSet(v1Range), .specific(["foo"])) - ]]) + "foo": (.versionSet(v1Range), .specific(["foo"])), + ], + ]) try builder.serve("foo", at: v1, with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]]) try builder.serve("config", at: v1) try builder.serve("config", at: v2) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and root depends on 'foo' 1.0.0..<2.0.0. - 'foo' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and root depends on 'foo' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 2.0.0..<3.0.0. + """) } // top level package -> version @@ -2323,22 +2657,23 @@ final class PubGrubDiagnosticsTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.versionSet(v1Range), .specific(["foo"])) - ]]) + "foo": (.versionSet(v1Range), .specific(["foo"])), + ], + ]) try builder.serve("foo", at: v1, with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]]) try builder.serve("config", at: v1) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and root depends on 'foo' 1.0.0..<2.0.0. - 'foo' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and root depends on 'foo' 1.0.0..<2.0.0. + 'foo' >= 1.0.0 practically depends on 'config' 2.0.0..<3.0.0 because no versions of 'foo' match the requirement 1.0.1..<2.0.0 and 'foo' 1.0.0 depends on 'config' 2.0.0..<3.0.0. + """) } // top level package -> version @@ -2348,22 +2683,27 @@ final class PubGrubDiagnosticsTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) - ]]) - try builder.serve("foo", at: .unversioned, with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + "foo": (.unversioned, .specific(["foo"])), + ], + ]) + try builder.serve( + "foo", + at: .unversioned, + with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v2) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and 'foo' depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and 'foo' depends on 'config' 2.0.0..<3.0.0. + """) } // top level package -> version @@ -2373,21 +2713,26 @@ final class PubGrubDiagnosticsTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) - ]]) - try builder.serve("foo", at: .unversioned, with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + "foo": (.unversioned, .specific(["foo"])), + ], + ]) + try builder.serve( + "foo", + at: .unversioned, + with: ["foo": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and 'foo' depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and 'foo' depends on 'config' 2.0.0..<3.0.0. + """) } // top level package -> version @@ -2397,49 +2742,65 @@ final class PubGrubDiagnosticsTests: XCTestCase { try builder.serve(package, at: .unversioned, with: [ "module": [ "config": (.versionSet(v1Range), .specific(["config"])), - "foo": (.unversioned, .specific(["foo"])) - ]]) + "foo": (.unversioned, .specific(["foo"])), + ], + ]) try builder.serve("foo", at: .unversioned, with: ["foo": ["bar": (.unversioned, .specific(["bar"]))]]) try builder.serve("bar", at: .unversioned, with: ["bar": ["baz": (.unversioned, .specific(["baz"]))]]) - try builder.serve("baz", at: .unversioned, with: ["baz": ["config": (.versionSet(v2Range), .specific(["config"]))]]) + try builder.serve( + "baz", + at: .unversioned, + with: ["baz": ["config": (.versionSet(v2Range), .specific(["config"]))]] + ) try builder.serve("config", at: v1) try builder.serve("config", at: v2) let resolver = builder.create() let dependencies = builder.create(dependencies: [ - package: (.unversioned, .everything) + package: (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and 'baz' depends on 'config' 2.0.0..<3.0.0. - """) + Dependencies could not be resolved because root depends on 'config' 1.0.0..<2.0.0 and 'baz' depends on 'config' 2.0.0..<3.0.0. + """) } func testUnversioned6() throws { try builder.serve("foo", at: .unversioned) try builder.serve("bar", at: .revision("master"), with: [ - "bar": ["foo": (.unversioned, .specific(["foo"]))] + "bar": ["foo": (.unversioned, .specific(["foo"]))], ]) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "bar": (.revision("master"), .specific(["bar"])) + "bar": (.revision("master"), .specific(["bar"])), ]) let result = resolver.solve(constraints: dependencies) - XCTAssertEqual(result.errorMsg, "package 'bar' is required using a revision-based requirement and it depends on local package 'foo', which is not supported") + XCTAssertEqual( + result.errorMsg, + "package 'bar' is required using a revision-based requirement and it depends on local package 'foo', which is not supported" + ) } func testResolutionWithOverridingBranchBasedDependency4() throws { - try builder.serve("foo", at: .revision("master"), with: ["foo": ["bar": (.revision("master"), .specific(["bar"]))]]) + try builder.serve( + "foo", + at: .revision("master"), + with: ["foo": ["bar": (.revision("master"), .specific(["bar"]))]] + ) try builder.serve("bar", at: .revision("master")) try builder.serve("bar", at: v1) - try builder.serve("baz", at: .revision("master"), with: ["baz": ["bar": (.revision("develop"), .specific(["baz"]))]]) + try builder.serve( + "baz", + at: .revision("master"), + with: ["baz": ["bar": (.revision("develop"), .specific(["baz"]))]] + ) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ @@ -2448,12 +2809,15 @@ final class PubGrubDiagnosticsTests: XCTestCase { ]) let result = resolver.solve(constraints: dependencies) - XCTAssertEqual(result.errorMsg, "bar is required using two different revision-based requirements (master and develop), which is not supported") + XCTAssertEqual( + result.errorMsg, + "bar is required using two different revision-based requirements (master and develop), which is not supported" + ) } func testNonVersionDependencyInVersionDependency1() throws { try builder.serve("foo", at: v1_1, with: [ - "foo": ["bar": (.revision("master"), .specific(["bar"]))] + "foo": ["bar": (.revision("master"), .specific(["bar"]))], ]) try builder.serve("bar", at: .revision("master")) @@ -2464,14 +2828,14 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0. - 'foo' cannot be used because no versions of 'foo' match the requirement {1.0.0..<1.1.0, 1.1.1..<2.0.0} and package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'. - """) + Dependencies could not be resolved because root depends on 'foo' 1.0.0..<2.0.0. + 'foo' cannot be used because no versions of 'foo' match the requirement {1.0.0..<1.1.0, 1.1.1..<2.0.0} and package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'. + """) } func testNonVersionDependencyInVersionDependency2() throws { try builder.serve("foo", at: v1, with: [ - "foo": ["bar": (.unversioned, .specific(["bar"]))] + "foo": ["bar": (.unversioned, .specific(["bar"]))], ]) try builder.serve("bar", at: .unversioned) @@ -2482,19 +2846,19 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar' and root depends on 'foo' 1.0.0. - """) + Dependencies could not be resolved because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar' and root depends on 'foo' 1.0.0. + """) } func testNonVersionDependencyInVersionDependency3() throws { try builder.serve("foo", at: "1.0.0-beta.1", with: [ - "foo": ["bar": (.revision("master"), .specific(["bar"]))] + "foo": ["bar": (.revision("master"), .specific(["bar"]))], ]) try builder.serve("foo", at: "1.0.0-beta.2", with: [ - "foo": ["bar": (.revision("master"), .specific(["bar"]))] + "foo": ["bar": (.revision("master"), .specific(["bar"]))], ]) try builder.serve("foo", at: "1.0.0-beta.3", with: [ - "foo": ["bar": (.revision("master"), .specific(["bar"]))] + "foo": ["bar": (.revision("master"), .specific(["bar"]))], ]) try builder.serve("bar", at: .revision("master")) @@ -2505,10 +2869,10 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar' and root depends on 'foo' 1.0.0-beta..<2.0.0. - 'foo' {1.0.0-beta..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} cannot be used because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'. - 'foo' {1.0.0-beta..<1.0.0-beta.2, 1.0.0-beta.2.0..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} cannot be used because no versions of 'foo' match the requirement {1.0.0-beta..<1.0.0-beta.1, 1.0.0-beta.1.0..<1.0.0-beta.2, 1.0.0-beta.2.0..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} and package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'. - """) + Dependencies could not be resolved because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar' and root depends on 'foo' 1.0.0-beta..<2.0.0. + 'foo' {1.0.0-beta..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} cannot be used because package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'. + 'foo' {1.0.0-beta..<1.0.0-beta.2, 1.0.0-beta.2.0..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} cannot be used because no versions of 'foo' match the requirement {1.0.0-beta..<1.0.0-beta.1, 1.0.0-beta.1.0..<1.0.0-beta.2, 1.0.0-beta.2.0..<1.0.0-beta.3, 1.0.0-beta.3.0..<2.0.0} and package 'foo' is required using a stable-version but 'foo' depends on an unstable-version package 'bar'. + """) } func testIncompatibleToolsVersion1() throws { @@ -2522,14 +2886,17 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'a' 1.0.0..<2.0.0. - 'a' >= 1.0.0 cannot be used because no versions of 'a' match the requirement 1.0.1..<2.0.0 and 'a' 1.0.0 contains incompatible tools version (\(ToolsVersion.v5)). - """) + Dependencies could not be resolved because root depends on 'a' 1.0.0..<2.0.0. + 'a' >= 1.0.0 cannot be used because no versions of 'a' match the requirement 1.0.1..<2.0.0 and 'a' 1.0.0 contains incompatible tools version (\( + ToolsVersion + .v5 + )). + """) } func testIncompatibleToolsVersion3() throws { try builder.serve("a", at: v1_1, with: [ - "a": ["b": (.versionSet(v1Range), .specific(["b"]))] + "a": ["b": (.versionSet(v1Range), .specific(["b"]))], ]) try builder.serve("a", at: v1, toolsVersion: .v4) @@ -2545,10 +2912,13 @@ final class PubGrubDiagnosticsTests: XCTestCase { let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because root depends on 'a' 1.0.0..<2.0.0 and root depends on 'b' 2.0.0..<3.0.0. - 'a' >= 1.0.0 practically depends on 'b' 1.0.0..<2.0.0 because 'a' 1.1.0 depends on 'b' 1.0.0..<2.0.0. - 'a' {1.0.0..<1.1.0, 1.1.1..<2.0.0} cannot be used because no versions of 'a' match the requirement {1.0.1..<1.1.0, 1.1.1..<2.0.0} and 'a' 1.0.0 contains incompatible tools version (\(ToolsVersion.v4)). - """) + Dependencies could not be resolved because root depends on 'a' 1.0.0..<2.0.0 and root depends on 'b' 2.0.0..<3.0.0. + 'a' >= 1.0.0 practically depends on 'b' 1.0.0..<2.0.0 because 'a' 1.1.0 depends on 'b' 1.0.0..<2.0.0. + 'a' {1.0.0..<1.1.0, 1.1.1..<2.0.0} cannot be used because no versions of 'a' match the requirement {1.0.1..<1.1.0, 1.1.1..<2.0.0} and 'a' 1.0.0 contains incompatible tools version (\( + ToolsVersion + .v4 + )). + """) } func testIncompatibleToolsVersion4() throws { @@ -2558,14 +2928,17 @@ final class PubGrubDiagnosticsTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("3.2.0"..<"4.0.0")), .specific(["a"])), + "a": (.versionSet(.range("3.2.0" ..< "4.0.0")), .specific(["a"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'a' contains incompatible tools version (\(ToolsVersion.v3)) and root depends on 'a' 3.2.0..<4.0.0. - """) + Dependencies could not be resolved because 'a' contains incompatible tools version (\( + ToolsVersion + .v3 + )) and root depends on 'a' 3.2.0..<4.0.0. + """) } func testIncompatibleToolsVersion5() throws { @@ -2575,14 +2948,17 @@ final class PubGrubDiagnosticsTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("3.2.0"..<"4.0.0")), .specific(["a"])), + "a": (.versionSet(.range("3.2.0" ..< "4.0.0")), .specific(["a"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'a' contains incompatible tools version (\(ToolsVersion.v5)) and root depends on 'a' 3.2.0..<4.0.0. - """) + Dependencies could not be resolved because 'a' contains incompatible tools version (\( + ToolsVersion + .v5 + )) and root depends on 'a' 3.2.0..<4.0.0. + """) } func testIncompatibleToolsVersion6() throws { @@ -2595,47 +2971,53 @@ final class PubGrubDiagnosticsTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("3.2.0"..<"4.0.0")), .specific(["a"])), + "a": (.versionSet(.range("3.2.0" ..< "4.0.0")), .specific(["a"])), ]) let result = resolver.solve(constraints: dependencies) XCTAssertEqual(result.errorMsg, """ - Dependencies could not be resolved because 'a' >= 3.2.1 contains incompatible tools version (\(ToolsVersion.v4)) and root depends on 'a' 3.2.0..<4.0.0. - 'a' 3.2.0 cannot be used because 'a' 3.2.0 depends on 'b' 1.0.0..<2.0.0. - 'b' >= 1.0.0 cannot be used because 'b' 1.0.0 contains incompatible tools version (\(ToolsVersion.v3)) and no versions of 'b' match the requirement 1.0.1..<2.0.0. - """) + Dependencies could not be resolved because 'a' >= 3.2.1 contains incompatible tools version (\( + ToolsVersion + .v4 + )) and root depends on 'a' 3.2.0..<4.0.0. + 'a' 3.2.0 cannot be used because 'a' 3.2.0 depends on 'b' 1.0.0..<2.0.0. + 'b' >= 1.0.0 cannot be used because 'b' 1.0.0 contains incompatible tools version (\( + ToolsVersion + .v3 + )) and no versions of 'b' match the requirement 1.0.1..<2.0.0. + """) } func testProductsCannotResolveToDifferentVersions() throws { try builder.serve("package", at: .unversioned, with: [ "package": [ "intermediate_a": (.versionSet(v1Range), .specific(["Intermediate A"])), - "intermediate_b": (.versionSet(v1Range), .specific(["Intermediate B"])) - ] + "intermediate_b": (.versionSet(v1Range), .specific(["Intermediate B"])), + ], ]) try builder.serve("intermediate_a", at: v1, with: [ "Intermediate A": [ - "transitive": (.versionSet(.exact(v1)), .specific(["Product A"])) - ] + "transitive": (.versionSet(.exact(v1)), .specific(["Product A"])), + ], ]) try builder.serve("intermediate_b", at: v1, with: [ "Intermediate B": [ - "transitive": (.versionSet(.exact(v1_1)), .specific(["Product B"])) - ] + "transitive": (.versionSet(.exact(v1_1)), .specific(["Product B"])), + ], ]) try builder.serve("transitive", at: v1, with: [ "Product A": [:], - "Product B": [:] + "Product B": [:], ]) try builder.serve("transitive", at: v1_1, with: [ "Product A": [:], - "Product B": [:] + "Product B": [:], ]) let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "package": (.unversioned, .everything) + "package": (.unversioned, .everything), ]) let result = resolver.solve(constraints: dependencies) @@ -2664,7 +3046,7 @@ final class PubGrubBacktrackTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("1.0.0"..<"3.0.0")), .specific(["a"])), + "a": (.versionSet(.range("1.0.0" ..< "3.0.0")), .specific(["a"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2677,14 +3059,14 @@ final class PubGrubBacktrackTests: XCTestCase { func testBacktrack2() throws { try builder.serve("a", at: v1) try builder.serve("a", at: "2.0.0", with: [ - "a": ["c": (.versionSet(.range("1.0.0"..<"2.0.0")), .specific(["c"]))], + "a": ["c": (.versionSet(.range("1.0.0" ..< "2.0.0")), .specific(["c"]))], ]) try builder.serve("b", at: "1.0.0", with: [ - "b": ["c": (.versionSet(.range("2.0.0"..<"3.0.0")), .specific(["c"]))], + "b": ["c": (.versionSet(.range("2.0.0" ..< "3.0.0")), .specific(["c"]))], ]) try builder.serve("b", at: "2.0.0", with: [ - "b": ["c": (.versionSet(.range("3.0.0"..<"4.0.0")), .specific(["c"]))], + "b": ["c": (.versionSet(.range("3.0.0" ..< "4.0.0")), .specific(["c"]))], ]) try builder.serve("c", at: "1.0.0") @@ -2693,8 +3075,8 @@ final class PubGrubBacktrackTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("1.0.0"..<"3.0.0")), .specific(["a"])), - "b": (.versionSet(.range("1.0.0"..<"3.0.0")), .specific(["b"])), + "a": (.versionSet(.range("1.0.0" ..< "3.0.0")), .specific(["a"])), + "b": (.versionSet(.range("1.0.0" ..< "3.0.0")), .specific(["b"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2708,18 +3090,18 @@ final class PubGrubBacktrackTests: XCTestCase { func testBacktrack3() throws { try builder.serve("a", at: "1.0.0", with: [ - "a": ["x": (.versionSet(.range("1.0.0"..<"5.0.0")), .specific(["x"]))], + "a": ["x": (.versionSet(.range("1.0.0" ..< "5.0.0")), .specific(["x"]))], ]) try builder.serve("b", at: "1.0.0", with: [ - "b": ["x": (.versionSet(.range("0.0.0"..<"2.0.0")), .specific(["x"]))], + "b": ["x": (.versionSet(.range("0.0.0" ..< "2.0.0")), .specific(["x"]))], ]) try builder.serve("c", at: "1.0.0") try builder.serve("c", at: "2.0.0", with: [ "c": [ - "a": (.versionSet(.range("0.0.0"..<"5.0.0")), .specific(["a"])), - "b": (.versionSet(.range("0.0.0"..<"5.0.0")), .specific(["b"])), - ] + "a": (.versionSet(.range("0.0.0" ..< "5.0.0")), .specific(["a"])), + "b": (.versionSet(.range("0.0.0" ..< "5.0.0")), .specific(["b"])), + ], ]) try builder.serve("x", at: "0.0.0") @@ -2733,8 +3115,8 @@ final class PubGrubBacktrackTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "c": (.versionSet(.range("1.0.0"..<"3.0.0")), .specific(["c"])), - "y": (.versionSet(.range("2.0.0"..<"3.0.0")), .specific(["y"])), + "c": (.versionSet(.range("1.0.0" ..< "3.0.0")), .specific(["c"])), + "y": (.versionSet(.range("2.0.0" ..< "3.0.0")), .specific(["y"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2747,18 +3129,18 @@ final class PubGrubBacktrackTests: XCTestCase { func testBacktrack4() throws { try builder.serve("a", at: "1.0.0", with: [ - "a": ["x": (.versionSet(.range("1.0.0"..<"5.0.0")), .specific(["x"]))], + "a": ["x": (.versionSet(.range("1.0.0" ..< "5.0.0")), .specific(["x"]))], ]) try builder.serve("b", at: "1.0.0", with: [ - "b": ["x": (.versionSet(.range("0.0.0"..<"2.0.0")), .specific(["x"]))], + "b": ["x": (.versionSet(.range("0.0.0" ..< "2.0.0")), .specific(["x"]))], ]) try builder.serve("c", at: "1.0.0") try builder.serve("c", at: "2.0.0", with: [ "c": [ - "a": (.versionSet(.range("0.0.0"..<"5.0.0")), .specific(["a"])), - "b": (.versionSet(.range("0.0.0"..<"5.0.0")), .specific(["b"])), - ] + "a": (.versionSet(.range("0.0.0" ..< "5.0.0")), .specific(["a"])), + "b": (.versionSet(.range("0.0.0" ..< "5.0.0")), .specific(["b"])), + ], ]) try builder.serve("x", at: "0.0.0") @@ -2772,8 +3154,8 @@ final class PubGrubBacktrackTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "c": (.versionSet(.range("1.0.0"..<"3.0.0")), .specific(["c"])), - "y": (.versionSet(.range("2.0.0"..<"3.0.0")), .specific(["y"])), + "c": (.versionSet(.range("1.0.0" ..< "3.0.0")), .specific(["c"])), + "y": (.versionSet(.range("2.0.0" ..< "3.0.0")), .specific(["y"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2796,7 +3178,7 @@ final class PubGrubBacktrackTests: XCTestCase { ]) try builder.serve("bar", at: "1.0.0", with: [ - "bar": ["baz": (.versionSet(.range("0.0.0"..<"3.0.0")), .specific(["baz"]))], + "bar": ["baz": (.versionSet(.range("0.0.0" ..< "3.0.0")), .specific(["baz"]))], ]) try builder.serve("bar", at: "2.0.0", with: [ "bar": ["baz": (.versionSet(.exact("3.0.0")), .specific(["baz"]))], @@ -2809,7 +3191,7 @@ final class PubGrubBacktrackTests: XCTestCase { let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "foo": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["foo"])), + "foo": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["foo"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2828,16 +3210,16 @@ final class PubGrubBacktrackTests: XCTestCase { "b": ["a": (.versionSet(.exact("1.0.0")), .specific(["a"]))], ]) try builder.serve("c", at: "1.0.0", with: [ - "c": ["b": (.versionSet(.range("0.0.0"..<"3.0.0")), .specific(["b"]))], + "c": ["b": (.versionSet(.range("0.0.0" ..< "3.0.0")), .specific(["b"]))], ]) try builder.serve("d", at: "1.0.0") try builder.serve("d", at: "2.0.0") let resolver = builder.create() let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["a"])), - "c": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["c"])), - "d": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["d"])), + "a": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["a"])), + "c": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["c"])), + "d": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["d"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2857,18 +3239,21 @@ final class PubGrubBacktrackTests: XCTestCase { "b": ["a": (.versionSet(.exact("1.0.0")), .specific(["a"]))], ]) try builder.serve("c", at: "1.5.2", with: [ - "c": ["b": (.versionSet(.range("0.0.0"..<"3.0.0")), .specific(["b"]))], + "c": ["b": (.versionSet(.range("0.0.0" ..< "3.0.0")), .specific(["b"]))], ]) try builder.serve("d", at: "1.0.1") try builder.serve("d", at: "2.3.0") let observability = ObservabilitySystem.makeForTesting() - let resolver = builder.create(pins: [:], delegate: ObservabilityDependencyResolverDelegate(observabilityScope: observability.topScope)) + let resolver = builder.create( + pins: [:], + delegate: ObservabilityDependencyResolverDelegate(observabilityScope: observability.topScope) + ) let dependencies = try builder.create(dependencies: [ - "a": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["a"])), - "c": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["c"])), - "d": (.versionSet(.range("1.0.0"..<"4.0.0")), .specific(["d"])), + "a": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["a"])), + "c": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["c"])), + "d": (.versionSet(.range("1.0.0" ..< "4.0.0")), .specific(["d"])), ]) let result = resolver.solve(constraints: dependencies) @@ -2882,16 +3267,28 @@ final class PubGrubBacktrackTests: XCTestCase { observability.diagnostics.forEach { print("\($0)") } - XCTAssertTrue(observability.diagnostics.contains(where: { $0.message == "[DependencyResolver] resolved 'a' @ '1.0.0'" })) - XCTAssertTrue(observability.diagnostics.contains(where: { $0.message == "[DependencyResolver] resolved 'b' @ '1.0.1'" })) - XCTAssertTrue(observability.diagnostics.contains(where: { $0.message == "[DependencyResolver] resolved 'c' @ '1.5.2'" })) - XCTAssertTrue(observability.diagnostics.contains(where: { $0.message == "[DependencyResolver] resolved 'd' @ '2.3.0'" })) + XCTAssertTrue( + observability.diagnostics + .contains(where: { $0.message == "[DependencyResolver] resolved 'a' @ '1.0.0'" }) + ) + XCTAssertTrue( + observability.diagnostics + .contains(where: { $0.message == "[DependencyResolver] resolved 'b' @ '1.0.1'" }) + ) + XCTAssertTrue( + observability.diagnostics + .contains(where: { $0.message == "[DependencyResolver] resolved 'c' @ '1.5.2'" }) + ) + XCTAssertTrue( + observability.diagnostics + .contains(where: { $0.message == "[DependencyResolver] resolved 'd' @ '2.3.0'" }) + ) } } -fileprivate extension PinsStore.PinState { +extension PinsStore.PinState { /// Creates a checkout state with the given version and a mocked revision. - static func version(_ version: Version) -> Self { + fileprivate static func version(_ version: Version) -> Self { .version(version, revision: .none) } } @@ -2911,18 +3308,28 @@ private func AssertBindings( pkg.identity != binding.package.identity }) } - .map { $0.package.identity } + .map(\.package.identity) - XCTFail("Unexpected binding(s) found for \(unexpectedBindings.map { $0.description }.joined(separator: ", ")).", file: file, line: line) + XCTFail( + "Unexpected binding(s) found for \(unexpectedBindings.map(\.description).joined(separator: ", ")).", + file: file, + line: line + ) } for package in packages { - guard let binding = bindings.first(where: { $0.package.identity == package.identity }) else { + guard let binding = bindings.first(where: { + $0.package.identity == package.identity + }) else { XCTFail("No binding found for \(package.identity).", file: file, line: line) continue } if binding.boundVersion != package.version { - XCTFail("Expected \(package.version) for \(package.identity), found \(binding.boundVersion) instead.", file: file, line: line) + XCTFail( + "Expected \(package.version) for \(package.identity), found \(binding.boundVersion) instead.", + file: file, + line: line + ) } } } @@ -2936,7 +3343,12 @@ private func AssertResult( ) { switch result { case .success(let bindings): - AssertBindings(bindings, packages.map { (PackageIdentity($0.identifier), $0.version) }, file: file, line: line) + AssertBindings( + bindings, + packages.map { (PackageIdentity($0.identifier), $0.version) }, + file: file, + line: line + ) case .failure(let error): XCTFail("Unexpected error: \(error)", file: file, line: line) } @@ -2960,7 +3372,11 @@ private func AssertError( // FIXME: this is not thread-safe public class MockContainer: PackageContainer { - public typealias Dependency = (container: PackageReference, requirement: PackageRequirement, productFilter: ProductFilter) + public typealias Dependency = ( + container: PackageReference, + requirement: PackageRequirement, + productFilter: ProductFilter + ) public var package: PackageReference var manifestName: PackageReference? @@ -2970,7 +3386,7 @@ public class MockContainer: PackageContainer { public var unversionedDeps: [PackageContainerConstraint] = [] /// The list of versions that have incompatible tools version. - var toolsVersion: ToolsVersion = ToolsVersion.current + var toolsVersion: ToolsVersion = .current var versionsToolsVersions = [Version: ToolsVersion]() private var _versions: [BoundVersion] @@ -2979,16 +3395,16 @@ public class MockContainer: PackageContainer { public func toolsVersionsAppropriateVersionsDescending() throws -> [Version] { var versions: [Version] = [] for version in self._versions.reversed() { - guard case .version(let v) = version else { continue } + guard case .version(let v, _) = version else { continue } versions.append(v) } return versions } - public func versionsAscending() throws -> [Version] { + public func versionsAscending() throws -> [Version] { var versions: [Version] = [] for version in self._versions { - guard case .version(let v) = version else { continue } + guard case .version(let v, _) = version else { continue } versions.append(v) } return versions @@ -3012,11 +3428,17 @@ public class MockContainer: PackageContainer { return version } - public func getDependencies(at version: Version, productFilter: ProductFilter) throws -> [PackageContainerConstraint] { - return try getDependencies(at: version.description, productFilter: productFilter) + public func getDependencies( + at version: Version, + productFilter: ProductFilter + ) throws -> [PackageContainerConstraint] { + try self.getDependencies(at: version.description, productFilter: productFilter) } - public func getDependencies(at revision: String, productFilter: ProductFilter) throws -> [PackageContainerConstraint] { + public func getDependencies( + at revision: String, + productFilter: ProductFilter + ) throws -> [PackageContainerConstraint] { guard let revisionDependencies = dependencies[revision] else { throw _MockLoadingError.unknownRevision } @@ -3024,18 +3446,18 @@ public class MockContainer: PackageContainer { for (product, productDependencies) in revisionDependencies where productFilter.contains(product) { filteredDependencies.append(contentsOf: productDependencies) } - return filteredDependencies.map({ value in + return filteredDependencies.map { value in let (package, requirement, filter) = value return PackageContainerConstraint(package: package, requirement: requirement, products: filter) - }) + } } public func getUnversionedDependencies(productFilter: ProductFilter) throws -> [PackageContainerConstraint] { // FIXME: This is messy, remove unversionedDeps property. - if !unversionedDeps.isEmpty { - return unversionedDeps + if !self.unversionedDeps.isEmpty { + return self.unversionedDeps } - return try getDependencies(at: PackageRequirement.unversioned.description, productFilter: productFilter) + return try self.getDependencies(at: PackageRequirement.unversioned.description, productFilter: productFilter) } public func loadPackageReference(at boundVersion: BoundVersion) throws -> PackageReference { @@ -3049,7 +3471,7 @@ public class MockContainer: PackageContainer { self._versions.append(version) self._versions = self._versions .sorted(by: { lhs, rhs -> Bool in - guard case .version(let lv) = lhs, case .version(let rv) = rhs else { + guard case .version(let lv, _) = lhs, case .version(let rv, _) = rhs else { return true } return lv < rv @@ -3058,11 +3480,19 @@ public class MockContainer: PackageContainer { public convenience init( package: PackageReference, - unversionedDependencies: [(package: PackageReference, requirement: PackageRequirement, productFilter: ProductFilter)] + unversionedDependencies: [( + package: PackageReference, + requirement: PackageRequirement, + productFilter: ProductFilter + )] ) { self.init(package: package) self.unversionedDeps = unversionedDependencies - .map { PackageContainerConstraint(package: $0.package, requirement: $0.requirement, products: $0.productFilter) } + .map { PackageContainerConstraint( + package: $0.package, + requirement: $0.requirement, + products: $0.productFilter + ) } } public convenience init( @@ -3071,16 +3501,17 @@ public class MockContainer: PackageContainer { package: PackageReference, requirement: VersionSetSpecifier, productFilter: ProductFilter - )]]]) { + )]]] + ) { var dependencies: [String: [String: [Dependency]]] = [:] for (version, productDependencies) in dependenciesByVersion { if dependencies[version.description] == nil { dependencies[version.description] = [:] } for (product, deps) in productDependencies { - dependencies[version.description, default: [:]][product] = deps.map({ + dependencies[version.description, default: [:]][product] = deps.map { ($0.package, .versionSet($0.requirement), $0.productFilter) - }) + } } } self.init(package: package, dependencies: dependencies) @@ -3095,7 +3526,7 @@ public class MockContainer: PackageContainer { let versions = dependencies.keys.compactMap(Version.init(_:)) self._versions = versions .sorted() - .map(BoundVersion.version) + .map { .version($0) } } } @@ -3105,13 +3536,12 @@ public enum _MockLoadingError: Error { } public struct MockProvider: PackageContainerProvider { - public let containers: [MockContainer] public let containersByIdentifier: [PackageReference: MockContainer] public init(containers: [MockContainer]) { self.containers = containers - self.containersByIdentifier = Dictionary(uniqueKeysWithValues: containers.map({ ($0.package, $0) })) + self.containersByIdentifier = Dictionary(uniqueKeysWithValues: containers.map { ($0.package, $0) }) } public func getContainer( @@ -3119,11 +3549,15 @@ public struct MockProvider: PackageContainerProvider { updateStrategy: ContainerUpdateStrategy, observabilityScope: ObservabilityScope, on queue: DispatchQueue, - completion: @escaping (Result - ) -> Void) { + completion: @escaping ( + Result + ) -> Void + ) { queue.async { - completion(self.containersByIdentifier[package].map{ .success($0) } ?? - .failure(_MockLoadingError.unknownModule)) + completion( + self.containersByIdentifier[package].map { .success($0) } ?? + .failure(_MockLoadingError.unknownModule) + ) } } } @@ -3136,7 +3570,10 @@ class DependencyGraphBuilder { if let reference = self.references[packageName] { return reference } - let newReference = PackageReference.localSourceControl(identity: .plain(packageName), path: try .init(validating: "/\(packageName)")) + let newReference = try PackageReference.localSourceControl( + identity: .plain(packageName), + path: .init(validating: "/\(packageName)") + ) self.references[packageName] = newReference return newReference } @@ -3144,9 +3581,12 @@ class DependencyGraphBuilder { func create( dependencies: OrderedCollections.OrderedDictionary ) throws -> [PackageContainerConstraint] { - var refDependencies = OrderedCollections.OrderedDictionary() + var refDependencies = OrderedCollections.OrderedDictionary< + PackageReference, + (PackageRequirement, ProductFilter) + >() for dependency in dependencies { - try refDependencies[reference(for: dependency.key)] = dependency.value + try refDependencies[self.reference(for: dependency.key)] = dependency.value } return self.create(dependencies: refDependencies) } @@ -3154,7 +3594,7 @@ class DependencyGraphBuilder { func create( dependencies: OrderedCollections.OrderedDictionary ) -> [PackageContainerConstraint] { - return dependencies.map { + dependencies.map { PackageContainerConstraint(package: $0, requirement: $1.0, products: $1.1) } } @@ -3163,16 +3603,22 @@ class DependencyGraphBuilder { _ package: String, at versions: [Version], toolsVersion: ToolsVersion? = nil, - with dependencies: KeyValuePairs> = [:] + with dependencies: KeyValuePairs< + String, + OrderedCollections.OrderedDictionary + > = [:] ) throws { - try self.serve(package, at: versions.map{ .version($0) }, toolsVersion: toolsVersion, with: dependencies) + try self.serve(package, at: versions.map { .version($0) }, toolsVersion: toolsVersion, with: dependencies) } func serve( _ package: String, at version: Version, toolsVersion: ToolsVersion? = nil, - with dependencies: KeyValuePairs> = [:] + with dependencies: KeyValuePairs< + String, + OrderedCollections.OrderedDictionary + > = [:] ) throws { try self.serve(package, at: .version(version), toolsVersion: toolsVersion, with: dependencies) } @@ -3181,7 +3627,10 @@ class DependencyGraphBuilder { _ package: String, at versions: [BoundVersion], toolsVersion: ToolsVersion? = nil, - with dependencies: KeyValuePairs> = [:] + with dependencies: KeyValuePairs< + String, + OrderedCollections.OrderedDictionary + > = [:] ) throws { let packageReference = try reference(for: package) try self.serve( @@ -3196,7 +3645,10 @@ class DependencyGraphBuilder { _ package: String, at version: BoundVersion, toolsVersion: ToolsVersion? = nil, - with dependencies: KeyValuePairs> = [:] + with dependencies: KeyValuePairs< + String, + OrderedCollections.OrderedDictionary + > = [:] ) throws { let packageReference = try reference(for: package) try self.serve( @@ -3211,10 +3663,13 @@ class DependencyGraphBuilder { _ packageReference: PackageReference, at versions: [BoundVersion], toolsVersion: ToolsVersion? = nil, - with dependencies: KeyValuePairs> = [:] + with dependencies: KeyValuePairs< + String, + OrderedCollections.OrderedDictionary + > = [:] ) throws { for version in versions { - try serve(packageReference, at: version, toolsVersion: toolsVersion, with: dependencies) + try self.serve(packageReference, at: version, toolsVersion: toolsVersion, with: dependencies) } } @@ -3222,11 +3677,15 @@ class DependencyGraphBuilder { _ packageReference: PackageReference, at version: BoundVersion, toolsVersion: ToolsVersion? = nil, - with dependencies: KeyValuePairs> = [:] + with dependencies: KeyValuePairs< + String, + OrderedCollections.OrderedDictionary + > = [:] ) throws { - let container = self.containers[packageReference.identity.description] ?? MockContainer(package: packageReference) + let container = self + .containers[packageReference.identity.description] ?? MockContainer(package: packageReference) - if case .version(let v) = version { + if case .version(let v, _) = version { container.versionsToolsVersions[v] = toolsVersion ?? container.toolsVersion } @@ -3236,8 +3695,8 @@ class DependencyGraphBuilder { container.dependencies[version.description] = [:] } for (product, filteredDependencies) in dependencies { - let packageDependencies: [MockContainer.Dependency] = try filteredDependencies.map { - (container: try reference(for: $0), requirement: $1.0, productFilter: $1.1) + let packageDependencies: [MockContainer.Dependency] = filteredDependencies.map { + (container: $0, requirement: $1.0, productFilter: $1.1) } container.dependencies[version.description, default: [:]][product, default: []] += packageDependencies } @@ -3247,24 +3706,38 @@ class DependencyGraphBuilder { /// Creates a pins store with the given pins. func create(pinsStore pins: [String: (PinsStore.PinState, ProductFilter)]) throws -> PinsStore { let fs = InMemoryFileSystem() - let store = try! PinsStore(pinsFile: "/tmp/Package.resolved", workingDirectory: .root, fileSystem: fs, mirrors: .init()) + let store = try! PinsStore( + pinsFile: "/tmp/Package.resolved", + workingDirectory: .root, + fileSystem: fs, + mirrors: .init() + ) for (package, pin) in pins { - store.pin(packageRef: try reference(for: package), state: pin.0) + try store.pin(packageRef: self.reference(for: package), state: pin.0) } try! store.saveState(toolsVersion: ToolsVersion.current, originHash: .none) return store } - - func create(pins: PinsStore.Pins = [:], delegate: DependencyResolverDelegate? = .none) -> PubGrubDependencyResolver { + func create( + pins: PinsStore.Pins = [:], + availableLibraries: [ProvidedLibrary] = [], + delegate: DependencyResolverDelegate? = .none + ) -> PubGrubDependencyResolver { defer { self.containers = [:] self.references = [:] } let provider = MockProvider(containers: Array(self.containers.values)) - return PubGrubDependencyResolver(provider :provider, pins: pins, observabilityScope: ObservabilitySystem.NOOP, delegate: delegate) + return PubGrubDependencyResolver( + provider: provider, + pins: pins, + availableLibraries: availableLibraries, + observabilityScope: ObservabilitySystem.NOOP, + delegate: delegate + ) } } @@ -3293,26 +3766,35 @@ extension Term { } else if value.contains("^") { components = value.split(separator: "^").map(String.init) let upperMajor = Int(String(components[1].split(separator: ".").first!))! + 1 - requirement = .versionSet(.range(Version(stringLiteral: components[1]).. ()) throws { - try testWithTemporaryDirectory { tmpdir in - let manifestFile = tmpdir.appending("Package.swift") - try localFileSystem.writeFileContents(manifestFile, string: content) - body(tmpdir) - } - } - - func testTrivialManifestLoading_X1() throws { - #if !os(macOS) - try XCTSkipIf(true, "test is only supported on macOS") - #endif - let N = 1 - let trivialManifest = """ - import PackageDescription - let package = Package(name: "Trivial") - """ - - try write(trivialManifest) { path in - measure { - for _ in 0.. [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult] { +private func makeMockManifests( + fileSystem: FileSystem, + rootPath: AbsolutePath, + count: Int = Int.random(in: 50 ..< 100) +) throws -> [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult] { var manifests = [ManifestLoader.CacheKey: ManifestLoader.EvaluationResult]() for index in 0 ..< count { let packagePath = rootPath.appending("\(index)") diff --git a/Tests/PackageLoadingTests/PDLoadingTests.swift b/Tests/PackageLoadingTests/PDLoadingTests.swift index 37a46c17a3c..aa2bfd7af44 100644 --- a/Tests/PackageLoadingTests/PDLoadingTests.swift +++ b/Tests/PackageLoadingTests/PDLoadingTests.swift @@ -66,8 +66,8 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { observabilityScope: ObservabilityScope, file: StaticString = #file, line: UInt = #line - ) throws -> (manifest: Manifest, diagnostics: [Basics.Diagnostic]) { - try Self.loadAndValidateManifest( + ) async throws -> (manifest: Manifest, diagnostics: [Basics.Diagnostic]) { + try await Self.loadAndValidateManifest( content, toolsVersion: toolsVersion ?? self.toolsVersion, packageKind: packageKind ?? .fileSystem(.root), @@ -86,7 +86,7 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { observabilityScope: ObservabilityScope, file: StaticString = #file, line: UInt = #line - ) throws -> (manifest: Manifest, diagnostics: [Basics.Diagnostic]) { + ) async throws -> (manifest: Manifest, diagnostics: [Basics.Diagnostic]) { let packagePath: AbsolutePath switch packageKind { case .root(let path): @@ -103,7 +103,7 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { let fileSystem = InMemoryFileSystem() let manifestPath = packagePath.appending(component: Manifest.filename) try fileSystem.writeFileContents(manifestPath, string: content) - let manifest = try manifestLoader.load( + let manifest = try await manifestLoader.load( manifestPath: manifestPath, packageKind: packageKind, toolsVersion: toolsVersion, @@ -124,15 +124,6 @@ class PackageDescriptionLoadingTests: XCTestCase, ManifestLoaderDelegate { final class ManifestTestDelegate: ManifestLoaderDelegate { private let loaded = ThreadSafeArrayStore() private let parsed = ThreadSafeArrayStore() - private let loadingGroup = DispatchGroup() - private let parsingGroup = DispatchGroup() - - func prepare(expectParsing: Bool = true) { - self.loadingGroup.enter() - if expectParsing { - self.parsingGroup.enter() - } - } func willLoad(packageIdentity: PackageModel.PackageIdentity, packageLocation: String, manifestPath: AbsolutePath) { // noop @@ -140,7 +131,6 @@ final class ManifestTestDelegate: ManifestLoaderDelegate { func didLoad(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { self.loaded.append(manifestPath) - self.loadingGroup.leave() } func willParse(packageIdentity: PackageIdentity, packageLocation: String) { @@ -165,7 +155,6 @@ final class ManifestTestDelegate: ManifestLoaderDelegate { func didEvaluate(packageIdentity: PackageIdentity, packageLocation: String, manifestPath: AbsolutePath, duration: DispatchTimeInterval) { self.parsed.append(manifestPath) - self.parsingGroup.leave() } @@ -174,17 +163,13 @@ final class ManifestTestDelegate: ManifestLoaderDelegate { self.parsed.clear() } - func loaded(timeout: DispatchTime) throws -> [AbsolutePath] { - guard case .success = self.loadingGroup.wait(timeout: timeout) else { - throw StringError("timeout waiting for loading") - } + func loaded(timeout: Duration) async throws -> [AbsolutePath] { + try await Task.sleep(for: timeout) return self.loaded.get() } - func parsed(timeout: DispatchTime) throws -> [AbsolutePath] { - guard case .success = self.parsingGroup.wait(timeout: timeout) else { - throw StringError("timeout waiting for parsing") - } + func parsed(timeout: Duration) async throws -> [AbsolutePath] { + try await Task.sleep(for: timeout) return self.parsed.get() } } diff --git a/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift b/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift index 435c8025a27..365985a8618 100644 --- a/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_4_0_LoadingTests.swift @@ -18,12 +18,12 @@ import XCTest import class TSCBasic.InMemoryFileSystem -class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v4 } - func testTrivial() throws { + func testTrivial() async throws { let content = """ import PackageDescription let package = Package( @@ -32,7 +32,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -42,7 +42,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.dependencies, []) } - func testTargetDependencies() throws { + func testTargetDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -62,7 +62,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -86,7 +86,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(bar.dependencies, ["foo"]) } - func testCompatibleSwiftVersions() throws { + func testCompatibleSwiftVersions() async throws { do { let content = """ import PackageDescription @@ -96,7 +96,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { ) """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertEqual(manifest.swiftLanguageVersions?.map({$0.rawValue}), ["3", "4"]) @@ -111,7 +111,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { ) """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertEqual(manifest.swiftLanguageVersions, []) @@ -124,14 +124,14 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { name: "Foo") """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertEqual(manifest.swiftLanguageVersions, nil) } } - func testPackageDependencies() throws { + func testPackageDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -148,7 +148,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -161,7 +161,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(deps["foo6"], .localSourceControl(path: "/foo6", requirement: .revision("58e9de4e7b79e67c72a46e164158e3542e570ab6"))) } - func testProducts() throws { + func testProducts() async throws { let content = """ import PackageDescription let package = Package( @@ -179,7 +179,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -201,7 +201,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(fooDy.targets, ["Foo"]) } - func testSystemPackage() throws { + func testSystemPackage() async throws { let content = """ import PackageDescription let package = Package( @@ -215,7 +215,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -227,7 +227,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { ]) } - func testCTarget() throws { + func testCTarget() async throws { let content = """ import PackageDescription let package = Package( @@ -243,7 +243,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -254,7 +254,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(bar.publicHeadersPath, nil) } - func testTargetProperties() throws { + func testTargetProperties() async throws { let content = """ import PackageDescription let package = Package( @@ -273,7 +273,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -290,7 +290,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssert(bar.sources == nil) } - func testUnavailableAPIs() throws { + func testUnavailableAPIs() async throws { let content = """ import PackageDescription let package = Package( @@ -305,7 +305,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssert(error.contains("error: 'package(url:version:)' is unavailable: use package(url:exact:) instead"), "\(error)") XCTAssert(error.contains("error: 'package(url:range:)' is unavailable: use package(url:_:) instead"), "\(error)") @@ -315,7 +315,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { } } - func testLanguageStandards() throws { + func testLanguageStandards() async throws { let content = """ import PackageDescription let package = Package( @@ -329,7 +329,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -338,7 +338,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.cxxLanguageStandard, "gnu++14") } - func testManifestWithWarnings() throws { + func testManifestWithWarnings() async throws { let fs = InMemoryFileSystem() let manifestPath = AbsolutePath.root.appending(component: Manifest.filename) @@ -355,7 +355,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { try fs.writeFileContents(manifestPath, string: content) let observability = ObservabilitySystem.makeForTesting() - let manifest = try manifestLoader.load( + let manifest = try await manifestLoader.load( manifestPath: manifestPath, packageKind: .root(.root), toolsVersion: .v4, @@ -373,7 +373,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { } } - func testDuplicateTargets() throws { + func testDuplicateTargets() async throws { let content = """ import PackageDescription @@ -389,7 +389,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.checkUnordered(diagnostic: "duplicate target named 'A'", severity: .error) @@ -397,7 +397,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { } } - func testEmptyProductTargets() throws { + func testEmptyProductTargets() async throws { let content = """ import PackageDescription @@ -413,14 +413,14 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "product 'Product' doesn't reference any targets", severity: .error) } } - func testProductTargetNotFound() throws { + func testProductTargetNotFound() async throws { let content = """ import PackageDescription @@ -436,7 +436,7 @@ class PackageDescription4_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "target 'B' referenced in product 'Product' could not be found; valid targets are: 'A'", severity: .error) diff --git a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift index ddd8f7e402a..d460fb6be9a 100644 --- a/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_4_2_LoadingTests.swift @@ -24,12 +24,13 @@ import func TSCTestSupport.withCustomEnv import struct TSCUtility.Version -class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { +@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) +final class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v4_2 } - func testBasics() throws { + func testBasics() async throws { let content = """ import PackageDescription let package = Package( @@ -55,7 +56,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -90,7 +91,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(fooProduct.targets, ["foo"]) } - func testSwiftLanguageVersions() throws { + func testSwiftLanguageVersions() async throws { // Ensure integer values are not accepted. do { let content = """ @@ -102,7 +103,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch( message, @@ -128,7 +129,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -145,7 +146,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -165,7 +166,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5")) @@ -176,7 +177,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testPlatforms() throws { + func testPlatforms() async throws { do { let content = """ import PackageDescription @@ -187,7 +188,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5")) @@ -207,7 +208,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5")) @@ -218,7 +219,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testBuildSettings() throws { + func testBuildSettings() async throws { let content = """ import PackageDescription let package = Package( @@ -238,7 +239,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5")) @@ -248,7 +249,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testPackageDependencies() throws { + func testPackageDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -273,7 +274,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -331,7 +332,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testSystemLibraryTargets() throws { + func testSystemLibraryTargets() async throws { let content = """ import PackageDescription let package = Package( @@ -352,7 +353,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -371,7 +372,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { /// Check that we load the manifest appropriate for the current version, if /// version specific customization is used. - func testVersionSpecificLoading() throws { + func testVersionSpecificLoading() async throws { let bogusManifest = "THIS WILL NOT PARSE" let trivialManifest = """ @@ -405,7 +406,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { ) } // Check we can load the repository. - let manifest = try manifestLoader.load( + let manifest = try await manifestLoader.load( packagePath: root, packageKind: .root(.root), currentToolsVersion: .v4_2, @@ -417,7 +418,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } // Check that ancient `Package@swift-3.swift` manifests are properly treated as 3.1 even without a tools-version comment. - func testVersionSpecificLoadingOfVersion3Manifest() throws { + func testVersionSpecificLoadingOfVersion3Manifest() async throws { // Create a temporary FS to hold the package manifests. let fs = InMemoryFileSystem() let observability = ObservabilitySystem.makeForTesting() @@ -434,7 +435,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { string: manifestContents ) // Check we can load the manifest. - let manifest = try manifestLoader.load(packagePath: packageDir, packageKind: .root(packageDir), currentToolsVersion: .v4_2, fileSystem: fs, observabilityScope: observability.topScope) + let manifest = try await manifestLoader.load(packagePath: packageDir, packageKind: .root(packageDir), currentToolsVersion: .v4_2, fileSystem: fs, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(manifest.displayName, "Trivial") @@ -448,12 +449,12 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { string: "// swift-tools-version:4.0\n" + manifestContents ) // Check we can load the manifest. - let manifest2 = try manifestLoader.load(packagePath: packageDir, packageKind: .root(packageDir), currentToolsVersion: .v4_2, fileSystem: fs, observabilityScope: observability.topScope) + let manifest2 = try await manifestLoader.load(packagePath: packageDir, packageKind: .root(packageDir), currentToolsVersion: .v4_2, fileSystem: fs, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(manifest2.displayName, "Trivial") } - func testRuntimeManifestErrors() throws { + func testRuntimeManifestErrors() async throws { let content = """ import PackageDescription let package = Package( @@ -477,7 +478,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.runtimeManifestErrors(let errors) = error { XCTAssertEqual(errors, ["Invalid semantic version string '1.0,0'"]) } else { @@ -486,7 +487,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testNotAbsoluteDependencyPath() throws { + func testNotAbsoluteDependencyPath() async throws { let content = """ import PackageDescription let package = Package( @@ -503,7 +504,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, let diagnosticFile, _) = error { XCTAssertNil(diagnosticFile) XCTAssertEqual(message, "'https://someurl.com' is not a valid path for path-based dependencies; use relative or absolute path instead.") @@ -513,7 +514,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testFileURLErrors() throws { + func testFileURLErrors() async throws { enum ExpectedError { case invalidAbsolutePath case relativePath @@ -563,7 +564,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in switch error { case is ManifestParseError: XCTAssertEqual(error as? ManifestParseError, expectedError.manifestError) @@ -576,7 +577,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { } } - func testProductTargetNotFound() throws { + func testProductTargetNotFound() async throws { let content = """ import PackageDescription @@ -594,7 +595,7 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: .contains("target 'B' referenced in product 'Product' could not be found; valid targets are: 'A', 'C', 'b'"), severity: .error) @@ -629,7 +630,6 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { let dependencyMapper = DefaultDependencyMapper(identityResolver: identityResolver) // warm up caches - delegate.prepare() let manifest = try await manifestLoader.load( manifestPath: manifestPath, manifestToolsVersion: .v4_2, @@ -649,37 +649,28 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.displayName, "Trivial") XCTAssertEqual(manifest.targets[0].name, "foo") - await withTaskGroup(of:Void.self) { group in - for _ in 0 ..< total { - delegate.prepare(expectParsing: false) - group.addTask { - do { - let manifest = try await manifestLoader.load( - manifestPath: manifestPath, - manifestToolsVersion: .v4_2, - packageIdentity: .plain("Trivial"), - packageKind: .fileSystem(manifestPath.parentDirectory), - packageLocation: manifestPath.pathString, - packageVersion: nil, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper, - fileSystem: localFileSystem, - observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent - ) - XCTAssertNoDiagnostics(observability.diagnostics) - XCTAssertEqual(manifest.displayName, "Trivial") - XCTAssertEqual(manifest.targets[0].name, "foo") - } catch { - XCTFail("\(error)") - } - } - } - await group.waitForAll() + for _ in 0 ..< total { + let manifest = try await manifestLoader.load( + manifestPath: manifestPath, + manifestToolsVersion: .v4_2, + packageIdentity: .plain("Trivial"), + packageKind: .fileSystem(manifestPath.parentDirectory), + packageLocation: manifestPath.pathString, + packageVersion: nil, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: localFileSystem, + observabilityScope: observability.topScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) + + XCTAssertNoDiagnostics(observability.diagnostics) + XCTAssertEqual(manifest.displayName, "Trivial") + XCTAssertEqual(manifest.targets[0].name, "foo") } - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, total+1) + try await XCTAssertAsyncEqual(try await delegate.loaded(timeout: .seconds(1)).count, total+1) XCTAssertFalse(observability.hasWarningDiagnostics, observability.diagnostics.description) XCTAssertFalse(observability.hasErrorDiagnostics, observability.diagnostics.description) } @@ -702,55 +693,47 @@ class PackageDescription4_2LoadingTests: PackageDescriptionLoadingTests { let identityResolver = DefaultIdentityResolver() let dependencyMapper = DefaultDependencyMapper(identityResolver: identityResolver) - try await withThrowingTaskGroup(of: Void.self) { group in - for _ in 0 ..< total { - let random = Int.random(in: 0 ... total / 4) - let manifestPath = path.appending(components: "pkg-\(random)", "Package.swift") - if !localFileSystem.exists(manifestPath) { - try localFileSystem.createDirectory(manifestPath.parentDirectory) - try localFileSystem.writeFileContents( - manifestPath, - string: """ - import PackageDescription - let package = Package( - name: "Trivial-\(random)", - targets: [ - .target( - name: "foo-\(random)", - dependencies: []), - ] - ) - """ + for _ in 0 ..< total { + let random = Int.random(in: 0 ... total / 4) + let manifestPath = path.appending(components: "pkg-\(random)", "Package.swift") + if !localFileSystem.exists(manifestPath) { + try localFileSystem.createDirectory(manifestPath.parentDirectory) + try localFileSystem.writeFileContents( + manifestPath, + string: """ + import PackageDescription + let package = Package( + name: "Trivial-\(random)", + targets: [ + .target( + name: "foo-\(random)", + dependencies: []), + ] ) - } - group.addTask { - do { - delegate.prepare() - let manifest = try await manifestLoader.load( - manifestPath: manifestPath, - manifestToolsVersion: .v4_2, - packageIdentity: .plain("Trivial-\(random)"), - packageKind: .fileSystem(manifestPath.parentDirectory), - packageLocation: manifestPath.pathString, - packageVersion: nil, - identityResolver: identityResolver, - dependencyMapper: dependencyMapper, - fileSystem: localFileSystem, - observabilityScope: observability.topScope, - delegateQueue: .sharedConcurrent, - callbackQueue: .sharedConcurrent - ) - XCTAssertEqual(manifest.displayName, "Trivial-\(random)") - XCTAssertEqual(manifest.targets[0].name, "foo-\(random)") - } catch { - XCTFail("\(error)") - } - } + """ + ) } - try await group.waitForAll() + + let manifest = try await manifestLoader.load( + manifestPath: manifestPath, + manifestToolsVersion: .v4_2, + packageIdentity: .plain("Trivial-\(random)"), + packageKind: .fileSystem(manifestPath.parentDirectory), + packageLocation: manifestPath.pathString, + packageVersion: nil, + identityResolver: identityResolver, + dependencyMapper: dependencyMapper, + fileSystem: localFileSystem, + observabilityScope: observability.topScope, + delegateQueue: .sharedConcurrent, + callbackQueue: .sharedConcurrent + ) + + XCTAssertEqual(manifest.displayName, "Trivial-\(random)") + XCTAssertEqual(manifest.targets[0].name, "foo-\(random)") } - XCTAssertEqual(try delegate.loaded(timeout: .now() + 1).count, total) + try await XCTAssertAsyncEqual(try await delegate.loaded(timeout: .seconds(1)).count, total) XCTAssertFalse(observability.hasWarningDiagnostics, observability.diagnostics.description) XCTAssertFalse(observability.hasErrorDiagnostics, observability.diagnostics.description) } diff --git a/Tests/PackageLoadingTests/PD_5_0_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_0_LoadingTests.swift index 18873299874..7cc3c97e863 100644 --- a/Tests/PackageLoadingTests/PD_5_0_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_0_LoadingTests.swift @@ -18,12 +18,12 @@ import XCTest import struct TSCBasic.ByteString -class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v5 } - func testBasics() throws { + func testBasics() async throws { let content = """ import PackageDescription let package = Package( @@ -49,7 +49,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -84,7 +84,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(fooProduct.targets, ["foo"]) } - func testSwiftLanguageVersion() throws { + func testSwiftLanguageVersion() async throws { do { let content = """ import PackageDescription @@ -95,7 +95,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -112,7 +112,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("'v3' is unavailable")) XCTAssertMatch(message, .contains("'v3' was obsoleted in PackageDescription 5")) @@ -132,7 +132,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.runtimeManifestErrors(let messages) = error { XCTAssertEqual(messages, ["invalid Swift language version: "]) } else { @@ -142,7 +142,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { } } - func testPlatformOptions() throws { + func testPlatformOptions() async throws { let content = """ import PackageDescription let package = Package( @@ -155,7 +155,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -166,7 +166,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { ]) } - func testPlatforms() throws { + func testPlatforms() async throws { do { let content = """ import PackageDescription @@ -180,7 +180,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -205,7 +205,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.runtimeManifestErrors(let errors) = error { XCTAssertEqual(errors, [ "invalid macOS version -11.2; -11 should be a positive integer", @@ -232,7 +232,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.runtimeManifestErrors(let errors) = error { XCTAssertEqual(errors, ["found multiple declaration for the platform: macos"]) } else { @@ -252,7 +252,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.runtimeManifestErrors(let errors) = error { XCTAssertEqual(errors, ["supported platforms can't be empty"]) } else { @@ -274,7 +274,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("error: 'v11' is unavailable")) XCTAssertMatch(message, .contains("note: 'v11' was introduced in PackageDescription 5.3")) @@ -298,7 +298,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("error: 'v10_16' has been renamed to 'v11'")) XCTAssertMatch(message, .contains("note: 'v10_16' has been explicitly marked unavailable here")) @@ -310,7 +310,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { } } - func testBuildSettings() throws { + func testBuildSettings() async throws { let content = """ import PackageDescription let package = Package( @@ -341,7 +341,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -361,8 +361,8 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(settings[8], .init(tool: .linker, kind: .linkedFramework("CoreData"), condition: .init(platformNames: ["macos", "tvos"]))) } - func testSerializedDiagnostics() throws { - try testWithTemporaryDirectory { path in + func testSerializedDiagnostics() async throws { + try await testWithTemporaryDirectory { path in let fs = localFileSystem let manifestPath = path.appending(components: "pkg", "Package.swift") @@ -391,7 +391,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { ) do { - _ = try loader.load( + _ = try await loader.load( manifestPath: manifestPath, packageKind: .fileSystem(manifestPath.parentDirectory), toolsVersion: .v5, @@ -426,7 +426,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ ) - _ = try loader.load( + _ = try await loader.load( manifestPath: manifestPath, packageKind: .fileSystem(manifestPath.parentDirectory), toolsVersion: .v5, @@ -443,7 +443,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { } } - func testInvalidBuildSettings() throws { + func testInvalidBuildSettings() async throws { do { let content = """ import PackageDescription @@ -461,7 +461,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.runtimeManifestErrors(let errors) = error { XCTAssertEqual(errors, ["the build setting 'headerSearchPath' contains invalid component(s): $(BYE) $(SRCROOT) $(HELLO)"]) } else { @@ -485,12 +485,12 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - _ = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + _ = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) } } - func testWindowsPlatform() throws { + func testWindowsPlatform() async throws { let content = """ import PackageDescription let package = Package( @@ -508,7 +508,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { do { let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5.2")) @@ -520,7 +520,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { do { let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, toolsVersion: .v5_2, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, toolsVersion: .v5_2, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -537,7 +537,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { } } - func testPackageNameUnavailable() throws { + func testPackageNameUnavailable() async throws { let content = """ import PackageDescription let package = Package( @@ -555,7 +555,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5.2")) @@ -565,7 +565,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { } } - func testManifestWithPrintStatements() throws { + func testManifestWithPrintStatements() async throws { let content = """ import PackageDescription print(String(repeating: "Hello manifest... ", count: 65536)) @@ -575,7 +575,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -585,8 +585,8 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.dependencies, []) } - func testManifestLoaderEnvironment() throws { - try testWithTemporaryDirectory { path in + func testManifestLoaderEnvironment() async throws { + try await testWithTemporaryDirectory { path in let fs = localFileSystem let packagePath = path.appending("pkg") @@ -620,7 +620,7 @@ class PackageDescription5_0LoadingTests: PackageDescriptionLoadingTests { cacheDir: nil) let observability = ObservabilitySystem.makeForTesting() - let manifest = try manifestLoader.load( + let manifest = try await manifestLoader.load( manifestPath: manifestPath, packageKind: .fileSystem(manifestPath.parentDirectory), toolsVersion: .v5, diff --git a/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift index 436448b46c3..f9669df56b6 100644 --- a/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_2_LoadingTests.swift @@ -16,12 +16,12 @@ import PackageModel import SPMTestSupport import XCTest -class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v5_2 } - func testMissingTargetProductDependencyPackage() throws { + func testMissingTargetProductDependencyPackage() async throws { let content = """ import PackageDescription let package = Package( @@ -39,7 +39,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssert(error.contains("error: \'product(name:package:)\' is unavailable: the 'package' argument is mandatory as of tools version 5.2")) } else { @@ -48,7 +48,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { } } - func testDependencyNameForTargetDependencyResolution() throws { + func testDependencyNameForTargetDependencyResolution() async throws { let content = """ import PackageDescription let package = Package( @@ -85,7 +85,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -101,7 +101,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.dependencies[8].nameForTargetDependencyResolutionOnly, "swift") } - func testTargetDependencyProductInvalidPackage() throws { + func testTargetDependencyProductInvalidPackage() async throws { do { let content = """ import PackageDescription @@ -124,7 +124,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.checkUnordered(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'foo' (from 'http://scm.com/org/foo'), 'bar' (from 'http://scm.com/org/bar')", severity: .error) @@ -154,7 +154,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.checkUnordered(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'Foo' (from 'http://scm.com/org/foo'), 'Bar' (from 'http://scm.com/org/bar')", severity: .error) @@ -185,7 +185,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, packageKind: .root(.root), observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, packageKind: .root(.root), observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "unknown package 'foo1' in dependencies of target 'Target1'; valid packages are: 'Foo' (from 'http://scm.com/org/foo1')", severity: .error) @@ -215,7 +215,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in let fooPkg: AbsolutePath = "/foo" @@ -247,7 +247,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in let foo1Pkg: AbsolutePath = "/foo1" @@ -258,7 +258,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { } } - func testTargetDependencyReference() throws { + func testTargetDependencyReference() async throws { let content = """ import PackageDescription let package = Package( @@ -280,7 +280,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -294,7 +294,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.packageDependency(referencedBy: targetBar.dependencies[0]), nil) } - func testResourcesUnavailable() throws { + func testResourcesUnavailable() async throws { let content = """ import PackageDescription let package = Package( @@ -312,7 +312,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertMatch(error, .contains("is unavailable")) XCTAssertMatch(error, .contains("was introduced in PackageDescription 5.3")) @@ -322,7 +322,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { } } - func testBinaryTargetUnavailable() throws { + func testBinaryTargetUnavailable() async throws { do { let content = """ import PackageDescription @@ -338,7 +338,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertMatch(error, .contains("is unavailable")) XCTAssertMatch(error, .contains("was introduced in PackageDescription 5.3")) @@ -364,7 +364,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertMatch(error, .contains("is unavailable")) XCTAssertMatch(error, .contains("was introduced in PackageDescription 5.3")) @@ -375,7 +375,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { } } - func testConditionalTargetDependenciesUnavailable() throws { + func testConditionalTargetDependenciesUnavailable() async throws { let content = """ import PackageDescription let package = Package( @@ -394,7 +394,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertMatch(error, .contains("is unavailable")) XCTAssertMatch(error, .contains("was introduced in PackageDescription 5.3")) @@ -404,7 +404,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { } } - func testDefaultLocalizationUnavailable() throws { + func testDefaultLocalizationUnavailable() async throws { do { let content = """ import PackageDescription @@ -419,7 +419,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertMatch(error, .contains("is unavailable")) XCTAssertMatch(error, .contains("was introduced in PackageDescription 5.3")) @@ -430,7 +430,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { } } - func testManifestLoadingIsSandboxed() throws { + func testManifestLoadingIsSandboxed() async throws { #if !os(macOS) // Sandboxing is only done on macOS today. try XCTSkipIf(true, "test is only supported on macOS") @@ -450,7 +450,7 @@ class PackageDescription5_2LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertTrue(error.contains("Operation not permitted"), "unexpected error message: \(error)") } else { diff --git a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift index 276931c802a..7158e605a81 100644 --- a/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_3_LoadingTests.swift @@ -18,12 +18,12 @@ import XCTest import enum TSCBasic.PathValidationError -class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v5_3 } - func testResources() throws { + func testResources() async throws { let content = """ import PackageDescription let package = Package( @@ -49,7 +49,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -63,7 +63,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(testResources[0], TargetDescription.Resource(rule: .process(localization: .none), path: "testfixture.txt")) } - func testBinaryTargetsTrivial() throws { + func testBinaryTargetsTrivial() async throws { let content = """ import PackageDescription let package = Package( @@ -88,7 +88,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -144,7 +144,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { )) } - func testBinaryTargetsDisallowedProperties() throws { + func testBinaryTargetsDisallowedProperties() async throws { let content = """ import PackageDescription var fwBinaryTarget = Target.binaryTarget( @@ -157,12 +157,12 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in XCTAssertEqual(error.localizedDescription, "target 'Foo' contains a value for disallowed property 'settings'") } } - func testBinaryTargetsValidation() throws { + func testBinaryTargetsValidation() async throws { do { let content = """ import PackageDescription @@ -178,7 +178,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "invalid type for binary product 'FooLibrary'; products referencing only binary targets must be executable or automatic library products", severity: .error) @@ -201,7 +201,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) } @@ -221,7 +221,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "invalid local path ' ' for binary target 'Foo', path expected to be relative to package root.", severity: .error) @@ -243,7 +243,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "invalid URL scheme for binary target 'Foo'; valid schemes are: 'https'", severity: .error) @@ -265,7 +265,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'xcframework', 'artifactbundle'", severity: .error) @@ -290,7 +290,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'artifactbundleindex'", severity: .error) @@ -312,7 +312,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'xcframework', 'artifactbundle'", severity: .error) @@ -337,7 +337,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "unsupported extension for binary target 'Foo'; valid extensions are: 'zip', 'artifactbundleindex'", severity: .error) @@ -362,7 +362,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "invalid URL scheme for binary target 'Foo'; valid schemes are: 'https'", severity: .error) @@ -387,7 +387,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "invalid URL ' ' for binary target 'Foo'", severity: .error) @@ -411,7 +411,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.check(diagnostic: "invalid local path '/tmp/foo/bar' for binary target 'Foo', path expected to be relative to package root.", severity: .error) @@ -419,7 +419,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { } } - func testConditionalTargetDependencies() throws { + func testConditionalTargetDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -441,7 +441,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -452,7 +452,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(dependencies[3], .byName(name: "Bar", condition: .init(platformNames: ["watchos", "ios"]))) } - func testDefaultLocalization() throws { + func testDefaultLocalization() async throws { let content = """ import PackageDescription let package = Package( @@ -465,13 +465,13 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertEqual(manifest.defaultLocalization, "fr") } - func testTargetPathsValidation() throws { + func testTargetPathsValidation() async throws { let manifestItemToDiagnosticMap = [ "sources: [\"/foo.swift\"]": "invalid relative path '/foo.swift", "resources: [.copy(\"/foo.txt\")]": "invalid relative path '/foo.txt'", @@ -493,7 +493,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if let error = error as? PathValidationError { XCTAssertMatch(error.description, .contains(expectedDiag)) } else { @@ -503,7 +503,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { } } - func testNonZeroExitStatusDoesNotAssert() throws { + func testNonZeroExitStatusDoesNotAssert() async throws { let content = """ #if canImport(Glibc) import Glibc @@ -521,12 +521,12 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in XCTAssertNotNil(error as? ManifestParseError) } } - func testManifestLoadingIsSandboxed() throws { + func testManifestLoadingIsSandboxed() async throws { #if !os(macOS) // Sandboxing is only done on macOS today. try XCTSkipIf(true, "test is only supported on macOS") @@ -546,7 +546,7 @@ class PackageDescription5_3LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertTrue(error.contains("Operation not permitted"), "unexpected error message: \(error)") } else { diff --git a/Tests/PackageLoadingTests/PD_5_4_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_4_LoadingTests.swift index 211a889583f..2e39b3db248 100644 --- a/Tests/PackageLoadingTests/PD_5_4_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_4_LoadingTests.swift @@ -21,7 +21,7 @@ class PackageDescription5_4LoadingTests: PackageDescriptionLoadingTests { .v5_4 } - func testExecutableTargets() throws { + func testExecutableTargets() async throws { let content = """ import PackageDescription let package = Package( @@ -35,14 +35,14 @@ class PackageDescription5_4LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertEqual(manifest.targets[0].type, .executable) } - func testPluginsAreUnavailable() throws { + func testPluginsAreUnavailable() async throws { let content = """ import PackageDescription let package = Package( @@ -57,7 +57,7 @@ class PackageDescription5_4LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("is unavailable")) XCTAssertMatch(message, .contains("was introduced in PackageDescription 5.5")) diff --git a/Tests/PackageLoadingTests/PD_5_5_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_5_LoadingTests.swift index 5096d829c1f..d94c0600ed8 100644 --- a/Tests/PackageLoadingTests/PD_5_5_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_5_LoadingTests.swift @@ -22,7 +22,7 @@ class PackageDescription5_5LoadingTests: PackageDescriptionLoadingTests { .v5_5 } - func testPackageDependencies() throws { + func testPackageDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -35,7 +35,7 @@ class PackageDescription5_5LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -44,7 +44,7 @@ class PackageDescription5_5LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(deps["foo7"], .localSourceControl(path: "/foo7", requirement: .revision("58e9de4e7b79e67c72a46e164158e3542e570ab6"))) } - func testPlatforms() throws { + func testPlatforms() async throws { let content = """ import PackageDescription let package = Package( @@ -58,7 +58,7 @@ class PackageDescription5_5LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) diff --git a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift index 77a67485b3e..8abb54efe97 100644 --- a/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_6_LoadingTests.swift @@ -16,12 +16,12 @@ import PackageModel import SPMTestSupport import XCTest -class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v5_6 } - func testSourceControlDependencies() throws { + func testSourceControlDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -53,7 +53,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertFalse(observability.diagnostics.hasErrors) XCTAssertNoDiagnostics(validationDiagnostics) @@ -75,7 +75,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(deps["quuz3"], .remoteSourceControl(identity: .plain("quuz3"), url: "http://localhost/quuz3", requirement: .revision("abcdefg"))) } - func testBuildToolPluginTarget() throws { + func testBuildToolPluginTarget() async throws { let content = """ import PackageDescription let package = Package( @@ -90,7 +90,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -98,7 +98,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.targets[0].pluginCapability, .buildTool) } - func testPluginTargetCustomization() throws { + func testPluginTargetCustomization() async throws { let content = """ import PackageDescription let package = Package( @@ -116,7 +116,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -127,7 +127,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(manifest.targets[0].sources, ["CountMeIn.swift"]) } - func testCustomPlatforms() throws { + func testCustomPlatforms() async throws { // One custom platform. do { let content = """ @@ -141,7 +141,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -164,7 +164,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -188,23 +188,23 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { let observability = ObservabilitySystem.makeForTesting() do { - _ = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + _ = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTFail("manifest loading unexpectedly did not throw an error") } catch ManifestParseError.runtimeManifestErrors(let errors) { XCTAssertEqual(errors, ["invalid custom platform version xx; xx should be a positive integer"]) } } } - + /// Tests use of Context.current.packageDirectory - func testPackageContextName() throws { + func testPackageContextName() async throws { let content = """ import PackageDescription let package = Package(name: Context.packageDirectory) """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -215,7 +215,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { } /// Tests access to the package's directory contents. - func testPackageContextDirectory() throws { + func testPackageContextDirectory() async throws { #if os(Windows) throw XCTSkip("Skipping since this tests does not fully work without the VFS overlay which is currently disabled on Windows") #endif @@ -223,15 +223,15 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { let content = """ import PackageDescription import Foundation - + let fileManager = FileManager.default let contents = (try? fileManager.contentsOfDirectory(atPath: Context.packageDirectory)) ?? [] - + let package = Package(name: contents.joined(separator: ",")) """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) // FIXME: temporary filter a diagnostic that shows up on macOS 14.0 XCTAssertNoDiagnostics(observability.diagnostics.filter { !$0.message.contains("coreservicesd") }) XCTAssertNoDiagnostics(validationDiagnostics) @@ -242,7 +242,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(files, expectedFiles) } - func testCommandPluginTarget() throws { + func testCommandPluginTarget() async throws { let content = """ import PackageDescription let package = Package( @@ -260,7 +260,7 @@ class PackageDescription5_6LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) diff --git a/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift index a21d9ff5ed4..981eca47913 100644 --- a/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_7_LoadingTests.swift @@ -16,12 +16,12 @@ import PackageModel import SPMTestSupport import XCTest -class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v5_7 } - func testImplicitFoundationImportWorks() throws { + func testImplicitFoundationImportWorks() async throws { let content = """ import PackageDescription @@ -31,13 +31,13 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) XCTAssertEqual(manifest.displayName, "MyPackage") } - func testRegistryDependencies() throws { + func testRegistryDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -53,7 +53,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -65,7 +65,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(deps["x.quux"], .registry(identity: "x.quux", requirement: .range("1.1.1" ..< "3.0.0"))) } - func testConditionalTargetDependencies() throws { + func testConditionalTargetDependencies() async throws { let content = """ import PackageDescription let package = Package( @@ -83,7 +83,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -92,7 +92,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { XCTAssertEqual(dependencies[1], .target(name: "Baz", condition: .init(platformNames: ["linux"], config: .none))) } - func testConditionalTargetDependenciesDeprecation() throws { + func testConditionalTargetDependenciesDeprecation() async throws { let content = """ import PackageDescription let package = Package( @@ -108,7 +108,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { error in if case ManifestParseError.invalidManifestFormat(let error, _, _) = error { XCTAssertMatch(error, .contains("when(platforms:)' was obsoleted")) } else { @@ -117,7 +117,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { } } - func testTargetDeprecatedDependencyCase() throws { + func testTargetDeprecatedDependencyCase() async throws { let content = """ import PackageDescription let package = Package( @@ -135,7 +135,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope)) { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope)) { error in if case ManifestParseError.invalidManifestFormat(let message, _, _) = error { XCTAssertMatch(message, .contains("error: 'productItem(name:package:condition:)' is unavailable: use .product(name:package:condition) instead.")) } else { @@ -144,7 +144,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { } } - func testPlatforms() throws { + func testPlatforms() async throws { let content = """ import PackageDescription let package = Package( @@ -158,7 +158,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -172,7 +172,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { ]) } - func testImportRestrictions() throws { + func testImportRestrictions() async throws { let content = """ import PackageDescription import BestModule @@ -181,7 +181,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { let observability = ObservabilitySystem.makeForTesting() let manifestLoader = ManifestLoader(toolchain: try UserToolchain.default, importRestrictions: (.v5_7, [])) - XCTAssertThrowsError(try loadAndValidateManifest(content, customManifestLoader: manifestLoader, observabilityScope: observability.topScope)) { error in + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, customManifestLoader: manifestLoader, observabilityScope: observability.topScope)) { error in if case ManifestParseError.importsRestrictedModules(let modules) = error { XCTAssertEqual(modules.sorted(), ["BestModule", "Foundation"]) } else { @@ -190,7 +190,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { } } - func testTargetDependencyProductInvalidPackage() throws { + func testTargetDependencyProductInvalidPackage() async throws { do { let content = """ import PackageDescription @@ -213,7 +213,7 @@ class PackageDescription5_7LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) testDiagnostics(validationDiagnostics) { result in result.checkUnordered(diagnostic: "unknown package 'org.baz' in dependencies of target 'Target1'; valid packages are: 'org.foo', 'org.bar'", severity: .error) diff --git a/Tests/PackageLoadingTests/PD_5_9_LoadingTests.swift b/Tests/PackageLoadingTests/PD_5_9_LoadingTests.swift index 5890021a1f1..88f712c2ceb 100644 --- a/Tests/PackageLoadingTests/PD_5_9_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_5_9_LoadingTests.swift @@ -16,12 +16,12 @@ import PackageModel import SPMTestSupport import XCTest -class PackageDescription5_9LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription5_9LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { .v5_9 } - func testPlatforms() throws { + func testPlatforms() async throws { let content = """ import PackageDescription let package = Package( @@ -35,7 +35,7 @@ class PackageDescription5_9LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (manifest, validationDiagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (manifest, validationDiagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertNoDiagnostics(validationDiagnostics) @@ -50,7 +50,7 @@ class PackageDescription5_9LoadingTests: PackageDescriptionLoadingTests { ]) } - func testMacroTargets() throws { + func testMacroTargets() async throws { let content = """ import CompilerPluginSupport import PackageDescription @@ -63,7 +63,7 @@ class PackageDescription5_9LoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - let (_, diagnostics) = try loadAndValidateManifest(content, observabilityScope: observability.topScope) + let (_, diagnostics) = try await loadAndValidateManifest(content, observabilityScope: observability.topScope) XCTAssertEqual(diagnostics.count, 0, "unexpected diagnostics: \(diagnostics)") } } diff --git a/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift b/Tests/PackageLoadingTests/PD_6_0_LoadingTests.swift similarity index 73% rename from Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift rename to Tests/PackageLoadingTests/PD_6_0_LoadingTests.swift index 36b4c8a6f91..8e6144278bf 100644 --- a/Tests/PackageLoadingTests/PD_5_11_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_6_0_LoadingTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Copyright (c) 2023-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -16,42 +16,48 @@ import SourceControl import SPMTestSupport import XCTest -class PackageDescription5_11LoadingTests: PackageDescriptionLoadingTests { +final class PackageDescription6_0LoadingTests: PackageDescriptionLoadingTests { override var toolsVersion: ToolsVersion { - .v5_11 + .v6_0 } - func testPackageContextGitStatus() throws { + func testPackageContextGitStatus() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + let content = """ import PackageDescription let package = Package(name: "\\(Context.gitInformation?.hasUncommittedChanges == true)") """ - try loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in + try await loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(manifest.displayName, "true") } } - func testPackageContextGitTag() throws { + func testPackageContextGitTag() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + let content = """ import PackageDescription let package = Package(name: "\\(Context.gitInformation?.currentTag ?? "")") """ - try loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in + try await loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(manifest.displayName, "lunch") } } - func testPackageContextGitCommit() throws { + func testPackageContextGitCommit() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + let content = """ import PackageDescription let package = Package(name: "\\(Context.gitInformation?.currentCommit ?? "")") """ - try loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in + try await loadRootManifestWithBasicGitRepository(manifestContent: content) { manifest, observability in XCTAssertNoDiagnostics(observability.diagnostics) let repo = GitRepository(path: manifest.path.parentDirectory) @@ -63,10 +69,10 @@ class PackageDescription5_11LoadingTests: PackageDescriptionLoadingTests { private func loadRootManifestWithBasicGitRepository( manifestContent: String, validator: (Manifest, TestingObservability) throws -> () - ) throws { + ) async throws { let observability = ObservabilitySystem.makeForTesting() - try testWithTemporaryDirectory { tmpdir in + try await testWithTemporaryDirectory { tmpdir in let manifestPath = tmpdir.appending(component: Manifest.filename) try localFileSystem.writeFileContents(manifestPath, string: manifestContent) try localFileSystem.writeFileContents(tmpdir.appending("best.txt"), string: "best") @@ -77,7 +83,7 @@ class PackageDescription5_11LoadingTests: PackageDescriptionLoadingTests { try repo.commit(message: "best") try repo.tag(name: "lunch") - let manifest = try manifestLoader.load( + let manifest = try await manifestLoader.load( manifestPath: manifestPath, packageKind: .root(tmpdir), toolsVersion: self.toolsVersion, diff --git a/Tests/PackageLoadingTests/PD_Next_LoadingTests.swift b/Tests/PackageLoadingTests/PD_Next_LoadingTests.swift index 4c0d6da3644..f922e03e332 100644 --- a/Tests/PackageLoadingTests/PD_Next_LoadingTests.swift +++ b/Tests/PackageLoadingTests/PD_Next_LoadingTests.swift @@ -21,7 +21,7 @@ class PackageDescriptionNextLoadingTests: PackageDescriptionLoadingTests { .vNext } - func testImplicitFoundationImportFails() throws { + func testImplicitFoundationImportFails() async throws { let content = """ import PackageDescription @@ -31,7 +31,7 @@ class PackageDescriptionNextLoadingTests: PackageDescriptionLoadingTests { """ let observability = ObservabilitySystem.makeForTesting() - XCTAssertThrowsError(try loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { + await XCTAssertAsyncThrowsError(try await loadAndValidateManifest(content, observabilityScope: observability.topScope), "expected error") { if case ManifestParseError.invalidManifestFormat(let error, _, _) = $0 { XCTAssertMatch(error, .contains("cannot find 'FileManager' in scope")) } else { diff --git a/Tests/PackageLoadingTests/PackageBuilderTests.swift b/Tests/PackageLoadingTests/PackageBuilderTests.swift index 6d530c4cf62..d42e401f7c7 100644 --- a/Tests/PackageLoadingTests/PackageBuilderTests.swift +++ b/Tests/PackageLoadingTests/PackageBuilderTests.swift @@ -1963,15 +1963,36 @@ final class PackageBuilderTests: XCTestCase { package.checkProduct("foo") { _ in } } + manifest = try createManifest(swiftVersions: [SwiftLanguageVersion(string: "5")!]) + PackageBuilderTester(manifest, in: fs) { package, diagnostics in + package.checkModule("foo") { module in + module.check(swiftVersion: "5") + } + package.checkProduct("foo") { _ in } + } + + manifest = try createManifest(swiftVersions: [SwiftLanguageVersion(string: "6")!]) + PackageBuilderTester(manifest, in: fs) { package, diagnostics in + package.checkModule("foo") { module in + module.check(swiftVersion: "6") + } + package.checkProduct("foo") { _ in } + } + manifest = try createManifest(swiftVersions: []) PackageBuilderTester(manifest, in: fs) { package, diagnostics in - diagnostics.check(diagnostic: "package '\(package.packageIdentity)' supported Swift language versions is empty", severity: .error) + diagnostics.check( + diagnostic: "package '\(package.packageIdentity)' supported Swift language versions is empty", + severity: .error + ) } - manifest = try createManifest( - swiftVersions: [SwiftLanguageVersion(string: "6")!, SwiftLanguageVersion(string: "7")!]) + manifest = try createManifest(swiftVersions: [SwiftLanguageVersion(string: "7")!]) PackageBuilderTester(manifest, in: fs) { package, diagnostics in - diagnostics.check(diagnostic: "package '\(package.packageIdentity)' requires minimum Swift language version 6 which is not supported by the current tools version (\(ToolsVersion.current))", severity: .error) + diagnostics.check( + diagnostic: "package '\(package.packageIdentity)' requires minimum Swift language version 7 which is not supported by the current tools version (\(ToolsVersion.current))", + severity: .error + ) } } @@ -2991,8 +3012,12 @@ final class PackageBuilderTests: XCTestCase { assignment.values = ["YOLO"] assignment.conditions = [PackageCondition(platforms: [.custom(name: "bestOS", oldestSupportedVersion: .unknown)])] + var versionAssignment = BuildSettings.Assignment(default: true) + versionAssignment.values = ["4"] + var settings = BuildSettings.AssignmentTable() settings.add(assignment, for: .SWIFT_ACTIVE_COMPILATION_CONDITIONS) + settings.add(versionAssignment, for: .SWIFT_VERSION) PackageBuilderTester(manifest, in: fs) { package, _ in package.checkModule("Foo") { module in @@ -3001,6 +3026,69 @@ final class PackageBuilderTests: XCTestCase { } } } + + func testSwiftLanguageVersionPerTarget() throws { + let fs = InMemoryFileSystem(emptyFiles: + "/Sources/foo/foo.swift", + "/Sources/bar/bar.swift" + ) + + let manifest = Manifest.createRootManifest( + displayName: "pkg", + toolsVersion: .v5, + targets: [ + try TargetDescription( + name: "foo", + settings: [ + .init(tool: .swift, kind: .swiftLanguageVersion(.v5)) + ] + ), + try TargetDescription( + name: "bar", + settings: [ + .init(tool: .swift, kind: .swiftLanguageVersion(.v3), condition: .init(platformNames: ["linux"])), + .init(tool: .swift, kind: .swiftLanguageVersion(.v4), condition: .init(platformNames: ["macos"], config: "debug")) + ] + ), + ] + ) + + PackageBuilderTester(manifest, in: fs) { package, _ in + package.checkModule("foo") { package in + let macosDebugScope = BuildSettings.Scope( + package.target.buildSettings, + environment: BuildEnvironment(platform: .macOS, configuration: .debug) + ) + XCTAssertEqual(macosDebugScope.evaluate(.SWIFT_VERSION), ["5"]) + + let macosReleaseScope = BuildSettings.Scope( + package.target.buildSettings, + environment: BuildEnvironment(platform: .macOS, configuration: .release) + ) + XCTAssertEqual(macosReleaseScope.evaluate(.SWIFT_VERSION), ["5"]) + } + + package.checkModule("bar") { package in + let linuxDebugScope = BuildSettings.Scope( + package.target.buildSettings, + environment: BuildEnvironment(platform: .linux, configuration: .debug) + ) + XCTAssertEqual(linuxDebugScope.evaluate(.SWIFT_VERSION), ["3"]) + + let macosDebugScope = BuildSettings.Scope( + package.target.buildSettings, + environment: BuildEnvironment(platform: .macOS, configuration: .debug) + ) + XCTAssertEqual(macosDebugScope.evaluate(.SWIFT_VERSION), ["4"]) + + let macosReleaseScope = BuildSettings.Scope( + package.target.buildSettings, + environment: BuildEnvironment(platform: .macOS, configuration: .release) + ) + XCTAssertEqual(macosReleaseScope.evaluate(.SWIFT_VERSION), ["5"]) + } + } + } } final class PackageBuilderTester { @@ -3219,7 +3307,9 @@ final class PackageBuilderTester { guard case let swiftTarget as SwiftTarget = target else { return XCTFail("\(target) is not a swift target", file: file, line: line) } - XCTAssertEqual(SwiftLanguageVersion(string: swiftVersion)!, swiftTarget.swiftVersion, file: file, line: line) + let versionAssignments = swiftTarget.buildSettings.assignments[.SWIFT_VERSION]? + .filter { $0.conditions.isEmpty }.flatMap(\.values) + XCTAssertNotNil(versionAssignments?.contains(swiftVersion), file: file, line: line) } func check(pluginCapability: PluginCapability, file: StaticString = #file, line: UInt = #line) { diff --git a/Tests/PackageLoadingTests/PkgConfigParserTests.swift b/Tests/PackageLoadingTests/PkgConfigParserTests.swift index 009f1328ff4..110bf941ff7 100644 --- a/Tests/PackageLoadingTests/PkgConfigParserTests.swift +++ b/Tests/PackageLoadingTests/PkgConfigParserTests.swift @@ -244,12 +244,92 @@ final class PkgConfigParserTests: XCTestCase { } } + func testSysrootDir() throws { + // sysroot should be prepended to all path variables, and should therefore appear in cflags and libs. + try loadPCFile("gtk+-3.0.pc", sysrootDir: "/opt/sysroot/somewhere") { parser in + XCTAssertEqual(parser.variables, [ + "libdir": "/opt/sysroot/somewhere/usr/local/Cellar/gtk+3/3.18.9/lib", + "gtk_host": "x86_64-apple-darwin15.3.0", + "includedir": "/opt/sysroot/somewhere/usr/local/Cellar/gtk+3/3.18.9/include", + "prefix": "/opt/sysroot/somewhere/usr/local/Cellar/gtk+3/3.18.9", + "gtk_binary_version": "3.0.0", + "exec_prefix": "/opt/sysroot/somewhere/usr/local/Cellar/gtk+3/3.18.9", + "targets": "quartz", + "pcfiledir": parser.pcFile.parentDirectory.pathString, + "pc_sysrootdir": "/opt/sysroot/somewhere" + ]) + XCTAssertEqual(parser.dependencies, ["gdk-3.0", "atk", "cairo", "cairo-gobject", "gdk-pixbuf-2.0", "gio-2.0"]) + XCTAssertEqual(parser.privateDependencies, ["atk", "epoxy", "gio-unix-2.0"]) + XCTAssertEqual(parser.cFlags, ["-I/opt/sysroot/somewhere/usr/local/Cellar/gtk+3/3.18.9/include/gtk-3.0"]) + XCTAssertEqual(parser.libs, ["-L/opt/sysroot/somewhere/usr/local/Cellar/gtk+3/3.18.9/lib", "-lgtk-3"]) + } + + // sysroot should be not be prepended if it is already a prefix + // - pkgconf makes this check, but pkg-config does not + // - If the .pc file lies outside sysrootDir, pkgconf sets pc_sysrootdir to the empty string + // https://github.com/pkgconf/pkgconf/issues/213 + // SwiftPM does not currently implement this special case. + try loadPCFile("gtk+-3.0.pc", sysrootDir: "/usr/local/Cellar") { parser in + XCTAssertEqual(parser.variables, [ + "libdir": "/usr/local/Cellar/gtk+3/3.18.9/lib", + "gtk_host": "x86_64-apple-darwin15.3.0", + "includedir": "/usr/local/Cellar/gtk+3/3.18.9/include", + "prefix": "/usr/local/Cellar/gtk+3/3.18.9", + "gtk_binary_version": "3.0.0", + "exec_prefix": "/usr/local/Cellar/gtk+3/3.18.9", + "targets": "quartz", + "pcfiledir": parser.pcFile.parentDirectory.pathString, + "pc_sysrootdir": "/usr/local/Cellar" + ]) + XCTAssertEqual(parser.dependencies, ["gdk-3.0", "atk", "cairo", "cairo-gobject", "gdk-pixbuf-2.0", "gio-2.0"]) + XCTAssertEqual(parser.privateDependencies, ["atk", "epoxy", "gio-unix-2.0"]) + XCTAssertEqual(parser.cFlags, ["-I/usr/local/Cellar/gtk+3/3.18.9/include/gtk-3.0"]) + XCTAssertEqual(parser.libs, ["-L/usr/local/Cellar/gtk+3/3.18.9/lib", "-lgtk-3"]) + } + + // sysroot should be not be double-prepended if it is used explicitly by the .pc file + // - pkgconf makes this check, but pkg-config does not + try loadPCFile("double_sysroot.pc", sysrootDir: "/sysroot") { parser in + XCTAssertEqual(parser.variables, [ + "prefix": "/sysroot/usr", + "datarootdir": "/sysroot/usr/share", + "pkgdatadir": "/sysroot/usr/share/pkgdata", + "pcfiledir": parser.pcFile.parentDirectory.pathString, + "pc_sysrootdir": "/sysroot" + ]) + } + + // pkgconfig strips a leading sysroot prefix if sysroot appears anywhere else in the + // expanded variable. SwiftPM's implementation is faithful to pkgconfig, even + // thought it might seem more logical not to strip the prefix in this case. + try loadPCFile("not_double_sysroot.pc", sysrootDir: "/sysroot") { parser in + XCTAssertEqual(parser.variables, [ + "prefix": "/sysroot/usr", + "datarootdir": "/sysroot/usr/share", + "pkgdatadir": "/filler/sysroot/usr/share/pkgdata", + "pcfiledir": parser.pcFile.parentDirectory.pathString, + "pc_sysrootdir": "/sysroot" + ]) + } + + // pkgconfig does not strip sysroot if it is a relative path + try loadPCFile("double_sysroot.pc", sysrootDir: "sysroot") { parser in + XCTAssertEqual(parser.variables, [ + "prefix": "sysroot/usr", + "datarootdir": "sysroot/usr/share", + "pkgdatadir": "sysroot/sysroot/usr/share/pkgdata", + "pcfiledir": parser.pcFile.parentDirectory.pathString, + "pc_sysrootdir": "sysroot" + ]) + } + } + private func pcFilePath(_ inputName: String) -> AbsolutePath { return AbsolutePath(#file).parentDirectory.appending(components: "pkgconfigInputs", inputName) } - private func loadPCFile(_ inputName: String, body: ((PkgConfigParser) -> Void)? = nil) throws { - var parser = try PkgConfigParser(pcFile: pcFilePath(inputName), fileSystem: localFileSystem) + private func loadPCFile(_ inputName: String, sysrootDir: String? = nil, body: ((PkgConfigParser) -> Void)? = nil) throws { + var parser = try PkgConfigParser(pcFile: pcFilePath(inputName), fileSystem: localFileSystem, sysrootDir: sysrootDir) try parser.parse() body?(parser) } diff --git a/Tests/PackageLoadingTests/ToolsVersionParserTests.swift b/Tests/PackageLoadingTests/ToolsVersionParserTests.swift index bf4145dfbdc..486828a7254 100644 --- a/Tests/PackageLoadingTests/ToolsVersionParserTests.swift +++ b/Tests/PackageLoadingTests/ToolsVersionParserTests.swift @@ -154,12 +154,12 @@ class ToolsVersionParserTests: XCTestCase { """ // comment 1 // comment 2 - // swift-tools-version: 5.11 + // swift-tools-version: 6.0 // comment let package = .. """ ) { toolsVersion in - XCTAssertEqual(toolsVersion.description, "5.11.0") + XCTAssertEqual(toolsVersion.description, "6.0.0") } do { @@ -174,7 +174,7 @@ class ToolsVersionParserTests: XCTestCase { ) { _ in XCTFail("expected an error to be thrown") } - } catch ToolsVersionParser.Error.backwardIncompatiblePre5_11(let incompatibility, _) { + } catch ToolsVersionParser.Error.backwardIncompatiblePre6_0(let incompatibility, _) { XCTAssertEqual(incompatibility, .toolsVersionNeedsToBeFirstLine) } catch { XCTFail("unexpected error: \(error)") @@ -201,12 +201,12 @@ class ToolsVersionParserTests: XCTestCase { /* this is a multiline comment */ - // swift-tools-version: 5.11 + // swift-tools-version: 6.0 // comment let package = .. """ ) { toolsVersion in - XCTAssertEqual(toolsVersion.description, "5.11.0") + XCTAssertEqual(toolsVersion.description, "6.0.0") } } diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/case_insensitive.pc b/Tests/PackageLoadingTests/pkgconfigInputs/case_insensitive.pc index b65a14c7d16..83017682ea9 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/case_insensitive.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/case_insensitive.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} #some comment +Name: case_insensitive +Version: 1 +Description: Demonstrate that pkgconfig keys are case-insensitive # upstream pkg-config parser allows Cflags & CFlags as key CFlags: -I/usr/local/include diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/deps_variable.pc b/Tests/PackageLoadingTests/pkgconfigInputs/deps_variable.pc index 225d86d1369..de06c50db85 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/deps_variable.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/deps_variable.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} my_dep=atk #some comment +Name: deps_variable +Version: 1 +Description: Demonstrate use of a locally-defined variable Requires: gdk-3.0 >= 1.0.0 ${my_dep} Libs: -L${prefix} -lgtk-3 diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/double_sysroot.pc b/Tests/PackageLoadingTests/pkgconfigInputs/double_sysroot.pc new file mode 100644 index 00000000000..be7abee01ce --- /dev/null +++ b/Tests/PackageLoadingTests/pkgconfigInputs/double_sysroot.pc @@ -0,0 +1,7 @@ +prefix=/usr +datarootdir=${prefix}/share +pkgdatadir=${pc_sysrootdir}/${datarootdir}/pkgdata + +Name: double_sysroot +Description: Demonstrate double-prefixing of pc_sysrootdir (https://github.com/pkgconf/pkgconf/issues/123) +Version: 1 diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/dummy_dependency.pc b/Tests/PackageLoadingTests/pkgconfigInputs/dummy_dependency.pc index c4ae253c81a..e3a797923da 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/dummy_dependency.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/dummy_dependency.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} #some comment +Name: dummy_dependency +Version: 1 +Description: Demonstrate a blank dependency entry Requires: pango, , fontconfig >= 2.13.0 Libs:-L${prefix} -lpangoft2-1.0 diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/empty_cflags.pc b/Tests/PackageLoadingTests/pkgconfigInputs/empty_cflags.pc index 0f7f8e6c1a2..a7d2ffa6bae 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/empty_cflags.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/empty_cflags.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} #some comment +Name: empty_cflags +Version: 1 +Description: Demonstrate an empty cflags list Requires: gdk-3.0 atk Libs:-L${prefix} -lgtk-3 diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/escaped_spaces.pc b/Tests/PackageLoadingTests/pkgconfigInputs/escaped_spaces.pc index f00283e070f..e0e77e73774 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/escaped_spaces.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/escaped_spaces.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} my_dep=atk #some comment +Name: escaped_spaces +Version: 1 +Description: Demonstrate use of escape characters in flag values Requires: gdk-3.0 >= 1.0.0 ${my_dep} Libs: -L"${prefix}" -l"gtk 3" -wantareal\\here -one\\ -two diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/failure_case.pc b/Tests/PackageLoadingTests/pkgconfigInputs/failure_case.pc index e81dc924502..f1981c1b3d4 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/failure_case.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/failure_case.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} #some comment +Name: failure_case +Version: 1 +Description: Demonstrate failure caused by use of an undefined variable Requires: gdk-3.0 >= 1.0.0 Libs: -L${prefix} -lgtk-3 ${my_dep} diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/not_double_sysroot.pc b/Tests/PackageLoadingTests/pkgconfigInputs/not_double_sysroot.pc new file mode 100644 index 00000000000..c537069beb6 --- /dev/null +++ b/Tests/PackageLoadingTests/pkgconfigInputs/not_double_sysroot.pc @@ -0,0 +1,6 @@ +prefix=/usr +datarootdir=${prefix}/share +pkgdatadir=${pc_sysrootdir}/filler/${datarootdir}/pkgdata + +Name: double_sysroot +Description: Demonstrate pc_sysrootdir appearing elsewhere in a path - this is not a double prefix and should not be removed \ No newline at end of file diff --git a/Tests/PackageLoadingTests/pkgconfigInputs/quotes_failure.pc b/Tests/PackageLoadingTests/pkgconfigInputs/quotes_failure.pc index 3006c2fbd85..07ec4503a76 100644 --- a/Tests/PackageLoadingTests/pkgconfigInputs/quotes_failure.pc +++ b/Tests/PackageLoadingTests/pkgconfigInputs/quotes_failure.pc @@ -2,6 +2,9 @@ prefix=/usr/local/bin exec_prefix=${prefix} my_dep=atk #some comment +Name: quotes_failure +Version: 1 +Description: Demonstrate failure due to unbalanced quotes Requires: gdk-3.0 >= 1.0.0 ${my_dep} Libs: -L"${prefix}" -l"gt"k3" -wantareal\\here -one\\ -two diff --git a/Tests/PackageModelSyntaxTests/ManifestEditTests.swift b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift new file mode 100644 index 00000000000..614ca912d3e --- /dev/null +++ b/Tests/PackageModelSyntaxTests/ManifestEditTests.swift @@ -0,0 +1,800 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +import Basics +import PackageModel +import PackageModelSyntax +import SPMTestSupport +@_spi(FixItApplier) import SwiftIDEUtils +import SwiftParser +import SwiftSyntax +import struct TSCUtility.Version +import XCTest + +/// Assert that applying the given edit/refactor operation to the manifest +/// produces the expected manifest source file and the expected auxiliary +/// files. +func assertManifestRefactor( + _ originalManifest: SourceFileSyntax, + expectedManifest: SourceFileSyntax, + expectedAuxiliarySources: [RelativePath: SourceFileSyntax] = [:], + file: StaticString = #filePath, + line: UInt = #line, + operation: (SourceFileSyntax) throws -> PackageEditResult +) rethrows { + let edits = try operation(originalManifest) + let editedManifestSource = FixItApplier.apply( + edits: edits.manifestEdits, + to: originalManifest + ) + + let editedManifest = Parser.parse(source: editedManifestSource) + assertStringsEqualWithDiff( + editedManifest.description, + expectedManifest.description, + file: file, + line: line + ) + + // Check all of the auxiliary sources. + for (auxSourcePath, auxSourceSyntax) in edits.auxiliaryFiles { + guard let expectedSyntax = expectedAuxiliarySources[auxSourcePath] else { + XCTFail("unexpected auxiliary source file \(auxSourcePath)") + return + } + + assertStringsEqualWithDiff( + auxSourceSyntax.description, + expectedSyntax.description, + file: file, + line: line + ) + } + + XCTAssertEqual( + edits.auxiliaryFiles.count, + expectedAuxiliarySources.count, + "didn't get all of the auxiliary files we expected" + ) +} + +class ManifestEditTests: XCTestCase { + static let swiftSystemURL: SourceControlURL = "https://github.com/apple/swift-system.git" + static let swiftSystemPackageDependency = PackageDependency.remoteSourceControl( + identity: PackageIdentity(url: swiftSystemURL), + nameForTargetDependencyResolutionOnly: nil, + url: swiftSystemURL, + requirement: .branch("main"), productFilter: .nothing + ) + + func testAddPackageDependencyExistingComma() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"), + ] + ) + """, expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", branch: "main"), + ] + ) + """) { manifest in + try AddPackageDependency.addPackageDependency( + PackageDependency.remoteSourceControl( + identity: PackageIdentity(url: Self.swiftSystemURL), + nameForTargetDependencyResolutionOnly: nil, + url: Self.swiftSystemURL, + requirement: .branch("main"), productFilter: .nothing + ), + to: manifest + ) + } + } + + func testAddPackageDependencyExistingNoComma() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1") + ] + ) + """, expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", exact: "510.0.0"), + ] + ) + """) { manifest in + try AddPackageDependency.addPackageDependency( + PackageDependency.remoteSourceControl( + identity: PackageIdentity(url: Self.swiftSystemURL), + nameForTargetDependencyResolutionOnly: nil, + url: Self.swiftSystemURL, + requirement: .exact("510.0.0"), + productFilter: .nothing + ), + to: manifest + ) + } + } + + func testAddPackageDependencyExistingAppended() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1") + ] + [] + ) + """, expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"), + .package(url: "https://github.com/apple/swift-system.git", from: "510.0.0"), + ] + [] + ) + """) { manifest in + let versionRange = Range.upToNextMajor(from: Version(510, 0, 0)) + + return try AddPackageDependency.addPackageDependency( + PackageDependency.remoteSourceControl( + identity: PackageIdentity(url: Self.swiftSystemURL), + nameForTargetDependencyResolutionOnly: nil, + url: Self.swiftSystemURL, + requirement: .range(versionRange), + productFilter: .nothing + ), + to: manifest + ) + } + } + + func testAddPackageDependencyExistingOneLine() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1") ] + ) + """, expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ .package(url: "https://github.com/apple/swift-syntax.git", from: "510.0.1"), .package(url: "https://github.com/apple/swift-system.git", from: "510.0.0"),] + ) + """) { manifest in + let versionRange = Range.upToNextMajor(from: Version(510, 0, 0)) + + return try AddPackageDependency.addPackageDependency( + PackageDependency.remoteSourceControl( + identity: PackageIdentity(url: Self.swiftSystemURL), + nameForTargetDependencyResolutionOnly: nil, + url: Self.swiftSystemURL, + requirement: .range(versionRange), + productFilter: .nothing + ), + to: manifest + ) + } + } + func testAddPackageDependencyExistingEmpty() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-system.git", "508.0.0" ..< "510.0.0"), + ] + ) + """) { manifest in + try AddPackageDependency.addPackageDependency( + PackageDependency.remoteSourceControl( + identity: PackageIdentity(url: Self.swiftSystemURL), + nameForTargetDependencyResolutionOnly: nil, + url: Self.swiftSystemURL, + requirement: .range(Version(508,0,0).. ExpressionMacro + /// @attached(member) macro --> MemberMacro + } + """, + RelativePath("Sources/MyMacro/ProvidedMacros.swift") : """ + import SwiftCompilerPlugin + + @main + struct MyMacroMacros: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + MyMacro.self, + ] + } + """ + ] + ) { manifest in + try AddTarget.addTarget( + TargetDescription(name: "MyMacro", type: .macro), + to: manifest + ) + } + } + + func testAddSwiftTestingTestTarget() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages" + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + ], + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ .product(name: "Testing", package: "swift-testing") ] + ), + ] + ) + """, + expectedAuxiliarySources: [ + RelativePath("Tests/MyTest/MyTest.swift") : """ + import Testing + + @Suite + struct MyTestTests { + @Test("MyTest tests") + func example() { + #expect(42 == 17 + 25) + } + } + """ + ]) { manifest in + try AddTarget.addTarget( + TargetDescription( + name: "MyTest", + type: .test + ), + to: manifest, + configuration: .init( + testHarness: .swiftTesting + ) + ) + } + } + + func testAddTargetDependency() throws { + try assertManifestRefactor(""" + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + ], + targets: [ + .testTarget( + name: "MyTest" + ), + ] + ) + """, + expectedManifest: """ + // swift-tools-version: 5.5 + let package = Package( + name: "packages", + dependencies: [ + .package(url: "https://github.com/apple/swift-testing.git", from: "0.8.0"), + ], + targets: [ + .testTarget( + name: "MyTest", + dependencies: [ + .product(name: "Testing", package: "swift-testing"), + ] + ), + ] + ) + """) { manifest in + try AddTargetDependency.addTargetDependency( + .product(name: "Testing", package: "swift-testing"), + targetName: "MyTest", + to: manifest + ) + } + } +} + + +// FIXME: Copy-paste from _SwiftSyntaxTestSupport + +/// Asserts that the two strings are equal, providing Unix `diff`-style output if they are not. +/// +/// - Parameters: +/// - actual: The actual string. +/// - expected: The expected string. +/// - message: An optional description of the failure. +/// - additionalInfo: Additional information about the failed test case that will be printed after the diff +/// - file: The file in which failure occurred. Defaults to the file name of the test case in +/// which this function was called. +/// - line: The line number on which failure occurred. Defaults to the line number on which this +/// function was called. +public func assertStringsEqualWithDiff( + _ actual: String, + _ expected: String, + _ message: String = "", + additionalInfo: @autoclosure () -> String? = nil, + file: StaticString = #filePath, + line: UInt = #line +) { + if actual == expected { + return + } + + failStringsEqualWithDiff( + actual, + expected, + message, + additionalInfo: additionalInfo(), + file: file, + line: line + ) +} + +/// Asserts that the two data are equal, providing Unix `diff`-style output if they are not. +/// +/// - Parameters: +/// - actual: The actual string. +/// - expected: The expected string. +/// - message: An optional description of the failure. +/// - additionalInfo: Additional information about the failed test case that will be printed after the diff +/// - file: The file in which failure occurred. Defaults to the file name of the test case in +/// which this function was called. +/// - line: The line number on which failure occurred. Defaults to the line number on which this +/// function was called. +public func assertDataEqualWithDiff( + _ actual: Data, + _ expected: Data, + _ message: String = "", + additionalInfo: @autoclosure () -> String? = nil, + file: StaticString = #filePath, + line: UInt = #line +) { + if actual == expected { + return + } + + // NOTE: Converting to `Stirng` here looses invalid UTF8 sequence difference, + // but at least we can see something is different. + failStringsEqualWithDiff( + String(decoding: actual, as: UTF8.self), + String(decoding: expected, as: UTF8.self), + message, + additionalInfo: additionalInfo(), + file: file, + line: line + ) +} + +/// `XCTFail` with `diff`-style output. +public func failStringsEqualWithDiff( + _ actual: String, + _ expected: String, + _ message: String = "", + additionalInfo: @autoclosure () -> String? = nil, + file: StaticString = #filePath, + line: UInt = #line +) { + let stringComparison: String + + // Use `CollectionDifference` on supported platforms to get `diff`-like line-based output. On + // older platforms, fall back to simple string comparison. + if #available(macOS 10.15, *) { + let actualLines = actual.components(separatedBy: .newlines) + let expectedLines = expected.components(separatedBy: .newlines) + + let difference = actualLines.difference(from: expectedLines) + + var result = "" + + var insertions = [Int: String]() + var removals = [Int: String]() + + for change in difference { + switch change { + case .insert(let offset, let element, _): + insertions[offset] = element + case .remove(let offset, let element, _): + removals[offset] = element + } + } + + var expectedLine = 0 + var actualLine = 0 + + while expectedLine < expectedLines.count || actualLine < actualLines.count { + if let removal = removals[expectedLine] { + result += "–\(removal)\n" + expectedLine += 1 + } else if let insertion = insertions[actualLine] { + result += "+\(insertion)\n" + actualLine += 1 + } else { + result += " \(expectedLines[expectedLine])\n" + expectedLine += 1 + actualLine += 1 + } + } + + stringComparison = result + } else { + // Fall back to simple message on platforms that don't support CollectionDifference. + stringComparison = """ + Expected: + \(expected) + + Actual: + \(actual) + """ + } + + var fullMessage = """ + \(message.isEmpty ? "Actual output does not match the expected" : message) + \(stringComparison) + """ + if let additional = additionalInfo() { + fullMessage = """ + \(fullMessage) + \(additional) + """ + } + XCTFail(fullMessage, file: file, line: line) +} diff --git a/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift b/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift index 42828726b2b..bcb2e58d62b 100644 --- a/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift +++ b/Tests/PackageModelTests/MinimumDeploymentTargetTests.swift @@ -22,7 +22,7 @@ class MinimumDeploymentTargetTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let result = ProcessResult(arguments: [], - environment: [:], + environmentBlock: [:], exitStatus: .terminated(code: 0), output: "".asResult, stderrOutput: "xcodebuild: error: SDK \"macosx\" cannot be located.".asResult) @@ -36,7 +36,7 @@ class MinimumDeploymentTargetTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let result = ProcessResult(arguments: [], - environment: [:], + environmentBlock: [:], exitStatus: .terminated(code: 0), output: "some string".asResult, stderrOutput: "".asResult) @@ -50,7 +50,7 @@ class MinimumDeploymentTargetTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif let result = ProcessResult(arguments: [], - environment: [:], + environmentBlock: [:], exitStatus: .terminated(code: 0), output: .failure(DummyError()), stderrOutput: "".asResult) diff --git a/Tests/PackageModelTests/PackageModelTests.swift b/Tests/PackageModelTests/PackageModelTests.swift index 54c32fa276e..7ba89f6927d 100644 --- a/Tests/PackageModelTests/PackageModelTests.swift +++ b/Tests/PackageModelTests/PackageModelTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(SwiftPMInternal) @testable import PackageModel + import func TSCBasic.withTemporaryFile import XCTest diff --git a/Tests/PackageModelTests/SwiftSDKBundleTests.swift b/Tests/PackageModelTests/SwiftSDKBundleTests.swift index 5650d09ec33..0491ba1e344 100644 --- a/Tests/PackageModelTests/SwiftSDKBundleTests.swift +++ b/Tests/PackageModelTests/SwiftSDKBundleTests.swift @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// import Basics -@_spi(SwiftPMInternal) @testable import PackageModel import SPMTestSupport import XCTest diff --git a/Tests/PackageModelTests/SwiftSDKTests.swift b/Tests/PackageModelTests/SwiftSDKTests.swift index 889906b76a5..1ddcd9c6f50 100644 --- a/Tests/PackageModelTests/SwiftSDKTests.swift +++ b/Tests/PackageModelTests/SwiftSDKTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,7 +11,10 @@ //===----------------------------------------------------------------------===// @testable import Basics + +@_spi(SwiftPMInternal) @testable import PackageModel + @testable import SPMBuildCore import XCTest @@ -21,6 +24,7 @@ private let bundleRootPath = try! AbsolutePath(validating: "/tmp/cross-toolchain private let toolchainBinDir = RelativePath("swift.xctoolchain/usr/bin") private let sdkRootDir = RelativePath("ubuntu-jammy.sdk") private let hostTriple = try! Triple("arm64-apple-darwin22.1.0") +private let olderHostTriple = try! Triple("arm64-apple-darwin20.1.0") private let linuxGNUTargetTriple = try! Triple("x86_64-unknown-linux-gnu") private let linuxMuslTargetTriple = try! Triple("x86_64-unknown-linux-musl") private let extraFlags = BuildFlags( @@ -291,6 +295,12 @@ private let parsedDestinationV2Musl = SwiftSDK( pathsConfiguration: .init(sdkRootPath: sdkRootAbsolutePath) ) +private let parsedDestinationForOlderHost = SwiftSDK( + targetTriple: linuxMuslTargetTriple, + toolset: .init(toolchainBinDir: toolchainBinAbsolutePath, buildFlags: extraFlags), + pathsConfiguration: .init(sdkRootPath: sdkRootAbsolutePath) +) + private let parsedToolsetNoRootDestination = SwiftSDK( targetTriple: linuxGNUTargetTriple, toolset: .init( @@ -520,6 +530,24 @@ final class DestinationTests: XCTestCase { swiftSDKs: [parsedDestinationV2Musl] ), ], + "id4": [ + .init( + metadata: .init( + path: "id4", + supportedTriples: [olderHostTriple] + ), + swiftSDKs: [parsedDestinationForOlderHost] + ), + ], + "id5": [ + .init( + metadata: .init( + path: "id5", + supportedTriples: nil + ), + swiftSDKs: [parsedDestinationV2GNU] + ), + ], ] ), ] @@ -553,5 +581,41 @@ final class DestinationTests: XCTestCase { ), parsedDestinationV2Musl ) + + // Newer hostTriple should match with older supportedTriples + XCTAssertEqual( + bundles.selectSwiftSDK( + id: "id4", + hostTriple: hostTriple, + targetTriple: linuxMuslTargetTriple + ), + parsedDestinationForOlderHost + ) + XCTAssertEqual( + bundles.selectSwiftSDK( + matching: "id4", + hostTriple: hostTriple, + observabilityScope: system.topScope + ), + parsedDestinationForOlderHost + ) + + // nil supportedTriples should match with any hostTriple + XCTAssertEqual( + bundles.selectSwiftSDK( + id: "id5", + hostTriple: hostTriple, + targetTriple: linuxGNUTargetTriple + ), + parsedDestinationV2GNU + ) + XCTAssertEqual( + bundles.selectSwiftSDK( + matching: "id5", + hostTriple: hostTriple, + observabilityScope: system.topScope + ), + parsedDestinationV2GNU + ) } } diff --git a/Tests/PackageModelTests/ToolsVersionTests.swift b/Tests/PackageModelTests/ToolsVersionTests.swift index e80c36235b5..8d3ab3feaeb 100644 --- a/Tests/PackageModelTests/ToolsVersionTests.swift +++ b/Tests/PackageModelTests/ToolsVersionTests.swift @@ -91,8 +91,12 @@ class ToolsVersionTests: XCTestCase { XCTAssertEqual(ToolsVersion(string: version)?.swiftLanguageVersion.description, "4.2") } - for version in ["5.0.0", "5.1.9", "6.0.0", "7.0.0"] { + for version in ["5.0.0", "5.1.9"] { XCTAssertEqual(ToolsVersion(string: version)?.swiftLanguageVersion.description, "5") } + + for version in ["6.0.0", "7.0.0"] { + XCTAssertEqual(ToolsVersion(string: version)?.swiftLanguageVersion.description, "6") + } } } diff --git a/Tests/PackageRegistryTests/RegistryConfigurationTests.swift b/Tests/PackageRegistryTests/RegistryConfigurationTests.swift index d7fbed94526..93375acf908 100644 --- a/Tests/PackageRegistryTests/RegistryConfigurationTests.swift +++ b/Tests/PackageRegistryTests/RegistryConfigurationTests.swift @@ -138,7 +138,7 @@ final class RegistryConfigurationTests: XCTestCase { }, "bar": { "url": "\#(customRegistryBaseURL)" - }, + } }, "authentication": { "packages.example.com": { diff --git a/Tests/QueryEngineTests/QueryEngineTests.swift b/Tests/QueryEngineTests/QueryEngineTests.swift new file mode 100644 index 00000000000..39c92af40d2 --- /dev/null +++ b/Tests/QueryEngineTests/QueryEngineTests.swift @@ -0,0 +1,153 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +import class Basics.ObservabilitySystem +import struct Foundation.Data +@testable import QueryEngine +import struct SystemPackage.FilePath +import SPMTestSupport +import XCTest + +private let encoder = JSONEncoder() +private let decoder = JSONDecoder() + +private extension AsyncFileSystem { + func read(_ path: FilePath, bufferLimit: Int = 10 * 1024 * 1024, as: V.Type) async throws -> V { + let data = try await self.withOpenReadableFile(path) { + var data = Data() + for try await chunk in try await $0.read() { + data.append(contentsOf: chunk) + + guard data.count < bufferLimit else { + throw FileSystemError.bufferLimitExceeded(path) + } + } + return data + } + + return try decoder.decode(V.self, from: data) + } + + func write(_ path: FilePath, _ value: some Encodable) async throws { + let data = try encoder.encode(value) + try await self.withOpenWritableFile(path) { fileHandle in + try await fileHandle.write(data) + } + } +} + +private struct Const: Query { + let x: Int + + func run(engine: QueryEngine) async throws -> FilePath { + let resultPath = FilePath("/Const-\(x)") + try await engine.fileSystem.write(resultPath, self.x) + return resultPath + } +} + +private struct MultiplyByTwo: Query { + let x: Int + + func run(engine: QueryEngine) async throws -> FilePath { + let constPath = try await engine[Const(x: self.x)].path + let constResult = try await engine.fileSystem.read(constPath, as: Int.self) + + let resultPath = FilePath("/MultiplyByTwo-\(constResult)") + try await engine.fileSystem.write(resultPath, constResult * 2) + return resultPath + } +} + +private struct AddThirty: Query { + let x: Int + + func run(engine: QueryEngine) async throws -> FilePath { + let constPath = try await engine[Const(x: self.x)].path + let constResult = try await engine.fileSystem.read(constPath, as: Int.self) + + let resultPath = FilePath("/AddThirty-\(constResult)") + try await engine.fileSystem.write(resultPath, constResult + 30) + return resultPath + } +} + +private struct Expression: Query { + let x: Int + let y: Int + + func run(engine: QueryEngine) async throws -> FilePath { + let multiplyPath = try await engine[MultiplyByTwo(x: self.x)].path + let addThirtyPath = try await engine[AddThirty(x: self.y)].path + + let multiplyResult = try await engine.fileSystem.read(multiplyPath, as: Int.self) + let addThirtyResult = try await engine.fileSystem.read(addThirtyPath, as: Int.self) + + let resultPath = FilePath("/Expression-\(multiplyResult)-\(addThirtyResult)") + try await engine.fileSystem.write(resultPath, multiplyResult + addThirtyResult) + return resultPath + } +} + +final class QueryEngineTests: XCTestCase { + func testSimpleCaching() async throws { + let observabilitySystem = ObservabilitySystem.makeForTesting() + let engine = QueryEngine( + VirtualFileSystem(), + observabilitySystem.topScope, + cacheLocation: .memory + ) + + var resultPath = try await engine[Expression(x: 1, y: 2)].path + var result = try await engine.fileSystem.read(resultPath, as: Int.self) + + XCTAssertEqual(result, 34) + + var cacheMisses = await engine.cacheMisses + XCTAssertEqual(cacheMisses, 5) + + var cacheHits = await engine.cacheHits + XCTAssertEqual(cacheHits, 0) + + resultPath = try await engine[Expression(x: 1, y: 2)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + XCTAssertEqual(result, 34) + + cacheMisses = await engine.cacheMisses + XCTAssertEqual(cacheMisses, 5) + + cacheHits = await engine.cacheHits + XCTAssertEqual(cacheHits, 1) + + resultPath = try await engine[Expression(x: 2, y: 1)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + XCTAssertEqual(result, 35) + + cacheMisses = await engine.cacheMisses + XCTAssertEqual(cacheMisses, 8) + + cacheHits = await engine.cacheHits + XCTAssertEqual(cacheHits, 3) + + resultPath = try await engine[Expression(x: 2, y: 1)].path + result = try await engine.fileSystem.read(resultPath, as: Int.self) + XCTAssertEqual(result, 35) + + cacheMisses = await engine.cacheMisses + XCTAssertEqual(cacheMisses, 8) + + cacheHits = await engine.cacheHits + XCTAssertEqual(cacheHits, 4) + + try await engine.shutDown() + } +} diff --git a/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift b/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift index 4e3690ef2f4..2ab3ce8cdb4 100644 --- a/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift +++ b/Tests/SPMBuildCoreTests/ArtifactsArchiveMetadataTests.swift @@ -65,4 +65,61 @@ final class ArtifactsArchiveMetadataTests: XCTestCase { ] )) } + func testParseMetadataWithoutSupportedTriple() throws { + let fileSystem = InMemoryFileSystem() + try fileSystem.writeFileContents( + "/info.json", + string: """ + { + "schemaVersion": "1.0", + "artifacts": { + "protocol-buffer-compiler": { + "type": "executable", + "version": "3.5.1", + "variants": [ + { + "path": "x86_64-apple-macosx/protoc" + }, + { + "path": "x86_64-unknown-linux-gnu/protoc", + "supportedTriples": null + } + ] + } + } + } + """ + ) + + let metadata = try ArtifactsArchiveMetadata.parse(fileSystem: fileSystem, rootPath: .root) + XCTAssertEqual(metadata, ArtifactsArchiveMetadata( + schemaVersion: "1.0", + artifacts: [ + "protocol-buffer-compiler": ArtifactsArchiveMetadata.Artifact( + type: .executable, + version: "3.5.1", + variants: [ + ArtifactsArchiveMetadata.Variant( + path: "x86_64-apple-macosx/protoc", + supportedTriples: nil + ), + ArtifactsArchiveMetadata.Variant( + path: "x86_64-unknown-linux-gnu/protoc", + supportedTriples: nil + ), + ] + ), + ] + )) + + let binaryTarget = BinaryTarget( + name: "protoc", kind: .artifactsArchive, path: .root, origin: .local + ) + // No supportedTriples with binaryTarget should be rejected + XCTAssertThrowsError( + try binaryTarget.parseArtifactArchives( + for: Triple("x86_64-apple-macosx"), fileSystem: fileSystem + ) + ) + } } diff --git a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift index 490c6e421fe..eb9be4cd970 100644 --- a/Tests/SPMBuildCoreTests/PluginInvocationTests.swift +++ b/Tests/SPMBuildCoreTests/PluginInvocationTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors +// Copyright (c) 2021-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -11,9 +11,15 @@ //===----------------------------------------------------------------------===// import Basics + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) @testable import PackageGraph + import PackageLoading + +@_spi(SwiftPMInternal) import PackageModel + @testable import SPMBuildCore import SPMTestSupport import Workspace @@ -23,7 +29,7 @@ import class TSCBasic.InMemoryFileSystem import struct TSCUtility.SerializedDiagnostics -class PluginInvocationTests: XCTestCase { +final class PluginInvocationTests: XCTestCase { func testBasics() throws { // Construct a canned file system and package graph with a single package and a library that uses a build tool plugin that invokes a tool. @@ -34,7 +40,7 @@ class PluginInvocationTests: XCTestCase { "/Foo/Sources/Foo/SomeFile.abc" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fileSystem, manifests: [ Manifest.createRootManifest( @@ -74,7 +80,8 @@ class PluginInvocationTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) PackageGraphTester(graph) { graph in graph.check(packages: "Foo") - graph.check(targets: "Foo", "FooPlugin", "FooTool") + // "FooTool" duplicated as it's present for both build tools and end products triples. + graph.check(targets: "Foo", "FooPlugin", "FooTool", "FooTool") graph.checkTarget("Foo") { target in target.check(dependencies: "FooPlugin") } @@ -82,8 +89,10 @@ class PluginInvocationTests: XCTestCase { target.check(type: .plugin) target.check(dependencies: "FooTool") } - graph.checkTarget("FooTool") { target in - target.check(type: .executable) + graph.checkTargets("FooTool") { targets in + for target in targets { + target.check(type: .executable) + } } } @@ -188,13 +197,13 @@ class PluginInvocationTests: XCTestCase { // Construct a canned input and run plugins using our MockPluginScriptRunner(). let outputDir = AbsolutePath("/Foo/.build") - let builtToolsDir = AbsolutePath("/path/to/build/debug") let pluginRunner = MockPluginScriptRunner() + let buildParameters = mockBuildParameters( + environment: BuildEnvironment(platform: .macOS, configuration: .debug) + ) let results = try graph.invokeBuildToolPlugins( outputDir: outputDir, - buildParameters: mockBuildParameters( - environment: BuildEnvironment(platform: .macOS, configuration: .debug) - ), + buildParameters: buildParameters, additionalFileRules: [], toolSearchDirectories: [UserToolchain.default.swiftCompilerPath.parentDirectory], pkgConfigDirectories: [], @@ -202,11 +211,12 @@ class PluginInvocationTests: XCTestCase { observabilityScope: observability.topScope, fileSystem: fileSystem ) + let builtToolsDir = AbsolutePath("/path/to/build/\(buildParameters.triple)/debug") // Check the canned output to make sure nothing was lost in transport. XCTAssertNoDiagnostics(observability.diagnostics) XCTAssertEqual(results.count, 1) - let (evalTargetID, (evalTarget, evalResults)) = try XCTUnwrap(results.first) + let (_, (evalTarget, evalResults)) = try XCTUnwrap(results.first) XCTAssertEqual(evalTarget.name, "Foo") XCTAssertEqual(evalResults.count, 1) @@ -294,7 +304,10 @@ class PluginInvocationTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssert(packageGraph.packages.count == 1, "\(packageGraph.packages)") @@ -671,7 +684,10 @@ class PluginInvocationTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - XCTAssertThrowsError(try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope)) { error in + XCTAssertThrowsError(try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + )) { error in var diagnosed = false if let realError = error as? PackageGraphError, realError.description == "plugin 'MyPlugin' cannot depend on 'FooLib' of type 'library' from package 'foopackage'; this dependency is unsupported" { @@ -747,7 +763,9 @@ class PluginInvocationTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - XCTAssertThrowsError(try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope)) { error in + XCTAssertThrowsError(try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope)) { error in var diagnosed = false if let realError = error as? PackageGraphError, realError.description == "plugin 'MyPlugin' cannot depend on 'MyLibrary' of type 'library'; this dependency is unsupported" { @@ -854,7 +872,10 @@ class PluginInvocationTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) XCTAssert(packageGraph.packages.count == 1, "\(packageGraph.packages)") @@ -1034,7 +1055,10 @@ class PluginInvocationTests: XCTestCase { ) XCTAssert(rootManifests.count == 1, "\(rootManifests)") - let graph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let graph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) let dict = try await workspace.loadPluginImports(packageGraph: graph) var count = 0 @@ -1062,7 +1086,6 @@ class PluginInvocationTests: XCTestCase { } } - XCTAssertEqual(count, 2) } } @@ -1070,7 +1093,7 @@ class PluginInvocationTests: XCTestCase { func checkParseArtifactsPlatformCompatibility( artifactSupportedTriples: [Triple], hostTriple: Triple - ) async throws -> [ResolvedTarget.ID: [BuildToolPluginInvocationResult]] { + ) async throws -> [ResolvedModule.ID: [BuildToolPluginInvocationResult]] { // Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require). try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency") @@ -1179,7 +1202,10 @@ class PluginInvocationTests: XCTestCase { XCTAssert(rootManifests.count == 1, "\(rootManifests)") // Load the package graph. - let packageGraph = try workspace.loadPackageGraph(rootInput: rootInput, observabilityScope: observability.topScope) + let packageGraph = try workspace.loadPackageGraph( + rootInput: rootInput, + observabilityScope: observability.topScope + ) XCTAssertNoDiagnostics(observability.diagnostics) // Find the build tool plugin. diff --git a/Tests/SourceControlTests/GitRepositoryTests.swift b/Tests/SourceControlTests/GitRepositoryTests.swift index 00dcc4870eb..d69d5b711d6 100644 --- a/Tests/SourceControlTests/GitRepositoryTests.swift +++ b/Tests/SourceControlTests/GitRepositoryTests.swift @@ -291,6 +291,7 @@ class GitRepositoryTests: XCTestCase { // Check read of a file. XCTAssertEqual(try view.readFileContents("/test-file-1.txt"), test1FileContents) XCTAssertEqual(try view.readFileContents("/subdir/test-file-2.txt"), test2FileContents) + XCTAssertEqual(try view.readFileContents("/test-file-3.sh"), test3FileContents) } } diff --git a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift index 18cbcd13d14..92b275bab08 100644 --- a/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift +++ b/Tests/SourceKitLSPAPITests/SourceKitLSPAPITests.swift @@ -12,7 +12,10 @@ import Basics import Build + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph + import PackageModel import SourceKitLSPAPI import SPMTestSupport @@ -27,7 +30,7 @@ class SourceKitLSPAPITests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -42,24 +45,50 @@ class SourceKitLSPAPITests: XCTestCase { ) XCTAssertNoDiagnostics(observability.diagnostics) + let buildParameters = mockBuildParameters(shouldLinkStaticSwiftStdlib: true) let plan = try BuildPlan( - productsBuildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), - toolsBuildParameters: mockBuildParameters(shouldLinkStaticSwiftStdlib: true), + destinationBuildParameters: buildParameters, + toolsBuildParameters: buildParameters, graph: graph, fileSystem: fs, observabilityScope: observability.topScope ) let description = BuildDescription(buildPlan: plan) - try description.checkArguments(for: "exe", graph: graph, partialArguments: ["/fake/path/to/swiftc", "-module-name", "exe", "-emit-dependencies", "-emit-module", "-emit-module-path", "/path/to/build/debug/exe.build/exe.swiftmodule"]) - try description.checkArguments(for: "lib", graph: graph, partialArguments: ["/fake/path/to/swiftc", "-module-name", "lib", "-emit-dependencies", "-emit-module", "-emit-module-path", "/path/to/build/debug/Modules/lib.swiftmodule"]) + try description.checkArguments( + for: "exe", + graph: graph, + partialArguments: [ + "-module-name", "exe", + "-emit-dependencies", + "-emit-module", + "-emit-module-path", "/path/to/build/\(buildParameters.triple)/debug/exe.build/exe.swiftmodule" + ], + isPartOfRootPackage: true + ) + try description.checkArguments( + for: "lib", + graph: graph, + partialArguments: [ + "-module-name", "lib", + "-emit-dependencies", + "-emit-module", + "-emit-module-path", "/path/to/build/\(buildParameters.triple)/debug/Modules/lib.swiftmodule" + ], + isPartOfRootPackage: true + ) } } extension SourceKitLSPAPI.BuildDescription { - @discardableResult func checkArguments(for targetName: String, graph: PackageGraph, partialArguments: [String]) throws -> Bool { + @discardableResult func checkArguments( + for targetName: String, + graph: ModulesGraph, + partialArguments: [String], + isPartOfRootPackage: Bool + ) throws -> Bool { let target = try XCTUnwrap(graph.allTargets.first(where: { $0.name == targetName })) - let buildTarget = try XCTUnwrap(self.getBuildTarget(for: target)) + let buildTarget = try XCTUnwrap(self.getBuildTarget(for: target, in: graph)) guard let file = buildTarget.sources.first else { XCTFail("build target \(targetName) contains no files") @@ -70,6 +99,7 @@ extension SourceKitLSPAPI.BuildDescription { let result = arguments.contains(partialArguments) XCTAssertTrue(result, "could not match \(partialArguments) to actual arguments \(arguments)") + XCTAssertEqual(buildTarget.isPartOfRootPackage, isPartOfRootPackage) return result } } diff --git a/Tests/WorkspaceTests/InitTests.swift b/Tests/WorkspaceTests/InitTests.swift index 687f7f1ed01..dc3fc388b6a 100644 --- a/Tests/WorkspaceTests/InitTests.swift +++ b/Tests/WorkspaceTests/InitTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -16,7 +16,7 @@ import PackageModel import Workspace import XCTest -class InitTests: XCTestCase { +final class InitTests: XCTestCase { // MARK: TSCBasic package creation for each package type. @@ -53,8 +53,10 @@ class InitTests: XCTestCase { XCTAssertMatch(manifestContents, .contains(packageWithNameOnly(named: name))) } } - - func testInitPackageExecutable() throws { + + func testInitPackageExecutable() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("Foo") @@ -98,7 +100,9 @@ class InitTests: XCTestCase { } } - func testInitPackageLibraryWithXCTestOnly() throws { + func testInitPackageLibraryWithXCTestOnly() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("Foo") @@ -148,7 +152,7 @@ class InitTests: XCTestCase { XCTAssertFileExists(path.appending(components: ".build", triple.platformBuildPathComponent, "debug", "Modules", "Foo.swiftmodule")) } } - + func testInitPackageLibraryWithSwiftTestingOnly() throws { try testWithTemporaryDirectory { tmpPath in let fs = localFileSystem @@ -170,6 +174,11 @@ class InitTests: XCTestCase { let manifest = path.appending("Package.swift") XCTAssertFileExists(manifest) let manifestContents: String = try localFileSystem.readFileContents(manifest) + XCTAssertMatch(manifestContents, .contains(#".macOS(.v10_15)"#)) + XCTAssertMatch(manifestContents, .contains(#".iOS(.v13)"#)) + XCTAssertMatch(manifestContents, .contains(#".tvOS(.v13)"#)) + XCTAssertMatch(manifestContents, .contains(#".watchOS(.v6)"#)) + XCTAssertMatch(manifestContents, .contains(#".macCatalyst(.v13)"#)) XCTAssertMatch(manifestContents, .contains(#"swift-testing.git", from: "0.2.0""#)) XCTAssertMatch(manifestContents, .contains(#".product(name: "Testing", package: "swift-testing")"#)) @@ -208,6 +217,11 @@ class InitTests: XCTestCase { let manifest = path.appending("Package.swift") XCTAssertFileExists(manifest) let manifestContents: String = try localFileSystem.readFileContents(manifest) + XCTAssertMatch(manifestContents, .contains(#".macOS(.v10_15)"#)) + XCTAssertMatch(manifestContents, .contains(#".iOS(.v13)"#)) + XCTAssertMatch(manifestContents, .contains(#".tvOS(.v13)"#)) + XCTAssertMatch(manifestContents, .contains(#".watchOS(.v6)"#)) + XCTAssertMatch(manifestContents, .contains(#".macCatalyst(.v13)"#)) XCTAssertMatch(manifestContents, .contains(#"swift-testing.git", from: "0.2.0""#)) XCTAssertMatch(manifestContents, .contains(#".product(name: "Testing", package: "swift-testing")"#)) @@ -225,7 +239,9 @@ class InitTests: XCTestCase { } } - func testInitPackageLibraryWithNoTests() throws { + func testInitPackageLibraryWithNoTests() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try testWithTemporaryDirectory { tmpPath in let fs = localFileSystem let path = tmpPath.appending("Foo") @@ -329,8 +345,9 @@ class InitTests: XCTestCase { } // MARK: Special case testing - - func testInitPackageNonc99Directory() throws { + + func testInitPackageNonc99Directory() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in XCTAssertDirectoryExists(tempDirPath) @@ -357,7 +374,9 @@ class InitTests: XCTestCase { } } - func testNonC99NameExecutablePackage() throws { + func testNonC99NameExecutablePackage() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDirPath in XCTAssertDirectoryExists(tempDirPath) diff --git a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift index 88652873d23..1833c2bd8c2 100644 --- a/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift +++ b/Tests/WorkspaceTests/ManifestSourceGenerationTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors +// Copyright (c) 2020-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -33,8 +33,7 @@ extension String { } } -class ManifestSourceGenerationTests: XCTestCase { - +final class ManifestSourceGenerationTests: XCTestCase { /// Private function that writes the contents of a package manifest to a temporary package directory and then loads it, then serializes the loaded manifest back out again and loads it once again, after which it compares that no information was lost. Return the source of the newly generated manifest. @discardableResult private func testManifestWritingRoundTrip( @@ -589,4 +588,39 @@ class ManifestSourceGenerationTests: XCTestCase { let contents = try manifest.generateManifestFileContents(packageDirectory: manifest.path.parentDirectory) try await testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v5_9) } + + func testManifestGenerationWithSwiftLanguageVersion() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + let manifest = Manifest.createRootManifest( + displayName: "pkg", + path: "/pkg", + toolsVersion: .v6_0, + dependencies: [], + targets: [ + try TargetDescription( + name: "v5", + type: .executable, + settings: [ + .init(tool: .swift, kind: .swiftLanguageVersion(.v6)) + ] + ), + try TargetDescription( + name: "custom", + type: .executable, + settings: [ + .init(tool: .swift, kind: .swiftLanguageVersion(.init(string: "5.10")!)) + ] + ), + try TargetDescription( + name: "conditional", + type: .executable, + settings: [ + .init(tool: .swift, kind: .swiftLanguageVersion(.v5), condition: .init(platformNames: ["linux"])), + .init(tool: .swift, kind: .swiftLanguageVersion(.v4), condition: .init(platformNames: ["macos"], config: "debug")) + ] + ) + ]) + let contents = try manifest.generateManifestFileContents(packageDirectory: manifest.path.parentDirectory) + try await testManifestWritingRoundTrip(manifestContents: contents, toolsVersion: .v6_0) + } } diff --git a/Tests/WorkspaceTests/WorkspaceTests.swift b/Tests/WorkspaceTests/WorkspaceTests.swift index 4869739b0c0..ef7df48a69b 100644 --- a/Tests/WorkspaceTests/WorkspaceTests.swift +++ b/Tests/WorkspaceTests/WorkspaceTests.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift open source project // -// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors +// Copyright (c) 2014-2024 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See http://swift.org/LICENSE.txt for license information @@ -155,7 +155,7 @@ final class WorkspaceTests: XCTestCase { } } - func testInterpreterFlags() throws { + func testInterpreterFlags() async throws { let fs = localFileSystem try testWithTemporaryDirectory { path in @@ -214,7 +214,7 @@ final class WorkspaceTests: XCTestCase { """ ) - XCTAssertMatch(ws.interpreterFlags(for: foo), [.equal("-swift-version"), .equal("5")]) + XCTAssertMatch(ws.interpreterFlags(for: foo), [.equal("-swift-version"), .equal("6")]) } } } @@ -1971,7 +1971,7 @@ final class WorkspaceTests: XCTestCase { // Ensure that the order of the manifests is stable. XCTAssertEqual( manifests.allDependencyManifests.map(\.value.manifest.displayName), - ["Foo", "Baz", "Bam", "Bar"] + ["Bam", "Baz", "Bar", "Foo"] ) XCTAssertNoDiagnostics(diagnostics) } @@ -5252,6 +5252,8 @@ final class WorkspaceTests: XCTestCase { // This verifies that the simplest possible loading APIs are available for package clients. func testSimpleAPI() async throws { + try await UserToolchain.default.skipUnlessAtLeastSwift6() + try await testWithTemporaryDirectory { path in // Create a temporary package as a test case. let packagePath = path.appending("MyPkg") @@ -5590,6 +5592,48 @@ final class WorkspaceTests: XCTestCase { } } + func testUnsafeFlagsInFoundation() throws { + let sandbox = AbsolutePath("/tmp/ws/") + let fs = InMemoryFileSystem() + + let workspace = try MockWorkspace( + sandbox: sandbox, + fileSystem: fs, + roots: [ + MockPackage( + name: "Test", + targets: [ + MockTarget(name: "Test", + dependencies: [ + .product(name: "Foundation", + package: "swift-corelibs-foundation") + ]), + ], + products: [], + dependencies: [ + .sourceControl(path: "swift-corelibs-foundation", requirement: .upToNextMajor(from: "1.0.0")), + ] + ), + ], + packages: [ + MockPackage( + name: "swift-corelibs-foundation", + targets: [ + MockTarget(name: "Foundation", settings: [.init(tool: .swift, kind: .unsafeFlags(["-F", "/tmp"]))]), + ], + products: [ + MockProduct(name: "Foundation", targets: ["Foundation"]) + ], + versions: ["1.0.0", nil] + ) + ] + ) + + try workspace.checkPackageGraph(roots: ["Test"]) { _, diagnostics in + XCTAssertNoDiagnostics(diagnostics) + } + } + func testEditDependencyHadOverridableConstraints() throws { let sandbox = AbsolutePath("/tmp/ws/") let fs = InMemoryFileSystem() @@ -11081,7 +11125,7 @@ final class WorkspaceTests: XCTestCase { // FIXME: rdar://72940946 // we need to improve this situation or diagnostics when working on identity result.check( - diagnostic: "cyclic dependency declaration found: Root -> FooUtilityPackage -> BarPackage -> FooUtilityPackage", + diagnostic: "'bar' dependency on '/tmp/ws/pkgs/other/utility' conflicts with dependency on '/tmp/ws/pkgs/foo/utility' which has the same identity 'utility'", severity: .error ) } @@ -11165,7 +11209,11 @@ final class WorkspaceTests: XCTestCase { // FIXME: rdar://72940946 // we need to improve this situation or diagnostics when working on identity result.check( - diagnostic: "cyclic dependency declaration found: Root -> FooUtilityPackage -> BarPackage -> FooUtilityPackage", + diagnostic: "'bar' dependency on '/tmp/ws/pkgs/other/utility' conflicts with dependency on '/tmp/ws/pkgs/foo/utility' which has the same identity 'utility'. this will be escalated to an error in future versions of SwiftPM.", + severity: .warning + ) + result.check( + diagnostic: "product 'OtherUtilityProduct' required by package 'bar' target 'BarTarget' not found in package 'OtherUtilityPackage'.", severity: .error ) } @@ -11240,7 +11288,7 @@ final class WorkspaceTests: XCTestCase { // FIXME: rdar://72940946 // we need to improve this situation or diagnostics when working on identity result.check( - diagnostic: "cyclic dependency declaration found: Root -> BarPackage -> Root", + diagnostic: "product 'FooProduct' required by package 'bar' target 'BarTarget' not found in package 'FooPackage'.", severity: .error ) } @@ -11973,7 +12021,7 @@ final class WorkspaceTests: XCTestCase { targets: [ .init(name: "Root1Target", dependencies: [ .product(name: "FooProduct", package: "foo"), - .product(name: "Root2Target", package: "Root2") + .product(name: "Root2Product", package: "Root2") ]), ], products: [ @@ -12069,15 +12117,7 @@ final class WorkspaceTests: XCTestCase { try workspace.checkPackageGraph(roots: ["Root1", "Root2"]) { _, diagnostics in testDiagnostics(diagnostics) { result in result.check( - diagnostic: .regex("cyclic dependency declaration found: root[1|2] -> *"), - severity: .error - ) - result.check( - diagnostic: """ - exhausted attempts to resolve the dependencies graph, with the following dependencies unresolved: - * 'bar' from http://scm.com/org/bar - * 'foo' from http://scm.com/org/foo - """, + diagnostic: .regex("cyclic dependency declaration found: Root[1|2]Target -> *"), severity: .error ) } diff --git a/Tests/XCBuildSupportTests/PIFBuilderTests.swift b/Tests/XCBuildSupportTests/PIFBuilderTests.swift index dcabed69073..78883d9e7c5 100644 --- a/Tests/XCBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/XCBuildSupportTests/PIFBuilderTests.swift @@ -12,9 +12,12 @@ import Basics import Foundation + +@_spi(DontAdoptOutsideOfSwiftPMExposedForBenchmarksAndTestsOnly) import PackageGraph -@testable import PackageModel + import PackageLoading +@testable import PackageModel import SPMBuildCore import SPMTestSupport @testable import XCBuildSupport @@ -32,17 +35,18 @@ class PIFBuilderTests: XCTestCase { try XCTSkipIf(true, "test is only supported on macOS") #endif // Repeat multiple times to detect non-deterministic shuffling due to sets. - for _ in 0..<10 { - let fs = InMemoryFileSystem(emptyFiles: - "/A/Sources/A1/main.swift", - "/A/Sources/A2/lib.swift", - "/A/Sources/A3/lib.swift", - "/B/Sources/B1/main.swift", - "/B/Sources/B2/lib.swift" + for _ in 0 ..< 10 { + let fs = InMemoryFileSystem( + emptyFiles: + "/A/Sources/A1/main.swift", + "/A/Sources/A2/lib.swift", + "/A/Sources/A3/lib.swift", + "/B/Sources/B1/main.swift", + "/B/Sources/B2/lib.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createLocalSourceControlManifest( @@ -56,7 +60,8 @@ class PIFBuilderTests: XCTestCase { targets: [ .init(name: "B2", dependencies: []), .init(name: "B1", dependencies: ["B2"]), - ]), + ] + ), Manifest.createRootManifest( displayName: "A", path: "/A", @@ -72,7 +77,8 @@ class PIFBuilderTests: XCTestCase { .init(name: "A1", dependencies: ["A3", "A2", .product(name: "blib", package: "B")]), .init(name: "A2", dependencies: []), .init(name: "A3", dependencies: []), - ]), + ] + ), ], observabilityScope: observability.topScope ) @@ -87,18 +93,27 @@ class PIFBuilderTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) - let projectNames = pif.workspace.projects.map({ $0.name }) + let projectNames = pif.workspace.projects.map(\.name) XCTAssertEqual(projectNames, ["A", "B", "Aggregate"]) - let projectATargetNames = pif.workspace.projects[0].targets.map({ $0.name }) - XCTAssertEqual(projectATargetNames, ["aexe_79CC9E117_PackageProduct", "alib_79D40CF5C_PackageProduct", "A2", "A3"]) + let projectATargetNames = pif.workspace.projects[0].targets.map(\.name) + XCTAssertEqual( + projectATargetNames, + ["aexe_79CC9E117_PackageProduct", "alib_79D40CF5C_PackageProduct", "A2", "A3"] + ) let targetAExeDependencies = pif.workspace.projects[0].targets[0].dependencies - XCTAssertEqual(targetAExeDependencies.map{ $0.targetGUID }, ["PACKAGE-PRODUCT:blib", "PACKAGE-TARGET:A2", "PACKAGE-TARGET:A3"]) - let projectBTargetNames = pif.workspace.projects[1].targets.map({ $0.name }) -#if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION + XCTAssertEqual( + targetAExeDependencies.map(\.targetGUID), + ["PACKAGE-PRODUCT:blib", "PACKAGE-TARGET:A2", "PACKAGE-TARGET:A3"] + ) + let projectBTargetNames = pif.workspace.projects[1].targets.map(\.name) + #if ENABLE_TARGET_BASED_DEPENDENCY_RESOLUTION XCTAssertEqual(projectBTargetNames, ["blib_7AE74026D_PackageProduct", "B2"]) -#else - XCTAssertEqual(projectBTargetNames, ["bexe_7ADFD1428_PackageProduct", "blib_7AE74026D_PackageProduct", "B2"]) -#endif + #else + XCTAssertEqual( + projectBTargetNames, + ["bexe_7ADFD1428_PackageProduct", "blib_7AE74026D_PackageProduct", "B2"] + ) + #endif } } @@ -106,14 +121,15 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/foo/main.swift", - "/Foo/Tests/FooTests/tests.swift", - "/Bar/Sources/BarLib/lib.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Tests/FooTests/tests.swift", + "/Bar/Sources/BarLib/lib.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createManifest( @@ -128,7 +144,8 @@ class PIFBuilderTests: XCTestCase { targets: [ .init(name: "foo", dependencies: [.product(name: "BarLib", package: "Bar")]), .init(name: "FooTests", type: .test), - ]), + ] + ), Manifest.createLocalSourceControlManifest( displayName: "Bar", path: "/Bar", @@ -145,7 +162,8 @@ class PIFBuilderTests: XCTestCase { targets: [ .init(name: "BarLib"), .init(name: "BarTests", type: .test), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope @@ -188,7 +206,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.ENABLE_TESTING_SEARCH_PATHS], "YES") XCTAssertEqual(settings[.ENTITLEMENTS_REQUIRED], "NO") XCTAssertEqual(settings[.GCC_OPTIMIZATION_LEVEL], "0") - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "SWIFT_PACKAGE", "DEBUG=1"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "SWIFT_PACKAGE", "DEBUG=1"] + ) XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET, for: .macCatalyst], "13.0") XCTAssertEqual(settings[.KEEP_PRIVATE_EXTERNS], "NO") @@ -200,7 +221,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "auto") XCTAssertEqual(settings[.SKIP_INSTALL], "YES") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["$(AVAILABLE_PLATFORMS)"]) - XCTAssertEqual(settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], ["$(inherited)", "SWIFT_PACKAGE", "DEBUG"]) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_PACKAGE", "DEBUG"] + ) XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Onone") @@ -246,7 +270,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "auto") XCTAssertEqual(settings[.SKIP_INSTALL], "YES") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["$(AVAILABLE_PLATFORMS)"]) - XCTAssertEqual(settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], ["$(inherited)", "SWIFT_PACKAGE"]) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_PACKAGE"] + ) XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Owholemodule") @@ -292,7 +319,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.ENABLE_TESTING_SEARCH_PATHS], "YES") XCTAssertEqual(settings[.ENTITLEMENTS_REQUIRED], "NO") XCTAssertEqual(settings[.GCC_OPTIMIZATION_LEVEL], "0") - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "SWIFT_PACKAGE", "DEBUG=1"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "SWIFT_PACKAGE", "DEBUG=1"] + ) XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], "12.0") XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET, for: .macCatalyst], "13.0") XCTAssertEqual(settings[.KEEP_PRIVATE_EXTERNS], "NO") @@ -304,7 +334,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "auto") XCTAssertEqual(settings[.SKIP_INSTALL], "YES") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["$(AVAILABLE_PLATFORMS)"]) - XCTAssertEqual(settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], ["$(inherited)", "SWIFT_PACKAGE", "DEBUG"]) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_PACKAGE", "DEBUG"] + ) XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Onone") @@ -350,7 +383,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "auto") XCTAssertEqual(settings[.SKIP_INSTALL], "YES") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["$(AVAILABLE_PLATFORMS)"]) - XCTAssertEqual(settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], ["$(inherited)", "SWIFT_PACKAGE"]) + XCTAssertEqual( + settings[.SWIFT_ACTIVE_COMPILATION_CONDITIONS], + ["$(inherited)", "SWIFT_PACKAGE"] + ) XCTAssertEqual(settings[.SWIFT_INSTALL_OBJC_HEADER], "NO") XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "") XCTAssertEqual(settings[.SWIFT_OPTIMIZATION_LEVEL], "-Owholemodule") @@ -389,18 +425,19 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/foo/main.swift", - "/Foo/Sources/cfoo/main.c", - "/Foo/Sources/FooLib/lib.swift", - "/Foo/Sources/SystemLib/module.modulemap", - "/Bar/Sources/bar/main.swift", - "/Bar/Sources/cbar/main.c", - "/Bar/Sources/BarLib/lib.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/cfoo/main.c", + "/Foo/Sources/FooLib/lib.swift", + "/Foo/Sources/SystemLib/module.modulemap", + "/Bar/Sources/bar/main.swift", + "/Bar/Sources/cbar/main.c", + "/Bar/Sources/BarLib/lib.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -417,14 +454,15 @@ class PIFBuilderTests: XCTestCase { "SystemLib", "cfoo", .product(name: "bar", package: "Bar"), - .product(name: "cbar", package: "Bar") + .product(name: "cbar", package: "Bar"), ]), .init(name: "cfoo"), .init(name: "SystemLib", type: .system, pkgConfig: "Foo"), .init(name: "FooLib", dependencies: [ .product(name: "BarLib", package: "Bar"), - ]) - ]), + ]), + ] + ), Manifest.createLocalSourceControlManifest( displayName: "Bar", path: "/Bar", @@ -441,13 +479,14 @@ class PIFBuilderTests: XCTestCase { .init(name: "bar", dependencies: ["BarLib"]), .init(name: "cbar"), .init(name: "BarLib"), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(), @@ -474,7 +513,7 @@ class PIFBuilderTests: XCTestCase { "PACKAGE-PRODUCT:BarLib", "PACKAGE-PRODUCT:cbar", "PACKAGE-TARGET:FooLib", - "PACKAGE-TARGET:SystemLib" + "PACKAGE-TARGET:SystemLib", ]) XCTAssertEqual(target.sources, ["/Foo/Sources/foo/main.swift"]) XCTAssertEqual(target.frameworks, [ @@ -492,7 +531,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "foo") XCTAssertEqual(settings[.INSTALL_PATH], "/usr/local/bin") - XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], ["$(inherited)", "@executable_path/../lib"]) + XCTAssertEqual( + settings[.LD_RUNPATH_SEARCH_PATHS], + ["$(inherited)", "@executable_path/../lib"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "foo") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "foo") @@ -500,11 +542,12 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "foo") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -516,7 +559,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "foo") XCTAssertEqual(settings[.INSTALL_PATH], "/usr/local/bin") - XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], ["$(inherited)", "@executable_path/../lib"]) + XCTAssertEqual( + settings[.LD_RUNPATH_SEARCH_PATHS], + ["$(inherited)", "@executable_path/../lib"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "foo") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "foo") @@ -524,11 +570,12 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "foo") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -552,9 +599,15 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.CLANG_ENABLE_MODULES], "YES") XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "cfoo") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Foo/Sources/cfoo/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Foo/Sources/cfoo/include"] + ) XCTAssertEqual(settings[.INSTALL_PATH], "/usr/local/bin") - XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], ["$(inherited)", "@executable_path/../lib"]) + XCTAssertEqual( + settings[.LD_RUNPATH_SEARCH_PATHS], + ["$(inherited)", "@executable_path/../lib"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "cfoo") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "cfoo") @@ -562,10 +615,11 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cfoo") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -576,9 +630,15 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.CLANG_ENABLE_MODULES], "YES") XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "cfoo") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Foo/Sources/cfoo/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Foo/Sources/cfoo/include"] + ) XCTAssertEqual(settings[.INSTALL_PATH], "/usr/local/bin") - XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], ["$(inherited)", "@executable_path/../lib"]) + XCTAssertEqual( + settings[.LD_RUNPATH_SEARCH_PATHS], + ["$(inherited)", "@executable_path/../lib"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "cfoo") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "cfoo") @@ -586,10 +646,11 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cfoo") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -622,11 +683,12 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_NAME], "bar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "4.2") XCTAssertEqual(settings[.TARGET_NAME], "bar") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -643,11 +705,12 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.PRODUCT_NAME], "bar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "4.2") XCTAssertEqual(settings[.TARGET_NAME], "bar") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -673,17 +736,21 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "cbar") XCTAssertEqual(settings[.GCC_C_LANGUAGE_STANDARD], "c11") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Bar/Sources/cbar/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Bar/Sources/cbar/include"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "cbar") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "cbar") XCTAssertEqual(settings[.PRODUCT_NAME], "cbar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cbar") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -696,17 +763,21 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "cbar") XCTAssertEqual(settings[.GCC_C_LANGUAGE_STANDARD], "c11") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Bar/Sources/cbar/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Bar/Sources/cbar/include"] + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "cbar") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "cbar") XCTAssertEqual(settings[.PRODUCT_NAME], "cbar") XCTAssertEqual(settings[.SDKROOT], "macosx") XCTAssertEqual(settings[.SUPPORTED_PLATFORMS], ["macosx", "linux"]) - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "cbar") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -718,23 +789,24 @@ class PIFBuilderTests: XCTestCase { func testTestProducts() throws { #if !os(macOS) - try XCTSkipIf(true, "test is only supported on macOS") + try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/FooTests/FooTests.swift", - "/Foo/Sources/CFooTests/CFooTests.m", - "/Foo/Sources/foo/main.swift", - "/Foo/Sources/FooLib/lib.swift", - "/Foo/Sources/SystemLib/module.modulemap", - "/Bar/Sources/bar/main.swift", - "/Bar/Sources/BarTests/BarTests.swift", - "/Bar/Sources/CBarTests/CBarTests.m", - "/Bar/Sources/BarLib/lib.swift", - inputsDir.appending("Foo.pc").pathString + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/FooTests/FooTests.swift", + "/Foo/Sources/CFooTests/CFooTests.m", + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/FooLib/lib.swift", + "/Foo/Sources/SystemLib/module.modulemap", + "/Bar/Sources/bar/main.swift", + "/Bar/Sources/BarTests/BarTests.swift", + "/Bar/Sources/CBarTests/CBarTests.m", + "/Bar/Sources/BarLib/lib.swift", + inputsDir.appending("Foo.pc").pathString ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -757,8 +829,9 @@ class PIFBuilderTests: XCTestCase { .init(name: "SystemLib", type: .system, pkgConfig: "Foo"), .init(name: "FooLib", dependencies: [ .product(name: "BarLib", package: "Bar"), - ]) - ]), + ]), + ] + ), Manifest.createLocalSourceControlManifest( displayName: "Bar", path: "/Bar", @@ -775,14 +848,15 @@ class PIFBuilderTests: XCTestCase { .init(name: "BarTests", dependencies: ["BarLib"], type: .test), .init(name: "CBarTests", type: .test), .init(name: "BarLib"), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(), @@ -805,7 +879,7 @@ class PIFBuilderTests: XCTestCase { "PACKAGE-PRODUCT:bar", "PACKAGE-PRODUCT:BarLib", "PACKAGE-TARGET:FooLib", - "PACKAGE-TARGET:SystemLib" + "PACKAGE-TARGET:SystemLib", ]) XCTAssertEqual(target.sources, ["/Foo/Sources/FooTests/FooTests.swift"]) XCTAssertEqual(target.frameworks, [ @@ -825,25 +899,40 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], [ "$(inherited)", "@loader_path/Frameworks", - "@loader_path/../Frameworks" + "@loader_path/../Frameworks", ]) XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], [ "$(inherited)", - "/toolchain/lib/swift/macosx" + "/toolchain/lib/swift/macosx", ]) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "FooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooTests") - XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS).versionString) - XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString) - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString) - XCTAssertEqual(settings[.MACOSX_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString) - XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS).versionString) + XCTAssertEqual( + settings[.WATCHOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS) + .versionString + ) + XCTAssertEqual( + settings[.IPHONEOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString + ) + XCTAssertEqual( + settings[.TVOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString + ) + XCTAssertEqual( + settings[.MACOSX_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString + ) + XCTAssertEqual( + settings[.XROS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS) + .versionString + ) } } @@ -858,25 +947,40 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], [ "$(inherited)", "@loader_path/Frameworks", - "@loader_path/../Frameworks" + "@loader_path/../Frameworks", ]) XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], [ "$(inherited)", - "/toolchain/lib/swift/macosx" + "/toolchain/lib/swift/macosx", ]) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "FooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooTests") - XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS).versionString) - XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString) - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString) - XCTAssertEqual(settings[.MACOSX_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString) - XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS).versionString) + XCTAssertEqual( + settings[.WATCHOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS) + .versionString + ) + XCTAssertEqual( + settings[.IPHONEOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString + ) + XCTAssertEqual( + settings[.TVOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString + ) + XCTAssertEqual( + settings[.MACOSX_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString + ) + XCTAssertEqual( + settings[.XROS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS) + .versionString + ) } } @@ -901,29 +1005,44 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.GENERATE_INFOPLIST_FILE], "YES") XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], [ "$(inherited)", - "/Foo/Sources/CFooTests/include" + "/Foo/Sources/CFooTests/include", ]) XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], [ "$(inherited)", "@loader_path/Frameworks", - "@loader_path/../Frameworks" + "@loader_path/../Frameworks", ]) XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], [ "$(inherited)", - "/toolchain/lib/swift/macosx" + "/toolchain/lib/swift/macosx", ]) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "CFooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "CFooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "CFooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "CFooTests") - XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS).versionString) - XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString) - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString) - XCTAssertEqual(settings[.MACOSX_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString) - XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS).versionString) + XCTAssertEqual( + settings[.WATCHOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS) + .versionString + ) + XCTAssertEqual( + settings[.IPHONEOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString + ) + XCTAssertEqual( + settings[.TVOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString + ) + XCTAssertEqual( + settings[.MACOSX_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString + ) + XCTAssertEqual( + settings[.XROS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS) + .versionString + ) } } @@ -937,29 +1056,44 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.GENERATE_INFOPLIST_FILE], "YES") XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], [ "$(inherited)", - "/Foo/Sources/CFooTests/include" + "/Foo/Sources/CFooTests/include", ]) XCTAssertEqual(settings[.LD_RUNPATH_SEARCH_PATHS], [ "$(inherited)", "@loader_path/Frameworks", - "@loader_path/../Frameworks" + "@loader_path/../Frameworks", ]) XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], [ "$(inherited)", - "/toolchain/lib/swift/macosx" + "/toolchain/lib/swift/macosx", ]) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "CFooTests") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "CFooTests") XCTAssertEqual(settings[.PRODUCT_NAME], "CFooTests") - XCTAssertEqual(settings[.SWIFT_FORCE_DYNAMIC_LINK_STDLIB], "YES") - XCTAssertEqual(settings[.SWIFT_FORCE_STATIC_LINK_STDLIB], "NO") XCTAssertEqual(settings[.TARGET_NAME], "CFooTests") - XCTAssertEqual(settings[.WATCHOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS).versionString) - XCTAssertEqual(settings[.IPHONEOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString) - XCTAssertEqual(settings[.TVOS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString) - XCTAssertEqual(settings[.MACOSX_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString) - XCTAssertEqual(settings[.XROS_DEPLOYMENT_TARGET], MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS).versionString) + XCTAssertEqual( + settings[.WATCHOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .watchOS) + .versionString + ) + XCTAssertEqual( + settings[.IPHONEOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .iOS).versionString + ) + XCTAssertEqual( + settings[.TVOS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .tvOS).versionString + ) + XCTAssertEqual( + settings[.MACOSX_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .macOS).versionString + ) + XCTAssertEqual( + settings[.XROS_DEPLOYMENT_TARGET], + MinimumDeploymentTarget.computeXCTestMinimumDeploymentTarget(for: .visionOS) + .versionString + ) } } @@ -978,15 +1112,16 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/FooLib1/lib.swift", - "/Foo/Sources/FooLib2/lib.swift", - "/Foo/Sources/SystemLib/module.modulemap", - "/Bar/Sources/BarLib/lib.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/FooLib1/lib.swift", + "/Foo/Sources/FooLib2/lib.swift", + "/Foo/Sources/SystemLib/module.modulemap", + "/Bar/Sources/BarLib/lib.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1007,7 +1142,8 @@ class PIFBuilderTests: XCTestCase { .product(name: "BarLib", package: "Bar"), ]), .init(name: "SystemLib", type: .system, pkgConfig: "Foo"), - ]), + ] + ), Manifest.createLocalSourceControlManifest( displayName: "Bar", path: "/Bar", @@ -1020,13 +1156,14 @@ class PIFBuilderTests: XCTestCase { ], targets: [ .init(name: "BarLib"), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(), @@ -1072,7 +1209,7 @@ class PIFBuilderTests: XCTestCase { } } - target.checkAllImpartedBuildSettings { settings in + target.checkAllImpartedBuildSettings { _ in // No imparted build settings. } } @@ -1142,7 +1279,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.SKIP_INSTALL], "NO") XCTAssertEqual(settings[.TARGET_BUILD_DIR], "$(TARGET_BUILD_DIR)/PackageFrameworks") XCTAssertEqual(settings[.TARGET_NAME], "BarLib") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -1166,7 +1306,10 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.TARGET_BUILD_DIR], "$(TARGET_BUILD_DIR)/PackageFrameworks") XCTAssertEqual(settings[.TARGET_NAME], "BarLib") XCTAssertEqual(settings[.USES_SWIFTPM_UNSAFE_FLAGS], "NO") - XCTAssertEqual(settings[.LIBRARY_SEARCH_PATHS], ["$(inherited)", "/toolchain/lib/swift/macosx"]) + XCTAssertEqual( + settings[.LIBRARY_SEARCH_PATHS], + ["$(inherited)", "/toolchain/lib/swift/macosx"] + ) } } @@ -1180,15 +1323,16 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/FooLib1/lib.swift", - "/Foo/Sources/FooLib2/lib.cpp", - "/Foo/Sources/SystemLib/module.modulemap", - "/Bar/Sources/BarLib/lib.c" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/FooLib1/lib.swift", + "/Foo/Sources/FooLib2/lib.cpp", + "/Foo/Sources/SystemLib/module.modulemap", + "/Bar/Sources/BarLib/lib.c" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1206,7 +1350,8 @@ class PIFBuilderTests: XCTestCase { .product(name: "BarLib", package: "Bar"), ]), .init(name: "SystemLib", type: .system, pkgConfig: "Foo"), - ]), + ] + ), Manifest.createLocalSourceControlManifest( displayName: "Bar", path: "/Bar", @@ -1218,13 +1363,14 @@ class PIFBuilderTests: XCTestCase { ], targets: [ .init(name: "BarLib"), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(), @@ -1261,17 +1407,23 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.GENERATE_MASTER_OBJECT_FILE], "NO") XCTAssertEqual(settings[.MACH_O_TYPE], "mh_object") XCTAssertEqual(settings[.MODULEMAP_FILE_CONTENTS], """ - module FooLib1 { - header "FooLib1-Swift.h" - export * - } - """) - XCTAssertEqual(settings[.MODULEMAP_PATH], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib1.modulemap") + module FooLib1 { + header "FooLib1-Swift.h" + export * + } + """) + XCTAssertEqual( + settings[.MODULEMAP_PATH], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib1.modulemap" + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooLib1") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooLib1") XCTAssertEqual(settings[.PRODUCT_NAME], "FooLib1.o") - XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)") + XCTAssertEqual( + settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)" + ) XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "FooLib1-Swift.h") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooLib1") @@ -1289,17 +1441,23 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.GENERATE_MASTER_OBJECT_FILE], "NO") XCTAssertEqual(settings[.MACH_O_TYPE], "mh_object") XCTAssertEqual(settings[.MODULEMAP_FILE_CONTENTS], """ - module FooLib1 { - header "FooLib1-Swift.h" - export * - } - """) - XCTAssertEqual(settings[.MODULEMAP_PATH], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib1.modulemap") + module FooLib1 { + header "FooLib1-Swift.h" + export * + } + """) + XCTAssertEqual( + settings[.MODULEMAP_PATH], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib1.modulemap" + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooLib1") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooLib1") XCTAssertEqual(settings[.PRODUCT_NAME], "FooLib1.o") - XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)") + XCTAssertEqual( + settings[.SWIFT_OBJC_INTERFACE_HEADER_DIR], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)" + ) XCTAssertEqual(settings[.SWIFT_OBJC_INTERFACE_HEADER_NAME], "FooLib1-Swift.h") XCTAssertEqual(settings[.SWIFT_VERSION], "5") XCTAssertEqual(settings[.TARGET_NAME], "FooLib1") @@ -1309,9 +1467,10 @@ class PIFBuilderTests: XCTestCase { target.checkAllImpartedBuildSettings { settings in XCTAssertEqual(settings[.OTHER_CFLAGS], [ "$(inherited)", - "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib1.modulemap" + "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib1.modulemap", ]) XCTAssertEqual(settings[.OTHER_LDRFLAGS], []) + XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-Wl,-no_warn_duplicate_libraries"]) } } @@ -1333,15 +1492,21 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "FooLib2.o") XCTAssertEqual(settings[.GENERATE_MASTER_OBJECT_FILE], "NO") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Foo/Sources/FooLib2/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Foo/Sources/FooLib2/include"] + ) XCTAssertEqual(settings[.MACH_O_TYPE], "mh_object") XCTAssertEqual(settings[.MODULEMAP_FILE_CONTENTS], """ - module FooLib2 { - umbrella "/Foo/Sources/FooLib2/include" - export * - } - """) - XCTAssertEqual(settings[.MODULEMAP_PATH], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap") + module FooLib2 { + umbrella "/Foo/Sources/FooLib2/include" + export * + } + """) + XCTAssertEqual( + settings[.MODULEMAP_PATH], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap" + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooLib2") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooLib2") @@ -1360,15 +1525,21 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.DEFINES_MODULE], "YES") XCTAssertEqual(settings[.EXECUTABLE_NAME], "FooLib2.o") XCTAssertEqual(settings[.GENERATE_MASTER_OBJECT_FILE], "NO") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Foo/Sources/FooLib2/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Foo/Sources/FooLib2/include"] + ) XCTAssertEqual(settings[.MACH_O_TYPE], "mh_object") XCTAssertEqual(settings[.MODULEMAP_FILE_CONTENTS], """ - module FooLib2 { - umbrella "/Foo/Sources/FooLib2/include" - export * - } - """) - XCTAssertEqual(settings[.MODULEMAP_PATH], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap") + module FooLib2 { + umbrella "/Foo/Sources/FooLib2/include" + export * + } + """) + XCTAssertEqual( + settings[.MODULEMAP_PATH], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap" + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "FooLib2") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "FooLib2") @@ -1381,13 +1552,17 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Foo/Sources/FooLib2/include"]) XCTAssertEqual(settings[.OTHER_CFLAGS], [ "$(inherited)", - "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap" + "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap", ]) XCTAssertEqual(settings[.OTHER_LDRFLAGS], []) - XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-lc++"]) + XCTAssertEqual( + settings[.OTHER_LDFLAGS], + ["$(inherited)", "-lc++", "-Wl,-no_warn_duplicate_libraries"] + ) XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], [ "$(inherited)", - "-Xcc", "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap" + "-Xcc", + "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/FooLib2.modulemap", ]) } } @@ -1412,15 +1587,21 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.EXECUTABLE_NAME], "BarLib.o") XCTAssertEqual(settings[.GCC_C_LANGUAGE_STANDARD], "c11") XCTAssertEqual(settings[.GENERATE_MASTER_OBJECT_FILE], "NO") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Bar/Sources/BarLib/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Bar/Sources/BarLib/include"] + ) XCTAssertEqual(settings[.MACH_O_TYPE], "mh_object") XCTAssertEqual(settings[.MODULEMAP_FILE_CONTENTS], """ - module BarLib { - umbrella "/Bar/Sources/BarLib/include" - export * - } - """) - XCTAssertEqual(settings[.MODULEMAP_PATH], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap") + module BarLib { + umbrella "/Bar/Sources/BarLib/include" + export * + } + """) + XCTAssertEqual( + settings[.MODULEMAP_PATH], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap" + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "BarLib") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "BarLib") @@ -1439,15 +1620,21 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.EXECUTABLE_NAME], "BarLib.o") XCTAssertEqual(settings[.GCC_C_LANGUAGE_STANDARD], "c11") XCTAssertEqual(settings[.GENERATE_MASTER_OBJECT_FILE], "NO") - XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Bar/Sources/BarLib/include"]) + XCTAssertEqual( + settings[.HEADER_SEARCH_PATHS], + ["$(inherited)", "/Bar/Sources/BarLib/include"] + ) XCTAssertEqual(settings[.MACH_O_TYPE], "mh_object") XCTAssertEqual(settings[.MODULEMAP_FILE_CONTENTS], """ - module BarLib { - umbrella "/Bar/Sources/BarLib/include" - export * - } - """) - XCTAssertEqual(settings[.MODULEMAP_PATH], "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap") + module BarLib { + umbrella "/Bar/Sources/BarLib/include" + export * + } + """) + XCTAssertEqual( + settings[.MODULEMAP_PATH], + "$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap" + ) XCTAssertEqual(settings[.PACKAGE_RESOURCE_TARGET_KIND], "regular") XCTAssertEqual(settings[.PRODUCT_BUNDLE_IDENTIFIER], "BarLib") XCTAssertEqual(settings[.PRODUCT_MODULE_NAME], "BarLib") @@ -1460,12 +1647,14 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], ["$(inherited)", "/Bar/Sources/BarLib/include"]) XCTAssertEqual(settings[.OTHER_CFLAGS], [ "$(inherited)", - "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap" + "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap", ]) XCTAssertEqual(settings[.OTHER_LDRFLAGS], []) + XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-Wl,-no_warn_duplicate_libraries"]) XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], [ "$(inherited)", - "-Xcc", "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap" + "-Xcc", + "-fmodule-map-file=$(OBJROOT)/GeneratedModuleMaps/$(PLATFORM_NAME)/BarLib.modulemap", ]) } } @@ -1477,16 +1666,17 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/App/Sources/App/main.swift", - "/App/Sources/Logging/lib.swift", - "/App/Sources/Utils/lib.swift", - "/Bar/Sources/Lib/lib.swift", - "/Bar/Sources/Logging/lib.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/App/Sources/App/main.swift", + "/App/Sources/Logging/lib.swift", + "/App/Sources/Utils/lib.swift", + "/Bar/Sources/Lib/lib.swift", + "/Bar/Sources/Logging/lib.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1501,7 +1691,8 @@ class PIFBuilderTests: XCTestCase { .init(name: "Utils", dependencies: [ .product(name: "BarLib", package: "Bar", moduleAliases: ["Logging": "BarLogging"]), ]), - ]), + ] + ), Manifest.createLocalSourceControlManifest( displayName: "Bar", path: "/Bar", @@ -1511,13 +1702,14 @@ class PIFBuilderTests: XCTestCase { targets: [ .init(name: "Lib", dependencies: ["Logging"]), .init(name: "Logging", dependencies: []), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(), @@ -1586,7 +1778,6 @@ class PIFBuilderTests: XCTestCase { XCTAssertNil(settings[.SWIFT_MODULE_ALIASES]) } } - } project.checkTarget("PACKAGE-TARGET:Logging") { target in XCTAssertEqual(target.name, "Logging") @@ -1704,12 +1895,13 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Bar/Sources/BarLib/lib.c" + let fs = InMemoryFileSystem( + emptyFiles: + "/Bar/Sources/BarLib/lib.c" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1723,13 +1915,14 @@ class PIFBuilderTests: XCTestCase { ], targets: [ .init(name: "BarLib"), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(shouldCreateDylibForDynamicProducts: true), @@ -1756,13 +1949,14 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Bar/Sources/BarLib/lib.c", - "/Bar/Sources/BarLib/module.modulemap" + let fs = InMemoryFileSystem( + emptyFiles: + "/Bar/Sources/BarLib/lib.c", + "/Bar/Sources/BarLib/module.modulemap" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createManifest( @@ -1777,13 +1971,14 @@ class PIFBuilderTests: XCTestCase { ], targets: [ .init(name: "BarLib"), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(shouldCreateDylibForDynamicProducts: true), @@ -1814,13 +2009,14 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/SystemLib1/module.modulemap", - "/Foo/Sources/SystemLib2/module.modulemap" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/SystemLib1/module.modulemap", + "/Foo/Sources/SystemLib2/module.modulemap" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1832,13 +2028,14 @@ class PIFBuilderTests: XCTestCase { targets: [ .init(name: "SystemLib1", type: .system), .init(name: "SystemLib2", type: .system, pkgConfig: "Foo"), - ]), + ] + ), ], observabilityScope: observability.topScope ) var pif: PIF.TopLevelObject! - try withCustomEnv(["PKG_CONFIG_PATH": inputsDir.pathString]) { + try withCustomEnv(["PKG_CONFIG_PATH": self.inputsDir.pathString]) { let builder = PIFBuilder( graph: graph, parameters: .mock(), @@ -1873,12 +2070,12 @@ class PIFBuilderTests: XCTestCase { target.checkAllImpartedBuildSettings { settings in XCTAssertEqual(settings[.OTHER_CFLAGS], [ "$(inherited)", - "-fmodule-map-file=/Foo/Sources/SystemLib1/module.modulemap" + "-fmodule-map-file=/Foo/Sources/SystemLib1/module.modulemap", ]) XCTAssertEqual(settings[.OTHER_LDRFLAGS], []) XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], [ "$(inherited)", - "-Xcc", "-fmodule-map-file=/Foo/Sources/SystemLib1/module.modulemap" + "-Xcc", "-fmodule-map-file=/Foo/Sources/SystemLib1/module.modulemap", ]) } } @@ -1906,20 +2103,20 @@ class PIFBuilderTests: XCTestCase { "$(inherited)", "-fmodule-map-file=/Foo/Sources/SystemLib2/module.modulemap", "-I/path/to/inc", - "-I\(self.inputsDir)" + "-I\(self.inputsDir)", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], [ "$(inherited)", "-L/usr/da/lib", "-lSystemModule", - "-lok" + "-lok", ]) XCTAssertEqual(settings[.OTHER_LDRFLAGS], []) XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], [ "$(inherited)", "-Xcc", "-fmodule-map-file=/Foo/Sources/SystemLib2/module.modulemap", "-I/path/to/inc", - "-I\(self.inputsDir)" + "-I\(self.inputsDir)", ]) } } @@ -1931,7 +2128,8 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: + let fs = InMemoryFileSystem( + emptyFiles: "/Foo/Sources/foo/main.swift", "/Foo/Sources/FooLib/lib.swift", "/Foo/Sources/FooTests/FooTests.swift", @@ -1939,7 +2137,7 @@ class PIFBuilderTests: XCTestCase { ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -1953,13 +2151,14 @@ class PIFBuilderTests: XCTestCase { .init(name: "foo", dependencies: ["BinaryLibrary"]), .init(name: "FooLib", dependencies: ["BinaryLibrary"]), .init(name: "FooTests", dependencies: ["BinaryLibrary"], type: .test), - .init(name: "BinaryLibrary", path: "BinaryLibrary.xcframework", type: .binary) - ]), + .init(name: "BinaryLibrary", path: "BinaryLibrary.xcframework", type: .binary), + ] + ), ], binaryArtifacts: [ .plain("foo"): [ - "BinaryLibrary": .init(kind: .xcframework, originURL: nil, path: "/Foo/BinaryLibrary.xcframework") - ] + "BinaryLibrary": .init(kind: .xcframework, originURL: nil, path: "/Foo/BinaryLibrary.xcframework"), + ], ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope @@ -2000,20 +2199,21 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/foo/main.swift", - "/Foo/Sources/foo/Resources/Data.plist", - "/Foo/Sources/foo/Resources/Database.xcdatamodel", - "/Foo/Sources/FooLib/lib.swift", - "/Foo/Sources/FooLib/Resources/Data.plist", - "/Foo/Sources/FooLib/Resources/Database.xcdatamodel", - "/Foo/Sources/FooTests/FooTests.swift", - "/Foo/Sources/FooTests/Resources/Data.plist", - "/Foo/Sources/FooTests/Resources/Database.xcdatamodel" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/foo/Resources/Data.plist", + "/Foo/Sources/foo/Resources/Database.xcdatamodel", + "/Foo/Sources/FooLib/lib.swift", + "/Foo/Sources/FooLib/Resources/Data.plist", + "/Foo/Sources/FooLib/Resources/Database.xcdatamodel", + "/Foo/Sources/FooTests/FooTests.swift", + "/Foo/Sources/FooTests/Resources/Data.plist", + "/Foo/Sources/FooTests/Resources/Database.xcdatamodel" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -2026,15 +2226,16 @@ class PIFBuilderTests: XCTestCase { targets: [ .init(name: "foo", resources: [ // This is intentionally specific to test that we pick up `.xcdatamodel` implicitly. - .init(rule: .process(localization: .none), path: "Resources/Data.plist") + .init(rule: .process(localization: .none), path: "Resources/Data.plist"), ]), .init(name: "FooLib", resources: [ - .init(rule: .process(localization: .none), path: "Resources") + .init(rule: .process(localization: .none), path: "Resources"), ]), .init(name: "FooTests", resources: [ - .init(rule: .process(localization: .none), path: "Resources") + .init(rule: .process(localization: .none), path: "Resources"), ], type: .test), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, useXCBuildFileRules: true, @@ -2218,80 +2419,100 @@ class PIFBuilderTests: XCTestCase { } } - func testBuildSettings() throws { + func buildSettingsTestCase(isPackageAccessModifierSupported: Bool) throws { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/foo/main.swift", - "/Foo/Sources/FooLib/lib.swift", - "/Foo/Sources/FooTests/FooTests.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/FooLib/lib.swift", + "/Foo/Sources/FooTests/FooTests.swift" ) + let toolsVersion: ToolsVersion = if isPackageAccessModifierSupported { .v5_9 } else { .v5 } + let mainTargetType: TargetDescription.TargetType = if toolsVersion >= .v5_9 { .executable } else { .regular } let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( displayName: "Foo", path: "/Foo", - toolsVersion: .v5, + toolsVersion: toolsVersion, products: [ .init(name: "FooLib", type: .library(.automatic), targets: ["FooLib"]), ], targets: [ - .init(name: "foo", settings: [ - .init( - tool: .c, - kind: .define("ENABLE_BEST_MODE")), - .init( - tool: .cxx, - kind: .headerSearchPath("some/path"), - condition: .init(platformNames: ["macos"])), - .init( - tool: .linker, - kind: .linkedLibrary("z"), - condition: .init(config: "debug")), - .init( - tool: .swift, - kind: .unsafeFlags(["-secret", "value"]), - condition: .init(platformNames: ["macos", "linux"], config: "release")), - ]), + .init( + name: "foo", + type: mainTargetType, + settings: [ + .init( + tool: .c, + kind: .define("ENABLE_BEST_MODE") + ), + .init( + tool: .cxx, + kind: .headerSearchPath("some/path"), + condition: .init(platformNames: ["macos"]) + ), + .init( + tool: .linker, + kind: .linkedLibrary("z"), + condition: .init(config: "debug") + ), + .init( + tool: .swift, + kind: .unsafeFlags(["-secret", "value"]), + condition: .init(platformNames: ["macos", "linux"], config: "release") + ), + ] + ), .init(name: "FooLib", settings: [ .init( tool: .c, - kind: .define("ENABLE_BEST_MODE")), + kind: .define("ENABLE_BEST_MODE") + ), .init( tool: .cxx, kind: .headerSearchPath("some/path"), - condition: .init(platformNames: ["macos"])), + condition: .init(platformNames: ["macos"]) + ), .init( tool: .linker, kind: .linkedLibrary("z"), - condition: .init(config: "debug")), + condition: .init(config: "debug") + ), .init( tool: .swift, kind: .unsafeFlags(["-secret", "value"]), - condition: .init(platformNames: ["macos", "linux"], config: "release")), + condition: .init(platformNames: ["macos", "linux"], config: "release") + ), ]), .init(name: "FooTests", type: .test, settings: [ .init( tool: .c, - kind: .define("ENABLE_BEST_MODE")), + kind: .define("ENABLE_BEST_MODE") + ), .init( tool: .cxx, kind: .headerSearchPath("some/path"), - condition: .init(platformNames: ["macos"])), + condition: .init(platformNames: ["macos"]) + ), .init( tool: .linker, kind: .linkedLibrary("z"), - condition: .init(config: "debug")), + condition: .init(config: "debug") + ), .init( tool: .swift, kind: .unsafeFlags(["-secret", "value"]), - condition: .init(platformNames: ["macos", "linux"], config: "release")), + condition: .init(platformNames: ["macos", "linux"], config: "release") + ), ]), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope @@ -2299,7 +2520,7 @@ class PIFBuilderTests: XCTestCase { let builder = PIFBuilder( graph: graph, - parameters: .mock(), + parameters: .mock(isPackageAccessModifierSupported: isPackageAccessModifierSupported), fileSystem: fs, observabilityScope: observability.topScope ) @@ -2307,34 +2528,52 @@ class PIFBuilderTests: XCTestCase { XCTAssertNoDiagnostics(observability.diagnostics) + let packageNameOptions = if isPackageAccessModifierSupported { + ["-package-name", "foo"] + } else { + [String]?.none + } + try PIFTester(pif) { workspace in try workspace.checkProject("PACKAGE:/Foo") { project in project.checkTarget("PACKAGE-PRODUCT:foo") { target in target.checkBuildConfiguration("Debug") { configuration in configuration.checkBuildSettings { settings in - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "ENABLE_BEST_MODE"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "ENABLE_BEST_MODE"] + ) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS, for: .macOS], [ "$(inherited)", - "/Foo/Sources/foo/some/path" + "/Foo/Sources/foo/some/path", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-lz"]) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) + XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], packageNameOptions) } } target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "ENABLE_BEST_MODE"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "ENABLE_BEST_MODE"] + ) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS, for: .macOS], [ "$(inherited)", - "/Foo/Sources/foo/some/path" + "/Foo/Sources/foo/some/path", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], nil) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS, for: .macOS], ["$(inherited)", "-secret", "value"]) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS, for: .linux], ["$(inherited)", "-secret", "value"]) + XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], packageNameOptions) + XCTAssertEqual( + settings[.OTHER_SWIFT_FLAGS, for: .macOS], + ["$(inherited)", "-secret", "value"] + ) + XCTAssertEqual( + settings[.OTHER_SWIFT_FLAGS, for: .linux], + ["$(inherited)", "-secret", "value"] + ) } } } @@ -2364,36 +2603,51 @@ class PIFBuilderTests: XCTestCase { project.checkTarget("PACKAGE-TARGET:FooLib") { target in target.checkBuildConfiguration("Debug") { configuration in configuration.checkBuildSettings { settings in - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "ENABLE_BEST_MODE"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "ENABLE_BEST_MODE"] + ) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS, for: .macOS], [ "$(inherited)", - "/Foo/Sources/FooLib/some/path" + "/Foo/Sources/FooLib/some/path", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-lz"]) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) + XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], packageNameOptions) } } target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "ENABLE_BEST_MODE"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "ENABLE_BEST_MODE"] + ) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS, for: .macOS], [ "$(inherited)", - "/Foo/Sources/FooLib/some/path" + "/Foo/Sources/FooLib/some/path", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], nil) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS, for: .macOS], ["$(inherited)", "-secret", "value"]) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS, for: .linux], ["$(inherited)", "-secret", "value"]) + XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], packageNameOptions) + XCTAssertEqual( + settings[.OTHER_SWIFT_FLAGS, for: .macOS], + ["$(inherited)", "-secret", "value"] + ) + XCTAssertEqual( + settings[.OTHER_SWIFT_FLAGS, for: .linux], + ["$(inherited)", "-secret", "value"] + ) } } target.checkImpartedBuildSettings { settings in XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) - XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-lz"]) + XCTAssertEqual( + settings[.OTHER_LDFLAGS], + ["$(inherited)", "-Wl,-no_warn_duplicate_libraries", "-lz"] + ) XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) } } @@ -2401,29 +2655,41 @@ class PIFBuilderTests: XCTestCase { project.checkTarget("PACKAGE-PRODUCT:FooTests") { target in target.checkBuildConfiguration("Debug") { configuration in configuration.checkBuildSettings { settings in - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "ENABLE_BEST_MODE"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "ENABLE_BEST_MODE"] + ) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS, for: .macOS], [ "$(inherited)", - "/Foo/Sources/FooTests/some/path" + "/Foo/Sources/FooTests/some/path", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], ["$(inherited)", "-lz"]) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) + XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], packageNameOptions) } } target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in - XCTAssertEqual(settings[.GCC_PREPROCESSOR_DEFINITIONS], ["$(inherited)", "ENABLE_BEST_MODE"]) + XCTAssertEqual( + settings[.GCC_PREPROCESSOR_DEFINITIONS], + ["$(inherited)", "ENABLE_BEST_MODE"] + ) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS], nil) XCTAssertEqual(settings[.HEADER_SEARCH_PATHS, for: .macOS], [ "$(inherited)", - "/Foo/Sources/FooTests/some/path" + "/Foo/Sources/FooTests/some/path", ]) XCTAssertEqual(settings[.OTHER_LDFLAGS], nil) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS, for: .macOS], ["$(inherited)", "-secret", "value"]) - XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS, for: .linux], ["$(inherited)", "-secret", "value"]) + XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], packageNameOptions) + XCTAssertEqual( + settings[.OTHER_SWIFT_FLAGS, for: .macOS], + ["$(inherited)", "-secret", "value"] + ) + XCTAssertEqual( + settings[.OTHER_SWIFT_FLAGS, for: .linux], + ["$(inherited)", "-secret", "value"] + ) } } } @@ -2431,19 +2697,28 @@ class PIFBuilderTests: XCTestCase { } } + func testBuildSettings() throws { + try self.buildSettingsTestCase(isPackageAccessModifierSupported: false) + } + + func testBuildSettingsPackageAccess() throws { + try self.buildSettingsTestCase(isPackageAccessModifierSupported: true) + } + func testConditionalDependencies() throws { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/foo/main.swift", - "/Foo/Sources/FooLib1/lib.swift", - "/Foo/Sources/FooLib2/lib.swift", - "/Foo/Sources/FooTests/FooTests.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/FooLib1/lib.swift", + "/Foo/Sources/FooLib2/lib.swift", + "/Foo/Sources/FooTests/FooTests.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createManifest( @@ -2458,7 +2733,8 @@ class PIFBuilderTests: XCTestCase { ]), .init(name: "FooLib1"), .init(name: "FooLib2"), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope @@ -2485,18 +2761,28 @@ class PIFBuilderTests: XCTestCase { XCTAssertEqual(target.dependencies, ["PACKAGE-TARGET:FooLib1", "PACKAGE-TARGET:FooLib2"]) XCTAssertEqual(target.frameworks, ["PACKAGE-TARGET:FooLib1", "PACKAGE-TARGET:FooLib2"]) - let dependencyMap = Dictionary(uniqueKeysWithValues: target.baseTarget.dependencies.map{ ($0.targetGUID, $0.platformFilters) }) + let dependencyMap = Dictionary(uniqueKeysWithValues: target.baseTarget.dependencies.map { ( + $0.targetGUID, + $0.platformFilters + ) }) XCTAssertEqual(dependencyMap, expectedFilters) - let frameworksBuildFiles = target.baseTarget.buildPhases.first{ $0 is PIF.FrameworksBuildPhase }?.buildFiles ?? [] - let frameworksBuildFilesMap = Dictionary(uniqueKeysWithValues: frameworksBuildFiles.compactMap{ file -> (PIF.GUID, [PIF.PlatformFilter])? in - switch file.reference { - case .target(let guid): - return (guid, file.platformFilters) - case .file: - return nil - } - }) + let frameworksBuildFiles = target.baseTarget.buildPhases.first { $0 is PIF.FrameworksBuildPhase }? + .buildFiles ?? [] + let frameworksBuildFilesMap = Dictionary( + uniqueKeysWithValues: frameworksBuildFiles + .compactMap { file -> ( + PIF.GUID, + [PIF.PlatformFilter] + )? in + switch file.reference { + case .target(let guid): + return (guid, file.platformFilters) + case .file: + return nil + } + } + ) XCTAssertEqual(dependencyMap, frameworksBuildFilesMap) } } @@ -2507,12 +2793,13 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/Foo/Sources/foo/main.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -2524,7 +2811,8 @@ class PIFBuilderTests: XCTestCase { toolsVersion: .v5_3, targets: [ .init(name: "foo", dependencies: []), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope @@ -2557,12 +2845,13 @@ class PIFBuilderTests: XCTestCase { #if !os(macOS) try XCTSkipIf(true, "test is only supported on macOS") #endif - let fs = InMemoryFileSystem(emptyFiles: - "/MyLib/Sources/MyLib/Foo.swift" + let fs = InMemoryFileSystem( + emptyFiles: + "/MyLib/Sources/MyLib/Foo.swift" ) let observability = ObservabilitySystem.makeForTesting() - let graph = try loadPackageGraph( + let graph = try loadModulesGraph( fileSystem: fs, manifests: [ Manifest.createRootManifest( @@ -2577,9 +2866,11 @@ class PIFBuilderTests: XCTestCase { .init( tool: .swift, kind: .unsafeFlags(["-enable-library-evolution"]), - condition: .init(config: "release")), + condition: .init(config: "release") + ), ]), - ]), + ] + ), ], shouldCreateMultipleTestProducts: true, observabilityScope: observability.topScope @@ -2607,7 +2898,8 @@ class PIFBuilderTests: XCTestCase { } target.checkBuildConfiguration("Release") { configuration in configuration.checkBuildSettings { settings in - // Check that the `-enable-library-evolution` setting for Release also set SWIFT_EMIT_MODULE_INTERFACE. + // Check that the `-enable-library-evolution` setting for Release also set + // SWIFT_EMIT_MODULE_INTERFACE. XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], "YES") XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], ["$(inherited)", "-enable-library-evolution"]) } @@ -2616,18 +2908,161 @@ class PIFBuilderTests: XCTestCase { } } } + + func testSupportedSwiftVersions() throws { + #if !os(macOS) + try XCTSkipIf(true, "test is only supported on macOS") + #endif + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/bar/main.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v5_3, + swiftLanguageVersions: [.v4_2, .v5], + targets: [ + .init(name: "foo", dependencies: []), + .init(name: "bar", dependencies: [], settings: [ + .init( + tool: .swift, + kind: .swiftLanguageVersion(.v4_2), + condition: .init(platformNames: ["linux"]) + ), + ]), + ] + ), + ], + shouldCreateMultipleTestProducts: true, + observabilityScope: observability.topScope + ) + + let builder = PIFBuilder( + graph: graph, + parameters: .mock(supportedSwiftVersions: [.v4_2, .v5]), + fileSystem: fs, + observabilityScope: observability.topScope + ) + let pif = try builder.construct() + + XCTAssertNoDiagnostics(observability.diagnostics) + + try PIFTester(pif) { workspace in + try workspace.checkProject("PACKAGE:/Foo") { project in + project.checkTarget("PACKAGE-PRODUCT:foo") { target in + target.checkBuildConfiguration("Debug") { configuration in + configuration.checkBuildSettings { settings in + XCTAssertEqual(settings[.SWIFT_VERSION], "5") + } + } + } + + project.checkTarget("PACKAGE-PRODUCT:bar") { target in + target.checkBuildConfiguration("Debug") { configuration in + configuration.checkBuildSettings { settings in + XCTAssertEqual(settings[.SWIFT_VERSION], "5") + XCTAssertEqual(settings[.SWIFT_VERSION, for: .linux], "4.2") + } + } + } + } + } + } + + func testPerTargetSwiftVersions() throws { + #if !os(macOS) + try XCTSkipIf(true, "test is only supported on macOS") + #endif + let fs = InMemoryFileSystem( + emptyFiles: + "/Foo/Sources/foo/main.swift", + "/Foo/Sources/bar/main.swift", + "/Foo/Sources/baz/main.swift" + ) + + let observability = ObservabilitySystem.makeForTesting() + let graph = try loadModulesGraph( + fileSystem: fs, + manifests: [ + Manifest.createRootManifest( + displayName: "Foo", + path: "/Foo", + toolsVersion: .v5_3, + swiftLanguageVersions: [.v4_2, .v5], + targets: [ + .init(name: "foo", dependencies: [], settings: [ + .init( + tool: .swift, + kind: .swiftLanguageVersion(.v4_2) + ), + ]), + .init(name: "bar", dependencies: [], settings: [ + .init( + tool: .swift, + kind: .swiftLanguageVersion(.v6) + ), + ]), + .init(name: "baz", dependencies: [], settings: [ + .init( + tool: .swift, + kind: .swiftLanguageVersion(.v3), + condition: .init(platformNames: ["linux"]) + ), + .init( + tool: .swift, + kind: .swiftLanguageVersion(.v4_2), + condition: .init(platformNames: ["macOS"]) + ), + ]), + ] + ), + ], + shouldCreateMultipleTestProducts: true, + observabilityScope: observability.topScope + ) + + let builder = PIFBuilder( + graph: graph, + parameters: .mock(supportedSwiftVersions: [.v4_2, .v5]), + fileSystem: fs, + observabilityScope: observability.topScope + ) + let _ = try builder.construct() + + testDiagnostics(observability.diagnostics) { result in + result.check( + diagnostic: "Some of the Swift language versions used in target 'bar' settings are supported. (given: [6], supported: [4.2, 5])", + severity: .error + ) + result.check( + diagnostic: "Some of the Swift language versions used in target 'baz' settings are supported. (given: [3], supported: [4.2, 5])", + severity: .error + ) + } + } } extension PIFBuilderParameters { static func mock( - shouldCreateDylibForDynamicProducts: Bool = false + isPackageAccessModifierSupported: Bool = false, + shouldCreateDylibForDynamicProducts: Bool = false, + supportedSwiftVersions: [SwiftLanguageVersion] = [] ) -> Self { PIFBuilderParameters( + isPackageAccessModifierSupported: isPackageAccessModifierSupported, enableTestability: false, shouldCreateDylibForDynamicProducts: shouldCreateDylibForDynamicProducts, toolchainLibDir: "/toolchain/lib", pkgConfigDirectories: ["/pkg-config"], - sdkRootPath: "/some.sdk" + sdkRootPath: "/some.sdk", + supportedSwiftVersions: supportedSwiftVersions ) } } diff --git a/Utilities/bootstrap b/Utilities/bootstrap index 76923ddac92..23bb9bd8e91 100755 --- a/Utilities/bootstrap +++ b/Utilities/bootstrap @@ -202,6 +202,7 @@ def parse_global_args(args): args.source_dirs["swift-collections"] = os.path.join(args.project_root, "..", "swift-collections") args.source_dirs["swift-certificates"] = os.path.join(args.project_root, "..", "swift-certificates") args.source_dirs["swift-asn1"] = os.path.join(args.project_root, "..", "swift-asn1") + args.source_dirs["swift-syntax"] = os.path.join(args.project_root, "..", "swift-syntax") args.source_root = os.path.join(args.project_root, "Sources") if platform.system() == 'Darwin': @@ -403,6 +404,9 @@ def install(args): config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json") install_file(args, config_path, os.path.join(os.path.join(prefix, "share"), "pm")) + libs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "provided-libraries.json") + install_file(args, config_path, os.path.join(os.path.join(prefix, "share"), "pm")) + # Install libSwiftPM if an install directory was provided. if args.libswiftpm_install_dir: libswiftpm_modules = [ @@ -431,7 +435,7 @@ def install_swiftpm(prefix, args): # Install the swift-package-manager tool and create symlinks to it. cli_tool_dest = os.path.join(prefix, "bin") install_binary(args, "swift-package-manager", os.path.join(cli_tool_dest, "swift-package"), destination_is_directory=False) - for tool in ["swift-build", "swift-test", "swift-run", "swift-package-collection", "swift-package-registry", "swift-experimental-sdk"]: + for tool in ["swift-build", "swift-test", "swift-run", "swift-package-collection", "swift-package-registry", "swift-sdk", "swift-experimental-sdk"]: src = "swift-package" dest = os.path.join(cli_tool_dest, tool) note("Creating tool symlink from %s to %s" % (src, dest)) @@ -605,6 +609,7 @@ def build_swiftpm_with_cmake(args): "-DSwiftCrypto_DIR=" + os.path.join(args.build_dirs["swift-crypto"], "cmake/modules"), "-DSwiftASN1_DIR=" + os.path.join(args.build_dirs["swift-asn1"], "cmake/modules"), "-DSwiftCertificates_DIR=" + os.path.join(args.build_dirs["swift-certificates"], "cmake/modules"), + "-DSWIFTPM_PATH_TO_SWIFT_SYNTAX_SOURCE=" + args.source_dirs["swift-syntax"], ] if platform.system() == 'Darwin': diff --git a/Utilities/config.json b/Utilities/config.json index 8638fc1f588..b31399a6276 100644 --- a/Utilities/config.json +++ b/Utilities/config.json @@ -1 +1,3 @@ -{"version":1,"swiftSyntaxVersionForMacroTemplate":{"major":509,"minor":0,"patch":0}} \ No newline at end of file +{"version":1, + "swiftSyntaxVersionForMacroTemplate":{"major":600,"minor":0,"patch":0, "prereleaseIdentifier":"latest"}, + "swiftTestingVersionForTestTemplate":{"major":0,"minor":8,"patch":0}} diff --git a/Utilities/provided-libraries.json b/Utilities/provided-libraries.json new file mode 100644 index 00000000000..02d798442b7 --- /dev/null +++ b/Utilities/provided-libraries.json @@ -0,0 +1,16 @@ +[ + { + "identities": + [ + { + "sourceControl": + { + "url": {"urlString": "https://github.com/apple/swift-testing.git"} + } + } + ], + "productName": "Testing", + "version": "0.4.0", + "schemaVersion": 1 + } +]