Skip to content

Commit 6cf3009

Browse files
[CompilationCaching] Write .cas-config file
Write a .cas-config file when compilation caching is enabled. This allows tools that might not be orchestrated by the build system directly but might need to read CAS to find the CAS by searching for the configuration file. rdar://166398186
1 parent a94b27b commit 6cf3009

File tree

4 files changed

+87
-0
lines changed

4 files changed

+87
-0
lines changed

Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ private struct WorkspaceProductPlanBuilder {
8282
HeadermapVFSTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
8383
PCHModuleMapTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
8484
BuildDependencyInfoTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
85+
CASConfigFileTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
8586
] + (globalProductPlan.planRequest.buildRequest.enableIndexBuildArena ? [IndexBuildVFSDirectoryRemapTaskProducer(context: globalTaskProducerContext)] : [])
8687

8788
for taskProducerExtension in await taskProducerExtensions(globalTaskProducerContext.workspaceContext) {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// File.swift
3+
// SwiftBuild
4+
//
5+
// Created by Steven Wu on 12/13/25.
6+
//
7+
8+
//===----------------------------------------------------------------------===//
9+
//
10+
// This source file is part of the Swift open source project
11+
//
12+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
13+
// Licensed under Apache License v2.0 with Runtime Library Exception
14+
//
15+
// See http://swift.org/LICENSE.txt for license information
16+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
import SWBCore
21+
import SWBUtil
22+
import SWBMacro
23+
import Foundation
24+
import SWBProtocol
25+
26+
final class CASConfigFileTaskProducer: StandardTaskProducer, TaskProducer {
27+
private let targetContexts: [TaskProducerContext]
28+
29+
init(context globalContext: TaskProducerContext, targetContexts: [TaskProducerContext]) {
30+
self.targetContexts = targetContexts
31+
super.init(globalContext)
32+
}
33+
34+
func generateTasks() async -> [any SWBCore.PlannedTask] {
35+
var tasks = [any PlannedTask]()
36+
do {
37+
let casConfigFiles = try Dictionary(try await targetContexts.concurrentMap(maximumParallelism: 100) { (targetContext: TaskProducerContext) async throws -> (Path, ByteString)? in
38+
let scope = targetContext.settings.globalScope
39+
40+
// If compilation caching is not on, then there is no file to write.
41+
// The condition here is more relax than the actual check in the compile task generation
42+
// since it won't hurt if the file is not used.
43+
guard scope.evaluate(BuiltinMacros.CLANG_ENABLE_COMPILE_CACHE) || scope.evaluate(BuiltinMacros.SWIFT_ENABLE_COMPILE_CACHE) else {
44+
return nil
45+
}
46+
47+
// FIXME: we need consistent CAS configuration across all languages.
48+
if !scope.evaluate(BuiltinMacros.COMPILATION_CACHE_REMOTE_SERVICE_PATH).isEmpty && !scope.evaluate(BuiltinMacros.COMPILATION_CACHE_REMOTE_SUPPORTED_LANGUAGES).isEmpty {
49+
return nil
50+
}
51+
52+
let casOpts = try CASOptions.create(scope, .compiler(.other(dialectName: "swift")))
53+
struct CASConfig: Encodable {
54+
let CASPath: String
55+
let PluginPath: String?
56+
}
57+
let content = try JSONEncoder().encode(CASConfig(CASPath: casOpts.casPath.str, PluginPath: casOpts.pluginPath?.str))
58+
let path = scope.evaluate(BuiltinMacros.TARGET_TEMP_DIR).join(".cas-config")
59+
return (path, ByteString(content))
60+
}.compactMap { $0 }, uniquingKeysWith: { first, second in
61+
guard first == second else {
62+
throw StubError.error("Unexpected difference in CAS config file.\nPath: \(first.asString)\nContent:\(second.asString)")
63+
}
64+
return first
65+
})
66+
67+
for (configFilePath, configFileContent) in casConfigFiles {
68+
await appendGeneratedTasks(&tasks) { delegate in
69+
context.writeFileSpec.constructFileTasks(CommandBuildContext(producer: context, scope: context.settings.globalScope, inputs: [], output: configFilePath), delegate, contents: configFileContent, permissions: nil, preparesForIndexing: true, additionalTaskOrderingOptions: [.immediate])
70+
}
71+
}
72+
} catch {
73+
self.context.error(error.localizedDescription)
74+
}
75+
return tasks
76+
}
77+
}

Tests/SWBBuildSystemTests/ClangCompilationCachingTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ fileprivate struct ClangCompilationCachingTests: CoreBasedTests {
246246
}
247247
#expect(try readMetrics("one") == #"{"global":{"clangCacheHits":0,"clangCacheMisses":1,"swiftCacheHits":0,"swiftCacheMisses":0},"tasks":{"CompileC":{"cacheMisses":1,"headerDependenciesNotValidatedTasks":1,"moduleDependenciesNotValidatedTasks":1}}}"#)
248248

249+
let CASConfigPath = tmpDirPath.join("Test/aProject/build/aProject.build/Debug\(runDestination == .macOS ? "": "-" + runDestination.platform)/Library.build/.cas-config")
250+
251+
#expect(try tester.fs.read(CASConfigPath).asString.contains("\"CASPath\":"))
252+
if usePlugin {
253+
#expect(try tester.fs.read(CASConfigPath).asString.contains("\"PluginPath\":"))
254+
}
255+
249256
// Touch the source file to trigger a new scan.
250257
try await tester.fs.updateTimestamp(testWorkspace.sourceRoot.join("aProject/file.c"))
251258

Tests/SWBBuildSystemTests/SwiftCompilationCachingTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ fileprivate struct SwiftCompilationCachingTests: CoreBasedTests {
116116
}
117117
#expect(try readMetrics("one").contains("\"swiftCacheHits\":0,\"swiftCacheMisses\":\(numCompile)"))
118118

119+
#expect(try tester.fs.read(tmpDirPath.join("Test/aProject/build/aProject.build/Debug-iphoneos/Application.build/.cas-config")).asString.contains("\"CASPath\":"))
120+
119121
// touch a file, clean build folder, and rebuild.
120122
try await tester.fs.updateTimestamp(testWorkspace.sourceRoot.join("aProject/App.swift"))
121123
try await tester.checkBuild(runDestination: .anyiOSDevice, buildCommand: .cleanBuildFolder(style: .regular), body: { _ in })

0 commit comments

Comments
 (0)