File: SWBBuildServiceSession.swift

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (954 lines) | stat: -rw-r--r-- 53,187 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 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 Foundation

import SWBProtocol
import SWBUtil

public enum SwiftBuildServicePIFObjectType: Sendable {
    case workspace
    case project
    case target

    var name: String {
        switch self {
        case .workspace:
            return "workspace"
        case .project:
            return "project"
        case .target:
            return "target"
        }
    }
}

/// This class encapsulates a unique session connection to the inferior build service process.
///
/// The state for each individual session is separated, and generally a unique session is used for each separate workspace, project, etc. that a client may want to build
public final class SWBBuildServiceSession: Sendable {
    /// The service this session is connected to.
    internal let service: SWBBuildService

    /// The name of the session.
    public let name: String

    /// The unique ID that was provided by the service, to refer to the session.
    public let uid: String

    /// An opaque identifier representing the lifetime of the specific service connection instance with which this session was created.
    private let connectionUUID: Foundation.UUID

    /// An object that keeps track of build operations and macro evaluation scopes created by the session in order to ensure they are properly torn down when the session is closed.
    ///
    /// For macro evaluation scopes, the session will register each newly created scope with the tracker. If the client manually discards the scope, it will be removed from the tracker by the session. If the client does not manually discard their scopes, they will be cleaned up by the tracker when the session is closed.
    ///
    /// For build operations, the tracker implements a generic background operation queue. Closures are enqueued to a stream, and consumed and awaited on a background task. Closing the session awaits that background taskto ensure all closures are awaited before continuing with teardown.
    private actor SessionResourceTracker {
        private var awaitableBuildOperations: AsyncStreamController<SWBBuildOperation, Never>
        private let cancellableBuildOperationsHolder = CancellableBuildOperationsHolder()

        private actor CancellableBuildOperationsHolder {
            private var cancellableBuildOperations: [Foundation.UUID: SWBBuildOperation] = [:]

            func insert(_ operation: SWBBuildOperation) {
                cancellableBuildOperations[operation.buildUUID] = operation
            }

            func finalize(_ operation: SWBBuildOperation) {
                cancellableBuildOperations.removeValue(forKey: operation.buildUUID)
            }

            func cancelAll() {
                let operations = cancellableBuildOperations
                cancellableBuildOperations = [:]
                for (_, operation) in operations {
                    if operation.state != .cancelled {
                        operation.cancel()
                    }
                }
            }
        }

        init() {
            awaitableBuildOperations = AsyncStreamController { [cancellableBuildOperationsHolder] stream in
                for await operation in stream {
                    await operation.waitForCompletion()
                    await cancellableBuildOperationsHolder.finalize(operation)
                }
            }
        }

        func enqueue(_ operation: SWBBuildOperation) async {
            let queuedResult = awaitableBuildOperations.yield(operation)
            switch queuedResult {
            case .enqueued:
                // The enqueued operation is going to be awaited by our operationAwaiter: AsyncStreamController.
                await cancellableBuildOperationsHolder.insert(operation)
            case .terminated:
                // If we've already begun shutdown, await the operation immediately to prevent a potential race where a build operation could be started after session shutdown and not awaited. In that case starting the build operation will simply "block" (await) until the operation has completed.
                if operation.state != .cancelled {
                    operation.cancel()
                }
                await operation.waitForCompletion()
            case .dropped:
                assertionFailure("unreachable")
                fallthrough
            @unknown default:
                break
            }
        }

        func invalidateAll() async throws {
            awaitableBuildOperations.finish()
            await cancellableBuildOperationsHolder.cancelAll()
            await awaitableBuildOperations.wait()
        }
    }

    private let sessionResourceTracker = SessionResourceTracker()

    private let closed = LockedValue(false)

    init(name: String, uid: String, service: SWBBuildService) {
        // Check parameters.
        precondition(name != "")
        precondition(uid != "")

        self.service = service
        self.name = name
        self.uid = uid
        self.connectionUUID = service.connectionUUID
    }

    deinit {
        closed.withLock { closed in
            if !closed {
                #if os(Windows)
                // FIXME: This is getting hit sometimes in the test suite
                print("Expected SWBBuildServiceSession to be closed before deinit")
                #else
                assertionFailure("Expected SWBBuildServiceSession to be closed before deinit")
                #endif
            }
        }
    }

    /// Whether or not the underlying service subprocess is currently terminated or a previous termination has invalidated the session.
    public var terminated: Bool {
        return service.terminated || service.connectionUUID != connectionUUID
    }

    /// Wait for background I/O operations to finish (for testing).
    @_spi(Testing) public func waitForQuiescence() async throws {
        try await self.service.waitForQuiescence(sessionHandle: uid)
    }

    /// Close the session and release any associated resources.
    ///
    /// It is an error to allow the session to be deallocated without being closed.
    public func close() async throws {
        var errors: [any Error] = []
        do {
            try await sessionResourceTracker.invalidateAll()
        } catch {
            errors.append(error)
        }
        do {
            try await self.service.deleteSession(sessionHandle: uid)
        } catch {
            errors.append(error)
        }
        closed.withLock { $0 = true }
        for error in errors {
            throw error
        }
    }

    /// Add a build request, and return an object that can be used to observe and to control it.  The provided delegate will be queried for any additional inputs required during the build operation.
    /// - note: This method does not _start_ the build operation, which must subsequently be done by calling ``SWBBuildOperation/start()`` on the received build operation object.
    @_disfavoredOverload
    public func createBuildOperation(request: SWBBuildRequest, delegate: any SWBPlanningOperationDelegate) async throws -> SWBBuildOperation {
        try await createBuildOperation(request: request, delegate: delegate, onlyCreateBuildDescription: false)
    }

    @_disfavoredOverload
    public func createBuildOperationForBuildDescriptionOnly(request: SWBBuildRequest, delegate: any SWBPlanningOperationDelegate) async throws -> SWBBuildOperation {
        try await createBuildOperation(request: request, delegate: delegate, onlyCreateBuildDescription: true)
    }

    internal func trackBuildOperation(_ operation: SWBBuildOperation) async {
        await sessionResourceTracker.enqueue(operation)
    }

    private func createBuildOperation(request: SWBBuildRequest, delegate: any SWBPlanningOperationDelegate, onlyCreateBuildDescription: Bool) async throws -> SWBBuildOperation {
        // Allocate a new SWBBuildOperation object that we can return to the client to use for observing and controlling the build.  As we start getting replies from Swift Build, we will keep it informed, and it will in turn tell all of its observers what’s going on.
        return try await SWBBuildOperation(session: self, delegate: delegate, request: request, onlyCreateBuildDescription: onlyCreateBuildDescription)
    }

    public func generateIndexingFileSettings(for request: SWBBuildRequest, targetID: String, filePath: String?, outputPathOnly: Bool, delegate: any SWBIndexingDelegate) async throws -> SWBIndexingFileSettings {
        guard let msg: IndexingFileSettingsResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in IndexingFileSettingsRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation, targetID: targetID, filePath: filePath, outputPathOnly: outputPathOnly) }, delegate: delegate) else {
            throw StubError.error("The indexing operation was cancelled.")
        }

        precondition(targetID == msg.targetID)

        let plist = try PropertyList.fromBytes(msg.data.bytes)
        guard let array = plist.arrayValue else {
            throw StubError.error("Expected an array value")
        }
        let dicts = try array.map { item -> [String: PropertyListItem] in
            guard let dict = item.dictValue else {
                throw StubError.error("Expected an dictionary value")
            }
            return dict
        }
        return try SWBIndexingFileSettings(sourceFileBuildInfos: dicts.map { try Dictionary($0) })
    }

    @_disfavoredOverload
    @available(*, deprecated, renamed: "generateIndexingFileSettings(for:targetID:filePath:outputPathOnly:delegate:)", message: "Use the overload returning SWBIndexingInfo")
    public func generateIndexingInfo(for request: SWBBuildRequest, targetID: String, filePath: String?, outputPathOnly: Bool, delegate: any SWBIndexingDelegate) async throws -> [[String: SWBPropertyListItem]] {
        try await generateIndexingFileSettings(for: request, targetID: targetID, filePath: filePath, outputPathOnly: outputPathOnly, delegate: delegate).sourceFileBuildInfos
    }

    public func generateIndexingHeaderInfo(for request: SWBBuildRequest, targetID: String, delegate: any SWBIndexingDelegate) async throws -> SWBIndexingHeaderInfo {
        guard let msg: IndexingHeaderInfoResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in IndexingHeaderInfoRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation, targetID: targetID) }, delegate: delegate) else {
            throw StubError.error("The indexing operation was cancelled.")
        }

        precondition(targetID == msg.targetID)

        return try SWBIndexingHeaderInfo(
            productName: msg.productName,
            copiedPathMap: Dictionary(uniqueKeysWithValues: msg.copiedPathMap.map { (try AbsolutePath(validating: $0.key), try AbsolutePath(validating: $0.value)) }))
    }

    /// Generate information about the documentation that will be built for a given build request.
    ///
    /// This API is used to get a list of all the documentation that a build request will build, if any at all, and the location where that built documentation will be written.
    /// It's used by IDEDocumentation to keep track of all the built documentation in the current workspace, so that documentation can be displayed in the Doc Viewer when the workspace is open.
    ///
    /// The user facing aspect of this feature is that when a developer opens the Doc Viewer, they will see the all built documentation for all everything in that workspace. Since the Doc Viewer will
    /// only display documentation that has already been built, this API is called _after_ a build has completed, requesting the documentation information for the build operation configuration that
    /// just finished. Since this build operation configuration was just built, it will be cached and doesn't need to be recomputed.
    ///
    /// The information about what documentation will be built and where it will be written is attached to the documentation compiler task as a custom payload. If the planned build doesn't have any
    /// documentation tasks, then the response to the message will return an empty array.
    public func generateDocumentationInfo(for request: SWBBuildRequest, delegate: any SWBDocumentationDelegate) async throws -> SWBDocumentationInfo {
        guard let msg: DocumentationInfoResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in DocumentationInfoRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation) }, delegate: delegate) else {
            throw StubError.error("The documentation info operation was cancelled.")
        }

        return SWBDocumentationInfo(builtDocumentationBundles: msg.output.map {
            SWBDocumentationBundleInfo(outputPath: $0.outputPath.normalize().str, targetIdentifier: $0.targetIdentifier)
        })
    }

    /// Generate and return information about the localizable content of a given build request.
    ///
    /// - Note: `buildRequest` must include the `buildDescriptionID` of a historical build.
    ///
    /// This API is used by the IDELocalization infrastructure to get an accurate picture of various things relevant to localization in a particular build, such as inputs, outputs, the target closure, etc.
    ///
    /// One primary use of this feature is determining which stringsdata files to consult when performing an XCStrings sync after a successful build.
    ///
    /// The info is provided by two mechanisms:
    /// 1. Output .stringsdata files anywhere in the generated task graph will be recognized.
    /// 2. Individual task specs get an opportunity to provide additional localization info.
    ///
    /// - Parameters:
    ///   - buildRequest: The build request to collect localization info for. `buildDescriptionID` must be set.
    ///   - targetIdentifiers: An optional filter of target GUIDs to return info for.
    ///   - delegate: An object that can perform delegate certain planning delegate tasks.
    public func generateLocalizationInfo(for buildRequest: SWBBuildRequest, targetIdentifiers: Set<String>? = nil, delegate: any SWBLocalizationDelegate) async throws -> SWBLocalizationInfo {
        guard let msg: LocalizationInfoResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in LocalizationInfoRequest(sessionHandle: uid, responseChannel: $0, request: buildRequest.messagePayloadRepresentation, targetIdentifiers: targetIdentifiers) }, delegate: delegate) else {
            throw StubError.error("The localization info operation was cancelled.")
        }

        // We got our localization info.
        var targetInfos = [String: SWBLocalizationTargetInfo]()
        for payload in msg.targetInfos {
            let xcstrings = payload.compilableXCStringsPaths.map { $0.normalize().str }

            var stringsdata = [SWBLocalizationBuildPortion: Set<String>]()
            for (buildPortion, paths) in payload.producedStringsdataPaths {
                stringsdata[SWBLocalizationBuildPortion(effectivePlatformName: buildPortion.effectivePlatformName, variant: buildPortion.variant, architecture: buildPortion.architecture)] = Set(paths.map({ $0.normalize().str }))
            }

            var swbTargetInfo = SWBLocalizationTargetInfo(compilableXCStringsPaths: Set(xcstrings), stringsdataPathsByBuildPortion: stringsdata, effectivePlatformName: payload.effectivePlatformName)

            var generatedFiles = [String: Set<String>]()
            for (xcstringsPath, generatedPaths) in payload.generatedSymbolFilesByXCStringsPath {
                generatedFiles[xcstringsPath.normalize().str] = Set(generatedPaths.map({ $0.normalize().str }))
            }
            swbTargetInfo.generatedSymbolFilesByXCStringsPath = generatedFiles

            targetInfos[payload.targetIdentifier] = swbTargetInfo
        }

        return SWBLocalizationInfo(infoByTarget: targetInfos)
    }

    /// Provides a list of target GUIDs that a build description covers. Providing a build description ID is a requirement.
    ///
    /// Note that the order of the list is non-deterministic.
    public func generateBuildDescriptionTargetInfo(for request: SWBBuildRequest) async throws -> SWBBuildDescriptionTargetInfo {
        guard let msg: StringListResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in BuildDescriptionTargetInfoRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation) }, delegate: nil) else {
            throw StubError.error("The build description target operation was cancelled.")
        }

        return SWBBuildDescriptionTargetInfo(targetGUIDs: msg.value)
    }

    public func dumpBuildDependencyInfo(for request: SWBBuildRequest, to outputPath: String) async throws {
        guard let _: VoidResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in DumpBuildDependencyInfoRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation, outputPath: outputPath) }, delegate: nil) else {
            throw StubError.error("The build dependency info dump was cancelled.")
        }

        return
    }

    public func generatePreviewInfo(for request: SWBBuildRequest, targetID: String, sourceFile: String, thunkVariantSuffix: String, delegate: any SWBPreviewDelegate) async throws -> [SWBPreviewInfo] {
        guard let msg: PreviewInfoResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in PreviewInfoRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation, targetID: targetID, sourceFile: Path(sourceFile), thunkVariantSuffix: thunkVariantSuffix) }, delegate: delegate) else {
            throw StubError.error("The preview info operation was cancelled.")
        }

        precondition([targetID] == msg.targetIDs)
        return try msg.output.map { try SWBPreviewInfo($0) }
    }

    @available(*, deprecated, renamed: "generatePreviewTargetDependencyInfo(for:targetIDs:delegate:)", message: "Use the method that takes multiple target IDs.")
    public func generatePreviewTargetDependencyInfo(
        for request: SWBBuildRequest,
        targetID: String,
        delegate: any SWBPreviewDelegate
    ) async throws -> [SWBPreviewTargetDependencyInfo] {
        try await generatePreviewTargetDependencyInfo(for: request, targetIDs: [targetID], delegate: delegate)
    }

    public func generatePreviewTargetDependencyInfo(
        for request: SWBBuildRequest,
        targetIDs: [String],
        delegate: any SWBPreviewDelegate
    ) async throws -> [SWBPreviewTargetDependencyInfo] {
        guard let msg: PreviewInfoResponse = try await handleDelegateMessageOnChannel(makeMessage: { [uid] in PreviewTargetDependencyInfoRequest(sessionHandle: uid, responseChannel: $0, request: request.messagePayloadRepresentation, targetIDs: targetIDs) }, delegate: delegate) else {
            throw StubError.error("The preview target dependency info operation was cancelled.")
        }

        precondition(targetIDs == msg.targetIDs)
        return try msg.output.map { try SWBPreviewTargetDependencyInfo($0) }
    }

    public func describeSchemes(input: [SWBSchemeInput]) async throws -> [SWBSchemeDescription] {
        let response: DescribeSchemesResponse = try await handleProductPlannerMessage(makeMessage: { DescribeSchemesRequest(sessionHandle: self.uid, responseChannel: $0, input: input.map(SchemeInput.init)) })
        return response.schemes.map(SWBSchemeDescription.init)
    }

    public func describeProducts(input: SWBActionInput, platformName: String) async throws -> [SWBProductDescription] {
        let response: DescribeProductsResponse = try await handleProductPlannerMessage(makeMessage: { DescribeProductsRequest(sessionHandle: self.uid, responseChannel: $0, input: .init(input), platformName: platformName) })
        return response.products.map(SWBProductDescription.init)
    }

    public func describeArchivableProducts(input: [SWBSchemeInput]) async throws -> [SWBProductTupleDescription] {
        let response: DescribeArchivableProductsResponse = try await handleProductPlannerMessage(makeMessage: { DescribeArchivableProductsRequest(sessionHandle: self.uid, responseChannel: $0, input: input.map(SchemeInput.init)) })
        return response.products.map(SWBProductTupleDescription.init)
    }

    public func computeDependencyClosure(targetGUIDs: [String], buildParameters: SWBBuildParameters, includeImplicitDependencies: Bool) async throws -> [String] {
        try await service.send(request: ComputeDependencyClosureRequest(sessionHandle: uid, targetGUIDs: targetGUIDs, buildParameters: buildParameters.messagePayloadRepresentation, includeImplicitDependencies: includeImplicitDependencies, dependencyScope: .workspace)).value
    }

    public func computeDependencyGraph(targetGUIDs: [SWBTargetGUID], buildParameters: SWBBuildParameters, includeImplicitDependencies: Bool) async throws -> [SWBTargetGUID: [SWBTargetGUID]] {
        var result: [SWBTargetGUID: [SWBTargetGUID]] = [:]
        for (key, value) in try await service.send(request: ComputeDependencyGraphRequest(sessionHandle: uid, targetGUIDs: targetGUIDs.map { TargetGUID(rawValue: $0.rawValue) }, buildParameters: buildParameters.messagePayloadRepresentation, includeImplicitDependencies: includeImplicitDependencies, dependencyScope: .workspace)).adjacencyList {
            result[SWBTargetGUID(rawValue: key.rawValue)] = value.map { SWBTargetGUID(rawValue: $0.rawValue) }
        }
        return result
    }

    public func computeDependencyClosure(targetGUIDs: [String], buildParameters: SWBBuildParameters, includeImplicitDependencies: Bool, dependencyScope: SWBDependencyScope) async throws -> [String] {
        try await service.send(request: ComputeDependencyClosureRequest(sessionHandle: uid, targetGUIDs: targetGUIDs, buildParameters: buildParameters.messagePayloadRepresentation, includeImplicitDependencies: includeImplicitDependencies, dependencyScope: dependencyScope.messagePayload)).value
    }

    public func computeDependencyGraph(targetGUIDs: [SWBTargetGUID], buildParameters: SWBBuildParameters, includeImplicitDependencies: Bool, dependencyScope: SWBDependencyScope) async throws -> [SWBTargetGUID: [SWBTargetGUID]] {
        var result: [SWBTargetGUID: [SWBTargetGUID]] = [:]
        for (key, value) in try await service.send(request: ComputeDependencyGraphRequest(sessionHandle: uid, targetGUIDs: targetGUIDs.map { TargetGUID(rawValue: $0.rawValue) }, buildParameters: buildParameters.messagePayloadRepresentation, includeImplicitDependencies: includeImplicitDependencies, dependencyScope: dependencyScope.messagePayload)).adjacencyList {
            result[SWBTargetGUID(rawValue: key.rawValue)] = value.map { SWBTargetGUID(rawValue: $0.rawValue) }
        }
        return result
    }


    // MARK: Macro evaluation


    // Helper methods to send a macro evaluation request and handle the response.

    /// Dispatch a macro evaluation request expecting a string response.
    private func sendMacroEvaluationStringRequest(_ session: SWBBuildServiceSession, _ request: MacroEvaluationRequest) async throws -> String {
        switch try await session.service.send(request: request).result {
        case .string(let value):
            return value
        case .stringList(let value):
            throw SWBMacroEvaluationError(message: "expected string but got string list '\(value)' for macro evaluation request: \(request.request)")
        case .error(let error):
            throw SWBMacroEvaluationError(message: error)
        }
    }

    /// Dispatch a macro evaluation request expecting a string list response.
    private func sendMacroEvaluationStringListRequest(_ session: SWBBuildServiceSession, _ request: MacroEvaluationRequest) async throws -> [String] {
        switch try await session.service.send(request: request).result {
        case .string(let value):
            throw SWBMacroEvaluationError(message: "expected string list but got string '\(value)' for macro evaluation request: \(request.request)")
        case .stringList(let value):
            return value
        case .error(let error):
            throw SWBMacroEvaluationError(message: error)
        }
    }


    // Public methods to perform macro evaluations for a level and build parameters.

    public func evaluateMacroAsString(_ macro: String, level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters, overrides: [String : String]?) async throws -> String {
        let parameters = buildParameters.messagePayloadRepresentation
        let request = MacroEvaluationRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters), request: .macro(macro), overrides: overrides, resultType: .string)
        return try await sendMacroEvaluationStringRequest(self, request)
    }

    public func evaluateMacroAsStringList(_ macro: String, level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters, overrides: [String : String]?) async throws -> [String] {
        let parameters = buildParameters.messagePayloadRepresentation
        let request = MacroEvaluationRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters), request: .macro(macro), overrides: overrides, resultType: .stringList)
        return try await sendMacroEvaluationStringListRequest(self, request)
    }

    public func evaluateMacroAsBoolean(_ macro: String, level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters, overrides: [String : String]?) async throws -> Bool {
        let parameters = buildParameters.messagePayloadRepresentation
        let request = MacroEvaluationRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters), request: .macro(macro), overrides: overrides, resultType: .string)
        return try await sendMacroEvaluationStringRequest(self, request) == "YES"
    }

    public func evaluateMacroExpressionAsString(_ expr: String, level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters, overrides: [String : String]?) async throws -> String {
        let parameters = buildParameters.messagePayloadRepresentation
        let request = MacroEvaluationRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters), request: .stringExpression(expr), overrides: overrides, resultType: .string)
        return try await sendMacroEvaluationStringRequest(self, request)
    }

    public func evaluateMacroExpressionAsStringList(_ expr: String, level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters, overrides: [String : String]?) async throws -> [String] {
        let parameters = buildParameters.messagePayloadRepresentation
        let request = MacroEvaluationRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters), request: .stringExpression(expr), overrides: overrides, resultType: .stringList)
        return try await sendMacroEvaluationStringListRequest(self, request)
    }

    public func evaluateMacroExpressionArrayAsStringList(_ expr: [String], level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters, overrides: [String : String]?) async throws -> [String] {
        let parameters = buildParameters.messagePayloadRepresentation
        let request = MacroEvaluationRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters), request: .stringExpressionArray(expr), overrides: overrides, resultType: .stringList)
        return try await sendMacroEvaluationStringListRequest(self, request)
    }


    // MARK: Other methods to get information about macros

    public func allExportedMacroNamesAndValues(level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters) async throws -> [String : String] {
        let parameters = buildParameters.messagePayloadRepresentation
        return try await service.send(request: AllExportedMacrosAndValuesRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters))).result
    }


    // MARK: Build settings editor support


    public func buildSettingsEditorInfo(level: SWBMacroEvaluationLevel, buildParameters: SWBBuildParameters) async throws -> SWBBuildSettingsEditorInfo {
        let parameters = buildParameters.messagePayloadRepresentation
        return SWBBuildSettingsEditorInfo(from: try await service.send(request: BuildSettingsEditorInfoRequest(sessionHandle: uid, context: .components(level: level.toSWBRequest, buildParameters: parameters))).result)
    }


    /// Loads the workspace at the specified container path.
    ///
    /// The container path may be:
    /// - An Xcode workspace (.xcworkspace)
    /// - An Xcode project (.xcodeproj)
    /// - Path to a directory containing a Package.swift file
    /// - A JSON-PIF document (.json or .pif)
    public func loadWorkspace(containerPath: String) async throws {
        _ = try await service.send(request: SetSessionWorkspaceContainerPathRequest(sessionHandle: uid, containerPath: containerPath))
    }

    /// Send a PIF property list.
    public func sendPIF(_ pif: SWBPropertyListItem) async throws {
        let pifContents = try pif.propertyListItem.asJSONFragment().bytes
        _ = try await service.send(request: SetSessionPIFRequest(sessionHandle: uid, pifContents: pifContents))
    }

    /// Send a PIF property list incrementally to the service.
    ///
    /// The service will call back to the client to look up the PIF data for each object that the service does not have and requires to be transferred, and this method will be called to send that object to the service.
    ///
    /// - parameter workspaceSignature: The signature of the workspace to send.
    /// - parameter auditPIF: if not nil, this should be a complete PIF which is sent to the service to compare against the PIF the service has received from us, the client.
    /// - parameter lookup: A closure for looking up additional objects to send (called on an unspecified thread).
    public func sendPIF(workspaceSignature: String, auditPIF: SWBPropertyListItem? = nil, lookupObject lookup: @Sendable @escaping (SwiftBuildServicePIFObjectType, String) async throws -> SWBPropertyListItem) async throws {
        var message: any IncrementalPIFMessage = TransferSessionPIFRequest(sessionHandle: self.uid, workspaceSignature: workspaceSignature)
        var hasSentAuditRequest = false

        while true {
            let asPIFResponse = try await service.send(request: message)

            // Check if the transfer is complete.
            if asPIFResponse.isComplete {
                return
            }

            if let auditPIF, !hasSentAuditRequest {
                // If we have an audit PIF and haven't sent it yet, do that first, before sending missing objects.
                // FIXME: The way this currently works, if the service thinks it already has a complete PIF but is wrong, then it will never call back to us to send the audit PIF. We should find a way to send it the audit PIF no matter what it thinks.
                let pifContents = try auditPIF.propertyListItem.asJSONFragment().bytes
                message = AuditSessionPIFRequest(sessionHandle: self.uid, pifContents: pifContents)
                hasSentAuditRequest = true
            } else {
                message = await constructIncrementalPIFReply(asPIFResponse, lookup)
            }
        }
    }

    /// Construct reply for incremental PIF transfer based on the PIF transfer session response.
    private func constructIncrementalPIFReply(_ pifResponse: TransferSessionPIFResponse, _ lookup: @Sendable @escaping (SwiftBuildServicePIFObjectType, String) async throws -> SWBPropertyListItem) async -> any IncrementalPIFMessage {
        do {
            return try await withThrowingTaskGroup(of: [UInt8].self) { group in
                for missingObject in pifResponse.missingObjects {
                    group.addTask {
                        let object = try await lookup(SwiftBuildServicePIFObjectType(missingObject.type), missingObject.signature)
                        return try object.propertyListItem.asJSONFragment().bytes
                    }
                }

                return try await TransferSessionPIFObjectsLegacyRequest(sessionHandle: self.uid, objects: group.reduce(into: [], { $0.append($1) }))
            }
        } catch {
            return IncrementalPIFLookupFailureRequest(sessionHandle: self.uid, diagnostic: "\(error)")
        }
    }

    // FIXME: Get stronger typing in the lookup method.
    //
    /// Transfer a workspace via PIF from the client, incrementally.
    ///
    /// This method will call back to the client to look up the PIF data for each object that the service does not have and requires to be transferred.
    ///
    /// - parameter workspaceSignature: The signature of the workspace to send.
    /// - parameter lookup: A closure for looking up additional objects to send (called on an unspecified thread).
    private func transfer(workspaceSignature: String, lookupObject lookup: @escaping (SwiftBuildServicePIFObjectType, String) -> any Serializable) async throws {
        var message: any IncrementalPIFMessage = TransferSessionPIFRequest(sessionHandle: self.uid, workspaceSignature: workspaceSignature)

        while true {
            let asPIFResponse = try await service.send(request: message)

            // Check if the transfer is complete.
            if asPIFResponse.isComplete {
                return
            }

            // Otherwise, send all of the missing objects.
            var additionalObjects: [TransferSessionPIFObjectsRequest.ObjectData] = []
            for missingObject in asPIFResponse.missingObjects {
                let object = lookup(SwiftBuildServicePIFObjectType(missingObject.type), missingObject.signature)
                // FIXME: This is wasteful, we should be able to just encode it all in one go.
                let serializer = MsgPackSerializer()
                object.serialize(to: serializer)
                additionalObjects.append(.init(pifType: missingObject.type, signature: missingObject.signature, data: serializer.byteString))
            }
            message = TransferSessionPIFObjectsRequest(sessionHandle: self.uid, objects: additionalObjects)
        }
    }

    public func workspaceInfo() async throws -> SWBWorkspaceInfo {
        try await SWBWorkspaceInfo(service.send(request: WorkspaceInfoRequest(sessionHandle: uid)).workspaceInfo)
    }

    /// Get the `DEVELOPER_DIR` path.
    public func developerPath() async throws -> String {
        try await service.send(request: DeveloperPathRequest(sessionHandle: uid)).value
    }

    /// Set the session system information.
    public func setSystemInfo(_ systemInfo: SWBSystemInfo) async throws {
        _ = try await service.send(request: SetSessionSystemInfoRequest(sessionHandle: uid, operatingSystemVersion: Version(systemInfo.operatingSystemVersion), productBuildVersion: systemInfo.productBuildVersion, nativeArchitecture: systemInfo.nativeArchitecture))
    }

    /// Set the session user information.
    public func setUserInfo(_ userInfo: SWBUserInfo) async throws {
        _ = try await service.send(request: SetSessionUserInfoRequest(sessionHandle: self.uid, user: userInfo.userName, group: userInfo.groupName, uid: userInfo.uid, gid: userInfo.gid, home: userInfo.homeDirectory, processEnvironment: userInfo.processEnvironment, buildSystemEnvironment: userInfo.buildSystemEnvironment))
    }

    @available(*, deprecated, renamed: "setUserPreferences(enableDebugActivityLogs:enableBuildDebugging:enableBuildSystemCaching:activityTextShorteningLevel:usePerConfigurationBuildLocations:allowsExternalToolExecution:)")
    public func setUserPreferences(enableDebugActivityLogs: Bool, enableBuildDebugging: Bool, enableBuildSystemCaching: Bool, activityTextShorteningLevel: Int, usePerConfigurationBuildLocations: Bool?) async throws {
        _ = try await service.send(request: SetSessionUserPreferencesRequest(sessionHandle: self.uid, enableDebugActivityLogs: enableDebugActivityLogs, enableBuildDebugging: enableBuildDebugging, enableBuildSystemCaching: enableBuildSystemCaching, activityTextShorteningLevel: ActivityTextShorteningLevel(rawValue: activityTextShorteningLevel) ?? .default, usePerConfigurationBuildLocations: usePerConfigurationBuildLocations))
    }

    public func setUserPreferences(enableDebugActivityLogs: Bool, enableBuildDebugging: Bool, enableBuildSystemCaching: Bool, activityTextShorteningLevel: Int, usePerConfigurationBuildLocations: Bool?, allowsExternalToolExecution: Bool) async throws {
        _ = try await service.send(request: SetSessionUserPreferencesRequest(sessionHandle: self.uid, enableDebugActivityLogs: enableDebugActivityLogs, enableBuildDebugging: enableBuildDebugging, enableBuildSystemCaching: enableBuildSystemCaching, activityTextShorteningLevel: ActivityTextShorteningLevel(rawValue: activityTextShorteningLevel) ?? .default, usePerConfigurationBuildLocations: usePerConfigurationBuildLocations, allowsExternalToolExecution: allowsExternalToolExecution))
    }
}

extension SWBBuildServiceSession {
    private func handleDelegateMessageOnChannel<T: Message>(makeMessage: @Sendable @escaping (UInt64) -> any SessionChannelMessage, delegate: (any SWBPlanningOperationDelegate)?) async throws -> T? {
        let replyChannel = service.openChannel()

        // Creates a session message using the `makeMessage` closure and send it on the provided channel.
        let response = await replyChannel.send(makeMessage)
        switch response {
        case is VoidResponse:
            // Request was received successfully, continue on to handle the channel messages.
            break
        default:
            throw StubError.error("Unexpected message: \(response)")
        }

        // Enumerate the infinite stream of channel reply messages; we'll return once we receive a message of our expected type or some other message type indicating a failure or cancellation.
        for await msgOpt in replyChannel.stream {
            switch msgOpt {
            case let msg as T:
                return msg
            case is VoidResponse:
                return nil
            case let msg as ErrorResponse:
                throw StubError.error(msg.description)
            case is PlanningOperationWillStart, is PlanningOperationDidFinish:
                continue
            case let msg as GetProvisioningTaskInputsRequest:
                await handle(message: msg, session: self, delegate: delegate)
                continue
            case let msg as ExternalToolExecutionRequest:
                await handle(message: msg, session: self, delegate: delegate)
                continue
            default:
                break
            }

            throw StubError.error("Unexpected message: \(msgOpt.map(String.init(describing:)) ?? "<nil>")")
        }

        // NOTE: If any GetProvisioningTaskInputsRequest/ExternalToolExecutionRequest messages are received, cancellation may result in them never receiving a reply, which could result in background operations getting stuck. We should find a way to handle that better.

        // nil indicates Task cancellation (we broke out of the loop)
        return nil
    }

    private func handleProductPlannerMessage<T: Message>(makeMessage: @Sendable @escaping (UInt64) -> any SessionChannelMessage) async throws -> T {
        guard let msg: T = try await handleDelegateMessageOnChannel(makeMessage: makeMessage, delegate: nil) else {
            throw StubError.error("The describe operation was cancelled.")
        }
        return msg
    }
}

// MARK: Convenience extensions for marshalling build parameters

// FIXME: We have moved the base objects to Swift, but we need to do more work to be able to use the native Swift objects here -- we can't simply expect them to be re-exported from SwiftBuild.framework, since they live in a separate module we do not want to install.

extension SWBBuildTaskStyle {
    var messagePayloadRepresentation: BuildTaskStyleMessagePayload {
        switch self {
        case .buildOnly:
            return .buildOnly
        case .buildAndRun:
            return .buildAndRun
        }
    }
}

extension SWBBuildLocationStyle {
    var messagePayloadRepresentation: BuildLocationStyleMessagePayload {
        switch self {
        case .regular:
            return .regular
        case .legacy:
            return .legacy
        }
    }
}

extension SWBPreviewStyle {
    var messagePayloadRepresentation: PreviewStyleMessagePayload {
        switch self {
        case .dynamicReplacement:
            return .dynamicReplacement
        case .xojit:
            return .xojit
        }
    }
}

extension SWBBuildCommand {
    var messagePayloadRepresentation: BuildCommandMessagePayload {
        switch command {
        case let .build(style, skipDependencies):
            return .build(style: style.messagePayloadRepresentation, skipDependencies: skipDependencies)
        case let .buildFiles(paths: paths, action: action):
            switch action {
            case .compile:
                return .singleFileBuild(buildOnlyTheseFiles: paths)
            case .preprocess:
                return .generatePreprocessedFile(buildOnlyTheseFiles: paths)
            case .assemble:
                return .generateAssemblyCode(buildOnlyTheseFiles: paths)
            }
        case let .prepareForIndexing(buildOnlyTheseTargets, enableIndexBuildArena):
            return .prepareForIndexing(buildOnlyTheseTargets: buildOnlyTheseTargets, enableIndexBuildArena: enableIndexBuildArena)
        case .migrate:
            return .migrate
        case let .cleanBuildFolder(style):
            return .cleanBuildFolder(style: style.messagePayloadRepresentation)
        case let .preview(style):
            return .preview(style: style.messagePayloadRepresentation)
        }
    }
}

extension SWBSchemeCommand {
    var messagePayloadRepresentation: SchemeCommandMessagePayload {
        switch self {
        case .archive:
            return .archive
        case .launch:
            return .launch
        case .profile:
            return .profile
        case .test:
            return .test
        }
    }
}

extension SWBBuildQoS {
    var messagePayloadRepresentation: BuildQoSMessagePayload {
        switch self {
        case .background:
            return .background
        case .utility:
            return .utility
        case .default:
            return .default
        case .userInitiated:
            return .userInitiated
        }
    }
}

extension SWBBuildRequest {
    var messagePayloadRepresentation: BuildRequestMessagePayload {
        return BuildRequestMessagePayload(parameters: parameters.messagePayloadRepresentation, configuredTargets: configuredTargets.map{ $0.messagePayloadRepresentation }, dependencyScope: dependencyScope.messagePayload, continueBuildingAfterErrors: continueBuildingAfterErrors, hideShellScriptEnvironment: hideShellScriptEnvironment, useParallelTargets: useParallelTargets, useImplicitDependencies: useImplicitDependencies, useDryRun: useDryRun, showNonLoggedProgress: showNonLoggedProgress, recordBuildBacktraces: recordBuildBacktraces, generatePrecompiledModulesReport: generatePrecompiledModulesReport, buildPlanDiagnosticsDirPath: buildPlanDiagnosticsDirPath.map(Path.init), buildCommand: buildCommand.messagePayloadRepresentation, schemeCommand: schemeCommand?.messagePayloadRepresentation, containerPath: containerPath.map(Path.init), buildDescriptionID: buildDescriptionID, qos: qos?.messagePayloadRepresentation, jsonRepresentation: try? jsonData())
    }
}

extension SWBConfiguredTarget {
    var messagePayloadRepresentation: ConfiguredTargetMessagePayload {
        return ConfiguredTargetMessagePayload(guid: guid, parameters: parameters?.messagePayloadRepresentation)
    }
}

extension SWBBuildParameters {
    var messagePayloadRepresentation: BuildParametersMessagePayload {
        let overridesPayload = overrides.messagePayloadRepresentation
        return BuildParametersMessagePayload(action: action ?? "", configuration: configurationName, activeRunDestination: activeRunDestination.flatMap{RunDestinationInfo($0)}, activeArchitecture: activeArchitecture, arenaInfo: arenaInfo.flatMap{ArenaInfo($0)}, overrides: overridesPayload)
    }
}

fileprivate extension RunDestinationInfo {
    init(_ x: SWBRunDestinationInfo) {
        self.init(platform: x.platform, sdk: x.sdk, sdkVariant: x.sdkVariant, targetArchitecture: x.targetArchitecture, supportedArchitectures: OrderedSet(x.supportedArchitectures), disableOnlyActiveArch: x.disableOnlyActiveArch, hostTargetedPlatform: x.hostTargetedPlatform)
    }
}

fileprivate extension ArenaInfo {
    init(_ x: SWBArenaInfo) {
        self.init(derivedDataPath: Path(x.derivedDataPath), buildProductsPath: Path(x.buildProductsPath), buildIntermediatesPath: Path(x.buildIntermediatesPath), pchPath: Path(x.pchPath), indexRegularBuildProductsPath: x.indexRegularBuildProductsPath.map(Path.init), indexRegularBuildIntermediatesPath: x.indexRegularBuildIntermediatesPath.map(Path.init), indexPCHPath: Path(x.indexPCHPath), indexDataStoreFolderPath: x.indexDataStoreFolderPath.map(Path.init), indexEnableDataStore: x.indexEnableDataStore)
    }
}

fileprivate extension ActionInput {
    init(_ x: SWBActionInput) {
        self.init(configurationName: x.configurationName, targetIdentifiers: x.targetIdentifiers)
    }
}

fileprivate extension SchemeInput {
    init(_ x: SWBSchemeInput) {
        self.init(name: x.name, isShared: x.isShared, isAutogenerated: x.isAutogenerated, analyze: .init(x.analyze), archive: .init(x.archive), profile: .init(x.profile), run: .init(x.run), test: .init(x.test))
    }
}

fileprivate extension SWBActionInfo {
    init(_ x: ActionInfo) {
        self.init(configurationName: x.configurationName, products: x.products.map { .init($0) }, testPlans: x.testPlans?.map { .init($0) })
    }
}

fileprivate extension SWBActionsInfo {
    init(_ x: ActionsInfo) {
        self.init(analyze: .init(x.analyze), archive: .init(x.archive), profile: .init(x.profile), run: .init(x.run), test: .init(x.test))
    }
}

fileprivate extension SWBDestinationInfo {
    init(_ x: DestinationInfo) {
        self.init(platformName: x.platformName, isSimulator: x.isSimulator)
    }
}

fileprivate extension SWBProductInfo {
    init(_ x: ProductInfo) {
        self.init(displayName: x.displayName, identifier: x.identifier, supportedDestinations: x.supportedDestinations.map { .init($0) })
    }
}

fileprivate extension SWBTestPlanInfo {
    init(_ x: TestPlanInfo) {
        self.init(displayName: x.displayName)
    }
}

fileprivate extension SWBProductDescription {
    init(_ x: ProductDescription) {
        self.init(displayName: x.displayName, productName: x.productName, identifier: x.identifier, productType: .init(x.productType), dependencies: x.dependencies?.map { .init($0) }, bundleIdentifier: x.bundleIdentifier, targetedDeviceFamilies: x.targetedDeviceFamilies?.map { .init($0) }, deploymentTarget: x.deploymentTarget.description, marketingVersion: x.marketingVersion, buildVersion: x.buildVersion, enableBitcode: x.enableBitcode, codesign: x.codesign.map { .init($0) }, team: x.team, infoPlistPath: x.infoPlistPath, iconPath: x.iconPath)
    }
}

fileprivate extension SWBProductDescription.CodesignMode {
    init(_ x: ProductDescription.CodesignMode) {
        switch x {
        case .automatic:
            self = .automatic
        case .manual:
            self = .manual
        case let .unknown(value):
            self = .unknown(value)
        }
    }
}

fileprivate extension SWBProductDescription.DeviceFamily {
    init(_ x: ProductDescription.DeviceFamily) {
        switch x {
        case .appleTV:
            self = .appleTV
        case .appleWatch:
            self = .appleWatch
        case .iPad:
            self = .iPad
        case .iPhone:
            self = .iPhone
        case let .unknown(value):
            self = .unknown(value)
        }
    }
}

fileprivate extension SWBProductDescription.ProductType {
    init(_ x: ProductDescription.ProductType) {
        switch x {
        case .app:
            self = .app
        case .appex:
            self = .appex
        case .library:
            self = .library
        case .none:
            self = .none
        case .tests:
            self = .tests
        case .tool:
            self = .tool
        case let .unknown(value):
            self = .unknown(value)
        }
    }
}

fileprivate extension SWBProductTupleDescription {
    init(_ x: ProductTupleDescription) {
        self.init(displayName: x.displayName, productName: x.productName, productType: .init(x.productType), identifier: x.identifier, team: x.team, bundleIdentifier: x.bundleIdentifier, destination: .init(x.destination), containingSchemes: x.containingSchemes, iconPath: x.iconPath)
    }
}

fileprivate extension SWBSchemeDescription {
    init(_ x: SchemeDescription) {
        self.init(name: x.name, disambiguatedName: x.disambiguatedName, isShared: x.isShared, isAutogenerated: x.isAutogenerated, actions: .init(x.actions))
    }
}

fileprivate extension SWBPreviewInfo {
    init(_ previewInfo: PreviewInfoMessagePayload) throws {
        self.sdkRoot = previewInfo.context.sdkRoot
        self.sdkVariant = previewInfo.context.sdkVariant
        self.buildVariant = previewInfo.context.buildVariant
        self.architecture = previewInfo.context.architecture
        self.pifGUID = previewInfo.context.pifGUID
        switch previewInfo.kind {
        case let .thunkInfo(previewInfo):
            self.compileCommandLine = previewInfo.compileCommandLine
            self.linkCommandLine = previewInfo.linkCommandLine
            self.thunkSourceFile = previewInfo.thunkSourceFile.str
            self.thunkObjectFile = previewInfo.thunkObjectFile.str
            self.thunkLibrary = previewInfo.thunkLibrary.str
        case .targetDependencyInfo:
            throw StubError.error("Unexpected response type for request")
        }
    }
}

fileprivate extension SWBPreviewTargetDependencyInfo {
    init(_ previewInfo: PreviewInfoMessagePayload) throws {
        self.sdkRoot = previewInfo.context.sdkRoot
        self.sdkVariant = previewInfo.context.sdkVariant
        self.buildVariant = previewInfo.context.buildVariant
        self.architecture = previewInfo.context.architecture
        self.pifGUID = previewInfo.context.pifGUID
        switch previewInfo.kind {
        case .thunkInfo:
            throw StubError.error("Unexpected response type for request")
        case let .targetDependencyInfo(targetDependencyInfo):
            self.objectFileInputMap = targetDependencyInfo.objectFileInputMap
            self.linkCommandLine = targetDependencyInfo.linkCommandLine
            self.linkerWorkingDirectory = targetDependencyInfo.linkerWorkingDirectory
            self.swiftEnableOpaqueTypeErasure = targetDependencyInfo.swiftEnableOpaqueTypeErasure
            self.swiftUseIntegratedDriver = targetDependencyInfo.swiftUseIntegratedDriver
            self.enableJITPreviews = targetDependencyInfo.enableJITPreviews
            self.enableDebugDylib = targetDependencyInfo.enableDebugDylib
            self.enableAddressSanitizer = targetDependencyInfo.enableAddressSanitizer
            self.enableThreadSanitizer = targetDependencyInfo.enableThreadSanitizer
            self.enableUndefinedBehaviorSanitizer = targetDependencyInfo.enableUndefinedBehaviorSanitizer
        }
    }
}

extension SWBSettingsOverrides {
    var messagePayloadRepresentation: SettingsOverridesMessagePayload {
        return SettingsOverridesMessagePayload(synthesized: synthesized?.table ?? [:], commandLine: commandLine?.table ?? [:], commandLineConfigPath: commandLineConfigPath.map(Path.init), commandLineConfig: commandLineConfig?.table ?? [:], environmentConfigPath: environmentConfigPath.map(Path.init), environmentConfig: environmentConfig?.table ?? [:], toolchainOverride: toolchainOverride)
    }
}

extension SwiftBuildServicePIFObjectType {
    init(_ type: PIFObjectType) {
        switch type {
        case .workspace:
            self = .workspace
        case .project:
            self = .project
        case .target:
            self = .target
        }
    }
}