Skip to content

Commit 42c47b4

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 42c47b4

File tree

5 files changed

+81
-0
lines changed

5 files changed

+81
-0
lines changed

Sources/SWBTaskConstruction/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ add_library(SWBTaskConstruction
5151
TaskProducers/StandardTaskProducer.swift
5252
TaskProducers/TaskProducer.swift
5353
TaskProducers/TaskProducerExtensionPoint.swift
54+
TaskProducers/WorkspaceTaskProducers/CASConfigFileTaskProducer.swift
5455
TaskProducers/WorkspaceTaskProducers/BuildDependencyInfoTaskProducer.swift
5556
TaskProducers/WorkspaceTaskProducers/CreateBuildDirectoryTaskProducer.swift
5657
TaskProducers/WorkspaceTaskProducers/HeadermapVFSTaskProducer.swift

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

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)