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