@@ -90,6 +90,9 @@ package final class BuildDescriptionManager: Sendable {
90
90
/// The in-memory cache of build descriptions.
91
91
private let inMemoryCachedBuildDescriptions : HeavyCache < BuildDescriptionSignature , BuildDescription >
92
92
93
+ /// Build descriptions explicitly retained by clients.
94
+ private let retainedBuildDescriptions : Registry < BuildDescriptionSignature , ( BuildDescription , UInt ) > = . init( )
95
+
93
96
/// The last build plan request. Used to generate a diff of the current plan for debugging purposes.
94
97
private let lastBuildPlanRequest : SWBMutex < BuildPlanRequest ? > = . init( nil )
95
98
@@ -254,35 +257,44 @@ package final class BuildDescriptionManager: Sendable {
254
257
/// During normal operation (outside of tests), this should always be called on `queue`.
255
258
package enum BuildDescriptionRequest {
256
259
/// Retrieve or create a build description based on a build plan.
257
- case newOrCached( BuildPlanRequest , bypassActualTasks: Bool , useSynchronousBuildDescriptionSerialization: Bool )
260
+ case newOrCached( BuildPlanRequest , bypassActualTasks: Bool , useSynchronousBuildDescriptionSerialization: Bool , retain : Bool )
258
261
/// Retrieve an existing build description, build planning has been avoided. If the build description is not available then `getNewOrCachedBuildDescription` will fail.
259
- case cachedOnly( BuildDescriptionID , request: BuildRequest , buildRequestContext: BuildRequestContext , workspaceContext: WorkspaceContext )
262
+ case cachedOnly( BuildDescriptionID , request: BuildRequest , buildRequestContext: BuildRequestContext , workspaceContext: WorkspaceContext , retain : Bool )
260
263
261
264
var buildRequest : BuildRequest {
262
265
switch self {
263
- case . newOrCached( let planRequest, _, _) : return planRequest. buildRequest
264
- case . cachedOnly( _, let request, _, _) : return request
266
+ case . newOrCached( let planRequest, _, _, _ ) : return planRequest. buildRequest
267
+ case . cachedOnly( _, let request, _, _, _ ) : return request
265
268
}
266
269
}
267
270
268
271
var buildRequestContext : BuildRequestContext {
269
272
switch self {
270
- case . newOrCached( let planRequest, _, _) : return planRequest. buildRequestContext
271
- case . cachedOnly( _, _, let buildRequestContext, _) : return buildRequestContext
273
+ case . newOrCached( let planRequest, _, _, _ ) : return planRequest. buildRequestContext
274
+ case . cachedOnly( _, _, let buildRequestContext, _, _ ) : return buildRequestContext
272
275
}
273
276
}
274
277
275
278
var planRequest : BuildPlanRequest ? {
276
279
switch self {
277
- case . newOrCached( let planRequest, _, _) : return planRequest
280
+ case . newOrCached( let planRequest, _, _, _ ) : return planRequest
278
281
case . cachedOnly: return nil
279
282
}
280
283
}
281
284
282
285
var workspaceContext : WorkspaceContext {
283
286
switch self {
284
- case . newOrCached( let planRequest, _, _) : return planRequest. workspaceContext
285
- case . cachedOnly( _, _, _, let workspaceContext) : return workspaceContext
287
+ case . newOrCached( let planRequest, _, _, _) : return planRequest. workspaceContext
288
+ case . cachedOnly( _, _, _, let workspaceContext, _) : return workspaceContext
289
+ }
290
+ }
291
+
292
+ var retain : Bool {
293
+ switch self {
294
+ case . newOrCached( _, _, _, let retain) :
295
+ retain
296
+ case . cachedOnly( _, _, _, _, let retain) :
297
+ retain
286
298
}
287
299
}
288
300
@@ -305,8 +317,8 @@ package final class BuildDescriptionManager: Sendable {
305
317
306
318
func signature( cacheDir: Path ) throws -> BuildDescriptionSignature {
307
319
switch self {
308
- case . newOrCached( let planRequest, _, _) : return try BuildDescriptionSignature . buildDescriptionSignature ( planRequest, cacheDir: cacheDir)
309
- case . cachedOnly( let buildDescriptionID, _, _, _) : return BuildDescriptionSignature . buildDescriptionSignature ( buildDescriptionID)
320
+ case . newOrCached( let planRequest, _, _, _ ) : return try BuildDescriptionSignature . buildDescriptionSignature ( planRequest, cacheDir: cacheDir)
321
+ case . cachedOnly( let buildDescriptionID, _, _, _, _ ) : return BuildDescriptionSignature . buildDescriptionSignature ( buildDescriptionID)
310
322
}
311
323
}
312
324
}
@@ -317,6 +329,8 @@ package final class BuildDescriptionManager: Sendable {
317
329
description = lastDescription
318
330
} else if let inMemoryDescription = inMemoryCachedBuildDescriptions [ signature] {
319
331
description = inMemoryDescription
332
+ } else if let retainedDescription = retainedBuildDescriptions [ signature] {
333
+ description = retainedDescription. 0
320
334
} else {
321
335
description = nil
322
336
}
@@ -397,18 +411,35 @@ package final class BuildDescriptionManager: Sendable {
397
411
}
398
412
}
399
413
414
+ if request. retain {
415
+ retainedBuildDescriptions. update ( signature, update: { ( $0. 0 , $0. 1 + 1 ) } , default: { ( buildDescription, 0 ) } )
416
+ }
417
+
400
418
return BuildDescriptionRetrievalInfo ( buildDescription: buildDescription, source: source, inMemoryCacheSize: inMemoryCachedBuildDescriptions. count, onDiskCachePath: buildDescriptionPath)
401
419
}
402
420
403
421
/// Returns a build description for a particular workspace and request.
404
422
///
405
423
/// - Returns: A build description, or nil if cancelled.
406
- package func getBuildDescription( _ request: BuildPlanRequest , bypassActualTasks: Bool = false , useSynchronousBuildDescriptionSerialization: Bool = false , clientDelegate: any TaskPlanningClientDelegate , constructionDelegate: any BuildDescriptionConstructionDelegate ) async throws -> BuildDescription ? {
407
- let descRequest = BuildDescriptionRequest . newOrCached ( request, bypassActualTasks: bypassActualTasks, useSynchronousBuildDescriptionSerialization: useSynchronousBuildDescriptionSerialization)
424
+ package func getBuildDescription( _ request: BuildPlanRequest , bypassActualTasks: Bool = false , useSynchronousBuildDescriptionSerialization: Bool = false , retained : Bool , clientDelegate: any TaskPlanningClientDelegate , constructionDelegate: any BuildDescriptionConstructionDelegate ) async throws -> BuildDescription ? {
425
+ let descRequest = BuildDescriptionRequest . newOrCached ( request, bypassActualTasks: bypassActualTasks, useSynchronousBuildDescriptionSerialization: useSynchronousBuildDescriptionSerialization, retain : retained )
408
426
let retrievalInfo = try await getNewOrCachedBuildDescription ( descRequest, clientDelegate: clientDelegate, constructionDelegate: constructionDelegate)
409
427
return retrievalInfo? . buildDescription
410
428
}
411
429
430
+ package func releaseBuildDescription( id: BuildDescriptionID ) {
431
+ self . retainedBuildDescriptions. update ( BuildDescriptionSignature . buildDescriptionSignature ( id) , update: {
432
+ let newCount = $0. 1 - 1
433
+ if newCount == 0 {
434
+ return nil
435
+ } else {
436
+ return ( $0. 0 , newCount)
437
+ }
438
+ } , default: {
439
+ nil
440
+ } )
441
+ }
442
+
412
443
/// Returns the path in which the`XCBuildData` directory will live. That location is uses to cache build descriptions for a particular workspace and request, the manifest, and the `build.db` database for llbuild.
413
444
package static func cacheDirectory( _ request: BuildPlanRequest ) throws -> Path {
414
445
return try cacheDirectory ( request. buildRequest, buildRequestContext: request. buildRequestContext, workspaceContext: request. workspaceContext)
@@ -514,7 +545,7 @@ package final class BuildDescriptionManager: Sendable {
514
545
}
515
546
516
547
// Unable to load from disk, create a new description
517
- guard case let . newOrCached( request, bypassActualTasks, useSynchronousBuildDescriptionSerialization) = request else {
548
+ guard case let . newOrCached( request, bypassActualTasks, useSynchronousBuildDescriptionSerialization, _ ) = request else {
518
549
preconditionFailure ( " entered build construction path but request was for existing cached description " )
519
550
}
520
551
0 commit comments