Skip to content

Commit 185b652

Browse files
committed
Merge remote-tracking branch 'upstream/main' into vswhere
2 parents 7867ea8 + a09400d commit 185b652

File tree

6 files changed

+484
-18
lines changed

6 files changed

+484
-18
lines changed

.swiftformat

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,43 @@
1-
# file options
1+
## File options
22

33
--swiftversion 5.5
44
--exclude .build
55

6-
# format options
6+
## Formatting options
77

8-
--self insert
9-
--patternlet inline
10-
--stripunusedargs unnamed-only
8+
# Configure the placement of an extension's access control keyword.
9+
--extensionacl on-declarations
10+
11+
# #if indenting
1112
--ifdef no-indent
13+
14+
# Wrap lines that exceed the specified maximum width.
1215
--maxwidth 120
13-
--extensionacl on-declarations
1416

15-
# rules
17+
# Reposition `let` or `var` bindings within pattern.
18+
--patternlet inline
19+
20+
# Insert explicit `self` where applicable.
21+
--self insert
22+
23+
# Mark unused function arguments with `_`.
24+
--stripunusedargs unnamed-only
25+
26+
# Remove trailing space at end of a line.
27+
--trimwhitespace always
28+
29+
# Wrap `@attributes` onto a separate line.
30+
--funcattributes prev-line
31+
--typeattributes prev-line
32+
--varattributes prev-line
33+
34+
# Use `Void` for type declarations and `()` for values.
35+
--voidtype void
36+
37+
# Align wrapped function arguments or collection elements.
38+
--wraparguments before-first
39+
40+
## Rules
41+
42+
# Prefer `&&` over `,` comma in `if`, `guard` or `while` conditions.
43+
--disable andOperator

Sources/PackageModel/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ add_library(PackageModel
4242
Target.swift
4343
Toolchain.swift
4444
ToolchainConfiguration.swift
45+
Toolset.swift
4546
ToolsVersion.swift
4647
ToolsVersionSpecificationGeneration.swift
4748
UserToolchain.swift

Sources/PackageModel/Toolset.swift

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2014-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Foundation
14+
15+
import class Basics.ObservabilityScope
16+
import struct TSCBasic.AbsolutePath
17+
import protocol TSCBasic.FileSystem
18+
import struct TSCBasic.RelativePath
19+
import struct TSCBasic.StringError
20+
import struct TSCUtility.Version
21+
22+
/// A set of paths and flags for tools used for building Swift packages. This type unifies pre-existing assorted ways
23+
/// to specify these properties across SwiftPM codebase.
24+
public struct Toolset {
25+
public enum KnownTool: String, Hashable, CaseIterable {
26+
case swiftCompiler
27+
case cCompiler
28+
case cxxCompiler
29+
case linker
30+
case librarian
31+
case debugger
32+
case testRunner
33+
}
34+
35+
/// Properties of a known tool in a ``Toolset``.
36+
public struct ToolProperties: Equatable {
37+
/// Absolute path to the tool on the filesystem. If absent, implies a default tool is used.
38+
public fileprivate(set) var path: AbsolutePath?
39+
40+
/// Command-line options to be passed to the tool when it's invoked.
41+
public fileprivate(set) var extraCLIOptions: [String]?
42+
}
43+
44+
/// A dictionary of known tools in this toolset.
45+
public fileprivate(set) var knownTools: [KnownTool: ToolProperties]
46+
}
47+
48+
extension Toolset {
49+
/// Initialize a toolset from an encoded file on a file system.
50+
/// - Parameters:
51+
/// - path: absolute path on the `fileSystem`.
52+
/// - fileSystem: file system from which the toolset should be read.
53+
/// - observability: an instance of `ObservabilityScope` to log warnings about unknown or invalid tools.
54+
public init(from toolsetPath: AbsolutePath, at fileSystem: FileSystem, _ observability: ObservabilityScope) throws {
55+
let decoder = JSONDecoder()
56+
let decoded = try decoder.decode(path: toolsetPath, fileSystem: fileSystem, as: DecodedToolset.self)
57+
guard decoded.schemaVersion == Version(1, 0, 0) else {
58+
throw StringError(
59+
"Unsupported `schemaVersion` \(decoded.schemaVersion) in toolset configuration at \(toolsetPath)"
60+
)
61+
}
62+
63+
var knownTools = [KnownTool: ToolProperties]()
64+
var hasEmptyToolConfiguration = false
65+
for (tool, properties) in decoded.tools {
66+
guard let knownTool = KnownTool(rawValue: tool) else {
67+
observability.emit(warning: "Unknown tool `\(tool)` in toolset configuration at `\(toolsetPath)`")
68+
continue
69+
}
70+
71+
let toolPath: AbsolutePath?
72+
if let path = properties.path {
73+
if let absolutePath = try? AbsolutePath(validating: path) {
74+
toolPath = absolutePath
75+
} else {
76+
let rootPath = decoded.rootPath ?? toolsetPath.parentDirectory
77+
toolPath = rootPath.appending(RelativePath(path))
78+
}
79+
} else {
80+
toolPath = nil
81+
}
82+
83+
guard toolPath != nil || !(properties.extraCLIOptions?.isEmpty ?? true) else {
84+
// don't keep track of a tool with no path and CLI options specified.
85+
observability.emit(error:
86+
"""
87+
Tool `\(knownTool.rawValue) in toolset configuration at `\(toolsetPath)` has neither `path` nor \
88+
`extraCLIOptions` properties specified with valid values, skipping it.
89+
""")
90+
hasEmptyToolConfiguration = true
91+
continue
92+
}
93+
94+
knownTools[knownTool] = ToolProperties(
95+
path: toolPath,
96+
extraCLIOptions: properties.extraCLIOptions
97+
)
98+
}
99+
100+
guard !hasEmptyToolConfiguration else {
101+
throw StringError("Toolset configuration at `\(toolsetPath)` has at least one tool with no properties.")
102+
}
103+
104+
self.init(knownTools: knownTools)
105+
}
106+
107+
/// Merges toolsets together into a single configuration. Tools passed in a new toolset will shadow tools with
108+
/// same names from previous toolsets. When no `path` is specified for a new tool, its `extraCLIOptions` are
109+
/// appended to `extraCLIOptions` of a tool from a previous toolset, which allows augmenting existing tools instead
110+
/// of replacing them.
111+
/// - Parameter newToolset: new toolset to merge into the existing `self` toolset.
112+
public mutating func merge(with newToolset: Toolset) {
113+
for (newTool, newProperties) in newToolset.knownTools {
114+
if newProperties.path != nil {
115+
// if `newTool` has `path` specified, it overrides the existing tool completely.
116+
knownTools[newTool] = newProperties
117+
} else if let newExtraCLIOptions = newProperties.extraCLIOptions, !newExtraCLIOptions.isEmpty {
118+
// if `newTool` has no `path` specified, `newExtraCLIOptions` are appended to the existing tool.
119+
if var existingTool = knownTools[newTool] {
120+
// either update the existing tool and store it back...
121+
if existingTool.extraCLIOptions == nil {
122+
existingTool.extraCLIOptions = newExtraCLIOptions
123+
} else {
124+
existingTool.extraCLIOptions?.append(contentsOf: newExtraCLIOptions)
125+
}
126+
knownTools[newTool] = existingTool
127+
} else {
128+
// ...or store a new tool if no existing tool is found.
129+
knownTools[newTool] = newProperties
130+
}
131+
}
132+
}
133+
}
134+
}
135+
136+
/// A raw decoding of toolset configuration stored on disk.
137+
private struct DecodedToolset {
138+
/// Version of a toolset schema used for decoding a toolset file.
139+
let schemaVersion: Version
140+
141+
/// Root path of the toolset, if present. When filling in ``Toolset.ToolProperties/path``, if a raw path string in
142+
/// ``DecodedToolset`` is inferred to be relative, it's resolved as absolute path relatively to `rootPath`.
143+
let rootPath: AbsolutePath?
144+
145+
/// Dictionary of raw tools that haven't been validated yet to match ``Toolset.KnownTool``.
146+
var tools: [String: ToolProperties]
147+
148+
/// Properties of a tool in a ``DecodedToolset``.
149+
public struct ToolProperties {
150+
/// Either a relative or an absolute path to the tool on the filesystem.
151+
let path: String?
152+
153+
/// Command-line options to be passed to the tool when it's invoked.
154+
let extraCLIOptions: [String]?
155+
}
156+
}
157+
158+
extension DecodedToolset.ToolProperties: Decodable {}
159+
160+
extension DecodedToolset: Decodable {
161+
/// Custom decoding keys that allow decoding tools with arbitrary names,
162+
enum CodingKeys: Equatable {
163+
case schemaVersion
164+
case rootPath
165+
case tool(String)
166+
}
167+
168+
public init(from decoder: Decoder) throws {
169+
let container = try decoder.container(keyedBy: CodingKeys.self)
170+
171+
self.schemaVersion = try Version(
172+
versionString: container.decode(String.self, forKey: .schemaVersion),
173+
usesLenientParsing: true
174+
)
175+
self.rootPath = try container.decodeIfPresent(AbsolutePath.self, forKey: .rootPath)
176+
177+
self.tools = [String: DecodedToolset.ToolProperties]()
178+
for key in container.allKeys {
179+
switch key {
180+
case .rootPath, .schemaVersion:
181+
// These keys were already decoded before entering this loop, skipping.
182+
continue
183+
case .tool(let tool):
184+
self.tools[tool] = try container.decode(DecodedToolset.ToolProperties.self, forKey: key)
185+
}
186+
}
187+
}
188+
}
189+
190+
/// Custom `CodingKey` implementation for `DecodedToolset`, which allows us to resiliently decode unknown tools and emit
191+
/// multiple diagnostic messages about them separately from the decoding process, instead of emitting a single error
192+
/// that will disrupt whole decoding at once.
193+
extension DecodedToolset.CodingKeys: CodingKey {
194+
var stringValue: String {
195+
switch self {
196+
case .schemaVersion:
197+
return "schemaVersion"
198+
case .rootPath:
199+
return "rootPath"
200+
case .tool(let toolName):
201+
return toolName
202+
}
203+
}
204+
205+
init?(stringValue: String) {
206+
switch stringValue {
207+
case "schemaVersion":
208+
self = .schemaVersion
209+
case "rootPath":
210+
self = .rootPath
211+
default:
212+
self = .tool(stringValue)
213+
}
214+
}
215+
216+
var intValue: Int? { nil }
217+
218+
init?(intValue: Int) {
219+
nil
220+
}
221+
}

Sources/PackageRegistryTool/SwiftPackageRegistryTool+Publish.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import PackageModel
1919
import PackageRegistry
2020
import TSCBasic
2121

22+
import struct TSCUtility.Version
23+
2224
extension SwiftPackageRegistryTool {
2325
struct Publish: SwiftCommand {
2426
static let configuration = CommandConfiguration(

Sources/SPMTestSupport/Observability.swift

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
import Basics
1414
import struct TSCBasic.StringError
15-
import func XCTest.XCTFail
1615
import func XCTest.XCTAssertEqual
16+
import func XCTest.XCTFail
1717

1818
extension ObservabilitySystem {
1919
public static func makeForTesting(verbose: Bool = true) -> TestingObservability {
@@ -23,7 +23,7 @@ extension ObservabilitySystem {
2323
}
2424

2525
public static var NOOP: ObservabilityScope {
26-
ObservabilitySystem({ _, _ in }).topScope
26+
ObservabilitySystem { _, _ in }.topScope
2727
}
2828
}
2929

@@ -40,6 +40,14 @@ public struct TestingObservability {
4040
self.collector.diagnostics.get()
4141
}
4242

43+
public var errors: [Basics.Diagnostic] {
44+
self.diagnostics.filter { $0.severity == .error }
45+
}
46+
47+
public var warnings: [Basics.Diagnostic] {
48+
self.diagnostics.filter { $0.severity == .warning }
49+
}
50+
4351
public var hasErrorDiagnostics: Bool {
4452
self.collector.hasErrors
4553
}
@@ -49,7 +57,7 @@ public struct TestingObservability {
4957
}
5058

5159
final class Collector: ObservabilityHandlerProvider, DiagnosticsHandler, CustomStringConvertible {
52-
var diagnosticsHandler: DiagnosticsHandler { return self }
60+
var diagnosticsHandler: DiagnosticsHandler { self }
5361

5462
let diagnostics: ThreadSafeArrayStore<Basics.Diagnostic>
5563
private let verbose: Bool
@@ -68,11 +76,11 @@ public struct TestingObservability {
6876
}
6977

7078
var hasErrors: Bool {
71-
return self.diagnostics.get().hasErrors
79+
self.diagnostics.get().hasErrors
7280
}
7381

7482
var hasWarnings: Bool {
75-
return self.diagnostics.get().hasWarnings
83+
self.diagnostics.get().hasWarnings
7684
}
7785

7886
var description: String {
@@ -82,10 +90,15 @@ public struct TestingObservability {
8290
}
8391
}
8492

85-
public func XCTAssertNoDiagnostics(_ diagnostics: [Basics.Diagnostic], problemsOnly: Bool = true, file: StaticString = #file, line: UInt = #line) {
86-
let diagnostics = problemsOnly ? diagnostics.filter({ $0.severity >= .warning }) : diagnostics
93+
public func XCTAssertNoDiagnostics(
94+
_ diagnostics: [Basics.Diagnostic],
95+
problemsOnly: Bool = true,
96+
file: StaticString = #file,
97+
line: UInt = #line
98+
) {
99+
let diagnostics = problemsOnly ? diagnostics.filter { $0.severity >= .warning } : diagnostics
87100
if diagnostics.isEmpty { return }
88-
let description = diagnostics.map({ "- " + $0.description }).joined(separator: "\n")
101+
let description = diagnostics.map { "- " + $0.description }.joined(separator: "\n")
89102
XCTFail("Found unexpected diagnostics: \n\(description)", file: file, line: line)
90103
}
91104

@@ -96,7 +109,13 @@ public func testDiagnostics(
96109
line: UInt = #line,
97110
handler: (DiagnosticsTestResult) throws -> Void
98111
) {
99-
testDiagnostics(diagnostics, minSeverity: problemsOnly ? .warning : .debug, file: file, line: line, handler: handler)
112+
testDiagnostics(
113+
diagnostics,
114+
minSeverity: problemsOnly ? .warning : .debug,
115+
file: file,
116+
line: line,
117+
handler: handler
118+
)
100119
}
101120

102121
public func testDiagnostics(
@@ -106,7 +125,7 @@ public func testDiagnostics(
106125
line: UInt = #line,
107126
handler: (DiagnosticsTestResult) throws -> Void
108127
) {
109-
let diagnostics = diagnostics.filter{ $0.severity >= minSeverity }
128+
let diagnostics = diagnostics.filter { $0.severity >= minSeverity }
110129
let testResult = DiagnosticsTestResult(diagnostics)
111130

112131
do {
@@ -165,7 +184,8 @@ public class DiagnosticsTestResult {
165184
return nil
166185
}
167186

168-
let matching = self.uncheckedDiagnostics.indices.filter { diagnosticPattern ~= self.uncheckedDiagnostics[$0].message }
187+
let matching = self.uncheckedDiagnostics.indices
188+
.filter { diagnosticPattern ~= self.uncheckedDiagnostics[$0].message }
169189
if matching.isEmpty {
170190
XCTFail("No diagnostics match \(diagnosticPattern)", file: file, line: line)
171191
return nil

0 commit comments

Comments
 (0)